2025-01-01 22:26:53 -08:00
import { AsyncPipe , NgTemplateOutlet } from '@angular/common'
2025-04-09 09:03:38 -07:00
import { HttpClient , HttpResponse } from '@angular/common/http'
2025-06-27 14:06:40 -07:00
import { Component , inject , OnDestroy , OnInit , ViewChild } from '@angular/core'
2025-01-01 22:26:53 -08:00
import {
FormArray ,
FormControl ,
FormGroup ,
FormsModule ,
ReactiveFormsModule ,
} from '@angular/forms'
2022-03-11 10:53:32 -08:00
import { ActivatedRoute , Router } from '@angular/router'
2023-05-27 23:16:33 -07:00
import {
NgbDateStruct ,
2025-01-01 22:26:53 -08:00
NgbDropdownModule ,
2023-05-27 23:16:33 -07:00
NgbModal ,
NgbNav ,
NgbNavChangeEvent ,
2025-01-01 22:26:53 -08:00
NgbNavModule ,
2023-05-27 23:16:33 -07:00
} from '@ng-bootstrap/ng-bootstrap'
2022-03-11 10:53:32 -08:00
import { dirtyCheck , DirtyComponent } from '@ngneat/dirty-check-forms'
2025-01-01 22:26:53 -08:00
import { PDFDocumentProxy , PdfViewerModule } from 'ng2-pdf-viewer'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
2025-01-30 08:31:52 -08:00
import { DeviceDetectorService } from 'ngx-device-detector'
2024-12-13 00:27:30 -08:00
import { BehaviorSubject , Observable , Subject } from 'rxjs'
2022-03-11 10:53:32 -08:00
import {
debounceTime ,
distinctUntilChanged ,
2024-01-13 13:40:22 -08:00
filter ,
2024-12-13 00:27:30 -08:00
first ,
map ,
switchMap ,
takeUntil ,
2022-03-11 10:53:32 -08:00
} from 'rxjs/operators'
2024-12-13 00:27:30 -08:00
import { Correspondent } from 'src/app/data/correspondent'
import { CustomField , CustomFieldDataType } from 'src/app/data/custom-field'
import { CustomFieldInstance } from 'src/app/data/custom-field-instance'
import { DataType } from 'src/app/data/datatype'
import { Document } from 'src/app/data/document'
import { DocumentMetadata } from 'src/app/data/document-metadata'
import { DocumentNote } from 'src/app/data/document-note'
2023-12-19 22:36:35 -08:00
import { DocumentSuggestions } from 'src/app/data/document-suggestions'
2024-12-13 00:27:30 -08:00
import { DocumentType } from 'src/app/data/document-type'
import { FilterRule } from 'src/app/data/filter-rule'
2023-05-27 23:16:33 -07:00
import {
FILTER_CORRESPONDENT ,
FILTER_CREATED_AFTER ,
FILTER_CREATED_BEFORE ,
FILTER_DOCUMENT_TYPE ,
FILTER_FULLTEXT_MORELIKE ,
FILTER_HAS_TAGS_ALL ,
FILTER_STORAGE_PATH ,
} from 'src/app/data/filter-rule-type'
2024-12-13 00:27:30 -08:00
import { ObjectWithId } from 'src/app/data/object-with-id'
2023-12-19 22:36:35 -08:00
import { StoragePath } from 'src/app/data/storage-path'
2024-12-13 00:27:30 -08:00
import { Tag } from 'src/app/data/tag'
2023-12-19 22:36:35 -08:00
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
2024-12-13 00:27:30 -08:00
import { User } from 'src/app/data/user'
2025-01-01 22:26:53 -08:00
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
2024-12-13 00:27:30 -08:00
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
2025-01-01 22:26:53 -08:00
import { FileSizePipe } from 'src/app/pipes/file-size.pipe'
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
2025-01-28 22:26:30 -08:00
import { ComponentRouterService } from 'src/app/services/component-router.service'
2024-12-13 00:27:30 -08:00
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { HotKeyService } from 'src/app/services/hot-key.service'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
2022-11-12 04:03:35 -08:00
import {
PermissionAction ,
PermissionsService ,
PermissionType ,
} from 'src/app/services/permissions.service'
2024-12-13 00:27:30 -08:00
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { DocumentService } from 'src/app/services/rest/document.service'
2025-07-23 22:07:13 -07:00
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
2024-12-13 00:27:30 -08:00
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
2025-04-19 21:57:06 -07:00
import { TagService } from 'src/app/services/rest/tag.service'
2022-12-09 10:04:39 -08:00
import { UserService } from 'src/app/services/rest/user.service'
2024-12-13 00:27:30 -08:00
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
2025-04-19 15:10:34 -07:00
import { getFilenameFromContentDisposition } from 'src/app/utils/http'
2023-05-27 23:16:33 -07:00
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
2024-12-01 11:46:19 -08:00
import * as UTIF from 'utif'
2024-12-13 00:27:30 -08:00
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
import { DeletePagesConfirmDialogComponent } from '../common/confirm-dialog/delete-pages-confirm-dialog/delete-pages-confirm-dialog.component'
import { RotateConfirmDialogComponent } from '../common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component'
import { SplitConfirmDialogComponent } from '../common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component'
2025-01-01 22:26:53 -08:00
import { CustomFieldsDropdownComponent } from '../common/custom-fields-dropdown/custom-fields-dropdown.component'
2024-12-13 00:27:30 -08:00
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 { EditDialogMode } from '../common/edit-dialog/edit-dialog.component'
import { StoragePathEditDialogComponent } from '../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
2025-04-22 00:21:26 -07:00
import { TagEditDialogComponent } from '../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
2025-02-21 08:44:03 -08:00
import { EmailDocumentDialogComponent } from '../common/email-document-dialog/email-document-dialog.component'
2025-01-01 22:26:53 -08:00
import { CheckComponent } from '../common/input/check/check.component'
import { DateComponent } from '../common/input/date/date.component'
import { DocumentLinkComponent } from '../common/input/document-link/document-link.component'
import { MonetaryComponent } from '../common/input/monetary/monetary.component'
import { NumberComponent } from '../common/input/number/number.component'
import { PermissionsFormComponent } from '../common/input/permissions/permissions-form/permissions-form.component'
import { SelectComponent } from '../common/input/select/select.component'
import { TagsComponent } from '../common/input/tags/tags.component'
2024-12-13 00:27:30 -08:00
import { TextComponent } from '../common/input/text/text.component'
2025-01-01 22:26:53 -08:00
import { UrlComponent } from '../common/input/url/url.component'
import { PageHeaderComponent } from '../common/page-header/page-header.component'
2025-02-21 08:44:03 -08:00
import { ShareLinksDialogComponent } from '../common/share-links-dialog/share-links-dialog.component'
2025-04-21 01:04:34 -07:00
import { SuggestionsDropdownComponent } from '../common/suggestions-dropdown/suggestions-dropdown.component'
2025-01-01 22:26:53 -08:00
import { DocumentHistoryComponent } from '../document-history/document-history.component'
import { DocumentNotesComponent } from '../document-notes/document-notes.component'
2024-12-13 00:27:30 -08:00
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
2025-01-01 22:26:53 -08:00
import { MetadataCollapseComponent } from './metadata-collapse/metadata-collapse.component'
2020-10-30 22:46:43 +01:00
2023-03-16 23:18:16 -07:00
enum DocumentDetailNavIDs {
Details = 1 ,
Content = 2 ,
Metadata = 3 ,
Preview = 4 ,
2023-03-17 16:36:08 -07:00
Notes = 5 ,
2023-03-16 23:18:16 -07:00
Permissions = 6 ,
2024-04-23 08:16:28 -07:00
History = 7 ,
2023-03-16 23:18:16 -07:00
}
2024-01-20 08:26:24 -08:00
enum ContentRenderType {
PDF = 'pdf' ,
Image = 'image' ,
Text = 'text' ,
Other = 'other' ,
2024-01-25 13:22:03 -08:00
Unknown = 'unknown' ,
2024-12-01 11:46:19 -08:00
TIFF = 'tiff' ,
2024-01-20 08:26:24 -08:00
}
2025-02-03 21:54:18 -08:00
export enum ZoomSetting {
2023-12-04 17:17:40 -08:00
PageFit = 'page-fit' ,
PageWidth = 'page-width' ,
Quarter = '.25' ,
Half = '.5' ,
ThreeQuarters = '.75' ,
One = '1' ,
OneAndHalf = '1.5' ,
Two = '2' ,
Three = '3' ,
}
2020-10-27 01:10:18 +01:00
@Component ( {
2023-09-14 14:03:28 -07:00
selector : 'pngx-document-detail' ,
2020-10-27 01:10:18 +01:00
templateUrl : './document-detail.component.html' ,
2022-03-11 10:53:32 -08:00
styleUrls : [ './document-detail.component.scss' ] ,
2025-01-01 22:26:53 -08:00
imports : [
PageHeaderComponent ,
CustomFieldsDropdownComponent ,
DocumentNotesComponent ,
DocumentHistoryComponent ,
CheckComponent ,
DateComponent ,
DocumentLinkComponent ,
MetadataCollapseComponent ,
PermissionsFormComponent ,
SelectComponent ,
TagsComponent ,
TextComponent ,
NumberComponent ,
MonetaryComponent ,
UrlComponent ,
2025-04-21 01:04:34 -07:00
SuggestionsDropdownComponent ,
2025-01-01 22:26:53 -08:00
CustomDatePipe ,
FileSizePipe ,
IfPermissionsDirective ,
AsyncPipe ,
FormsModule ,
ReactiveFormsModule ,
NgTemplateOutlet ,
SafeUrlPipe ,
NgbNavModule ,
NgbDropdownModule ,
NgxBootstrapIconsModule ,
PdfViewerModule ,
] ,
2020-10-27 01:10:18 +01:00
} )
2022-03-11 10:53:32 -08:00
export class DocumentDetailComponent
2023-05-11 12:49:33 -07:00
extends ComponentWithPermissions
2022-03-11 10:53:32 -08:00
implements OnInit , OnDestroy , DirtyComponent
{
2025-06-27 14:06:40 -07:00
private documentsService = inject ( DocumentService )
private route = inject ( ActivatedRoute )
2025-04-29 21:45:34 -07:00
private tagService = inject ( TagService )
2025-06-27 14:06:40 -07:00
private correspondentService = inject ( CorrespondentService )
private documentTypeService = inject ( DocumentTypeService )
private router = inject ( Router )
private modalService = inject ( NgbModal )
private openDocumentService = inject ( OpenDocumentsService )
private documentListViewService = inject ( DocumentListViewService )
private documentTitlePipe = inject ( DocumentTitlePipe )
private toastService = inject ( ToastService )
private settings = inject ( SettingsService )
private storagePathService = inject ( StoragePathService )
private permissionsService = inject ( PermissionsService )
private userService = inject ( UserService )
private customFieldsService = inject ( CustomFieldsService )
private http = inject ( HttpClient )
private hotKeyService = inject ( HotKeyService )
private componentRouterService = inject ( ComponentRouterService )
private deviceDetectorService = inject ( DeviceDetectorService )
2025-07-23 22:07:13 -07:00
private savedViewService = inject ( SavedViewService )
2025-06-27 14:06:40 -07:00
2022-03-11 10:53:32 -08:00
@ViewChild ( 'inputTitle' )
2021-01-05 22:11:42 +01:00
titleInput : TextComponent
2025-04-21 10:59:37 -07:00
@ViewChild ( 'tagsInput' ) tagsInput : TagsComponent
2021-01-03 13:09:16 +01:00
expandOriginalMetadata = false
expandArchivedMetadata = false
error : any
networkActive = false
2020-12-08 15:28:09 +01:00
2020-10-27 01:10:18 +01:00
documentId : number
2023-12-19 22:36:35 -08:00
document : Document
metadata : DocumentMetadata
suggestions : DocumentSuggestions
2025-04-19 23:39:47 -07:00
suggestionsLoading : boolean = false
2023-12-19 22:36:35 -08:00
users : User [ ]
2021-01-29 16:48:51 +01:00
2020-10-27 01:10:18 +01:00
title : string
2022-02-16 14:34:19 -08:00
titleSubject : Subject < string > = new Subject ( )
2020-10-27 01:10:18 +01:00
previewUrl : string
2024-11-12 16:20:52 -08:00
thumbUrl : string
2023-05-09 21:51:31 -07:00
previewText : string
2024-11-12 16:20:52 -08:00
previewLoaded : boolean = false
2024-12-01 11:46:19 -08:00
tiffURL : string
tiffError : string
2020-10-27 01:10:18 +01:00
2023-12-19 22:36:35 -08:00
correspondents : Correspondent [ ]
documentTypes : DocumentType [ ]
storagePaths : StoragePath [ ]
2020-10-27 01:10:18 +01:00
documentForm : FormGroup = new FormGroup ( {
title : new FormControl ( '' ) ,
content : new FormControl ( '' ) ,
2025-05-19 09:38:01 -07:00
created : new FormControl ( ) ,
2020-12-03 20:28:17 +01:00
correspondent : new FormControl ( ) ,
document_type : new FormControl ( ) ,
2022-05-19 23:42:25 +02:00
storage_path : new FormControl ( ) ,
2020-10-27 01:10:18 +01:00
archive_serial_number : new FormControl ( ) ,
2022-03-11 10:53:32 -08:00
tags : new FormControl ( [ ] ) ,
2022-12-09 10:04:39 -08:00
permissions_form : new FormControl ( null ) ,
2023-11-05 17:26:51 -08:00
custom_fields : new FormArray ( [ ] ) ,
2020-10-27 01:10:18 +01:00
} )
2024-04-07 11:15:49 -07:00
previewCurrentPage : number = 1
2024-04-07 08:16:33 -07:00
previewNumPages : number
2023-12-04 17:17:40 -08:00
previewZoomSetting : ZoomSetting = ZoomSetting . One
previewZoomScale : ZoomSetting = ZoomSetting . PageWidth
2020-12-18 14:31:09 -08:00
2021-01-25 23:00:57 -08:00
store : BehaviorSubject < any >
isDirty$ : Observable < boolean >
2022-02-15 23:43:54 -08:00
unsubscribeNotifier : Subject < any > = new Subject ( )
2022-05-17 09:59:14 -07:00
docChangeNotifier : Subject < any > = new Subject ( )
2021-01-25 23:00:57 -08:00
2022-03-29 10:35:42 -07:00
requiresPassword : boolean = false
password : string
2022-04-05 22:11:09 -07:00
ogDate : Date
2023-12-19 22:36:35 -08:00
customFields : CustomField [ ]
2025-01-30 08:31:52 -08:00
public downloading : boolean = false
2024-01-20 08:26:24 -08:00
public readonly CustomFieldDataType = CustomFieldDataType
public readonly ContentRenderType = ContentRenderType
2023-11-05 17:26:51 -08:00
2024-05-22 16:15:58 -07:00
public readonly DataType = DataType
2020-12-17 00:50:59 -08:00
@ViewChild ( 'nav' ) nav : NgbNav
2020-12-17 07:33:20 -08:00
@ViewChild ( 'pdfPreview' ) set pdfPreview ( element ) {
2024-01-08 13:03:05 -08:00
// this gets called when component added or removed from DOM
2022-03-11 10:53:32 -08:00
if (
element &&
element . nativeElement . offsetParent !== null &&
this . nav ? . activeId == 4
) {
// its visible
setTimeout ( ( ) = > this . nav ? . select ( 1 ) )
2020-12-17 00:50:59 -08:00
}
}
2023-03-16 23:18:16 -07:00
DocumentDetailNavIDs = DocumentDetailNavIDs
activeNavID : number
2022-11-12 04:03:35 -08:00
2022-02-16 14:34:19 -08:00
titleKeyUp ( event ) {
this . titleSubject . next ( event . target ? . value )
}
2021-01-14 13:35:21 +01:00
get useNativePdfViewer ( ) : boolean {
return this . settings . get ( SETTINGS_KEYS . USE_NATIVE_PDF_VIEWER )
}
2020-10-27 01:10:18 +01:00
2025-04-23 12:40:42 -07:00
get aiEnabled ( ) : boolean {
return this . settings . get ( SETTINGS_KEYS . AI_ENABLED )
}
2024-05-22 16:01:15 -07:00
get archiveContentRenderType ( ) : ContentRenderType {
2024-11-12 16:20:52 -08:00
return this . document ? . archived_file_name
? this . getRenderType ( 'application/pdf' )
: this . getRenderType ( this . document ? . mime_type )
2024-05-22 16:01:15 -07:00
}
2020-12-12 22:56:44 +01:00
2024-05-22 16:01:15 -07:00
get originalContentRenderType ( ) : ContentRenderType {
2024-11-12 16:20:52 -08:00
return this . getRenderType ( this . document ? . mime_type )
}
get showThumbnailOverlay ( ) : boolean {
return this . settings . get ( SETTINGS_KEYS . DOCUMENT_EDITING_OVERLAY_THUMBNAIL )
2024-05-22 16:01:15 -07:00
}
private getRenderType ( mimeType : string ) : ContentRenderType {
if ( ! mimeType ) return ContentRenderType . Unknown
if ( mimeType === 'application/pdf' ) {
2024-01-20 08:26:24 -08:00
return ContentRenderType . PDF
} else if (
2024-05-22 16:01:15 -07:00
[ 'text/plain' , 'application/csv' , 'text/csv' ] . includes ( mimeType )
2024-01-20 08:26:24 -08:00
) {
return ContentRenderType . Text
2024-12-01 11:46:19 -08:00
} else if ( mimeType . indexOf ( 'tiff' ) >= 0 ) {
return ContentRenderType . TIFF
2024-05-22 16:01:15 -07:00
} else if ( mimeType ? . indexOf ( 'image/' ) === 0 ) {
2024-01-20 08:26:24 -08:00
return ContentRenderType . Image
}
return ContentRenderType . Other
2023-05-09 21:51:31 -07:00
}
2023-01-01 08:59:43 -08:00
get isRTL() {
if ( ! this . metadata || ! this . metadata . lang ) return false
else {
return [ 'ar' , 'he' , 'fe' ] . includes ( this . metadata . lang )
}
}
2020-10-27 01:10:18 +01:00
ngOnInit ( ) : void {
2025-02-03 21:54:18 -08:00
this . setZoom ( this . settings . get ( SETTINGS_KEYS . PDF_VIEWER_ZOOM_SETTING ) )
2022-03-11 10:53:32 -08:00
this . documentForm . valueChanges
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
2022-05-15 21:09:42 -07:00
. subscribe ( ( ) = > {
2022-05-01 14:03:40 -07:00
this . error = null
2022-12-09 10:04:39 -08:00
const docValues = Object . assign ( { } , this . documentForm . value )
docValues [ 'owner' ] =
this . documentForm . get ( 'permissions_form' ) . value [ 'owner' ]
docValues [ 'set_permissions' ] =
this . documentForm . get ( 'permissions_form' ) . value [ 'set_permissions' ]
delete docValues [ 'permissions_form' ]
Object . assign ( this . document , docValues )
2022-02-16 02:27:17 -08:00
} )
2024-02-01 01:20:14 -08:00
if (
this . permissionsService . currentUserCan (
PermissionAction . View ,
PermissionType . Correspondent
)
) {
this . correspondentService
. listAll ( )
. pipe ( first ( ) , takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( result ) = > ( this . correspondents = result . results ) )
}
if (
this . permissionsService . currentUserCan (
PermissionAction . View ,
PermissionType . DocumentType
)
) {
this . documentTypeService
. listAll ( )
. pipe ( first ( ) , takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( result ) = > ( this . documentTypes = result . results ) )
}
if (
this . permissionsService . currentUserCan (
PermissionAction . View ,
PermissionType . StoragePath
)
) {
this . storagePathService
. listAll ( )
. pipe ( first ( ) , takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( result ) = > ( this . storagePaths = result . results ) )
}
if (
this . permissionsService . currentUserCan (
PermissionAction . View ,
PermissionType . User
)
) {
this . userService
. listAll ( )
. pipe ( first ( ) , takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( result ) = > ( this . users = result . results ) )
}
2022-12-09 10:04:39 -08:00
2023-11-05 17:26:51 -08:00
this . getCustomFields ( )
2022-03-11 10:53:32 -08:00
this . route . paramMap
. pipe (
2024-01-13 13:40:22 -08:00
filter ( ( paramMap ) = > {
// only init when changing docs & section is set
return (
+ paramMap . get ( 'id' ) !== this . documentId &&
paramMap . get ( 'section' ) ? . length > 0
)
} ) ,
2022-05-17 10:36:43 -07:00
takeUntil ( this . unsubscribeNotifier ) ,
2022-03-11 10:53:32 -08:00
switchMap ( ( paramMap ) = > {
const documentId = + paramMap . get ( 'id' )
2022-05-17 09:59:14 -07:00
this . docChangeNotifier . next ( documentId )
2024-10-10 19:43:13 -07:00
// Dont wait to get the preview
this . previewUrl = this . documentsService . getPreviewUrl ( documentId )
2023-04-03 10:03:59 -07:00
this . http . get ( this . previewUrl , { responseType : 'text' } ) . subscribe ( {
next : ( res ) = > {
2023-05-09 21:51:31 -07:00
this . previewText = res . toString ( )
2023-04-03 10:03:59 -07:00
} ,
error : ( err ) = > {
2023-05-09 21:51:31 -07:00
this . previewText = $localize ` An error occurred loading content: ${
2023-04-03 10:03:59 -07:00
err . message ? ? err . toString ( )
} `
} ,
} )
2024-11-12 16:20:52 -08:00
this . thumbUrl = this . documentsService . getThumbUrl ( documentId )
2024-10-10 19:43:13 -07:00
return this . documentsService . get ( documentId )
} )
)
. pipe (
switchMap ( ( doc ) = > {
this . documentId = doc . id
2022-03-11 10:53:32 -08:00
this . suggestions = null
2023-04-29 00:05:05 -07:00
const openDocument = this . openDocumentService . getOpenDocument (
this . documentId
)
2024-01-13 12:28:10 -08:00
2023-04-29 00:05:05 -07:00
if ( openDocument ) {
2024-01-13 12:28:10 -08:00
if (
new Date ( doc . modified ) > new Date ( openDocument . modified ) &&
! this . modalService . hasOpenModals ( )
) {
2024-01-13 13:40:22 -08:00
let modal = this . modalService . open ( ConfirmDialogComponent )
2024-01-13 12:28:10 -08:00
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. `
2024-01-13 13:40:22 -08:00
modal . componentInstance . cancelBtnClass = 'visually-hidden'
modal . componentInstance . btnCaption = $localize ` Ok `
2024-01-16 15:18:26 -08:00
modal . componentInstance . confirmClicked . subscribe ( ( ) = >
modal . close ( )
)
2024-01-13 12:28:10 -08:00
}
2023-04-29 00:05:05 -07:00
if ( this . documentForm . dirty ) {
Object . assign ( openDocument , this . documentForm . value )
openDocument [ 'owner' ] =
this . documentForm . get ( 'permissions_form' ) . value [ 'owner' ]
openDocument [ 'permissions' ] =
this . documentForm . get ( 'permissions_form' ) . value [
'set_permissions'
]
delete openDocument [ 'permissions_form' ]
}
2025-07-29 00:38:43 -04:00
if ( openDocument . __changedFields ) {
openDocument . __changedFields . forEach ( ( field ) = > {
if ( field === 'owner' || field === 'set_permissions' ) {
this . documentForm . get ( 'permissions_form' ) . markAsDirty ( )
} else {
this . documentForm . get ( field ) ? . markAsDirty ( )
}
} )
}
2023-04-29 00:05:05 -07:00
this . updateComponent ( openDocument )
2022-03-11 10:53:32 -08:00
} else {
2022-12-09 17:51:01 -08:00
this . openDocumentService . openDocument ( doc )
2022-03-11 10:53:32 -08:00
this . updateComponent ( doc )
}
2022-05-17 09:59:14 -07:00
this . titleSubject
. pipe (
debounceTime ( 1000 ) ,
distinctUntilChanged ( ) ,
takeUntil ( this . docChangeNotifier ) ,
takeUntil ( this . unsubscribeNotifier )
)
2022-05-17 10:36:43 -07:00
. subscribe ( {
next : ( titleValue ) = > {
2023-01-25 10:53:08 -08:00
// In the rare case when the field changed just after debounced event was fired.
2024-01-08 13:03:05 -08:00
// We dont want to overwrite what's actually in the text field, so just return
2023-01-25 10:53:08 -08:00
if ( titleValue !== this . titleInput . value ) return
2022-05-17 10:36:43 -07:00
this . title = titleValue
this . documentForm . patchValue ( { title : titleValue } )
} ,
complete : ( ) = > {
// doc changed so we manually check dirty in case title was changed
if (
this . store . getValue ( ) . title !==
this . documentForm . get ( 'title' ) . value
) {
2022-08-06 19:30:39 -07:00
this . openDocumentService . setDirty ( doc , true )
2022-05-17 10:36:43 -07:00
}
} ,
2022-05-17 09:59:14 -07:00
} )
2022-03-11 10:53:32 -08:00
// Initialize dirtyCheck
this . store = new BehaviorSubject ( {
title : doc.title ,
content : doc.content ,
2025-05-19 09:38:01 -07:00
created : doc.created ,
2022-03-11 10:53:32 -08:00
correspondent : doc.correspondent ,
document_type : doc.document_type ,
2022-05-19 23:42:25 +02:00
storage_path : doc.storage_path ,
2022-03-11 10:53:32 -08:00
archive_serial_number : doc.archive_serial_number ,
tags : [ . . . doc . tags ] ,
2022-12-09 10:04:39 -08:00
permissions_form : {
owner : doc.owner ,
set_permissions : doc.permissions ,
} ,
2024-05-09 10:10:11 -07:00
custom_fields : [ . . . doc . custom_fields ] ,
2022-03-11 10:53:32 -08:00
} )
this . isDirty $ = dirtyCheck (
this . documentForm ,
this . store . asObservable ( )
)
2022-08-06 19:30:39 -07:00
return this . isDirty $ . pipe (
takeUntil ( this . unsubscribeNotifier ) ,
map ( ( dirty ) = > ( { doc , dirty } ) )
)
2022-03-11 10:53:32 -08:00
} )
)
2022-03-14 16:23:14 -07:00
. subscribe ( {
next : ( { doc , dirty } ) = > {
2025-07-29 00:38:43 -04:00
this . openDocumentService . setDirty ( doc , dirty , this . getChangedFields ( ) )
2022-03-11 10:53:32 -08:00
} ,
2022-03-14 16:23:14 -07:00
error : ( error ) = > {
2023-08-08 23:59:13 -07:00
this . router . navigate ( [ '404' ] , {
replaceUrl : true ,
} )
2022-03-14 16:23:14 -07:00
} ,
} )
2023-03-16 23:18:16 -07:00
this . route . paramMap . subscribe ( ( paramMap ) = > {
const section = paramMap . get ( 'section' )
if ( section ) {
const navIDKey : string = Object . keys ( DocumentDetailNavIDs ) . find (
( navID ) = > navID . toLowerCase ( ) == section
)
if ( navIDKey ) {
this . activeNavID = DocumentDetailNavIDs [ navIDKey ]
}
2023-04-28 08:14:24 -07:00
} else if ( paramMap . get ( 'id' ) ) {
this . router . navigate ( [ 'documents' , + paramMap . get ( 'id' ) , 'details' ] , {
replaceUrl : true ,
} )
2023-03-16 23:18:16 -07:00
}
} )
2024-05-02 09:15:56 -07:00
this . hotKeyService
. addShortcut ( {
keys : 'control.arrowright' ,
description : $localize ` Next document ` ,
} )
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( ) = > {
if ( this . hasNext ( ) ) this . nextDoc ( )
} )
this . hotKeyService
. addShortcut ( {
keys : 'control.arrowleft' ,
description : $localize ` Previous document ` ,
} )
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( ) = > {
if ( this . hasPrevious ( ) ) this . previousDoc ( )
} )
this . hotKeyService
. addShortcut ( { keys : 'escape' , description : $localize ` Close document ` } )
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( ) = > {
this . close ( )
} )
this . hotKeyService
. addShortcut ( { keys : 'control.s' , description : $localize ` Save document ` } )
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( ) = > {
if ( this . openDocumentService . isDirty ( this . document ) ) this . save ( )
} )
2024-11-12 16:21:10 -08:00
this . hotKeyService
. addShortcut ( {
keys : 'control.shift.s' ,
description : $localize ` Save and close / next ` ,
} )
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( ) = > {
if ( this . openDocumentService . isDirty ( this . document ) ) this . saveEditNext ( )
} )
2020-10-27 01:10:18 +01:00
}
2022-03-11 10:53:32 -08:00
ngOnDestroy ( ) : void {
2022-03-14 16:23:14 -07:00
this . unsubscribeNotifier . next ( this )
2022-03-11 10:53:32 -08:00
this . unsubscribeNotifier . complete ( )
2022-02-15 23:43:54 -08:00
}
2023-03-16 23:18:16 -07:00
onNavChange ( navChangeEvent : NgbNavChangeEvent ) {
const [ foundNavIDkey ] = Object . entries ( DocumentDetailNavIDs ) . find (
( [ , navIDValue ] ) = > navIDValue == navChangeEvent . nextId
)
if ( foundNavIDkey )
this . router . navigate ( [
'documents' ,
this . documentId ,
foundNavIDkey . toLowerCase ( ) ,
] )
}
2023-12-19 22:36:35 -08:00
updateComponent ( doc : Document ) {
2020-11-04 13:10:23 +01:00
this . document = doc
2022-04-28 21:07:25 -07:00
this . requiresPassword = false
2023-11-05 17:26:51 -08:00
this . updateFormForCustomFields ( )
2024-12-01 11:46:19 -08:00
if ( this . archiveContentRenderType === ContentRenderType . TIFF ) {
this . tryRenderTiff ( )
}
2022-03-11 10:53:32 -08:00
this . documentsService
. getMetadata ( doc . id )
2024-01-13 13:40:22 -08:00
. pipe (
first ( ) ,
takeUntil ( this . unsubscribeNotifier ) ,
takeUntil ( this . docChangeNotifier )
)
2022-03-14 16:23:14 -07:00
. subscribe ( {
next : ( result ) = > {
2022-03-11 10:53:32 -08:00
this . metadata = result
2025-01-13 09:53:39 -08:00
if (
this . archiveContentRenderType !== ContentRenderType . PDF ||
this . useNativePdfViewer
) {
2024-11-12 16:20:52 -08:00
this . previewLoaded = true
}
2022-03-11 10:53:32 -08:00
} ,
2022-03-14 16:23:14 -07:00
error : ( error ) = > {
2024-02-09 11:35:32 -08:00
this . metadata = { } // allow display to fallback to <object> tag
2023-02-16 19:41:10 -08:00
this . toastService . showError (
2023-05-20 10:28:37 -07:00
$localize ` Error retrieving metadata ` ,
error
2023-02-16 19:41:10 -08:00
)
2022-03-14 16:23:14 -07:00
} ,
} )
2023-03-03 15:21:02 -08:00
if (
this . permissionsService . currentUserHasObjectPermissions (
PermissionAction . Change ,
doc
2024-12-11 01:05:23 -08:00
) &&
this . permissionsService . currentUserCan (
PermissionAction . Change ,
PermissionType . Document
2023-03-03 15:21:02 -08:00
)
) {
2025-04-19 21:57:06 -07:00
this . tagService . getCachedMany ( doc . tags ) . subscribe ( ( tags ) = > {
// only show suggestions if document has inbox tags
if ( tags . some ( ( tag ) = > tag . is_inbox_tag ) ) {
2025-04-19 23:39:47 -07:00
this . getSuggestions ( )
2025-04-19 21:57:06 -07:00
}
} )
2023-03-03 15:21:02 -08:00
}
2020-12-16 16:44:54 +01:00
this . title = this . documentTitlePipe . transform ( doc . title )
2022-12-09 10:04:39 -08:00
const docFormValues = Object . assign ( { } , doc )
docFormValues [ 'permissions_form' ] = {
owner : doc.owner ,
set_permissions : doc.permissions ,
}
this . documentForm . patchValue ( docFormValues , { emitEvent : false } )
2022-12-07 15:46:52 -08:00
if ( ! this . userCanEdit ) this . documentForm . disable ( )
2020-11-04 13:10:23 +01:00
}
2023-11-05 17:26:51 -08:00
get customFieldFormFields ( ) : FormArray {
return this . documentForm . get ( 'custom_fields' ) as FormArray
}
2025-04-19 23:39:47 -07:00
getSuggestions() {
this . suggestionsLoading = true
this . documentsService
. getSuggestions ( this . documentId )
. pipe (
first ( ) ,
takeUntil ( this . unsubscribeNotifier ) ,
takeUntil ( this . docChangeNotifier )
)
. subscribe ( {
next : ( result ) = > {
this . suggestions = result
this . suggestionsLoading = false
} ,
error : ( error ) = > {
this . suggestions = null
this . suggestionsLoading = false
this . toastService . showError (
$localize ` Error retrieving suggestions. ` ,
error
)
} ,
} )
}
2025-04-22 00:21:26 -07:00
createTag ( newName : string ) {
var modal = this . modalService . open ( TagEditDialogComponent , {
backdrop : 'static' ,
} )
modal . componentInstance . dialogMode = EditDialogMode . CREATE
if ( newName ) modal . componentInstance . object = { name : newName }
modal . componentInstance . succeeded
. pipe (
switchMap ( ( newTag ) = > {
return this . tagService
. listAll ( )
. pipe ( map ( ( tags ) = > ( { newTag , tags } ) ) )
} )
)
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( { newTag , tags } ) = > {
this . tagsInput . tags = tags . results
this . tagsInput . addTag ( newTag . id )
2025-04-22 00:28:01 -07:00
if ( this . suggestions ) {
this . suggestions . suggested_tags =
this . suggestions . suggested_tags . filter ( ( tag ) = > tag !== newName )
}
2025-04-22 00:21:26 -07:00
} )
}
2021-03-24 12:21:13 -07:00
createDocumentType ( newName : string ) {
2022-03-11 10:53:32 -08:00
var modal = this . modalService . open ( DocumentTypeEditDialogComponent , {
backdrop : 'static' ,
} )
2023-05-23 15:02:54 -07:00
modal . componentInstance . dialogMode = EditDialogMode . CREATE
2021-03-24 12:21:13 -07:00
if ( newName ) modal . componentInstance . object = { name : newName }
2022-12-30 07:33:45 -08:00
modal . componentInstance . succeeded
2022-03-11 10:53:32 -08:00
. pipe (
switchMap ( ( newDocumentType ) = > {
return this . documentTypeService
. listAll ( )
. pipe ( map ( ( documentTypes ) = > ( { newDocumentType , documentTypes } ) ) )
} )
)
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( { newDocumentType , documentTypes } ) = > {
this . documentTypes = documentTypes . results
this . documentForm . get ( 'document_type' ) . setValue ( newDocumentType . id )
2025-05-24 07:28:04 -07:00
this . documentForm . get ( 'document_type' ) . markAsDirty ( )
2025-04-22 00:28:01 -07:00
if ( this . suggestions ) {
this . suggestions . suggested_document_types =
this . suggestions . suggested_document_types . filter (
( dt ) = > dt !== newName
)
}
2022-03-11 10:53:32 -08:00
} )
2020-10-27 01:10:18 +01:00
}
2021-03-24 12:21:13 -07:00
createCorrespondent ( newName : string ) {
2022-03-11 10:53:32 -08:00
var modal = this . modalService . open ( CorrespondentEditDialogComponent , {
backdrop : 'static' ,
} )
2023-05-23 15:02:54 -07:00
modal . componentInstance . dialogMode = EditDialogMode . CREATE
2021-03-24 12:21:13 -07:00
if ( newName ) modal . componentInstance . object = { name : newName }
2022-12-30 07:33:45 -08:00
modal . componentInstance . succeeded
2022-03-11 10:53:32 -08:00
. pipe (
switchMap ( ( newCorrespondent ) = > {
return this . correspondentService
. listAll ( )
. pipe (
map ( ( correspondents ) = > ( { newCorrespondent , correspondents } ) )
)
} )
)
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( { newCorrespondent , correspondents } ) = > {
this . correspondents = correspondents . results
this . documentForm . get ( 'correspondent' ) . setValue ( newCorrespondent . id )
2025-05-24 07:28:04 -07:00
this . documentForm . get ( 'correspondent' ) . markAsDirty ( )
2025-04-22 00:28:01 -07:00
if ( this . suggestions ) {
this . suggestions . suggested_correspondents =
this . suggestions . suggested_correspondents . filter (
( c ) = > c !== newName
)
}
2022-03-11 10:53:32 -08:00
} )
2020-10-27 01:10:18 +01:00
}
2022-05-19 23:42:25 +02:00
createStoragePath ( newName : string ) {
var modal = this . modalService . open ( StoragePathEditDialogComponent , {
backdrop : 'static' ,
} )
2023-05-23 15:02:54 -07:00
modal . componentInstance . dialogMode = EditDialogMode . CREATE
2022-05-19 23:42:25 +02:00
if ( newName ) modal . componentInstance . object = { name : newName }
2022-12-30 07:33:45 -08:00
modal . componentInstance . succeeded
2022-05-19 23:42:25 +02:00
. pipe (
switchMap ( ( newStoragePath ) = > {
return this . storagePathService
. listAll ( )
. pipe ( map ( ( storagePaths ) = > ( { newStoragePath , storagePaths } ) ) )
} )
)
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
2022-10-11 12:23:15 -07:00
. subscribe ( ( { newStoragePath , storagePaths } ) = > {
2022-05-19 23:42:25 +02:00
this . storagePaths = storagePaths . results
this . documentForm . get ( 'storage_path' ) . setValue ( newStoragePath . id )
2025-05-24 07:28:04 -07:00
this . documentForm . get ( 'storage_path' ) . markAsDirty ( )
2022-05-19 23:42:25 +02:00
} )
}
2024-08-05 16:45:48 -07:00
createDisabled ( dataType : DataType ) {
switch ( dataType ) {
case DataType . Correspondent :
return ! this . permissionsService . currentUserCan (
PermissionAction . Add ,
PermissionType . Correspondent
)
case DataType . DocumentType :
return ! this . permissionsService . currentUserCan (
PermissionAction . Add ,
PermissionType . DocumentType
)
case DataType . StoragePath :
return ! this . permissionsService . currentUserCan (
PermissionAction . Add ,
PermissionType . StoragePath
)
case DataType . Tag :
return ! this . permissionsService . currentUserCan (
PermissionAction . Add ,
PermissionType . Tag
)
}
}
2020-11-04 17:23:36 +01:00
discard() {
2022-03-11 10:53:32 -08:00
this . documentsService
. get ( this . documentId )
. pipe ( first ( ) )
2022-05-01 14:03:40 -07:00
. subscribe ( {
next : ( doc ) = > {
2022-03-11 10:53:32 -08:00
Object . assign ( this . document , doc )
2023-03-03 19:22:11 -08:00
doc [ 'permissions_form' ] = {
owner : doc.owner ,
set_permissions : doc.permissions ,
}
2022-03-11 10:53:32 -08:00
this . title = doc . title
2023-11-05 17:26:51 -08:00
this . updateFormForCustomFields ( )
2022-03-11 10:53:32 -08:00
this . documentForm . patchValue ( doc )
2025-04-22 12:58:28 -07:00
this . documentForm . markAsPristine ( )
2022-08-06 19:30:39 -07:00
this . openDocumentService . setDirty ( doc , false )
2022-03-11 10:53:32 -08:00
} ,
2022-05-01 14:03:40 -07:00
error : ( ) = > {
2023-08-08 23:59:13 -07:00
this . router . navigate ( [ '404' ] , {
replaceUrl : true ,
} )
2022-05-01 14:03:40 -07:00
} ,
} )
2020-10-27 01:10:18 +01:00
}
2025-04-22 12:58:28 -07:00
private getChangedFields ( ) : any {
const changes = {
id : this.document.id ,
}
Object . keys ( this . documentForm . controls ) . forEach ( ( key ) = > {
if ( this . documentForm . get ( key ) . dirty ) {
if ( key === 'permissions_form' ) {
changes [ 'owner' ] =
this . documentForm . get ( 'permissions_form' ) . value [ 'owner' ]
changes [ 'set_permissions' ] =
this . documentForm . get ( 'permissions_form' ) . value [ 'set_permissions' ]
} else {
changes [ key ] = this . documentForm . get ( key ) . value
}
}
} )
return changes
}
2023-06-18 06:47:52 +02:00
save ( close : boolean = false ) {
2021-01-03 13:09:16 +01:00
this . networkActive = true
2024-11-27 23:25:52 -08:00
; ( document . activeElement as HTMLElement ) ? . dispatchEvent ( new Event ( 'change' ) )
2022-03-11 10:53:32 -08:00
this . documentsService
2025-04-22 12:58:28 -07:00
. patch ( this . getChangedFields ( ) )
2022-03-11 10:53:32 -08:00
. pipe ( first ( ) )
2022-03-14 16:23:14 -07:00
. subscribe ( {
2024-02-01 18:41:10 -08:00
next : ( docValues ) = > {
// in case data changed while saving eg removing inbox_tags
this . documentForm . patchValue ( docValues )
2024-11-05 20:00:44 -08:00
const newValues = Object . assign ( { } , this . documentForm . value )
newValues . tags = [ . . . docValues . tags ]
newValues . custom_fields = [ . . . docValues . custom_fields ]
this . store . next ( newValues )
2024-02-25 16:59:29 -08:00
this . openDocumentService . setDirty ( this . document , false )
2024-05-01 12:07:19 -07:00
this . openDocumentService . save ( )
2025-02-01 21:57:57 -08:00
this . toastService . showInfo (
$localize ` Document " ${ newValues . title } " saved successfully. `
)
2022-03-11 10:53:32 -08:00
this . networkActive = false
this . error = null
2024-05-01 12:07:19 -07:00
if ( close ) {
2024-02-25 16:59:29 -08:00
this . close ( ( ) = >
this . openDocumentService . refreshDocument ( this . documentId )
)
2024-05-01 12:07:19 -07:00
} else {
this . openDocumentService . refreshDocument ( this . documentId )
}
2025-07-23 22:07:13 -07:00
this . savedViewService . maybeRefreshDocumentCounts ( )
2022-03-11 10:53:32 -08:00
} ,
2022-03-14 16:23:14 -07:00
error : ( error ) = > {
2022-03-11 10:53:32 -08:00
this . networkActive = false
2025-03-21 09:50:04 -07:00
const canEdit =
this . permissionsService . currentUserHasObjectPermissions (
PermissionAction . Change ,
this . document
)
if ( ! canEdit ) {
// document was 'given away'
this . openDocumentService . setDirty ( this . document , false )
2025-02-01 21:57:57 -08:00
this . toastService . showInfo (
$localize ` Document " ${ this . document . title } " saved successfully. `
)
2025-03-21 09:50:04 -07:00
this . close ( )
2023-03-06 10:04:18 -08:00
} else {
this . error = error . error
2025-02-01 21:57:57 -08:00
this . toastService . showError (
$localize ` Error saving document " ${ this . document . title } " ` ,
error
)
2023-03-06 10:04:18 -08:00
}
2022-03-14 16:23:14 -07:00
} ,
} )
2020-10-27 01:10:18 +01:00
}
saveEditNext() {
2021-01-03 13:09:16 +01:00
this . networkActive = true
2021-01-25 23:06:25 -08:00
this . store . next ( this . documentForm . value )
2022-03-11 10:53:32 -08:00
this . documentsService
2025-04-22 12:58:28 -07:00
. patch ( this . getChangedFields ( ) )
2022-03-11 10:53:32 -08:00
. pipe (
switchMap ( ( updateResult ) = > {
return this . documentListViewService
. getNext ( this . documentId )
. pipe ( map ( ( nextDocId ) = > ( { nextDocId , updateResult } ) ) )
} )
)
. pipe (
switchMap ( ( { nextDocId , updateResult } ) = > {
2025-01-23 17:51:01 -08:00
if ( nextDocId && updateResult ) {
this . openDocumentService . setDirty ( this . document , false )
2022-03-11 10:53:32 -08:00
return this . openDocumentService
. closeDocument ( this . document )
. pipe (
map ( ( closeResult ) = > ( { updateResult , nextDocId , closeResult } ) )
)
2025-01-23 17:51:01 -08:00
}
2022-03-11 10:53:32 -08:00
} )
)
. pipe ( first ( ) )
2022-03-14 16:23:14 -07:00
. subscribe ( {
next : ( { updateResult , nextDocId , closeResult } ) = > {
2022-03-11 10:53:32 -08:00
this . error = null
this . networkActive = false
if ( closeResult && updateResult && nextDocId ) {
this . router . navigate ( [ 'documents' , nextDocId ] )
this . titleInput ? . focus ( )
}
} ,
2022-03-14 16:23:14 -07:00
error : ( error ) = > {
2022-03-11 10:53:32 -08:00
this . networkActive = false
this . error = error . error
2023-08-23 23:50:54 -07:00
this . toastService . showError ( $localize ` Error saving document ` , error )
2022-03-14 16:23:14 -07:00
} ,
} )
2020-10-27 01:10:18 +01:00
}
2024-02-25 16:59:29 -08:00
close ( closedCallback : ( ) = > void = null ) {
2022-03-11 10:53:32 -08:00
this . openDocumentService
. closeDocument ( this . document )
. pipe ( first ( ) )
. subscribe ( ( closed ) = > {
if ( ! closed ) return
2024-02-25 16:59:29 -08:00
if ( closedCallback ) closedCallback ( )
2022-03-11 10:53:32 -08:00
if ( this . documentListViewService . activeSavedViewId ) {
this . router . navigate ( [
'view' ,
this . documentListViewService . activeSavedViewId ,
] )
2025-01-28 22:26:30 -08:00
} else if ( this . componentRouterService . getComponentURLBefore ( ) ) {
this . router . navigate ( [
this . componentRouterService . getComponentURLBefore ( ) ,
] )
2022-03-11 10:53:32 -08:00
} else {
this . router . navigate ( [ 'documents' ] )
}
} )
2021-01-26 20:46:28 -08:00
}
2020-10-27 01:10:18 +01:00
delete ( ) {
2022-03-11 10:53:32 -08:00
let modal = this . modalService . open ( ConfirmDialogComponent , {
backdrop : 'static' ,
} )
2024-06-17 08:07:08 -07:00
modal . componentInstance . title = $localize ` Confirm `
modal . componentInstance . messageBold = $localize ` Do you really want to move the document " ${ this . document . title } " to the trash? `
modal . componentInstance . message = $localize ` Documents can be restored prior to permanent deletion. `
2022-03-11 10:53:32 -08:00
modal . componentInstance . btnClass = 'btn-danger'
2024-06-17 08:07:08 -07:00
modal . componentInstance . btnCaption = $localize ` Move to trash `
2023-04-04 16:16:17 -07:00
this . subscribeModalDelete ( modal ) // so can be re-subscribed if error
}
subscribeModalDelete ( modal ) {
2022-03-11 10:53:32 -08:00
modal . componentInstance . confirmClicked
. pipe (
switchMap ( ( ) = > {
modal . componentInstance . buttonsEnabled = false
return this . documentsService . delete ( this . document )
} )
)
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
2023-04-04 16:16:17 -07:00
. subscribe ( {
next : ( ) = > {
2022-03-11 10:53:32 -08:00
modal . close ( )
this . close ( )
} ,
2023-04-04 16:16:17 -07:00
error : ( error ) = > {
2023-08-23 23:50:54 -07:00
this . toastService . showError ( $localize ` Error deleting document ` , error )
2022-03-11 10:53:32 -08:00
modal . componentInstance . buttonsEnabled = true
2023-04-04 16:16:17 -07:00
this . subscribeModalDelete ( modal )
} ,
} )
2020-10-27 01:10:18 +01:00
}
2020-12-17 21:36:21 +01:00
moreLike() {
2022-05-20 15:16:17 -07:00
this . documentListViewService . quickFilter ( [
2022-03-11 10:53:32 -08:00
{
rule_type : FILTER_FULLTEXT_MORELIKE ,
value : this.documentId.toString ( ) ,
} ,
] )
2020-12-17 21:36:21 +01:00
}
2024-05-31 11:41:52 -07:00
reprocess() {
2022-07-28 15:36:24 -07:00
let modal = this . modalService . open ( ConfirmDialogComponent , {
backdrop : 'static' ,
} )
2024-05-31 11:41:52 -07:00
modal . componentInstance . title = $localize ` Reprocess confirm `
modal . componentInstance . messageBold = $localize ` This operation will permanently recreate the archive file for this document. `
modal . componentInstance . message = $localize ` The archive file will be re-generated with the current settings. `
2022-07-28 15:36:24 -07:00
modal . componentInstance . btnClass = 'btn-danger'
modal . componentInstance . btnCaption = $localize ` Proceed `
modal . componentInstance . confirmClicked . subscribe ( ( ) = > {
modal . componentInstance . buttonsEnabled = false
this . documentsService
2024-05-31 11:41:52 -07:00
. bulkEdit ( [ this . document . id ] , 'reprocess' , { } )
2022-07-28 15:36:24 -07:00
. subscribe ( {
next : ( ) = > {
this . toastService . showInfo (
2025-02-01 21:57:57 -08:00
$localize ` Reprocess operation for " ${ this . document . title } " will begin in the background. Close and re-open or reload this document after the operation has completed to see new content. `
2022-07-28 15:36:24 -07:00
)
if ( modal ) {
modal . close ( )
}
} ,
error : ( error ) = > {
if ( modal ) {
modal . componentInstance . buttonsEnabled = true
}
this . toastService . showError (
2023-08-23 23:50:54 -07:00
$localize ` Error executing operation ` ,
error
2022-07-28 15:36:24 -07:00
)
} ,
} )
} )
}
2025-01-30 08:31:52 -08:00
download ( original : boolean = false ) {
this . downloading = true
const downloadUrl = this . documentsService . getDownloadUrl (
this . documentId ,
original
)
2025-04-09 09:03:38 -07:00
this . http
. get ( downloadUrl , { observe : 'response' , responseType : 'blob' } )
. subscribe ( {
next : ( response : HttpResponse < Blob > ) = > {
2025-04-19 15:10:34 -07:00
const contentDisposition = response . headers . get ( 'Content-Disposition' )
const filename =
getFilenameFromContentDisposition ( contentDisposition ) ||
this . document . title
2025-04-09 09:03:38 -07:00
const blob = new Blob ( [ response . body ] , {
type : response . body . type ,
2025-01-30 08:31:52 -08:00
} )
2025-04-09 09:03:38 -07:00
this . downloading = false
const file = new File ( [ blob ] , filename , {
type : response . body . type ,
} )
if (
! this . deviceDetectorService . isDesktop ( ) &&
navigator . canShare &&
navigator . canShare ( { files : [ file ] } )
) {
navigator . share ( {
files : [ file ] ,
} )
} else {
const url = URL . createObjectURL ( blob )
const a = document . createElement ( 'a' )
a . href = url
a . download = filename
a . click ( )
URL . revokeObjectURL ( url )
}
} ,
error : ( error ) = > {
this . downloading = false
this . toastService . showError (
$localize ` Error downloading document ` ,
error
)
} ,
} )
2025-01-30 08:31:52 -08:00
}
2020-10-27 01:10:18 +01:00
hasNext() {
return this . documentListViewService . hasNext ( this . documentId )
}
2020-12-18 14:31:09 -08:00
2022-03-11 17:35:38 +01:00
hasPrevious() {
return this . documentListViewService . hasPrevious ( this . documentId )
}
nextDoc() {
2022-03-11 12:00:31 -08:00
this . documentListViewService
. getNext ( this . document . id )
. subscribe ( ( nextDocId : number ) = > {
this . router . navigate ( [ 'documents' , nextDocId ] )
} )
2022-03-11 17:35:38 +01:00
}
2022-03-11 12:00:31 -08:00
previousDoc() {
this . documentListViewService
. getPrevious ( this . document . id )
. subscribe ( ( prevDocId : number ) = > {
this . router . navigate ( [ 'documents' , prevDocId ] )
} )
2022-03-11 17:35:38 +01:00
}
2020-12-18 14:31:09 -08:00
pdfPreviewLoaded ( pdf : PDFDocumentProxy ) {
this . previewNumPages = pdf . numPages
2022-03-29 10:35:42 -07:00
if ( this . password ) this . requiresPassword = false
2024-11-12 16:20:52 -08:00
setTimeout ( ( ) = > {
this . previewLoaded = true
} , 300 )
2022-03-29 10:35:42 -07:00
}
onError ( event ) {
if ( event . name == 'PasswordException' ) {
this . requiresPassword = true
2024-11-12 16:20:52 -08:00
this . previewLoaded = true
2022-03-29 10:35:42 -07:00
}
}
onPasswordKeyUp ( event : KeyboardEvent ) {
if ( 'Enter' == event . key ) {
this . password = ( event . target as HTMLInputElement ) . value
}
2020-12-18 14:31:09 -08:00
}
2022-08-07 15:05:58 -07:00
2025-02-03 21:54:18 -08:00
setZoom ( setting : ZoomSetting ) {
if ( ZoomSetting . PageFit === setting || ZoomSetting . PageWidth === setting ) {
2023-12-04 17:17:40 -08:00
this . previewZoomScale = setting
2025-02-03 21:54:18 -08:00
this . previewZoomSetting = ZoomSetting . One
2023-12-04 17:17:40 -08:00
} else {
this . previewZoomSetting = setting
2025-02-03 21:54:18 -08:00
this . previewZoomScale = ZoomSetting . PageWidth
2023-12-04 17:17:40 -08:00
}
}
get zoomSettings() {
return Object . values ( ZoomSetting ) . filter (
( setting ) = > setting !== ZoomSetting . PageWidth
)
}
2025-04-22 08:02:43 -07:00
get currentZoom() {
2025-02-03 21:54:18 -08:00
if ( this . previewZoomScale === ZoomSetting . PageFit ) {
2025-04-22 08:02:43 -07:00
return ZoomSetting . PageFit
} else return this . previewZoomSetting
2025-02-03 21:54:18 -08:00
}
2023-12-04 17:17:40 -08:00
getZoomSettingTitle ( setting : ZoomSetting ) : string {
switch ( setting ) {
case ZoomSetting . PageFit :
return $localize ` Page Fit `
default :
return ` ${ parseFloat ( setting ) * 100 } % `
}
}
increaseZoom ( ) : void {
let currentIndex = Object . values ( ZoomSetting ) . indexOf (
this . previewZoomSetting
)
if ( this . previewZoomScale === ZoomSetting . PageFit ) currentIndex = 5
this . previewZoomScale = ZoomSetting . PageWidth
this . previewZoomSetting =
Object . values ( ZoomSetting ) [
Math . min ( Object . values ( ZoomSetting ) . length - 1 , currentIndex + 1 )
]
}
decreaseZoom ( ) : void {
let currentIndex = Object . values ( ZoomSetting ) . indexOf (
this . previewZoomSetting
)
if ( this . previewZoomScale === ZoomSetting . PageFit ) currentIndex = 4
this . previewZoomScale = ZoomSetting . PageWidth
this . previewZoomSetting =
Object . values ( ZoomSetting ) [ Math . max ( 2 , currentIndex - 1 ) ]
}
2023-04-09 16:17:48 -07:00
get showPermissions ( ) : boolean {
return (
this . permissionsService . currentUserCan (
PermissionAction . View ,
PermissionType . User
) && this . userIsOwner
)
}
2023-03-17 16:36:08 -07:00
get notesEnabled ( ) : boolean {
2022-11-11 14:32:18 -08:00
return (
2023-03-17 16:36:08 -07:00
this . settings . get ( SETTINGS_KEYS . NOTES_ENABLED ) &&
2022-12-07 21:11:47 -08:00
this . permissionsService . currentUserCan (
PermissionAction . View ,
2023-04-09 16:17:48 -07:00
PermissionType . Note
2022-12-07 21:11:47 -08:00
)
2022-11-11 14:32:18 -08:00
)
2022-08-07 15:05:58 -07:00
}
2022-12-07 15:46:52 -08:00
2024-04-23 08:16:28 -07:00
get historyEnabled ( ) : boolean {
return (
this . settings . get ( SETTINGS_KEYS . AUDITLOG_ENABLED ) &&
this . userIsOwner &&
this . permissionsService . currentUserCan (
PermissionAction . View ,
PermissionType . History
)
)
}
2023-12-19 22:36:35 -08:00
notesUpdated ( notes : DocumentNote [ ] ) {
2023-03-17 16:36:08 -07:00
this . document . notes = notes
2023-03-16 00:42:24 -07:00
this . openDocumentService . refreshDocument ( this . documentId )
2025-07-23 22:07:13 -07:00
this . savedViewService . maybeRefreshDocumentCounts ( )
2023-03-16 00:42:24 -07:00
}
2022-12-08 11:19:52 -08:00
get userIsOwner ( ) : boolean {
2023-12-19 22:36:35 -08:00
let doc : Document = Object . assign ( { } , this . document )
2022-12-09 10:04:39 -08:00
// dont disable while editing
2024-01-17 09:44:04 -08:00
if (
this . document &&
this . store ? . value . permissions_form ? . hasOwnProperty ( 'owner' )
) {
doc . owner = this . store . value . permissions_form . owner
2022-12-09 10:04:39 -08:00
}
return ! this . document || this . permissionsService . currentUserOwnsObject ( doc )
2022-12-08 11:19:52 -08:00
}
2022-12-07 15:46:52 -08:00
get userCanEdit ( ) : boolean {
2023-12-19 22:36:35 -08:00
let doc : Document = Object . assign ( { } , this . document )
2022-12-09 10:04:39 -08:00
// dont disable while editing
2024-01-17 09:44:04 -08:00
if (
this . document &&
this . store ? . value . permissions_form ? . hasOwnProperty ( 'owner' )
) {
doc . owner = this . store . value . permissions_form . owner
2022-12-09 10:04:39 -08:00
}
2022-12-07 15:46:52 -08:00
return (
! this . document ||
2024-10-17 14:31:24 -07:00
( this . permissionsService . currentUserCan (
2022-12-07 15:46:52 -08:00
PermissionAction . Change ,
2024-10-17 14:31:24 -07:00
PermissionType . Document
) &&
this . permissionsService . currentUserHasObjectPermissions (
PermissionAction . Change ,
doc
) )
2022-12-07 15:46:52 -08:00
)
}
2023-05-27 23:16:33 -07:00
2024-12-11 01:05:23 -08:00
get userCanAdd ( ) : boolean {
return this . permissionsService . currentUserCan (
PermissionAction . Add ,
PermissionType . Document
)
}
2024-05-22 16:15:58 -07:00
filterDocuments ( items : ObjectWithId [ ] | NgbDateStruct [ ] , type ? : DataType ) {
2023-05-27 23:16:33 -07:00
const filterRules : FilterRule [ ] = items . flatMap ( ( i ) = > {
if ( i . hasOwnProperty ( 'year' ) ) {
const isoDateAdapter = new ISODateAdapter ( )
const dateAfter : Date = new Date ( isoDateAdapter . toModel ( i ) )
dateAfter . setDate ( dateAfter . getDate ( ) - 1 )
const dateBefore : Date = new Date ( isoDateAdapter . toModel ( i ) )
dateBefore . setDate ( dateBefore . getDate ( ) + 1 )
// Created Date
return [
{
rule_type : FILTER_CREATED_AFTER ,
value : dateAfter.toISOString ( ) . substring ( 0 , 10 ) ,
} ,
{
rule_type : FILTER_CREATED_BEFORE ,
value : dateBefore.toISOString ( ) . substring ( 0 , 10 ) ,
} ,
]
2024-05-22 16:15:58 -07:00
}
switch ( type ) {
case DataType . Correspondent :
return {
rule_type : FILTER_CORRESPONDENT ,
value : ( i as Correspondent ) . id . toString ( ) ,
}
case DataType . DocumentType :
return {
rule_type : FILTER_DOCUMENT_TYPE ,
value : ( i as DocumentType ) . id . toString ( ) ,
}
case DataType . StoragePath :
return {
rule_type : FILTER_STORAGE_PATH ,
value : ( i as StoragePath ) . id . toString ( ) ,
}
case DataType . Tag :
return {
rule_type : FILTER_HAS_TAGS_ALL ,
value : ( i as Tag ) . id . toString ( ) ,
}
2023-05-27 23:16:33 -07:00
}
} )
this . documentListViewService . quickFilter ( filterRules )
}
2023-11-05 17:26:51 -08:00
private getCustomFields() {
this . customFieldsService
. listAll ( )
. pipe ( first ( ) , takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( result ) = > ( this . customFields = result . results ) )
}
public refreshCustomFields() {
this . customFieldsService . clearCache ( )
this . getCustomFields ( )
}
public getCustomFieldFromInstance (
2023-12-19 22:36:35 -08:00
instance : CustomFieldInstance
) : CustomField {
2023-11-05 17:26:51 -08:00
return this . customFields ? . find ( ( f ) = > f . id === instance . field )
}
public getCustomFieldError ( index : number ) {
const fieldError = this . error ? . custom_fields ? . [ index ]
return fieldError ? . [ 'non_field_errors' ] ? ? fieldError ? . [ 'value' ]
}
private updateFormForCustomFields ( emitEvent : boolean = false ) {
this . customFieldFormFields . clear ( { emitEvent : false } )
this . document . custom_fields ? . forEach ( ( fieldInstance ) = > {
this . customFieldFormFields . push (
new FormGroup ( {
2025-04-02 10:00:25 -07:00
field : new FormControl ( fieldInstance . field ) ,
2023-11-05 17:26:51 -08:00
value : new FormControl ( fieldInstance . value ) ,
} ) ,
{ emitEvent }
)
} )
}
2023-12-19 22:36:35 -08:00
public addField ( field : CustomField ) {
2023-11-05 17:26:51 -08:00
this . document . custom_fields . push ( {
field : field.id ,
value : null ,
document : this . documentId ,
created : new Date ( ) ,
} )
this . updateFormForCustomFields ( true )
2025-04-22 12:58:28 -07:00
this . documentForm . get ( 'custom_fields' ) . markAsDirty ( )
this . documentForm . updateValueAndValidity ( )
2023-11-05 17:26:51 -08:00
}
2023-12-19 22:36:35 -08:00
public removeField ( fieldInstance : CustomFieldInstance ) {
2023-11-05 17:26:51 -08:00
this . document . custom_fields . splice (
this . document . custom_fields . indexOf ( fieldInstance ) ,
1
)
this . updateFormForCustomFields ( true )
2025-04-22 12:58:28 -07:00
this . documentForm . get ( 'custom_fields' ) . markAsDirty ( )
2023-11-05 17:26:51 -08:00
this . documentForm . updateValueAndValidity ( )
}
2024-03-25 18:41:24 -07:00
splitDocument() {
let modal = this . modalService . open ( SplitConfirmDialogComponent , {
backdrop : 'static' ,
2024-11-29 22:58:59 -08:00
size : 'lg' ,
2024-03-25 18:41:24 -07:00
} )
modal . componentInstance . title = $localize ` Split confirm `
modal . componentInstance . messageBold = $localize ` This operation will split the selected document(s) into new documents. `
modal . componentInstance . btnCaption = $localize ` Proceed `
modal . componentInstance . documentID = this . document . id
modal . componentInstance . confirmClicked
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( ) = > {
modal . componentInstance . buttonsEnabled = false
this . documentsService
. bulkEdit ( [ this . document . id ] , 'split' , {
pages : modal.componentInstance.pagesString ,
2024-06-08 18:56:25 +02:00
delete_originals : modal.componentInstance.deleteOriginal ,
2024-03-25 18:41:24 -07:00
} )
. pipe ( first ( ) , takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( {
next : ( ) = > {
this . toastService . showInfo (
2025-02-01 21:57:57 -08:00
$localize ` Split operation for " ${ this . document . title } " will begin in the background. `
2024-03-25 18:41:24 -07:00
)
modal . close ( )
} ,
error : ( error ) = > {
if ( modal ) {
modal . componentInstance . buttonsEnabled = true
}
this . toastService . showError (
$localize ` Error executing split operation ` ,
error
)
} ,
} )
} )
}
rotateDocument() {
let modal = this . modalService . open ( RotateConfirmDialogComponent , {
backdrop : 'static' ,
2024-11-29 22:58:59 -08:00
size : 'lg' ,
2024-03-25 18:41:24 -07:00
} )
modal . componentInstance . title = $localize ` Rotate confirm `
2024-04-07 12:41:08 -07:00
modal . componentInstance . messageBold = $localize ` This operation will permanently rotate the original version of the current document. `
2024-03-25 18:41:24 -07:00
modal . componentInstance . btnCaption = $localize ` Proceed `
modal . componentInstance . documentID = this . document . id
modal . componentInstance . showPDFNote = false
modal . componentInstance . confirmClicked
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( ) = > {
modal . componentInstance . buttonsEnabled = false
this . documentsService
. bulkEdit ( [ this . document . id ] , 'rotate' , {
degrees : modal.componentInstance.degrees ,
} )
. pipe ( first ( ) , takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( {
next : ( ) = > {
this . toastService . show ( {
2025-02-01 21:57:57 -08:00
content : $localize ` Rotation of " ${ this . document . title } " will begin in the background. Close and re-open the document after the operation has completed to see the changes. ` ,
2024-03-25 18:41:24 -07:00
delay : 8000 ,
action : this.close.bind ( this ) ,
actionName : $localize ` Close ` ,
} )
modal . close ( )
} ,
error : ( error ) = > {
if ( modal ) {
modal . componentInstance . buttonsEnabled = true
}
this . toastService . showError (
$localize ` Error executing rotate operation ` ,
error
)
} ,
} )
} )
}
2024-05-22 16:01:15 -07:00
deletePages() {
let modal = this . modalService . open ( DeletePagesConfirmDialogComponent , {
backdrop : 'static' ,
} )
modal . componentInstance . title = $localize ` Delete pages confirm `
modal . componentInstance . messageBold = $localize ` This operation will permanently delete the selected pages from the original document. `
modal . componentInstance . btnCaption = $localize ` Proceed `
modal . componentInstance . documentID = this . document . id
modal . componentInstance . confirmClicked
. pipe ( takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( ( ) = > {
modal . componentInstance . buttonsEnabled = false
this . documentsService
. bulkEdit ( [ this . document . id ] , 'delete_pages' , {
pages : modal.componentInstance.pages ,
} )
. pipe ( first ( ) , takeUntil ( this . unsubscribeNotifier ) )
. subscribe ( {
next : ( ) = > {
this . toastService . showInfo (
2025-02-01 21:57:57 -08:00
$localize ` Delete pages operation for " ${ this . document . title } " will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes. `
2024-05-22 16:01:15 -07:00
)
modal . close ( )
} ,
error : ( error ) = > {
if ( modal ) {
modal . componentInstance . buttonsEnabled = true
}
this . toastService . showError (
$localize ` Error executing delete pages operation ` ,
error
)
} ,
} )
} )
}
2024-12-01 11:46:19 -08:00
2025-02-21 08:44:03 -08:00
public openShareLinks() {
const modal = this . modalService . open ( ShareLinksDialogComponent )
modal . componentInstance . documentId = this . document . id
modal . componentInstance . hasArchiveVersion =
! ! this . document ? . archived_file_name
}
get emailEnabled ( ) : boolean {
return this . settings . get ( SETTINGS_KEYS . EMAIL_ENABLED )
}
public openEmailDocument() {
const modal = this . modalService . open ( EmailDocumentDialogComponent , {
backdrop : 'static' ,
} )
modal . componentInstance . documentId = this . document . id
modal . componentInstance . hasArchiveVersion =
! ! this . document ? . archived_file_name
}
2024-12-01 11:46:19 -08:00
private tryRenderTiff() {
this . http . get ( this . previewUrl , { responseType : 'arraybuffer' } ) . subscribe ( {
next : ( res ) = > {
/* istanbul ignore next */
try {
// See UTIF.js > _imgLoaded
const tiffIfds : any [ ] = UTIF . decode ( res )
var vsns = tiffIfds ,
ma = 0 ,
page = vsns [ 0 ]
if ( tiffIfds [ 0 ] . subIFD ) vsns = vsns . concat ( tiffIfds [ 0 ] . subIFD )
for ( var i = 0 ; i < vsns . length ; i ++ ) {
var img = vsns [ i ]
if ( img [ 't258' ] == null || img [ 't258' ] . length < 3 ) continue
var ar = img [ 't256' ] * img [ 't257' ]
if ( ar > ma ) {
ma = ar
page = img
}
}
UTIF . decodeImage ( res , page , tiffIfds )
const rgba = UTIF . toRGBA8 ( page )
const { width : w , height : h } = page
var cnv = document . createElement ( 'canvas' )
cnv . width = w
cnv . height = h
var ctx = cnv . getContext ( '2d' ) ,
imgd = ctx . createImageData ( w , h )
for ( var i = 0 ; i < rgba . length ; i ++ ) imgd . data [ i ] = rgba [ i ]
ctx . putImageData ( imgd , 0 , 0 )
this . tiffURL = cnv . toDataURL ( )
} catch ( err ) {
this . tiffError = $localize ` An error occurred loading tiff: ${ err . toString ( ) } `
}
} ,
error : ( err ) = > {
this . tiffError = $localize ` An error occurred loading tiff: ${ err . toString ( ) } `
} ,
} )
}
2020-10-27 01:10:18 +01:00
}