]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Unify management lists with single template 514/head
authorMichael Shamoon <4887959+shamoon@users.noreply.github.com>
Wed, 23 Mar 2022 05:01:46 +0000 (22:01 -0700)
committerMichael Shamoon <4887959+shamoon@users.noreply.github.com>
Wed, 23 Mar 2022 20:31:00 +0000 (13:31 -0700)
27 files changed:
src-ui/src/app/app.module.ts
src-ui/src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html [moved from src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.html with 100% similarity]
src-ui/src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.scss [moved from src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.scss with 100% similarity]
src-ui/src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.ts [moved from src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.ts with 100% similarity]
src-ui/src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html [moved from src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.html with 100% similarity]
src-ui/src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.scss [moved from src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.scss with 100% similarity]
src-ui/src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.ts [moved from src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.ts with 100% similarity]
src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html [moved from src-ui/src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.html with 100% similarity]
src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.scss [moved from src-ui/src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.scss with 100% similarity]
src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.ts [moved from src-ui/src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.ts with 100% similarity]
src-ui/src/app/components/common/input/tags/tags.component.ts
src-ui/src/app/components/document-detail/document-detail.component.html
src-ui/src/app/components/document-detail/document-detail.component.ts
src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html
src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html
src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.html [deleted file]
src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.ts
src-ui/src/app/components/manage/document-type-list/document-type-list.component.scss [deleted file]
src-ui/src/app/components/manage/document-type-list/document-type-list.component.ts
src-ui/src/app/components/manage/management-list/management-list.component.html [moved from src-ui/src/app/components/manage/document-type-list/document-type-list.component.html with 81% similarity]
src-ui/src/app/components/manage/management-list/management-list.component.scss [moved from src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.scss with 100% similarity]
src-ui/src/app/components/manage/management-list/management-list.component.ts [moved from src-ui/src/app/components/manage/generic-list/generic-list.component.ts with 86% similarity]
src-ui/src/app/components/manage/tag-list/tag-list.component.html [deleted file]
src-ui/src/app/components/manage/tag-list/tag-list.component.scss [deleted file]
src-ui/src/app/components/manage/tag-list/tag-list.component.ts
src-ui/src/app/pipes/safehtml.pipe.ts [new file with mode: 0644]
src-ui/src/app/pipes/safeurl.pipe.ts [moved from src-ui/src/app/pipes/safe.pipe.ts with 83% similarity]

index 836337fbed932117734ccda5cc7512a3ea5c403d..eadafee890fe6ed849f858bee0b6a92c7ae01f12 100644 (file)
@@ -13,16 +13,16 @@ import { DocumentDetailComponent } from './components/document-detail/document-d
 import { DashboardComponent } from './components/dashboard/dashboard.component'
 import { TagListComponent } from './components/manage/tag-list/tag-list.component'
 import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'
+import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'
 import { LogsComponent } from './components/manage/logs/logs.component'
 import { SettingsComponent } from './components/manage/settings/settings.component'
 import { FormsModule, ReactiveFormsModule } from '@angular/forms'
 import { DatePipe, registerLocaleData } from '@angular/common'
 import { NotFoundComponent } from './components/not-found/not-found.component'
-import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'
 import { ConfirmDialogComponent } from './components/common/confirm-dialog/confirm-dialog.component'
-import { CorrespondentEditDialogComponent } from './components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component'
-import { TagEditDialogComponent } from './components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component'
-import { DocumentTypeEditDialogComponent } from './components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component'
+import { CorrespondentEditDialogComponent } from './components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
+import { TagEditDialogComponent } from './components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
+import { DocumentTypeEditDialogComponent } from './components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
 import { TagComponent } from './components/common/tag/tag.component'
 import { PageHeaderComponent } from './components/common/page-header/page-header.component'
 import { AppFrameComponent } from './components/app-frame/app-frame.component'
@@ -58,7 +58,8 @@ import { MetadataCollapseComponent } from './components/document-detail/metadata
 import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component'
 import { NgSelectModule } from '@ng-select/ng-select'
 import { NumberComponent } from './components/common/input/number/number.component'
-import { SafePipe } from './pipes/safe.pipe'
+import { SafeUrlPipe } from './pipes/safeurl.pipe'
+import { SafeHtmlPipe } from './pipes/safehtml.pipe'
 import { CustomDatePipe } from './pipes/custom-date.pipe'
 import { DateComponent } from './components/common/input/date/date.component'
 import { ISODateTimeAdapter } from './utils/ngb-iso-date-time-adapter'
@@ -112,8 +113,8 @@ registerLocaleData(localeZh)
     DocumentDetailComponent,
     DashboardComponent,
     TagListComponent,
-    CorrespondentListComponent,
     DocumentTypeListComponent,
+    CorrespondentListComponent,
     LogsComponent,
     SettingsComponent,
     NotFoundComponent,
@@ -150,7 +151,8 @@ registerLocaleData(localeZh)
     MetadataCollapseComponent,
     SelectDialogComponent,
     NumberComponent,
-    SafePipe,
+    SafeUrlPipe,
+    SafeHtmlPipe,
     CustomDatePipe,
     DateComponent,
     ColorComponent,
index d1dc61544a65d52f1a9c9973f295456eccec86f2..ecc63c54ccf05fb639831bdda43be1104505f8dc 100644 (file)
@@ -1,8 +1,8 @@
 import { Component, forwardRef, Input, OnInit } from '@angular/core'
 import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
-import { TagEditDialogComponent } from 'src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component'
 import { PaperlessTag } from 'src/app/data/paperless-tag'
+import { TagEditDialogComponent } from '../../edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
 import { TagService } from 'src/app/services/rest/tag.service'
 
 @Component({
index 5cdaf420da45df79501682def9a0ec97ba48f1c8..d16acb164df387057a284367616e047f11328009 100644 (file)
                             <pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true" [show-all]="true" [render-text-mode]="2"></pdf-viewer>
                         </div>
                         <ng-template #nativePdfViewer>
-                            <object [data]="previewUrl | safe" class="preview-sticky" width="100%"></object>
+                            <object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
                         </ng-template>
                     </ng-container>
                     <ng-container *ngIf="getContentType() == 'text/plain'">
-                        <object [data]="previewUrl | safe" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
+                        <object [data]="previewUrl | safeUrl" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
                     </ng-container>
                   </ng-template>
                 </li>
                 <pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" [render-text-mode]="2" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer>
             </div>
             <ng-template #nativePdfViewer>
-                <object [data]="previewUrl | safe" class="preview-sticky" width="100%"></object>
+                <object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
             </ng-template>
         </ng-container>
         <ng-container *ngIf="getContentType() == 'text/plain'">
-            <object [data]="previewUrl | safe" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
+            <object [data]="previewUrl | safeUrl" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
         </ng-container>
     </div>
 </div>
index 4d705131ef21ee8d9246915582380c85f0e36584..52593ced5178d95b392490bc0575a6ec23a851bb 100644 (file)
@@ -19,8 +19,8 @@ import { CorrespondentService } from 'src/app/services/rest/correspondent.servic
 import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
 import { DocumentService } from 'src/app/services/rest/document.service'
 import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
-import { CorrespondentEditDialogComponent } from '../manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component'
-import { DocumentTypeEditDialogComponent } from '../manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component'
+import { CorrespondentEditDialogComponent } from '../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
+import { DocumentTypeEditDialogComponent } from '../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
 import { PDFDocumentProxy } from 'ng2-pdf-viewer'
 import { ToastService } from 'src/app/services/toast.service'
 import { TextComponent } from '../common/input/text/text.component'
index 6cbc30c78760f19a341f6504957dd95c1eba6ae9..ec4b28bc156c32fee1fc0799fdd8694c09b81e0a 100644 (file)
@@ -51,7 +51,7 @@
               </svg>&nbsp;<span class="d-block d-md-inline" i18n>View</span>
             </a>
             <ng-template #previewContent>
-              <object [data]="previewUrl | safe" class="preview" width="100%"></object>
+              <object [data]="previewUrl | safeUrl" class="preview" width="100%"></object>
             </ng-template>
             <a class="btn btn-sm btn-outline-secondary" [href]="getDownloadUrl()">
               <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-download" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
index 9fe42779524c92b2d2b2b329c4e54dc314c4bdf8..e3c06e3f0e305b3d0d9b29765e2ba3ca166bc703 100644 (file)
@@ -77,7 +77,7 @@
             </svg>
           </a>
           <ng-template #previewContent>
-            <object [data]="previewUrl | safe" class="preview" width="100%"></object>
+            <object [data]="previewUrl | safeUrl" class="preview" width="100%"></object>
           </ng-template>
           <a [href]="getDownloadUrl()" class="btn btn-sm btn-outline-secondary" title="Download" (click)="$event.stopPropagation()" i18n-title>
             <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-download" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
diff --git a/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.html b/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.html
deleted file mode 100644 (file)
index 2b03305..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<app-page-header title="Correspondents" i18n-title>
-  <button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" i18n>Create</button>
-</app-page-header>
-
-<div class="row">
-  <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" (pageChange)="reloadData()" aria-label="Default pagination"></ngb-pagination>
-</div>
-
-<table class="table table-striped align-middle border shadow-sm">
-    <thead>
-    <tr>
-      <th scope="col" sortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
-      <th scope="col" sortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
-      <th scope="col" sortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
-      <th scope="col" sortable="last_correspondence" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Last correspondence</th>
-      <th scope="col" i18n>Actions</th>
-    </tr>
-    </thead>
-    <tbody>
-    <tr *ngFor="let correspondent of data">
-      <td scope="row">{{ correspondent.name }}</td>
-      <td scope="row">{{ getMatching(correspondent) }}</td>
-      <td scope="row">{{ correspondent.document_count }}</td>
-      <td scope="row">{{ correspondent.last_correspondence | customDate }}</td>
-        <td scope="row">
-          <div class="btn-group">
-            <button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(correspondent)">
-              <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel" viewBox="0 0 16 16">
-                <path fill-rule="evenodd" d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/>
-              </svg>&nbsp;<ng-container i18n>Documents</ng-container>
-            </button>
-            <button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(correspondent)">
-              <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
-                <path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
-              </svg>&nbsp;<ng-container i18n>Edit</ng-container>
-            </button>
-            <button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(correspondent)">
-              <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
-                <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
-                <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
-              </svg>&nbsp;<ng-container i18n>Delete</ng-container>
-            </button>
-          </div>
-        </td>
-    </tr>
-    </tbody>
-  </table>
-
-  <div class="d-flex">
-    <div i18n *ngIf="collectionSize > 0">{collectionSize, plural, =1 {One correspondent} other {{{collectionSize || 0}} total correspondents}}</div>
-    <ngb-pagination *ngIf="collectionSize > 20" class="ms-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" (pageChange)="reloadData()" aria-label="Default pagination"></ngb-pagination>
-  </div>
index 3322d2ff0bb9ba192516288c31dbae4b009fe630..4887f5e34dedee8b4140e9fbfbb862f4707628f1 100644 (file)
@@ -2,39 +2,48 @@ import { Component } from '@angular/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type'
 import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
+import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
 import { ToastService } from 'src/app/services/toast.service'
-import { GenericListComponent } from '../generic-list/generic-list.component'
-import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog/correspondent-edit-dialog.component'
+import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
+import { ManagementListComponent } from '../management-list/management-list.component'
 
 @Component({
   selector: 'app-correspondent-list',
-  templateUrl: './correspondent-list.component.html',
-  styleUrls: ['./correspondent-list.component.scss'],
+  templateUrl: './../management-list/management-list.component.html',
+  styleUrls: ['./../management-list/management-list.component.scss'],
+  providers: [{ provide: CustomDatePipe }],
 })
-export class CorrespondentListComponent extends GenericListComponent<PaperlessCorrespondent> {
+export class CorrespondentListComponent extends ManagementListComponent<PaperlessCorrespondent> {
   constructor(
     correspondentsService: CorrespondentService,
     modalService: NgbModal,
-    private list: DocumentListViewService,
-    toastService: ToastService
+    toastService: ToastService,
+    list: DocumentListViewService,
+    private datePipe: CustomDatePipe
   ) {
     super(
       correspondentsService,
       modalService,
       CorrespondentEditDialogComponent,
-      toastService
+      toastService,
+      list,
+      FILTER_CORRESPONDENT,
+      $localize`correspondent`,
+      [
+        {
+          key: 'last_correspondence',
+          name: $localize`Last correspondence`,
+          valueFn: (c: PaperlessCorrespondent) => {
+            return this.datePipe.transform(c.last_correspondence)
+          },
+        },
+      ]
     )
   }
 
   getDeleteMessage(object: PaperlessCorrespondent) {
     return $localize`Do you really want to delete the correspondent "${object.name}"?`
   }
-
-  filterDocuments(object: PaperlessCorrespondent) {
-    this.list.quickFilter([
-      { rule_type: FILTER_CORRESPONDENT, value: object.id.toString() },
-    ])
-  }
 }
diff --git a/src-ui/src/app/components/manage/document-type-list/document-type-list.component.scss b/src-ui/src/app/components/manage/document-type-list/document-type-list.component.scss
deleted file mode 100644 (file)
index e69de29..0000000
index 6950fb4839d75c183d53c2d7a1f822fbab90137e..106194f36cfe2f66240d3364f67253415bff3c6f 100644 (file)
@@ -5,31 +5,34 @@ import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
 import { ToastService } from 'src/app/services/toast.service'
-import { GenericListComponent } from '../generic-list/generic-list.component'
-import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog/document-type-edit-dialog.component'
+import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
+import { ManagementListComponent } from '../management-list/management-list.component'
 
 @Component({
   selector: 'app-document-type-list',
-  templateUrl: './document-type-list.component.html',
-  styleUrls: ['./document-type-list.component.scss'],
+  templateUrl: './../management-list/management-list.component.html',
+  styleUrls: ['./../management-list/management-list.component.scss'],
 })
-export class DocumentTypeListComponent extends GenericListComponent<PaperlessDocumentType> {
+export class DocumentTypeListComponent extends ManagementListComponent<PaperlessDocumentType> {
   constructor(
-    service: DocumentTypeService,
+    documentTypeService: DocumentTypeService,
     modalService: NgbModal,
-    private list: DocumentListViewService,
-    toastService: ToastService
+    toastService: ToastService,
+    list: DocumentListViewService
   ) {
-    super(service, modalService, DocumentTypeEditDialogComponent, toastService)
+    super(
+      documentTypeService,
+      modalService,
+      DocumentTypeEditDialogComponent,
+      toastService,
+      list,
+      FILTER_DOCUMENT_TYPE,
+      $localize`document type`,
+      []
+    )
   }
 
   getDeleteMessage(object: PaperlessDocumentType) {
     return $localize`Do you really want to delete the document type "${object.name}"?`
   }
-
-  filterDocuments(object: PaperlessDocumentType) {
-    this.list.quickFilter([
-      { rule_type: FILTER_DOCUMENT_TYPE, value: object.id.toString() },
-    ])
-  }
 }
similarity index 81%
rename from src-ui/src/app/components/manage/document-type-list/document-type-list.component.html
rename to src-ui/src/app/components/manage/management-list/management-list.component.html
index 32a47dcd80931cdec07a02385296a6e0e5bac4fb..c9c9d60d34b403a293edd23ab2b7b33bed76640a 100644 (file)
@@ -1,4 +1,4 @@
-<app-page-header title="Document types" i18n-title>
+<app-page-header title="{{ typeName | titlecase }}s">
   <button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" i18n>Create</button>
 </app-page-header>
 
       <th scope="col" sortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
       <th scope="col" sortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
       <th scope="col" sortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
+      <th scope="col" *ngFor="let column of extraColumns" sortable="{{column.key}}" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)">{{column.name}}</th>
       <th scope="col" i18n>Actions</th>
     </tr>
   </thead>
   <tbody>
-    <tr *ngFor="let document_type of data">
-      <td scope="row">{{ document_type.name }}</td>
-      <td scope="row">{{ getMatching(document_type) }}</td>
-      <td scope="row">{{ document_type.document_count }}</td>
+    <tr *ngFor="let object of data">
+      <td scope="row">{{ object.name }}</td>
+      <td scope="row">{{ 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">
-          <button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(document_type)">
+          <button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(object)">
             <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel" viewBox="0 0 16 16">
               <path fill-rule="evenodd" d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/>
             </svg>&nbsp;<ng-container i18n>Documents</ng-container>
           </button>
-          <button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(document_type)">
+          <button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(object)">
             <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
               <path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
             </svg>&nbsp;<ng-container i18n>Edit</ng-container>
           </button>
-          <button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(document_type)">
+          <button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(object)">
             <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
               <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
               <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
@@ -52,6 +57,6 @@
 </table>
 
 <div class="d-flex">
-  <div i18n *ngIf="collectionSize > 0">{collectionSize, plural, =1 {One document type} other {{{collectionSize || 0}} total document types}}</div>
+  <div i18n *ngIf="collectionSize > 0">{collectionSize, plural, =1 {One {{typeName}}} other {{{collectionSize || 0}} total {{typeName}}s}}</div>
   <ngb-pagination *ngIf="collectionSize > 20" class="ms-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" (pageChange)="reloadData()" aria-label="Default pagination"></ngb-pagination>
 </div>
similarity index 86%
rename from src-ui/src/app/components/manage/generic-list/generic-list.component.ts
rename to src-ui/src/app/components/manage/management-list/management-list.component.ts
index c719a67a1fe23fc1836d3b4c83b55b68c2199517..193ac8bb6c24a81620036218bbc6497d5582cd2b 100644 (file)
@@ -18,19 +18,34 @@ import {
   SortableDirective,
   SortEvent,
 } from 'src/app/directives/sortable.directive'
+import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service'
 import { ToastService } from 'src/app/services/toast.service'
 import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 
+export interface ManagementListColumn {
+  key: string
+
+  name: string
+
+  valueFn: any
+
+  rendersHtml?: boolean
+}
+
 @Directive()
-export abstract class GenericListComponent<T extends ObjectWithId>
+export abstract class ManagementListComponent<T extends ObjectWithId>
   implements OnInit, OnDestroy
 {
   constructor(
     private service: AbstractNameFilterService<T>,
     private modalService: NgbModal,
     private editDialogComponent: any,
-    private toastService: ToastService
+    private toastService: ToastService,
+    private list: DocumentListViewService,
+    protected filterRuleType: number,
+    public typeName: string,
+    public extraColumns: ManagementListColumn[]
   ) {}
 
   @ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
@@ -48,24 +63,6 @@ export abstract class GenericListComponent<T extends ObjectWithId>
   private subscription: Subscription
   private _nameFilter: string
 
-  getMatching(o: MatchingModel) {
-    if (o.matching_algorithm == MATCH_AUTO) {
-      return $localize`Automatic`
-    } else if (o.match && o.match.length > 0) {
-      return `${
-        MATCHING_ALGORITHMS.find((a) => a.id == o.matching_algorithm).shortName
-      }: ${o.match}`
-    } else {
-      return '-'
-    }
-  }
-
-  onSort(event: SortEvent) {
-    this.sortField = event.column
-    this.sortReverse = event.reverse
-    this.reloadData()
-  }
-
   ngOnInit(): void {
     this.reloadData()
 
@@ -84,6 +81,24 @@ export abstract class GenericListComponent<T extends ObjectWithId>
     this.subscription.unsubscribe()
   }
 
+  getMatching(o: MatchingModel) {
+    if (o.matching_algorithm == MATCH_AUTO) {
+      return $localize`Automatic`
+    } else if (o.match && o.match.length > 0) {
+      return `${
+        MATCHING_ALGORITHMS.find((a) => a.id == o.matching_algorithm).shortName
+      }: ${o.match}`
+    } else {
+      return '-'
+    }
+  }
+
+  onSort(event: SortEvent) {
+    this.sortField = event.column
+    this.sortReverse = event.reverse
+    this.reloadData()
+  }
+
   reloadData() {
     this.service
       .listFiltered(
@@ -121,7 +136,13 @@ export abstract class GenericListComponent<T extends ObjectWithId>
   }
 
   getDeleteMessage(object: T) {
-    return $localize`Do you really want to delete this element?`
+    return $localize`Do you really want to delete the ${this.typeName}?`
+  }
+
+  filterDocuments(object: ObjectWithId) {
+    this.list.quickFilter([
+      { rule_type: this.filterRuleType, value: object.id.toString() },
+    ])
   }
 
   openDeleteDialog(object: T) {
diff --git a/src-ui/src/app/components/manage/tag-list/tag-list.component.html b/src-ui/src/app/components/manage/tag-list/tag-list.component.html
deleted file mode 100644 (file)
index d51ad7d..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<app-page-header title="Tags" i18n-title>
-  <button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" i18n>Create</button>
-</app-page-header>
-
-<div class="row">
-  <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" (pageChange)="reloadData()" aria-label="Default pagination"></ngb-pagination>
-</div>
-
-<table class="table table-striped align-middle border shadow-sm">
-  <thead>
-    <tr>
-      <th scope="col" sortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
-      <th scope="col" i18n>Color</th>
-      <th scope="col" sortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
-      <th scope="col" sortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
-      <th scope="col" i18n>Actions</th>
-    </tr>
-  </thead>
-  <tbody>
-    <tr *ngFor="let tag of data">
-      <td scope="row">{{ tag.name }}</td>
-      <td scope="row"><span class="badge" [style.color]="tag.text_color"
-          [style.background-color]="tag.color">{{tag.color}}</span></td>
-      <td scope="row">{{ getMatching(tag) }}</td>
-      <td scope="row">{{ tag.document_count }}</td>
-      <td scope="row">
-        <div class="btn-group">
-          <button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(tag)">
-            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel" viewBox="0 0 16 16">
-              <path fill-rule="evenodd" d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/>
-            </svg>&nbsp;<ng-container i18n>Documents</ng-container>
-          </button>
-          <button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(tag)">
-            <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
-              <path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
-            </svg>&nbsp;<ng-container i18n>Edit</ng-container>
-          </button>
-          <button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(tag)">
-            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
-              <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
-              <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
-            </svg>&nbsp;<ng-container i18n>Delete</ng-container>
-          </button>
-        </div>
-      </td>
-    </tr>
-  </tbody>
-</table>
-
-<div class="d-flex">
-  <div i18n *ngIf="collectionSize > 0">{collectionSize, plural, =1 {One tag} other {{{collectionSize || 0}} total tags}}</div>
-  <ngb-pagination *ngIf="collectionSize > 20" class="ms-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" (pageChange)="reloadData()" aria-label="Default pagination"></ngb-pagination>
-</div>
diff --git a/src-ui/src/app/components/manage/tag-list/tag-list.component.scss b/src-ui/src/app/components/manage/tag-list/tag-list.component.scss
deleted file mode 100644 (file)
index e69de29..0000000
index f29598bfebedbd54239b1b071708aab35e62c5a8..01a1614bf7f66dbdc7a0da5dc3e182c976b77745 100644 (file)
@@ -5,31 +5,43 @@ import { PaperlessTag } from 'src/app/data/paperless-tag'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 import { TagService } from 'src/app/services/rest/tag.service'
 import { ToastService } from 'src/app/services/toast.service'
-import { GenericListComponent } from '../generic-list/generic-list.component'
-import { TagEditDialogComponent } from './tag-edit-dialog/tag-edit-dialog.component'
+import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
+import { ManagementListComponent } from '../management-list/management-list.component'
 
 @Component({
   selector: 'app-tag-list',
-  templateUrl: './tag-list.component.html',
-  styleUrls: ['./tag-list.component.scss'],
+  templateUrl: './../management-list/management-list.component.html',
+  styleUrls: ['./../management-list/management-list.component.scss'],
 })
-export class TagListComponent extends GenericListComponent<PaperlessTag> {
+export class TagListComponent extends ManagementListComponent<PaperlessTag> {
   constructor(
     tagService: TagService,
     modalService: NgbModal,
-    private list: DocumentListViewService,
-    toastService: ToastService
+    toastService: ToastService,
+    list: DocumentListViewService
   ) {
-    super(tagService, modalService, TagEditDialogComponent, toastService)
+    super(
+      tagService,
+      modalService,
+      TagEditDialogComponent,
+      toastService,
+      list,
+      FILTER_HAS_TAGS_ALL,
+      $localize`tag`,
+      [
+        {
+          key: 'color',
+          name: $localize`Color`,
+          rendersHtml: true,
+          valueFn: (t: PaperlessTag) => {
+            return `<span class="badge" style="color: ${t.text_color}; background-color: ${t.color}">${t.color}</span>`
+          },
+        },
+      ]
+    )
   }
 
   getDeleteMessage(object: PaperlessTag) {
     return $localize`Do you really want to delete the tag "${object.name}"?`
   }
-
-  filterDocuments(object: PaperlessTag) {
-    this.list.quickFilter([
-      { rule_type: FILTER_HAS_TAGS_ALL, value: object.id.toString() },
-    ])
-  }
 }
diff --git a/src-ui/src/app/pipes/safehtml.pipe.ts b/src-ui/src/app/pipes/safehtml.pipe.ts
new file mode 100644 (file)
index 0000000..6fba1e7
--- /dev/null
@@ -0,0 +1,13 @@
+import { Pipe, PipeTransform } from '@angular/core'
+import { DomSanitizer } from '@angular/platform-browser'
+
+@Pipe({
+  name: 'safeHtml',
+})
+export class SafeHtmlPipe implements PipeTransform {
+  constructor(private sanitizer: DomSanitizer) {}
+
+  transform(html) {
+    return this.sanitizer.bypassSecurityTrustHtml(html)
+  }
+}
similarity index 83%
rename from src-ui/src/app/pipes/safe.pipe.ts
rename to src-ui/src/app/pipes/safeurl.pipe.ts
index 5a3921cbb1e0c7e5ff39f4cc74c229308b3bece2..5a7174e06ed671c6809bdb057c087027489711fb 100644 (file)
@@ -2,9 +2,9 @@ import { Pipe, PipeTransform } from '@angular/core'
 import { DomSanitizer } from '@angular/platform-browser'
 
 @Pipe({
-  name: 'safe',
+  name: 'safeurl',
 })
-export class SafePipe implements PipeTransform {
+export class SafeUrlPipe implements PipeTransform {
   constructor(private sanitizer: DomSanitizer) {}
 
   transform(url) {