mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Fix: doc detail component fixes (#5373)
This commit is contained in:
parent
53e04e66cf
commit
98c5cf89ef
@ -1931,7 +1931,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">701</context>
|
||||
<context context-type="linenumber">716</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
@ -1970,7 +1970,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">703</context>
|
||||
<context context-type="linenumber">718</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
@ -4214,31 +4214,31 @@
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6581372518205328477" datatype="html">
|
||||
<source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to <x id="PH_1" equiv-text="appTitle"/></source>
|
||||
<source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to <x id="PH_1" equiv-text="environment.appTitle"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
|
||||
<context context-type="linenumber">41</context>
|
||||
<context context-type="linenumber">38</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2901300640157872718" datatype="html">
|
||||
<source>Welcome to <x id="PH" equiv-text="appTitle"/></source>
|
||||
<source>Welcome to <x id="PH" equiv-text="environment.appTitle"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
|
||||
<context context-type="linenumber">43</context>
|
||||
<context context-type="linenumber">40</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1325877348738783391" datatype="html">
|
||||
<source>Dashboard updated</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
<context context-type="linenumber">71</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3214475953924351473" datatype="html">
|
||||
<source>Error updating dashboard</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
|
||||
<context context-type="linenumber">77</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2946624699882754313" datatype="html">
|
||||
@ -4752,7 +4752,7 @@
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7206723502037428235" datatype="html">
|
||||
<source>Notes <x id="START_BLOCK_IF" equiv-text="@if (document?.notes.length) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="badge text-bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="ngth}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="}</a>"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></source>
|
||||
<source>Notes <x id="START_BLOCK_IF" equiv-text="@if (document?.notes.length) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="badge text-bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="ngth}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||
<context context-type="linenumber">286,289</context>
|
||||
@ -4787,78 +4787,78 @@
|
||||
<source>An error occurred loading content: <x id="PH" equiv-text="err.message ?? err.toString()"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">276,278</context>
|
||||
<context context-type="linenumber">284,286</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3200733026060976258" datatype="html">
|
||||
<source>Document changes detected</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">298</context>
|
||||
<context context-type="linenumber">307</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2887155916749964" datatype="html">
|
||||
<source>The version of this document in your browser session appears older than the existing version.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">299</context>
|
||||
<context context-type="linenumber">308</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="237142428785956348" datatype="html">
|
||||
<source>Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">300</context>
|
||||
<context context-type="linenumber">309</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8720977247725652816" datatype="html">
|
||||
<source>Ok</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">301</context>
|
||||
<context context-type="linenumber">311</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5758784066858623886" datatype="html">
|
||||
<source>Error retrieving metadata</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">437</context>
|
||||
<context context-type="linenumber">448</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3456881259945295697" datatype="html">
|
||||
<source>Error retrieving suggestions.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">458</context>
|
||||
<context context-type="linenumber">473</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8348337312757497317" datatype="html">
|
||||
<source>Document saved successfully.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">576</context>
|
||||
<context context-type="linenumber">591</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">585</context>
|
||||
<context context-type="linenumber">600</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="448882439049417053" datatype="html">
|
||||
<source>Error saving document</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">589</context>
|
||||
<context context-type="linenumber">604</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">630</context>
|
||||
<context context-type="linenumber">645</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9021887951960049161" datatype="html">
|
||||
<source>Confirm delete</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">656</context>
|
||||
<context context-type="linenumber">671</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||
@ -4869,35 +4869,35 @@
|
||||
<source>Do you really want to delete document "<x id="PH" equiv-text="this.document.title"/>"?</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">657</context>
|
||||
<context context-type="linenumber">672</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6691075929777935948" datatype="html">
|
||||
<source>The files for this document will be deleted permanently. This operation cannot be undone.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">658</context>
|
||||
<context context-type="linenumber">673</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="719892092227206532" datatype="html">
|
||||
<source>Delete document</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">660</context>
|
||||
<context context-type="linenumber">675</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7295637485862454066" datatype="html">
|
||||
<source>Error deleting document</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">679</context>
|
||||
<context context-type="linenumber">694</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7362691899087997122" datatype="html">
|
||||
<source>Redo OCR confirm</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">699</context>
|
||||
<context context-type="linenumber">714</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
@ -4908,28 +4908,28 @@
|
||||
<source>This operation will permanently redo OCR for this document.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">700</context>
|
||||
<context context-type="linenumber">715</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5729001209753056399" datatype="html">
|
||||
<source>Redo OCR operation will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">711</context>
|
||||
<context context-type="linenumber">726</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4409560272830824468" datatype="html">
|
||||
<source>Error executing operation</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">722</context>
|
||||
<context context-type="linenumber">737</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4458954481601077369" datatype="html">
|
||||
<source>Page Fit</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">791</context>
|
||||
<context context-type="linenumber">806</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6857598786757174736" datatype="html">
|
||||
@ -7095,21 +7095,21 @@
|
||||
<source>Successfully completed one-time migratration of settings to the database!</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">468</context>
|
||||
<context context-type="linenumber">471</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5558341108007064934" datatype="html">
|
||||
<source>Unable to migrate settings to the database, please try saving manually.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">469</context>
|
||||
<context context-type="linenumber">472</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1168781785897678748" datatype="html">
|
||||
<source>You can restart the tour from the settings page.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||
<context context-type="linenumber">539</context>
|
||||
<context context-type="linenumber">542</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3852289441366561594" datatype="html">
|
||||
|
@ -465,33 +465,42 @@ export class PdfViewerComponent
|
||||
|
||||
this.clear()
|
||||
|
||||
this.setupViewer()
|
||||
|
||||
this.loadingTask = PDFJS.getDocument(this.getDocumentParams())
|
||||
|
||||
this.loadingTask!.onProgress = (progressData: PDFProgressData) => {
|
||||
this.onProgress.emit(progressData)
|
||||
if (this.pdfViewer) {
|
||||
this.pdfViewer._resetView()
|
||||
this.pdfViewer = null
|
||||
}
|
||||
|
||||
const src = this.src
|
||||
this.setupViewer()
|
||||
|
||||
from(this.loadingTask!.promise as Promise<PDFDocumentProxy>)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (pdf) => {
|
||||
this._pdf = pdf
|
||||
this.lastLoaded = src
|
||||
try {
|
||||
this.loadingTask = PDFJS.getDocument(this.getDocumentParams())
|
||||
|
||||
this.afterLoadComplete.emit(pdf)
|
||||
this.resetPdfDocument()
|
||||
this.loadingTask!.onProgress = (progressData: PDFProgressData) => {
|
||||
this.onProgress.emit(progressData)
|
||||
}
|
||||
|
||||
this.update()
|
||||
},
|
||||
error: (error) => {
|
||||
this.lastLoaded = null
|
||||
this.onError.emit(error)
|
||||
},
|
||||
})
|
||||
const src = this.src
|
||||
|
||||
from(this.loadingTask!.promise as Promise<PDFDocumentProxy>)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (pdf) => {
|
||||
this._pdf = pdf
|
||||
this.lastLoaded = src
|
||||
|
||||
this.afterLoadComplete.emit(pdf)
|
||||
this.resetPdfDocument()
|
||||
|
||||
this.update()
|
||||
},
|
||||
error: (error) => {
|
||||
this.lastLoaded = null
|
||||
this.onError.emit(error)
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
this.onError.emit(e)
|
||||
}
|
||||
}
|
||||
|
||||
private update() {
|
||||
|
@ -21,361 +21,361 @@
|
||||
<button type="button" class="btn btn-sm btn-outline-danger me-4" (click)="delete()" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg><span class="d-none d-lg-inline ps-1" i18n>Delete</span>
|
||||
</button>
|
||||
</svg><span class="d-none d-lg-inline ps-1" i18n>Delete</span>
|
||||
</button>
|
||||
|
||||
<div class="btn-group me-2">
|
||||
<a [href]="downloadUrl" class="btn btn-sm btn-outline-primary">
|
||||
<svg class="buttonicon me-md-1" fill="currentColor">
|
||||
<div class="btn-group me-2">
|
||||
<a [href]="downloadUrl" class="btn btn-sm btn-outline-primary">
|
||||
<svg class="buttonicon me-md-1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#download" />
|
||||
</svg><span class="d-none d-lg-inline ps-1" i18n>Download</span>
|
||||
</a>
|
||||
</svg><span class="d-none d-lg-inline ps-1" i18n>Download</span>
|
||||
</a>
|
||||
|
||||
@if (metadata?.has_archive_version) {
|
||||
<div class="btn-group" ngbDropdown role="group">
|
||||
<button class="btn btn-sm btn-outline-primary dropdown-toggle" ngbDropdownToggle></button>
|
||||
<div class="dropdown-menu shadow" ngbDropdownMenu>
|
||||
<a ngbDropdownItem [href]="downloadOriginalUrl" i18n>Download original</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (metadata?.has_archive_version) {
|
||||
<div class="btn-group" ngbDropdown role="group">
|
||||
<button class="btn btn-sm btn-outline-primary dropdown-toggle" ngbDropdownToggle></button>
|
||||
<div class="dropdown-menu shadow" ngbDropdownMenu>
|
||||
<a ngbDropdownItem [href]="downloadOriginalUrl" i18n>Download original</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="ms-auto" ngbDropdown>
|
||||
<button class="btn btn-sm btn-outline-primary me-2" id="actionsDropdown" ngbDropdownToggle>
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#three-dots" />
|
||||
</svg>
|
||||
<div class="d-none d-sm-inline"> <ng-container i18n>Actions</ng-container></div>
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="actionsDropdown" class="shadow">
|
||||
<button ngbDropdownItem (click)="redoOcr()" [disabled]="!userCanEdit">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" />
|
||||
</svg><span class="ps-1" i18n>Redo OCR</span>
|
||||
</button>
|
||||
<div class="ms-auto" ngbDropdown>
|
||||
<button class="btn btn-sm btn-outline-primary me-2" id="actionsDropdown" ngbDropdownToggle>
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#three-dots" />
|
||||
</svg>
|
||||
<div class="d-none d-sm-inline"> <ng-container i18n>Actions</ng-container></div>
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="actionsDropdown" class="shadow">
|
||||
<button ngbDropdownItem (click)="redoOcr()" [disabled]="!userCanEdit">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" />
|
||||
</svg><span class="ps-1" i18n>Redo OCR</span>
|
||||
</button>
|
||||
|
||||
<button ngbDropdownItem (click)="moreLike()">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#diagram-3" />
|
||||
</svg><span class="ps-1" i18n>More like this</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button ngbDropdownItem (click)="moreLike()">
|
||||
<svg class="buttonicon-sm" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#diagram-3" />
|
||||
</svg><span class="ps-1" i18n>More like this</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pngx-custom-fields-dropdown
|
||||
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }"
|
||||
class="me-2"
|
||||
[documentId]="documentId"
|
||||
[disabled]="!userIsOwner"
|
||||
[existingFields]="document?.custom_fields"
|
||||
(created)="refreshCustomFields()"
|
||||
(added)="addField($event)">
|
||||
</pngx-custom-fields-dropdown>
|
||||
<pngx-custom-fields-dropdown
|
||||
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }"
|
||||
class="me-2"
|
||||
[documentId]="documentId"
|
||||
[disabled]="!userIsOwner"
|
||||
[existingFields]="document?.custom_fields"
|
||||
(created)="refreshCustomFields()"
|
||||
(added)="addField($event)">
|
||||
</pngx-custom-fields-dropdown>
|
||||
|
||||
<pngx-share-links-dropdown [documentId]="documentId" [hasArchiveVersion]="!!document?.archived_file_name" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ShareLink }"></pngx-share-links-dropdown>
|
||||
</pngx-page-header>
|
||||
<pngx-share-links-dropdown [documentId]="documentId" [hasArchiveVersion]="!!document?.archived_file_name" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ShareLink }"></pngx-share-links-dropdown>
|
||||
</pngx-page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-xl-4 mb-4">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-xl-4 mb-4">
|
||||
|
||||
<form [formGroup]='documentForm' (ngSubmit)="save()">
|
||||
|
||||
<div class="btn-toolbar mb-1 pb-3 border-bottom">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Close" (click)="close()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Previous" (click)="previousDoc()" [disabled]="!hasPrevious()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-left" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Next" (click)="nextDoc()" [disabled]="!hasNext()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-right" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ng-container *ngTemplateOutlet="saveButtons"></ng-container>
|
||||
</div>
|
||||
|
||||
<ul ngbNav #nav="ngbNav" class="nav-underline flex-nowrap flex-md-wrap overflow-auto" (navChange)="onNavChange($event)" [(activeId)]="activeNavID">
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Details">
|
||||
<a ngbNavLink i18n>Details</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div>
|
||||
<pngx-input-text #inputTitle i18n-title title="Title" formControlName="title" [horizontal]="true" (keyup)="titleKeyUp($event)" [error]="error?.title"></pngx-input-text>
|
||||
<pngx-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" [horizontal]="true" formControlName='archive_serial_number'></pngx-input-number>
|
||||
<pngx-input-date i18n-title title="Date created" formControlName="created_date" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
[error]="error?.created_date"></pngx-input-date>
|
||||
<pngx-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
(createNew)="createCorrespondent($event)" [suggestions]="suggestions?.correspondents" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }"></pngx-input-select>
|
||||
<pngx-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
(createNew)="createDocumentType($event)" [suggestions]="suggestions?.document_types" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }"></pngx-input-select>
|
||||
<pngx-input-select [items]="storagePaths" i18n-title title="Storage path" formControlName="storage_path" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
(createNew)="createStoragePath($event)" [suggestions]="suggestions?.storage_paths" i18n-placeholder placeholder="Default" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }"></pngx-input-select>
|
||||
<pngx-input-tags formControlName="tags" [suggestions]="suggestions?.tags" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }"></pngx-input-tags>
|
||||
@for (fieldInstance of document?.custom_fields; track fieldInstance; let i = $index) {
|
||||
<div [formGroup]="customFieldFormFields.controls[i]">
|
||||
@switch (getCustomFieldFromInstance(fieldInstance)?.data_type) {
|
||||
@case (PaperlessCustomFieldDataType.String) {
|
||||
<pngx-input-text formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-text>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Date) {
|
||||
<pngx-input-date formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-date>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Integer) {
|
||||
<pngx-input-number formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[showAdd]="false"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-number>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Float) {
|
||||
<pngx-input-number formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[showAdd]="false"
|
||||
[step]=".1"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-number>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Monetary) {
|
||||
<pngx-input-number formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[showAdd]="false"
|
||||
[step]=".01"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-number>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Boolean) {
|
||||
<pngx-input-check formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"></pngx-input-check>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Url) {
|
||||
<pngx-input-url formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-url>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.DocumentLink) {
|
||||
<pngx-input-document-link formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[parentDocumentID]="documentId"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-document-link>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="d-flex border-top pt-3">
|
||||
<ng-container *ngTemplateOutlet="saveButtons"></ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Content">
|
||||
<a ngbNavLink i18n>Content</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div>
|
||||
<textarea class="form-control" id="content" rows="20" formControlName='content' [class.rtl]="isRTL"></textarea>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Metadata">
|
||||
<a ngbNavLink i18n>Metadata</a>
|
||||
<ng-template ngbNavContent>
|
||||
|
||||
@if (document) {
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td i18n>Date modified</td>
|
||||
<td>{{document.modified | customDate}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Date added</td>
|
||||
<td>{{document.added | customDate}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Media filename</td>
|
||||
<td>{{metadata?.media_filename}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original filename</td>
|
||||
<td>{{metadata?.original_filename}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original MD5 checksum</td>
|
||||
<td>{{metadata?.original_checksum}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original file size</td>
|
||||
<td>{{metadata?.original_size | fileSize}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original mime type</td>
|
||||
<td>{{metadata?.original_mime_type}}</td>
|
||||
</tr>
|
||||
@if (metadata?.has_archive_version) {
|
||||
<tr>
|
||||
<td i18n>Archive MD5 checksum</td>
|
||||
<td>{{metadata?.archive_checksum}}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (metadata?.has_archive_version) {
|
||||
<tr>
|
||||
<td i18n>Archive file size</td>
|
||||
<td>{{metadata?.archive_size | fileSize}}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@if (metadata?.original_metadata?.length > 0) {
|
||||
<pngx-metadata-collapse i18n-title title="Original document metadata" [metadata]="metadata.original_metadata"></pngx-metadata-collapse>
|
||||
}
|
||||
@if (metadata?.archive_metadata?.length > 0) {
|
||||
<pngx-metadata-collapse i18n-title title="Archived document metadata" [metadata]="metadata.archive_metadata"></pngx-metadata-collapse>
|
||||
}
|
||||
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Preview" class="d-md-none">
|
||||
<a ngbNavLink i18n>Preview</a>
|
||||
@if (!pdfPreview.offsetParent) {
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container *ngTemplateOutlet="previewContent"></ng-container>
|
||||
</ng-template>
|
||||
}
|
||||
</li>
|
||||
|
||||
@if (notesEnabled) {
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Notes">
|
||||
<a class="text-nowrap" ngbNavLink i18n>Notes @if (document?.notes.length) {
|
||||
<span class="badge text-bg-secondary ms-1">{{document.notes.length}}</span>
|
||||
}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<pngx-document-notes [documentId]="documentId" [notes]="document?.notes" [addDisabled]="!userCanEdit" (updated)="notesUpdated($event)"></pngx-document-notes>
|
||||
</ng-template>
|
||||
</li>
|
||||
}
|
||||
|
||||
@if (showPermissions) {
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Permissions">
|
||||
<a ngbNavLink i18n>Permissions</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="mb-3">
|
||||
<pngx-permissions-form [users]="users" formControlName="permissions_form"></pngx-permissions-form>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
<div [ngbNavOutlet]="nav" class="mt-3"></div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-xl-8 mb-3 d-none d-md-block position-relative" #pdfPreview>
|
||||
<ng-container *ngTemplateOutlet="previewContent"></ng-container>
|
||||
@if (renderAsPlainText) {
|
||||
<div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div>
|
||||
}
|
||||
@if (requiresPassword) {
|
||||
<div class="password-prompt">
|
||||
<form>
|
||||
<input autocomplete="" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<form [formGroup]='documentForm' (ngSubmit)="save()">
|
||||
|
||||
<div class="btn-toolbar mb-1 pb-3 border-bottom">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Close" (click)="close()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Previous" (click)="previousDoc()" [disabled]="!hasPrevious()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-left" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Next" (click)="nextDoc()" [disabled]="!hasNext()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-right" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ng-template #saveButtons>
|
||||
<div class="btn-group ms-auto">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||
<button type="submit" class="order-3 btn btn-sm btn-primary" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save</button>
|
||||
@if (hasNext()) {
|
||||
<button type="button" class="order-1 btn btn-sm btn-outline-primary" (click)="saveEditNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & next</button>
|
||||
}
|
||||
@if (!hasNext()) {
|
||||
<button type="button" class="order-2 btn btn-sm btn-outline-primary" (click)="save(true)" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & close</button>
|
||||
}
|
||||
</ng-container>
|
||||
<button type="button" class="order-0 btn btn-sm btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Discard</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-container *ngTemplateOutlet="saveButtons"></ng-container>
|
||||
</div>
|
||||
|
||||
<ng-template #previewContent>
|
||||
@if (!metadata) {
|
||||
<div class="w-100 h-100 d-flex align-items-center justify-content-center">
|
||||
<div>
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</div>
|
||||
<ul ngbNav #nav="ngbNav" class="nav-underline flex-nowrap flex-md-wrap overflow-auto" (navChange)="onNavChange($event)" [(activeId)]="activeNavID">
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Details">
|
||||
<a ngbNavLink i18n>Details</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div>
|
||||
<pngx-input-text #inputTitle i18n-title title="Title" formControlName="title" [horizontal]="true" (keyup)="titleKeyUp($event)" [error]="error?.title"></pngx-input-text>
|
||||
<pngx-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" [horizontal]="true" formControlName='archive_serial_number'></pngx-input-number>
|
||||
<pngx-input-date i18n-title title="Date created" formControlName="created_date" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
[error]="error?.created_date"></pngx-input-date>
|
||||
<pngx-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
(createNew)="createCorrespondent($event)" [suggestions]="suggestions?.correspondents" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }"></pngx-input-select>
|
||||
<pngx-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
(createNew)="createDocumentType($event)" [suggestions]="suggestions?.document_types" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }"></pngx-input-select>
|
||||
<pngx-input-select [items]="storagePaths" i18n-title title="Storage path" formControlName="storage_path" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||
(createNew)="createStoragePath($event)" [suggestions]="suggestions?.storage_paths" i18n-placeholder placeholder="Default" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }"></pngx-input-select>
|
||||
<pngx-input-tags formControlName="tags" [suggestions]="suggestions?.tags" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }"></pngx-input-tags>
|
||||
@for (fieldInstance of document?.custom_fields; track fieldInstance; let i = $index) {
|
||||
<div [formGroup]="customFieldFormFields.controls[i]">
|
||||
@switch (getCustomFieldFromInstance(fieldInstance)?.data_type) {
|
||||
@case (PaperlessCustomFieldDataType.String) {
|
||||
<pngx-input-text formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-text>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Date) {
|
||||
<pngx-input-date formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-date>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Integer) {
|
||||
<pngx-input-number formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[showAdd]="false"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-number>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Float) {
|
||||
<pngx-input-number formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[showAdd]="false"
|
||||
[step]=".1"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-number>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Monetary) {
|
||||
<pngx-input-number formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[showAdd]="false"
|
||||
[step]=".01"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-number>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Boolean) {
|
||||
<pngx-input-check formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"></pngx-input-check>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.Url) {
|
||||
<pngx-input-url formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-url>
|
||||
}
|
||||
@case (PaperlessCustomFieldDataType.DocumentLink) {
|
||||
<pngx-input-document-link formControlName="value"
|
||||
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
|
||||
[parentDocumentID]="documentId"
|
||||
[removable]="userIsOwner"
|
||||
(removed)="removeField(fieldInstance)"
|
||||
[horizontal]="true"
|
||||
[error]="getCustomFieldError(i)"></pngx-input-document-link>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (getContentType() === 'application/pdf') {
|
||||
@if (!useNativePdfViewer ) {
|
||||
<div class="preview-sticky pdf-viewer-container">
|
||||
<pngx-pdf-viewer
|
||||
[src]="{ url: previewUrl, password: password }"
|
||||
[original-size]="false"
|
||||
[show-borders]="true"
|
||||
[show-all]="true"
|
||||
[(page)]="previewCurrentPage"
|
||||
[zoom-scale]="previewZoomScale"
|
||||
[zoom]="previewZoomSetting"
|
||||
(error)="onError($event)"
|
||||
(after-load-complete)="pdfPreviewLoaded($event)">
|
||||
</pngx-pdf-viewer>
|
||||
</div>
|
||||
} @else {
|
||||
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
|
||||
|
||||
<div class="d-flex border-top pt-3">
|
||||
<ng-container *ngTemplateOutlet="saveButtons"></ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Content">
|
||||
<a ngbNavLink i18n>Content</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div>
|
||||
<textarea class="form-control" id="content" rows="20" formControlName='content' [class.rtl]="isRTL"></textarea>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Metadata">
|
||||
<a ngbNavLink i18n>Metadata</a>
|
||||
<ng-template ngbNavContent>
|
||||
|
||||
@if (document) {
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td i18n>Date modified</td>
|
||||
<td>{{document.modified | customDate}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Date added</td>
|
||||
<td>{{document.added | customDate}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Media filename</td>
|
||||
<td>{{metadata?.media_filename}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original filename</td>
|
||||
<td>{{metadata?.original_filename}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original MD5 checksum</td>
|
||||
<td>{{metadata?.original_checksum}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original file size</td>
|
||||
<td>{{metadata?.original_size | fileSize}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n>Original mime type</td>
|
||||
<td>{{metadata?.original_mime_type}}</td>
|
||||
</tr>
|
||||
@if (metadata?.has_archive_version) {
|
||||
<tr>
|
||||
<td i18n>Archive MD5 checksum</td>
|
||||
<td>{{metadata?.archive_checksum}}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (metadata?.has_archive_version) {
|
||||
<tr>
|
||||
<td i18n>Archive file size</td>
|
||||
<td>{{metadata?.archive_size | fileSize}}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@if (metadata?.original_metadata?.length > 0) {
|
||||
<pngx-metadata-collapse i18n-title title="Original document metadata" [metadata]="metadata.original_metadata"></pngx-metadata-collapse>
|
||||
}
|
||||
@if (metadata?.archive_metadata?.length > 0) {
|
||||
<pngx-metadata-collapse i18n-title title="Archived document metadata" [metadata]="metadata.archive_metadata"></pngx-metadata-collapse>
|
||||
}
|
||||
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Preview" class="d-md-none">
|
||||
<a ngbNavLink i18n>Preview</a>
|
||||
@if (!pdfPreview.offsetParent) {
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container *ngTemplateOutlet="previewContent"></ng-container>
|
||||
</ng-template>
|
||||
}
|
||||
@if (renderAsPlainText) {
|
||||
<div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div>
|
||||
}
|
||||
@if (showPasswordField) {
|
||||
<div class="password-prompt">
|
||||
<form>
|
||||
<input autocomplete="" autofocus="true" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
@if (notesEnabled) {
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Notes">
|
||||
<a class="text-nowrap" ngbNavLink i18n>Notes @if (document?.notes.length) {
|
||||
<span class="badge text-bg-secondary ms-1">{{document.notes.length}}</span>
|
||||
}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<pngx-document-notes [documentId]="documentId" [notes]="document?.notes" [addDisabled]="!userCanEdit" (updated)="notesUpdated($event)"></pngx-document-notes>
|
||||
</ng-template>
|
||||
</li>
|
||||
}
|
||||
|
||||
@if (showPermissions) {
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Permissions">
|
||||
<a ngbNavLink i18n>Permissions</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="mb-3">
|
||||
<pngx-permissions-form [users]="users" formControlName="permissions_form"></pngx-permissions-form>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
<div [ngbNavOutlet]="nav" class="mt-3"></div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-xl-8 mb-3 d-none d-md-block position-relative" #pdfPreview>
|
||||
<ng-container *ngTemplateOutlet="previewContent"></ng-container>
|
||||
@if (renderAsPlainText) {
|
||||
<div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div>
|
||||
}
|
||||
@if (requiresPassword) {
|
||||
<div class="password-prompt">
|
||||
<form>
|
||||
<input autocomplete="" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<ng-template #saveButtons>
|
||||
<div class="btn-group ms-auto">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||
<button type="submit" class="order-3 btn btn-sm btn-primary" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save</button>
|
||||
@if (hasNext()) {
|
||||
<button type="button" class="order-1 btn btn-sm btn-outline-primary" (click)="saveEditNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & next</button>
|
||||
}
|
||||
@if (!hasNext()) {
|
||||
<button type="button" class="order-2 btn btn-sm btn-outline-primary" (click)="save(true)" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & close</button>
|
||||
}
|
||||
</ng-container>
|
||||
<button type="button" class="order-0 btn btn-sm btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Discard</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #previewContent>
|
||||
@if (!metadata) {
|
||||
<div class="w-100 h-100 d-flex align-items-center justify-content-center">
|
||||
<div>
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (getContentType() === 'application/pdf') {
|
||||
@if (!useNativePdfViewer ) {
|
||||
<div class="preview-sticky pdf-viewer-container">
|
||||
<pngx-pdf-viewer
|
||||
[src]="{ url: previewUrl, password: password }"
|
||||
[original-size]="false"
|
||||
[show-borders]="true"
|
||||
[show-all]="true"
|
||||
[(page)]="previewCurrentPage"
|
||||
[zoom-scale]="previewZoomScale"
|
||||
[zoom]="previewZoomSetting"
|
||||
(error)="onError($event)"
|
||||
(after-load-complete)="pdfPreviewLoaded($event)">
|
||||
</pngx-pdf-viewer>
|
||||
</div>
|
||||
} @else {
|
||||
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
|
||||
}
|
||||
}
|
||||
@if (renderAsPlainText) {
|
||||
<div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div>
|
||||
}
|
||||
@if (showPasswordField) {
|
||||
<div class="password-prompt">
|
||||
<form>
|
||||
<input autocomplete="" autofocus="true" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
|
@ -255,9 +255,6 @@ describe('DocumentDetailComponent', () => {
|
||||
|
||||
router = TestBed.inject(Router)
|
||||
activatedRoute = TestBed.inject(ActivatedRoute)
|
||||
jest
|
||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap({ id: 3 })))
|
||||
openDocumentsService = TestBed.inject(OpenDocumentsService)
|
||||
documentService = TestBed.inject(DocumentService)
|
||||
modalService = TestBed.inject(NgbModal)
|
||||
@ -295,6 +292,17 @@ describe('DocumentDetailComponent', () => {
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['documents', 3, 'notes'])
|
||||
})
|
||||
|
||||
it('should forward id without section to details', () => {
|
||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||
jest
|
||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap({ id: 3 })))
|
||||
fixture.detectChanges()
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['documents', 3, 'details'], {
|
||||
replaceUrl: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('should update title after debounce', fakeAsync(() => {
|
||||
initNormally()
|
||||
component.titleInput.value = 'Foo Bar'
|
||||
@ -320,6 +328,7 @@ describe('DocumentDetailComponent', () => {
|
||||
})
|
||||
|
||||
it('should load already-opened document via param', () => {
|
||||
initNormally()
|
||||
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(doc))
|
||||
jest.spyOn(openDocumentsService, 'getOpenDocument').mockReturnValue(doc)
|
||||
jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
|
||||
@ -400,8 +409,11 @@ describe('DocumentDetailComponent', () => {
|
||||
})
|
||||
|
||||
it('should 404 on invalid id', () => {
|
||||
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(null))
|
||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||
jest
|
||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap({ id: 999, section: 'details' })))
|
||||
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(null))
|
||||
fixture.detectChanges()
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['404'], { replaceUrl: true })
|
||||
})
|
||||
@ -936,11 +948,33 @@ describe('DocumentDetailComponent', () => {
|
||||
expect(refreshSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should get suggestions', () => {
|
||||
const suggestionsSpy = jest.spyOn(documentService, 'getSuggestions')
|
||||
suggestionsSpy.mockReturnValue(of({ tags: [1, 2] }))
|
||||
initNormally()
|
||||
expect(suggestionsSpy).toHaveBeenCalled()
|
||||
expect(component.suggestions).toEqual({ tags: [1, 2] })
|
||||
})
|
||||
|
||||
it('should show error if needed for get suggestions', () => {
|
||||
const suggestionsSpy = jest.spyOn(documentService, 'getSuggestions')
|
||||
const errorSpy = jest.spyOn(toastService, 'showError')
|
||||
suggestionsSpy.mockImplementationOnce(() =>
|
||||
throwError(() => new Error('failed to get suggestions'))
|
||||
)
|
||||
initNormally()
|
||||
expect(suggestionsSpy).toHaveBeenCalled()
|
||||
expect(errorSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should warn when open document does not match doc retrieved from backend on init', () => {
|
||||
const modalSpy = jest.spyOn(modalService, 'open')
|
||||
const openDoc = Object.assign({}, doc)
|
||||
// simulate a document being modified elsewhere and db updated
|
||||
doc.modified = new Date()
|
||||
jest
|
||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap({ id: 3, section: 'details' })))
|
||||
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(doc))
|
||||
jest.spyOn(openDocumentsService, 'getOpenDocument').mockReturnValue(openDoc)
|
||||
jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
|
||||
@ -951,12 +985,13 @@ describe('DocumentDetailComponent', () => {
|
||||
})
|
||||
)
|
||||
fixture.detectChanges() // calls ngOnInit
|
||||
expect(modalSpy).toHaveBeenCalledWith(ConfirmDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
expect(modalSpy).toHaveBeenCalledWith(ConfirmDialogComponent)
|
||||
})
|
||||
|
||||
function initNormally() {
|
||||
jest
|
||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap({ id: 3, section: 'details' })))
|
||||
jest
|
||||
.spyOn(documentService, 'get')
|
||||
.mockReturnValueOnce(of(Object.assign({}, doc)))
|
||||
|
@ -33,6 +33,7 @@ import {
|
||||
map,
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
} from 'rxjs/operators'
|
||||
import { DocumentSuggestions } from 'src/app/data/document-suggestions'
|
||||
import {
|
||||
@ -257,6 +258,13 @@ export class DocumentDetailComponent
|
||||
|
||||
this.route.paramMap
|
||||
.pipe(
|
||||
filter((paramMap) => {
|
||||
// only init when changing docs & section is set
|
||||
return (
|
||||
+paramMap.get('id') !== this.documentId &&
|
||||
paramMap.get('section')?.length > 0
|
||||
)
|
||||
}),
|
||||
takeUntil(this.unsubscribeNotifier),
|
||||
switchMap((paramMap) => {
|
||||
const documentId = +paramMap.get('id')
|
||||
@ -295,15 +303,12 @@ export class DocumentDetailComponent
|
||||
new Date(doc.modified) > new Date(openDocument.modified) &&
|
||||
!this.modalService.hasOpenModals()
|
||||
) {
|
||||
let modal = this.modalService.open(ConfirmDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
let modal = this.modalService.open(ConfirmDialogComponent)
|
||||
modal.componentInstance.title = $localize`Document changes detected`
|
||||
modal.componentInstance.messageBold = $localize`The version of this document in your browser session appears older than the existing version.`
|
||||
modal.componentInstance.message = $localize`Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.`
|
||||
modal.componentInstance.cancelBtnCaption = $localize`Ok`
|
||||
modal.componentInstance.cancelBtnClass = 'btn-primary'
|
||||
modal.componentInstance.btnClass = 'visually-hidden'
|
||||
modal.componentInstance.cancelBtnClass = 'visually-hidden'
|
||||
modal.componentInstance.btnCaption = $localize`Ok`
|
||||
}
|
||||
|
||||
if (this.documentForm.dirty) {
|
||||
@ -425,11 +430,14 @@ export class DocumentDetailComponent
|
||||
updateComponent(doc: Document) {
|
||||
this.document = doc
|
||||
this.requiresPassword = false
|
||||
// this.customFields = doc.custom_fields.concat([])
|
||||
this.updateFormForCustomFields()
|
||||
this.documentsService
|
||||
.getMetadata(doc.id)
|
||||
.pipe(first())
|
||||
.pipe(
|
||||
first(),
|
||||
takeUntil(this.unsubscribeNotifier),
|
||||
takeUntil(this.docChangeNotifier)
|
||||
)
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.metadata = result
|
||||
@ -450,7 +458,11 @@ export class DocumentDetailComponent
|
||||
) {
|
||||
this.documentsService
|
||||
.getSuggestions(doc.id)
|
||||
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||
.pipe(
|
||||
first(),
|
||||
takeUntil(this.unsubscribeNotifier),
|
||||
takeUntil(this.docChangeNotifier)
|
||||
)
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.suggestions = result
|
||||
|
Loading…
x
Reference in New Issue
Block a user