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

@@ -17,28 +17,28 @@ describe('document-detail', () => {
req.reply({ result: 'OK' })
}).as('saveDoc')
cy.fixture('documents/1/comments.json').then((commentsJson) => {
cy.fixture('documents/1/notes.json').then((notesJson) => {
cy.intercept(
'GET',
'http://localhost:8000/api/documents/1/comments/',
'http://localhost:8000/api/documents/1/notes/',
(req) => {
req.reply(commentsJson.filter((c) => c.id != 10)) // 3
req.reply(notesJson.filter((c) => c.id != 10)) // 3
}
)
cy.intercept(
'DELETE',
'http://localhost:8000/api/documents/1/comments/?id=9',
'http://localhost:8000/api/documents/1/notes/?id=9',
(req) => {
req.reply(commentsJson.filter((c) => c.id != 9 && c.id != 10)) // 2
req.reply(notesJson.filter((c) => c.id != 9 && c.id != 10)) // 2
}
)
cy.intercept(
'POST',
'http://localhost:8000/api/documents/1/comments/',
'http://localhost:8000/api/documents/1/notes/',
(req) => {
req.reply(commentsJson) // 4
req.reply(notesJson) // 4
}
)
})
@@ -75,33 +75,40 @@ describe('document-detail', () => {
cy.get('pdf-viewer').should('be.visible')
})
it('should show a list of comments', () => {
cy.wait(1000)
.get('a')
.contains('Comments')
.click({ force: true })
.wait(1000)
cy.get('app-document-comments').find('.card').its('length').should('eq', 3)
it('should show a list of notes', () => {
cy.wait(1000).get('a').contains('Notes').click({ force: true }).wait(1000)
cy.get('app-document-notes').find('.card').its('length').should('eq', 3)
})
it('should support comment deletion', () => {
cy.wait(1000).get('a').contains('Comments').click().wait(1000)
cy.get('app-document-comments')
it('should support note deletion', () => {
cy.wait(1000).get('a').contains('Notes').click().wait(1000)
cy.get('app-document-notes')
.find('.card')
.first()
.find('button')
.click({ force: true })
.wait(500)
cy.get('app-document-comments').find('.card').its('length').should('eq', 2)
cy.get('app-document-notes').find('.card').its('length').should('eq', 2)
})
it('should support comment insertion', () => {
cy.wait(1000).get('a').contains('Comments').click().wait(1000)
cy.get('app-document-comments')
it('should support note insertion', () => {
cy.wait(1000).get('a').contains('Notes').click().wait(1000)
cy.get('app-document-notes')
.find('form textarea')
.type('Testing new comment')
.type('Testing new note')
.wait(500)
cy.get('app-document-comments').find('form button').click().wait(1500)
cy.get('app-document-comments').find('.card').its('length').should('eq', 4)
cy.get('app-document-notes').find('form button').click().wait(1500)
cy.get('app-document-notes').find('.card').its('length').should('eq', 4)
})
it('should support navigation to notes tab by url', () => {
cy.visit('/documents/1/notes')
cy.get('app-document-notes').should('exist')
})
it('should dynamically update note counts', () => {
cy.visit('/documents/1/notes')
cy.get('app-document-notes').within(() => cy.contains('Delete').click())
cy.get('ul.nav').find('li').contains('Notes').find('.badge').contains('2')
})
})

View File

@@ -1,46 +0,0 @@
[
{
"id": 10,
"comment": "Testing new comment",
"created": "2022-08-08T04:24:55.176008Z",
"user": {
"id": 1,
"username": "user2",
"first_name": "",
"last_name": ""
}
},
{
"id": 9,
"comment": "Testing one more time",
"created": "2022-02-18T04:24:55.176008Z",
"user": {
"id": 2,
"username": "user1",
"first_name": "",
"last_name": ""
}
},
{
"id": 8,
"comment": "Another comment",
"created": "2021-11-08T04:24:47.925042Z",
"user": {
"id": 2,
"username": "user33",
"first_name": "",
"last_name": ""
}
},
{
"id": 7,
"comment": "Cupcake ipsum dolor sit amet cheesecake candy cookie tiramisu. Donut chocolate chupa chups macaroon brownie halvah pie cheesecake gummies. Sweet chocolate bar candy donut gummi bears bear claw liquorice bonbon shortbread.\n\nDonut chocolate bar candy wafer wafer tiramisu. Gummies chocolate cake muffin toffee carrot cake macaroon. Toffee toffee jelly beans danish lollipop cake.",
"created": "2021-02-08T02:37:49.724132Z",
"user": {
"id": 3,
"username": "admin",
"first_name": "",
"last_name": ""
}
}
]

View File

@@ -0,0 +1,26 @@
[
{
"id": 10,
"note": "Testing new note",
"created": "2022-08-08T04:24:55.176008Z",
"user": 3
},
{
"id": 9,
"note": "Testing one more time",
"created": "2022-02-18T04:24:55.176008Z",
"user": 15
},
{
"id": 8,
"note": "Another note",
"created": "2021-11-08T04:24:47.925042Z",
"user": 3
},
{
"id": 7,
"note": "Cupcake ipsum dolor sit amet cheesecake candy cookie tiramisu. Donut chocolate chupa chups macaroon brownie halvah pie cheesecake gummies. Sweet chocolate bar candy donut gummi bears bear claw liquorice bonbon shortbread.\n\nDonut chocolate bar candy wafer wafer tiramisu. Gummies chocolate cake muffin toffee carrot cake macaroon. Toffee toffee jelly beans danish lollipop cake.",
"created": "2021-02-08T02:37:49.724132Z",
"user": 3
}
]

View File

@@ -21,7 +21,27 @@
"original_file_name": "2022-03-22 no latin title.pdf",
"archived_file_name": "2022-03-22 no latin title.pdf",
"owner": null,
"permissions": []
"permissions": [],
"notes": [
{
"id": 9,
"note": "Testing one more time",
"created": "2022-02-18T04:24:55.176008Z",
"user": 15
},
{
"id": 8,
"note": "Another note",
"created": "2021-11-08T04:24:47.925042Z",
"user": 3
},
{
"id": 7,
"note": "Cupcake ipsum dolor sit amet cheesecake candy cookie tiramisu. Donut chocolate chupa chups macaroon brownie halvah pie cheesecake gummies. Sweet chocolate bar candy donut gummi bears bear claw liquorice bonbon shortbread.\n\nDonut chocolate bar candy wafer wafer tiramisu. Gummies chocolate cake muffin toffee carrot cake macaroon. Toffee toffee jelly beans danish lollipop cake.",
"created": "2021-02-08T02:37:49.724132Z",
"user": 3
}
]
},
{
"id": 2,
@@ -39,7 +59,8 @@
"original_file_name": "2022-03-23 lorem ipsum dolor sit amet.pdf",
"archived_file_name": "2022-03-23 llorem ipsum dolor sit amet.pdf",
"owner": null,
"permissions": []
"permissions": [],
"notes": []
},
{
"id": 3,
@@ -59,7 +80,8 @@
"original_file_name": "2022-03-24 dolor.pdf",
"archived_file_name": "2022-03-24 dolor.pdf",
"owner": null,
"permissions": []
"permissions": [],
"notes": []
},
{
"id": 4,
@@ -79,7 +101,8 @@
"original_file_name": "2022-06-01 sit amet.pdf",
"archived_file_name": "2022-06-01 sit amet.pdf",
"owner": null,
"permissions": []
"permissions": [],
"notes": []
}
]
}

View File

@@ -11,10 +11,10 @@
"change_user",
"delete_user",
"view_user",
"add_comment",
"change_comment",
"delete_comment",
"view_comment"
"add_note",
"change_note",
"delete_note",
"view_note"
]
},
{
@@ -73,10 +73,10 @@
"change_task",
"delete_task",
"view_task",
"add_comment",
"change_comment",
"delete_comment",
"view_comment",
"add_note",
"change_note",
"delete_note",
"view_note",
"add_correspondent",
"change_correspondent",
"delete_correspondent",

View File

@@ -94,10 +94,10 @@
"change_task",
"delete_task",
"view_task",
"add_comment",
"change_comment",
"delete_comment",
"view_comment",
"add_note",
"change_note",
"delete_note",
"view_note",
"add_correspondent",
"change_correspondent",
"delete_correspondent",

View File

@@ -74,7 +74,7 @@
"change_task",
"delete_task",
"view_task",
"add_comment",
"add_note",
"add_frontendsettings",
"change_frontendsettings",
"delete_frontendsettings",

View File

@@ -30,7 +30,7 @@
"django_celery_results.delete_taskresult",
"paperless_mail.add_mailaccount",
"auth.change_group",
"documents.add_comment",
"documents.add_note",
"paperless_mail.delete_mailaccount",
"authtoken.delete_tokenproxy",
"guardian.delete_groupobjectpermission",
@@ -44,7 +44,7 @@
"documents.add_documenttype",
"django_q.change_success",
"documents.delete_tag",
"documents.change_comment",
"documents.change_note",
"django_q.delete_task",
"documents.add_savedviewfilterrule",
"django_q.view_task",
@@ -59,7 +59,7 @@
"documents.add_savedview",
"auth.delete_user",
"documents.view_log",
"documents.view_comment",
"documents.view_note",
"guardian.change_groupobjectpermission",
"sessions.delete_session",
"django_q.change_failure",
@@ -139,7 +139,7 @@
"django_celery_results.view_taskresult",
"contenttypes.add_contenttype",
"django_q.delete_success",
"documents.delete_comment",
"documents.delete_note",
"django_q.add_failure",
"guardian.add_userobjectpermission",
"sessions.view_session",
@@ -216,10 +216,10 @@
"change_task",
"delete_task",
"view_task",
"add_comment",
"change_comment",
"delete_comment",
"view_comment",
"add_note",
"change_note",
"delete_note",
"view_note",
"add_frontendsettings",
"change_frontendsettings",
"delete_frontendsettings",
@@ -256,7 +256,7 @@
"django_celery_results.delete_taskresult",
"authtoken.change_token",
"auth.change_group",
"documents.add_comment",
"documents.add_note",
"authtoken.delete_tokenproxy",
"documents.view_documenttype",
"contenttypes.delete_contenttype",
@@ -285,7 +285,7 @@
"django_q.change_task",
"sessions.add_session",
"documents.change_taskattributes",
"documents.change_comment",
"documents.change_note",
"django_q.delete_task",
"django_q.delete_ormq",
"auth.change_permission",
@@ -311,7 +311,7 @@
"documents.view_document",
"documents.add_savedview",
"django_q.view_failure",
"documents.view_comment",
"documents.view_note",
"documents.view_log",
"documents.add_log",
"documents.change_savedview",
@@ -324,7 +324,7 @@
"django_celery_results.view_taskresult",
"contenttypes.add_contenttype",
"django_q.delete_success",
"documents.delete_comment",
"documents.delete_note",
"django_q.add_failure",
"sessions.view_session",
"contenttypes.view_contenttype",
@@ -373,7 +373,7 @@
"django_celery_results.delete_taskresult",
"authtoken.change_token",
"auth.change_group",
"documents.add_comment",
"documents.add_note",
"authtoken.delete_tokenproxy",
"documents.view_documenttype",
"contenttypes.delete_contenttype",
@@ -402,7 +402,7 @@
"django_q.change_task",
"sessions.add_session",
"documents.change_taskattributes",
"documents.change_comment",
"documents.change_note",
"django_q.delete_task",
"django_q.delete_ormq",
"auth.change_permission",
@@ -429,7 +429,7 @@
"documents.view_document",
"documents.add_savedview",
"django_q.view_failure",
"documents.view_comment",
"documents.view_note",
"documents.view_log",
"auth.delete_user",
"documents.add_log",
@@ -443,7 +443,7 @@
"django_celery_results.view_taskresult",
"contenttypes.add_contenttype",
"django_q.delete_success",
"documents.delete_comment",
"documents.delete_note",
"django_q.add_failure",
"sessions.view_session",
"contenttypes.view_contenttype",

View File

@@ -492,7 +492,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">94</context>
<context context-type="linenumber">97</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
@@ -1826,7 +1826,7 @@
<source>Not assigned</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts</context>
<context context-type="linenumber">321</context>
<context context-type="linenumber">335</context>
</context-group>
<note priority="1" from="description">Filter drop down element to filter for documents with no correspondent/type/tag assigned</note>
</trans-unit>
@@ -1922,7 +1922,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">70</context>
<context context-type="linenumber">78</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
@@ -2157,7 +2157,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">159</context>
<context context-type="linenumber">165</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
@@ -2328,41 +2328,6 @@
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
<trans-unit id="2122666445936087317" datatype="html">
<source>Enter comment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
</trans-unit>
<trans-unit id="4025397324401332794" datatype="html">
<source> Please enter a comment. </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.html</context>
<context context-type="linenumber">5,7</context>
</context-group>
</trans-unit>
<trans-unit id="2337485514607640701" datatype="html">
<source>Add comment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.html</context>
<context context-type="linenumber">11</context>
</context-group>
</trans-unit>
<trans-unit id="5438997040668245251" datatype="html">
<source>Error saving comment: <x id="PH" equiv-text="e.toString()"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.ts</context>
<context context-type="linenumber">71</context>
</context-group>
</trans-unit>
<trans-unit id="7593210124183303626" datatype="html">
<source>Error deleting comment: <x id="PH" equiv-text="e.toString()"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.ts</context>
<context context-type="linenumber">86</context>
</context-group>
</trans-unit>
<trans-unit id="1407560924967345762" datatype="html">
<source>Page</source>
<context-group purpose="location">
@@ -2397,7 +2362,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">86</context>
<context context-type="linenumber">94</context>
</context-group>
</trans-unit>
<trans-unit id="8659635229098859487" datatype="html">
@@ -2503,7 +2468,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">147</context>
<context context-type="linenumber">153</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
@@ -2526,7 +2491,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">153</context>
<context context-type="linenumber">159</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
@@ -2653,15 +2618,11 @@
<context context-type="linenumber">215</context>
</context-group>
</trans-unit>
<trans-unit id="3807699453257291879" datatype="html">
<source>Comments</source>
<trans-unit id="8460995830263484763" datatype="html">
<source>Notes <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span *ngIf=&quot;document?.notes.length&quot; class=&quot;badge text-bg-secondary ms-1&quot;&gt;"/><x id="INTERPOLATION" equiv-text="ngth}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/a&gt;"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">175</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">159</context>
<context context-type="linenumber">175,176</context>
</context-group>
</trans-unit>
<trans-unit id="3823219296477075982" datatype="html">
@@ -2682,32 +2643,32 @@
<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">305</context>
<context context-type="linenumber">341</context>
</context-group>
</trans-unit>
<trans-unit id="2374084708811774419" 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">325</context>
<context context-type="linenumber">361</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">439</context>
<context context-type="linenumber">475</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">483</context>
<context context-type="linenumber">519</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">512</context>
<context context-type="linenumber">548</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
@@ -2718,35 +2679,35 @@
<source>Do you really want to delete document &quot;<x id="PH" equiv-text="this.document.title"/>&quot;?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">513</context>
<context context-type="linenumber">549</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">514</context>
<context context-type="linenumber">550</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">516</context>
<context context-type="linenumber">552</context>
</context-group>
</trans-unit>
<trans-unit id="1844801255494293730" datatype="html">
<source>Error deleting document: <x id="PH" equiv-text="JSON.stringify(error)"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">532</context>
<context context-type="linenumber">568</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">552</context>
<context context-type="linenumber">588</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -2757,14 +2718,14 @@
<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">553</context>
<context context-type="linenumber">589</context>
</context-group>
</trans-unit>
<trans-unit id="5641451190833696892" datatype="html">
<source>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">554</context>
<context context-type="linenumber">590</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -2795,7 +2756,7 @@
<source>Proceed</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">556</context>
<context context-type="linenumber">592</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@@ -2822,7 +2783,7 @@
<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">564</context>
<context context-type="linenumber">600</context>
</context-group>
</trans-unit>
<trans-unit id="8008978164775353960" datatype="html">
@@ -2831,7 +2792,7 @@
)"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">575,577</context>
<context context-type="linenumber">611,613</context>
</context-group>
</trans-unit>
<trans-unit id="6857598786757174736" datatype="html">
@@ -3152,7 +3113,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">180</context>
<context context-type="linenumber">186</context>
</context-group>
</trans-unit>
<trans-unit id="2784168796433474565" datatype="html">
@@ -3163,69 +3124,83 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">185</context>
<context context-type="linenumber">191</context>
</context-group>
</trans-unit>
<trans-unit id="106713086593101376" datatype="html">
<source>View notes</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">70</context>
</context-group>
</trans-unit>
<trans-unit id="8778002102373462277" datatype="html">
<source><x id="INTERPOLATION" equiv-text="otes.length}}"/> Notes</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">74</context>
</context-group>
</trans-unit>
<trans-unit id="78870852467682010" datatype="html">
<source>Filter by document type</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">69</context>
<context context-type="linenumber">76</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">189</context>
<context context-type="linenumber">204</context>
</context-group>
</trans-unit>
<trans-unit id="157572966557284263" datatype="html">
<source>Filter by storage path</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">76</context>
<context context-type="linenumber">83</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">194</context>
<context context-type="linenumber">209</context>
</context-group>
</trans-unit>
<trans-unit id="3727324658595204357" datatype="html">
<source>Created: <x id="INTERPOLATION" equiv-text="{{ document.created | customDate }}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">91,92</context>
<context context-type="linenumber">98,99</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">48,49</context>
<context context-type="linenumber">56,57</context>
</context-group>
</trans-unit>
<trans-unit id="2030261243264601523" datatype="html">
<source>Added: <x id="INTERPOLATION" equiv-text="{{ document.added | customDate }}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">92,93</context>
<context context-type="linenumber">99,100</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">49,50</context>
<context context-type="linenumber">57,58</context>
</context-group>
</trans-unit>
<trans-unit id="4235671847487610290" datatype="html">
<source>Modified: <x id="INTERPOLATION" equiv-text="{{ document.modified | customDate }}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">93,94</context>
<context context-type="linenumber">100,101</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">50,51</context>
<context context-type="linenumber">58,59</context>
</context-group>
</trans-unit>
<trans-unit id="2332107018974972998" datatype="html">
<source>Score:</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">104</context>
<context context-type="linenumber">110</context>
</context-group>
</trans-unit>
<trans-unit id="3661756380991326939" datatype="html">
@@ -3239,21 +3214,21 @@
<source>Toggle correspondent filter</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">24</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="5319701482646590642" datatype="html">
<source>Toggle document type filter</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">31</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="8950368321707344185" datatype="html">
<source>Toggle storage path filter</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">46</context>
</context-group>
</trans-unit>
<trans-unit id="5145213156408463657" datatype="html">
@@ -3352,11 +3327,26 @@
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit id="8104421162933956065" datatype="html">
<source>Notes</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">147</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">159</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit id="231679111972850796" datatype="html">
<source>Added</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">165</context>
<context context-type="linenumber">171</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
@@ -3371,21 +3361,21 @@
<source>Edit document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">184</context>
<context context-type="linenumber">190</context>
</context-group>
</trans-unit>
<trans-unit id="2155249406916744630" datatype="html">
<source>View &quot;<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>&quot; saved successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">202</context>
<context context-type="linenumber">205</context>
</context-group>
</trans-unit>
<trans-unit id="6837554170707123455" datatype="html">
<source>View &quot;<x id="PH" equiv-text="savedView.name"/>&quot; created successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">243</context>
<context context-type="linenumber">246</context>
</context-group>
</trans-unit>
<trans-unit id="6849725902312323996" datatype="html">
@@ -3550,6 +3540,52 @@
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="1044349881182559852" datatype="html">
<source>Enter note</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-notes/document-notes.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
</trans-unit>
<trans-unit id="7770536883443596194" datatype="html">
<source> Please enter a note. </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-notes/document-notes.component.html</context>
<context context-type="linenumber">5,7</context>
</context-group>
</trans-unit>
<trans-unit id="8433732438274024544" datatype="html">
<source>Add note</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-notes/document-notes.component.html</context>
<context context-type="linenumber">11</context>
</context-group>
</trans-unit>
<trans-unit id="8428006099054244235" datatype="html">
<source>Delete note</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-notes/document-notes.component.html</context>
<context context-type="linenumber">21</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-notes/document-notes.component.html</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit id="207390237682956115" datatype="html">
<source>Error saving note: <x id="PH" equiv-text="e.toString()"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-notes/document-notes.component.ts</context>
<context context-type="linenumber">65</context>
</context-group>
</trans-unit>
<trans-unit id="5682285129543775369" datatype="html">
<source>Error deleting note: <x id="PH" equiv-text="e.toString()"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-notes/document-notes.component.ts</context>
<context context-type="linenumber">81</context>
</context-group>
</trans-unit>
<trans-unit id="6316128875819022658" datatype="html">
<source>correspondent</source>
<context-group purpose="location">
@@ -4018,8 +4054,8 @@
<context context-type="linenumber">155</context>
</context-group>
</trans-unit>
<trans-unit id="4666858503087488647" datatype="html">
<source>Enable comments</source>
<trans-unit id="293524471897878391" datatype="html">
<source>Enable notes</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">163</context>
@@ -4670,11 +4706,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">103</context>
<context context-type="linenumber">104</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">130</context>
<context context-type="linenumber">131</context>
</context-group>
</trans-unit>
<trans-unit id="2573823578527613511" datatype="html">
@@ -4685,7 +4721,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">131</context>
<context context-type="linenumber">132</context>
</context-group>
</trans-unit>
<trans-unit id="3305084982600522070" datatype="html">
@@ -4857,35 +4893,35 @@
<source>You have unsaved changes to the document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">105</context>
<context context-type="linenumber">106</context>
</context-group>
</trans-unit>
<trans-unit id="2089045849587358256" datatype="html">
<source>Are you sure you want to close this document?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">109</context>
<context context-type="linenumber">110</context>
</context-group>
</trans-unit>
<trans-unit id="2885986061416655600" datatype="html">
<source>Close document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">111</context>
<context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit id="6755718693176327396" datatype="html">
<source>Are you sure you want to close all documents?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">132</context>
<context context-type="linenumber">133</context>
</context-group>
</trans-unit>
<trans-unit id="4215561719980781894" datatype="html">
<source>Close documents</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">134</context>
<context context-type="linenumber">135</context>
</context-group>
</trans-unit>
<trans-unit id="3553216189604488439" datatype="html">
@@ -4899,7 +4935,7 @@
<source>Search score</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">31</context>
<context context-type="linenumber">32</context>
</context-group>
<note priority="1" from="description">Score is a value returned by the full text search engine and specifies how well a result matches the given query</note>
</trans-unit>

View File

@@ -65,6 +65,17 @@ const routes: Routes = [
},
},
},
{
path: 'documents/:id/:section',
component: DocumentDetailComponent,
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.Document,
},
},
},
{
path: 'asn/:id',
component: DocumentAsnComponent,
@@ -143,17 +154,6 @@ const routes: Routes = [
},
},
},
{
path: 'tasks',
component: TasksComponent,
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.PaperlessTask,
},
},
},
{
path: 'settings/:section',
component: SettingsComponent,
@@ -171,6 +171,17 @@ const routes: Routes = [
component: SettingsComponent,
canDeactivate: [DirtyFormGuard],
},
{
path: 'tasks',
component: TasksComponent,
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.PaperlessTask,
},
},
},
{ path: 'tasks', component: TasksComponent },
],
},

View File

@@ -70,7 +70,7 @@ import { ApiVersionInterceptor } from './interceptors/api-version.interceptor'
import { ColorSliderModule } from 'ngx-color/slider'
import { ColorComponent } from './components/common/input/color/color.component'
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
import { DocumentCommentsComponent } from './components/document-comments/document-comments.component'
import { DocumentNotesComponent } from './components/document-notes/document-notes.component'
import { PermissionsGuard } from './guards/permissions.guard'
import { DirtyDocGuard } from './guards/dirty-doc.guard'
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
@@ -196,7 +196,7 @@ function initializeApp(settings: SettingsService) {
DateComponent,
ColorComponent,
DocumentAsnComponent,
DocumentCommentsComponent,
DocumentNotesComponent,
TasksComponent,
UserEditDialogComponent,
GroupEditDialogComponent,

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,

View File

@@ -1,8 +0,0 @@
import { ObjectWithId } from './object-with-id'
import { PaperlessUser } from './paperless-user'
export interface PaperlessDocumentComment extends ObjectWithId {
created?: Date
comment?: string
user?: PaperlessUser
}

View File

@@ -0,0 +1,7 @@
import { ObjectWithId } from './object-with-id'
export interface PaperlessDocumentNote extends ObjectWithId {
created?: Date
note?: string
user?: number // PaperlessUser
}

View File

@@ -4,13 +4,14 @@ import { PaperlessDocumentType } from './paperless-document-type'
import { Observable } from 'rxjs'
import { PaperlessStoragePath } from './paperless-storage-path'
import { ObjectWithPermissions } from './object-with-permissions'
import { PaperlessDocumentNote } from './paperless-document-note'
export interface SearchHit {
score?: number
rank?: number
highlights?: string
comment_highlights?: string
note_highlights?: string
}
export interface PaperlessDocument extends ObjectWithPermissions {
@@ -54,5 +55,7 @@ export interface PaperlessDocument extends ObjectWithPermissions {
archive_serial_number?: number
notes?: PaperlessDocumentNote[]
__search_hit__?: SearchHit
}

View File

@@ -34,7 +34,7 @@ export const SETTINGS_KEYS = {
'general-settings:notifications:consumer-failed',
NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD:
'general-settings:notifications:consumer-suppress-on-dashboard',
COMMENTS_ENABLED: 'general-settings:comments-enabled',
NOTES_ENABLED: 'general-settings:notes-enabled',
SLIM_SIDEBAR: 'general-settings:slim-sidebar',
UPDATE_CHECKING_ENABLED: 'general-settings:update-checking:enabled',
UPDATE_CHECKING_BACKEND_SETTING:
@@ -125,7 +125,7 @@ export const SETTINGS: PaperlessUiSetting[] = [
default: true,
},
{
key: SETTINGS_KEYS.COMMENTS_ENABLED,
key: SETTINGS_KEYS.NOTES_ENABLED,
type: 'boolean',
default: true,
},

View File

@@ -35,15 +35,16 @@ export class OpenDocumentsService {
refreshDocument(id: number) {
let index = this.openDocuments.findIndex((doc) => doc.id == id)
if (index > -1) {
this.documentService.get(id).subscribe(
(doc) => {
this.documentService.get(id).subscribe({
next: (doc) => {
this.openDocuments[index] = doc
this.save()
},
(error) => {
error: () => {
this.openDocuments.splice(index, 1)
this.save()
}
)
},
})
}
}

View File

@@ -18,7 +18,7 @@ export enum PermissionType {
SavedView = '%s_savedview',
PaperlessTask = '%s_paperlesstask',
UISettings = '%s_uisettings',
Comment = '%s_comment',
Note = '%s_note',
MailAccount = '%s_mailaccount',
MailRule = '%s_mailrule',
User = '%s_user',

View File

@@ -2,10 +2,8 @@ import { HttpClient, HttpParams } from '@angular/common/http'
import { Observable } from 'rxjs'
import { map, publishReplay, refCount } from 'rxjs/operators'
import { ObjectWithId } from 'src/app/data/object-with-id'
import { PaperlessUser } from 'src/app/data/paperless-user'
import { Results } from 'src/app/data/results'
import { environment } from 'src/environments/environment'
import { PermissionAction, PermissionType } from '../permissions.service'
export abstract class AbstractPaperlessService<T extends ObjectWithId> {
protected baseUrl: string = environment.apiBaseUrl

View File

@@ -1,37 +0,0 @@
import { Injectable } from '@angular/core'
import { HttpClient, HttpParams } from '@angular/common/http'
import { PaperlessDocumentComment } from 'src/app/data/paperless-document-comment'
import { AbstractPaperlessService } from './abstract-paperless-service'
import { Observable } from 'rxjs'
@Injectable({
providedIn: 'root',
})
export class DocumentCommentsService extends AbstractPaperlessService<PaperlessDocumentComment> {
constructor(http: HttpClient) {
super(http, 'documents')
}
getComments(documentId: number): Observable<PaperlessDocumentComment[]> {
return this.http.get<PaperlessDocumentComment[]>(
this.getResourceUrl(documentId, 'comments')
)
}
addComment(id: number, comment): Observable<PaperlessDocumentComment[]> {
return this.http.post<PaperlessDocumentComment[]>(
this.getResourceUrl(id, 'comments'),
{ comment: comment }
)
}
deleteComment(
documentId: number,
commentId: number
): Observable<PaperlessDocumentComment[]> {
return this.http.delete<PaperlessDocumentComment[]>(
this.getResourceUrl(documentId, 'comments'),
{ params: new HttpParams({ fromString: `id=${commentId}` }) }
)
}
}

View File

@@ -0,0 +1,37 @@
import { Injectable } from '@angular/core'
import { HttpClient, HttpParams } from '@angular/common/http'
import { PaperlessDocumentNote } from 'src/app/data/paperless-document-note'
import { AbstractPaperlessService } from './abstract-paperless-service'
import { Observable } from 'rxjs'
@Injectable({
providedIn: 'root',
})
export class DocumentNotesService extends AbstractPaperlessService<PaperlessDocumentNote> {
constructor(http: HttpClient) {
super(http, 'documents')
}
getNotes(documentId: number): Observable<PaperlessDocumentNote[]> {
return this.http.get<PaperlessDocumentNote[]>(
this.getResourceUrl(documentId, 'notes')
)
}
addNote(id: number, note: string): Observable<PaperlessDocumentNote[]> {
return this.http.post<PaperlessDocumentNote[]>(
this.getResourceUrl(id, 'notes'),
{ note: note }
)
}
deleteNote(
documentId: number,
noteId: number
): Observable<PaperlessDocumentNote[]> {
return this.http.delete<PaperlessDocumentNote[]>(
this.getResourceUrl(documentId, 'notes'),
{ params: new HttpParams({ fromString: `id=${noteId}` }) }
)
}
}

View File

@@ -22,6 +22,7 @@ export const DOCUMENT_SORT_FIELDS = [
{ field: 'created', name: $localize`Created` },
{ field: 'added', name: $localize`Added` },
{ field: 'modified', name: $localize`Modified` },
{ field: 'num_notes', name: $localize`Notes` },
]
export const DOCUMENT_SORT_FIELDS_FULLTEXT = [

View File

@@ -436,6 +436,12 @@ textarea,
height: 12px;
}
.metadata-icon {
width: 0.9rem;
height: 0.9rem;
padding: 0.05rem;
}
table.table {
color: var(--bs-body-color);

View File

@@ -133,6 +133,14 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='htt
filter: brightness(.8);
}
.badge.bg-light.border {
border-color: rgba(0,0,0,0) !important;
}
.document-card .card-body.bg-light {
background-color: var(--bs-body-bg);
}
.doc-img {
mix-blend-mode: normal;
border-radius: 0;