mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Unify management lists with single template
This commit is contained in:
		| @@ -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, | ||||
|   | ||||
| @@ -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({ | ||||
|   | ||||
| @@ -142,11 +142,11 @@ | ||||
|                             <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> | ||||
| @@ -166,11 +166,11 @@ | ||||
|                 <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> | ||||
|   | ||||
| @@ -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' | ||||
|   | ||||
| @@ -51,7 +51,7 @@ | ||||
|               </svg> <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"> | ||||
|   | ||||
| @@ -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"> | ||||
|   | ||||
| @@ -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> <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> <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> <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> | ||||
| @@ -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() }, | ||||
|     ]) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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() }, | ||||
|     ]) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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> | ||||
| 
 | ||||
| @@ -19,27 +19,32 @@ | ||||
|       <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> <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> <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> | ||||
| @@ -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) { | ||||
| @@ -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> <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> <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> <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> | ||||
| @@ -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() }, | ||||
|     ]) | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										13
									
								
								src-ui/src/app/pipes/safehtml.pipe.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src-ui/src/app/pipes/safehtml.pipe.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
|   } | ||||
| } | ||||
| @@ -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) { | ||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon