Merge pull request #2904 from paperless-ngx/feature-improve-comments-ui

Enhancement: rename comments to notes and improve notes UI
This commit is contained in:
shamoon
2023-03-19 14:34:49 -07:00
committed by GitHub
49 changed files with 740 additions and 514 deletions

View File

@@ -1,104 +0,0 @@
import { Component, Input } from '@angular/core'
import { DocumentCommentsService } from 'src/app/services/rest/document-comments.service'
import { PaperlessDocumentComment } from 'src/app/data/paperless-document-comment'
import { FormControl, FormGroup } from '@angular/forms'
import { first } from 'rxjs/operators'
import { ToastService } from 'src/app/services/toast.service'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
@Component({
selector: 'app-document-comments',
templateUrl: './document-comments.component.html',
styleUrls: ['./document-comments.component.scss'],
})
export class DocumentCommentsComponent extends ComponentWithPermissions {
commentForm: FormGroup = new FormGroup({
newComment: new FormControl(''),
})
networkActive = false
comments: PaperlessDocumentComment[] = []
newCommentError: boolean = false
private _documentId: number
@Input()
set documentId(id: number) {
if (id != this._documentId) {
this._documentId = id
this.update()
}
}
constructor(
private commentsService: DocumentCommentsService,
private toastService: ToastService
) {
super()
}
update(): void {
this.networkActive = true
this.commentsService
.getComments(this._documentId)
.pipe(first())
.subscribe((comments) => {
this.comments = comments
this.networkActive = false
})
}
addComment() {
const comment: string = this.commentForm
.get('newComment')
.value.toString()
.trim()
if (comment.length == 0) {
this.newCommentError = true
return
}
this.newCommentError = false
this.networkActive = true
this.commentsService.addComment(this._documentId, comment).subscribe({
next: (result) => {
this.comments = result
this.commentForm.get('newComment').reset()
this.networkActive = false
},
error: (e) => {
this.networkActive = false
this.toastService.showError(
$localize`Error saving comment: ${e.toString()}`
)
},
})
}
deleteComment(commentId: number) {
this.commentsService.deleteComment(this._documentId, commentId).subscribe({
next: (result) => {
this.comments = result
this.networkActive = false
},
error: (e) => {
this.networkActive = false
this.toastService.showError(
$localize`Error deleting comment: ${e.toString()}`
)
},
})
}
displayName(comment: PaperlessDocumentComment): string {
if (!comment.user) return ''
let nameComponents = []
if (comment.user.first_name) nameComponents.unshift(comment.user.first_name)
if (comment.user.last_name) nameComponents.unshift(comment.user.last_name)
if (comment.user.username) {
if (nameComponents.length > 0)
nameComponents.push(`(${comment.user.username})`)
else nameComponents.push(comment.user.username)
}
return nameComponents.join(' ')
}
}

View File

@@ -67,8 +67,8 @@
<form [formGroup]='documentForm' (ngSubmit)="save()">
<ul ngbNav #nav="ngbNav" class="nav-tabs">
<li [ngbNavItem]="1">
<ul ngbNav #nav="ngbNav" class="nav-tabs" (navChange)="onNavChange($event)" [(activeId)]="activeNavID">
<li [ngbNavItem]="DocumentDetailNavIDs.Details">
<a ngbNavLink i18n>Details</a>
<ng-template ngbNavContent>
@@ -87,7 +87,7 @@
</ng-template>
</li>
<li [ngbNavItem]="2">
<li [ngbNavItem]="DocumentDetailNavIDs.Content">
<a ngbNavLink i18n>Content</a>
<ng-template ngbNavContent>
<div class="mb-3">
@@ -96,7 +96,7 @@
</ng-template>
</li>
<li [ngbNavItem]="3">
<li [ngbNavItem]="DocumentDetailNavIDs.Metadata">
<a ngbNavLink i18n>Metadata</a>
<ng-template ngbNavContent>
@@ -147,7 +147,7 @@
</ng-template>
</li>
<li [ngbNavItem]="4" class="d-md-none">
<li [ngbNavItem]="DocumentDetailNavIDs.Preview" class="d-md-none">
<a ngbNavLink i18n>Preview</a>
<ng-template ngbNavContent *ngIf="!pdfPreview.offsetParent">
<div class="position-relative">
@@ -171,14 +171,14 @@
</ng-template>
</li>
<li [ngbNavItem]="5" *ngIf="commentsEnabled">
<a ngbNavLink i18n>Comments</a>
<li [ngbNavItem]="DocumentDetailNavIDs.Notes" *ngIf="notesEnabled">
<a ngbNavLink i18n>Notes <span *ngIf="document?.notes.length" class="badge text-bg-secondary ms-1">{{document.notes.length}}</span></a>
<ng-template ngbNavContent>
<app-document-comments [documentId]="documentId"></app-document-comments>
<app-document-notes [documentId]="documentId" [notes]="document?.notes" (updated)="notesUpdated($event)"></app-document-notes>
</ng-template>
</li>
<li [ngbNavItem]="6" *appIfOwner="document">
<li [ngbNavItem]="DocumentDetailNavIDs.Permissions" *appIfOwner="document">
<a ngbNavLink i18n>Permissions</a>
<ng-template ngbNavContent>
<div class="mb-3">

View File

@@ -1,7 +1,7 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'
import { FormControl, FormGroup } from '@angular/forms'
import { ActivatedRoute, Router } from '@angular/router'
import { NgbModal, NgbNav } from '@ng-bootstrap/ng-bootstrap'
import { NgbModal, NgbNav, NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
import { PaperlessDocument } from 'src/app/data/paperless-document'
import { PaperlessDocumentMetadata } from 'src/app/data/paperless-document-metadata'
@@ -42,6 +42,16 @@ import {
} from 'src/app/services/permissions.service'
import { PaperlessUser } from 'src/app/data/paperless-user'
import { UserService } from 'src/app/services/rest/user.service'
import { PaperlessDocumentNote } from 'src/app/data/paperless-document-note'
enum DocumentDetailNavIDs {
Details = 1,
Content = 2,
Metadata = 3,
Preview = 4,
Notes = 5,
Permissions = 6,
}
@Component({
selector: 'app-document-detail',
@@ -117,6 +127,8 @@ export class DocumentDetailComponent
PermissionAction = PermissionAction
PermissionType = PermissionType
DocumentDetailNavIDs = DocumentDetailNavIDs
activeNavID: number
constructor(
private documentsService: DocumentService,
@@ -282,6 +294,18 @@ export class DocumentDetailComponent
this.router.navigate(['404'])
},
})
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]
}
}
})
}
ngOnDestroy(): void {
@@ -289,6 +313,18 @@ export class DocumentDetailComponent
this.unsubscribeNotifier.complete()
}
onNavChange(navChangeEvent: NgbNavChangeEvent) {
const [foundNavIDkey] = Object.entries(DocumentDetailNavIDs).find(
([, navIDValue]) => navIDValue == navChangeEvent.nextId
)
if (foundNavIDkey)
this.router.navigate([
'documents',
this.documentId,
foundNavIDkey.toLowerCase(),
])
}
updateComponent(doc: PaperlessDocument) {
this.document = doc
this.requiresPassword = false
@@ -622,9 +658,9 @@ export class DocumentDetailComponent
}
}
get commentsEnabled(): boolean {
get notesEnabled(): boolean {
return (
this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED) &&
this.settings.get(SETTINGS_KEYS.NOTES_ENABLED) &&
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.Document
@@ -632,6 +668,11 @@ export class DocumentDetailComponent
)
}
notesUpdated(notes: PaperlessDocumentNote[]) {
this.document.notes = notes
this.openDocumentService.refreshDocument(this.documentId)
}
get userIsOwner(): boolean {
let doc: PaperlessDocument = Object.assign({}, this.document)
// dont disable while editing

View File

@@ -26,7 +26,7 @@
</div>
<p class="card-text">
<span *ngIf="document.__search_hit__ && document.__search_hit__.highlights" [innerHtml]="document.__search_hit__.highlights"></span>
<span *ngFor="let highlight of searchCommentHighlights" class="d-block">
<span *ngFor="let highlight of searchNoteHighlights" class="d-block">
<svg width="1em" height="1em" fill="currentColor" class="me-2">
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
</svg>
@@ -65,24 +65,31 @@
</a>
</div>
<div class="list-group list-group-horizontal border-0 card-info ms-md-auto mt-2 mt-md-0">
<button *ngIf="notesEnabled && document.notes.length" routerLink="/documents/{{document.id}}/notes" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2" title="View notes" i18n-title>
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
</svg>
<small i18n>{{document.notes.length}} Notes</small>
</button>
<button *ngIf="document.document_type" type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2" title="Filter by document type" i18n-title
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
<svg class="metadata-icon me-2 text-muted bi bi-file-earmark" viewBox="0 0 16 16" fill="currentColor">
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#file-earmark"/>
</svg>
<small>{{(document.document_type$ | async)?.name}}</small>
</button>
<button *ngIf="document.storage_path" type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2" title="Filter by storage path" i18n-title
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
<svg class="metadata-icon me-2 text-muted bi bi-folder" viewBox="0 0 16 16" fill="currentColor">
<path d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/>
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#archive"/>
</svg>
<small>{{(document.storage_path$ | async)?.name}}</small>
</button>
<div *ngIf="document.archive_serial_number" class="list-group-item me-2 bg-light text-dark p-1 border-0">
<svg class="metadata-icon me-2 text-muted bi bi-upc-scan" viewBox="0 0 16 16" fill="currentColor">
<path d="M1.5 1a.5.5 0 0 0-.5.5v3a.5.5 0 0 1-1 0v-3A1.5 1.5 0 0 1 1.5 0h3a.5.5 0 0 1 0 1h-3zM11 .5a.5.5 0 0 1 .5-.5h3A1.5 1.5 0 0 1 16 1.5v3a.5.5 0 0 1-1 0v-3a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 1-.5-.5zM.5 11a.5.5 0 0 1 .5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 1 0 1h-3A1.5 1.5 0 0 1 0 14.5v-3a.5.5 0 0 1 .5-.5zm15 0a.5.5 0 0 1 .5.5v3a1.5 1.5 0 0 1-1.5 1.5h-3a.5.5 0 0 1 0-1h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 1 .5-.5zM3 4.5a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7zm2 0a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7zm2 0a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7zm2 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-7zm3 0a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7z"/>
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#upc-scan"/>
</svg>
<small>#{{document.archive_serial_number}}</small>
</div>
@@ -94,9 +101,8 @@
</div>
</ng-template>
<div class="list-group-item bg-light text-dark p-1 border-0" [ngbTooltip]="dateTooltip">
<svg class="metadata-icon me-2 text-muted bi bi-calendar-event" viewBox="0 0 16 16" fill="currentColor">
<path d="M11 6.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1z"/>
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#calendar-event"/>
</svg>
<small>{{document.created_date | customDate:'mediumDate'}}</small>
</div>

View File

@@ -73,12 +73,6 @@
}
}
.metadata-icon {
width: 0.9rem;
height: 0.9rem;
padding: 0.05rem;
}
.search-score {
padding-top: 0.35rem !important;
}

View File

@@ -73,16 +73,14 @@ export class DocumentCardLargeComponent extends ComponentWithPermissions {
}
}
get searchCommentHighlights() {
get searchNoteHighlights() {
let highlights = []
if (
this.document['__search_hit__'] &&
this.document['__search_hit__'].comment_highlights
this.document['__search_hit__'].note_highlights
) {
// only show comments with a match
highlights = (
this.document['__search_hit__'].comment_highlights as string
)
// only show notes with a match
highlights = (this.document['__search_hit__'].note_highlights as string)
.split(',')
.filter((higlight) => higlight.includes('<span'))
}
@@ -136,4 +134,8 @@ export class DocumentCardLargeComponent extends ComponentWithPermissions {
(this.document.content.length > 500 ? '...' : '')
)
}
get notesEnabled(): boolean {
return this.settingsService.get(SETTINGS_KEYS.NOTES_ENABLED)
}
}

View File

@@ -13,12 +13,20 @@
<div class="tags d-flex flex-column text-end position-absolute me-1 fs-6">
<app-tag *ngFor="let t of getTagsLimited$() | async" [tag]="t" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="true" linkTitle="Toggle tag filter" i18n-linkTitle></app-tag>
<div *ngIf="moreTags">
<span class="badge badge-secondary">+ {{moreTags}}</span>
<span class="badge text-dark">+ {{moreTags}}</span>
</div>
</div>
</div>
<div class="card-body p-2">
<a *ngIf="notesEnabled && document.notes.length" routerLink="/documents/{{document.id}}/notes" class="document-card-notes py-2 px-1">
<span class="badge rounded-pill bg-light border text-primary">
<svg class="metadata-icon ms-1 me-1" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
</svg>
{{document.notes.length}}</span>
</a>
<div class="card-body bg-light p-2">
<p class="card-text">
<ng-container *ngIf="document.correspondent">
<a title="Toggle correspondent filter" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name}}</a>:

View File

@@ -5,7 +5,7 @@
.doc-img {
object-fit: cover;
object-position: top left;
height: 175px;
height: 180px;
mix-blend-mode: multiply;
}
@@ -34,6 +34,12 @@
display: block;
}
.document-card-notes {
position: absolute;
right: 0;
top: 142px;
}
.card-selected {
border-color:var(--bs-primary);
@@ -58,12 +64,6 @@
color: var(--bs-primary);
}
}
.metadata-icon {
width: 0.9rem;
height: 0.9rem;
padding: 0.05rem;
}
}
.card-footer .btn {

View File

@@ -74,11 +74,12 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
}
getTagsLimited$() {
const limit = this.document.notes.length > 0 ? 6 : 7
return this.document.tags$.pipe(
map((tags) => {
if (tags.length > 7) {
this.moreTags = tags.length - 6
return tags.slice(0, 6)
if (tags.length > limit) {
this.moreTags = tags.length - (limit - 1)
return tags.slice(0, limit - 1)
} else {
return tags
}
@@ -110,4 +111,8 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
mouseLeaveCard() {
this.popover.close()
}
get notesEnabled(): boolean {
return this.settingsService.get(SETTINGS_KEYS.NOTES_ENABLED)
}
}

View File

@@ -139,6 +139,12 @@
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Title</th>
<th *ngIf="notesEnabled" class="d-none d-xl-table-cell"
appSortable="num_notes"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Notes</th>
<th class="d-none d-xl-table-cell"
appSortable="document_type__name"
[currentSortField]="list.sortField"
@@ -184,6 +190,15 @@
<a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
<app-tag [tag]="t" *ngFor="let t of d.tags$ | async" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></app-tag>
</td>
<td *ngIf="notesEnabled" class="d-none d-xl-table-cell">
<a *ngIf="d.notes.length" routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
<span class="badge rounded-pill bg-light border text-primary">
<svg class="metadata-icon ms-1 me-1" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
</svg>
{{d.notes.length}}</span>
</a>
</td>
<td class="d-none d-xl-table-cell">
<ng-container *ngIf="d.document_type">
<a (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type" i18n-title>{{(d.document_type$ | async)?.name}}</a>

View File

@@ -17,6 +17,7 @@ import {
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
import { PaperlessDocument } from 'src/app/data/paperless-document'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import {
SortableDirective,
SortEvent,
@@ -29,6 +30,7 @@ import {
DOCUMENT_SORT_FIELDS_FULLTEXT,
} from 'src/app/services/rest/document.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
import { FilterEditorComponent } from './filter-editor/filter-editor.component'
@@ -51,7 +53,8 @@ export class DocumentListComponent
private toastService: ToastService,
private modalService: NgbModal,
private consumerStatusService: ConsumerStatusService,
public openDocumentsService: OpenDocumentsService
public openDocumentsService: OpenDocumentsService,
private settingsService: SettingsService
) {
super()
}
@@ -289,4 +292,8 @@ export class DocumentListComponent
trackByDocumentId(index, item: PaperlessDocument) {
return item.id
}
get notesEnabled(): boolean {
return this.settingsService.get(SETTINGS_KEYS.NOTES_ENABLED)
}
}

View File

@@ -1,27 +1,28 @@
<div *ngIf="comments">
<form [formGroup]="commentForm" class="needs-validation mt-3" *appIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Comment }" novalidate>
<div *ngIf="notes">
<form [formGroup]="noteForm" class="needs-validation mt-3" *appIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Note }" novalidate>
<div class="form-group">
<textarea class="form-control form-control-sm" [class.is-invalid]="newCommentError" rows="3" formControlName="newComment" placeholder="Enter comment" i18n-placeholder required></textarea>
<textarea class="form-control form-control-sm" [class.is-invalid]="newNoteError" rows="3" formControlName="newNote" placeholder="Enter note" i18n-placeholder (keydown)="noteFormKeydown($event)" required></textarea>
<div class="invalid-feedback" i18n>
Please enter a comment.
Please enter a note.
</div>
</div>
<div class="form-group mt-2 d-flex justify-content-end align-items-center">
<div *ngIf="networkActive" class="spinner-border spinner-border-sm fw-normal me-auto" role="status"></div>
<button type="button" class="btn btn-primary btn-sm" [disabled]="networkActive" (click)="addComment()" i18n>Add comment</button>
<button type="button" class="btn btn-primary btn-sm" [disabled]="networkActive" (click)="addNote()" i18n>Add note</button>
</div>
</form>
<hr>
<div *ngFor="let comment of comments" class="card border mb-3">
<div *ngFor="let note of notes" class="card border mb-3">
<div class="card-body text-dark">
<p class="card-text">{{comment.comment}}</p>
<p class="card-text">{{note.note}}</p>
</div>
<div class="d-flex card-footer small bg-light text-primary justify-content-between align-items-center">
<span>{{displayName(comment)}} - {{ comment.created | customDate}}</span>
<button type="button" class="btn btn-link btn-sm p-0 fade" (click)="deleteComment(comment.id)" *appIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Comment }">
<span>{{displayName(note)}} - {{ note.created | customDate}}</span>
<button type="button" class="btn btn-link btn-sm p-0 fade" title="Delete note" i18n-title (click)="deleteNote(note.id)" *appIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Note }">
<svg width="13" height="13" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg>
<span class="visually-hidden" i18n>Delete note</span>
</button>
</div>
</div>

View File

@@ -0,0 +1,106 @@
import { Component, Input, Output, EventEmitter } from '@angular/core'
import { DocumentNotesService } from 'src/app/services/rest/document-notes.service'
import { PaperlessDocumentNote } from 'src/app/data/paperless-document-note'
import { FormControl, FormGroup } from '@angular/forms'
import { first } from 'rxjs/operators'
import { ToastService } from 'src/app/services/toast.service'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
import { UserService } from 'src/app/services/rest/user.service'
import { PaperlessUser } from 'src/app/data/paperless-user'
@Component({
selector: 'app-document-notes',
templateUrl: './document-notes.component.html',
styleUrls: ['./document-notes.component.scss'],
})
export class DocumentNotesComponent extends ComponentWithPermissions {
noteForm: FormGroup = new FormGroup({
newNote: new FormControl(''),
})
networkActive = false
newNoteError: boolean = false
@Input()
documentId: number
@Input()
notes: PaperlessDocumentNote[] = []
@Output()
updated: EventEmitter<PaperlessDocumentNote[]> = new EventEmitter()
users: PaperlessUser[]
constructor(
private notesService: DocumentNotesService,
private toastService: ToastService,
private usersService: UserService
) {
super()
this.usersService.listAll().subscribe({
next: (users) => {
this.users = users.results
},
})
}
addNote() {
const note: string = this.noteForm.get('newNote').value.toString().trim()
if (note.length == 0) {
this.newNoteError = true
return
}
this.newNoteError = false
this.networkActive = true
this.notesService.addNote(this.documentId, note).subscribe({
next: (result) => {
this.notes = result
this.noteForm.get('newNote').reset()
this.networkActive = false
this.updated.emit(this.notes)
},
error: (e) => {
this.networkActive = false
this.toastService.showError(
$localize`Error saving note: ${e.toString()}`
)
},
})
}
deleteNote(noteId: number) {
this.notesService.deleteNote(this.documentId, noteId).subscribe({
next: (result) => {
this.notes = result
this.networkActive = false
this.updated.emit(this.notes)
},
error: (e) => {
this.networkActive = false
this.toastService.showError(
$localize`Error deleting note: ${e.toString()}`
)
},
})
}
displayName(note: PaperlessDocumentNote): string {
if (!note.user) return ''
const user = this.users.find((u) => u.id === note.user)
if (!user) return ''
const nameComponents = []
if (user.first_name) nameComponents.unshift(user.first_name)
if (user.last_name) nameComponents.unshift(user.last_name)
if (user.username) {
if (nameComponents.length > 0) nameComponents.push(`(${user.username})`)
else nameComponents.push(user.username)
}
return nameComponents.join(' ')
}
noteFormKeydown(event: KeyboardEvent) {
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
this.addNote()
}
}
}

View File

@@ -156,11 +156,11 @@
</div>
</div>
<h4 class="mt-4" i18n>Comments</h4>
<h4 class="mt-4" i18n>Notes</h4>
<div class="row mb-3">
<div class="offset-md-3 col">
<app-input-check i18n-title title="Enable comments" formControlName="commentsEnabled"></app-input-check>
<app-input-check i18n-title title="Enable notes" formControlName="notesEnabled"></app-input-check>
</div>
</div>

View File

@@ -85,7 +85,7 @@ export class SettingsComponent
displayLanguage: new FormControl(null),
dateLocale: new FormControl(null),
dateFormat: new FormControl(null),
commentsEnabled: new FormControl(null),
notesEnabled: new FormControl(null),
updateCheckingEnabled: new FormControl(null),
notificationsConsumerNewDocument: new FormControl(null),
@@ -196,7 +196,7 @@ export class SettingsComponent
displayLanguage: this.settings.getLanguage(),
dateLocale: this.settings.get(SETTINGS_KEYS.DATE_LOCALE),
dateFormat: this.settings.get(SETTINGS_KEYS.DATE_FORMAT),
commentsEnabled: this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED),
notesEnabled: this.settings.get(SETTINGS_KEYS.NOTES_ENABLED),
updateCheckingEnabled: this.settings.get(
SETTINGS_KEYS.UPDATE_CHECKING_ENABLED
),
@@ -552,8 +552,8 @@ export class SettingsComponent
this.settingsForm.value.notificationsConsumerSuppressOnDashboard
)
this.settings.set(
SETTINGS_KEYS.COMMENTS_ENABLED,
this.settingsForm.value.commentsEnabled
SETTINGS_KEYS.NOTES_ENABLED,
this.settingsForm.value.notesEnabled
)
this.settings.set(
SETTINGS_KEYS.UPDATE_CHECKING_ENABLED,