mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Chore: Update Angular to v17 (#4980)
This commit is contained in:
parent
b1f6f52486
commit
a6248bec2d
@ -94,7 +94,7 @@ The following files need to be changed:
|
||||
|
||||
- src-ui/angular.json (under the _projects/paperless-ui/i18n/locales_ JSON key)
|
||||
- src/paperless/settings.py (in the _LANGUAGES_ array)
|
||||
- src-ui/src/app/services/settings.service.ts (inside the _getLanguageOptions_ method)
|
||||
- src-ui/src/app/services/settings.service.ts (inside the _LANGUAGE_OPTIONS_ array)
|
||||
- src-ui/src/app/app.module.ts (import locale from _angular/common/locales_ and call _registerLocaleData_)
|
||||
|
||||
Please add the language in the correct order, alphabetically by locale.
|
||||
|
@ -12,7 +12,7 @@ COPY ./src-ui /src/src-ui
|
||||
WORKDIR /src/src-ui
|
||||
RUN set -eux \
|
||||
&& npm update npm -g \
|
||||
&& npm ci --omit=optional
|
||||
&& npm ci
|
||||
RUN set -eux \
|
||||
&& ./node_modules/.bin/ng build --configuration production
|
||||
|
||||
|
@ -277,27 +277,17 @@ Adding new languages requires adding the translated files in the
|
||||
}
|
||||
```
|
||||
|
||||
2. Add the language to the available options in
|
||||
2. Add the language to the `LANGUAGE_OPTIONS` array in
|
||||
`src-ui/src/app/services/settings.service.ts`:
|
||||
|
||||
```typescript
|
||||
getLanguageOptions(): LanguageOption[] {
|
||||
return [
|
||||
{code: "en-us", name: $localize`English (US)`, englishName: "English (US)", dateInputFormat: "mm/dd/yyyy"},
|
||||
{code: "en-gb", name: $localize`English (GB)`, englishName: "English (GB)", dateInputFormat: "dd/mm/yyyy"},
|
||||
{code: "de", name: $localize`German`, englishName: "German", dateInputFormat: "dd.mm.yyyy"},
|
||||
{code: "nl", name: $localize`Dutch`, englishName: "Dutch", dateInputFormat: "dd-mm-yyyy"},
|
||||
{code: "fr", name: $localize`French`, englishName: "French", dateInputFormat: "dd/mm/yyyy"},
|
||||
{code: "pt-br", name: $localize`Portuguese (Brazil)`, englishName: "Portuguese (Brazil)", dateInputFormat: "dd/mm/yyyy"}
|
||||
// Add your new language here
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`dateInputFormat` is a special string that defines the behavior of
|
||||
the date input fields and absolutely needs to contain "dd", "mm"
|
||||
and "yyyy".
|
||||
|
||||
```
|
||||
|
||||
3. Import and register the Angular data for this locale in
|
||||
`src-ui/src/app/app.module.ts`:
|
||||
|
||||
|
@ -125,18 +125,18 @@
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "paperless-ui:build:en-US"
|
||||
"buildTarget": "paperless-ui:build:en-US"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "paperless-ui:build:production"
|
||||
"buildTarget": "paperless-ui:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "paperless-ui:build"
|
||||
"buildTarget": "paperless-ui:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
|
7697
src-ui/package-lock.json
generated
7697
src-ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,56 +11,56 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/cdk": "^16.2.11",
|
||||
"@angular/common": "~16.2.11",
|
||||
"@angular/compiler": "~16.2.11",
|
||||
"@angular/core": "~16.2.11",
|
||||
"@angular/forms": "~16.2.11",
|
||||
"@angular/localize": "~16.2.11",
|
||||
"@angular/platform-browser": "~16.2.11",
|
||||
"@angular/platform-browser-dynamic": "~16.2.11",
|
||||
"@angular/router": "~16.2.11",
|
||||
"@ng-bootstrap/ng-bootstrap": "^15.1.2",
|
||||
"@ng-select/ng-select": "^11.2.0",
|
||||
"@angular/cdk": "^17.0.4",
|
||||
"@angular/common": "~17.0.7",
|
||||
"@angular/compiler": "~17.0.7",
|
||||
"@angular/core": "~17.0.7",
|
||||
"@angular/forms": "~17.0.7",
|
||||
"@angular/localize": "~17.0.7",
|
||||
"@angular/platform-browser": "~17.0.7",
|
||||
"@angular/platform-browser-dynamic": "~17.0.7",
|
||||
"@angular/router": "~17.0.7",
|
||||
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
|
||||
"@ng-select/ng-select": "^12.0.4",
|
||||
"@ngneat/dirty-check-forms": "^3.0.3",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "^5.3.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"mime-names": "^1.0.0",
|
||||
"ngx-color": "^9.0.0",
|
||||
"ngx-cookie-service": "^16.0.1",
|
||||
"ngx-cookie-service": "^17.0.1",
|
||||
"ngx-file-drop": "^16.0.0",
|
||||
"ngx-ui-tour-ng-bootstrap": "^13.0.6",
|
||||
"ngx-ui-tour-ng-bootstrap": "^14.0.1",
|
||||
"pdfjs-dist": "^3.11.174",
|
||||
"rxjs": "^7.8.1",
|
||||
"tslib": "^2.6.2",
|
||||
"uuid": "^9.0.1",
|
||||
"zone.js": "^0.13.3"
|
||||
"zone.js": "^0.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-builders/jest": "16.0.1",
|
||||
"@angular-devkit/build-angular": "~16.2.9",
|
||||
"@angular-eslint/builder": "16.2.0",
|
||||
"@angular-eslint/eslint-plugin": "16.2.0",
|
||||
"@angular-eslint/eslint-plugin-template": "16.2.0",
|
||||
"@angular-eslint/schematics": "16.2.0",
|
||||
"@angular-eslint/template-parser": "16.2.0",
|
||||
"@angular/cli": "~16.2.9",
|
||||
"@angular/compiler-cli": "~16.2.3",
|
||||
"@angular-builders/jest": "17.0.0",
|
||||
"@angular-devkit/build-angular": "~17.0.7",
|
||||
"@angular-eslint/builder": "17.1.1",
|
||||
"@angular-eslint/eslint-plugin": "17.1.1",
|
||||
"@angular-eslint/eslint-plugin-template": "17.1.1",
|
||||
"@angular-eslint/schematics": "17.1.1",
|
||||
"@angular-eslint/template-parser": "17.1.1",
|
||||
"@angular/cli": "~17.0.7",
|
||||
"@angular/compiler-cli": "~17.0.7",
|
||||
"@playwright/test": "^1.40.1",
|
||||
"@types/jest": "^29.5.10",
|
||||
"@types/node": "^20.10.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.13.1",
|
||||
"@typescript-eslint/parser": "^6.13.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||
"@typescript-eslint/parser": "^6.10.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint": "^8.53.0",
|
||||
"jest": "29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-preset-angular": "^13.1.4",
|
||||
"jest-websocket-mock": "^2.5.0",
|
||||
"patch-package": "^8.0.0",
|
||||
"ts-node": "~10.9.1",
|
||||
"typescript": "^5.1.6",
|
||||
"typescript": "^5.2.2",
|
||||
"wait-on": "^7.2.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,36 @@
|
||||
<pngx-toasts></pngx-toasts>
|
||||
|
||||
<pngx-file-drop>
|
||||
<ng-container content>
|
||||
<router-outlet></router-outlet>
|
||||
</ng-container>
|
||||
<ng-container content>
|
||||
<router-outlet></router-outlet>
|
||||
</ng-container>
|
||||
</pngx-file-drop>
|
||||
|
||||
<tour-step-template>
|
||||
<ng-template #tourStep let-step="step">
|
||||
<p class="tour-step-content" [innerHTML]="step?.content"></p>
|
||||
<hr/>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="badge bg-light text-dark">{{ tourService.steps?.indexOf(step) + 1 }} / {{ tourService.steps?.length }}</span>
|
||||
<div class="tour-step-navigation btn-toolbar" role="toolbar" aria-label="Controls">
|
||||
<div class="btn-group btn-group-sm me-2" role="group" aria-label="Dismiss">
|
||||
<button class="btn btn-outline-danger" (click)="tourService.end()">
|
||||
{{ step?.endBtnTitle }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm align-self-end" role="group" aria-label="Previous / Next">
|
||||
<button *ngIf="tourService.hasPrev(step)" class="btn btn-outline-primary" (click)="tourService.prev()">
|
||||
« {{ step?.prevBtnTitle }}
|
||||
</button>
|
||||
<button *ngIf="tourService.hasNext(step)" class="btn btn-outline-primary" (click)="tourService.next()">
|
||||
{{ step?.nextBtnTitle }} »
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #tourStep let-step="step">
|
||||
<p class="tour-step-content" [innerHTML]="step?.content"></p>
|
||||
<hr/>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="badge bg-light text-dark">{{ tourService.steps?.indexOf(step) + 1 }} / {{ tourService.steps?.length }}</span>
|
||||
<div class="tour-step-navigation btn-toolbar" role="toolbar" aria-label="Controls">
|
||||
<div class="btn-group btn-group-sm me-2" role="group" aria-label="Dismiss">
|
||||
<button class="btn btn-outline-danger" (click)="tourService.end()">
|
||||
{{ step?.endBtnTitle }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="btn-group btn-group-sm align-self-end" role="group" aria-label="Previous / Next">
|
||||
@if (tourService.hasPrev(step)) {
|
||||
<button class="btn btn-outline-primary" (click)="tourService.prev()">
|
||||
« {{ step?.prevBtnTitle }}
|
||||
</button>
|
||||
}
|
||||
@if (tourService.hasNext(step)) {
|
||||
<button class="btn btn-outline-primary" (click)="tourService.next()">
|
||||
{{ step?.nextBtnTitle }} »
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</tour-step-template>
|
||||
|
@ -6,25 +6,35 @@
|
||||
</pngx-page-header>
|
||||
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeLog" (activeIdChange)="reloadLogs()" class="nav-tabs">
|
||||
<li *ngFor="let logFile of logFiles" [ngbNavItem]="logFile">
|
||||
<a ngbNavLink>
|
||||
{{logFile}}.log
|
||||
</a>
|
||||
</li>
|
||||
<div *ngIf="isLoading || !logFiles.length" class="ps-2 d-flex align-items-center">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container *ngIf="!logFiles.length" i18n>Loading...</ng-container>
|
||||
</div>
|
||||
@for (logFile of logFiles; track logFile) {
|
||||
<li [ngbNavItem]="logFile">
|
||||
<a ngbNavLink>
|
||||
{{logFile}}.log
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
@if (isLoading || !logFiles.length) {
|
||||
<div class="ps-2 d-flex align-items-center">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
@if (!logFiles.length) {
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ul>
|
||||
|
||||
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
||||
|
||||
<div class="bg-dark p-3 text-light font-monospace log-container" #logContainer>
|
||||
<div *ngIf="isLoading && logFiles.length">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</div>
|
||||
<p
|
||||
class="m-0 p-0 log-entry-{{getLogLevel(log)}}"
|
||||
*ngFor="let log of logs">{{log}}</p>
|
||||
@if (isLoading && logFiles.length) {
|
||||
<div>
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</div>
|
||||
}
|
||||
@for (log of logs; track log) {
|
||||
<p
|
||||
class="m-0 p-0 log-entry-{{getLogLevel(log)}}"
|
||||
>{{log}}</p>
|
||||
}
|
||||
</div>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<pngx-page-header title="Settings" i18n-title>
|
||||
<button class="btn btn-sm btn-outline-primary" (click)="tourService.start()"><ng-container i18n>Start tour</ng-container></button>
|
||||
<a *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }" class="btn btn-sm btn-primary ms-3" href="admin/" target="_blank">
|
||||
<ng-container i18n>Open Django Admin</ng-container>
|
||||
<svg class="sidebaricon ms-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-up-right"/>
|
||||
</svg>
|
||||
<ng-container i18n>Open Django Admin</ng-container>
|
||||
<svg class="sidebaricon ms-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-up-right"/>
|
||||
</svg>
|
||||
</a>
|
||||
</pngx-page-header>
|
||||
|
||||
@ -24,10 +24,16 @@
|
||||
<div class="col">
|
||||
|
||||
<select class="form-select" formControlName="displayLanguage">
|
||||
<option *ngFor="let lang of displayLanguageOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code && currentLocale !== 'en-US'"> - {{lang.englishName}}</span></option>
|
||||
@for (lang of displayLanguageOptions; track lang) {
|
||||
<option [ngValue]="lang.code">{{lang.name}}@if (lang.code && currentLocale !== 'en-US') {
|
||||
<span> - {{lang.englishName}}</span>
|
||||
}</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
<small *ngIf="displayLanguageIsDirty" class="form-text text-primary" i18n>You need to reload the page after applying a new language.</small>
|
||||
@if (displayLanguageIsDirty) {
|
||||
<small class="form-text text-primary" i18n>You need to reload the page after applying a new language.</small>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -39,7 +45,11 @@
|
||||
<div class="col">
|
||||
|
||||
<select class="form-select" formControlName="dateLocale">
|
||||
<option *ngFor="let lang of dateLocaleOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code"> - {{today | customDate:'shortDate':null:lang.code}}</span></option>
|
||||
@for (lang of dateLocaleOptions; track lang) {
|
||||
<option [ngValue]="lang.code">{{lang.name}}@if (lang.code) {
|
||||
<span> - {{today | customDate:'shortDate':null:lang.code}}</span>
|
||||
}</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
</div>
|
||||
@ -127,198 +137,202 @@
|
||||
<button class="btn btn-link btn-sm pt-2 ps-0" [disabled]="!this.settingsForm.get('themeColor').value" (click)="clearThemeColor()">
|
||||
<svg fill="currentColor" class="buttonicon-sm me-1">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg><ng-container i18n>Reset</ng-container>
|
||||
</button>
|
||||
</svg><ng-container i18n>Reset</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="mt-4" id="update-checking" i18n>Update checking</h4>
|
||||
<h4 class="mt-4" id="update-checking" i18n>Update checking</h4>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="offset-md-3 col">
|
||||
<p i18n>
|
||||
<div class="row mb-3">
|
||||
<div class="offset-md-3 col">
|
||||
<p i18n>
|
||||
Update checking works by pinging the public <a href="https://api.github.com/repos/paperless-ngx/paperless-ngx/releases/latest" target="_blank" rel="noopener noreferrer">GitHub API</a> for the latest release to determine whether a new version is available.<br/>
|
||||
Actual updating of the app must still be performed manually.
|
||||
</p>
|
||||
<p i18n>
|
||||
<p i18n>
|
||||
<em>No tracking data is collected by the app in any way.</em>
|
||||
</p>
|
||||
<pngx-input-check i18n-title title="Enable update checking" formControlName="updateCheckingEnabled"></pngx-input-check>
|
||||
<pngx-input-check i18n-title title="Enable update checking" formControlName="updateCheckingEnabled"></pngx-input-check>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="mt-4" i18n>Bulk editing</h4>
|
||||
<h4 class="mt-4" i18n>Bulk editing</h4>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="offset-md-3 col">
|
||||
<pngx-input-check i18n-title title="Show confirmation dialogs" formControlName="bulkEditConfirmationDialogs" i18n-hint hint="Deleting documents will always ask for confirmation."></pngx-input-check>
|
||||
<pngx-input-check i18n-title title="Apply on close" formControlName="bulkEditApplyOnClose"></pngx-input-check>
|
||||
<div class="row mb-3">
|
||||
<div class="offset-md-3 col">
|
||||
<pngx-input-check i18n-title title="Show confirmation dialogs" formControlName="bulkEditConfirmationDialogs" i18n-hint hint="Deleting documents will always ask for confirmation."></pngx-input-check>
|
||||
<pngx-input-check i18n-title title="Apply on close" formControlName="bulkEditApplyOnClose"></pngx-input-check>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="mt-4" i18n>Notes</h4>
|
||||
<h4 class="mt-4" i18n>Notes</h4>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="offset-md-3 col">
|
||||
<pngx-input-check i18n-title title="Enable notes" formControlName="notesEnabled"></pngx-input-check>
|
||||
<div class="row mb-3">
|
||||
<div class="offset-md-3 col">
|
||||
<pngx-input-check i18n-title title="Enable notes" formControlName="notesEnabled"></pngx-input-check>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ng-template>
|
||||
</li>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="SettingsNavIDs.Permissions">
|
||||
<a ngbNavLink i18n>Permissions</a>
|
||||
<ng-template ngbNavContent>
|
||||
<li [ngbNavItem]="SettingsNavIDs.Permissions">
|
||||
<a ngbNavLink i18n>Permissions</a>
|
||||
<ng-template ngbNavContent>
|
||||
|
||||
<h4 i18n>Default Permissions</h4>
|
||||
<h4 i18n>Default Permissions</h4>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="offset-md-3 col">
|
||||
<p i18n>
|
||||
<div class="row mb-3">
|
||||
<div class="offset-md-3 col">
|
||||
<p i18n>
|
||||
Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3 col-form-label pt-0">
|
||||
<span i18n>Default Owner</span>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<pngx-input-select [items]="users" bindLabel="username" formControlName="defaultPermsOwner" [allowNull]="true"></pngx-input-select>
|
||||
<small class="form-text text-muted text-end d-block mt-n2" i18n>Objects without an owner can be viewed and edited by all users</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3 col-form-label pt-0">
|
||||
<span i18n>Default View Permissions</span>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<span class="d-block pt-1" i18n>Users:</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }">
|
||||
<pngx-permissions-user type="view" formControlName="defaultPermsViewUsers"></pngx-permissions-user>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<span class="d-block pt-1" i18n>Groups:</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Group }">
|
||||
<pngx-permissions-group type="view" formControlName="defaultPermsViewGroups"></pngx-permissions-group>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3 col-form-label pt-0">
|
||||
<span i18n>Default Edit Permissions</span>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<span class="d-block pt-1" i18n>Users:</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }">
|
||||
<pngx-permissions-user type="view" formControlName="defaultPermsEditUsers"></pngx-permissions-user>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3 col-form-label pt-0">
|
||||
<span i18n>Default Owner</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<span class="d-block pt-1" i18n>Groups:</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Group }">
|
||||
<pngx-permissions-group type="view" formControlName="defaultPermsEditGroups"></pngx-permissions-group>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<small class="form-text text-muted text-end d-block" i18n>Edit permissions also grant viewing permissions</small>
|
||||
<div class="col-md-5">
|
||||
<pngx-input-select [items]="users" bindLabel="username" formControlName="defaultPermsOwner" [allowNull]="true"></pngx-input-select>
|
||||
<small class="form-text text-muted text-end d-block mt-n2" i18n>Objects without an owner can be viewed and edited by all users</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="SettingsNavIDs.Notifications">
|
||||
<a ngbNavLink i18n>Notifications</a>
|
||||
<ng-template ngbNavContent>
|
||||
|
||||
<h4 i18n>Document processing</h4>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="offset-md-3 col">
|
||||
<pngx-input-check i18n-title title="Show notifications when new documents are detected" formControlName="notificationsConsumerNewDocument"></pngx-input-check>
|
||||
<pngx-input-check i18n-title title="Show notifications when document processing completes successfully" formControlName="notificationsConsumerSuccess"></pngx-input-check>
|
||||
<pngx-input-check i18n-title title="Show notifications when document processing fails" formControlName="notificationsConsumerFailed"></pngx-input-check>
|
||||
<pngx-input-check i18n-title title="Suppress notifications on dashboard" formControlName="notificationsConsumerSuppressOnDashboard" i18n-hint hint="This will suppress all messages about document processing status on the dashboard."></pngx-input-check>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="SettingsNavIDs.SavedViews">
|
||||
<a ngbNavLink i18n>Saved views</a>
|
||||
<ng-template ngbNavContent>
|
||||
|
||||
<h4 i18n>Settings</h4>
|
||||
<div class="row mb-3">
|
||||
<div class="offset-md-3 col">
|
||||
<pngx-input-check i18n-title title="Show warning when closing saved views with unsaved changes" formControlName="savedViewsWarnOnUnsavedChange"></pngx-input-check>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 i18n>Views</h4>
|
||||
<div formGroupName="savedViews">
|
||||
|
||||
<div *ngFor="let view of savedViews" [formGroupName]="view.id" class="row">
|
||||
<div class="mb-3 col">
|
||||
<label class="form-label" for="name_{{view.id}}" i18n>Name</label>
|
||||
<input type="text" class="form-control" formControlName="name" id="name_{{view.id}}">
|
||||
</div>
|
||||
|
||||
<div class="mb-2 col">
|
||||
<label class="form-label" for="show_on_dashboard_{{view.id}}" i18n> <span class="visually-hidden">Appears on</span></label>
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="show_on_dashboard_{{view.id}}" formControlName="show_on_dashboard">
|
||||
<label class="form-check-label" for="show_on_dashboard_{{view.id}}" i18n>Show on dashboard</label>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3 col-form-label pt-0">
|
||||
<span i18n>Default View Permissions</span>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<span class="d-block pt-1" i18n>Users:</span>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="show_in_sidebar_{{view.id}}" formControlName="show_in_sidebar">
|
||||
<label class="form-check-label" for="show_in_sidebar_{{view.id}}" i18n>Show in sidebar</label>
|
||||
<div class="col">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }">
|
||||
<pngx-permissions-user type="view" formControlName="defaultPermsViewUsers"></pngx-permissions-user>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2 col-auto">
|
||||
<label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger form-control" (click)="deleteSavedView(view)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.SavedView }" i18n>Delete</button>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<span class="d-block pt-1" i18n>Groups:</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Group }">
|
||||
<pngx-permissions-group type="view" formControlName="defaultPermsViewGroups"></pngx-permissions-group>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="savedViews && savedViews.length === 0" i18n>No saved views defined.</div>
|
||||
|
||||
<div *ngIf="!savedViews">
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3 col-form-label pt-0">
|
||||
<span i18n>Default Edit Permissions</span>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<span class="d-block pt-1" i18n>Users:</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }">
|
||||
<pngx-permissions-user type="view" formControlName="defaultPermsEditUsers"></pngx-permissions-user>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<span class="d-block pt-1" i18n>Groups:</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Group }">
|
||||
<pngx-permissions-group type="view" formControlName="defaultPermsEditGroups"></pngx-permissions-group>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<small class="form-text text-muted text-end d-block" i18n>Edit permissions also grant viewing permissions</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
</div>
|
||||
<li [ngbNavItem]="SettingsNavIDs.Notifications">
|
||||
<a ngbNavLink i18n>Notifications</a>
|
||||
<ng-template ngbNavContent>
|
||||
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
<h4 i18n>Document processing</h4>
|
||||
|
||||
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
|
||||
<div class="row mb-3">
|
||||
<div class="offset-md-3 col">
|
||||
<pngx-input-check i18n-title title="Show notifications when new documents are detected" formControlName="notificationsConsumerNewDocument"></pngx-input-check>
|
||||
<pngx-input-check i18n-title title="Show notifications when document processing completes successfully" formControlName="notificationsConsumerSuccess"></pngx-input-check>
|
||||
<pngx-input-check i18n-title title="Show notifications when document processing fails" formControlName="notificationsConsumerFailed"></pngx-input-check>
|
||||
<pngx-input-check i18n-title title="Suppress notifications on dashboard" formControlName="notificationsConsumerSuppressOnDashboard" i18n-hint hint="This will suppress all messages about document processing status on the dashboard."></pngx-input-check>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mb-2" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
|
||||
</form>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="SettingsNavIDs.SavedViews">
|
||||
<a ngbNavLink i18n>Saved views</a>
|
||||
<ng-template ngbNavContent>
|
||||
|
||||
<h4 i18n>Settings</h4>
|
||||
<div class="row mb-3">
|
||||
<div class="offset-md-3 col">
|
||||
<pngx-input-check i18n-title title="Show warning when closing saved views with unsaved changes" formControlName="savedViewsWarnOnUnsavedChange"></pngx-input-check>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 i18n>Views</h4>
|
||||
<div formGroupName="savedViews">
|
||||
|
||||
@for (view of savedViews; track view) {
|
||||
<div [formGroupName]="view.id" class="row">
|
||||
<div class="mb-3 col">
|
||||
<label class="form-label" for="name_{{view.id}}" i18n>Name</label>
|
||||
<input type="text" class="form-control" formControlName="name" id="name_{{view.id}}">
|
||||
</div>
|
||||
<div class="mb-2 col">
|
||||
<label class="form-label" for="show_on_dashboard_{{view.id}}" i18n> <span class="visually-hidden">Appears on</span></label>
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="show_on_dashboard_{{view.id}}" formControlName="show_on_dashboard">
|
||||
<label class="form-check-label" for="show_on_dashboard_{{view.id}}" i18n>Show on dashboard</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="show_in_sidebar_{{view.id}}" formControlName="show_in_sidebar">
|
||||
<label class="form-check-label" for="show_in_sidebar_{{view.id}}" i18n>Show in sidebar</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2 col-auto">
|
||||
<label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger form-control" (click)="deleteSavedView(view)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.SavedView }" i18n>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (savedViews && savedViews.length === 0) {
|
||||
<div i18n>No saved views defined.</div>
|
||||
}
|
||||
|
||||
@if (!savedViews) {
|
||||
<div>
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mb-2" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
|
||||
</form>
|
||||
|
@ -48,6 +48,12 @@ enum SettingsNavIDs {
|
||||
SavedViews = 4,
|
||||
}
|
||||
|
||||
const systemLanguage = { code: '', name: $localize`Use system language` }
|
||||
const systemDateFormat = {
|
||||
code: '',
|
||||
name: $localize`Use date format of display language`,
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-settings',
|
||||
templateUrl: './settings.component.html',
|
||||
@ -512,15 +518,11 @@ export class SettingsComponent
|
||||
}
|
||||
|
||||
get displayLanguageOptions(): LanguageOption[] {
|
||||
return [{ code: '', name: $localize`Use system language` }].concat(
|
||||
this.settings.getLanguageOptions()
|
||||
)
|
||||
return [systemLanguage].concat(this.settings.getLanguageOptions())
|
||||
}
|
||||
|
||||
get dateLocaleOptions(): LanguageOption[] {
|
||||
return [
|
||||
{ code: '', name: $localize`Use date format of display language` },
|
||||
].concat(this.settings.getDateLocaleOptions())
|
||||
return [systemDateFormat].concat(this.settings.getDateLocaleOptions())
|
||||
}
|
||||
|
||||
get today() {
|
||||
|
@ -3,127 +3,153 @@
|
||||
<button class="btn btn-sm btn-outline-secondary me-2" (click)="clearSelection()" [hidden]="selectedTasks.size === 0">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Clear selection</ng-container>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary me-4" (click)="dismissTasks()" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }" [disabled]="tasksService.total === 0">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check2-all"/>
|
||||
</svg> <ng-container i18n>{{dismissButtonText}}</ng-container>
|
||||
</button>
|
||||
<div class="form-check form-switch mb-0" (click)="toggleAutoRefresh()">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch" [attr.checked]="autoRefreshInterval">
|
||||
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
|
||||
</div>
|
||||
</div>
|
||||
</pngx-page-header>
|
||||
</svg> <ng-container i18n>Clear selection</ng-container>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary me-4" (click)="dismissTasks()" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }" [disabled]="tasksService.total === 0">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check2-all"/>
|
||||
</svg> <ng-container i18n>{{dismissButtonText}}</ng-container>
|
||||
</button>
|
||||
<div class="form-check form-switch mb-0" (click)="toggleAutoRefresh()">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch" [attr.checked]="autoRefreshInterval">
|
||||
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
|
||||
</div>
|
||||
</div>
|
||||
</pngx-page-header>
|
||||
|
||||
<ng-container *ngIf="!tasksService.completedFileTasks && tasksService.loading">
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
</ng-container>
|
||||
@if (!tasksService.completedFileTasks && tasksService.loading) {
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
}
|
||||
|
||||
<ng-template let-tasks="tasks" #tasksTemplate>
|
||||
<table class="table table-striped align-middle border shadow-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="all-tasks" [disabled]="currentTasks.length === 0" (click)="toggleAll($event); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="all-tasks"></label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" i18n>Name</th>
|
||||
<th scope="col" class="d-none d-lg-table-cell" i18n>Created</th>
|
||||
<th scope="col" class="d-none d-lg-table-cell" *ngIf="activeTab !== 'started' && activeTab !== 'queued'" i18n>Results</th>
|
||||
<th scope="col" class="d-table-cell d-lg-none" i18n>Info</th>
|
||||
<th scope="col" i18n>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let task of tasks | slice: (page-1) * pageSize : page * pageSize">
|
||||
<tr (click)="toggleSelected(task, $event); $event.stopPropagation();">
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="task{{task.id}}" [checked]="selectedTasks.has(task.id)" (click)="toggleSelected(task, $event); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="task{{task.id}}"></label>
|
||||
</div>
|
||||
</td>
|
||||
<td class="overflow-auto name-col">{{ task.task_file_name }}</td>
|
||||
<td class="d-none d-lg-table-cell">{{ task.date_created | customDate:'short' }}</td>
|
||||
<td class="d-none d-lg-table-cell" *ngIf="activeTab !== 'started' && activeTab !== 'queued'">
|
||||
<div *ngIf="task.result?.length > 50" class="result" (click)="expandTask(task); $event.stopPropagation();"
|
||||
[ngbPopover]="resultPopover" popoverClass="shadow small mobile" triggers="mouseenter:mouseleave" container="body">
|
||||
<span class="small d-none d-md-inline-block font-monospace text-muted">{{ task.result | slice:0:50 }}…</span>
|
||||
</div>
|
||||
<span *ngIf="task.result?.length <= 50" class="small d-none d-md-inline-block font-monospace text-muted">{{ task.result }}</span>
|
||||
<ng-template #resultPopover>
|
||||
<pre class="small mb-0">{{ task.result | slice:0:300 }}<ng-container *ngIf="task.result.length > 300">…</ng-container></pre>
|
||||
<ng-container *ngIf="task.result?.length > 300"><br/><em>(<ng-container i18n>click for full output</ng-container>)</em></ng-container>
|
||||
</ng-template>
|
||||
</td>
|
||||
<td class="d-lg-none">
|
||||
<button class="btn btn-link" (click)="expandTask(task); $event.stopPropagation();">
|
||||
<svg fill="currentColor" class="" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#info-circle" />
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
<td scope="row">
|
||||
<div class="btn-group" role="group">
|
||||
<button class="btn btn-sm btn-outline-secondary" (click)="dismissTask(task); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg> <ng-container i18n>Dismiss</ng-container>
|
||||
</button>
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||
<button *ngIf="task.related_document" class="btn btn-sm btn-outline-primary" (click)="dismissAndGo(task); $event.stopPropagation();">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
|
||||
</svg> <ng-container i18n>Open Document</ng-container>
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-0" [class.border-0]="expandedTask !== task.id" colspan="5">
|
||||
<pre #collapse="ngbCollapse" [ngbCollapse]="expandedTask !== task.id" class="small mb-0"><div class="small p-1 p-lg-3 ms-lg-3">{{ task.result }}</div></pre>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
<ng-template let-tasks="tasks" #tasksTemplate>
|
||||
<table class="table table-striped align-middle border shadow-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="all-tasks" [disabled]="currentTasks.length === 0" (click)="toggleAll($event); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="all-tasks"></label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" i18n>Name</th>
|
||||
<th scope="col" class="d-none d-lg-table-cell" i18n>Created</th>
|
||||
@if (activeTab !== 'started' && activeTab !== 'queued') {
|
||||
<th scope="col" class="d-none d-lg-table-cell" i18n>Results</th>
|
||||
}
|
||||
<th scope="col" class="d-table-cell d-lg-none" i18n>Info</th>
|
||||
<th scope="col" i18n>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (task of tasks | slice: (page-1) * pageSize : page * pageSize; track task) {
|
||||
<tr (click)="toggleSelected(task, $event); $event.stopPropagation();">
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="task{{task.id}}" [checked]="selectedTasks.has(task.id)" (click)="toggleSelected(task, $event); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="task{{task.id}}"></label>
|
||||
</div>
|
||||
</td>
|
||||
<td class="overflow-auto name-col">{{ task.task_file_name }}</td>
|
||||
<td class="d-none d-lg-table-cell">{{ task.date_created | customDate:'short' }}</td>
|
||||
@if (activeTab !== 'started' && activeTab !== 'queued') {
|
||||
<td class="d-none d-lg-table-cell">
|
||||
@if (task.result?.length > 50) {
|
||||
<div class="result" (click)="expandTask(task); $event.stopPropagation();"
|
||||
[ngbPopover]="resultPopover" popoverClass="shadow small mobile" triggers="mouseenter:mouseleave" container="body">
|
||||
<span class="small d-none d-md-inline-block font-monospace text-muted">{{ task.result | slice:0:50 }}…</span>
|
||||
</div>
|
||||
}
|
||||
@if (task.result?.length <= 50) {
|
||||
<span class="small d-none d-md-inline-block font-monospace text-muted">{{ task.result }}</span>
|
||||
}
|
||||
<ng-template #resultPopover>
|
||||
<pre class="small mb-0">{{ task.result | slice:0:300 }}@if (task.result.length > 300) {
|
||||
…
|
||||
}</pre>
|
||||
@if (task.result?.length > 300) {
|
||||
<br/><em>(<ng-container i18n>click for full output</ng-container>)</em>
|
||||
}
|
||||
</ng-template>
|
||||
</td>
|
||||
}
|
||||
<td class="d-lg-none">
|
||||
<button class="btn btn-link" (click)="expandTask(task); $event.stopPropagation();">
|
||||
<svg fill="currentColor" class="" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#info-circle" />
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
<td scope="row">
|
||||
<div class="btn-group" role="group">
|
||||
<button class="btn btn-sm btn-outline-secondary" (click)="dismissTask(task); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg> <ng-container i18n>Dismiss</ng-container>
|
||||
</button>
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||
@if (task.related_document) {
|
||||
<button class="btn btn-sm btn-outline-primary" (click)="dismissAndGo(task); $event.stopPropagation();">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
|
||||
</svg> <ng-container i18n>Open Document</ng-container>
|
||||
</button>
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-0" [class.border-0]="expandedTask !== task.id" colspan="5">
|
||||
<pre #collapse="ngbCollapse" [ngbCollapse]="expandedTask !== task.id" class="small mb-0"><div class="small p-1 p-lg-3 ms-lg-3">{{ task.result }}</div></pre>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pb-3 d-sm-flex justify-content-between align-items-center">
|
||||
<div class="pb-2 pb-sm-0" i18n *ngIf="tasks.length > 0">{tasks.length, plural, =1 {One {{this.activeTabLocalized}} task} other {{{tasks.length || 0}} total {{this.activeTabLocalized}} tasks}}</div>
|
||||
<ngb-pagination *ngIf="tasks.length > pageSize" [(page)]="page" [pageSize]="pageSize" [collectionSize]="tasks.length" maxSize="8" size="sm"></ngb-pagination>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="pb-3 d-sm-flex justify-content-between align-items-center">
|
||||
@if (tasks.length > 0) {
|
||||
<div class="pb-2 pb-sm-0" i18n>{tasks.length, plural, =1 {One {{this.activeTabLocalized}} task} other {{{tasks.length || 0}} total {{this.activeTabLocalized}} tasks}}</div>
|
||||
}
|
||||
@if (tasks.length > pageSize) {
|
||||
<ngb-pagination [(page)]="page" [pageSize]="pageSize" [collectionSize]="tasks.length" maxSize="8" size="sm"></ngb-pagination>
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTab" class="nav-tabs" (hidden)="duringTabChange($event)">
|
||||
<li ngbNavItem="failed">
|
||||
<a ngbNavLink i18n>Failed<span *ngIf="tasksService.failedFileTasks.length > 0" class="badge bg-danger ms-2">{{tasksService.failedFileTasks.length}}</span></a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.failedFileTasks}"></ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li ngbNavItem="completed">
|
||||
<a ngbNavLink i18n>Complete<span *ngIf="tasksService.completedFileTasks.length > 0" class="badge bg-secondary ms-2">{{tasksService.completedFileTasks.length}}</span></a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.completedFileTasks}"></ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li ngbNavItem="started">
|
||||
<a ngbNavLink i18n>Started<span *ngIf="tasksService.startedFileTasks.length > 0" class="badge bg-secondary ms-2">{{tasksService.startedFileTasks.length}}</span></a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.startedFileTasks}"></ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li ngbNavItem="queued">
|
||||
<a ngbNavLink i18n>Queued<span *ngIf="tasksService.queuedFileTasks.length > 0" class="badge bg-secondary ms-2">{{tasksService.queuedFileTasks.length}}</span></a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.queuedFileTasks}"></ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
<div [ngbNavOutlet]="nav"></div>
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTab" class="nav-tabs" (hidden)="duringTabChange($event)">
|
||||
<li ngbNavItem="failed">
|
||||
<a ngbNavLink i18n>Failed@if (tasksService.failedFileTasks.length > 0) {
|
||||
<span class="badge bg-danger ms-2">{{tasksService.failedFileTasks.length}}</span>
|
||||
}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.failedFileTasks}"></ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li ngbNavItem="completed">
|
||||
<a ngbNavLink i18n>Complete@if (tasksService.completedFileTasks.length > 0) {
|
||||
<span class="badge bg-secondary ms-2">{{tasksService.completedFileTasks.length}}</span>
|
||||
}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.completedFileTasks}"></ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li ngbNavItem="started">
|
||||
<a ngbNavLink i18n>Started@if (tasksService.startedFileTasks.length > 0) {
|
||||
<span class="badge bg-secondary ms-2">{{tasksService.startedFileTasks.length}}</span>
|
||||
}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.startedFileTasks}"></ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li ngbNavItem="queued">
|
||||
<a ngbNavLink i18n>Queued@if (tasksService.queuedFileTasks.length > 0) {
|
||||
<span class="badge bg-secondary ms-2">{{tasksService.queuedFileTasks.length}}</span>
|
||||
}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.queuedFileTasks}"></ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
<div [ngbNavOutlet]="nav"></div>
|
||||
|
@ -1,97 +1,104 @@
|
||||
<pngx-page-header title="Users & Groups" i18n-title>
|
||||
</pngx-page-header>
|
||||
|
||||
<ng-container *ngIf="users">
|
||||
<h4 class="d-flex">
|
||||
<ng-container i18n>Users</ng-container>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editUser()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.User }">
|
||||
<svg class="sidebaricon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||
</svg>
|
||||
<ng-container i18n>Add User</ng-container>
|
||||
</button>
|
||||
</h4>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
@if (users) {
|
||||
<h4 class="d-flex">
|
||||
<ng-container i18n>Users</ng-container>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editUser()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.User }">
|
||||
<svg class="sidebaricon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||
</svg>
|
||||
<ng-container i18n>Add User</ng-container>
|
||||
</button>
|
||||
</h4>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col" i18n>Username</div>
|
||||
<div class="col" i18n>Name</div>
|
||||
<div class="col" i18n>Groups</div>
|
||||
<div class="col" i18n>Actions</div>
|
||||
</div>
|
||||
</li>
|
||||
@for (user of users; track user) {
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col" i18n>Username</div>
|
||||
<div class="col" i18n>Name</div>
|
||||
<div class="col" i18n>Groups</div>
|
||||
<div class="col" i18n>Actions</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li *ngFor="let user of users" class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editUser(user)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.User)">{{user.username}}</button></div>
|
||||
<div class="col d-flex align-items-center">{{user.first_name}} {{user.last_name}}</div>
|
||||
<div class="col d-flex align-items-center">{{user.groups?.map(getGroupName, this).join(', ')}}</div>
|
||||
<div class="col">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editUser(user)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.User)">{{user.username}}</button></div>
|
||||
<div class="col d-flex align-items-center">{{user.first_name}} {{user.last_name}}</div>
|
||||
<div class="col d-flex align-items-center">{{user.groups?.map(getGroupName, this).join(', ')}}</div>
|
||||
<div class="col">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="editUser(user)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.User }">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="editUser(user)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.User }">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteUser(user)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.User }">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
<ng-container *ngIf="groups">
|
||||
<h4 class="mt-4 d-flex">
|
||||
@if (groups) {
|
||||
<h4 class="mt-4 d-flex">
|
||||
<ng-container i18n>Groups</ng-container>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editGroup()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Group }">
|
||||
<svg class="sidebaricon me-1" fill="currentColor">
|
||||
<svg class="sidebaricon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||
</svg>
|
||||
<ng-container i18n>Add Group</ng-container>
|
||||
</svg>
|
||||
<ng-container i18n>Add Group</ng-container>
|
||||
</button>
|
||||
</h4>
|
||||
<ul *ngIf="groups.length > 0" class="list-group">
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col" i18n>Name</div>
|
||||
<div class="col"></div>
|
||||
<div class="col"></div>
|
||||
<div class="col" i18n>Actions</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li *ngFor="let group of groups" class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editGroup(group)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.Group)">{{group.name}}</button></div>
|
||||
<div class="col"></div>
|
||||
<div class="col"></div>
|
||||
<div class="col">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="editGroup(group)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Group }">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
</h4>
|
||||
@if (groups.length > 0) {
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col" i18n>Name</div>
|
||||
<div class="col"></div>
|
||||
<div class="col"></div>
|
||||
<div class="col" i18n>Actions</div>
|
||||
</div>
|
||||
</li>
|
||||
@for (group of groups; track group) {
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editGroup(group)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.Group)">{{group.name}}</button></div>
|
||||
<div class="col"></div>
|
||||
<div class="col"></div>
|
||||
<div class="col">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="editGroup(group)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Group }">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteGroup(group)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Group }">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li *ngIf="groups.length === 0" class="list-group-item" i18n>No groups defined</li>
|
||||
</ul>
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteGroup(group)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Group }">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
@if (groups.length === 0) {
|
||||
<li class="list-group-item" i18n>No groups defined</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="!users || !groups">
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
</div>
|
||||
@if (!users || !groups) {
|
||||
<div>
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -17,11 +17,13 @@
|
||||
</svg>
|
||||
<input class="form-control form-control-sm" type="text" placeholder="Search documents" aria-label="Search"
|
||||
[formControl]="searchField" [ngbTypeahead]="searchAutoComplete" (keyup)="searchFieldKeyup($event)" (selectItem)="itemSelected($event)" i18n-placeholder>
|
||||
<button type="button" *ngIf="!searchFieldEmpty" class="btn btn-link btn-sm px-0 position-absolute top-0 end-0" (click)="resetSearchField()">
|
||||
<svg fill="currentColor" class="buttonicon-sm me-1">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
</button>
|
||||
@if (!searchFieldEmpty) {
|
||||
<button type="button" class="btn btn-link btn-sm px-0 position-absolute top-0 end-0" (click)="resetSearchField()">
|
||||
<svg fill="currentColor" class="buttonicon-sm me-1">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<ul ngbNav class="order-sm-3">
|
||||
@ -42,255 +44,282 @@
|
||||
<button ngbDropdownItem class="nav-link" (click)="editProfile()">
|
||||
<svg class="sidebaricon me-2" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person"/>
|
||||
</svg><ng-container i18n>My Profile</ng-container>
|
||||
</button>
|
||||
<a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }">
|
||||
<svg class="sidebaricon me-2" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
|
||||
</svg><ng-container i18n>Settings</ng-container>
|
||||
</a>
|
||||
<a ngbDropdownItem class="nav-link" href="accounts/logout/" (click)="onLogout()">
|
||||
<svg class="sidebaricon me-2" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#door-open"/>
|
||||
</svg><ng-container i18n>Logout</ng-container>
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a ngbDropdownItem class="nav-link" target="_blank" rel="noopener noreferrer" href="https://docs.paperless-ngx.com">
|
||||
<svg class="sidebaricon me-2" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#question-circle"/>
|
||||
</svg><ng-container i18n>Documentation</ng-container>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<nav id="sidebarMenu" class="d-md-block bg-light sidebar collapse" [ngClass]="slimSidebarEnabled ? 'slim' : 'col-md-3 col-lg-2 col-xxxl-1'" [class.animating]="slimSidebarAnimating" [ngbCollapse]="isMenuCollapsed">
|
||||
<button class="btn btn-sm btn-dark sidebar-slim-toggler" (click)="toggleSlimSidebar()">
|
||||
<svg class="sidebaricon-sm" fill="currentColor">
|
||||
<use *ngIf="slimSidebarEnabled" xlink:href="assets/bootstrap-icons.svg#chevron-double-right"/>
|
||||
<use *ngIf="!slimSidebarEnabled" xlink:href="assets/bootstrap-icons.svg#chevron-double-left"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="sidebar-sticky pt-3 d-flex flex-column justify-space-around">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="dashboard" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Dashboard" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#house"/>
|
||||
</svg><span> <ng-container i18n>Dashboard</ng-container></span>
|
||||
</svg><ng-container i18n>My Profile</ng-container>
|
||||
</button>
|
||||
<a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }">
|
||||
<svg class="sidebaricon me-2" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
|
||||
</svg><ng-container i18n>Settings</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||
<a class="nav-link" routerLink="documents" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Documents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#files"/>
|
||||
</svg><span> <ng-container i18n>Documents</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
|
||||
<h6 class="sidebar-heading px-3 mt-3 mb-1 text-muted" *ngIf='savedViewService.loading || savedViewService.sidebarViews?.length > 0'>
|
||||
<span i18n>Saved views</span>
|
||||
<div *ngIf="savedViewService.loading" class="spinner-border spinner-border-sm fw-normal ms-2" role="status"></div>
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2" cdkDropList (cdkDropListDropped)="onDrop($event)">
|
||||
<li class="nav-item w-100" *ngFor="let view of savedViewService.sidebarViews"
|
||||
cdkDrag
|
||||
[cdkDragDisabled]="!settingsService.organizingSidebarSavedViews"
|
||||
cdkDragPreviewContainer="parent"
|
||||
cdkDragPreviewClass="navItemDrag"
|
||||
(cdkDragStarted)="onDragStart($event)"
|
||||
(cdkDragEnded)="onDragEnd($event)">
|
||||
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="view/{{view.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#funnel"/>
|
||||
</svg><span> {{view.name}}</span>
|
||||
<a ngbDropdownItem class="nav-link" href="accounts/logout/" (click)="onLogout()">
|
||||
<svg class="sidebaricon me-2" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#door-open"/>
|
||||
</svg><ng-container i18n>Logout</ng-container>
|
||||
</a>
|
||||
<div *ngIf="settingsService.organizingSidebarSavedViews" class="position-absolute end-0 top-0 px-3 py-2" [class.me-n3]="slimSidebarEnabled" cdkDragHandle>
|
||||
<svg class="sidebaricon text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#grip-vertical"/>
|
||||
</svg>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||
<h6 class="sidebar-heading px-3 mt-3 mb-1 text-muted" *ngIf='openDocuments.length > 0'>
|
||||
<span i18n>Open documents</span>
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2">
|
||||
<li class="nav-item w-100" *ngFor='let d of openDocuments'>
|
||||
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="documents/{{d.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="d.title | documentTitle" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
|
||||
</svg><span> {{d.title | documentTitle}}</span>
|
||||
<span class="close" (click)="closeDocument(d); $event.preventDefault()">
|
||||
<svg fill="currentColor" class="toolbaricon">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item w-100" *ngIf="openDocuments.length >= 1">
|
||||
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" [routerLink]="[]" (click)="closeAll()" ngbPopover="Close all" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg><span> <ng-container i18n>Close all</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
|
||||
<h6 class="sidebar-heading px-3 mt-3 mb-1 text-muted">
|
||||
<span i18n>Manage</span>
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2">
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
|
||||
<a class="nav-link" routerLink="correspondents" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Correspondents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person"/>
|
||||
</svg><span> <ng-container i18n>Correspondents</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }" tourAnchor="tour.tags">
|
||||
<a class="nav-link" routerLink="tags" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Tags" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#tags"/>
|
||||
</svg><span> <ng-container i18n>Tags</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
|
||||
<a class="nav-link" routerLink="documenttypes" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Document Types" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#hash"/>
|
||||
</svg><span> <ng-container i18n>Document Types</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
|
||||
<a class="nav-link" routerLink="storagepaths" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Storage Paths" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#folder"/>
|
||||
</svg><span> <ng-container i18n>Storage Paths</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }">
|
||||
<a class="nav-link" routerLink="customfields" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Custom Fields" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#ui-radios"/>
|
||||
</svg><span> <ng-container i18n>Custom Fields</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.ConsumptionTemplate }" tourAnchor="tour.consumption-templates">
|
||||
<a class="nav-link" routerLink="templates" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Consumption templates" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-earmark-ruled"/>
|
||||
</svg><span> <ng-container i18n>Templates</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailAccount }" tourAnchor="tour.mail">
|
||||
<a class="nav-link" routerLink="mail" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Mail" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#envelope"/>
|
||||
</svg><span> <ng-container i18n>Mail</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="sidebar-heading px-3 mt-auto pt-4 mb-1 text-muted">
|
||||
<span i18n>Administration</span>
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2">
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }" tourAnchor="tour.settings">
|
||||
<a class="nav-link" routerLink="settings" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Settings" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
|
||||
</svg><span> <ng-container i18n>Settings</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }">
|
||||
<a class="nav-link" routerLink="usersgroups" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Users & Groups" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#people"/>
|
||||
</svg><span> <ng-container i18n>Users & Groups</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.PaperlessTask }" tourAnchor="tour.file-tasks">
|
||||
<a class="nav-link" routerLink="tasks" routerLinkActive="active" (click)="closeMenu()" ngbPopover="File Tasks" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<span *ngIf="tasksService.failedFileTasks.length > 0 && slimSidebarEnabled" class="badge bg-danger position-absolute top-0 end-0">{{tasksService.failedFileTasks.length}}</span>
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#list-task"/>
|
||||
</svg><span> <ng-container i18n>File Tasks<span *ngIf="tasksService.failedFileTasks.length > 0"><span class="badge bg-danger ms-2">{{tasksService.failedFileTasks.length}}</span></span></ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }">
|
||||
<a class="nav-link" routerLink="logs" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Logs" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#text-left"/>
|
||||
</svg><span> <ng-container i18n>Logs</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-2" tourAnchor="tour.outro">
|
||||
<a class="px-3 py-2 text-muted small d-flex align-items-center flex-wrap text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://docs.paperless-ngx.com" ngbPopover="Documentation" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#question-circle"/>
|
||||
</svg><span class="ms-1"> <ng-container i18n>Documentation</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" [class.visually-hidden]="slimSidebarEnabled">
|
||||
<div class="px-3 py-0 text-muted small d-flex align-items-center flex-wrap">
|
||||
<div class="me-3">
|
||||
<a class="text-muted text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx" ngbPopover="GitHub" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
{{ versionString }}
|
||||
<div class="dropdown-divider"></div>
|
||||
<a ngbDropdownItem class="nav-link" target="_blank" rel="noopener noreferrer" href="https://docs.paperless-ngx.com">
|
||||
<svg class="sidebaricon me-2" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#question-circle"/>
|
||||
</svg><ng-container i18n>Documentation</ng-container>
|
||||
</a>
|
||||
</div>
|
||||
<div *ngIf="!settingsService.updateCheckingIsSet || appRemoteVersion" class="version-check">
|
||||
<ng-template #updateAvailablePopContent>
|
||||
<span class="small">Paperless-ngx {{ appRemoteVersion.version }} <ng-container i18n>is available.</ng-container><br/><ng-container i18n>Click to view.</ng-container></span>
|
||||
</ng-template>
|
||||
<ng-template #updateCheckingNotEnabledPopContent>
|
||||
<p class="small mb-2">
|
||||
<ng-container i18n>Paperless-ngx can automatically check for updates</ng-container>
|
||||
</p>
|
||||
<div class="btn-group btn-group-xs flex-fill w-100">
|
||||
<button class="btn btn-outline-primary" (click)="setUpdateChecking(true)">Enable</button>
|
||||
<button class="btn btn-outline-secondary" (click)="setUpdateChecking(false)">Disable</button>
|
||||
</div>
|
||||
<p class="small mb-0 mt-2">
|
||||
<a class="small text-decoration-none fst-italic" routerLink="/settings" fragment="update-checking" i18n>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<nav id="sidebarMenu" class="d-md-block bg-light sidebar collapse" [ngClass]="slimSidebarEnabled ? 'slim' : 'col-md-3 col-lg-2 col-xxxl-1'" [class.animating]="slimSidebarAnimating" [ngbCollapse]="isMenuCollapsed">
|
||||
<button class="btn btn-sm btn-dark sidebar-slim-toggler" (click)="toggleSlimSidebar()">
|
||||
<svg class="sidebaricon-sm" fill="currentColor">
|
||||
@if (slimSidebarEnabled) {
|
||||
<use xlink:href="assets/bootstrap-icons.svg#chevron-double-right"/>
|
||||
}
|
||||
@if (!slimSidebarEnabled) {
|
||||
<use xlink:href="assets/bootstrap-icons.svg#chevron-double-left"/>
|
||||
}
|
||||
</svg>
|
||||
</button>
|
||||
<div class="sidebar-sticky pt-3 d-flex flex-column justify-space-around">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="dashboard" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Dashboard" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#house"/>
|
||||
</svg><span> <ng-container i18n>Dashboard</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||
<a class="nav-link" routerLink="documents" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Documents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#files"/>
|
||||
</svg><span> <ng-container i18n>Documents</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
|
||||
@if (savedViewService.loading || savedViewService.sidebarViews?.length > 0) {
|
||||
<h6 class="sidebar-heading px-3 mt-3 mb-1 text-muted">
|
||||
<span i18n>Saved views</span>
|
||||
@if (savedViewService.loading) {
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2" role="status"></div>
|
||||
}
|
||||
</h6>
|
||||
}
|
||||
<ul class="nav flex-column mb-2" cdkDropList (cdkDropListDropped)="onDrop($event)">
|
||||
@for (view of savedViewService.sidebarViews; track view) {
|
||||
<li class="nav-item w-100"
|
||||
cdkDrag
|
||||
[cdkDragDisabled]="!settingsService.organizingSidebarSavedViews"
|
||||
cdkDragPreviewContainer="parent"
|
||||
cdkDragPreviewClass="navItemDrag"
|
||||
(cdkDragStarted)="onDragStart($event)"
|
||||
(cdkDragEnded)="onDragEnd($event)">
|
||||
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="view/{{view.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#funnel"/>
|
||||
</svg><span> {{view.name}}</span>
|
||||
</a>
|
||||
@if (settingsService.organizingSidebarSavedViews) {
|
||||
<div class="position-absolute end-0 top-0 px-3 py-2" [class.me-n3]="slimSidebarEnabled" cdkDragHandle>
|
||||
<svg class="sidebaricon text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#grip-vertical"/>
|
||||
</svg>
|
||||
</div>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||
@if (openDocuments.length > 0) {
|
||||
<h6 class="sidebar-heading px-3 mt-3 mb-1 text-muted">
|
||||
<span i18n>Open documents</span>
|
||||
</h6>
|
||||
}
|
||||
<ul class="nav flex-column mb-2">
|
||||
@for (d of openDocuments; track d) {
|
||||
<li class="nav-item w-100">
|
||||
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="documents/{{d.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="d.title | documentTitle" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
|
||||
</svg><span> {{d.title | documentTitle}}</span>
|
||||
<span class="close" (click)="closeDocument(d); $event.preventDefault()">
|
||||
<svg fill="currentColor" class="toolbaricon">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
@if (openDocuments.length >= 1) {
|
||||
<li class="nav-item w-100">
|
||||
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" [routerLink]="[]" (click)="closeAll()" ngbPopover="Close all" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg><span> <ng-container i18n>Close all</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</ng-container>
|
||||
|
||||
<h6 class="sidebar-heading px-3 mt-3 mb-1 text-muted">
|
||||
<span i18n>Manage</span>
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2">
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
|
||||
<a class="nav-link" routerLink="correspondents" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Correspondents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person"/>
|
||||
</svg><span> <ng-container i18n>Correspondents</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }" tourAnchor="tour.tags">
|
||||
<a class="nav-link" routerLink="tags" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Tags" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#tags"/>
|
||||
</svg><span> <ng-container i18n>Tags</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
|
||||
<a class="nav-link" routerLink="documenttypes" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Document Types" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#hash"/>
|
||||
</svg><span> <ng-container i18n>Document Types</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
|
||||
<a class="nav-link" routerLink="storagepaths" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Storage Paths" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#folder"/>
|
||||
</svg><span> <ng-container i18n>Storage Paths</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }">
|
||||
<a class="nav-link" routerLink="customfields" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Custom Fields" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#ui-radios"/>
|
||||
</svg><span> <ng-container i18n>Custom Fields</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.ConsumptionTemplate }" tourAnchor="tour.consumption-templates">
|
||||
<a class="nav-link" routerLink="templates" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Consumption templates" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-earmark-ruled"/>
|
||||
</svg><span> <ng-container i18n>Templates</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailAccount }" tourAnchor="tour.mail">
|
||||
<a class="nav-link" routerLink="mail" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Mail" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#envelope"/>
|
||||
</svg><span> <ng-container i18n>Mail</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="sidebar-heading px-3 mt-auto pt-4 mb-1 text-muted">
|
||||
<span i18n>Administration</span>
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2">
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }" tourAnchor="tour.settings">
|
||||
<a class="nav-link" routerLink="settings" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Settings" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
|
||||
</svg><span> <ng-container i18n>Settings</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }">
|
||||
<a class="nav-link" routerLink="usersgroups" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Users & Groups" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#people"/>
|
||||
</svg><span> <ng-container i18n>Users & Groups</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.PaperlessTask }" tourAnchor="tour.file-tasks">
|
||||
<a class="nav-link" routerLink="tasks" routerLinkActive="active" (click)="closeMenu()" ngbPopover="File Tasks" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
@if (tasksService.failedFileTasks.length > 0 && slimSidebarEnabled) {
|
||||
<span class="badge bg-danger position-absolute top-0 end-0">{{tasksService.failedFileTasks.length}}</span>
|
||||
}
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#list-task"/>
|
||||
</svg><span> <ng-container i18n>File Tasks@if (tasksService.failedFileTasks.length > 0) {
|
||||
<span><span class="badge bg-danger ms-2">{{tasksService.failedFileTasks.length}}</span></span>
|
||||
}</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }">
|
||||
<a class="nav-link" routerLink="logs" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Logs" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#text-left"/>
|
||||
</svg><span> <ng-container i18n>Logs</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-2" tourAnchor="tour.outro">
|
||||
<a class="px-3 py-2 text-muted small d-flex align-items-center flex-wrap text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://docs.paperless-ngx.com" ngbPopover="Documentation" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#question-circle"/>
|
||||
</svg><span class="ms-1"> <ng-container i18n>Documentation</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" [class.visually-hidden]="slimSidebarEnabled">
|
||||
<div class="px-3 py-0 text-muted small d-flex align-items-center flex-wrap">
|
||||
<div class="me-3">
|
||||
<a class="text-muted text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx" ngbPopover="GitHub" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
{{ versionString }}
|
||||
</a>
|
||||
</div>
|
||||
@if (!settingsService.updateCheckingIsSet || appRemoteVersion) {
|
||||
<div class="version-check">
|
||||
<ng-template #updateAvailablePopContent>
|
||||
<span class="small">Paperless-ngx {{ appRemoteVersion.version }} <ng-container i18n>is available.</ng-container><br/><ng-container i18n>Click to view.</ng-container></span>
|
||||
</ng-template>
|
||||
<ng-template #updateCheckingNotEnabledPopContent>
|
||||
<p class="small mb-2">
|
||||
<ng-container i18n>Paperless-ngx can automatically check for updates</ng-container>
|
||||
</p>
|
||||
<div class="btn-group btn-group-xs flex-fill w-100">
|
||||
<button class="btn btn-outline-primary" (click)="setUpdateChecking(true)">Enable</button>
|
||||
<button class="btn btn-outline-secondary" (click)="setUpdateChecking(false)">Disable</button>
|
||||
</div>
|
||||
<p class="small mb-0 mt-2">
|
||||
<a class="small text-decoration-none fst-italic" routerLink="/settings" fragment="update-checking" i18n>
|
||||
How does this work?
|
||||
</a>
|
||||
</p>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="settingsService.updateCheckingIsSet; else updateCheckNotSet">
|
||||
<a *ngIf="appRemoteVersion.update_available" class="small text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx/releases"
|
||||
[ngbPopover]="updateAvailablePopContent" popoverClass="shadow" triggers="mouseenter:mouseleave" container="body">
|
||||
<svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#info-circle" />
|
||||
</svg>
|
||||
<ng-container *ngIf="appRemoteVersion?.update_available" i18n>Update available</ng-container>
|
||||
</a>
|
||||
</ng-container>
|
||||
<ng-template #updateCheckNotSet>
|
||||
<a class="small text-decoration-none" routerLink="/settings" fragment="update-checking"
|
||||
[ngbPopover]="updateCheckingNotEnabledPopContent" popoverClass="shadow" triggers="mouseenter" container="body">
|
||||
<svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#info-circle" />
|
||||
</svg>
|
||||
</a>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</p>
|
||||
</ng-template>
|
||||
@if (settingsService.updateCheckingIsSet) {
|
||||
@if (appRemoteVersion.update_available) {
|
||||
<a class="small text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx/releases"
|
||||
[ngbPopover]="updateAvailablePopContent" popoverClass="shadow" triggers="mouseenter:mouseleave" container="body">
|
||||
<svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#info-circle" />
|
||||
</svg>
|
||||
@if (appRemoteVersion?.update_available) {
|
||||
<ng-container i18n>Update available</ng-container>
|
||||
}
|
||||
</a>
|
||||
}
|
||||
} @else {
|
||||
<a class="small text-decoration-none" routerLink="/settings" fragment="update-checking"
|
||||
[ngbPopover]="updateCheckingNotEnabledPopContent" popoverClass="shadow" triggers="mouseenter" container="body">
|
||||
<svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#info-circle" />
|
||||
</svg>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main role="main" class="ms-sm-auto px-md-4" [ngClass]="slimSidebarEnabled ? 'col-slim' : 'col-md-9 col-lg-10 col-xxxl-11'">
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<main role="main" class="ms-sm-auto px-md-4" [ngClass]="slimSidebarEnabled ? 'col-slim' : 'col-md-9 col-lg-10 col-xxxl-11'">
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,9 +1,15 @@
|
||||
<button *ngIf="active" class="position-absolute top-0 start-100 translate-middle badge bg-secondary border border-light rounded-pill p-1" title="Clear" i18n-title (click)="onClick($event)">
|
||||
<svg *ngIf="!isNumbered && selected" width="1em" height="1em" class="check m-0 p-0 opacity-75" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
@if (active) {
|
||||
<button class="position-absolute top-0 start-100 translate-middle badge bg-secondary border border-light rounded-pill p-1" title="Clear" i18n-title (click)="onClick($event)">
|
||||
@if (!isNumbered && selected) {
|
||||
<svg width="1em" height="1em" class="check m-0 p-0 opacity-75" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check-lg"/>
|
||||
</svg>
|
||||
<div *ngIf="isNumbered" class="number">{{number}}<span class="visually-hidden">selected</span></div>
|
||||
</svg>
|
||||
}
|
||||
@if (isNumbered) {
|
||||
<div class="number">{{number}}<span class="visually-hidden">selected</span></div>
|
||||
}
|
||||
<svg width=".9em" height="1em" class="x m-0 p-0 opacity-75" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x-lg"/>
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x-lg"/>
|
||||
</svg>
|
||||
</button>
|
||||
</button>
|
||||
}
|
||||
|
@ -1,24 +1,32 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p *ngIf="messageBold"><b>{{messageBold}}</b></p>
|
||||
<p class="mb-0" *ngIf="message" [innerHTML]="message | safeHtml"></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" [disabled]="!buttonsEnabled" i18n>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@if (messageBold) {
|
||||
<p><b>{{messageBold}}</b></p>
|
||||
}
|
||||
@if (message) {
|
||||
<p class="mb-0" [innerHTML]="message | safeHtml"></p>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" [disabled]="!buttonsEnabled" i18n>
|
||||
<span class="d-inline-block" style="padding-bottom: 1px;" >Cancel</span>
|
||||
</button>
|
||||
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled">
|
||||
<span>
|
||||
{{btnCaption}}
|
||||
<span class="visually-hidden">{{ seconds | number: '1.0-0' }} seconds</span>
|
||||
</span>
|
||||
<ngb-progressbar *ngIf="!confirmButtonEnabled" style="height: 1px;" type="dark" [max]="secondsTotal" [value]="seconds"></ngb-progressbar>
|
||||
</button>
|
||||
<button *ngIf="alternativeBtnCaption" type="button" class="btn" [class]="alternativeBtnClass" (click)="alternative()" [disabled]="!alternativeButtonEnabled || !buttonsEnabled">
|
||||
{{alternativeBtnCaption}}
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled">
|
||||
<span>
|
||||
{{btnCaption}}
|
||||
<span class="visually-hidden">{{ seconds | number: '1.0-0' }} seconds</span>
|
||||
</span>
|
||||
@if (!confirmButtonEnabled) {
|
||||
<ngb-progressbar style="height: 1px;" type="dark" [max]="secondsTotal" [value]="seconds"></ngb-progressbar>
|
||||
}
|
||||
</button>
|
||||
@if (alternativeBtnCaption) {
|
||||
<button type="button" class="btn" [class]="alternativeBtnClass" (click)="alternative()" [disabled]="!alternativeButtonEnabled || !buttonsEnabled">
|
||||
{{alternativeBtnCaption}}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
@ -1,15 +1,18 @@
|
||||
<div class="btn-group w-100" ngbDropdown role="group">
|
||||
<div class="btn-group w-100" ngbDropdown role="group">
|
||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="dateBefore || dateAfter ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
|
||||
{{title}}
|
||||
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge><span class="visually-hidden">selected</span>
|
||||
</button>
|
||||
<div class="dropdown-menu date-dropdown shadow pt-0" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||
<div class="list-group list-group-flush">
|
||||
<button *ngFor="let rd of relativeDates" class="list-group-item small list-goup list-group-item-action d-flex p-2" role="menuitem" (click)="setRelativeDate(rd.id)">
|
||||
@for (rd of relativeDates; track rd) {
|
||||
<button class="list-group-item small list-goup list-group-item-action d-flex p-2" role="menuitem" (click)="setRelativeDate(rd.id)">
|
||||
<div class="selected-icon">
|
||||
<svg *ngIf="relativeDate === rd.id" fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
@if (relativeDate === rd.id) {
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex justify-content-between w-100 align-items-center ps-2">
|
||||
<div class="pe-2 pe-lg-4">
|
||||
@ -22,52 +25,57 @@
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
|
||||
}
|
||||
<div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
|
||||
|
||||
<div class="mb-2 d-flex flex-row w-100 justify-content-between small">
|
||||
<div i18n>After</div>
|
||||
<a *ngIf="dateAfter" class="btn btn-link p-0 m-0" (click)="clearAfter()">
|
||||
<div class="mb-2 d-flex flex-row w-100 justify-content-between small">
|
||||
<div i18n>After</div>
|
||||
@if (dateAfter) {
|
||||
<a class="btn btn-link p-0 m-0" (click)="clearAfter()">
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
<small i18n>Clear</small>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm">
|
||||
<input class="form-control" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="dateAfter" ngbDatepicker #dateAfterPicker="ngbDatepicker">
|
||||
<button class="btn btn-outline-secondary" (click)="dateAfterPicker.toggle()" type="button">
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#calendar"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
}
|
||||
</div>
|
||||
<div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
|
||||
|
||||
<div class="mb-2 d-flex flex-row w-100 justify-content-between small">
|
||||
<div i18n>Before</div>
|
||||
<a *ngIf="dateBefore" class="btn btn-link p-0 m-0" (click)="clearBefore()">
|
||||
<div class="input-group input-group-sm">
|
||||
<input class="form-control" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="dateAfter" ngbDatepicker #dateAfterPicker="ngbDatepicker">
|
||||
<button class="btn btn-outline-secondary" (click)="dateAfterPicker.toggle()" type="button">
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#calendar"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
|
||||
|
||||
<div class="mb-2 d-flex flex-row w-100 justify-content-between small">
|
||||
<div i18n>Before</div>
|
||||
@if (dateBefore) {
|
||||
<a class="btn btn-link p-0 m-0" (click)="clearBefore()">
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
<small i18n>Clear</small>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm">
|
||||
<input class="form-control" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="dateBefore" ngbDatepicker #dateBeforePicker="ngbDatepicker">
|
||||
<button class="btn btn-outline-secondary" (click)="dateBeforePicker.toggle()" type="button">
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#calendar"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm">
|
||||
<input class="form-control" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="dateBefore" ngbDatepicker #dateBeforePicker="ngbDatepicker">
|
||||
<button class="btn btn-outline-secondary" (click)="dateBeforePicker.toggle()" type="button">
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#calendar"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,93 +1,95 @@
|
||||
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
|
||||
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
||||
</div>
|
||||
<div class="col">
|
||||
<pngx-input-number i18n-title title="Sort order" formControlName="order" [showAdd]="false" [error]="error?.order"></pngx-input-number>
|
||||
</div>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
|
||||
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<h5 class="border-bottom pb-2" i18n>Filters</h5>
|
||||
<p class="small" i18n>Process documents that match <em>all</em> filters specified below.</p>
|
||||
<pngx-input-select i18n-title title="Filter sources" [items]="sourceOptions" [multiple]="true" formControlName="sources" [error]="error?.sources"></pngx-input-select>
|
||||
<pngx-input-text i18n-title title="Filter filename" formControlName="filter_filename" i18n-hint hint="Apply to documents that match this filename. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." [error]="error?.filter_filename"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Filter path" formControlName="filter_path" i18n-hint hint="Apply to documents that match this path. Wildcards specified as * are allowed. Case insensitive.</a>" [error]="error?.filter_path"></pngx-input-text>
|
||||
<pngx-input-select i18n-title title="Filter mail rule" [items]="mailRules" [allowNull]="true" formControlName="filter_mailrule" i18n-hint hint="Apply to documents consumed via this mail rule." [error]="error?.filter_mailrule"></pngx-input-select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h5 class="border-bottom pb-2" i18n>Assignments</h5>
|
||||
</div>
|
||||
<div class="col">
|
||||
<pngx-input-number i18n-title title="Sort order" formControlName="order" [showAdd]="false" [error]="error?.order"></pngx-input-number>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<h5 class="border-bottom pb-2" i18n>Filters</h5>
|
||||
<p class="small" i18n>Process documents that match <em>all</em> filters specified below.</p>
|
||||
<pngx-input-select i18n-title title="Filter sources" [items]="sourceOptions" [multiple]="true" formControlName="sources" [error]="error?.sources"></pngx-input-select>
|
||||
<pngx-input-text i18n-title title="Filter filename" formControlName="filter_filename" i18n-hint hint="Apply to documents that match this filename. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." [error]="error?.filter_filename"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Filter path" formControlName="filter_path" i18n-hint hint="Apply to documents that match this path. Wildcards specified as * are allowed. Case insensitive.</a>" [error]="error?.filter_path"></pngx-input-text>
|
||||
<pngx-input-select i18n-title title="Filter mail rule" [items]="mailRules" [allowNull]="true" formControlName="filter_mailrule" i18n-hint hint="Apply to documents consumed via this mail rule." [error]="error?.filter_mailrule"></pngx-input-select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h5 class="border-bottom pb-2" i18n>Assignments</h5>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<pngx-input-text i18n-title title="Assign title" formControlName="assign_title" i18n-hint hint="Can include some placeholders, see <a target='_blank' href='https://docs.paperless-ngx.com/usage/#consumption-templates'>documentation</a>." [error]="error?.assign_title"></pngx-input-text>
|
||||
<pngx-input-tags [allowCreate]="false" i18n-title title="Assign tags" formControlName="assign_tags"></pngx-input-tags>
|
||||
<pngx-input-select i18n-title title="Assign document type" [items]="documentTypes" [allowNull]="true" formControlName="assign_document_type"></pngx-input-select>
|
||||
<pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
|
||||
<pngx-input-select i18n-title title="Assign storage path" [items]="storagePaths" [allowNull]="true" formControlName="assign_storage_path"></pngx-input-select>
|
||||
<pngx-input-select i18n-title title="Assign custom fields" multiple="true" [items]="customFields" [allowNull]="true" formControlName="assign_custom_fields"></pngx-input-select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<pngx-input-text i18n-title title="Assign title" formControlName="assign_title" i18n-hint hint="Can include some placeholders, see <a target='_blank' href='https://docs.paperless-ngx.com/usage/#consumption-templates'>documentation</a>." [error]="error?.assign_title"></pngx-input-text>
|
||||
<pngx-input-tags [allowCreate]="false" i18n-title title="Assign tags" formControlName="assign_tags"></pngx-input-tags>
|
||||
<pngx-input-select i18n-title title="Assign document type" [items]="documentTypes" [allowNull]="true" formControlName="assign_document_type"></pngx-input-select>
|
||||
<pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
|
||||
<pngx-input-select i18n-title title="Assign storage path" [items]="storagePaths" [allowNull]="true" formControlName="assign_storage_path"></pngx-input-select>
|
||||
<pngx-input-select i18n-title title="Assign custom fields" multiple="true" [items]="customFields" [allowNull]="true" formControlName="assign_custom_fields"></pngx-input-select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<pngx-input-select i18n-title title="Assign owner" [items]="users" bindLabel="username" formControlName="assign_owner" [allowNull]="true"></pngx-input-select>
|
||||
<div>
|
||||
<label class="form-label" i18n>Assign view permissions</label>
|
||||
<div class="mb-2">
|
||||
<div class="row mb-1">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-user type="view" formControlName="assign_view_users"></pngx-permissions-user>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label" i18n>Assign view permissions</label>
|
||||
<div class="mb-2">
|
||||
<div class="row mb-1">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-group type="view" formControlName="assign_view_groups"></pngx-permissions-group>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-user type="view" formControlName="assign_view_users"></pngx-permissions-user>
|
||||
</div>
|
||||
</div>
|
||||
<label class="form-label" i18n>Assign edit permissions</label>
|
||||
<div>
|
||||
<div class="row mb-1">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-user type="change" formControlName="assign_change_users"></pngx-permissions-user>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-group type="change" formControlName="assign_change_groups"></pngx-permissions-group>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-group type="view" formControlName="assign_view_groups"></pngx-permissions-group>
|
||||
</div>
|
||||
<small class="form-text text-muted text-end d-block" i18n>Edit permissions also grant viewing permissions</small>
|
||||
</div>
|
||||
</div>
|
||||
<label class="form-label" i18n>Assign edit permissions</label>
|
||||
<div>
|
||||
<div class="row mb-1">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-user type="change" formControlName="assign_change_users"></pngx-permissions-user>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-group type="change" formControlName="assign_change_groups"></pngx-permissions-group>
|
||||
</div>
|
||||
</div>
|
||||
<small class="form-text text-muted text-end d-block" i18n>Edit permissions also grant viewing permissions</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="text-danger" *ngIf="error?.non_field_errors"><ng-container i18n>Error</ng-container>: {{error.non_field_errors}}</span>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@if (error?.non_field_errors) {
|
||||
<span class="text-danger"><ng-container i18n>Error</ng-container>: {{error.non_field_errors}}</span>
|
||||
}
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -8,8 +8,12 @@
|
||||
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
||||
<pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select>
|
||||
<pngx-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
|
||||
<pngx-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></pngx-input-check>
|
||||
@if (patternRequired) {
|
||||
<pngx-input-text i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
|
||||
}
|
||||
@if (patternRequired) {
|
||||
<pngx-input-check i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></pngx-input-check>
|
||||
}
|
||||
|
||||
<div *pngxIfOwner="object">
|
||||
<pngx-permissions-form [users]="users" accordion="true" formControlName="permissions_form"></pngx-permissions-form>
|
||||
|
@ -7,7 +7,9 @@
|
||||
<div class="modal-body">
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
||||
<pngx-input-select i18n-title title="Data type" [items]="getDataTypes()" formControlName="data_type"></pngx-input-select>
|
||||
<small class="d-block mt-n2" *ngIf="typeFieldDisabled" i18n>Data type cannot be changed after a field is created</small>
|
||||
@if (typeFieldDisabled) {
|
||||
<small class="d-block mt-n2" i18n>Data type cannot be changed after a field is created</small>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||
|
@ -1,25 +1,29 @@
|
||||
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
|
||||
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="col">
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
||||
<pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select>
|
||||
<pngx-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
|
||||
<pngx-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></pngx-input-check>
|
||||
</div>
|
||||
|
||||
<div *pngxIfOwner="object">
|
||||
<pngx-permissions-form [users]="users" accordion="true" formControlName="permissions_form"></pngx-permissions-form>
|
||||
</div>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
|
||||
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="col">
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
||||
<pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select>
|
||||
@if (patternRequired) {
|
||||
<pngx-input-text i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
|
||||
}
|
||||
@if (patternRequired) {
|
||||
<pngx-input-check i18n-title title="Case insensitive" formControlName="is_insensitive"></pngx-input-check>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
|
||||
|
||||
<div *pngxIfOwner="object">
|
||||
<pngx-permissions-form [users]="users" accordion="true" formControlName="permissions_form"></pngx-permissions-form>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -22,13 +22,15 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="m-0 me-2">
|
||||
<ngb-alert #testResultAlert *ngIf="testResult" [type]="testResult" class="mb-0 py-2" (closed)="testResult = null">{{testResultMessage}}</ngb-alert>
|
||||
@if (testResult) {
|
||||
<ngb-alert #testResultAlert [type]="testResult" class="mb-0 py-2" (closed)="testResult = null">{{testResultMessage}}</ngb-alert>
|
||||
}
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-primary" (click)="test()" [disabled]="networkActive || testActive">
|
||||
<ng-container *ngIf="testActive">
|
||||
@if (testActive) {
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<span class="visually-hidden mr-1" i18n>Loading...</span>
|
||||
</ng-container>
|
||||
}
|
||||
<ng-container i18n>Test</ng-container>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||
|
@ -26,19 +26,25 @@
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<pngx-input-select i18n-title title="Action" [items]="actionOptions" formControlName="action" i18n-hint hint="Action is only performed when documents are consumed from the mail. Mails without attachments remain entirely untouched."></pngx-input-select>
|
||||
<pngx-input-text i18n-title title="Action parameter" *ngIf="showActionParamField" formControlName="action_parameter" [error]="error?.action_parameter"></pngx-input-text>
|
||||
@if (showActionParamField) {
|
||||
<pngx-input-text i18n-title title="Action parameter" formControlName="action_parameter" [error]="error?.action_parameter"></pngx-input-text>
|
||||
}
|
||||
<p class="small fst-italic mt-5" i18n>Assignments specified here will supersede any consumption templates.</p>
|
||||
<pngx-input-select i18n-title title="Assign title from" [items]="metadataTitleOptions" formControlName="assign_title_from"></pngx-input-select>
|
||||
<pngx-input-tags [allowCreate]="false" formControlName="assign_tags"></pngx-input-tags>
|
||||
<pngx-input-select i18n-title title="Assign document type" [items]="documentTypes" [allowNull]="true" formControlName="assign_document_type"></pngx-input-select>
|
||||
<pngx-input-select i18n-title title="Assign correspondent from" [items]="metadataCorrespondentOptions" formControlName="assign_correspondent_from"></pngx-input-select>
|
||||
<pngx-input-select *ngIf="showCorrespondentField" i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
|
||||
@if (showCorrespondentField) {
|
||||
<pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
|
||||
}
|
||||
<pngx-input-check i18n-title title="Assign owner from rule" formControlName="assign_owner_from_rule"></pngx-input-check>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="text-danger" *ngIf="error?.non_field_errors"><ng-container i18n>Error</ng-container>: {{error.non_field_errors}}</span>
|
||||
@if (error?.non_field_errors) {
|
||||
<span class="text-danger"><ng-container i18n>Error</ng-container>: {{error.non_field_errors}}</span>
|
||||
}
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
|
||||
</div>
|
||||
|
@ -9,8 +9,12 @@
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Path" formControlName="path" [error]="error?.path" [hint]="pathHint"></pngx-input-text>
|
||||
<pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select>
|
||||
<pngx-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
|
||||
<pngx-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></pngx-input-check>
|
||||
@if (patternRequired) {
|
||||
<pngx-input-text i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
|
||||
}
|
||||
@if (patternRequired) {
|
||||
<pngx-input-check i18n-title title="Case insensitive" formControlName="is_insensitive"></pngx-input-check>
|
||||
}
|
||||
|
||||
<div *pngxIfOwner="object">
|
||||
<pngx-permissions-form [users]="users" accordion="true" formControlName="permissions_form"></pngx-permissions-form>
|
||||
|
@ -1,26 +1,30 @@
|
||||
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
|
||||
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
</button>
|
||||
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
|
||||
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
||||
|
||||
<pngx-input-color i18n-title title="Color" formControlName="color" [error]="error?.color"></pngx-input-color>
|
||||
|
||||
<pngx-input-check i18n-title title="Inbox tag" formControlName="is_inbox_tag" i18n-hint hint="Inbox tags are automatically assigned to all consumed documents."></pngx-input-check>
|
||||
<pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select>
|
||||
@if (patternRequired) {
|
||||
<pngx-input-text i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
|
||||
}
|
||||
@if (patternRequired) {
|
||||
<pngx-input-check i18n-title title="Case insensitive" formControlName="is_insensitive"></pngx-input-check>
|
||||
}
|
||||
|
||||
<div *pngxIfOwner="object">
|
||||
<pngx-permissions-form [users]="users" accordion="true" formControlName="permissions_form"></pngx-permissions-form>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
||||
|
||||
<pngx-input-color i18n-title title="Color" formControlName="color" [error]="error?.color"></pngx-input-color>
|
||||
|
||||
<pngx-input-check i18n-title title="Inbox tag" formControlName="is_inbox_tag" i18n-hint hint="Inbox tags are automatically assigned to all consumed documents."></pngx-input-check>
|
||||
<pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select>
|
||||
<pngx-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
|
||||
<pngx-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></pngx-input-check>
|
||||
|
||||
<div *pngxIfOwner="object">
|
||||
<pngx-permissions-form [users]="users" accordion="true" formControlName="permissions_form"></pngx-permissions-form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -4,49 +4,61 @@
|
||||
<use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" />
|
||||
</svg>
|
||||
<div class="d-none d-sm-inline"> {{title}}</div>
|
||||
<ng-container *ngIf="!editing && selectionModel.totalCount > 0">
|
||||
@if (!editing && selectionModel.totalCount > 0) {
|
||||
<pngx-clearable-badge [number]="selectionModel.totalCount" [selected]="selectionModel.selectionSize() > 0" (cleared)="reset()"></pngx-clearable-badge>
|
||||
</ng-container>
|
||||
}
|
||||
</button>
|
||||
<div class="dropdown-menu py-0 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown_{{name}}">
|
||||
<div class="list-group list-group-flush">
|
||||
<div *ngIf="!editing && manyToOne" class="list-group-item d-flex">
|
||||
<div class="btn-group btn-group-xs flex-fill" role="group">
|
||||
<input [(ngModel)]="selectionModel.logicalOperator" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleOperator()" type="radio" class="btn-check" id="logicalOperatorAnd_{{name}}" name="logicalOperatorAnd_{{name}}" value="and">
|
||||
<label class="btn btn-outline-primary" for="logicalOperatorAnd_{{name}}" i18n>All</label>
|
||||
<input [(ngModel)]="selectionModel.logicalOperator" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleOperator()" type="radio" class="btn-check" id="logicalOperatorOr_{{name}}" name="logicalOperatorOr_{{name}}" value="or">
|
||||
<label class="btn btn-outline-primary" for="logicalOperatorOr_{{name}}" i18n>Any</label>
|
||||
@if (!editing && manyToOne) {
|
||||
<div class="list-group-item d-flex">
|
||||
<div class="btn-group btn-group-xs flex-fill" role="group">
|
||||
<input [(ngModel)]="selectionModel.logicalOperator" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleOperator()" type="radio" class="btn-check" id="logicalOperatorAnd_{{name}}" name="logicalOperatorAnd_{{name}}" value="and">
|
||||
<label class="btn btn-outline-primary" for="logicalOperatorAnd_{{name}}" i18n>All</label>
|
||||
<input [(ngModel)]="selectionModel.logicalOperator" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleOperator()" type="radio" class="btn-check" id="logicalOperatorOr_{{name}}" name="logicalOperatorOr_{{name}}" value="or">
|
||||
<label class="btn btn-outline-primary" for="logicalOperatorOr_{{name}}" i18n>Any</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!editing && !manyToOne" class="list-group-item d-flex">
|
||||
<div class="btn-group btn-group-xs flex-fill" role="group">
|
||||
<input [(ngModel)]="selectionModel.intersection" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleIntersection()" type="radio" class="btn-check" id="intersectionInclude_{{name}}" name="intersectionInclude_{{name}}" value="include">
|
||||
<label class="btn btn-outline-primary" for="intersectionInclude_{{name}}" i18n>Include</label>
|
||||
<input [(ngModel)]="selectionModel.intersection" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleIntersection()" type="radio" class="btn-check" id="intersectionExclude_{{name}}" name="intersectionExclude_{{name}}" value="exclude">
|
||||
<label class="btn btn-outline-primary" for="intersectionExclude_{{name}}" i18n>Exclude</label>
|
||||
}
|
||||
@if (!editing && !manyToOne) {
|
||||
<div class="list-group-item d-flex">
|
||||
<div class="btn-group btn-group-xs flex-fill" role="group">
|
||||
<input [(ngModel)]="selectionModel.intersection" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleIntersection()" type="radio" class="btn-check" id="intersectionInclude_{{name}}" name="intersectionInclude_{{name}}" value="include">
|
||||
<label class="btn btn-outline-primary" for="intersectionInclude_{{name}}" i18n>Include</label>
|
||||
<input [(ngModel)]="selectionModel.intersection" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleIntersection()" type="radio" class="btn-check" id="intersectionExclude_{{name}}" name="intersectionExclude_{{name}}" value="exclude">
|
||||
<label class="btn btn-outline-primary" for="intersectionExclude_{{name}}" i18n>Exclude</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="list-group-item">
|
||||
<div class="input-group input-group-sm">
|
||||
<input class="form-control" type="text" [(ngModel)]="filterText" [placeholder]="filterPlaceholder" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="selectionModel.items" class="items" #buttonItems>
|
||||
<ng-container *ngFor="let item of selectionModel.itemsSorted | filter: filterText; let i = index">
|
||||
<pngx-toggleable-dropdown-button
|
||||
*ngIf="allowSelectNone || item.id" [item]="item" [hideCount]="hideCount(item)" [state]="selectionModel.get(item.id)" [count]="getUpdatedDocumentCount(item.id)" (toggle)="selectionModel.toggle(item.id)" (exclude)="excludeClicked(item.id)" (click)="setButtonItemIndex(i - 1)" [disabled]="disabled">
|
||||
</pngx-toggleable-dropdown-button>
|
||||
</ng-container>
|
||||
</div>
|
||||
<button *ngIf="editing" class="list-group-item list-group-item-action bg-light" (click)="applyClicked()" [disabled]="!modelIsDirty || disabled">
|
||||
<small class="ms-2" [ngClass]="{'fw-bold': modelIsDirty}" i18n>Apply</small>
|
||||
<svg width="1.5em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-right" />
|
||||
</svg>
|
||||
</button>
|
||||
<div *ngIf="!editing && manyToOne" class="list-group-item list-group-item-note pt-1 pb-2">
|
||||
<small i18n>Click again to exclude items.</small>
|
||||
</div>
|
||||
@if (selectionModel.items) {
|
||||
<div class="items" #buttonItems>
|
||||
@for (item of selectionModel.itemsSorted | filter: filterText; track item; let i = $index) {
|
||||
@if (allowSelectNone || item.id) {
|
||||
<pngx-toggleable-dropdown-button
|
||||
[item]="item" [hideCount]="hideCount(item)" [state]="selectionModel.get(item.id)" [count]="getUpdatedDocumentCount(item.id)" (toggle)="selectionModel.toggle(item.id)" (exclude)="excludeClicked(item.id)" (click)="setButtonItemIndex(i - 1)" [disabled]="disabled">
|
||||
</pngx-toggleable-dropdown-button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (editing) {
|
||||
<button class="list-group-item list-group-item-action bg-light" (click)="applyClicked()" [disabled]="!modelIsDirty || disabled">
|
||||
<small class="ms-2" [ngClass]="{'fw-bold': modelIsDirty}" i18n>Apply</small>
|
||||
<svg width="1.5em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-right" />
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
@if (!editing && manyToOne) {
|
||||
<div class="list-group-item list-group-item-note pt-1 pb-2">
|
||||
<small i18n>Click again to exclude items.</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,24 +1,29 @@
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="toggleItem($event)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
<ng-container *ngIf="isChecked()">
|
||||
@if (isChecked()) {
|
||||
<svg fill="currentColor" class="buttonicon-sm bi-check">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="isPartiallyChecked()">
|
||||
}
|
||||
@if (isPartiallyChecked()) {
|
||||
<svg fill="currentColor" class="buttonicon-sm bi-dash">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#dash"/>
|
||||
</svg>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="isExcluded()">
|
||||
}
|
||||
@if (isExcluded()) {
|
||||
<svg fill="currentColor" class="buttonicon-sm bi-x">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
</ng-container>
|
||||
}
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<pngx-tag *ngIf="isTag; else displayName" [tag]="item" [clickable]="false"></pngx-tag>
|
||||
<ng-template #displayName><small>{{item.name}}</small></ng-template>
|
||||
@if (isTag) {
|
||||
<pngx-tag [tag]="item" [clickable]="false"></pngx-tag>
|
||||
} @else {
|
||||
<small>{{item.name}}</small>
|
||||
}
|
||||
</div>
|
||||
<div *ngIf="!hideCount" class="badge bg-light text-dark rounded-pill ms-auto me-1">{{count ?? item.document_count}}</div>
|
||||
@if (!hideCount) {
|
||||
<div class="badge bg-light text-dark rounded-pill ms-auto me-1">{{count ?? item.document_count}}</div>
|
||||
}
|
||||
</button>
|
||||
|
@ -1,19 +1,27 @@
|
||||
<div class="mb-3">
|
||||
<div class="row">
|
||||
<div *ngIf="horizontal" class="d-flex align-items-center position-relative hidden-button-container col-md-3">
|
||||
<label class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
|
||||
<button type="button" *ngIf="removable" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
<div [ngClass]="{'col-md-9': horizontal, 'align-items-center': horizontal, 'd-flex': horizontal}">
|
||||
<div class="form-check">
|
||||
<input #inputField type="checkbox" class="form-check-input" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" (blur)="onTouched()" [disabled]="disabled">
|
||||
<label *ngIf="!horizontal" class="form-check-label" [for]="inputId">{{title}}</label>
|
||||
<div *ngIf="hint" class="form-text text-muted">{{hint}}</div>
|
||||
@if (horizontal) {
|
||||
<div class="d-flex align-items-center position-relative hidden-button-container col-md-3">
|
||||
<label class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
|
||||
@if (removable) {
|
||||
<button type="button" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div [ngClass]="{'col-md-9': horizontal, 'align-items-center': horizontal, 'd-flex': horizontal}">
|
||||
<div class="form-check">
|
||||
<input #inputField type="checkbox" class="form-check-input" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" (blur)="onTouched()" [disabled]="disabled">
|
||||
@if (!horizontal) {
|
||||
<label class="form-check-label" [for]="inputId">{{title}}</label>
|
||||
}
|
||||
@if (hint) {
|
||||
<div class="form-text text-muted">{{hint}}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,28 +1,32 @@
|
||||
<div class="mb-3">
|
||||
<label *ngIf="title" [for]="inputId">{{title}}</label>
|
||||
@if (title) {
|
||||
<label [for]="inputId">{{title}}</label>
|
||||
}
|
||||
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<span class="input-group-text" [style.background-color]="value"> </span>
|
||||
|
||||
<ng-template #popContent>
|
||||
<div style="min-width: 200px;" class="pb-3">
|
||||
<color-slider [color]="value" (onChangeComplete)="colorChanged($event.color.hex)"></color-slider>
|
||||
</div>
|
||||
<ng-template #popContent>
|
||||
<div style="min-width: 200px;" class="pb-3">
|
||||
<color-slider [color]="value" (onChangeComplete)="colorChanged($event.color.hex)"></color-slider>
|
||||
</div>
|
||||
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<input #inputField class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [autoClose]="'outside'" [ngbPopover]="popContent" placement="bottom" popoverClass="shadow">
|
||||
<input #inputField class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [autoClose]="'outside'" [ngbPopover]="popContent" placement="bottom" popoverClass="shadow">
|
||||
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="randomize()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-dice-5" viewBox="0 0 16 16">
|
||||
<path d="M13 1a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h10zM3 0a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V3a3 3 0 0 0-3-3H3z"/>
|
||||
<path d="M5.5 4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm8 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0 8a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm-8 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm4-4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="randomize()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-dice-5" viewBox="0 0 16 16">
|
||||
<path d="M13 1a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h10zM3 0a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V3a3 3 0 0 0-3-3H3z"/>
|
||||
<path d="M5.5 4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm8 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0 8a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm-8 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm4-4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
</div>
|
||||
@if (hint) {
|
||||
<small class="form-text text-muted">{{hint}}</small>
|
||||
}
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,36 +2,44 @@
|
||||
<div class="row">
|
||||
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
|
||||
<label class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
|
||||
<button type="button" *ngIf="removable" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
<div class="position-relative" [class.col-md-9]="horizontal">
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<input #inputField 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" [disabled]="disabled">
|
||||
<button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button" [disabled]="disabled">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="buttonicon">
|
||||
<use _ngcontent-ng-c3750736003="" xlink:href="assets/bootstrap-icons.svg#calendar"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<button *ngIf="showFilter" class="btn btn-outline-secondary" type="button" (click)="onFilterDocuments()" [disabled]="this.value === null" title="{{ fitlerButtonTitle }}">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#filter" />
|
||||
</svg>
|
||||
</button>
|
||||
@if (removable) {
|
||||
<button type="button" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="position-relative" [class.col-md-9]="horizontal">
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<input #inputField 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" [disabled]="disabled">
|
||||
<button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button" [disabled]="disabled">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="buttonicon">
|
||||
<use _ngcontent-ng-c3750736003="" xlink:href="assets/bootstrap-icons.svg#calendar"></use>
|
||||
</svg>
|
||||
</button>
|
||||
@if (showFilter) {
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="onFilterDocuments()" [disabled]="this.value === null" title="{{ filterButtonTitle }}">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#filter" />
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="invalid-feedback position-absolute top-100" i18n>Invalid date.</div>
|
||||
@if (hint) {
|
||||
<small class="form-text text-muted">{{hint}}</small>
|
||||
}
|
||||
@if (getSuggestions().length > 0) {
|
||||
<small>
|
||||
<span i18n>Suggestions:</span>
|
||||
@for (s of getSuggestions(); track s) {
|
||||
<a (click)="onSuggestionClick(s)" [routerLink]="[]">{{s}}</a>
|
||||
}
|
||||
</small>
|
||||
}
|
||||
</div>
|
||||
<div class="invalid-feedback position-absolute top-100" i18n>Invalid date.</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
<small *ngIf="getSuggestions().length > 0">
|
||||
<span i18n>Suggestions:</span>
|
||||
<ng-container *ngFor="let s of getSuggestions()">
|
||||
<a (click)="onSuggestionClick(s)" [routerLink]="[]">{{s}}</a>
|
||||
</ng-container>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,12 +1,16 @@
|
||||
<div class="mb-3 paperless-input-select" [class.disabled]="disabled">
|
||||
<div class="row">
|
||||
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
|
||||
<label *ngIf="title" class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
|
||||
<button type="button" *ngIf="removable" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<div class="row">
|
||||
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
|
||||
@if (title) {
|
||||
<label class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
|
||||
}
|
||||
@if (removable) {
|
||||
<button type="button" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div [class.col-md-9]="horizontal">
|
||||
<div>
|
||||
@ -23,28 +27,30 @@
|
||||
[loading]="loading"
|
||||
[typeahead]="documentsInput$"
|
||||
(change)="onChange(selectedDocuments)">
|
||||
<ng-template ng-label-tmp let-document="item">
|
||||
<div class="d-flex align-items-center">
|
||||
<svg class="sidebaricon" fill="currentColor" xmlns="http://www.w3.org/2000/svg" (click)="unselect(document)">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
<a routerLink="/documents/{{document.id}}" class="badge bg-light text-primary" (mousedown)="$event.stopImmediatePropagation();">
|
||||
<svg class="sidebaricon-sm me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
|
||||
</svg><span>{{document.title}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template ng-loadingspinner-tmp>
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
</ng-template>
|
||||
<ng-template ng-option-tmp let-document="item" let-index="index" let-search="searchTerm">
|
||||
<div>{{document.title}} <small class="text-muted">({{document.created | customDate:'shortDate'}})</small></div>
|
||||
</ng-template>
|
||||
</ng-select>
|
||||
<ng-template ng-label-tmp let-document="item">
|
||||
<div class="d-flex align-items-center">
|
||||
<svg class="sidebaricon" fill="currentColor" xmlns="http://www.w3.org/2000/svg" (click)="unselect(document)">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
<a routerLink="/documents/{{document.id}}" class="badge bg-light text-primary" (mousedown)="$event.stopImmediatePropagation();">
|
||||
<svg class="sidebaricon-sm me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
|
||||
</svg><span>{{document.title}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template ng-loadingspinner-tmp>
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
</ng-template>
|
||||
<ng-template ng-option-tmp let-document="item" let-index="index" let-search="searchTerm">
|
||||
<div>{{document.title}} <small class="text-muted">({{document.created | customDate:'shortDate'}})</small></div>
|
||||
</ng-template>
|
||||
</ng-select>
|
||||
</div>
|
||||
@if (hint) {
|
||||
<small class="form-text text-muted">{{hint}}</small>
|
||||
}
|
||||
</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,21 +2,27 @@
|
||||
<div class="row">
|
||||
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
|
||||
<label class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
|
||||
<button type="button" *ngIf="removable" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
<div class="position-relative" [class.col-md-9]="horizontal">
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<input #inputField type="number" class="form-control" [step]="step" [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>
|
||||
@if (removable) {
|
||||
<button type="button" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="invalid-feedback position-absolute top-100">
|
||||
{{error}}
|
||||
<div class="position-relative" [class.col-md-9]="horizontal">
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<input #inputField type="number" class="form-control" [step]="step" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error" [disabled]="disabled">
|
||||
@if (showAdd) {
|
||||
<button class="btn btn-outline-secondary" type="button" id="button-addon1" (click)="nextAsn()" [disabled]="disabled">+1</button>
|
||||
}
|
||||
</div>
|
||||
<div class="invalid-feedback position-absolute top-100">
|
||||
{{error}}
|
||||
</div>
|
||||
@if (hint) {
|
||||
<small class="form-text text-muted">{{hint}}</small>
|
||||
}
|
||||
</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,14 +2,18 @@
|
||||
<label class="form-label" [for]="inputId">{{title}}</label>
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<input #inputField [type]="showReveal && textVisible ? 'text' : 'password'" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (focus)="onFocus()" (focusout)="onFocusOut()" (change)="onChange(value)" [disabled]="disabled" [autocomplete]="autocomplete">
|
||||
<button *ngIf="showReveal" type="button" class="btn btn-outline-secondary" (click)="toggleVisibility()" i18n-title title="Show password" [disabled]="disabled || disableRevealToggle">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#eye" />
|
||||
</svg>
|
||||
</button>
|
||||
@if (showReveal) {
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="toggleVisibility()" i18n-title title="Show password" [disabled]="disabled || disableRevealToggle">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#eye" />
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
<small *ngIf="hint" class="form-text text-muted" [innerHTML]="hint | safeHtml"></small>
|
||||
@if (hint) {
|
||||
<small class="form-text text-muted" [innerHTML]="hint | safeHtml"></small>
|
||||
}
|
||||
</div>
|
||||
|
@ -1,68 +1,75 @@
|
||||
<ng-container *ngIf="!accordion">
|
||||
@if (!accordion) {
|
||||
<h5 i18n>Permissions</h5>
|
||||
<ng-container [ngTemplateOutlet]="permissionsForm"></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="accordion">
|
||||
<ngb-accordion #acc="ngbAccordion" activeIds="">
|
||||
<ngb-panel i18n-title title="Edit Permissions">
|
||||
<ng-template ngbPanelContent>
|
||||
<ng-container [ngTemplateOutlet]="permissionsForm"></ng-container>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
</ngb-accordion>
|
||||
</ng-container>
|
||||
}
|
||||
@if (accordion) {
|
||||
<div ngbAccordion activeIds="">
|
||||
<div ngbAccordionItem>
|
||||
<h2 ngbAccordionHeader>
|
||||
<button ngbAccordionButton i18n>Edit Permissions</button>
|
||||
</h2>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
<ng-template>
|
||||
<ng-container [ngTemplateOutlet]="permissionsForm"></ng-container>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<ng-template #permissionsForm>
|
||||
<div [formGroup]="form">
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2" i18n>Owner:</label>
|
||||
<div [formGroup]="form">
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2" i18n>Owner:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-input-select [items]="users" bindLabel="username" formControlName="owner" [allowNull]="true"></pngx-input-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-input-select [items]="users" bindLabel="username" formControlName="owner" [allowNull]="true"></pngx-input-select>
|
||||
<small class="form-text text-muted text-end d-block mt-n2" i18n>Objects without an owner can be viewed and edited by all users</small>
|
||||
<div formGroupName="set_permissions">
|
||||
<h6 class="mt-3" i18n>View</h6>
|
||||
<div formGroupName="view" class="mb-2">
|
||||
<div class="row mb-1">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2" i18n>Users:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-user type="view" formControlName="users"></pngx-permissions-user>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2" i18n>Groups:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-group type="view" formControlName="groups"></pngx-permissions-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h6 class="mt-4" i18n>Edit</h6>
|
||||
<div formGroupName="change">
|
||||
<div class="row mb-1">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2" i18n>Users:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-user type="change" formControlName="users"></pngx-permissions-user>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2" i18n>Groups:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-group type="change" formControlName="groups"></pngx-permissions-group>
|
||||
</div>
|
||||
</div>
|
||||
<small class="form-text text-muted text-end d-block" i18n>Edit permissions also grant viewing permissions</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<small class="form-text text-muted text-end d-block mt-n2" i18n>Objects without an owner can be viewed and edited by all users</small>
|
||||
<div formGroupName="set_permissions">
|
||||
<h6 class="mt-3" i18n>View</h6>
|
||||
<div formGroupName="view" class="mb-2">
|
||||
<div class="row mb-1">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2" i18n>Users:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-user type="view" formControlName="users"></pngx-permissions-user>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2" i18n>Groups:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-group type="view" formControlName="groups"></pngx-permissions-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h6 class="mt-4" i18n>Edit</h6>
|
||||
<div formGroupName="change">
|
||||
<div class="row mb-1">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2" i18n>Users:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-user type="change" formControlName="users"></pngx-permissions-user>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label d-block my-2" i18n>Groups:</label>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<pngx-permissions-group type="change" formControlName="groups"></pngx-permissions-group>
|
||||
</div>
|
||||
</div>
|
||||
<small class="form-text text-muted text-end d-block" i18n>Edit permissions also grant viewing permissions</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@ -1,57 +1,69 @@
|
||||
<div class="mb-3 paperless-input-select" [class.disabled]="disabled">
|
||||
<div class="row">
|
||||
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
|
||||
<label *ngIf="title" class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
|
||||
<button type="button" *ngIf="removable" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
<div [class.col-md-9]="horizontal">
|
||||
<div [class.input-group]="allowCreateNew || showFilter" [class.is-invalid]="error">
|
||||
<ng-select name="inputId" [(ngModel)]="value"
|
||||
[disabled]="disabled"
|
||||
[style.color]="textColor"
|
||||
[style.background]="backgroundColor"
|
||||
[class.private]="isPrivate"
|
||||
[clearable]="allowNull"
|
||||
[items]="items"
|
||||
[addTag]="allowCreateNew && addItemRef"
|
||||
addTagText="Add item"
|
||||
i18n-addTagText="Used for both types, correspondents, storage paths"
|
||||
[placeholder]="placeholder"
|
||||
[notFoundText]="notFoundText"
|
||||
[multiple]="multiple"
|
||||
[bindLabel]="bindLabel"
|
||||
bindValue="id"
|
||||
(change)="onChange(value)"
|
||||
(search)="onSearch($event)"
|
||||
(focus)="clearLastSearchTerm()"
|
||||
(clear)="clearLastSearchTerm()"
|
||||
(blur)="onBlur()">
|
||||
</ng-select>
|
||||
<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>
|
||||
</button>
|
||||
<button *ngIf="showFilter" class="btn btn-outline-secondary" type="button" (click)="onFilterDocuments()" [disabled]="isPrivate || this.value === null" title="{{ filterButtonTitle }}">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#filter" />
|
||||
</svg>
|
||||
</button>
|
||||
@if (title) {
|
||||
<label class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
|
||||
}
|
||||
@if (removable) {
|
||||
<button type="button" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
<div [class.col-md-9]="horizontal">
|
||||
<div [class.input-group]="allowCreateNew || showFilter" [class.is-invalid]="error">
|
||||
<ng-select name="inputId" [(ngModel)]="value"
|
||||
[disabled]="disabled"
|
||||
[style.color]="textColor"
|
||||
[style.background]="backgroundColor"
|
||||
[class.private]="isPrivate"
|
||||
[clearable]="allowNull"
|
||||
[items]="items"
|
||||
[addTag]="allowCreateNew && addItemRef"
|
||||
addTagText="Add item"
|
||||
i18n-addTagText="Used for both types, correspondents, storage paths"
|
||||
[placeholder]="placeholder"
|
||||
[notFoundText]="notFoundText"
|
||||
[multiple]="multiple"
|
||||
[bindLabel]="bindLabel"
|
||||
bindValue="id"
|
||||
(change)="onChange(value)"
|
||||
(search)="onSearch($event)"
|
||||
(focus)="clearLastSearchTerm()"
|
||||
(clear)="clearLastSearchTerm()"
|
||||
(blur)="onBlur()">
|
||||
</ng-select>
|
||||
@if (allowCreateNew) {
|
||||
<button 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>
|
||||
</button>
|
||||
}
|
||||
@if (showFilter) {
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="onFilterDocuments()" [disabled]="isPrivate || this.value === null" title="{{ filterButtonTitle }}">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#filter" />
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
@if (hint) {
|
||||
<small class="form-text text-muted">{{hint}}</small>
|
||||
}
|
||||
@if (getSuggestions().length > 0) {
|
||||
<small>
|
||||
<span i18n>Suggestions:</span>
|
||||
@for (s of getSuggestions(); track s) {
|
||||
<a (click)="value = s.id; onChange(value)" [routerLink]="[]">{{s.name}}</a>
|
||||
}
|
||||
</small>
|
||||
}
|
||||
</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
<small *ngIf="getSuggestions().length > 0">
|
||||
<span i18n>Suggestions:</span>
|
||||
<ng-container *ngFor="let s of getSuggestions()">
|
||||
<a (click)="value = s.id; onChange(value)" [routerLink]="[]">{{s.name}}</a>
|
||||
</ng-container>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -122,7 +122,7 @@ export class SelectComponent extends AbstractInputComponent<number> {
|
||||
}
|
||||
}
|
||||
|
||||
addItem(name: string) {
|
||||
addItem(name: string = null) {
|
||||
if (name) this.createNew.next(name)
|
||||
else this.createNew.next(this._lastSearchTerm)
|
||||
this.clearLastSearchTerm()
|
||||
|
@ -21,33 +21,45 @@
|
||||
<svg width="1.2em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
<pngx-tag *ngIf="item.id && tags" style="background-color: none;" [tag]="getTag(item.id)"></pngx-tag>
|
||||
@if (item.id && tags) {
|
||||
<pngx-tag style="background-color: none;" [tag]="getTag(item.id)"></pngx-tag>
|
||||
}
|
||||
</span>
|
||||
</ng-template>
|
||||
<ng-template ng-option-tmp let-item="item" let-index="index" let-search="searchTerm">
|
||||
<div class="tag-wrap">
|
||||
<pngx-tag *ngIf="item.id && tags" class="me-2" [tag]="getTag(item.id)"></pngx-tag>
|
||||
@if (item.id && tags) {
|
||||
<pngx-tag class="me-2" [tag]="getTag(item.id)"></pngx-tag>
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-select>
|
||||
<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>
|
||||
</button>
|
||||
<button *ngIf="showFilter" class="btn btn-outline-secondary" type="button" (click)="onFilterDocuments()" [disabled]="hasPrivate || this.value === null" i18n-title title="Filter documents with these Tags">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#filter" />
|
||||
</svg>
|
||||
</button>
|
||||
@if (allowCreate) {
|
||||
<button 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>
|
||||
</button>
|
||||
}
|
||||
@if (showFilter) {
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="onFilterDocuments()" [disabled]="hasPrivate || this.value === null" i18n-title title="Filter documents with these Tags">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#filter" />
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<small class="form-text text-muted" *ngIf="hint">{{hint}}</small>
|
||||
<small *ngIf="getSuggestions().length > 0" class="position-absolute top-100">
|
||||
<span i18n>Suggestions:</span>
|
||||
<ng-container *ngFor="let tag of getSuggestions()">
|
||||
<a (click)="addTag(tag.id)" [routerLink]="[]">{{tag?.name}}</a>
|
||||
</ng-container>
|
||||
</small>
|
||||
@if (hint) {
|
||||
<small class="form-text text-muted">{{hint}}</small>
|
||||
}
|
||||
@if (getSuggestions().length > 0) {
|
||||
<small class="position-absolute top-100">
|
||||
<span i18n>Suggestions:</span>
|
||||
@for (tag of getSuggestions(); track tag) {
|
||||
<a (click)="addTag(tag.id)" [routerLink]="[]">{{tag?.name}}</a>
|
||||
}
|
||||
</small>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,9 +1,4 @@
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
@ -15,7 +10,7 @@ import {
|
||||
DEFAULT_MATCHING_ALGORITHM,
|
||||
MATCH_ALL,
|
||||
} from 'src/app/data/matching-model'
|
||||
import { NgSelectComponent, NgSelectModule } from '@ng-select/ng-select'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { of } from 'rxjs'
|
||||
|
@ -2,18 +2,22 @@
|
||||
<div class="row">
|
||||
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
|
||||
<label class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
|
||||
<button type="button" *ngIf="removable" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
<div class="position-relative" [class.col-md-9]="horizontal">
|
||||
<input #inputField type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [disabled]="disabled" [autocomplete]="autocomplete">
|
||||
<small *ngIf="hint" class="form-text text-muted" [innerHTML]="hint | safeHtml"></small>
|
||||
<div class="invalid-feedback position-absolute top-100">
|
||||
{{error}}
|
||||
@if (removable) {
|
||||
<button type="button" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="position-relative" [class.col-md-9]="horizontal">
|
||||
<input #inputField type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [disabled]="disabled" [autocomplete]="autocomplete">
|
||||
@if (hint) {
|
||||
<small class="form-text text-muted" [innerHTML]="hint | safeHtml"></small>
|
||||
}
|
||||
<div class="invalid-feedback position-absolute top-100">
|
||||
{{error}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,12 +1,14 @@
|
||||
<div class="mb-3" [class.pb-3]="error">
|
||||
<div class="row">
|
||||
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
|
||||
<label class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
|
||||
<button type="button" *ngIf="removable" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<div class="row">
|
||||
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
|
||||
<label class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
|
||||
@if (removable) {
|
||||
<button type="button" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
</svg> <ng-container i18n>Remove</ng-container>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div [class.col-md-9]="horizontal">
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
@ -20,7 +22,9 @@
|
||||
{{error}}
|
||||
</div>
|
||||
</div>
|
||||
<small *ngIf="hint" class="form-text text-muted" [innerHTML]="hint | safeHtml"></small>
|
||||
@if (hint) {
|
||||
<small class="form-text text-muted" [innerHTML]="hint | safeHtml"></small>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,9 @@
|
||||
<div class="col-md text-truncate">
|
||||
<h3 class="text-truncate" style="line-height: 1.4">
|
||||
{{title}}
|
||||
<span *ngIf="subTitle" class="h6 mb-0 d-block d-md-inline fw-normal ms-md-3 text-truncate" style="line-height: 1.4">{{subTitle}}</span>
|
||||
@if (subTitle) {
|
||||
<span class="h6 mb-0 d-block d-md-inline fw-normal ms-md-3 text-truncate" style="line-height: 1.4">{{subTitle}}</span>
|
||||
}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="btn-toolbar col col-md-auto">
|
||||
|
@ -36,15 +36,6 @@ import { PDFSinglePageViewer } from 'pdfjs-dist/web/pdf_viewer'
|
||||
|
||||
PDFJS['verbosity'] = PDFJS.VerbosityLevel.ERRORS
|
||||
|
||||
// Yea this is a straight hack
|
||||
declare global {
|
||||
interface WeakKeyTypes {
|
||||
symbol: Object
|
||||
}
|
||||
|
||||
type WeakKey = WeakKeyTypes[keyof WeakKeyTypes]
|
||||
}
|
||||
|
||||
export enum RenderTextMode {
|
||||
DISABLED,
|
||||
ENABLED,
|
||||
|
@ -1,22 +1,24 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="cancelClicked()">
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="cancelClicked()">
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<p *ngIf="!object && message" class="mb-3" [innerHTML]="message | safeHtml"></p>
|
||||
@if (!object && message) {
|
||||
<p class="mb-3" [innerHTML]="message | safeHtml"></p>
|
||||
}
|
||||
|
||||
<form [formGroup]="form">
|
||||
<pngx-permissions-form [users]="users" formControlName="permissions_form"></pngx-permissions-form>
|
||||
</form>
|
||||
<form [formGroup]="form">
|
||||
<pngx-permissions-form [users]="users" formControlName="permissions_form"></pngx-permissions-form>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<ng-container *ngIf="!buttonsEnabled">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<span class="visually-hidden" i18n>Loading...</span>
|
||||
</ng-container>
|
||||
<button type="button" class="btn btn-outline-primary" (click)="cancelClicked()" [disabled]="!buttonsEnabled" i18n>Cancel</button>
|
||||
<button type="button" class="btn btn-primary" (click)="confirmClicked.emit(permissions)" [disabled]="!buttonsEnabled" i18n>Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@if (!buttonsEnabled) {
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<span class="visually-hidden" i18n>Loading...</span>
|
||||
}
|
||||
<button type="button" class="btn btn-outline-primary" (click)="cancelClicked()" [disabled]="!buttonsEnabled" i18n>Cancel</button>
|
||||
<button type="button" class="btn btn-primary" (click)="confirmClicked.emit(permissions)" [disabled]="!buttonsEnabled" i18n>Confirm</button>
|
||||
</div>
|
||||
|
@ -1,92 +1,106 @@
|
||||
<div class="btn-group w-100" ngbDropdown role="group">
|
||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="isActive ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock" />
|
||||
</svg>
|
||||
<div class="d-none d-sm-inline"> {{title}}</div>
|
||||
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge><span class="visually-hidden">selected</span>
|
||||
</button>
|
||||
<div class="dropdown-menu permission-filter-dropdown shadow py-0 w-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||
<div class="list-group list-group-flush">
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.NONE)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
<svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.NONE" fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>All</small>
|
||||
</div>
|
||||
</button>
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.SELF)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
<svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.SELF" fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>My documents</small>
|
||||
</div>
|
||||
</button>
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.NOT_SELF)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
<svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.NOT_SELF" fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>Shared with me</small>
|
||||
</div>
|
||||
</button>
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.SHARED_BY_ME)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
<svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.SHARED_BY_ME" fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>Shared by me</small>
|
||||
</div>
|
||||
</button>
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.UNOWNED)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
<svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.UNOWNED" fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>Unowned</small>
|
||||
</div>
|
||||
</button>
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.User }" class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
<svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.OTHERS" fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="me-1 w-100">
|
||||
<ng-select
|
||||
name="user"
|
||||
class="user-select small"
|
||||
[(ngModel)]="selectionModel.includeUsers"
|
||||
[disabled]="disabled"
|
||||
[clearable]="false"
|
||||
[items]="users"
|
||||
bindLabel="username"
|
||||
multiple="true"
|
||||
bindValue="id"
|
||||
placeholder="Users"
|
||||
i18n-placeholder
|
||||
(change)="onUserSelect()">
|
||||
</ng-select>
|
||||
</div>
|
||||
</button>
|
||||
<div *ngIf="selectionModel.ownerFilter === OwnerFilterType.NONE || selectionModel.ownerFilter === OwnerFilterType.NOT_SELF" class="list-group-item list-group-item-action d-flex align-items-center p-2 ps-3 border-bottom-0 border-start-0 border-end-0">
|
||||
<div class="form-check form-switch w-100">
|
||||
<input type="checkbox" class="form-check-input" id="hideUnowned" [(ngModel)]="this.selectionModel.hideUnowned" (change)="onChange()" [disabled]="disabled">
|
||||
<label class="form-check-label w-100" for="hideUnowned"><small i18n>Hide unowned</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="isActive ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock" />
|
||||
</svg>
|
||||
<div class="d-none d-sm-inline"> {{title}}</div>
|
||||
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge><span class="visually-hidden">selected</span>
|
||||
</button>
|
||||
<div class="dropdown-menu permission-filter-dropdown shadow py-0 w-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||
<div class="list-group list-group-flush">
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.NONE)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
@if (selectionModel.ownerFilter === OwnerFilterType.NONE) {
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
}
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>All</small>
|
||||
</div>
|
||||
</button>
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.SELF)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
@if (selectionModel.ownerFilter === OwnerFilterType.SELF) {
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
}
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>My documents</small>
|
||||
</div>
|
||||
</button>
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.NOT_SELF)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
@if (selectionModel.ownerFilter === OwnerFilterType.NOT_SELF) {
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
}
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>Shared with me</small>
|
||||
</div>
|
||||
</button>
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.SHARED_BY_ME)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
@if (selectionModel.ownerFilter === OwnerFilterType.SHARED_BY_ME) {
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
}
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>Shared by me</small>
|
||||
</div>
|
||||
</button>
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.UNOWNED)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
@if (selectionModel.ownerFilter === OwnerFilterType.UNOWNED) {
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
}
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>Unowned</small>
|
||||
</div>
|
||||
</button>
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.User }" class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
@if (selectionModel.ownerFilter === OwnerFilterType.OTHERS) {
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
}
|
||||
</div>
|
||||
<div class="me-1 w-100">
|
||||
<ng-select
|
||||
name="user"
|
||||
class="user-select small"
|
||||
[(ngModel)]="selectionModel.includeUsers"
|
||||
[disabled]="disabled"
|
||||
[clearable]="false"
|
||||
[items]="users"
|
||||
bindLabel="username"
|
||||
multiple="true"
|
||||
bindValue="id"
|
||||
placeholder="Users"
|
||||
i18n-placeholder
|
||||
(change)="onUserSelect()">
|
||||
</ng-select>
|
||||
</div>
|
||||
</button>
|
||||
@if (selectionModel.ownerFilter === OwnerFilterType.NONE || selectionModel.ownerFilter === OwnerFilterType.NOT_SELF) {
|
||||
<div class="list-group-item list-group-item-action d-flex align-items-center p-2 ps-3 border-bottom-0 border-start-0 border-end-0">
|
||||
<div class="form-check form-switch w-100">
|
||||
<input type="checkbox" class="form-check-input" id="hideUnowned" [(ngModel)]="this.selectionModel.hideUnowned" (change)="onChange()" [disabled]="disabled">
|
||||
<label class="form-check-label w-100" for="hideUnowned"><small i18n>Hide unowned</small></label>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,19 +9,23 @@
|
||||
<div class="col" i18n>Delete</div>
|
||||
<div class="col" i18n>View</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex" *ngFor="let type of PermissionType | keyvalue" [formGroupName]="type.key">
|
||||
<div class="col-3">{{type.key}}:</div>
|
||||
|
||||
<div class="col form-check form-check-inline form-switch" [ngbPopover]="inheritedWarning" [disablePopover]="!isInherited(type.key)" placement="left" triggers="mouseenter:mouseleave">
|
||||
<input type="checkbox" class="form-check-input" id="{{type.key}}_all" (change)="toggleAll($event, type.key)" [checked]="typesWithAllActions.has(type.key) || isInherited(type.key)" [attr.disabled]="disabled || isInherited(type.key) ? true : null">
|
||||
<label class="form-check-label visually-hidden" for="{{type.key}}_all" i18n>All</label>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let action of PermissionAction | keyvalue" class="col form-check form-check-inline" [ngbPopover]="inheritedWarning" [disablePopover]="!isInherited(type.key, action.key)" placement="left" triggers="mouseenter:mouseleave">
|
||||
<input type="checkbox" class="form-check-input" id="{{type.key}}_{{action.key}}" formControlName="{{action.key}}">
|
||||
<label class="form-check-label visually-hidden" for="{{type.key}}_{{action.key}}" i18n>{{action.key}}</label>
|
||||
</div>
|
||||
</li>
|
||||
<div *ngIf="error" class="invalid-feedback d-block">{{error}}</div>
|
||||
@for (type of PermissionType | keyvalue; track type) {
|
||||
<li class="list-group-item d-flex" [formGroupName]="type.key">
|
||||
<div class="col-3">{{type.key}}:</div>
|
||||
<div class="col form-check form-check-inline form-switch" [ngbPopover]="inheritedWarning" [disablePopover]="!isInherited(type.key)" placement="left" triggers="mouseenter:mouseleave">
|
||||
<input type="checkbox" class="form-check-input" id="{{type.key}}_all" (change)="toggleAll($event, type.key)" [checked]="typesWithAllActions.has(type.key) || isInherited(type.key)" [attr.disabled]="disabled || isInherited(type.key) ? true : null">
|
||||
<label class="form-check-label visually-hidden" for="{{type.key}}_all" i18n>All</label>
|
||||
</div>
|
||||
@for (action of PermissionAction | keyvalue; track action) {
|
||||
<div class="col form-check form-check-inline" [ngbPopover]="inheritedWarning" [disablePopover]="!isInherited(type.key, action.key)" placement="left" triggers="mouseenter:mouseleave">
|
||||
<input type="checkbox" class="form-check-input" id="{{type.key}}_{{action.key}}" formControlName="{{action.key}}">
|
||||
<label class="form-check-label visually-hidden" for="{{type.key}}_{{action.key}}" i18n>{{action.key}}</label>
|
||||
</div>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
@if (error) {
|
||||
<div class="invalid-feedback d-block">{{error}}</div>
|
||||
}
|
||||
</ul>
|
||||
</form>
|
||||
|
@ -1,22 +1,28 @@
|
||||
<div class="preview-popup-container">
|
||||
<div *ngIf="error; else noError" class="w-100 h-100 position-relative">
|
||||
<p class="fst-italic position-absolute top-50 start-50 translate-middle" i18n>Error loading preview</p>
|
||||
@if (error) {
|
||||
<div class="w-100 h-100 position-relative">
|
||||
<p class="fst-italic position-absolute top-50 start-50 translate-middle" i18n>Error loading preview</p>
|
||||
</div>
|
||||
<ng-template #noError>
|
||||
<object *ngIf="renderAsObject; else pngxViewer" [data]="previewURL | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf"></object>
|
||||
<ng-template #pngxViewer>
|
||||
<div *ngIf="requiresPassword" class="w-100 h-100 position-relative">
|
||||
<svg width="2em" height="2em" fill="currentColor" class="position-absolute top-50 start-50 translate-middle">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-earmark-lock"/>
|
||||
</svg>
|
||||
</div>
|
||||
<pngx-pdf-viewer *ngIf="!requiresPassword"
|
||||
[src]="previewURL"
|
||||
[original-size]="false"
|
||||
[show-borders]="true"
|
||||
[show-all]="true"
|
||||
(error)="onError($event)">
|
||||
</pngx-pdf-viewer>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
} @else {
|
||||
@if (renderAsObject) {
|
||||
<object [data]="previewURL | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf"></object>
|
||||
} @else {
|
||||
@if (requiresPassword) {
|
||||
<div class="w-100 h-100 position-relative">
|
||||
<svg width="2em" height="2em" fill="currentColor" class="position-absolute top-50 start-50 translate-middle">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-earmark-lock"/>
|
||||
</svg>
|
||||
</div>
|
||||
}
|
||||
@if (!requiresPassword) {
|
||||
<pngx-pdf-viewer
|
||||
[src]="previewURL"
|
||||
[original-size]="false"
|
||||
[show-borders]="true"
|
||||
[show-all]="true"
|
||||
(error)="onError($event)">
|
||||
</pngx-pdf-viewer>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
@ -1,53 +1,57 @@
|
||||
<form [formGroup]="form" (ngSubmit)="save()" autocomplete="off">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title" i18n>Edit Profile</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
</button>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title" i18n>Edit Profile</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pngx-input-text i18n-title title="Email" formControlName="email" (keyup)="onEmailKeyUp($event)" [error]="error?.email"></pngx-input-text>
|
||||
<div ngbAccordion>
|
||||
<div ngbAccordionItem="first" [collapsed]="!showEmailConfirm" class="border-0 bg-transparent">
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody class="p-0 pb-3">
|
||||
<pngx-input-text i18n-title title="Confirm Email" formControlName="email_confirm" (keyup)="onEmailConfirmKeyUp($event)" autocomplete="email" [error]="error?.email_confirm"></pngx-input-text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pngx-input-text i18n-title title="Email" formControlName="email" (keyup)="onEmailKeyUp($event)" [error]="error?.email"></pngx-input-text>
|
||||
<div ngbAccordion>
|
||||
<div ngbAccordionItem="first" [collapsed]="!showEmailConfirm" class="border-0 bg-transparent">
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody class="p-0 pb-3">
|
||||
<pngx-input-text i18n-title title="Confirm Email" formControlName="email_confirm" (keyup)="onEmailConfirmKeyUp($event)" autocomplete="email" [error]="error?.email_confirm"></pngx-input-text>
|
||||
</div>
|
||||
</div>
|
||||
<pngx-input-password i18n-title title="Password" formControlName="password" (keyup)="onPasswordKeyUp($event)" [showReveal]="true" autocomplete="current-password" [error]="error?.password"></pngx-input-password>
|
||||
<div ngbAccordion>
|
||||
<div ngbAccordionItem="first" [collapsed]="!showPasswordConfirm" class="border-0 bg-transparent">
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody class="p-0 pb-3">
|
||||
<pngx-input-password i18n-title title="Confirm Password" formControlName="password_confirm" (keyup)="onPasswordConfirmKeyUp($event)" autocomplete="new-password" [error]="error?.password_confirm"></pngx-input-password>
|
||||
</div>
|
||||
</div>
|
||||
<pngx-input-password i18n-title title="Password" formControlName="password" (keyup)="onPasswordKeyUp($event)" [showReveal]="true" autocomplete="current-password" [error]="error?.password"></pngx-input-password>
|
||||
<div ngbAccordion>
|
||||
<div ngbAccordionItem="first" [collapsed]="!showPasswordConfirm" class="border-0 bg-transparent">
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody class="p-0 pb-3">
|
||||
<pngx-input-password i18n-title title="Confirm Password" formControlName="password_confirm" (keyup)="onPasswordConfirmKeyUp($event)" autocomplete="new-password" [error]="error?.password_confirm"></pngx-input-password>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pngx-input-text i18n-title title="First name" formControlName="first_name" [error]="error?.first_name"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Last name" formControlName="last_name" [error]="error?.first_name"></pngx-input-text>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" i18n>API Auth Token</label>
|
||||
<div class="position-relative">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" formControlName="auth_token" readonly>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="copyAuthToken()" i18n-title title="Copy">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
@if (!copied) {
|
||||
<use xlink:href="assets/bootstrap-icons.svg#clipboard-fill" />
|
||||
}
|
||||
@if (copied) {
|
||||
<use xlink:href="assets/bootstrap-icons.svg#clipboard-check-fill" />
|
||||
}
|
||||
</svg><span class="visually-hidden" i18n>Copy</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="generateAuthToken()" i18n-title title="Regenerate auth token">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-repeat" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<span class="badge copied-badge bg-primary small fade ms-4 position-absolute top-50 translate-middle-y pe-none z-3" [class.show]="copied" i18n>Copied!</span>
|
||||
</div>
|
||||
<pngx-input-text i18n-title title="First name" formControlName="first_name" [error]="error?.first_name"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Last name" formControlName="last_name" [error]="error?.first_name"></pngx-input-text>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" i18n>API Auth Token</label>
|
||||
<div class="position-relative">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" formControlName="auth_token" readonly>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="copyAuthToken()" i18n-title title="Copy">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use *ngIf="!copied" xlink:href="assets/bootstrap-icons.svg#clipboard-fill" />
|
||||
<use *ngIf="copied" xlink:href="assets/bootstrap-icons.svg#clipboard-check-fill" />
|
||||
</svg><span class="visually-hidden" i18n>Copy</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="generateAuthToken()" i18n-title title="Regenerate auth token">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-repeat" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<span class="badge copied-badge bg-primary small fade ms-4 position-absolute top-50 translate-middle-y pe-none z-3" [class.show]="copied" i18n>Copied!</span>
|
||||
</div>
|
||||
<div class="form-text text-muted text-end fst-italic" i18n>Warning: changing the token cannot be undone</div>
|
||||
</div>
|
||||
<div class="form-text text-muted text-end fst-italic" i18n>Warning: changing the token cannot be undone</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||
|
@ -1,61 +1,79 @@
|
||||
<div ngbDropdown>
|
||||
<button class="btn btn-sm btn-outline-primary" id="shareLinksDropdown" [disabled]="disabled" ngbDropdownToggle>
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#link" />
|
||||
</svg>
|
||||
<div class="d-none d-sm-inline"> <ng-container i18n>Share Links</ng-container></div>
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="shareLinksDropdown" class="shadow share-links-dropdown">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li *ngIf="!shareLinks || shareLinks.length === 0" class="list-group-item fst-italic small text-center text-secondary" i18n>
|
||||
<button class="btn btn-sm btn-outline-primary" id="shareLinksDropdown" [disabled]="disabled" ngbDropdownToggle>
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#link" />
|
||||
</svg>
|
||||
<div class="d-none d-sm-inline"> <ng-container i18n>Share Links</ng-container></div>
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="shareLinksDropdown" class="shadow share-links-dropdown">
|
||||
<ul class="list-group list-group-flush">
|
||||
@if (!shareLinks || shareLinks.length === 0) {
|
||||
<li class="list-group-item fst-italic small text-center text-secondary" i18n>
|
||||
No existing links
|
||||
</li>
|
||||
<li class="list-group-item" *ngFor="let link of shareLinks">
|
||||
}
|
||||
@for (link of shareLinks; track link) {
|
||||
<li class="list-group-item">
|
||||
<div class="input-group input-group-sm w-100">
|
||||
<input type="text" class="form-control" aria-label="Share link" [value]="getShareUrl(link)" readonly>
|
||||
<span *ngIf="link.expiration" class="input-group-text">
|
||||
{{ getDaysRemaining(link) }}
|
||||
</span>
|
||||
@if (link.expiration) {
|
||||
<span class="input-group-text">
|
||||
{{ getDaysRemaining(link) }}
|
||||
</span>
|
||||
}
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="copy(link)">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use *ngIf="copied !== link.id" xlink:href="assets/bootstrap-icons.svg#clipboard-fill" />
|
||||
<use *ngIf="copied === link.id" xlink:href="assets/bootstrap-icons.svg#clipboard-check-fill" />
|
||||
</svg><span class="visually-hidden" i18n>Copy</span>
|
||||
</button>
|
||||
<button *ngIf="canShare(link)" type="button" class="btn btn-sm btn-outline-primary" (click)="share(link)">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#box-arrow-up" />
|
||||
</svg><span class="visually-hidden" i18n>Share</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="delete(link)">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
@if (copied !== link.id) {
|
||||
<use xlink:href="assets/bootstrap-icons.svg#clipboard-fill" />
|
||||
}
|
||||
@if (copied === link.id) {
|
||||
<use xlink:href="assets/bootstrap-icons.svg#clipboard-check-fill" />
|
||||
}
|
||||
</svg><span class="visually-hidden" i18n>Copy</span>
|
||||
</button>
|
||||
@if (canShare(link)) {
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="share(link)">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#box-arrow-up" />
|
||||
</svg><span class="visually-hidden" i18n>Share</span>
|
||||
</button>
|
||||
}
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="delete(link)">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg><span class="visually-hidden" i18n>Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
<span class="badge copied-badge bg-primary small fade ms-4 position-absolute top-50 translate-middle-y pe-none z-3" [class.show]="copied === link.id" i18n>Copied!</span>
|
||||
</li>
|
||||
<li class="list-group-item pt-3 pb-2">
|
||||
<div class="input-group input-group-sm w-100">
|
||||
<div class="form-check form-switch ms-auto small">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="versionSwitch" [disabled]="!hasArchiveVersion" [(ngModel)]="useArchiveVersion">
|
||||
<label class="form-check-label" for="versionSwitch" i18n>Share archive version</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group input-group-sm w-100 mt-2">
|
||||
<label class="input-group-text" for="addLink"><ng-container i18n>Expires</ng-container>:</label>
|
||||
<select class="form-select form-select-sm" [(ngModel)]="expirationDays">
|
||||
<option *ngFor="let option of EXPIRATION_OPTIONS" [ngValue]="option.value">{{ option.label }}</option>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline-primary ms-auto" type="button" (click)="createLink()" [disabled]="loading">
|
||||
<div *ngIf="loading" class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<svg *ngIf="!loading" class="buttonicon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus" />
|
||||
</svg>
|
||||
<ng-container i18n>Create</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</svg><span class="visually-hidden" i18n>Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
<span class="badge copied-badge bg-primary small fade ms-4 position-absolute top-50 translate-middle-y pe-none z-3" [class.show]="copied === link.id" i18n>Copied!</span>
|
||||
</li>
|
||||
}
|
||||
<li class="list-group-item pt-3 pb-2">
|
||||
<div class="input-group input-group-sm w-100">
|
||||
<div class="form-check form-switch ms-auto small">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="versionSwitch" [disabled]="!hasArchiveVersion" [(ngModel)]="useArchiveVersion">
|
||||
<label class="form-check-label" for="versionSwitch" i18n>Share archive version</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group input-group-sm w-100 mt-2">
|
||||
<label class="input-group-text" for="addLink"><ng-container i18n>Expires</ng-container>:</label>
|
||||
<select class="form-select form-select-sm" [(ngModel)]="expirationDays">
|
||||
@for (option of EXPIRATION_OPTIONS; track option) {
|
||||
<option [ngValue]="option.value">{{ option.label }}</option>
|
||||
}
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline-primary ms-auto" type="button" (click)="createLink()" [disabled]="loading">
|
||||
@if (loading) {
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
}
|
||||
@if (!loading) {
|
||||
<svg class="buttonicon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus" />
|
||||
</svg>
|
||||
}
|
||||
<ng-container i18n>Create</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,9 +1,15 @@
|
||||
<ng-container *ngIf="tag !== undefined; else privateTag" >
|
||||
<span *ngIf="!clickable" class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</span>
|
||||
<a [title]="linkTitle" *ngIf="clickable" class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</a>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #privateTag>
|
||||
<span *ngIf="!clickable" class="badge private" i18n>Private</span>
|
||||
<a [title]="linkTitle" *ngIf="clickable" class="badge private" i18n>Private</a>
|
||||
</ng-template>
|
||||
@if (tag !== undefined) {
|
||||
@if (!clickable) {
|
||||
<span class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</span>
|
||||
}
|
||||
@if (clickable) {
|
||||
<a [title]="linkTitle" class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</a>
|
||||
}
|
||||
} @else {
|
||||
@if (!clickable) {
|
||||
<span class="badge private" i18n>Private</span>
|
||||
}
|
||||
@if (clickable) {
|
||||
<a [title]="linkTitle" class="badge private" i18n>Private</a>
|
||||
}
|
||||
}
|
||||
|
@ -1,43 +1,58 @@
|
||||
<ngb-toast
|
||||
*ngFor="let toast of toasts"
|
||||
[autohide]="true" [delay]="toast.delay"
|
||||
[class]="toast.classname"
|
||||
[class.mb-2]="true"
|
||||
(shown)="onShow(toast)"
|
||||
(hidden)="toastService.closeToast(toast)">
|
||||
<ngb-progressbar class="position-absolute h-100 w-100 top-90 start-0 bottom-0 end-0 pe-none" type="dark" [max]="toast.delay" [value]="toast.delayRemaining"></ngb-progressbar>
|
||||
<span class="visually-hidden">{{ toast.delayRemaining / 1000 | number: '1.0-0' }} seconds</span>
|
||||
<div class="d-flex align-items-top">
|
||||
<svg class="sidebaricon-sm mt-1 me-2 flex-shrink-0" fill="currentColor">
|
||||
<use *ngIf="!toast.error" xlink:href="assets/bootstrap-icons.svg#info-circle"/>
|
||||
<use *ngIf="toast.error" xlink:href="assets/bootstrap-icons.svg#exclamation-triangle"/>
|
||||
</svg>
|
||||
<div>
|
||||
<p class="mb-0">{{toast.content}}</p>
|
||||
<details *ngIf="toast.error">
|
||||
<div class="mt-2">
|
||||
<dl class="row mb-0" *ngIf="isDetailedError(toast.error)">
|
||||
<dt class="col-sm-3 fw-normal text-end">URL</dt>
|
||||
<dd class="col-sm-9">{{ toast.error.url }}</dd>
|
||||
<dt class="col-sm-3 fw-normal text-end" i18n>Status</dt>
|
||||
<dd class="col-sm-9">{{ toast.error.status }} <em>{{ toast.error.statusText }}</em></dd>
|
||||
<dt class="col-sm-3 fw-normal text-end" i18n>Error</dt>
|
||||
<dd class="col-sm-9">{{ getErrorText(toast.error) }}</dd>
|
||||
</dl>
|
||||
<div class="row">
|
||||
<div class="col offset-sm-3">
|
||||
<button class="btn btn-sm btn-outline-dark" (click)="copyError(toast.error)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use *ngIf="!copied" xlink:href="assets/bootstrap-icons.svg#clipboard" />
|
||||
<use *ngIf="copied" xlink:href="assets/bootstrap-icons.svg#clipboard-check" />
|
||||
</svg> <ng-container i18n>Copy Raw Error</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@for (toast of toasts; track toast) {
|
||||
<ngb-toast
|
||||
[autohide]="true" [delay]="toast.delay"
|
||||
[class]="toast.classname"
|
||||
[class.mb-2]="true"
|
||||
(shown)="onShow(toast)"
|
||||
(hidden)="toastService.closeToast(toast)">
|
||||
<ngb-progressbar class="position-absolute h-100 w-100 top-90 start-0 bottom-0 end-0 pe-none" type="dark" [max]="toast.delay" [value]="toast.delayRemaining"></ngb-progressbar>
|
||||
<span class="visually-hidden">{{ toast.delayRemaining / 1000 | number: '1.0-0' }} seconds</span>
|
||||
<div class="d-flex align-items-top">
|
||||
<svg class="sidebaricon-sm mt-1 me-2 flex-shrink-0" fill="currentColor">
|
||||
@if (!toast.error) {
|
||||
<use xlink:href="assets/bootstrap-icons.svg#info-circle"/>
|
||||
}
|
||||
@if (toast.error) {
|
||||
<use xlink:href="assets/bootstrap-icons.svg#exclamation-triangle"/>
|
||||
}
|
||||
</svg>
|
||||
<div>
|
||||
<p class="mb-0">{{toast.content}}</p>
|
||||
@if (toast.error) {
|
||||
<details>
|
||||
<div class="mt-2">
|
||||
@if (isDetailedError(toast.error)) {
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-3 fw-normal text-end">URL</dt>
|
||||
<dd class="col-sm-9">{{ toast.error.url }}</dd>
|
||||
<dt class="col-sm-3 fw-normal text-end" i18n>Status</dt>
|
||||
<dd class="col-sm-9">{{ toast.error.status }} <em>{{ toast.error.statusText }}</em></dd>
|
||||
<dt class="col-sm-3 fw-normal text-end" i18n>Error</dt>
|
||||
<dd class="col-sm-9">{{ getErrorText(toast.error) }}</dd>
|
||||
</dl>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col offset-sm-3">
|
||||
<button class="btn btn-sm btn-outline-dark" (click)="copyError(toast.error)">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
@if (!copied) {
|
||||
<use xlink:href="assets/bootstrap-icons.svg#clipboard" />
|
||||
}
|
||||
@if (copied) {
|
||||
<use xlink:href="assets/bootstrap-icons.svg#clipboard-check" />
|
||||
}
|
||||
</svg> <ng-container i18n>Copy Raw Error</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
}
|
||||
@if (toast.action) {
|
||||
<p class="mb-0 mt-2"><button class="btn btn-sm btn-outline-secondary" (click)="toastService.closeToast(toast); toast.action()">{{toast.actionName}}</button></p>
|
||||
}
|
||||
</div>
|
||||
</details>
|
||||
<p class="mb-0 mt-2" *ngIf="toast.action"><button class="btn btn-sm btn-outline-secondary" (click)="toastService.closeToast(toast); toast.action()">{{toast.actionName}}</button></p>
|
||||
</div>
|
||||
<button type="button" class="btn-close ms-auto flex-shrink-0" data-bs-dismiss="toast" aria-label="Close" (click)="toastService.closeToast(toast);"></button>
|
||||
</div>
|
||||
</ngb-toast>
|
||||
<button type="button" class="btn-close ms-auto flex-shrink-0" data-bs-dismiss="toast" aria-label="Close" (click)="toastService.closeToast(toast);"></button>
|
||||
</div>
|
||||
</ngb-toast>
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import { Clipboard } from '@angular/cdk/clipboard'
|
||||
})
|
||||
export class ToastsComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private toastService: ToastService,
|
||||
public toastService: ToastService,
|
||||
private clipboard: Clipboard
|
||||
) {}
|
||||
|
||||
|
@ -8,25 +8,31 @@
|
||||
cdkDropList
|
||||
[cdkDropListDisabled]="settingsService.globalDropzoneActive"
|
||||
(cdkDropListDropped)="onDrop($event)"
|
||||
>
|
||||
<div *ngIf="savedViewService.loading" class="col">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</div>
|
||||
>
|
||||
@if (savedViewService.loading) {
|
||||
<div class="col">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div *ngIf="settingsService.offerTour()" class="col">
|
||||
<pngx-welcome-widget (dismiss)="completeTour()"></pngx-welcome-widget>
|
||||
</div>
|
||||
@if (settingsService.offerTour()) {
|
||||
<div class="col">
|
||||
<pngx-welcome-widget (dismiss)="completeTour()"></pngx-welcome-widget>
|
||||
</div>
|
||||
}
|
||||
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
|
||||
<div *ngFor="let v of dashboardViews" class="col">
|
||||
<pngx-saved-view-widget
|
||||
[savedView]="v"
|
||||
(cdkDragStarted)="onDragStart($event)"
|
||||
(cdkDragEnded)="onDragEnd($event)"
|
||||
>
|
||||
</pngx-saved-view-widget>
|
||||
</div>
|
||||
@for (v of dashboardViews; track v) {
|
||||
<div class="col">
|
||||
<pngx-saved-view-widget
|
||||
[savedView]="v"
|
||||
(cdkDragStarted)="onDragStart($event)"
|
||||
(cdkDragEnded)="onDragEnd($event)"
|
||||
>
|
||||
</pngx-saved-view-widget>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,54 +3,63 @@
|
||||
[title]="savedView.name"
|
||||
[loading]="loading"
|
||||
[draggable]="savedView"
|
||||
>
|
||||
>
|
||||
|
||||
<a *ngIf="documents.length" class="btn-link text-decoration-none" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a>
|
||||
@if (documents.length) {
|
||||
<a class="btn-link text-decoration-none" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a>
|
||||
}
|
||||
|
||||
<table *ngIf="documents.length; else empty" content class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" i18n>Created</th>
|
||||
<th scope="col" i18n>Title</th>
|
||||
<th scope="col" class="d-none d-md-table-cell" i18n>Tags</th>
|
||||
<th scope="col" class="d-none d-md-table-cell" i18n>Correspondent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let doc of documents" (mouseleave)="maybeClosePopover()">
|
||||
<td class="py-2 py-md-3"><a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.created_date | customDate}}</a></td>
|
||||
<td class="py-2 py-md-3">
|
||||
<a routerLink="/documents/{{doc.id}}" title="Edit" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a>
|
||||
</td>
|
||||
<td class="py-2 py-md-3 d-none d-md-table-cell">
|
||||
<pngx-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ms-1" (click)="clickTag(t, $event)"></pngx-tag>
|
||||
</td>
|
||||
<td class="position-relative py-2 py-md-3 d-none d-md-table-cell">
|
||||
<a *ngIf="doc.correspondent !== null" class="btn-link text-dark text-decoration-none py-2 py-md-3" routerLink="/documents" [queryParams]="getCorrespondentQueryParams(doc.correspondent)">{{(doc.correspondent$ | async)?.name}}</a>
|
||||
<div class="btn-group position-absolute top-50 end-0 translate-middle-y">
|
||||
<a [href]="getPreviewUrl(doc)" title="View Preview" i18n-title target="_blank" class="btn px-4 btn-dark border-dark-subtle"
|
||||
[ngbPopover]="previewContent" [popoverTitle]="doc.title | documentTitle"
|
||||
autoClose="true" popoverClass="shadow popover-preview" container="body" (mouseenter)="mouseEnterPreviewButton(doc)" (mouseleave)="mouseLeavePreviewButton()" #popover="ngbPopover">
|
||||
<svg class="buttonicon-xs" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#eye"/>
|
||||
</svg>
|
||||
</a>
|
||||
<ng-template #previewContent>
|
||||
<pngx-preview-popup [document]="doc" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()"></pngx-preview-popup>
|
||||
</ng-template>
|
||||
<a [href]="getDownloadUrl(doc)" class="btn px-4 btn-dark border-dark-subtle" title="Download" i18n-title (click)="$event.stopPropagation()">
|
||||
<svg class="buttonicon-xs" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#download"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ng-template #empty>
|
||||
@if (documents.length) {
|
||||
<table content class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" i18n>Created</th>
|
||||
<th scope="col" i18n>Title</th>
|
||||
<th scope="col" class="d-none d-md-table-cell" i18n>Tags</th>
|
||||
<th scope="col" class="d-none d-md-table-cell" i18n>Correspondent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (doc of documents; track doc) {
|
||||
<tr (mouseleave)="maybeClosePopover()">
|
||||
<td class="py-2 py-md-3"><a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.created_date | customDate}}</a></td>
|
||||
<td class="py-2 py-md-3">
|
||||
<a routerLink="/documents/{{doc.id}}" title="Edit" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a>
|
||||
</td>
|
||||
<td class="py-2 py-md-3 d-none d-md-table-cell">
|
||||
@for (t of doc.tags$ | async; track t) {
|
||||
<pngx-tag [tag]="t" class="ms-1" (click)="clickTag(t, $event)"></pngx-tag>
|
||||
}
|
||||
</td>
|
||||
<td class="position-relative py-2 py-md-3 d-none d-md-table-cell">
|
||||
@if (doc.correspondent !== null) {
|
||||
<a class="btn-link text-dark text-decoration-none py-2 py-md-3" routerLink="/documents" [queryParams]="getCorrespondentQueryParams(doc.correspondent)">{{(doc.correspondent$ | async)?.name}}</a>
|
||||
}
|
||||
<div class="btn-group position-absolute top-50 end-0 translate-middle-y">
|
||||
<a [href]="getPreviewUrl(doc)" title="View Preview" i18n-title target="_blank" class="btn px-4 btn-dark border-dark-subtle"
|
||||
[ngbPopover]="previewContent" [popoverTitle]="doc.title | documentTitle"
|
||||
autoClose="true" popoverClass="shadow popover-preview" container="body" (mouseenter)="mouseEnterPreviewButton(doc)" (mouseleave)="mouseLeavePreviewButton()" #popover="ngbPopover">
|
||||
<svg class="buttonicon-xs" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#eye"/>
|
||||
</svg>
|
||||
</a>
|
||||
<ng-template #previewContent>
|
||||
<pngx-preview-popup [document]="doc" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()"></pngx-preview-popup>
|
||||
</ng-template>
|
||||
<a [href]="getDownloadUrl(doc)" class="btn px-4 btn-dark border-dark-subtle" title="Download" i18n-title (click)="$event.stopPropagation()">
|
||||
<svg class="buttonicon-xs" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#download"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
} @else {
|
||||
<p i18n class="text-center text-muted mb-0 fst-italic">No documents</p>
|
||||
</ng-template>
|
||||
}
|
||||
|
||||
|
||||
</pngx-widget-frame>
|
||||
|
@ -1,10 +1,12 @@
|
||||
<pngx-widget-frame title="Statistics" [loading]="loading" i18n-title>
|
||||
<ng-container content>
|
||||
<div class="list-group border-light">
|
||||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" title="Go to inbox" i18n-title href="javascript:void(0)" *ngIf="statistics?.documents_inbox !== null" (click)="goToInbox()">
|
||||
<ng-container i18n>Documents in inbox</ng-container>:
|
||||
<span class="badge rounded-pill" [class.bg-primary]="statistics?.documents_inbox > 0" [class.bg-muted]="statistics?.documents_inbox === 0">{{statistics?.documents_inbox}}</span>
|
||||
</a>
|
||||
@if (statistics?.documents_inbox !== null) {
|
||||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" title="Go to inbox" i18n-title href="javascript:void(0)" (click)="goToInbox()">
|
||||
<ng-container i18n>Documents in inbox</ng-container>:
|
||||
<span class="badge rounded-pill" [class.bg-primary]="statistics?.documents_inbox > 0" [class.bg-muted]="statistics?.documents_inbox === 0">{{statistics?.documents_inbox}}</span>
|
||||
</a>
|
||||
}
|
||||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" title="Go to documents" i18n-title routerLink="/documents/">
|
||||
<ng-container i18n>Total documents</ng-container>:
|
||||
<span class="badge bg-primary rounded-pill">{{statistics?.documents_total}}</span>
|
||||
@ -13,60 +15,74 @@
|
||||
<ng-container i18n>Total characters</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.character_count | number}}</span>
|
||||
</div>
|
||||
<div *ngIf="statistics?.document_file_type_counts?.length > 1" class="list-group-item filetypes">
|
||||
<div class="d-flex justify-content-between align-items-center my-2">
|
||||
<div class="progress flex-grow-1">
|
||||
<div *ngFor="let filetype of statistics?.document_file_type_counts; let i = index; let last = last"
|
||||
class="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
[ngbPopover]="getFileTypeName(filetype)"
|
||||
i18n-ngbPopover
|
||||
triggers="mouseenter:mouseleave"
|
||||
[attr.aria-label]="getFileTypeName(filetype)"
|
||||
[class.me-1px]="!last"
|
||||
[style.width]="getFileTypePercent(filetype) + '%'"
|
||||
[style.opacity]="getItemOpacity(i)"
|
||||
[attr.aria-valuenow]="getFileTypePercent(filetype)"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
@if (statistics?.document_file_type_counts?.length > 1) {
|
||||
<div class="list-group-item filetypes">
|
||||
<div class="d-flex justify-content-between align-items-center my-2">
|
||||
<div class="progress flex-grow-1">
|
||||
@for (filetype of statistics?.document_file_type_counts; track filetype; let i = $index; let last = $last) {
|
||||
<div
|
||||
class="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
[ngbPopover]="getFileTypeName(filetype)"
|
||||
i18n-ngbPopover
|
||||
triggers="mouseenter:mouseleave"
|
||||
[attr.aria-label]="getFileTypeName(filetype)"
|
||||
[class.me-1px]="!last"
|
||||
[style.width]="getFileTypePercent(filetype) + '%'"
|
||||
[style.opacity]="getItemOpacity(i)"
|
||||
[attr.aria-valuenow]="getFileTypePercent(filetype)"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-items-start">
|
||||
<div class="d-flex" *ngFor="let filetype of statistics?.document_file_type_counts; let i = index">
|
||||
<div class="text-nowrap me-2">
|
||||
<span class="badge rounded-pill bg-primary d-inline-block p-0 me-1" [style.opacity]="getItemOpacity(i)"></span>
|
||||
<small class="text-nowrap"><span class="fw-bold">{{ getFileTypeExtension(filetype) }}</span> <span class="text-muted">({{getFileTypePercent(filetype) | number: '1.0-1'}}%)</span></small>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-items-start">
|
||||
@for (filetype of statistics?.document_file_type_counts; track filetype; let i = $index) {
|
||||
<div class="d-flex">
|
||||
<div class="text-nowrap me-2">
|
||||
<span class="badge rounded-pill bg-primary d-inline-block p-0 me-1" [style.opacity]="getItemOpacity(i)"></span>
|
||||
<small class="text-nowrap"><span class="fw-bold">{{ getFileTypeExtension(filetype) }}</span> <span class="text-muted">({{getFileTypePercent(filetype) | number: '1.0-1'}}%)</span></small>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="list-group border-light mt-3">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }">
|
||||
<a *ngIf="statistics?.tag_count > 0" class="list-group-item d-flex justify-content-between align-items-center" routerLink="/tags/">
|
||||
<ng-container i18n>Tags</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.tag_count | number}}</span>
|
||||
</a>
|
||||
@if (statistics?.tag_count > 0) {
|
||||
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/tags/">
|
||||
<ng-container i18n>Tags</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.tag_count | number}}</span>
|
||||
</a>
|
||||
}
|
||||
</ng-container>
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
|
||||
<a *ngIf="statistics?.correspondent_count > 0" class="list-group-item d-flex justify-content-between align-items-center" routerLink="/correspondents/">
|
||||
<ng-container i18n>Correspondents</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.correspondent_count | number}}</span>
|
||||
</a>
|
||||
@if (statistics?.correspondent_count > 0) {
|
||||
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/correspondents/">
|
||||
<ng-container i18n>Correspondents</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.correspondent_count | number}}</span>
|
||||
</a>
|
||||
}
|
||||
</ng-container>
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
|
||||
<a *ngIf="statistics?.document_type_count > 0" class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documenttypes/">
|
||||
<ng-container i18n>Document Types</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.document_type_count | number}}</span>
|
||||
</a>
|
||||
@if (statistics?.document_type_count > 0) {
|
||||
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documenttypes/">
|
||||
<ng-container i18n>Document Types</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.document_type_count | number}}</span>
|
||||
</a>
|
||||
}
|
||||
</ng-container>
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
|
||||
<a *ngIf="statistics?.storage_path_count > 0" class="list-group-item d-flex justify-content-between align-items-center" routerLink="/storagepaths/">
|
||||
<ng-container i18n>Storage Paths</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.storage_path_count | number}}</span>
|
||||
</a>
|
||||
@if (statistics?.storage_path_count > 0) {
|
||||
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/storagepaths/">
|
||||
<ng-container i18n>Storage Paths</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.storage_path_count | number}}</span>
|
||||
</a>
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
@ -8,32 +8,44 @@
|
||||
<div class="fixed-bottom p-2 p-md-4" [ngClass]="slimSidebarEnabled ? 'col-slim' : 'offset-md-3 offset-lg-2'">
|
||||
<div class="row d-flex justify-content-end">
|
||||
<div class="col col-lg-4 col-xl-3 d-flex px-4 justify-content-between align-items-center">
|
||||
<p class="m-0 small text-muted" *ngIf="getStatus().length > 0">{{getStatusSummary()}}</p>
|
||||
<a *ngIf="getStatusCompleted().length > 0" class="btn-link" (click)="dismissCompleted()" [routerLink]="[]" >
|
||||
<span class="me-1" i18n="This button dismisses all status messages about processed documents on the dashboard (failed and successful)">Dismiss completed</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-check2-all" viewBox="0 0 16 16">
|
||||
<path d="M12.354 4.354a.5.5 0 0 0-.708-.708L5 10.293 1.854 7.146a.5.5 0 1 0-.708.708l3.5 3.5a.5.5 0 0 0 .708 0l7-7zm-4.208 7l-.896-.897.707-.707.543.543 6.646-6.647a.5.5 0 0 1 .708.708l-7 7a.5.5 0 0 1-.708 0z"/>
|
||||
<path d="M5.354 7.146l.896.897-.707.707-.897-.896a.5.5 0 1 1 .708-.708z"/>
|
||||
</svg>
|
||||
</a>
|
||||
@if (getStatus().length > 0) {
|
||||
<p class="m-0 small text-muted">{{getStatusSummary()}}</p>
|
||||
}
|
||||
@if (getStatusCompleted().length > 0) {
|
||||
<a class="btn-link" (click)="dismissCompleted()" [routerLink]="[]" >
|
||||
<span class="me-1" i18n="This button dismisses all status messages about processed documents on the dashboard (failed and successful)">Dismiss completed</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-check2-all" viewBox="0 0 16 16">
|
||||
<path d="M12.354 4.354a.5.5 0 0 0-.708-.708L5 10.293 1.854 7.146a.5.5 0 1 0-.708.708l3.5 3.5a.5.5 0 0 0 .708 0l7-7zm-4.208 7l-.896-.897.707-.707.543.543 6.646-6.647a.5.5 0 0 1 .708.708l-7 7a.5.5 0 0 1-.708 0z"/>
|
||||
<path d="M5.354 7.146l.896.897-.707.707-.897-.896a.5.5 0 1 1 .708-.708z"/>
|
||||
</svg>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div *ngFor="let status of getStatus()">
|
||||
<ng-container [ngTemplateOutlet]="consumerAlert" [ngTemplateOutletContext]="{ $implicit: status }"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="getStatusHidden().length" class="alerts-hidden">
|
||||
<p *ngIf="!alertsExpanded" class="mt-3 mb-0 text-center">
|
||||
<span i18n="This is shown as a summary line when there are more than 5 document in the processing pipeline.">{getStatusHidden().length, plural, =1 {One more document} other {{{getStatusHidden().length}} more documents}}</span>
|
||||
•
|
||||
<a [routerLink]="[]" (click)="alertsExpanded = !alertsExpanded" aria-controls="hiddenAlerts" [attr.aria-expanded]="alertsExpanded" i18n>Show all</a>
|
||||
</p>
|
||||
<div #hiddenAlerts="ngbCollapse" [(ngbCollapse)]="!alertsExpanded">
|
||||
<div *ngFor="let status of getStatusHidden()">
|
||||
@for (status of getStatus(); track status) {
|
||||
<div>
|
||||
<ng-container [ngTemplateOutlet]="consumerAlert" [ngTemplateOutletContext]="{ $implicit: status }"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (getStatusHidden().length) {
|
||||
<div class="alerts-hidden">
|
||||
@if (!alertsExpanded) {
|
||||
<p class="mt-3 mb-0 text-center">
|
||||
<span i18n="This is shown as a summary line when there are more than 5 document in the processing pipeline.">{getStatusHidden().length, plural, =1 {One more document} other {{{getStatusHidden().length}} more documents}}</span>
|
||||
•
|
||||
<a [routerLink]="[]" (click)="alertsExpanded = !alertsExpanded" aria-controls="hiddenAlerts" [attr.aria-expanded]="alertsExpanded" i18n>Show all</a>
|
||||
</p>
|
||||
}
|
||||
<div #hiddenAlerts="ngbCollapse" [(ngbCollapse)]="!alertsExpanded">
|
||||
@for (status of getStatusHidden(); track status) {
|
||||
<div>
|
||||
<ng-container [ngTemplateOutlet]="consumerAlert" [ngTemplateOutletContext]="{ $implicit: status }"></ng-container>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</pngx-widget-frame>
|
||||
|
||||
@ -42,17 +54,23 @@
|
||||
<div class="col col-lg-4 col-xl-3">
|
||||
<ngb-alert type="secondary" class="mt-2 mb-0" [dismissible]="isFinished(status)" (closed)="dismiss(status)">
|
||||
<h6 class="alert-heading">{{status.filename}}</h6>
|
||||
<p class="mb-0 pb-1" *ngIf="!isFinished(status) || (isFinished(status) && !status.documentId)">{{status.message}}</p>
|
||||
@if (!isFinished(status) || (isFinished(status) && !status.documentId)) {
|
||||
<p class="mb-0 pb-1">{{status.message}}</p>
|
||||
}
|
||||
<ngb-progressbar [value]="status.getProgress()" [max]="1" [type]="getStatusColor(status)"></ngb-progressbar>
|
||||
<div *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||
<div *ngIf="isFinished(status)">
|
||||
<button *ngIf="status.documentId" class="btn btn-sm btn-outline-primary btn-open" routerLink="/documents/{{status.documentId}}" (click)="dismiss(status)">
|
||||
<small i18n>Open document</small>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-arrow-right-short" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@if (isFinished(status)) {
|
||||
<div>
|
||||
@if (status.documentId) {
|
||||
<button class="btn btn-sm btn-outline-primary btn-open" routerLink="/documents/{{status.documentId}}" (click)="dismiss(status)">
|
||||
<small i18n>Open document</small>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-arrow-right-short" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z"/>
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ngb-alert>
|
||||
</div>
|
||||
|
@ -27,6 +27,24 @@ import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
|
||||
import { UploadFileWidgetComponent } from './upload-file-widget.component'
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop'
|
||||
|
||||
const FAILED_STATUSES = [new FileStatus()]
|
||||
const WORKING_STATUSES = [new FileStatus(), new FileStatus()]
|
||||
const STARTED_STATUSES = [new FileStatus(), new FileStatus(), new FileStatus()]
|
||||
const SUCCESS_STATUSES = [
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
]
|
||||
const DEFAULT_STATUSES = [
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
]
|
||||
|
||||
describe('UploadFileWidgetComponent', () => {
|
||||
let component: UploadFileWidgetComponent
|
||||
let fixture: ComponentFixture<UploadFileWidgetComponent>
|
||||
@ -150,41 +168,22 @@ function mockConsumerStatuses(consumerStatusService) {
|
||||
.mockImplementation((phase) => {
|
||||
switch (phase) {
|
||||
case FileStatusPhase.FAILED:
|
||||
return [new FileStatus()]
|
||||
return FAILED_STATUSES
|
||||
case FileStatusPhase.WORKING:
|
||||
return [new FileStatus(), new FileStatus()]
|
||||
return WORKING_STATUSES
|
||||
case FileStatusPhase.STARTED:
|
||||
return [new FileStatus(), new FileStatus(), new FileStatus()]
|
||||
return STARTED_STATUSES
|
||||
case FileStatusPhase.SUCCESS:
|
||||
return [
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
]
|
||||
return SUCCESS_STATUSES
|
||||
case FileStatusPhase.UPLOADING:
|
||||
return [partialUpload1, partialUpload2]
|
||||
default:
|
||||
return [
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
]
|
||||
return DEFAULT_STATUSES
|
||||
}
|
||||
})
|
||||
jest
|
||||
.spyOn(consumerStatusService, 'getConsumerStatusNotCompleted')
|
||||
.mockImplementation(() => {
|
||||
return [
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
]
|
||||
return DEFAULT_STATUSES
|
||||
})
|
||||
}
|
||||
|
@ -2,17 +2,19 @@
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex">
|
||||
<div *ngIf="draggable" class="ms-n2 me-1" cdkDragHandle>
|
||||
<svg class="sidebaricon text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#grip-vertical"/>
|
||||
</svg>
|
||||
</div>
|
||||
@if (draggable) {
|
||||
<div class="ms-n2 me-1" cdkDragHandle>
|
||||
<svg class="sidebaricon text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#grip-vertical"/>
|
||||
</svg>
|
||||
</div>
|
||||
}
|
||||
<h6 class="card-title mb-0">{{title}}</h6>
|
||||
</div>
|
||||
<ng-container *ngIf="loading">
|
||||
@if (loading) {
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
</ng-container>
|
||||
}
|
||||
<ng-content select ="[header-buttons]"></ng-content>
|
||||
</div>
|
||||
|
||||
|
@ -1,43 +1,47 @@
|
||||
<pngx-page-header [(title)]="title">
|
||||
<ng-container *ngIf="getContentType() === 'application/pdf' && !useNativePdfViewer">
|
||||
<div class="input-group input-group-sm me-2 d-none d-md-flex">
|
||||
<div class="input-group-text" i18n>Page</div>
|
||||
<input class="form-control flex-grow-0 w-auto" type="number" min="1" [max]="previewNumPages" [(ngModel)]="previewCurrentPage" />
|
||||
<div class="input-group-text" i18n>of {{previewNumPages}}</div>
|
||||
</div>
|
||||
<div class="input-group input-group-sm me-5 d-none d-md-flex">
|
||||
<button class="btn btn-outline-secondary" (click)="decreaseZoom()" i18n>-</button>
|
||||
<select class="form-select" (change)="onZoomSelect($event)">
|
||||
<option *ngFor="let setting of zoomSettings" [value]="setting" [selected]="previewZoomSetting === setting">
|
||||
{{ getZoomSettingTitle(setting) }}
|
||||
</option>
|
||||
</select>
|
||||
<button class="btn btn-outline-secondary" (click)="increaseZoom()" i18n>+</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
@if (getContentType() === 'application/pdf' && !useNativePdfViewer) {
|
||||
<div class="input-group input-group-sm me-2 d-none d-md-flex">
|
||||
<div class="input-group-text" i18n>Page</div>
|
||||
<input class="form-control flex-grow-0 w-auto" type="number" min="1" [max]="previewNumPages" [(ngModel)]="previewCurrentPage" />
|
||||
<div class="input-group-text" i18n>of {{previewNumPages}}</div>
|
||||
</div>
|
||||
<div class="input-group input-group-sm me-5 d-none d-md-flex">
|
||||
<button class="btn btn-outline-secondary" (click)="decreaseZoom()" i18n>-</button>
|
||||
<select class="form-select" (change)="onZoomSelect($event)">
|
||||
@for (setting of zoomSettings; track setting) {
|
||||
<option [value]="setting" [selected]="previewZoomSetting === setting">
|
||||
{{ getZoomSettingTitle(setting) }}
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
<button class="btn btn-outline-secondary" (click)="increaseZoom()" i18n>+</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<button type="button" class="btn btn-sm btn-outline-danger me-4" (click)="delete()" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }">
|
||||
<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>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger me-4" (click)="delete()" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }">
|
||||
<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>
|
||||
</button>
|
||||
|
||||
<div class="btn-group me-2">
|
||||
<a [href]="downloadUrl" class="btn btn-sm btn-outline-primary">
|
||||
<svg class="buttonicon me-md-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#download" />
|
||||
</svg><span class="d-none d-lg-inline ps-1" i18n>Download</span>
|
||||
<a [href]="downloadUrl" class="btn btn-sm btn-outline-primary">
|
||||
<svg class="buttonicon me-md-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#download" />
|
||||
</svg><span class="d-none d-lg-inline ps-1" i18n>Download</span>
|
||||
</a>
|
||||
|
||||
<div class="btn-group" ngbDropdown role="group" *ngIf="metadata?.has_archive_version">
|
||||
@if (metadata?.has_archive_version) {
|
||||
<div class="btn-group" ngbDropdown role="group">
|
||||
<button class="btn btn-sm btn-outline-primary dropdown-toggle" ngbDropdownToggle></button>
|
||||
<div class="dropdown-menu shadow" ngbDropdownMenu>
|
||||
<a ngbDropdownItem [href]="downloadOriginalUrl" i18n>Download original</a>
|
||||
<a ngbDropdownItem [href]="downloadOriginalUrl" i18n>Download original</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="ms-auto" ngbDropdown>
|
||||
<div class="ms-auto" ngbDropdown>
|
||||
<button class="btn btn-sm btn-outline-primary me-2" id="actionsDropdown" ngbDropdownToggle>
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#three-dots" />
|
||||
@ -46,240 +50,287 @@
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="actionsDropdown" class="shadow">
|
||||
<button ngbDropdownItem (click)="redoOcr()" [disabled]="!userCanEdit">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" />
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" />
|
||||
</svg><span class="ps-1" i18n>Redo OCR</span>
|
||||
</button>
|
||||
</button>
|
||||
|
||||
<button ngbDropdownItem (click)="moreLike()">
|
||||
<button ngbDropdownItem (click)="moreLike()">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#diagram-3" />
|
||||
</svg><span class="ps-1" i18n>More like this</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<use xlink:href="assets/bootstrap-icons.svg#diagram-3" />
|
||||
</svg><span class="ps-1" i18n>More like this</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pngx-custom-fields-dropdown
|
||||
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }"
|
||||
class="me-2"
|
||||
[documentId]="documentId"
|
||||
[disabled]="!userIsOwner"
|
||||
[existingFields]="document?.custom_fields"
|
||||
(created)="refreshCustomFields()"
|
||||
(added)="addField($event)">
|
||||
</pngx-custom-fields-dropdown>
|
||||
<pngx-custom-fields-dropdown
|
||||
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }"
|
||||
class="me-2"
|
||||
[documentId]="documentId"
|
||||
[disabled]="!userIsOwner"
|
||||
[existingFields]="document?.custom_fields"
|
||||
(created)="refreshCustomFields()"
|
||||
(added)="addField($event)">
|
||||
</pngx-custom-fields-dropdown>
|
||||
|
||||
<pngx-share-links-dropdown [documentId]="documentId" [hasArchiveVersion]="!!document?.archived_file_name" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ShareLink }"></pngx-share-links-dropdown>
|
||||
</pngx-page-header>
|
||||
<pngx-share-links-dropdown [documentId]="documentId" [hasArchiveVersion]="!!document?.archived_file_name" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ShareLink }"></pngx-share-links-dropdown>
|
||||
</pngx-page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-xl-4 mb-4">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-xl-4 mb-4">
|
||||
|
||||
<form [formGroup]='documentForm' (ngSubmit)="save()">
|
||||
<form [formGroup]='documentForm' (ngSubmit)="save()">
|
||||
|
||||
<div class="btn-toolbar mb-1 pb-3 border-bottom">
|
||||
<div class="btn-toolbar mb-1 pb-3 border-bottom">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Close" (click)="close()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Previous" (click)="previousDoc()" [disabled]="!hasPrevious()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-left" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Next" (click)="nextDoc()" [disabled]="!hasNext()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-right" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Close" (click)="close()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Previous" (click)="previousDoc()" [disabled]="!hasPrevious()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-left" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Next" (click)="nextDoc()" [disabled]="!hasNext()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-right" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ng-container *ngTemplateOutlet="saveButtons"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul ngbNav #nav="ngbNav" class="nav-underline flex-nowrap flex-md-wrap overflow-auto" (navChange)="onNavChange($event)" [(activeId)]="activeNavID">
|
||||
<ul ngbNav #nav="ngbNav" class="nav-underline flex-nowrap flex-md-wrap overflow-auto" (navChange)="onNavChange($event)" [(activeId)]="activeNavID">
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Details">
|
||||
<a ngbNavLink i18n>Details</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div>
|
||||
<pngx-input-text #inputTitle i18n-title title="Title" formControlName="title" [horizontal]="true" (keyup)="titleKeyUp($event)" [error]="error?.title"></pngx-input-text>
|
||||
<pngx-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" [horizontal]="true" formControlName='archive_serial_number'></pngx-input-number>
|
||||
<pngx-input-date i18n-title title="Date created" formControlName="created_date" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
[error]="error?.created_date"></pngx-input-date>
|
||||
<pngx-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
(createNew)="createCorrespondent($event)" [suggestions]="suggestions?.correspondents" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }"></pngx-input-select>
|
||||
<pngx-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
(createNew)="createDocumentType($event)" [suggestions]="suggestions?.document_types" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }"></pngx-input-select>
|
||||
<pngx-input-select [items]="storagePaths" i18n-title title="Storage path" formControlName="storage_path" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
(createNew)="createStoragePath($event)" [suggestions]="suggestions?.storage_paths" i18n-placeholder placeholder="Default" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }"></pngx-input-select>
|
||||
<pngx-input-tags formControlName="tags" [suggestions]="suggestions?.tags" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }"></pngx-input-tags>
|
||||
<ng-container *ngFor="let fieldInstance of document?.custom_fields; let i = index">
|
||||
<div [formGroup]="customFieldFormFields.controls[i]" [ngSwitch]="getCustomFieldFromInstance(fieldInstance)?.data_type">
|
||||
<pngx-input-text *ngSwitchCase="PaperlessCustomFieldDataType.String" formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [error]="getCustomFieldError(i)"></pngx-input-text>
|
||||
<pngx-input-date *ngSwitchCase="PaperlessCustomFieldDataType.Date" formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [error]="getCustomFieldError(i)"></pngx-input-date>
|
||||
<pngx-input-number *ngSwitchCase="PaperlessCustomFieldDataType.Integer" formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [showAdd]="false" [error]="getCustomFieldError(i)"></pngx-input-number>
|
||||
<pngx-input-number *ngSwitchCase="PaperlessCustomFieldDataType.Float" formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [showAdd]="false" [step]=".1" [error]="getCustomFieldError(i)"></pngx-input-number>
|
||||
<pngx-input-number *ngSwitchCase="PaperlessCustomFieldDataType.Monetary" formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [showAdd]="false" [step]=".01" [error]="getCustomFieldError(i)"></pngx-input-number>
|
||||
<pngx-input-check *ngSwitchCase="PaperlessCustomFieldDataType.Boolean" formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-check>
|
||||
<pngx-input-url *ngSwitchCase="PaperlessCustomFieldDataType.Url" formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [error]="getCustomFieldError(i)"></pngx-input-url>
|
||||
<pngx-input-document-link *ngSwitchCase="PaperlessCustomFieldDataType.DocumentLink" formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [parentDocumentID]="documentId" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [error]="getCustomFieldError(i)"></pngx-input-document-link>
|
||||
</div>
|
||||
</ng-container>
|
||||
<a ngbNavLink i18n>Details</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div>
|
||||
<pngx-input-text #inputTitle i18n-title title="Title" formControlName="title" [horizontal]="true" (keyup)="titleKeyUp($event)" [error]="error?.title"></pngx-input-text>
|
||||
<pngx-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" [horizontal]="true" formControlName='archive_serial_number'></pngx-input-number>
|
||||
<pngx-input-date i18n-title title="Date created" formControlName="created_date" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
[error]="error?.created_date"></pngx-input-date>
|
||||
<pngx-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
(createNew)="createCorrespondent($event)" [suggestions]="suggestions?.correspondents" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }"></pngx-input-select>
|
||||
<pngx-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
(createNew)="createDocumentType($event)" [suggestions]="suggestions?.document_types" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }"></pngx-input-select>
|
||||
<pngx-input-select [items]="storagePaths" i18n-title title="Storage path" formControlName="storage_path" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
(createNew)="createStoragePath($event)" [suggestions]="suggestions?.storage_paths" i18n-placeholder placeholder="Default" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }"></pngx-input-select>
|
||||
<pngx-input-tags formControlName="tags" [suggestions]="suggestions?.tags" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }"></pngx-input-tags>
|
||||
@for (fieldInstance of document?.custom_fields; track fieldInstance; let i = $index) {
|
||||
<div [formGroup]="customFieldFormFields.controls[i]">
|
||||
@switch (getCustomFieldFromInstance(fieldInstance)?.data_type) {
|
||||
@case (PaperlessCustomFieldDataType.String) {
|
||||
<pngx-input-text formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [error]="getCustomFieldError(i)"></pngx-input-text>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Date) {
|
||||
<pngx-input-date formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [error]="getCustomFieldError(i)"></pngx-input-date>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Integer) {
|
||||
<pngx-input-number formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [showAdd]="false" [error]="getCustomFieldError(i)"></pngx-input-number>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Float) {
|
||||
<pngx-input-number formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [showAdd]="false" [step]=".1" [error]="getCustomFieldError(i)"></pngx-input-number>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Monetary) {
|
||||
<pngx-input-number formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [showAdd]="false" [step]=".01" [error]="getCustomFieldError(i)"></pngx-input-number>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Boolean) {
|
||||
<pngx-input-check formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-check>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Url) {
|
||||
<pngx-input-url formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [error]="getCustomFieldError(i)"></pngx-input-url>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.DocumentLink) {
|
||||
<pngx-input-document-link formControlName="value" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [parentDocumentID]="documentId" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [error]="getCustomFieldError(i)"></pngx-input-document-link>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="d-flex border-top pt-3">
|
||||
<ng-container *ngTemplateOutlet="saveButtons"></ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="d-flex border-top pt-3">
|
||||
<ng-container *ngTemplateOutlet="saveButtons"></ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Content">
|
||||
<a ngbNavLink i18n>Content</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div>
|
||||
<textarea class="form-control" id="content" rows="20" formControlName='content' [class.rtl]="isRTL"></textarea>
|
||||
</div>
|
||||
</ng-template>
|
||||
<a ngbNavLink i18n>Content</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div>
|
||||
<textarea class="form-control" id="content" rows="20" formControlName='content' [class.rtl]="isRTL"></textarea>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Metadata">
|
||||
<a ngbNavLink i18n>Metadata</a>
|
||||
<ng-template ngbNavContent>
|
||||
<a ngbNavLink i18n>Metadata</a>
|
||||
<ng-template ngbNavContent>
|
||||
|
||||
<table class="table table-borderless" *ngIf="document">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td i18n>Date modified</td>
|
||||
<td>{{document.modified | customDate}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Date added</td>
|
||||
<td>{{document.added | customDate}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Media filename</td>
|
||||
<td>{{metadata?.media_filename}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original filename</td>
|
||||
<td>{{metadata?.original_filename}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original MD5 checksum</td>
|
||||
<td>{{metadata?.original_checksum}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original file size</td>
|
||||
<td>{{metadata?.original_size | fileSize}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original mime type</td>
|
||||
<td>{{metadata?.original_mime_type}}</td>
|
||||
</tr>
|
||||
<tr *ngIf="metadata?.has_archive_version">
|
||||
<td i18n>Archive MD5 checksum</td>
|
||||
<td>{{metadata?.archive_checksum}}</td>
|
||||
</tr>
|
||||
<tr *ngIf="metadata?.has_archive_version">
|
||||
<td i18n>Archive file size</td>
|
||||
<td>{{metadata?.archive_size | fileSize}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@if (document) {
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td i18n>Date modified</td>
|
||||
<td>{{document.modified | customDate}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Date added</td>
|
||||
<td>{{document.added | customDate}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Media filename</td>
|
||||
<td>{{metadata?.media_filename}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original filename</td>
|
||||
<td>{{metadata?.original_filename}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original MD5 checksum</td>
|
||||
<td>{{metadata?.original_checksum}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original file size</td>
|
||||
<td>{{metadata?.original_size | fileSize}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original mime type</td>
|
||||
<td>{{metadata?.original_mime_type}}</td>
|
||||
</tr>
|
||||
@if (metadata?.has_archive_version) {
|
||||
<tr>
|
||||
<td i18n>Archive MD5 checksum</td>
|
||||
<td>{{metadata?.archive_checksum}}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (metadata?.has_archive_version) {
|
||||
<tr>
|
||||
<td i18n>Archive file size</td>
|
||||
<td>{{metadata?.archive_size | fileSize}}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
<pngx-metadata-collapse i18n-title title="Original document metadata" [metadata]="metadata.original_metadata" *ngIf="metadata?.original_metadata?.length > 0"></pngx-metadata-collapse>
|
||||
<pngx-metadata-collapse i18n-title title="Archived document metadata" [metadata]="metadata.archive_metadata" *ngIf="metadata?.archive_metadata?.length > 0"></pngx-metadata-collapse>
|
||||
@if (metadata?.original_metadata?.length > 0) {
|
||||
<pngx-metadata-collapse i18n-title title="Original document metadata" [metadata]="metadata.original_metadata"></pngx-metadata-collapse>
|
||||
}
|
||||
@if (metadata?.archive_metadata?.length > 0) {
|
||||
<pngx-metadata-collapse i18n-title title="Archived document metadata" [metadata]="metadata.archive_metadata"></pngx-metadata-collapse>
|
||||
}
|
||||
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Preview" class="d-md-none">
|
||||
<a ngbNavLink i18n>Preview</a>
|
||||
<ng-template ngbNavContent *ngIf="!pdfPreview.offsetParent">
|
||||
<ng-container *ngTemplateOutlet="previewContent"></ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Notes" *ngIf="notesEnabled">
|
||||
<a class="text-nowrap" ngbNavLink i18n>Notes <span *ngIf="document?.notes.length" class="badge text-bg-secondary ms-1">{{document.notes.length}}</span></a>
|
||||
<a ngbNavLink i18n>Preview</a>
|
||||
@if (!pdfPreview.offsetParent) {
|
||||
<ng-template ngbNavContent>
|
||||
<pngx-document-notes [documentId]="documentId" [notes]="document?.notes" [addDisabled]="!userCanEdit" (updated)="notesUpdated($event)"></pngx-document-notes>
|
||||
<ng-container *ngTemplateOutlet="previewContent"></ng-container>
|
||||
</ng-template>
|
||||
}
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Permissions" *ngIf="showPermissions">
|
||||
@if (notesEnabled) {
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Notes">
|
||||
<a class="text-nowrap" ngbNavLink i18n>Notes @if (document?.notes.length) {
|
||||
<span class="badge text-bg-secondary ms-1">{{document.notes.length}}</span>
|
||||
}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<pngx-document-notes [documentId]="documentId" [notes]="document?.notes" [addDisabled]="!userCanEdit" (updated)="notesUpdated($event)"></pngx-document-notes>
|
||||
</ng-template>
|
||||
</li>
|
||||
}
|
||||
|
||||
@if (showPermissions) {
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Permissions">
|
||||
<a ngbNavLink i18n>Permissions</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="mb-3">
|
||||
<pngx-permissions-form [users]="users" formControlName="permissions_form"></pngx-permissions-form>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<pngx-permissions-form [users]="users" formControlName="permissions_form"></pngx-permissions-form>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
<div [ngbNavOutlet]="nav" class="mt-3"></div>
|
||||
<div [ngbNavOutlet]="nav" class="mt-3"></div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-xl-8 mb-3 d-none d-md-block position-relative" #pdfPreview>
|
||||
<ng-container *ngTemplateOutlet="previewContent"></ng-container>
|
||||
<ng-container *ngIf="renderAsPlainText">
|
||||
<div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div>
|
||||
</ng-container>
|
||||
<div *ngIf="requiresPassword" class="password-prompt">
|
||||
<form>
|
||||
<input autocomplete="" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-xl-8 mb-3 d-none d-md-block position-relative" #pdfPreview>
|
||||
<ng-container *ngTemplateOutlet="previewContent"></ng-container>
|
||||
@if (renderAsPlainText) {
|
||||
<div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div>
|
||||
}
|
||||
@if (requiresPassword) {
|
||||
<div class="password-prompt">
|
||||
<form>
|
||||
<input autocomplete="" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<ng-template #saveButtons>
|
||||
<div class="btn-group ms-auto">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||
<button type="submit" class="order-3 btn btn-sm btn-primary" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save</button>
|
||||
@if (hasNext()) {
|
||||
<button type="button" class="order-1 btn btn-sm btn-outline-primary" (click)="saveEditNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & next</button>
|
||||
}
|
||||
@if (!hasNext()) {
|
||||
<button type="button" class="order-2 btn btn-sm btn-outline-primary" (click)="save(true)" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & close</button>
|
||||
}
|
||||
</ng-container>
|
||||
<button type="button" class="order-0 btn btn-sm btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Discard</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #saveButtons>
|
||||
<div class="btn-group ms-auto">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||
<button type="submit" class="order-3 btn btn-sm btn-primary" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save</button>
|
||||
<button *ngIf="hasNext()" type="button" class="order-1 btn btn-sm btn-outline-primary" (click)="saveEditNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & next</button>
|
||||
<button *ngIf="!hasNext()" type="button" class="order-2 btn btn-sm btn-outline-primary" (click)="save(true)" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & close</button>
|
||||
</ng-container>
|
||||
<button type="button" class="order-0 btn btn-sm btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Discard</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #previewContent>
|
||||
<div *ngIf="!metadata" class="w-100 h-100 d-flex align-items-center justify-content-center">
|
||||
<div>
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="getContentType() === 'application/pdf'">
|
||||
<div class="preview-sticky pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer">
|
||||
<pngx-pdf-viewer
|
||||
[src]="{ url: previewUrl, password: password }"
|
||||
[original-size]="false"
|
||||
[show-borders]="true"
|
||||
[show-all]="true"
|
||||
[(page)]="previewCurrentPage"
|
||||
[zoom-scale]="previewZoomScale"
|
||||
[zoom]="previewZoomSetting"
|
||||
(error)="onError($event)"
|
||||
(after-load-complete)="pdfPreviewLoaded($event)">
|
||||
</pngx-pdf-viewer>
|
||||
</div>
|
||||
<ng-template #nativePdfViewer>
|
||||
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="renderAsPlainText">
|
||||
<div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div>
|
||||
</ng-container>
|
||||
<div *ngIf="showPasswordField" class="password-prompt">
|
||||
<form>
|
||||
<input autocomplete="" autofocus="true" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
|
||||
</form>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #previewContent>
|
||||
@if (!metadata) {
|
||||
<div class="w-100 h-100 d-flex align-items-center justify-content-center">
|
||||
<div>
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (getContentType() === 'application/pdf') {
|
||||
@if (!useNativePdfViewer ) {
|
||||
<div class="preview-sticky pdf-viewer-container">
|
||||
<pngx-pdf-viewer
|
||||
[src]="{ url: previewUrl, password: password }"
|
||||
[original-size]="false"
|
||||
[show-borders]="true"
|
||||
[show-all]="true"
|
||||
[(page)]="previewCurrentPage"
|
||||
[zoom-scale]="previewZoomScale"
|
||||
[zoom]="previewZoomSetting"
|
||||
(error)="onError($event)"
|
||||
(after-load-complete)="pdfPreviewLoaded($event)">
|
||||
</pngx-pdf-viewer>
|
||||
</div>
|
||||
} @else {
|
||||
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
|
||||
}
|
||||
}
|
||||
@if (renderAsPlainText) {
|
||||
<div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div>
|
||||
}
|
||||
@if (showPasswordField) {
|
||||
<div class="password-prompt">
|
||||
<form>
|
||||
<input autocomplete="" autofocus="true" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
|
@ -1,23 +1,29 @@
|
||||
<h6>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm me-2"
|
||||
(click)="expand = !expand">
|
||||
<svg class="buttonicon" fill="currentColor" *ngIf="!expand">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#caret-down" />
|
||||
(click)="expand = !expand">
|
||||
@if (!expand) {
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#caret-down" />
|
||||
</svg>
|
||||
<svg class="buttonicon" fill="currentColor" *ngIf="expand">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#caret-up" />
|
||||
}
|
||||
@if (expand) {
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#caret-up" />
|
||||
</svg>
|
||||
}
|
||||
</button>
|
||||
{{title}}
|
||||
</h6>
|
||||
|
||||
<div #collapse="ngbCollapse" [(ngbCollapse)]="!expand">
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr *ngFor="let m of metadata">
|
||||
<td>{{m.prefix}}:{{m.key}}</td>
|
||||
<td class="metadata-column">{{m.value}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
@for (m of metadata; track m) {
|
||||
<tr>
|
||||
<td>{{m.prefix}}:{{m.key}}</td>
|
||||
<td class="metadata-column">{{m.value}}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -3,140 +3,144 @@
|
||||
<button class="btn btn-sm btn-outline-secondary" (click)="list.selectNone()">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#slash-circle" />
|
||||
</svg> <ng-container i18n>Cancel</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2" role="group" aria-label="Select">
|
||||
<label class="me-2" i18n>Select:</label>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-outline-primary" (click)="list.selectPage()">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-earmark-check" />
|
||||
</svg> <ng-container i18n>Page</ng-container>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary" (click)="list.selectAll()">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check-all" />
|
||||
</svg> <ng-container i18n>All</ng-container>
|
||||
</svg> <ng-container i18n>Cancel</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||
<label class="me-2" i18n>Edit:</label>
|
||||
<pngx-filterable-dropdown title="Tags" icon="tag-fill" i18n-title
|
||||
filterPlaceholder="Filter tags" i18n-filterPlaceholder
|
||||
[items]="tags"
|
||||
[disabled]="!userCanEditAll"
|
||||
[editing]="true"
|
||||
[manyToOne]="true"
|
||||
[applyOnClose]="applyOnClose"
|
||||
(opened)="openTagsDropdown()"
|
||||
[(selectionModel)]="tagSelectionModel"
|
||||
[documentCounts]="tagDocumentCounts"
|
||||
(apply)="setTags($event)">
|
||||
</pngx-filterable-dropdown>
|
||||
<pngx-filterable-dropdown title="Correspondent" icon="person-fill" i18n-title
|
||||
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
|
||||
[items]="correspondents"
|
||||
[disabled]="!userCanEditAll"
|
||||
[editing]="true"
|
||||
[applyOnClose]="applyOnClose"
|
||||
(opened)="openCorrespondentDropdown()"
|
||||
[(selectionModel)]="correspondentSelectionModel"
|
||||
[documentCounts]="correspondentDocumentCounts"
|
||||
(apply)="setCorrespondents($event)">
|
||||
</pngx-filterable-dropdown>
|
||||
<pngx-filterable-dropdown title="Document type" icon="file-earmark-fill" i18n-title
|
||||
filterPlaceholder="Filter document types" i18n-filterPlaceholder
|
||||
[items]="documentTypes"
|
||||
[disabled]="!userCanEditAll"
|
||||
[editing]="true"
|
||||
[applyOnClose]="applyOnClose"
|
||||
(opened)="openDocumentTypeDropdown()"
|
||||
[(selectionModel)]="documentTypeSelectionModel"
|
||||
[documentCounts]="documentTypeDocumentCounts"
|
||||
(apply)="setDocumentTypes($event)">
|
||||
</pngx-filterable-dropdown>
|
||||
<pngx-filterable-dropdown title="Storage path" icon="folder-fill" i18n-title
|
||||
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
|
||||
[items]="storagePaths"
|
||||
[disabled]="!userCanEditAll"
|
||||
[editing]="true"
|
||||
[applyOnClose]="applyOnClose"
|
||||
(opened)="openStoragePathDropdown()"
|
||||
[(selectionModel)]="storagePathsSelectionModel"
|
||||
[documentCounts]="storagePathDocumentCounts"
|
||||
(apply)="setStoragePaths($event)">
|
||||
</pngx-filterable-dropdown>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 ms-auto">
|
||||
<div class="btn-toolbar">
|
||||
|
||||
<button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="setPermissions()" [disabled]="!userOwnsAll || !userCanEditAll">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock" />
|
||||
</svg><div class="d-none d-sm-inline"> <ng-container i18n>Permissions</ng-container></div>
|
||||
</button>
|
||||
|
||||
<div ngbDropdown>
|
||||
<button class="btn btn-sm btn-outline-primary" id="dropdownSelect" ngbDropdownToggle>
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#three-dots" />
|
||||
</svg>
|
||||
<div class="d-none d-sm-inline"> <ng-container i18n>Actions</ng-container></div>
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="dropdownSelect" class="shadow">
|
||||
<button ngbDropdownItem (click)="redoOcrSelected()" [disabled]="!userCanEditAll" i18n>Redo OCR</button>
|
||||
<div class="d-flex align-items-center gap-2" role="group" aria-label="Select">
|
||||
<label class="me-2" i18n>Select:</label>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-outline-primary" (click)="list.selectPage()">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-earmark-check" />
|
||||
</svg> <ng-container i18n>Page</ng-container>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary" (click)="list.selectAll()">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check-all" />
|
||||
</svg> <ng-container i18n>All</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-sm btn-outline-primary" [disabled]="awaitingDownload" (click)="downloadSelected()">
|
||||
<svg *ngIf="!awaitingDownload" class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-down" />
|
||||
</svg>
|
||||
<div *ngIf="awaitingDownload" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="visually-hidden">Preparing download...</span>
|
||||
<div class="d-flex align-items-center gap-2" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||
<label class="me-2" i18n>Edit:</label>
|
||||
<pngx-filterable-dropdown title="Tags" icon="tag-fill" i18n-title
|
||||
filterPlaceholder="Filter tags" i18n-filterPlaceholder
|
||||
[items]="tags"
|
||||
[disabled]="!userCanEditAll"
|
||||
[editing]="true"
|
||||
[manyToOne]="true"
|
||||
[applyOnClose]="applyOnClose"
|
||||
(opened)="openTagsDropdown()"
|
||||
[(selectionModel)]="tagSelectionModel"
|
||||
[documentCounts]="tagDocumentCounts"
|
||||
(apply)="setTags($event)">
|
||||
</pngx-filterable-dropdown>
|
||||
<pngx-filterable-dropdown title="Correspondent" icon="person-fill" i18n-title
|
||||
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
|
||||
[items]="correspondents"
|
||||
[disabled]="!userCanEditAll"
|
||||
[editing]="true"
|
||||
[applyOnClose]="applyOnClose"
|
||||
(opened)="openCorrespondentDropdown()"
|
||||
[(selectionModel)]="correspondentSelectionModel"
|
||||
[documentCounts]="correspondentDocumentCounts"
|
||||
(apply)="setCorrespondents($event)">
|
||||
</pngx-filterable-dropdown>
|
||||
<pngx-filterable-dropdown title="Document type" icon="file-earmark-fill" i18n-title
|
||||
filterPlaceholder="Filter document types" i18n-filterPlaceholder
|
||||
[items]="documentTypes"
|
||||
[disabled]="!userCanEditAll"
|
||||
[editing]="true"
|
||||
[applyOnClose]="applyOnClose"
|
||||
(opened)="openDocumentTypeDropdown()"
|
||||
[(selectionModel)]="documentTypeSelectionModel"
|
||||
[documentCounts]="documentTypeDocumentCounts"
|
||||
(apply)="setDocumentTypes($event)">
|
||||
</pngx-filterable-dropdown>
|
||||
<pngx-filterable-dropdown title="Storage path" icon="folder-fill" i18n-title
|
||||
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
|
||||
[items]="storagePaths"
|
||||
[disabled]="!userCanEditAll"
|
||||
[editing]="true"
|
||||
[applyOnClose]="applyOnClose"
|
||||
(opened)="openStoragePathDropdown()"
|
||||
[(selectionModel)]="storagePathsSelectionModel"
|
||||
[documentCounts]="storagePathDocumentCounts"
|
||||
(apply)="setStoragePaths($event)">
|
||||
</pngx-filterable-dropdown>
|
||||
</div>
|
||||
<div class="d-none d-sm-inline"> <ng-container i18n>Download</ng-container></div>
|
||||
</button>
|
||||
<div ngbDropdown class="me-2 d-flex btn-group" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle-split rounded-end" ngbDropdownToggle></button>
|
||||
<div ngbDropdownMenu aria-labelledby="dropdownSelect" class="shadow">
|
||||
<form [formGroup]="downloadForm" class="px-3 py-1">
|
||||
<p class="mb-1" i18n>Include:</p>
|
||||
<div class="form-group ps-3 mb-2">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="downloadFileType_archive" formControlName="downloadFileTypeArchive" />
|
||||
<label class="form-check-label" for="downloadFileType_archive" i18n>
|
||||
<div class="d-flex align-items-center gap-2 ms-auto">
|
||||
<div class="btn-toolbar">
|
||||
|
||||
<button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="setPermissions()" [disabled]="!userOwnsAll || !userCanEditAll">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock" />
|
||||
</svg><div class="d-none d-sm-inline"> <ng-container i18n>Permissions</ng-container></div>
|
||||
</button>
|
||||
|
||||
<div ngbDropdown>
|
||||
<button class="btn btn-sm btn-outline-primary" id="dropdownSelect" ngbDropdownToggle>
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#three-dots" />
|
||||
</svg>
|
||||
<div class="d-none d-sm-inline"> <ng-container i18n>Actions</ng-container></div>
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="dropdownSelect" class="shadow">
|
||||
<button ngbDropdownItem (click)="redoOcrSelected()" [disabled]="!userCanEditAll" i18n>Redo OCR</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-sm btn-outline-primary" [disabled]="awaitingDownload" (click)="downloadSelected()">
|
||||
@if (!awaitingDownload) {
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-down" />
|
||||
</svg>
|
||||
}
|
||||
@if (awaitingDownload) {
|
||||
<div class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="visually-hidden">Preparing download...</span>
|
||||
</div>
|
||||
}
|
||||
<div class="d-none d-sm-inline"> <ng-container i18n>Download</ng-container></div>
|
||||
</button>
|
||||
<div ngbDropdown class="me-2 d-flex btn-group" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle-split rounded-end" ngbDropdownToggle></button>
|
||||
<div ngbDropdownMenu aria-labelledby="dropdownSelect" class="shadow">
|
||||
<form [formGroup]="downloadForm" class="px-3 py-1">
|
||||
<p class="mb-1" i18n>Include:</p>
|
||||
<div class="form-group ps-3 mb-2">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="downloadFileType_archive" formControlName="downloadFileTypeArchive" />
|
||||
<label class="form-check-label" for="downloadFileType_archive" i18n>
|
||||
Archived files
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="downloadFileType_originals" formControlName="downloadFileTypeOriginals" />
|
||||
<label class="form-check-label" for="downloadFileType_originals" i18n>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="downloadFileType_originals" formControlName="downloadFileTypeOriginals" />
|
||||
<label class="form-check-label" for="downloadFileType_originals" i18n>
|
||||
Original files
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="downloadUseFormatting" formControlName="downloadUseFormatting" />
|
||||
<label class="form-check-label" for="downloadUseFormatting" i18n>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="downloadUseFormatting" formControlName="downloadUseFormatting" />
|
||||
<label class="form-check-label" for="downloadUseFormatting" i18n>
|
||||
Use formatted filename
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }" [disabled]="!userOwnsAll">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }" [disabled]="!userOwnsAll">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -16,23 +16,35 @@
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title">
|
||||
<ng-container *ngIf="document.correspondent">
|
||||
<a *ngIf="clickCorrespondent.observers.length ; else nolink" title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name}}</a>
|
||||
<ng-template #nolink>{{(document.correspondent$ | async)?.name}}</ng-template>:
|
||||
</ng-container>
|
||||
@if (document.correspondent) {
|
||||
@if (clickCorrespondent.observers.length ) {
|
||||
<a title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name}}</a>
|
||||
} @else {
|
||||
{{(document.correspondent$ | async)?.name}}
|
||||
}
|
||||
:
|
||||
}
|
||||
{{document.title | documentTitle}}
|
||||
<pngx-tag [tag]="t" linkTitle="Filter by tag" i18n-linkTitle *ngFor="let t of document.tags$ | async" class="ms-1" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="clickTag.observers.length"></pngx-tag>
|
||||
@for (t of document.tags$ | async; track t) {
|
||||
<pngx-tag [tag]="t" linkTitle="Filter by tag" i18n-linkTitle class="ms-1" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="clickTag.observers.length"></pngx-tag>
|
||||
}
|
||||
</h5>
|
||||
</div>
|
||||
<p class="card-text">
|
||||
<span *ngIf="document.__search_hit__ && document.__search_hit__.highlights" [innerHtml]="document.__search_hit__.highlights"></span>
|
||||
<span *ngFor="let highlight of searchNoteHighlights" class="d-block">
|
||||
<svg width="1em" height="1em" fill="currentColor" class="me-2">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
|
||||
</svg>
|
||||
<span [innerHtml]="highlight"></span>
|
||||
</span>
|
||||
<span *ngIf="!document.__search_hit__" class="result-content">{{contentTrimmed}}</span>
|
||||
@if (document.__search_hit__ && document.__search_hit__.highlights) {
|
||||
<span [innerHtml]="document.__search_hit__.highlights"></span>
|
||||
}
|
||||
@for (highlight of searchNoteHighlights; track highlight) {
|
||||
<span class="d-block">
|
||||
<svg width="1em" height="1em" fill="currentColor" class="me-2">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
|
||||
</svg>
|
||||
<span [innerHtml]="highlight"></span>
|
||||
</span>
|
||||
}
|
||||
@if (!document.__search_hit__) {
|
||||
<span class="result-content">{{contentTrimmed}}</span>
|
||||
}
|
||||
</p>
|
||||
|
||||
|
||||
@ -41,91 +53,105 @@
|
||||
<a class="btn btn-sm btn-outline-secondary" (click)="clickMoreLike.emit()">
|
||||
<svg class="sidebaricon" fill="currentColor" class="sidebaricon">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#diagram-3"/>
|
||||
</svg> <span class="d-none d-md-inline" i18n>More like this</span>
|
||||
</a>
|
||||
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||
<svg class="sidebaricon" fill="currentColor" class="sidebaricon">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil"/>
|
||||
</svg> <span class="d-none d-md-inline" i18n>Edit</span>
|
||||
</a>
|
||||
<a class="btn btn-sm btn-outline-secondary" target="_blank" [href]="previewUrl"
|
||||
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle"
|
||||
autoClose="true" popoverClass="shadow popover-preview" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||
<svg class="sidebaricon" fill="currentColor" class="sidebaricon">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#eye"/>
|
||||
</svg> <span class="d-none d-md-inline" i18n>View</span>
|
||||
</a>
|
||||
<ng-template #previewContent>
|
||||
<pngx-preview-popup [document]="document"></pngx-preview-popup>
|
||||
</ng-template>
|
||||
<a class="btn btn-sm btn-outline-secondary" [href]="getDownloadUrl()">
|
||||
<svg class="sidebaricon" fill="currentColor" class="sidebaricon">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#download"/>
|
||||
</svg> <span class="d-none d-md-inline" i18n>Download</span>
|
||||
</a>
|
||||
</div>
|
||||
</svg> <span class="d-none d-md-inline" i18n>More like this</span>
|
||||
</a>
|
||||
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||
<svg class="sidebaricon" fill="currentColor" class="sidebaricon">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil"/>
|
||||
</svg> <span class="d-none d-md-inline" i18n>Edit</span>
|
||||
</a>
|
||||
<a class="btn btn-sm btn-outline-secondary" target="_blank" [href]="previewUrl"
|
||||
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle"
|
||||
autoClose="true" popoverClass="shadow popover-preview" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||
<svg class="sidebaricon" fill="currentColor" class="sidebaricon">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#eye"/>
|
||||
</svg> <span class="d-none d-md-inline" i18n>View</span>
|
||||
</a>
|
||||
<ng-template #previewContent>
|
||||
<pngx-preview-popup [document]="document"></pngx-preview-popup>
|
||||
</ng-template>
|
||||
<a class="btn btn-sm btn-outline-secondary" [href]="getDownloadUrl()">
|
||||
<svg class="sidebaricon" fill="currentColor" class="sidebaricon">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#download"/>
|
||||
</svg> <span class="d-none d-md-inline" i18n>Download</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="list-group list-group-horizontal border-0 card-info ms-md-auto mt-2 mt-md-0">
|
||||
<button *ngIf="notesEnabled && document.notes.length" routerLink="/documents/{{document.id}}/notes" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2" title="View notes" i18n-title>
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
|
||||
</svg>
|
||||
<small i18n>{{document.notes.length}} Notes</small>
|
||||
</button>
|
||||
<button *ngIf="document.document_type" type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2" title="Filter by document type" i18n-title
|
||||
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-earmark"/>
|
||||
</svg>
|
||||
<small>{{(document.document_type$ | async)?.name}}</small>
|
||||
</button>
|
||||
<button *ngIf="document.storage_path" type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2" title="Filter by storage path" i18n-title
|
||||
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#archive"/>
|
||||
</svg>
|
||||
<small>{{(document.storage_path$ | async)?.name}}</small>
|
||||
</button>
|
||||
<div *ngIf="document.archive_serial_number | isNumber" class="list-group-item me-2 bg-light text-dark p-1 border-0">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#upc-scan"/>
|
||||
</svg>
|
||||
<small>#{{document.archive_serial_number}}</small>
|
||||
</div>
|
||||
<ng-template #dateTooltip>
|
||||
<div class="d-flex flex-column text-light">
|
||||
<span i18n>Created: {{ document.created | customDate }}</span>
|
||||
<span i18n>Added: {{ document.added | customDate }}</span>
|
||||
<span i18n>Modified: {{ document.modified | customDate }}</span>
|
||||
<div class="list-group list-group-horizontal border-0 card-info ms-md-auto mt-2 mt-md-0">
|
||||
@if (notesEnabled && document.notes.length) {
|
||||
<button routerLink="/documents/{{document.id}}/notes" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2" title="View notes" i18n-title>
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
|
||||
</svg>
|
||||
<small i18n>{{document.notes.length}} Notes</small>
|
||||
</button>
|
||||
}
|
||||
@if (document.document_type) {
|
||||
<button type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2" title="Filter by document type" i18n-title
|
||||
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-earmark"/>
|
||||
</svg>
|
||||
<small>{{(document.document_type$ | async)?.name}}</small>
|
||||
</button>
|
||||
}
|
||||
@if (document.storage_path) {
|
||||
<button type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2" title="Filter by storage path" i18n-title
|
||||
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#archive"/>
|
||||
</svg>
|
||||
<small>{{(document.storage_path$ | async)?.name}}</small>
|
||||
</button>
|
||||
}
|
||||
@if (document.archive_serial_number | isNumber) {
|
||||
<div class="list-group-item me-2 bg-light text-dark p-1 border-0">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#upc-scan"/>
|
||||
</svg>
|
||||
<small>#{{document.archive_serial_number}}</small>
|
||||
</div>
|
||||
}
|
||||
<ng-template #dateTooltip>
|
||||
<div class="d-flex flex-column text-light">
|
||||
<span i18n>Created: {{ document.created | customDate }}</span>
|
||||
<span i18n>Added: {{ document.added | customDate }}</span>
|
||||
<span i18n>Modified: {{ document.modified | customDate }}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0" [ngbTooltip]="dateTooltip">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#calendar-event"/>
|
||||
</svg>
|
||||
<small>{{document.created_date | customDate:'mediumDate'}}</small>
|
||||
</div>
|
||||
@if (document.owner && document.owner !== settingsService.currentUser.id) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock"/>
|
||||
</svg>
|
||||
<small>{{document.owner | username}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (document.is_shared_by_requester) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#people-fill"/>
|
||||
</svg>
|
||||
<small i18n>Shared</small>
|
||||
</div>
|
||||
}
|
||||
@if (document.__search_hit__?.score) {
|
||||
<div class="list-group-item bg-light text-dark border-0 d-flex p-0 ps-4 search-score">
|
||||
<small class="text-muted" i18n>Score:</small>
|
||||
<ngb-progressbar [type]="searchScoreClass" [value]="document.__search_hit__.score" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0" [ngbTooltip]="dateTooltip">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#calendar-event"/>
|
||||
</svg>
|
||||
<small>{{document.created_date | customDate:'mediumDate'}}</small>
|
||||
</div>
|
||||
<div *ngIf="document.owner && document.owner !== settingsService.currentUser.id" class="list-group-item bg-light text-dark p-1 border-0">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock"/>
|
||||
</svg>
|
||||
<small>{{document.owner | username}}</small>
|
||||
</div>
|
||||
<div *ngIf="document.is_shared_by_requester" class="list-group-item bg-light text-dark p-1 border-0">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#people-fill"/>
|
||||
</svg>
|
||||
<small i18n>Shared</small>
|
||||
</div>
|
||||
<div *ngIf="document.__search_hit__?.score" class="list-group-item bg-light text-dark border-0 d-flex p-0 ps-4 search-score">
|
||||
<small class="text-muted" i18n>Score:</small>
|
||||
<ngb-progressbar [type]="searchScoreClass" [value]="document.__search_hit__.score" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -11,45 +11,55 @@
|
||||
</div>
|
||||
|
||||
<div class="tags d-flex flex-column text-end position-absolute me-1 fs-6">
|
||||
<pngx-tag *ngFor="let t of getTagsLimited$() | async" [tag]="t" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="true" linkTitle="Toggle tag filter" i18n-linkTitle></pngx-tag>
|
||||
<div *ngIf="moreTags">
|
||||
<span class="badge text-dark">+ {{moreTags}}</span>
|
||||
</div>
|
||||
@for (t of getTagsLimited$() | async; track t) {
|
||||
<pngx-tag [tag]="t" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="true" linkTitle="Toggle tag filter" i18n-linkTitle></pngx-tag>
|
||||
}
|
||||
@if (moreTags) {
|
||||
<div>
|
||||
<span class="badge text-dark">+ {{moreTags}}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a *ngIf="notesEnabled && document.notes.length" routerLink="/documents/{{document.id}}/notes" class="document-card-notes py-2 px-1">
|
||||
<span class="badge rounded-pill bg-light border text-primary">
|
||||
<svg class="metadata-icon ms-1 me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
|
||||
</svg>
|
||||
{{document.notes.length}}</span>
|
||||
</a>
|
||||
@if (notesEnabled && document.notes.length) {
|
||||
<a routerLink="/documents/{{document.id}}/notes" class="document-card-notes py-2 px-1">
|
||||
<span class="badge rounded-pill bg-light border text-primary">
|
||||
<svg class="metadata-icon ms-1 me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
|
||||
</svg>
|
||||
{{document.notes.length}}</span>
|
||||
</a>
|
||||
}
|
||||
|
||||
<div class="card-body bg-light p-2">
|
||||
<p class="card-text">
|
||||
<ng-container *ngIf="document.correspondent">
|
||||
@if (document.correspondent) {
|
||||
<a title="Toggle correspondent filter" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name ?? privateName}}</a>:
|
||||
</ng-container>
|
||||
}
|
||||
{{document.title | documentTitle}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer pt-0 pb-2 px-2">
|
||||
<div class="list-group list-group-flush border-0 pt-1 pb-2 card-info">
|
||||
<button *ngIf="document.document_type" type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle document type filter" i18n-title
|
||||
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-earmark"/>
|
||||
</svg>
|
||||
<small>{{(document.document_type$ | async)?.name ?? privateName}}</small>
|
||||
</button>
|
||||
<button *ngIf="document.storage_path" type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle storage path filter" i18n-title
|
||||
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#folder"/>
|
||||
</svg>
|
||||
<small>{{(document.storage_path$ | async)?.name ?? privateName}}</small>
|
||||
</button>
|
||||
@if (document.document_type) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle document type filter" i18n-title
|
||||
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-earmark"/>
|
||||
</svg>
|
||||
<small>{{(document.document_type$ | async)?.name ?? privateName}}</small>
|
||||
</button>
|
||||
}
|
||||
@if (document.storage_path) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle storage path filter" i18n-title
|
||||
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#folder"/>
|
||||
</svg>
|
||||
<small>{{(document.storage_path$ | async)?.name ?? privateName}}</small>
|
||||
</button>
|
||||
}
|
||||
<div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
|
||||
<ng-template #dateTooltip>
|
||||
<div class="d-flex flex-column text-light">
|
||||
@ -65,24 +75,30 @@
|
||||
<small>{{document.created_date | customDate:'mediumDate'}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="document.archive_serial_number | isNumber" class="ps-0 p-1">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#upc-scan"/>
|
||||
</svg>
|
||||
<small>#{{document.archive_serial_number}}</small>
|
||||
</div>
|
||||
<div *ngIf="document.owner && document.owner !== settingsService.currentUser.id" class="ps-0 p-1">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock"/>
|
||||
</svg>
|
||||
<small>{{document.owner | username}}</small>
|
||||
</div>
|
||||
<div *ngIf="document.is_shared_by_requester" class="ps-0 p-1">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#people-fill"/>
|
||||
</svg>
|
||||
<small i18n>Shared</small>
|
||||
</div>
|
||||
@if (document.archive_serial_number | isNumber) {
|
||||
<div class="ps-0 p-1">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#upc-scan"/>
|
||||
</svg>
|
||||
<small>#{{document.archive_serial_number}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (document.owner && document.owner !== settingsService.currentUser.id) {
|
||||
<div class="ps-0 p-1">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock"/>
|
||||
</svg>
|
||||
<small>{{document.owner | username}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (document.is_shared_by_requester) {
|
||||
<div class="ps-0 p-1">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#people-fill"/>
|
||||
</svg>
|
||||
<small i18n>Shared</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="btn-group w-100">
|
||||
@ -92,8 +108,8 @@
|
||||
</svg>
|
||||
</a>
|
||||
<a [href]="previewUrl" target="_blank" class="btn btn-sm btn-outline-secondary"
|
||||
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle"
|
||||
autoClose="true" popoverClass="shadow popover-preview" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle"
|
||||
autoClose="true" popoverClass="shadow popover-preview" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
|
||||
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
|
||||
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
|
||||
|
@ -52,9 +52,11 @@
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button *ngFor="let f of getSortFields()" ngbDropdownItem (click)="setSortField(f.field)"
|
||||
[class.active]="list.sortField === f.field">{{f.name}}
|
||||
</button>
|
||||
@for (f of getSortFields(); track f) {
|
||||
<button ngbDropdownItem (click)="setSortField(f.field)"
|
||||
[class.active]="list.sortField === f.field">{{f.name}}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -62,18 +64,26 @@
|
||||
<div class="btn-group ms-2 flex-fill" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }" ngbDropdown role="group">
|
||||
<button class="btn btn-sm btn-outline-primary dropdown-toggle flex-fill" tourAnchor="tour.documents-views" ngbDropdownToggle>
|
||||
<ng-container i18n>Views</ng-container>
|
||||
<div *ngIf="savedViewIsModified" class="position-absolute top-0 start-100 p-2 translate-middle badge bg-secondary border border-light rounded-circle">
|
||||
<span class="visually-hidden">selected</span>
|
||||
</div>
|
||||
@if (savedViewIsModified) {
|
||||
<div class="position-absolute top-0 start-100 p-2 translate-middle badge bg-secondary border border-light rounded-circle">
|
||||
<span class="visually-hidden">selected</span>
|
||||
</div>
|
||||
}
|
||||
</button>
|
||||
<div class="dropdown-menu shadow dropdown-menu-right" ngbDropdownMenu>
|
||||
<ng-container *ngIf="!list.activeSavedViewId">
|
||||
<button ngbDropdownItem *ngFor="let view of savedViewService.allViews" (click)="loadViewConfig(view.id)">{{view.name}}</button>
|
||||
<div class="dropdown-divider" *ngIf="savedViewService.allViews.length > 0"></div>
|
||||
</ng-container>
|
||||
@if (!list.activeSavedViewId) {
|
||||
@for (view of savedViewService.allViews; track view) {
|
||||
<button ngbDropdownItem (click)="loadViewConfig(view.id)">{{view.name}}</button>
|
||||
}
|
||||
@if (savedViewService.allViews.length > 0) {
|
||||
<div class="dropdown-divider"></div>
|
||||
}
|
||||
}
|
||||
|
||||
<div *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.SavedView }">
|
||||
<button ngbDropdownItem (click)="saveViewConfig()" *ngIf="list.activeSavedViewId" [disabled]="!savedViewIsModified" i18n>Save "{{list.activeSavedViewTitle}}"</button>
|
||||
@if (list.activeSavedViewId) {
|
||||
<button ngbDropdownItem (click)="saveViewConfig()" [disabled]="!savedViewIsModified" i18n>Save "{{list.activeSavedViewTitle}}"</button>
|
||||
}
|
||||
</div>
|
||||
<button ngbDropdownItem (click)="saveViewConfigAs()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.SavedView }" i18n>Save as...</button>
|
||||
</div>
|
||||
@ -90,165 +100,191 @@
|
||||
<ng-template #pagination>
|
||||
<div class="d-flex flex-wrap gap-3 justify-content-between align-items-center mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<ng-container *ngIf="list.isReloading">
|
||||
@if (list.isReloading) {
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</ng-container>
|
||||
<span i18n *ngIf="list.selected.size > 0">{list.collectionSize, plural, =1 {Selected {{list.selected.size}} of one document} other {Selected {{list.selected.size}} of {{list.collectionSize || 0}} documents}}</span>
|
||||
<ng-container *ngIf="!list.isReloading">
|
||||
<span i18n *ngIf="list.selected.size === 0">{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</span> <span i18n *ngIf="isFiltered">(filtered)</span>
|
||||
</ng-container>
|
||||
<button *ngIf="!list.isReloading && isFiltered" class="btn btn-link py-0" (click)="resetFilters()">
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg><small i18n>Reset filters</small>
|
||||
</button>
|
||||
}
|
||||
@if (list.selected.size > 0) {
|
||||
<span i18n>{list.collectionSize, plural, =1 {Selected {{list.selected.size}} of one document} other {Selected {{list.selected.size}} of {{list.collectionSize || 0}} documents}}</span>
|
||||
}
|
||||
@if (!list.isReloading) {
|
||||
@if (list.selected.size === 0) {
|
||||
<span i18n>{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</span>
|
||||
} @if (isFiltered) {
|
||||
<span i18n>(filtered)</span>
|
||||
}
|
||||
}
|
||||
@if (!list.isReloading && isFiltered) {
|
||||
<button class="btn btn-link py-0" (click)="resetFilters()">
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg><small i18n>Reset filters</small>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@if (list.collectionSize) {
|
||||
<ngb-pagination [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
|
||||
[rotate]="true" aria-label="Default pagination" size="sm"></ngb-pagination>
|
||||
}
|
||||
</div>
|
||||
<ngb-pagination *ngIf="list.collectionSize" [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
|
||||
[rotate]="true" aria-label="Default pagination" size="sm"></ngb-pagination>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<div tourAnchor="tour.documents">
|
||||
<ng-container *ngTemplateOutlet="pagination"></ng-container>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="list.error ; else documentListNoError">
|
||||
<div class="alert alert-danger" role="alert"><ng-container i18n>Error while loading documents</ng-container>: {{list.error}}</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #documentListNoError>
|
||||
<div *ngIf="displayMode === 'largeCards'">
|
||||
<pngx-document-card-large [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" (dblClickDocument)="openDocumentDetail(d)" *ngFor="let d of list.documents; trackBy: trackByDocumentId" [document]="d" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)" (clickStoragePath)="clickStoragePath($event)" (clickMoreLike)="clickMoreLike(d.id)">
|
||||
</pngx-document-card-large>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm align-middle border shadow-sm" *ngIf="displayMode === 'details'">
|
||||
<thead>
|
||||
<th></th>
|
||||
<th class="d-none d-lg-table-cell"
|
||||
pngxSortable="archive_serial_number"
|
||||
title="Sort by ASN" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>ASN</th>
|
||||
<th class="d-none d-md-table-cell"
|
||||
pngxSortable="correspondent__name"
|
||||
title="Sort by correspondent" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Correspondent</th>
|
||||
<th
|
||||
pngxSortable="title"
|
||||
title="Sort by title" i18n-title
|
||||
class="w-40"
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Title</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
pngxSortable="owner"
|
||||
title="Sort by owner" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Owner</th>
|
||||
<th *ngIf="notesEnabled" class="d-none d-xl-table-cell"
|
||||
pngxSortable="num_notes"
|
||||
title="Sort by notes" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Notes</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
pngxSortable="document_type__name"
|
||||
title="Sort by document type" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Document type</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
pngxSortable="storage_path__name"
|
||||
title="Sort by storage path" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Storage path</th>
|
||||
<th
|
||||
pngxSortable="created"
|
||||
title="Sort by created date" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Created</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
pngxSortable="added"
|
||||
title="Sort by added date" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Added</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let d of list.documents; trackBy: trackByDocumentId" (click)="toggleSelected(d, $event); $event.stopPropagation();" (dblclick)="openDocumentDetail(d)" [ngClass]="list.isSelected(d) ? 'table-row-selected' : ''">
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="docCheck{{d.id}}" [checked]="list.isSelected(d)" (click)="toggleSelected(d, $event); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="docCheck{{d.id}}"></label>
|
||||
</div>
|
||||
</td>
|
||||
<td class="d-none d-lg-table-cell">
|
||||
{{d.archive_serial_number}}
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<ng-container *ngIf="d.correspondent">
|
||||
<a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{(d.correspondent$ | async)?.name}}</a>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
<a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
|
||||
<pngx-tag [tag]="t" *ngFor="let t of d.tags$ | async" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></pngx-tag>
|
||||
</td>
|
||||
<td>
|
||||
{{d.owner | username}}
|
||||
</td>
|
||||
<td *ngIf="notesEnabled" class="d-none d-xl-table-cell">
|
||||
<a *ngIf="d.notes.length" routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
|
||||
<span class="badge rounded-pill bg-light border text-primary">
|
||||
<svg class="metadata-icon ms-1 me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
|
||||
</svg>
|
||||
{{d.notes.length}}</span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="d-none d-xl-table-cell">
|
||||
<ng-container *ngIf="d.document_type">
|
||||
<a (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type" i18n-title>{{(d.document_type$ | async)?.name}}</a>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td class="d-none d-xl-table-cell">
|
||||
<ng-container *ngIf="d.storage_path">
|
||||
<a (click)="clickStoragePath(d.storage_path);$event.stopPropagation()" title="Filter by storage path" i18n-title>{{(d.storage_path$ | async)?.name}}</a>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
{{d.created_date | customDate}}
|
||||
</td>
|
||||
<td class="d-none d-xl-table-cell">
|
||||
{{d.added | customDate}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="row row-cols-paperless-cards" *ngIf="displayMode === 'smallCards'">
|
||||
<pngx-document-card-small class="p-0" [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" (dblClickDocument)="openDocumentDetail(d)" [document]="d" *ngFor="let d of list.documents; trackBy: trackByDocumentId" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickStoragePath)="clickStoragePath($event)" (clickDocumentType)="clickDocumentType($event)"></pngx-document-card-small>
|
||||
</div>
|
||||
<div *ngIf="list.documents?.length > 15" class="mt-3">
|
||||
<div tourAnchor="tour.documents">
|
||||
<ng-container *ngTemplateOutlet="pagination"></ng-container>
|
||||
</div>
|
||||
|
||||
|
||||
</ng-template>
|
||||
@if (list.error ) {
|
||||
<div class="alert alert-danger" role="alert"><ng-container i18n>Error while loading documents</ng-container>: {{list.error}}</div>
|
||||
} @else {
|
||||
@if (displayMode === 'largeCards') {
|
||||
<div>
|
||||
@for (d of list.documents; track trackByDocumentId($index, d)) {
|
||||
<pngx-document-card-large [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" (dblClickDocument)="openDocumentDetail(d)" [document]="d" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)" (clickStoragePath)="clickStoragePath($event)" (clickMoreLike)="clickMoreLike(d.id)">
|
||||
</pngx-document-card-large>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (displayMode === 'details') {
|
||||
<table class="table table-sm align-middle border shadow-sm">
|
||||
<thead>
|
||||
<th></th>
|
||||
<th class="d-none d-lg-table-cell"
|
||||
pngxSortable="archive_serial_number"
|
||||
title="Sort by ASN" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>ASN</th>
|
||||
<th class="d-none d-md-table-cell"
|
||||
pngxSortable="correspondent__name"
|
||||
title="Sort by correspondent" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Correspondent</th>
|
||||
<th
|
||||
pngxSortable="title"
|
||||
title="Sort by title" i18n-title
|
||||
class="w-40"
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Title</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
pngxSortable="owner"
|
||||
title="Sort by owner" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Owner</th>
|
||||
@if (notesEnabled) {
|
||||
<th class="d-none d-xl-table-cell"
|
||||
pngxSortable="num_notes"
|
||||
title="Sort by notes" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Notes</th>
|
||||
}
|
||||
<th class="d-none d-xl-table-cell"
|
||||
pngxSortable="document_type__name"
|
||||
title="Sort by document type" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Document type</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
pngxSortable="storage_path__name"
|
||||
title="Sort by storage path" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Storage path</th>
|
||||
<th
|
||||
pngxSortable="created"
|
||||
title="Sort by created date" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Created</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
pngxSortable="added"
|
||||
title="Sort by added date" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Added</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (d of list.documents; track trackByDocumentId($index, d)) {
|
||||
<tr (click)="toggleSelected(d, $event); $event.stopPropagation();" (dblclick)="openDocumentDetail(d)" [ngClass]="list.isSelected(d) ? 'table-row-selected' : ''">
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="docCheck{{d.id}}" [checked]="list.isSelected(d)" (click)="toggleSelected(d, $event); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="docCheck{{d.id}}"></label>
|
||||
</div>
|
||||
</td>
|
||||
<td class="d-none d-lg-table-cell">
|
||||
{{d.archive_serial_number}}
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
@if (d.correspondent) {
|
||||
<a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{(d.correspondent$ | async)?.name}}</a>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
|
||||
@for (t of d.tags$ | async; track t) {
|
||||
<pngx-tag [tag]="t" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></pngx-tag>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
{{d.owner | username}}
|
||||
</td>
|
||||
@if (notesEnabled) {
|
||||
<td class="d-none d-xl-table-cell">
|
||||
@if (d.notes.length) {
|
||||
<a routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
|
||||
<span class="badge rounded-pill bg-light border text-primary">
|
||||
<svg class="metadata-icon ms-1 me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
|
||||
</svg>
|
||||
{{d.notes.length}}</span>
|
||||
</a>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
<td class="d-none d-xl-table-cell">
|
||||
@if (d.document_type) {
|
||||
<a (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type" i18n-title>{{(d.document_type$ | async)?.name}}</a>
|
||||
}
|
||||
</td>
|
||||
<td class="d-none d-xl-table-cell">
|
||||
@if (d.storage_path) {
|
||||
<a (click)="clickStoragePath(d.storage_path);$event.stopPropagation()" title="Filter by storage path" i18n-title>{{(d.storage_path$ | async)?.name}}</a>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
{{d.created_date | customDate}}
|
||||
</td>
|
||||
<td class="d-none d-xl-table-cell">
|
||||
{{d.added | customDate}}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
@if (displayMode === 'smallCards') {
|
||||
<div class="row row-cols-paperless-cards">
|
||||
@for (d of list.documents; track trackByDocumentId($index, d)) {
|
||||
<pngx-document-card-small class="p-0" [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" (dblClickDocument)="openDocumentDetail(d)" [document]="d" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickStoragePath)="clickStoragePath($event)" (clickDocumentType)="clickDocumentType($event)"></pngx-document-card-small>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (list.documents?.length > 15) {
|
||||
<div class="mt-3">
|
||||
<ng-container *ngTemplateOutlet="pagination"></ng-container>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
@ -5,17 +5,25 @@
|
||||
<div ngbDropdown>
|
||||
<button class="btn btn-sm btn-outline-primary" ngbDropdownToggle>{{textFilterTargetName}}</button>
|
||||
<div class="dropdown-menu shadow" ngbDropdownMenu>
|
||||
<button *ngFor="let t of textFilterTargets" ngbDropdownItem [class.active]="textFilterTarget === t.id" (click)="changeTextFilterTarget(t.id)">{{t.name}}</button>
|
||||
@for (t of textFilterTargets; track t) {
|
||||
<button ngbDropdownItem [class.active]="textFilterTarget === t.id" (click)="changeTextFilterTarget(t.id)">{{t.name}}</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<select *ngIf="textFilterTarget === 'asn'" class="form-select flex-grow-0 w-auto" [(ngModel)]="textFilterModifier" (change)="textFilterModifierChange()">
|
||||
<option *ngFor="let m of textFilterModifiers" ngbDropdownItem [value]="m.id">{{m.label}}</option>
|
||||
</select>
|
||||
<button *ngIf="_textFilter" class="btn btn-link btn-sm px-0 position-absolute top-0 end-0 z-10" (click)="resetTextField()">
|
||||
<svg fill="currentColor" class="buttonicon-sm me-1">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
</button>
|
||||
@if (textFilterTarget === 'asn') {
|
||||
<select class="form-select flex-grow-0 w-auto" [(ngModel)]="textFilterModifier" (change)="textFilterModifierChange()">
|
||||
@for (m of textFilterModifiers; track m) {
|
||||
<option ngbDropdownItem [value]="m.id">{{m.label}}</option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
@if (_textFilter) {
|
||||
<button class="btn btn-link btn-sm px-0 position-absolute top-0 end-0 z-10" (click)="resetTextField()">
|
||||
<svg fill="currentColor" class="buttonicon-sm me-1">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
<input #textFilterInput class="form-control form-control-sm" type="text" [disabled]="textFilterModifierIsNull" [(ngModel)]="textFilter" (keyup)="textFilterKeyup($event)" [readonly]="textFilterTarget === 'fulltext-morelike'">
|
||||
</div>
|
||||
</div>
|
||||
@ -31,7 +39,7 @@
|
||||
(selectionModelChange)="updateRules()"
|
||||
(opened)="onTagsDropdownOpen()"
|
||||
[documentCounts]="tagDocumentCounts"
|
||||
[allowSelectNone]="true"></pngx-filterable-dropdown>
|
||||
[allowSelectNone]="true"></pngx-filterable-dropdown>
|
||||
<pngx-filterable-dropdown class="flex-fill" title="Correspondent" icon="person-fill" i18n-title
|
||||
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
|
||||
[items]="correspondents"
|
||||
@ -39,7 +47,7 @@
|
||||
(selectionModelChange)="updateRules()"
|
||||
(opened)="onCorrespondentDropdownOpen()"
|
||||
[documentCounts]="correspondentDocumentCounts"
|
||||
[allowSelectNone]="true"></pngx-filterable-dropdown>
|
||||
[allowSelectNone]="true"></pngx-filterable-dropdown>
|
||||
<pngx-filterable-dropdown class="flex-fill" title="Document type" icon="file-earmark-fill" i18n-title
|
||||
filterPlaceholder="Filter document types" i18n-filterPlaceholder
|
||||
[items]="documentTypes"
|
||||
@ -47,7 +55,7 @@
|
||||
(selectionModelChange)="updateRules()"
|
||||
(opened)="onDocumentTypeDropdownOpen()"
|
||||
[documentCounts]="documentTypeDocumentCounts"
|
||||
[allowSelectNone]="true"></pngx-filterable-dropdown>
|
||||
[allowSelectNone]="true"></pngx-filterable-dropdown>
|
||||
<pngx-filterable-dropdown class="flex-fill" title="Storage path" icon="folder-fill" i18n-title
|
||||
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
|
||||
[items]="storagePaths"
|
||||
@ -55,7 +63,7 @@
|
||||
(selectionModelChange)="updateRules()"
|
||||
(opened)="onStoragePathDropdownOpen()"
|
||||
[documentCounts]="storagePathDocumentCounts"
|
||||
[allowSelectNone]="true"></pngx-filterable-dropdown>
|
||||
[allowSelectNone]="true"></pngx-filterable-dropdown>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<pngx-date-dropdown
|
||||
@ -63,27 +71,27 @@
|
||||
(datesSet)="updateRules()"
|
||||
[(dateBefore)]="dateCreatedBefore"
|
||||
[(dateAfter)]="dateCreatedAfter"
|
||||
[(relativeDate)]="dateCreatedRelativeDate"></pngx-date-dropdown>
|
||||
[(relativeDate)]="dateCreatedRelativeDate"></pngx-date-dropdown>
|
||||
<pngx-date-dropdown
|
||||
title="Added" i18n-title
|
||||
(datesSet)="updateRules()"
|
||||
[(dateBefore)]="dateAddedBefore"
|
||||
[(dateAfter)]="dateAddedAfter"
|
||||
[(relativeDate)]="dateAddedRelativeDate"></pngx-date-dropdown>
|
||||
[(relativeDate)]="dateAddedRelativeDate"></pngx-date-dropdown>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap">
|
||||
<pngx-permissions-filter-dropdown
|
||||
title="Permissions" i18n-title
|
||||
(ownerFilterSet)="updateRules()"
|
||||
[(selectionModel)]="permissionsSelectionModel"></pngx-permissions-filter-dropdown>
|
||||
[(selectionModel)]="permissionsSelectionModel"></pngx-permissions-filter-dropdown>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap d-none d-sm-inline-block">
|
||||
<button class="btn btn-outline-secondary btn-sm" [disabled]="!rulesModified" (click)="resetSelected()">
|
||||
<svg class="toolbaricon ms-n1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"></use>
|
||||
</svg><ng-container i18n>Reset filters</ng-container>
|
||||
</button>
|
||||
</svg><ng-container i18n>Reset filters</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1726,4 +1726,13 @@ describe('FilterEditorComponent', () => {
|
||||
)
|
||||
expect(component.textFilter).toEqual('')
|
||||
})
|
||||
|
||||
it('should adjust text filter targets if more like search', () => {
|
||||
const TEXT_FILTER_TARGET_FULLTEXT_MORELIKE = 'fulltext-morelike' // private const
|
||||
component.textFilterTarget = TEXT_FILTER_TARGET_FULLTEXT_MORELIKE
|
||||
expect(component.textFilterTargets).toContainEqual({
|
||||
id: TEXT_FILTER_TARGET_FULLTEXT_MORELIKE,
|
||||
name: $localize`More like`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -105,6 +105,51 @@ const RELATIVE_DATE_QUERYSTRINGS = [
|
||||
},
|
||||
]
|
||||
|
||||
const DEFAULT_TEXT_FILTER_TARGET_OPTIONS = [
|
||||
{ id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title` },
|
||||
{
|
||||
id: TEXT_FILTER_TARGET_TITLE_CONTENT,
|
||||
name: $localize`Title & content`,
|
||||
},
|
||||
{ id: TEXT_FILTER_TARGET_ASN, name: $localize`ASN` },
|
||||
{
|
||||
id: TEXT_FILTER_TARGET_CUSTOM_FIELDS,
|
||||
name: $localize`Custom fields`,
|
||||
},
|
||||
{
|
||||
id: TEXT_FILTER_TARGET_FULLTEXT_QUERY,
|
||||
name: $localize`Advanced search`,
|
||||
},
|
||||
]
|
||||
|
||||
const TEXT_FILTER_TARGET_MORELIKE_OPTION = {
|
||||
id: TEXT_FILTER_TARGET_FULLTEXT_MORELIKE,
|
||||
name: $localize`More like`,
|
||||
}
|
||||
|
||||
const DEFAULT_TEXT_FILTER_MODIFIER_OPTIONS = [
|
||||
{
|
||||
id: TEXT_FILTER_MODIFIER_EQUALS,
|
||||
label: $localize`equals`,
|
||||
},
|
||||
{
|
||||
id: TEXT_FILTER_MODIFIER_NULL,
|
||||
label: $localize`is empty`,
|
||||
},
|
||||
{
|
||||
id: TEXT_FILTER_MODIFIER_NOTNULL,
|
||||
label: $localize`is not empty`,
|
||||
},
|
||||
{
|
||||
id: TEXT_FILTER_MODIFIER_GT,
|
||||
label: $localize`greater than`,
|
||||
},
|
||||
{
|
||||
id: TEXT_FILTER_MODIFIER_LT,
|
||||
label: $localize`less than`,
|
||||
},
|
||||
]
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-filter-editor',
|
||||
templateUrl: './filter-editor.component.html',
|
||||
@ -200,29 +245,12 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
_moreLikeDoc: PaperlessDocument
|
||||
|
||||
get textFilterTargets() {
|
||||
let targets = [
|
||||
{ id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title` },
|
||||
{
|
||||
id: TEXT_FILTER_TARGET_TITLE_CONTENT,
|
||||
name: $localize`Title & content`,
|
||||
},
|
||||
{ id: TEXT_FILTER_TARGET_ASN, name: $localize`ASN` },
|
||||
{
|
||||
id: TEXT_FILTER_TARGET_CUSTOM_FIELDS,
|
||||
name: $localize`Custom fields`,
|
||||
},
|
||||
{
|
||||
id: TEXT_FILTER_TARGET_FULLTEXT_QUERY,
|
||||
name: $localize`Advanced search`,
|
||||
},
|
||||
]
|
||||
if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) {
|
||||
targets.push({
|
||||
id: TEXT_FILTER_TARGET_FULLTEXT_MORELIKE,
|
||||
name: $localize`More like`,
|
||||
})
|
||||
return DEFAULT_TEXT_FILTER_TARGET_OPTIONS.concat([
|
||||
TEXT_FILTER_TARGET_MORELIKE_OPTION,
|
||||
])
|
||||
}
|
||||
return targets
|
||||
return DEFAULT_TEXT_FILTER_TARGET_OPTIONS
|
||||
}
|
||||
|
||||
textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT
|
||||
@ -235,28 +263,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
public textFilterModifier: string
|
||||
|
||||
get textFilterModifiers() {
|
||||
return [
|
||||
{
|
||||
id: TEXT_FILTER_MODIFIER_EQUALS,
|
||||
label: $localize`equals`,
|
||||
},
|
||||
{
|
||||
id: TEXT_FILTER_MODIFIER_NULL,
|
||||
label: $localize`is empty`,
|
||||
},
|
||||
{
|
||||
id: TEXT_FILTER_MODIFIER_NOTNULL,
|
||||
label: $localize`is not empty`,
|
||||
},
|
||||
{
|
||||
id: TEXT_FILTER_MODIFIER_GT,
|
||||
label: $localize`greater than`,
|
||||
},
|
||||
{
|
||||
id: TEXT_FILTER_MODIFIER_LT,
|
||||
label: $localize`less than`,
|
||||
},
|
||||
]
|
||||
return DEFAULT_TEXT_FILTER_MODIFIER_OPTIONS
|
||||
}
|
||||
|
||||
get textFilterModifierIsNull(): boolean {
|
||||
|
@ -8,11 +8,13 @@
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
||||
<pngx-input-check i18n-title title="Show in sidebar" formControlName="showInSideBar"></pngx-input-check>
|
||||
<pngx-input-check i18n-title title="Show on dashboard" formControlName="showOnDashboard"></pngx-input-check>
|
||||
<div *ngIf="error?.filter_rules" class="alert alert-danger" role="alert">
|
||||
<h6 class="alert-heading" i18n>Filter rules error occurred while saving this view</h6>
|
||||
<ng-container i18n>The error returned was</ng-container>:<br/>
|
||||
{{ error.filter_rules }}
|
||||
</div>
|
||||
@if (error?.filter_rules) {
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h6 class="alert-heading" i18n>Filter rules error occurred while saving this view</h6>
|
||||
<ng-container i18n>The error returned was</ng-container>:<br/>
|
||||
{{ error.filter_rules }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-dark" (click)="cancel()" i18n [disabled]="!buttonsEnabled">Cancel</button>
|
||||
|
@ -1,29 +1,35 @@
|
||||
<div *ngIf="notes">
|
||||
@if (notes) {
|
||||
<div>
|
||||
<form [formGroup]="noteForm" class="needs-validation mt-3" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Note }" novalidate>
|
||||
<div class="form-group">
|
||||
<textarea class="form-control form-control-sm" [class.is-invalid]="newNoteError" rows="3" formControlName="newNote" placeholder="Enter note" i18n-placeholder (keydown)="noteFormKeydown($event)" required></textarea>
|
||||
<div class="invalid-feedback" i18n>
|
||||
<div class="form-group">
|
||||
<textarea class="form-control form-control-sm" [class.is-invalid]="newNoteError" rows="3" formControlName="newNote" placeholder="Enter note" i18n-placeholder (keydown)="noteFormKeydown($event)" required></textarea>
|
||||
<div class="invalid-feedback" i18n>
|
||||
Please enter a note.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mt-2 d-flex justify-content-end align-items-center">
|
||||
<div *ngIf="networkActive" class="spinner-border spinner-border-sm fw-normal me-auto" role="status"></div>
|
||||
<button type="button" class="btn btn-primary btn-sm" [disabled]="networkActive || addDisabled" (click)="addNote()" i18n>Add note</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mt-2 d-flex justify-content-end align-items-center">
|
||||
@if (networkActive) {
|
||||
<div class="spinner-border spinner-border-sm fw-normal me-auto" role="status"></div>
|
||||
}
|
||||
<button type="button" class="btn btn-primary btn-sm" [disabled]="networkActive || addDisabled" (click)="addNote()" i18n>Add note</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<div *ngFor="let note of notes" class="card border mb-3">
|
||||
<div class="card-body text-dark">
|
||||
@for (note of notes; track note) {
|
||||
<div class="card border mb-3">
|
||||
<div class="card-body text-dark">
|
||||
<p class="card-text">{{note.note}}</p>
|
||||
</div>
|
||||
<div class="d-flex card-footer small bg-light text-primary justify-content-between align-items-center">
|
||||
</div>
|
||||
<div class="d-flex card-footer small bg-light text-primary justify-content-between align-items-center">
|
||||
<span>{{displayName(note)}} - {{ note.created | customDate}}</span>
|
||||
<button type="button" class="btn btn-link btn-sm p-0 fade" title="Delete note" i18n-title (click)="deleteNote(note.id)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Note }">
|
||||
<svg width="13" height="13" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg>
|
||||
<span class="visually-hidden" i18n>Delete note</span>
|
||||
<svg width="13" height="13" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg>
|
||||
<span class="visually-hidden" i18n>Delete note</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -1,43 +1,47 @@
|
||||
<pngx-page-header title="Consumption Templates" i18n-title>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editTemplate()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ConsumptionTemplate }">
|
||||
<svg class="sidebaricon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||
</svg>
|
||||
<ng-container i18n>Add Template</ng-container>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editTemplate()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ConsumptionTemplate }">
|
||||
<svg class="sidebaricon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||
</svg>
|
||||
<ng-container i18n>Add Template</ng-container>
|
||||
</button>
|
||||
</pngx-page-header>
|
||||
|
||||
<ul class="list-group">
|
||||
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col" i18n>Name</div>
|
||||
<div class="col" i18n>Sort order</div>
|
||||
<div class="col" i18n>Document Sources</div>
|
||||
<div class="col" i18n>Actions</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col" i18n>Name</div>
|
||||
<div class="col" i18n>Sort order</div>
|
||||
<div class="col" i18n>Document Sources</div>
|
||||
<div class="col" i18n>Actions</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li *ngFor="let template of templates" class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editTemplate(template)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.ConsumptionTemplate)">{{template.name}}</button></div>
|
||||
<div class="col d-flex align-items-center"><code>{{template.order}}</code></div>
|
||||
<div class="col d-flex align-items-center">{{getSourceList(template)}}</div>
|
||||
<div class="col">
|
||||
<div class="btn-group">
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.ConsumptionTemplate }" class="btn btn-sm btn-outline-secondary" type="button" (click)="editTemplate(template)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
</button>
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.ConsumptionTemplate }" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteTemplate(template)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
@for (template of templates; track template) {
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editTemplate(template)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.ConsumptionTemplate)">{{template.name}}</button></div>
|
||||
<div class="col d-flex align-items-center"><code>{{template.order}}</code></div>
|
||||
<div class="col d-flex align-items-center">{{getSourceList(template)}}</div>
|
||||
<div class="col">
|
||||
<div class="btn-group">
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.ConsumptionTemplate }" class="btn btn-sm btn-outline-secondary" type="button" (click)="editTemplate(template)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
</button>
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.ConsumptionTemplate }" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteTemplate(template)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li *ngIf="templates.length === 0" class="list-group-item" i18n>No templates defined.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
@if (templates.length === 0) {
|
||||
<li class="list-group-item" i18n>No templates defined.</li>
|
||||
}
|
||||
</ul>
|
||||
|
@ -1,41 +1,45 @@
|
||||
<pngx-page-header title="Custom Fields" i18n-title>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editField()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.CustomField }">
|
||||
<svg class="sidebaricon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||
</svg>
|
||||
<ng-container i18n>Add Field</ng-container>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editField()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.CustomField }">
|
||||
<svg class="sidebaricon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||
</svg>
|
||||
<ng-container i18n>Add Field</ng-container>
|
||||
</button>
|
||||
</pngx-page-header>
|
||||
|
||||
<ul class="list-group">
|
||||
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col" i18n>Name</div>
|
||||
<div class="col" i18n>Data Type</div>
|
||||
<div class="col" i18n>Actions</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col" i18n>Name</div>
|
||||
<div class="col" i18n>Data Type</div>
|
||||
<div class="col" i18n>Actions</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li *ngFor="let field of fields" class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editField(field)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.CustomField)">{{field.name}}</button></div>
|
||||
<div class="col d-flex align-items-center">{{getDataType(field)}}</div>
|
||||
<div class="col">
|
||||
<div class="btn-group">
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.CustomField }" class="btn btn-sm btn-outline-secondary" type="button" (click)="editField(field)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
</button>
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.CustomField }" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteField(field)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
@for (field of fields; track field) {
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editField(field)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.CustomField)">{{field.name}}</button></div>
|
||||
<div class="col d-flex align-items-center">{{getDataType(field)}}</div>
|
||||
<div class="col">
|
||||
<div class="btn-group">
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.CustomField }" class="btn btn-sm btn-outline-secondary" type="button" (click)="editField(field)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
</button>
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.CustomField }" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteField(field)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li *ngIf="fields.length === 0" class="list-group-item" i18n>No fields defined.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
@if (fields.length === 0) {
|
||||
<li class="list-group-item" i18n>No fields defined.</li>
|
||||
}
|
||||
</ul>
|
||||
|
@ -2,104 +2,114 @@
|
||||
</pngx-page-header>
|
||||
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailAccount }">
|
||||
<h4>
|
||||
<ng-container i18n>Mail accounts</ng-container>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editMailAccount()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailAccount }">
|
||||
<svg class="sidebaricon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||
</svg>
|
||||
<ng-container i18n>Add Account</ng-container>
|
||||
</button>
|
||||
</h4>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col" i18n>Name</div>
|
||||
<div class="col" i18n>Server</div>
|
||||
<div class="col" i18n>Actions</div>
|
||||
</div>
|
||||
</li>
|
||||
<h4>
|
||||
<ng-container i18n>Mail accounts</ng-container>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editMailAccount()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailAccount }">
|
||||
<svg class="sidebaricon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||
</svg>
|
||||
<ng-container i18n>Add Account</ng-container>
|
||||
</button>
|
||||
</h4>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col" i18n>Name</div>
|
||||
<div class="col" i18n>Server</div>
|
||||
<div class="col" i18n>Actions</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li *ngFor="let account of mailAccounts" class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailAccount(account)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailAccount)">{{account.name}}</button></div>
|
||||
<div class="col d-flex align-items-center">{{account.imap_server}}</div>
|
||||
<div class="col">
|
||||
<div class="btn-group">
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" [disabled]="!userCanEdit(account)" class="btn btn-sm btn-outline-secondary" type="button" (click)="editMailAccount(account)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
@for (account of mailAccounts; track account) {
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailAccount(account)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailAccount)">{{account.name}}</button></div>
|
||||
<div class="col d-flex align-items-center">{{account.imap_server}}</div>
|
||||
<div class="col">
|
||||
<div class="btn-group">
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" [disabled]="!userCanEdit(account)" class="btn btn-sm btn-outline-secondary" type="button" (click)="editMailAccount(account)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
</button>
|
||||
<button *pngxIfOwner="account" class="btn btn-sm btn-outline-secondary" type="button" (click)="editPermissions(account)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-lock" />
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-lock" />
|
||||
</svg> <ng-container i18n>Permissions</ng-container>
|
||||
</button>
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailAccount }" [disabled]="!userIsOwner(account)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailAccount(account)">
|
||||
</button>
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailAccount }" [disabled]="!userIsOwner(account)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailAccount(account)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li *ngIf="mailAccounts.length === 0" class="list-group-item" i18n>No mail accounts defined.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
@if (mailAccounts.length === 0) {
|
||||
<li class="list-group-item" i18n>No mail accounts defined.</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailRule }">
|
||||
<h4 class="mt-4">
|
||||
<ng-container i18n>Mail rules</ng-container>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editMailRule()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailRule }">
|
||||
<svg class="sidebaricon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||
</svg>
|
||||
<ng-container i18n>Add Rule</ng-container>
|
||||
</button>
|
||||
</h4>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailRule }">
|
||||
<h4 class="mt-4">
|
||||
<ng-container i18n>Mail rules</ng-container>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editMailRule()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailRule }">
|
||||
<svg class="sidebaricon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||
</svg>
|
||||
<ng-container i18n>Add Rule</ng-container>
|
||||
</button>
|
||||
</h4>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col" i18n>Name</div>
|
||||
<div class="col" i18n>Account</div>
|
||||
<div class="col" i18n>Actions</div>
|
||||
<div class="col" i18n>Name</div>
|
||||
<div class="col" i18n>Account</div>
|
||||
<div class="col" i18n>Actions</div>
|
||||
</div>
|
||||
</li>
|
||||
</li>
|
||||
|
||||
<li *ngFor="let rule of mailRules" class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailRule(rule)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailRule)">{{rule.name}}</button></div>
|
||||
<div class="col d-flex align-items-center">{{(mailAccountService.getCached(rule.account) | async)?.name}}</div>
|
||||
<div class="col">
|
||||
<div class="btn-group">
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailRule }" [disabled]="!userCanEdit(rule)" class="btn btn-sm btn-outline-secondary" type="button" (click)="editMailRule(rule)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
@for (rule of mailRules; track rule) {
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailRule(rule)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailRule)">{{rule.name}}</button></div>
|
||||
<div class="col d-flex align-items-center">{{(mailAccountService.getCached(rule.account) | async)?.name}}</div>
|
||||
<div class="col">
|
||||
<div class="btn-group">
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailRule }" [disabled]="!userCanEdit(rule)" class="btn btn-sm btn-outline-secondary" type="button" (click)="editMailRule(rule)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
</button>
|
||||
<button *pngxIfOwner="rule" class="btn btn-sm btn-outline-secondary" type="button" (click)="editPermissions(rule)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-lock" />
|
||||
</svg> <ng-container i18n>Permissions</ng-container>
|
||||
</button>
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailRule }" [disabled]="!userIsOwner(rule)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailRule(rule)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li *ngIf="mailRules.length === 0" class="list-group-item" i18n>No mail rules defined.</li>
|
||||
</ul>
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
</button>
|
||||
<button *pngxIfOwner="rule" class="btn btn-sm btn-outline-secondary" type="button" (click)="editPermissions(rule)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-lock" />
|
||||
</svg> <ng-container i18n>Permissions</ng-container>
|
||||
</button>
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailRule }" [disabled]="!userIsOwner(rule)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailRule(rule)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
@if (mailRules.length === 0) {
|
||||
<li class="list-group-item" i18n>No mail rules defined.</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="!mailAccounts || !mailRules">
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
</div>
|
||||
@if (!mailAccounts || !mailRules) {
|
||||
<div>
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -2,112 +2,131 @@
|
||||
<button class="btn btn-sm btn-outline-secondary me-2" (click)="clearSelection()" [hidden]="selectedObjects.size === 0">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg> <ng-container i18n>Clear selection</ng-container>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary me-5" (click)="setPermissions()" [disabled]="!userOwnsAll || selectedObjects.size === 0">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock" />
|
||||
</svg> <ng-container i18n>Permissions</ng-container>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" *pngxIfPermissions="{ action: PermissionAction.Add, type: permissionType }" i18n>
|
||||
</svg> <ng-container i18n>Clear selection</ng-container>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary me-5" (click)="setPermissions()" [disabled]="!userOwnsAll || selectedObjects.size === 0">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock" />
|
||||
</svg> <ng-container i18n>Permissions</ng-container>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" *pngxIfPermissions="{ action: PermissionAction.Add, type: permissionType }" i18n>
|
||||
<svg class="sidebaricon me-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||
</svg>
|
||||
Create
|
||||
</button>
|
||||
</pngx-page-header>
|
||||
</pngx-page-header>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md mb-2 mb-xl-0">
|
||||
<div class="form-inline d-flex align-items-center">
|
||||
<label class="text-muted me-2 mb-0" i18n>Filter by:</label>
|
||||
<input class="form-control form-control-sm flex-fill w-auto" type="text" autofocus [(ngModel)]="nameFilter" (keyup)="onNameFilterKeyUp($event)" placeholder="Name" i18n-placeholder>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md mb-2 mb-xl-0">
|
||||
<div class="form-inline d-flex align-items-center">
|
||||
<label class="text-muted me-2 mb-0" i18n>Filter by:</label>
|
||||
<input class="form-control form-control-sm flex-fill w-auto" type="text" autofocus [(ngModel)]="nameFilter" (keyup)="onNameFilterKeyUp($event)" placeholder="Name" i18n-placeholder>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ngb-pagination class="col-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ngb-pagination class="col-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||
</div>
|
||||
|
||||
<div class="card border mb-3">
|
||||
<table class="table table-striped align-middle shadow-sm mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<div class="form-check m-0 ms-2 me-n2">
|
||||
<input type="checkbox" class="form-check-input" id="all-objects" [disabled]="data.length === 0" (click)="toggleAll($event); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="all-objects"></label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="fw-normal" pngxSortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
|
||||
<th scope="col" class="fw-normal d-none d-sm-table-cell" pngxSortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
|
||||
<th scope="col" class="fw-normal" pngxSortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
|
||||
<th scope="col" class="fw-normal" *ngFor="let column of extraColumns" pngxSortable="{{column.key}}" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)">{{column.name}}</th>
|
||||
<th scope="col" class="fw-normal" i18n>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngIf="isLoading">
|
||||
<td colspan="5">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngFor="let object of data" (click)="toggleSelected(object, $event); $event.stopPropagation();">
|
||||
<td>
|
||||
<div class="form-check m-0 ms-2 me-n2">
|
||||
<input type="checkbox" class="form-check-input" id="{{typeName}}{{object.id}}" [checked]="selectedObjects.has(object.id)" (click)="toggleSelected(object); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="{{typeName}}{{object.id}}"></label>
|
||||
</div>
|
||||
</td>
|
||||
<td scope="row"><button class="btn btn-link ms-0 ps-0 text-start" (click)="openEditDialog(object)">{{ object.name }}</button> </td>
|
||||
<td scope="row" class="d-none d-sm-table-cell">{{ getMatching(object) }}</td>
|
||||
<td scope="row">{{ object.document_count }}</td>
|
||||
<td scope="row" *ngFor="let column of extraColumns">
|
||||
<div *ngIf="column.rendersHtml; else colValue" [innerHtml]="column.valueFn.call(null, object) | safeHtml"></div>
|
||||
<ng-template #colValue>{{ column.valueFn.call(null, object) }}</ng-template>
|
||||
</td>
|
||||
<td scope="row">
|
||||
<div class="btn-group d-block d-sm-none">
|
||||
<div ngbDropdown class="d-inline-block">
|
||||
<button type="button" class="btn btn-link" id="actionsMenuMobile" ngbDropdownToggle>
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#three-dots-vertical" />
|
||||
</svg>
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="actionsMenuMobile">
|
||||
<button (click)="filterDocuments(object)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }" ngbDropdownItem i18n>Filter Documents</button>
|
||||
<button (click)="openEditDialog(object)" *pngxIfPermissions="{ action: PermissionAction.Change, type: permissionType }" ngbDropdownItem i18n>Edit</button>
|
||||
<button class="text-danger" (click)="openDeleteDialog(object)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: permissionType }" ngbDropdownItem i18n>Delete</button>
|
||||
</div>
|
||||
<div class="card border mb-3">
|
||||
<table class="table table-striped align-middle shadow-sm mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<div class="form-check m-0 ms-2 me-n2">
|
||||
<input type="checkbox" class="form-check-input" id="all-objects" [disabled]="data.length === 0" (click)="toggleAll($event); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="all-objects"></label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="fw-normal" pngxSortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
|
||||
<th scope="col" class="fw-normal d-none d-sm-table-cell" pngxSortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
|
||||
<th scope="col" class="fw-normal" pngxSortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
|
||||
@for (column of extraColumns; track column) {
|
||||
<th scope="col" class="fw-normal" pngxSortable="{{column.key}}" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)">{{column.name}}</th>
|
||||
}
|
||||
<th scope="col" class="fw-normal" i18n>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (isLoading) {
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@for (object of data; track object) {
|
||||
<tr (click)="toggleSelected(object); $event.stopPropagation();">
|
||||
<td>
|
||||
<div class="form-check m-0 ms-2 me-n2">
|
||||
<input type="checkbox" class="form-check-input" id="{{typeName}}{{object.id}}" [checked]="selectedObjects.has(object.id)" (click)="toggleSelected(object); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="{{typeName}}{{object.id}}"></label>
|
||||
</div>
|
||||
</td>
|
||||
<td scope="row"><button class="btn btn-link ms-0 ps-0 text-start" (click)="openEditDialog(object)">{{ object.name }}</button> </td>
|
||||
<td scope="row" class="d-none d-sm-table-cell">{{ getMatching(object) }}</td>
|
||||
<td scope="row">{{ object.document_count }}</td>
|
||||
@for (column of extraColumns; track column) {
|
||||
<td scope="row">
|
||||
@if (column.rendersHtml) {
|
||||
<div [innerHtml]="column.valueFn.call(null, object) | safeHtml"></div>
|
||||
} @else {
|
||||
{{ column.valueFn.call(null, object) }}
|
||||
}
|
||||
</td>
|
||||
}
|
||||
<td scope="row">
|
||||
<div class="btn-group d-block d-sm-none">
|
||||
<div ngbDropdown class="d-inline-block">
|
||||
<button type="button" class="btn btn-link" id="actionsMenuMobile" ngbDropdownToggle>
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#three-dots-vertical" />
|
||||
</svg>
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="actionsMenuMobile">
|
||||
<button (click)="filterDocuments(object)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }" ngbDropdownItem i18n>Filter Documents</button>
|
||||
<button (click)="openEditDialog(object)" *pngxIfPermissions="{ action: PermissionAction.Change, type: permissionType }" ngbDropdownItem i18n>Edit</button>
|
||||
<button class="text-danger" (click)="openDeleteDialog(object)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: permissionType }" ngbDropdownItem i18n>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group d-none d-sm-block">
|
||||
<button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(object); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#filter" />
|
||||
</svg> <ng-container i18n>Documents</ng-container>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(object); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Change, type: permissionType }" [disabled]="!userCanEdit(object)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(object); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Delete, type: permissionType }" [disabled]="!userCanDelete(object)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group d-none d-sm-block">
|
||||
<button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(object); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#filter" />
|
||||
</svg> <ng-container i18n>Documents</ng-container>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(object); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Change, type: permissionType }" [disabled]="!userCanEdit(object)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
||||
</svg> <ng-container i18n>Edit</ng-container>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(object); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Delete, type: permissionType }" [disabled]="!userCanDelete(object)">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="d-flex mb-2" *ngIf="!isLoading">
|
||||
<div *ngIf="collectionSize > 0">
|
||||
<ng-container i18n>{collectionSize, plural, =1 {One {{typeName}}} other {{{collectionSize || 0}} total {{typeNamePlural}}}}</ng-container>
|
||||
<ng-container *ngIf="selectedObjects.size > 0"> ({{selectedObjects.size}} selected)</ng-container>
|
||||
</div>
|
||||
<ngb-pagination *ngIf="collectionSize > 20" class="ms-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||
</div>
|
||||
@if (!isLoading) {
|
||||
<div class="d-flex mb-2">
|
||||
@if (collectionSize > 0) {
|
||||
<div>
|
||||
<ng-container i18n>{collectionSize, plural, =1 {One {{typeName}}} other {{{collectionSize || 0}} total {{typeNamePlural}}}}</ng-container>
|
||||
@if (selectedObjects.size > 0) {
|
||||
({{selectedObjects.size}} selected)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (collectionSize > 20) {
|
||||
<ngb-pagination class="ms-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
@ -38,6 +38,201 @@ export interface LanguageOption {
|
||||
dateInputFormat?: string
|
||||
}
|
||||
|
||||
const LANGUAGE_OPTIONS = [
|
||||
{
|
||||
code: 'en-us',
|
||||
name: $localize`English (US)`,
|
||||
englishName: 'English (US)',
|
||||
dateInputFormat: 'mm/dd/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'af-za',
|
||||
name: $localize`Afrikaans`,
|
||||
englishName: 'Afrikaans',
|
||||
dateInputFormat: 'yyyy-mm-dd',
|
||||
},
|
||||
{
|
||||
code: 'ar-ar',
|
||||
name: $localize`Arabic`,
|
||||
englishName: 'Arabic',
|
||||
dateInputFormat: 'yyyy-mm-dd',
|
||||
},
|
||||
{
|
||||
code: 'be-by',
|
||||
name: $localize`Belarusian`,
|
||||
englishName: 'Belarusian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'bg-bg',
|
||||
name: $localize`Bulgarian`,
|
||||
englishName: 'Bulgarian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'ca-es',
|
||||
name: $localize`Catalan`,
|
||||
englishName: 'Catalan',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'cs-cz',
|
||||
name: $localize`Czech`,
|
||||
englishName: 'Czech',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'da-dk',
|
||||
name: $localize`Danish`,
|
||||
englishName: 'Danish',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'de-de',
|
||||
name: $localize`German`,
|
||||
englishName: 'German',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'el-gr',
|
||||
name: $localize`Greek`,
|
||||
englishName: 'Greek',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'en-gb',
|
||||
name: $localize`English (GB)`,
|
||||
englishName: 'English (GB)',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'es-es',
|
||||
name: $localize`Spanish`,
|
||||
englishName: 'Spanish',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'fi-fi',
|
||||
name: $localize`Finnish`,
|
||||
englishName: 'Finnish',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'fr-fr',
|
||||
name: $localize`French`,
|
||||
englishName: 'French',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'hu-hu',
|
||||
name: $localize`Hungarian`,
|
||||
englishName: 'Hungarian',
|
||||
dateInputFormat: 'yyyy.mm.dd',
|
||||
},
|
||||
{
|
||||
code: 'it-it',
|
||||
name: $localize`Italian`,
|
||||
englishName: 'Italian',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'lb-lu',
|
||||
name: $localize`Luxembourgish`,
|
||||
englishName: 'Luxembourgish',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'nl-nl',
|
||||
name: $localize`Dutch`,
|
||||
englishName: 'Dutch',
|
||||
dateInputFormat: 'dd-mm-yyyy',
|
||||
},
|
||||
{
|
||||
code: 'no-no',
|
||||
name: $localize`Norwegian`,
|
||||
englishName: 'Norwegian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'pl-pl',
|
||||
name: $localize`Polish`,
|
||||
englishName: 'Polish',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'pt-br',
|
||||
name: $localize`Portuguese (Brazil)`,
|
||||
englishName: 'Portuguese (Brazil)',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'pt-pt',
|
||||
name: $localize`Portuguese`,
|
||||
englishName: 'Portuguese',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'ro-ro',
|
||||
name: $localize`Romanian`,
|
||||
englishName: 'Romanian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'ru-ru',
|
||||
name: $localize`Russian`,
|
||||
englishName: 'Russian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'sk-sk',
|
||||
name: $localize`Slovak`,
|
||||
englishName: 'Slovak',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'sl-si',
|
||||
name: $localize`Slovenian`,
|
||||
englishName: 'Slovenian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'sr-cs',
|
||||
name: $localize`Serbian`,
|
||||
englishName: 'Serbian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'sv-se',
|
||||
name: $localize`Swedish`,
|
||||
englishName: 'Swedish',
|
||||
dateInputFormat: 'yyyy-mm-dd',
|
||||
},
|
||||
{
|
||||
code: 'tr-tr',
|
||||
name: $localize`Turkish`,
|
||||
englishName: 'Turkish',
|
||||
dateInputFormat: 'yyyy-mm-dd',
|
||||
},
|
||||
{
|
||||
code: 'uk-ua',
|
||||
name: $localize`Ukrainian`,
|
||||
englishName: 'Ukrainian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'zh-cn',
|
||||
name: $localize`Chinese Simplified`,
|
||||
englishName: 'Chinese Simplified',
|
||||
dateInputFormat: 'yyyy-mm-dd',
|
||||
},
|
||||
]
|
||||
|
||||
const ISO_LANGUAGE_OPTION: LanguageOption = {
|
||||
code: 'iso-8601',
|
||||
name: $localize`ISO 8601`,
|
||||
dateInputFormat: 'yyyy-mm-dd',
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
@ -151,210 +346,14 @@ export class SettingsService {
|
||||
}
|
||||
|
||||
getLanguageOptions(): LanguageOption[] {
|
||||
const languages = [
|
||||
{
|
||||
code: 'en-us',
|
||||
name: $localize`English (US)`,
|
||||
englishName: 'English (US)',
|
||||
dateInputFormat: 'mm/dd/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'af-za',
|
||||
name: $localize`Afrikaans`,
|
||||
englishName: 'Afrikaans',
|
||||
dateInputFormat: 'yyyy-mm-dd',
|
||||
},
|
||||
{
|
||||
code: 'ar-ar',
|
||||
name: $localize`Arabic`,
|
||||
englishName: 'Arabic',
|
||||
dateInputFormat: 'yyyy-mm-dd',
|
||||
},
|
||||
{
|
||||
code: 'be-by',
|
||||
name: $localize`Belarusian`,
|
||||
englishName: 'Belarusian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'bg-bg',
|
||||
name: $localize`Bulgarian`,
|
||||
englishName: 'Bulgarian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'ca-es',
|
||||
name: $localize`Catalan`,
|
||||
englishName: 'Catalan',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'cs-cz',
|
||||
name: $localize`Czech`,
|
||||
englishName: 'Czech',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'da-dk',
|
||||
name: $localize`Danish`,
|
||||
englishName: 'Danish',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'de-de',
|
||||
name: $localize`German`,
|
||||
englishName: 'German',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'el-gr',
|
||||
name: $localize`Greek`,
|
||||
englishName: 'Greek',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'en-gb',
|
||||
name: $localize`English (GB)`,
|
||||
englishName: 'English (GB)',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'es-es',
|
||||
name: $localize`Spanish`,
|
||||
englishName: 'Spanish',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'fi-fi',
|
||||
name: $localize`Finnish`,
|
||||
englishName: 'Finnish',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'fr-fr',
|
||||
name: $localize`French`,
|
||||
englishName: 'French',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'hu-hu',
|
||||
name: $localize`Hungarian`,
|
||||
englishName: 'Hungarian',
|
||||
dateInputFormat: 'yyyy.mm.dd',
|
||||
},
|
||||
{
|
||||
code: 'it-it',
|
||||
name: $localize`Italian`,
|
||||
englishName: 'Italian',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'lb-lu',
|
||||
name: $localize`Luxembourgish`,
|
||||
englishName: 'Luxembourgish',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'nl-nl',
|
||||
name: $localize`Dutch`,
|
||||
englishName: 'Dutch',
|
||||
dateInputFormat: 'dd-mm-yyyy',
|
||||
},
|
||||
{
|
||||
code: 'no-no',
|
||||
name: $localize`Norwegian`,
|
||||
englishName: 'Norwegian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'pl-pl',
|
||||
name: $localize`Polish`,
|
||||
englishName: 'Polish',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'pt-br',
|
||||
name: $localize`Portuguese (Brazil)`,
|
||||
englishName: 'Portuguese (Brazil)',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'pt-pt',
|
||||
name: $localize`Portuguese`,
|
||||
englishName: 'Portuguese',
|
||||
dateInputFormat: 'dd/mm/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'ro-ro',
|
||||
name: $localize`Romanian`,
|
||||
englishName: 'Romanian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'ru-ru',
|
||||
name: $localize`Russian`,
|
||||
englishName: 'Russian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'sk-sk',
|
||||
name: $localize`Slovak`,
|
||||
englishName: 'Slovak',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'sl-si',
|
||||
name: $localize`Slovenian`,
|
||||
englishName: 'Slovenian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'sr-cs',
|
||||
name: $localize`Serbian`,
|
||||
englishName: 'Serbian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'sv-se',
|
||||
name: $localize`Swedish`,
|
||||
englishName: 'Swedish',
|
||||
dateInputFormat: 'yyyy-mm-dd',
|
||||
},
|
||||
{
|
||||
code: 'tr-tr',
|
||||
name: $localize`Turkish`,
|
||||
englishName: 'Turkish',
|
||||
dateInputFormat: 'yyyy-mm-dd',
|
||||
},
|
||||
{
|
||||
code: 'uk-ua',
|
||||
name: $localize`Ukrainian`,
|
||||
englishName: 'Ukrainian',
|
||||
dateInputFormat: 'dd.mm.yyyy',
|
||||
},
|
||||
{
|
||||
code: 'zh-cn',
|
||||
name: $localize`Chinese Simplified`,
|
||||
englishName: 'Chinese Simplified',
|
||||
dateInputFormat: 'yyyy-mm-dd',
|
||||
},
|
||||
]
|
||||
|
||||
// Sort languages by localized name at runtime
|
||||
languages.sort((a, b) => {
|
||||
return LANGUAGE_OPTIONS.sort((a, b) => {
|
||||
return a.name < b.name ? -1 : 1
|
||||
})
|
||||
|
||||
return languages
|
||||
}
|
||||
|
||||
getDateLocaleOptions(): LanguageOption[] {
|
||||
let isoOption: LanguageOption = {
|
||||
code: 'iso-8601',
|
||||
name: $localize`ISO 8601`,
|
||||
dateInputFormat: 'yyyy-mm-dd',
|
||||
}
|
||||
return [isoOption].concat(this.getLanguageOptions())
|
||||
return [ISO_LANGUAGE_OPTION].concat(this.getLanguageOptions())
|
||||
}
|
||||
|
||||
private getLanguageCookieName() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user