From 864e421cd36ed20c520e2e7a9d5f8180dc560cf7 Mon Sep 17 00:00:00 2001
From: jonaswinkler <jonas.winkler@jpwinkler.de>
Date: Sun, 17 Jan 2021 00:15:45 +0100
Subject: [PATCH] add configuration option for changing date display in
 paperless #325

---
 src-ui/src/app/app.module.ts                  | 14 ++++++-
 .../saved-view-widget.component.html          |  2 +-
 .../saved-view-widget.component.ts            |  4 +-
 .../document-detail.component.html            |  4 +-
 .../document-card-large.component.html        |  2 +-
 .../document-card-small.component.html        |  2 +-
 .../document-list.component.html              |  4 +-
 .../correspondent-list.component.html         |  2 +-
 .../manage/logs/logs.component.html           |  2 +-
 .../manage/settings/settings.component.html   | 37 ++++++++++++++++++-
 .../manage/settings/settings.component.ts     | 20 ++++++++--
 src-ui/src/app/pipes/custom-date.pipe.spec.ts |  8 ++++
 src-ui/src/app/pipes/custom-date.pipe.ts      | 19 ++++++++++
 src-ui/src/app/services/settings.service.ts   | 25 +++++++++----
 14 files changed, 119 insertions(+), 26 deletions(-)
 create mode 100644 src-ui/src/app/pipes/custom-date.pipe.spec.ts
 create mode 100644 src-ui/src/app/pipes/custom-date.pipe.ts

diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts
index 30411c980..49e937c0b 100644
--- a/src-ui/src/app/app.module.ts
+++ b/src-ui/src/app/app.module.ts
@@ -13,7 +13,7 @@ import { DocumentTypeListComponent } from './components/manage/document-type-lis
 import { LogsComponent } from './components/manage/logs/logs.component';
 import { SettingsComponent } from './components/manage/settings/settings.component';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-import { DatePipe } from '@angular/common';
+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';
@@ -59,6 +59,15 @@ import { SelectDialogComponent } from './components/common/select-dialog/select-
 import { NgSelectModule } from '@ng-select/ng-select';
 import { NumberComponent } from './components/common/input/number/number.component';
 import { SafePipe } from './pipes/safe.pipe';
+import { CustomDatePipe } from './pipes/custom-date.pipe';
+
+import localeFr from '@angular/common/locales/fr';
+import localeNl from '@angular/common/locales/nl';
+import localeDe from '@angular/common/locales/de';
+
+registerLocaleData(localeFr)
+registerLocaleData(localeNl)
+registerLocaleData(localeDe)
 
 @NgModule({
   declarations: [
@@ -108,7 +117,8 @@ import { SafePipe } from './pipes/safe.pipe';
     MetadataCollapseComponent,
     SelectDialogComponent,
     NumberComponent,
-    SafePipe
+    SafePipe,
+    CustomDatePipe
   ],
   imports: [
     BrowserModule,
diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html
index 1bf06b60f..d907c59d6 100644
--- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html
+++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html
@@ -12,7 +12,7 @@
     </thead>
     <tbody>
       <tr *ngFor="let doc of documents" routerLink="/documents/{{doc.id}}">
-        <td>{{doc.created | date}}</td>
+        <td>{{doc.created | customDate}}</td>
         <td>{{doc.title | documentTitle}}<app-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ml-1"></app-tag>
       </tr>
     </tbody>
diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts
index f200d8db9..7405c2848 100644
--- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts
+++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts
@@ -16,7 +16,7 @@ export class SavedViewWidgetComponent implements OnInit {
     private documentService: DocumentService,
     private router: Router,
     private list: DocumentListViewService) { }
-  
+
   @Input()
   savedView: PaperlessSavedView
 
@@ -34,7 +34,7 @@ export class SavedViewWidgetComponent implements OnInit {
     } else {
       this.list.load(this.savedView)
       this.router.navigate(["documents"])
-      }
+    }
   }
 
 }
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html
index 7d5e93fbe..253d5b167 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.html
+++ b/src-ui/src/app/components/document-detail/document-detail.component.html
@@ -85,11 +85,11 @@
                             <tbody>
                                 <tr>
                                     <td i18n>Date modified</td>
-                                    <td>{{document.modified | date:'medium'}}</td>
+                                    <td>{{document.modified | customDate}}</td>
                                 </tr>
                                 <tr>
                                     <td i18n>Date added</td>
-                                    <td>{{document.added | date:'medium'}}</td>
+                                    <td>{{document.added | customDate}}</td>
                                 </tr>
                                 <tr>
                                     <td i18n>Media filename</td>
diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html
index c2ea9980b..85964f528 100644
--- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html
+++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html
@@ -64,7 +64,7 @@
             <ngb-progressbar [type]="searchScoreClass" [value]="searchScore" class="search-score-bar mx-2" [max]="1"></ngb-progressbar>
           </div>
 
-          <small class="text-muted" [class.ml-auto]="!searchScore" i18n>Created: {{document.created | date}}</small>
+          <small class="text-muted" [class.ml-auto]="!searchScore" i18n>Created: {{document.created | customDate}}</small>
         </div>
 
       </div>
diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html
index 4f11b2a98..a0d0299a5 100644
--- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html
+++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html
@@ -50,7 +50,7 @@
             </svg>
           </a>
         </div>
-        <small class="text-muted pl-1">{{document.created | date}}</small>
+        <small class="text-muted pl-1">{{document.created | customDate:'shortDate'}}</small>
       </div>
 
     </div>
diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html
index 18f39bfb5..a29e146bf 100644
--- a/src-ui/src/app/components/document-list/document-list.component.html
+++ b/src-ui/src/app/components/document-list/document-list.component.html
@@ -160,10 +160,10 @@
         </ng-container>
       </td>
       <td>
-        {{d.created | date}}
+        {{d.created | customDate}}
       </td>
       <td class="d-none d-xl-table-cell">
-        {{d.added | date}}
+        {{d.added | customDate}}
       </td>
     </tr>
   </tbody>
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
index 08704190e..ffe260d73 100644
--- 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
@@ -21,7 +21,7 @@
       <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 | date }}</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)">
diff --git a/src-ui/src/app/components/manage/logs/logs.component.html b/src-ui/src/app/components/manage/logs/logs.component.html
index d21cea60d..be14faf1d 100644
--- a/src-ui/src/app/components/manage/logs/logs.component.html
+++ b/src-ui/src/app/components/manage/logs/logs.component.html
@@ -20,7 +20,7 @@
   <p
     class="m-0 p-0 log-entry-{{log.level}}"
     *ngFor="let log of logs">
-      {{log.created | date:'short'}}
+      {{log.created | customDate:'short'}}
       {{getLevelText(log.level)}}
       {{log.message}}
   </p>
diff --git a/src-ui/src/app/components/manage/settings/settings.component.html b/src-ui/src/app/components/manage/settings/settings.component.html
index e7fac646f..d5acfb149 100644
--- a/src-ui/src/app/components/manage/settings/settings.component.html
+++ b/src-ui/src/app/components/manage/settings/settings.component.html
@@ -19,7 +19,7 @@
           <div class="col">
 
             <select class="form-control" formControlName="displayLanguage">
-              <option *ngFor="let lang of languages" [ngValue]="lang.code">{{lang.name}}</option>
+              <option *ngFor="let lang of displayLanguageOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code"> ({{lang.englishName}})</span></option>
             </select>
 
             <small class="form-text text-muted" i18n>You need to reload the page after applying a new language.</small>
@@ -27,6 +27,41 @@
           </div>
         </div>
 
+        <div class="form-row form-group">
+          <div class="col-md-3 col-form-label">
+            <span i18n>Date display</span>
+          </div>
+          <div class="col">
+
+            <select class="form-control" formControlName="dateLocale">
+              <option *ngFor="let lang of dateLocaleOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code"> ({{today | date:'shortDate':null:lang.code}})</span></option>
+            </select>
+
+          </div>
+        </div>
+
+        <div class="form-row form-group">
+          <div class="col-md-3 col-form-label">
+            <span i18n>Date format</span>
+          </div>
+          <div class="col">
+
+            <div class="custom-control custom-radio">
+              <input type="radio" id="dateFormatShort" name="dateFormat" class="custom-control-input" formControlName="dateFormat" value="shortDate">
+              <label class="custom-control-label" for="dateFormatShort" i18n>Short: {{today | customDate:'shortDate'}}</label>
+            </div>
+            <div class="custom-control custom-radio">
+              <input type="radio" id="dateFormatMedium" name="dateFormat" class="custom-control-input" formControlName="dateFormat" value="mediumDate">
+              <label class="custom-control-label" for="dateFormatMedium" i18n>Medium: {{today | customDate:'mediumDate'}}</label>
+            </div>
+            <div class="custom-control custom-radio">
+              <input type="radio" id="dateFormatLong" name="dateFormat" class="custom-control-input" formControlName="dateFormat" value="longDate">
+              <label class="custom-control-label" for="dateFormatLong" i18n>Long: {{today | customDate:'longDate'}}</label>
+            </div>
+
+          </div>
+        </div>
+
         <div class="form-row form-group">
           <div class="col-md-3 col-form-label">
             <span i18n>Items per page</span>
diff --git a/src-ui/src/app/components/manage/settings/settings.component.ts b/src-ui/src/app/components/manage/settings/settings.component.ts
index f0d83a15f..8c1517800 100644
--- a/src-ui/src/app/components/manage/settings/settings.component.ts
+++ b/src-ui/src/app/components/manage/settings/settings.component.ts
@@ -3,7 +3,7 @@ import { FormControl, FormGroup } from '@angular/forms';
 import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
 import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 import { SavedViewService } from 'src/app/services/rest/saved-view.service';
-import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
+import { LanguageOption, SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
 import { ToastService } from 'src/app/services/toast.service';
 
 @Component({
@@ -23,7 +23,9 @@ export class SettingsComponent implements OnInit {
     'darkModeEnabled': new FormControl(this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED)),
     'useNativePdfViewer': new FormControl(this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER)),
     'savedViews': this.savedViewGroup,
-    'displayLanguage': new FormControl(this.settings.getLanguage())
+    'displayLanguage': new FormControl(this.settings.getLanguage()),
+    'dateLocale': new FormControl(this.settings.get(SETTINGS_KEYS.DATE_LOCALE)),
+    'dateFormat': new FormControl(this.settings.get(SETTINGS_KEYS.DATE_FORMAT)),
   })
 
   savedViews: PaperlessSavedView[]
@@ -64,14 +66,24 @@ export class SettingsComponent implements OnInit {
     this.settings.set(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, this.settingsForm.value.darkModeUseSystem)
     this.settings.set(SETTINGS_KEYS.DARK_MODE_ENABLED, (this.settingsForm.value.darkModeEnabled == true).toString())
     this.settings.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, this.settingsForm.value.useNativePdfViewer)
+    this.settings.set(SETTINGS_KEYS.DATE_LOCALE, this.settingsForm.value.dateLocale)
+    this.settings.set(SETTINGS_KEYS.DATE_FORMAT, this.settingsForm.value.dateFormat)
     this.settings.setLanguage(this.settingsForm.value.displayLanguage)
     this.documentListViewService.updatePageSize()
     this.settings.updateDarkModeSettings()
     this.toastService.showInfo($localize`Settings saved successfully.`)
   }
 
-  get languages() {
-    return this.settings.getLanguageOptions()
+  get displayLanguageOptions(): LanguageOption[] {
+    return [{code: "", name: $localize`Use system language`}].concat(this.settings.getLanguageOptions())
+  }
+
+  get dateLocaleOptions(): LanguageOption[] {
+    return [{code: "", name: $localize`Use same as display language`}].concat(this.settings.getLanguageOptions())
+  }
+
+  get today() {
+    return new Date()
   }
 
   saveSettings() {
diff --git a/src-ui/src/app/pipes/custom-date.pipe.spec.ts b/src-ui/src/app/pipes/custom-date.pipe.spec.ts
new file mode 100644
index 000000000..822966baf
--- /dev/null
+++ b/src-ui/src/app/pipes/custom-date.pipe.spec.ts
@@ -0,0 +1,8 @@
+import { CustomDatePipe } from './custom-date.pipe';
+
+describe('CustomDatePipe', () => {
+  it('create an instance', () => {
+    const pipe = new CustomDatePipe();
+    expect(pipe).toBeTruthy();
+  });
+});
diff --git a/src-ui/src/app/pipes/custom-date.pipe.ts b/src-ui/src/app/pipes/custom-date.pipe.ts
new file mode 100644
index 000000000..19989fbdd
--- /dev/null
+++ b/src-ui/src/app/pipes/custom-date.pipe.ts
@@ -0,0 +1,19 @@
+import { DatePipe } from '@angular/common';
+import { Inject, LOCALE_ID, Pipe, PipeTransform } from '@angular/core';
+import { SettingsService, SETTINGS_KEYS } from '../services/settings.service';
+
+@Pipe({
+  name: 'customDate'
+})
+export class CustomDatePipe extends DatePipe implements PipeTransform {
+
+  constructor(@Inject(LOCALE_ID) locale: string, private settings: SettingsService) {
+    super(settings.get(SETTINGS_KEYS.DATE_LOCALE) || locale)
+
+  }
+
+  transform(value: any, format?: string, timezone?: string, locale?: string): string | null {
+    return super.transform(value, format || this.settings.get(SETTINGS_KEYS.DATE_FORMAT), timezone, locale)
+  }
+
+}
diff --git a/src-ui/src/app/services/settings.service.ts b/src-ui/src/app/services/settings.service.ts
index 50adcf5a7..04918d835 100644
--- a/src-ui/src/app/services/settings.service.ts
+++ b/src-ui/src/app/services/settings.service.ts
@@ -9,13 +9,21 @@ export interface PaperlessSettings {
   default: any
 }
 
+export interface LanguageOption {
+  code: string,
+  name: string,
+  englishName?: string
+}
+
 export const SETTINGS_KEYS = {
   BULK_EDIT_CONFIRMATION_DIALOGS: 'general-settings:bulk-edit:confirmation-dialogs',
   BULK_EDIT_APPLY_ON_CLOSE: 'general-settings:bulk-edit:apply-on-close',
   DOCUMENT_LIST_SIZE: 'general-settings:documentListSize',
   DARK_MODE_USE_SYSTEM: 'general-settings:dark-mode:use-system',
   DARK_MODE_ENABLED: 'general-settings:dark-mode:enabled',
-  USE_NATIVE_PDF_VIEWER: 'general-settings:document-details:native-pdf-viewer'
+  USE_NATIVE_PDF_VIEWER: 'general-settings:document-details:native-pdf-viewer',
+  DATE_LOCALE: 'general-settings:date-display:date-locale',
+  DATE_FORMAT: 'general-settings:date-display:date-format'
 }
 
 const SETTINGS: PaperlessSettings[] = [
@@ -24,7 +32,9 @@ const SETTINGS: PaperlessSettings[] = [
   {key: SETTINGS_KEYS.DOCUMENT_LIST_SIZE, type: "number", default: 50},
   {key: SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, type: "boolean", default: true},
   {key: SETTINGS_KEYS.DARK_MODE_ENABLED, type: "boolean", default: false},
-  {key: SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, type: "boolean", default: false}
+  {key: SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, type: "boolean", default: false},
+  {key: SETTINGS_KEYS.DATE_LOCALE, type: "string", default: "de"},
+  {key: SETTINGS_KEYS.DATE_FORMAT, type: "string", default: "mediumDate"}
 ]
 
 @Injectable({
@@ -59,13 +69,12 @@ export class SettingsService {
 
   }
 
-  getLanguageOptions() {
+  getLanguageOptions(): LanguageOption[] {
     return [
-      {code: "", name: $localize`Use system language`},
-      {code: "en-us", name: `${$localize`English`} (English)`},
-      {code: "de", name: `${$localize`German`} (German)`},
-      {code: "nl", name: `${$localize`Dutch`} (Dutch)`},
-      {code: "fr", name: `${$localize`French`} (French)`}
+      {code: "en-us", name: $localize`English`, englishName: "English"},
+      {code: "de", name: $localize`German`, englishName: "German"},
+      {code: "nl", name: $localize`Dutch`, englishName: "Dutch"},
+      {code: "fr", name: $localize`French`, englishName: "French"}
     ]
   }