mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Rename comments --> notes
This commit is contained in:
parent
89c639f850
commit
bf8ae22f3f
@ -80,7 +80,7 @@ django_checks() {
|
||||
|
||||
search_index() {
|
||||
|
||||
local -r index_version=3
|
||||
local -r index_version=4
|
||||
local -r index_version_file=${DATA_DIR}/.index_version
|
||||
|
||||
if [[ (! -f "${index_version_file}") || $(<"${index_version_file}") != "$index_version" ]]; then
|
||||
|
@ -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,48 +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 comments tab by url', () => {
|
||||
cy.visit('/documents/1/comments')
|
||||
cy.get('app-document-comments').should('exist')
|
||||
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 comment counts', () => {
|
||||
cy.visit('/documents/1/comments')
|
||||
cy.get('app-document-comments').within(() => cy.contains('Delete').click())
|
||||
cy.get('ul.nav')
|
||||
.find('li')
|
||||
.contains('Comments')
|
||||
.find('.badge')
|
||||
.contains('2')
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
@ -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": ""
|
||||
}
|
||||
}
|
||||
]
|
26
src-ui/cypress/fixtures/documents/1/notes.json
Normal file
26
src-ui/cypress/fixtures/documents/1/notes.json
Normal 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
|
||||
}
|
||||
]
|
@ -22,39 +22,24 @@
|
||||
"archived_file_name": "2022-03-22 no latin title.pdf",
|
||||
"owner": null,
|
||||
"permissions": [],
|
||||
"comments": [
|
||||
"notes": [
|
||||
{
|
||||
"id": 30,
|
||||
"comment": "One more time",
|
||||
"created": "2023-03-17T22:02:14.357575Z",
|
||||
"user": {
|
||||
"id": 2,
|
||||
"username": "username",
|
||||
"first_name": "",
|
||||
"last_name": ""
|
||||
}
|
||||
"id": 9,
|
||||
"note": "Testing one more time",
|
||||
"created": "2022-02-18T04:24:55.176008Z",
|
||||
"user": 15
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"comment": "Lets keep going",
|
||||
"created": "2023-03-16T06:57:32.014027Z",
|
||||
"user": {
|
||||
"id": 2,
|
||||
"username": "username",
|
||||
"first_name": "",
|
||||
"last_name": ""
|
||||
}
|
||||
"id": 8,
|
||||
"note": "Another note",
|
||||
"created": "2021-11-08T04:24:47.925042Z",
|
||||
"user": 3
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"comment": "And just one more",
|
||||
"created": "2023-03-16T06:57:27.022729Z",
|
||||
"user": {
|
||||
"id": 2,
|
||||
"username": "username",
|
||||
"first_name": "",
|
||||
"last_name": ""
|
||||
}
|
||||
"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
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -75,7 +60,7 @@
|
||||
"archived_file_name": "2022-03-23 llorem ipsum dolor sit amet.pdf",
|
||||
"owner": null,
|
||||
"permissions": [],
|
||||
"comments": []
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
@ -96,7 +81,7 @@
|
||||
"archived_file_name": "2022-03-24 dolor.pdf",
|
||||
"owner": null,
|
||||
"permissions": [],
|
||||
"comments": []
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
@ -117,7 +102,7 @@
|
||||
"archived_file_name": "2022-06-01 sit amet.pdf",
|
||||
"owner": null,
|
||||
"permissions": [],
|
||||
"comments": []
|
||||
"notes": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -74,7 +74,7 @@
|
||||
"change_task",
|
||||
"delete_task",
|
||||
"view_task",
|
||||
"add_comment",
|
||||
"add_note",
|
||||
"add_frontendsettings",
|
||||
"change_frontendsettings",
|
||||
"delete_frontendsettings",
|
||||
|
@ -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",
|
||||
|
@ -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>
|
||||
@ -2328,52 +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="7978668497183230348" datatype="html">
|
||||
<source>Delete comment</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.html</context>
|
||||
<context context-type="linenumber">21</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-comments/document-comments.component.html</context>
|
||||
<context context-type="linenumber">25</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">59</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">75</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1407560924967345762" datatype="html">
|
||||
<source>Page</source>
|
||||
<context-group purpose="location">
|
||||
@ -2664,8 +2618,8 @@
|
||||
<context context-type="linenumber">215</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8403302283555274795" datatype="html">
|
||||
<source>Comments <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="document?.comments.length" class="badge text-bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text=".length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</a>"/></source>
|
||||
<trans-unit id="8460995830263484763" datatype="html">
|
||||
<source>Notes <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="document?.notes.length" class="badge text-bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="ngth}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</a>"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||
<context context-type="linenumber">175,176</context>
|
||||
@ -3173,15 +3127,15 @@
|
||||
<context context-type="linenumber">191</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3758078190163790058" datatype="html">
|
||||
<source>View comments</source>
|
||||
<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="6326693689225506833" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="omments.length}}"/> Comments</source>
|
||||
<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>
|
||||
@ -3373,8 +3327,8 @@
|
||||
<context context-type="linenumber">18</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3807699453257291879" datatype="html">
|
||||
<source>Comments</source>
|
||||
<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>
|
||||
@ -3383,6 +3337,10 @@
|
||||
<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>
|
||||
@ -3410,14 +3368,14 @@
|
||||
<source>View "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" 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 "<x id="PH" equiv-text="savedView.name"/>" 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">
|
||||
@ -3582,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">
|
||||
@ -4050,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>
|
||||
@ -4931,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>
|
||||
|
@ -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,
|
||||
|
@ -1,99 +0,0 @@
|
||||
import { Component, Input, Output, EventEmitter } 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
|
||||
newCommentError: boolean = false
|
||||
|
||||
@Input()
|
||||
documentId: number
|
||||
|
||||
@Input()
|
||||
comments: PaperlessDocumentComment[] = []
|
||||
|
||||
@Output()
|
||||
updated: EventEmitter<PaperlessDocumentComment[]> = new EventEmitter()
|
||||
|
||||
constructor(
|
||||
private commentsService: DocumentCommentsService,
|
||||
private toastService: ToastService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
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
|
||||
this.updated.emit(this.comments)
|
||||
},
|
||||
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
|
||||
this.updated.emit(this.comments)
|
||||
},
|
||||
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(' ')
|
||||
}
|
||||
|
||||
commentFormKeydown(event: KeyboardEvent) {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
|
||||
this.addComment()
|
||||
}
|
||||
}
|
||||
}
|
@ -171,10 +171,10 @@
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Comments" *ngIf="commentsEnabled">
|
||||
<a ngbNavLink i18n>Comments <span *ngIf="document?.comments.length" class="badge text-bg-secondary ms-1">{{document.comments.length}}</span></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" [comments]="document?.comments" (updated)="commentsUpdated($event)"></app-document-comments>
|
||||
<app-document-notes [documentId]="documentId" [notes]="document?.notes" (updated)="notesUpdated($event)"></app-document-notes>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
|
@ -42,14 +42,14 @@ 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 { PaperlessDocumentComment } from 'src/app/data/paperless-document-comment'
|
||||
import { PaperlessDocumentNote } from 'src/app/data/paperless-document-note'
|
||||
|
||||
enum DocumentDetailNavIDs {
|
||||
Details = 1,
|
||||
Content = 2,
|
||||
Metadata = 3,
|
||||
Preview = 4,
|
||||
Comments = 5,
|
||||
Notes = 5,
|
||||
Permissions = 6,
|
||||
}
|
||||
|
||||
@ -658,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
|
||||
@ -668,8 +668,8 @@ export class DocumentDetailComponent
|
||||
)
|
||||
}
|
||||
|
||||
commentsUpdated(comments: PaperlessDocumentComment[]) {
|
||||
this.document.comments = comments
|
||||
notesUpdated(notes: PaperlessDocumentNote[]) {
|
||||
this.document.notes = notes
|
||||
this.openDocumentService.refreshDocument(this.documentId)
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
@ -67,11 +67,11 @@
|
||||
|
||||
|
||||
<div class="list-group list-group-horizontal border-0 card-info ms-md-auto mt-2 mt-md-0">
|
||||
<button routerLink="/documents/{{document.id}}/comments" *ngIf="document.comments.length" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2" title="View comments" i18n-title>
|
||||
<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.comments.length}} Comments</small>
|
||||
<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()">
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -13,20 +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>
|
||||
|
||||
<a routerLink="/documents/{{document.id}}/comments" *ngIf="document.comments.length" class="document-card-comments py-2 px-1">
|
||||
<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.comments.length}}</span>
|
||||
{{document.notes.length}}</span>
|
||||
</a>
|
||||
|
||||
<div class="card-body p-2">
|
||||
<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>:
|
||||
|
@ -34,10 +34,10 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.document-card-comments {
|
||||
.document-card-notes {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 170px;
|
||||
top: 142px;
|
||||
}
|
||||
|
||||
.card-selected {
|
||||
|
@ -74,7 +74,7 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
||||
}
|
||||
|
||||
getTagsLimited$() {
|
||||
const limit = this.document.comments.length > 0 ? 6 : 7
|
||||
const limit = this.document.notes.length > 0 ? 6 : 7
|
||||
return this.document.tags$.pipe(
|
||||
map((tags) => {
|
||||
if (tags.length > limit) {
|
||||
@ -111,4 +111,8 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
||||
mouseLeaveCard() {
|
||||
this.popover.close()
|
||||
}
|
||||
|
||||
get notesEnabled(): boolean {
|
||||
return this.settingsService.get(SETTINGS_KEYS.NOTES_ENABLED)
|
||||
}
|
||||
}
|
||||
|
@ -139,12 +139,12 @@
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Title</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
appSortable="num_comments"
|
||||
<th *ngIf="notesEnabled" class="d-none d-xl-table-cell"
|
||||
appSortable="num_notes"
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Comments</th>
|
||||
i18n>Notes</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
appSortable="document_type__name"
|
||||
[currentSortField]="list.sortField"
|
||||
@ -190,13 +190,13 @@
|
||||
<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 class="d-none d-xl-table-cell">
|
||||
<a routerLink="/documents/{{d.id}}/comments" *ngIf="d.comments.length" class="btn btn-sm p-0">
|
||||
<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.comments.length}}</span>
|
||||
{{d.notes.length}}</span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="d-none d-xl-table-cell">
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +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 (keydown)="commentFormKeydown($event)" 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" title="Delete comment" i18n-title (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 comment</span>
|
||||
<span class="visually-hidden" i18n>Delete note</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
7
src-ui/src/app/data/paperless-document-note.ts
Normal file
7
src-ui/src/app/data/paperless-document-note.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { ObjectWithId } from './object-with-id'
|
||||
|
||||
export interface PaperlessDocumentNote extends ObjectWithId {
|
||||
created?: Date
|
||||
note?: string
|
||||
user?: number // PaperlessUser
|
||||
}
|
@ -4,14 +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 { PaperlessDocumentComment } from './paperless-document-comment'
|
||||
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 {
|
||||
@ -55,7 +55,7 @@ export interface PaperlessDocument extends ObjectWithPermissions {
|
||||
|
||||
archive_serial_number?: number
|
||||
|
||||
comments?: PaperlessDocumentComment[]
|
||||
notes?: PaperlessDocumentNote[]
|
||||
|
||||
__search_hit__?: SearchHit
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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',
|
||||
|
@ -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}` }) }
|
||||
)
|
||||
}
|
||||
}
|
37
src-ui/src/app/services/rest/document-notes.service.ts
Normal file
37
src-ui/src/app/services/rest/document-notes.service.ts
Normal 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}` }) }
|
||||
)
|
||||
}
|
||||
}
|
@ -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 = [
|
||||
|
@ -137,6 +137,10 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='htt
|
||||
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;
|
||||
|
@ -4,6 +4,7 @@ from guardian.admin import GuardedModelAdmin
|
||||
from .models import Correspondent
|
||||
from .models import Document
|
||||
from .models import DocumentType
|
||||
from .models import Note
|
||||
from .models import PaperlessTask
|
||||
from .models import SavedView
|
||||
from .models import SavedViewFilterRule
|
||||
@ -131,6 +132,13 @@ class TaskAdmin(admin.ModelAdmin):
|
||||
)
|
||||
|
||||
|
||||
class NotesAdmin(GuardedModelAdmin):
|
||||
|
||||
list_display = ("user", "created", "note", "document")
|
||||
list_filter = ("created", "user")
|
||||
list_display_links = ("created",)
|
||||
|
||||
|
||||
admin.site.register(Correspondent, CorrespondentAdmin)
|
||||
admin.site.register(Tag, TagAdmin)
|
||||
admin.site.register(DocumentType, DocumentTypeAdmin)
|
||||
@ -138,3 +146,4 @@ admin.site.register(Document, DocumentAdmin)
|
||||
admin.site.register(SavedView, SavedViewAdmin)
|
||||
admin.site.register(StoragePath, StoragePathAdmin)
|
||||
admin.site.register(PaperlessTask, TaskAdmin)
|
||||
admin.site.register(Note, NotesAdmin)
|
||||
|
@ -6,8 +6,8 @@ from contextlib import contextmanager
|
||||
from dateutil.parser import isoparse
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from documents.models import Comment
|
||||
from documents.models import Document
|
||||
from documents.models import Note
|
||||
from guardian.shortcuts import get_users_with_perms
|
||||
from whoosh import classify
|
||||
from whoosh import highlight
|
||||
@ -52,7 +52,7 @@ def get_schema():
|
||||
path=TEXT(sortable=True),
|
||||
path_id=NUMERIC(),
|
||||
has_path=BOOLEAN(),
|
||||
comments=TEXT(),
|
||||
notes=TEXT(),
|
||||
owner=TEXT(),
|
||||
owner_id=NUMERIC(),
|
||||
has_owner=BOOLEAN(),
|
||||
@ -98,7 +98,7 @@ def open_index_searcher():
|
||||
def update_document(writer: AsyncWriter, doc: Document):
|
||||
tags = ",".join([t.name for t in doc.tags.all()])
|
||||
tags_ids = ",".join([str(t.id) for t in doc.tags.all()])
|
||||
comments = ",".join([str(c.comment) for c in Comment.objects.filter(document=doc)])
|
||||
notes = ",".join([str(c.note) for c in Note.objects.filter(document=doc)])
|
||||
asn = doc.archive_serial_number
|
||||
if asn is not None and (
|
||||
asn < Document.ARCHIVE_SERIAL_NUMBER_MIN
|
||||
@ -136,7 +136,7 @@ def update_document(writer: AsyncWriter, doc: Document):
|
||||
path=doc.storage_path.name if doc.storage_path else None,
|
||||
path_id=doc.storage_path.id if doc.storage_path else None,
|
||||
has_path=doc.storage_path is not None,
|
||||
comments=comments,
|
||||
notes=notes,
|
||||
owner=doc.owner.username if doc.owner else None,
|
||||
owner_id=doc.owner.id if doc.owner else None,
|
||||
has_owner=doc.owner is not None,
|
||||
@ -293,7 +293,7 @@ class DelayedFullTextQuery(DelayedQuery):
|
||||
def _get_query(self):
|
||||
q_str = self.query_params["query"]
|
||||
qp = MultifieldParser(
|
||||
["content", "title", "correspondent", "tag", "type", "comments"],
|
||||
["content", "title", "correspondent", "tag", "type", "notes"],
|
||||
self.searcher.ixreader.schema,
|
||||
)
|
||||
qp.add_plugin(DateParserPlugin(basedate=timezone.now()))
|
||||
|
@ -17,10 +17,10 @@ from django.core.management.base import BaseCommand
|
||||
from django.core.management.base import CommandError
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from documents.models import Comment
|
||||
from documents.models import Correspondent
|
||||
from documents.models import Document
|
||||
from documents.models import DocumentType
|
||||
from documents.models import Note
|
||||
from documents.models import SavedView
|
||||
from documents.models import SavedViewFilterRule
|
||||
from documents.models import StoragePath
|
||||
@ -206,7 +206,7 @@ class Command(BaseCommand):
|
||||
self.files_in_export_dir.add(x.resolve())
|
||||
|
||||
# 2. Create manifest, containing all correspondents, types, tags, storage paths
|
||||
# comments, documents and ui_settings
|
||||
# note, documents and ui_settings
|
||||
with transaction.atomic():
|
||||
manifest = json.loads(
|
||||
serializers.serialize("json", Correspondent.objects.all()),
|
||||
@ -222,11 +222,11 @@ class Command(BaseCommand):
|
||||
serializers.serialize("json", StoragePath.objects.all()),
|
||||
)
|
||||
|
||||
comments = json.loads(
|
||||
serializers.serialize("json", Comment.objects.all()),
|
||||
notes = json.loads(
|
||||
serializers.serialize("json", Note.objects.all()),
|
||||
)
|
||||
if not self.split_manifest:
|
||||
manifest += comments
|
||||
manifest += notes
|
||||
|
||||
documents = Document.objects.order_by("id")
|
||||
document_map = {d.pk: d for d in documents}
|
||||
@ -359,7 +359,7 @@ class Command(BaseCommand):
|
||||
content += list(
|
||||
filter(
|
||||
lambda d: d["fields"]["document"] == document_dict["pk"],
|
||||
comments,
|
||||
notes,
|
||||
),
|
||||
)
|
||||
manifest_name.write_text(json.dumps(content, indent=2))
|
||||
|
@ -1,40 +0,0 @@
|
||||
# Generated by Django 4.1.5 on 2023-03-17 21:23
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("documents", "1033_alter_documenttype_options_alter_tag_options_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="comment",
|
||||
name="document",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="comments",
|
||||
to="documents.document",
|
||||
verbose_name="document",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="comment",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="comments",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="user",
|
||||
),
|
||||
),
|
||||
]
|
61
src/documents/migrations/1035_rename_comment_note.py
Normal file
61
src/documents/migrations/1035_rename_comment_note.py
Normal file
@ -0,0 +1,61 @@
|
||||
# Generated by Django 4.1.5 on 2023-03-17 22:15
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("documents", "1034_alter_savedviewfilterrule_rule_type"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name="Comment",
|
||||
new_name="Note",
|
||||
),
|
||||
migrations.RenameField(model_name="note", old_name="comment", new_name="note"),
|
||||
migrations.AlterModelOptions(
|
||||
name="note",
|
||||
options={
|
||||
"ordering": ("created",),
|
||||
"verbose_name": "note",
|
||||
"verbose_name_plural": "notes",
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="note",
|
||||
name="document",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="notes",
|
||||
to="documents.document",
|
||||
verbose_name="document",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="note",
|
||||
name="note",
|
||||
field=models.TextField(
|
||||
blank=True, help_text="Note for the document", verbose_name="content"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="note",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="notes",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="user",
|
||||
),
|
||||
),
|
||||
]
|
@ -635,11 +635,11 @@ class PaperlessTask(models.Model):
|
||||
)
|
||||
|
||||
|
||||
class Comment(models.Model):
|
||||
comment = models.TextField(
|
||||
class Note(models.Model):
|
||||
note = models.TextField(
|
||||
_("content"),
|
||||
blank=True,
|
||||
help_text=_("Comment for the document"),
|
||||
help_text=_("Note for the document"),
|
||||
)
|
||||
|
||||
created = models.DateTimeField(
|
||||
@ -652,7 +652,7 @@ class Comment(models.Model):
|
||||
Document,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="comments",
|
||||
related_name="notes",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("document"),
|
||||
)
|
||||
@ -661,15 +661,15 @@ class Comment(models.Model):
|
||||
User,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="comments",
|
||||
related_name="notes",
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name=_("user"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ("created",)
|
||||
verbose_name = _("comment")
|
||||
verbose_name_plural = _("comments")
|
||||
verbose_name = _("note")
|
||||
verbose_name_plural = _("notes")
|
||||
|
||||
def __str__(self):
|
||||
return self.content
|
||||
return self.note
|
||||
|
@ -382,8 +382,6 @@ class DocumentSerializer(OwnedObjectSerializer, DynamicFieldsModelSerializer):
|
||||
archived_file_name = SerializerMethodField()
|
||||
created_date = serializers.DateField(required=False)
|
||||
|
||||
num_comments = serializers.IntegerField(read_only=True)
|
||||
|
||||
owner = serializers.PrimaryKeyRelatedField(
|
||||
queryset=User.objects.all(),
|
||||
required=False,
|
||||
@ -444,8 +442,7 @@ class DocumentSerializer(OwnedObjectSerializer, DynamicFieldsModelSerializer):
|
||||
"owner",
|
||||
"permissions",
|
||||
"set_permissions",
|
||||
"comments",
|
||||
"num_comments",
|
||||
"notes",
|
||||
)
|
||||
|
||||
|
||||
|
@ -38,7 +38,7 @@ from documents.models import PaperlessTask
|
||||
from documents.models import SavedView
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.models import Comment
|
||||
from documents.models import Note
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
from paperless import version
|
||||
from rest_framework.test import APITestCase
|
||||
@ -1717,28 +1717,28 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
1,
|
||||
)
|
||||
|
||||
def test_get_existing_comments(self):
|
||||
def test_get_existing_notes(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- A document with a single comment
|
||||
- A document with a single note
|
||||
WHEN:
|
||||
- API reuqest for document comments is made
|
||||
- API reuqest for document notes is made
|
||||
THEN:
|
||||
- The associated comment is returned
|
||||
- The associated note is returned
|
||||
"""
|
||||
doc = Document.objects.create(
|
||||
title="test",
|
||||
mime_type="application/pdf",
|
||||
content="this is a document which will have comments!",
|
||||
content="this is a document which will have notes!",
|
||||
)
|
||||
comment = Comment.objects.create(
|
||||
comment="This is a comment.",
|
||||
note = Note.objects.create(
|
||||
note="This is a note.",
|
||||
document=doc,
|
||||
user=self.user,
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
f"/api/documents/{doc.pk}/comments/",
|
||||
f"/api/documents/{doc.pk}/notes/",
|
||||
format="json",
|
||||
)
|
||||
|
||||
@ -1754,39 +1754,39 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
self.assertDictEqual(
|
||||
resp_data,
|
||||
{
|
||||
"id": comment.id,
|
||||
"comment": comment.comment,
|
||||
"id": note.id,
|
||||
"note": note.note,
|
||||
"user": {
|
||||
"id": comment.user.id,
|
||||
"username": comment.user.username,
|
||||
"first_name": comment.user.first_name,
|
||||
"last_name": comment.user.last_name,
|
||||
"id": note.user.id,
|
||||
"username": note.user.username,
|
||||
"first_name": note.user.first_name,
|
||||
"last_name": note.user.last_name,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
def test_create_comment(self):
|
||||
def test_create_note(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Existing document
|
||||
WHEN:
|
||||
- API request is made to add a comment
|
||||
- API request is made to add a note
|
||||
THEN:
|
||||
- Comment is created and associated with document
|
||||
- note is created and associated with document
|
||||
"""
|
||||
doc = Document.objects.create(
|
||||
title="test",
|
||||
mime_type="application/pdf",
|
||||
content="this is a document which will have comments added",
|
||||
content="this is a document which will have notes added",
|
||||
)
|
||||
resp = self.client.post(
|
||||
f"/api/documents/{doc.pk}/comments/",
|
||||
data={"comment": "this is a posted comment"},
|
||||
f"/api/documents/{doc.pk}/notes/",
|
||||
data={"note": "this is a posted note"},
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
|
||||
response = self.client.get(
|
||||
f"/api/documents/{doc.pk}/comments/",
|
||||
f"/api/documents/{doc.pk}/notes/",
|
||||
format="json",
|
||||
)
|
||||
|
||||
@ -1798,48 +1798,48 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
|
||||
resp_data = resp_data[0]
|
||||
|
||||
self.assertEqual(resp_data["comment"], "this is a posted comment")
|
||||
self.assertEqual(resp_data["note"], "this is a posted note")
|
||||
|
||||
def test_delete_comment(self):
|
||||
def test_delete_note(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Existing document
|
||||
WHEN:
|
||||
- API request is made to add a comment
|
||||
- API request is made to add a note
|
||||
THEN:
|
||||
- Comment is created and associated with document
|
||||
- note is created and associated with document
|
||||
"""
|
||||
doc = Document.objects.create(
|
||||
title="test",
|
||||
mime_type="application/pdf",
|
||||
content="this is a document which will have comments!",
|
||||
content="this is a document which will have notes!",
|
||||
)
|
||||
comment = Comment.objects.create(
|
||||
comment="This is a comment.",
|
||||
note = Note.objects.create(
|
||||
note="This is a note.",
|
||||
document=doc,
|
||||
user=self.user,
|
||||
)
|
||||
|
||||
response = self.client.delete(
|
||||
f"/api/documents/{doc.pk}/comments/?id={comment.pk}",
|
||||
f"/api/documents/{doc.pk}/notes/?id={note.pk}",
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
self.assertEqual(len(Comment.objects.all()), 0)
|
||||
self.assertEqual(len(Note.objects.all()), 0)
|
||||
|
||||
def test_get_comments_no_doc(self):
|
||||
def test_get_notes_no_doc(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- A request to get comments from a non-existent document
|
||||
- A request to get notes from a non-existent document
|
||||
WHEN:
|
||||
- API request for document comments is made
|
||||
- API request for document notes is made
|
||||
THEN:
|
||||
- HTTP status.HTTP_404_NOT_FOUND is returned
|
||||
"""
|
||||
response = self.client.get(
|
||||
"/api/documents/500/comments/",
|
||||
"/api/documents/500/notes/",
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
@ -13,10 +13,10 @@ from django.test import override_settings
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
from documents.management.commands import document_exporter
|
||||
from documents.models import Comment
|
||||
from documents.models import Correspondent
|
||||
from documents.models import Document
|
||||
from documents.models import DocumentType
|
||||
from documents.models import Note
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.models import User
|
||||
@ -66,8 +66,8 @@ class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
storage_type=Document.STORAGE_TYPE_GPG,
|
||||
)
|
||||
|
||||
self.comment = Comment.objects.create(
|
||||
comment="This is a comment. amaze.",
|
||||
self.note = Note.objects.create(
|
||||
note="This is a note. amaze.",
|
||||
document=self.d1,
|
||||
user=self.user,
|
||||
)
|
||||
@ -199,8 +199,8 @@ class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
checksum = hashlib.md5(f.read()).hexdigest()
|
||||
self.assertEqual(checksum, element["fields"]["archive_checksum"])
|
||||
|
||||
elif element["model"] == "documents.comment":
|
||||
self.assertEqual(element["fields"]["comment"], self.comment.comment)
|
||||
elif element["model"] == "documents.note":
|
||||
self.assertEqual(element["fields"]["note"], self.note.note)
|
||||
self.assertEqual(element["fields"]["document"], self.d1.id)
|
||||
self.assertEqual(element["fields"]["user"], self.user.id)
|
||||
|
||||
|
@ -72,10 +72,10 @@ from .matching import match_correspondents
|
||||
from .matching import match_document_types
|
||||
from .matching import match_storage_paths
|
||||
from .matching import match_tags
|
||||
from .models import Comment
|
||||
from .models import Correspondent
|
||||
from .models import Document
|
||||
from .models import DocumentType
|
||||
from .models import Note
|
||||
from .models import PaperlessTask
|
||||
from .models import SavedView
|
||||
from .models import StoragePath
|
||||
@ -230,7 +230,7 @@ class DocumentViewSet(
|
||||
GenericViewSet,
|
||||
):
|
||||
model = Document
|
||||
queryset = Document.objects.annotate(num_comments=Count("comments"))
|
||||
queryset = Document.objects.annotate(num_notes=Count("notes"))
|
||||
serializer_class = DocumentSerializer
|
||||
pagination_class = StandardPagination
|
||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||
@ -251,11 +251,11 @@ class DocumentViewSet(
|
||||
"modified",
|
||||
"added",
|
||||
"archive_serial_number",
|
||||
"num_comments",
|
||||
"num_notes",
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
return Document.objects.distinct()
|
||||
return Document.objects.distinct().annotate(num_notes=Count("notes"))
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
super().get_serializer(*args, **kwargs)
|
||||
@ -442,11 +442,11 @@ class DocumentViewSet(
|
||||
except (FileNotFoundError, Document.DoesNotExist):
|
||||
raise Http404()
|
||||
|
||||
def getComments(self, doc):
|
||||
def getNotes(self, doc):
|
||||
return [
|
||||
{
|
||||
"id": c.id,
|
||||
"comment": c.comment,
|
||||
"note": c.note,
|
||||
"created": c.created,
|
||||
"user": {
|
||||
"id": c.user.id,
|
||||
@ -455,11 +455,11 @@ class DocumentViewSet(
|
||||
"last_name": c.user.last_name,
|
||||
},
|
||||
}
|
||||
for c in Comment.objects.filter(document=doc).order_by("-created")
|
||||
for c in Note.objects.filter(document=doc).order_by("-created")
|
||||
]
|
||||
|
||||
@action(methods=["get", "post", "delete"], detail=True)
|
||||
def comments(self, request, pk=None):
|
||||
def notes(self, request, pk=None):
|
||||
try:
|
||||
doc = Document.objects.get(pk=pk)
|
||||
except Document.DoesNotExist:
|
||||
@ -469,17 +469,17 @@ class DocumentViewSet(
|
||||
|
||||
if request.method == "GET":
|
||||
try:
|
||||
return Response(self.getComments(doc))
|
||||
return Response(self.getNotes(doc))
|
||||
except Exception as e:
|
||||
logger.warning(f"An error occurred retrieving comments: {str(e)}")
|
||||
logger.warning(f"An error occurred retrieving notes: {str(e)}")
|
||||
return Response(
|
||||
{"error": "Error retreiving comments, check logs for more detail."},
|
||||
{"error": "Error retreiving notes, check logs for more detail."},
|
||||
)
|
||||
elif request.method == "POST":
|
||||
try:
|
||||
c = Comment.objects.create(
|
||||
c = Note.objects.create(
|
||||
document=doc,
|
||||
comment=request.data["comment"],
|
||||
note=request.data["note"],
|
||||
user=currentUser,
|
||||
)
|
||||
c.save()
|
||||
@ -488,23 +488,23 @@ class DocumentViewSet(
|
||||
|
||||
index.add_or_update_document(self.get_object())
|
||||
|
||||
return Response(self.getComments(doc))
|
||||
return Response(self.getNotes(doc))
|
||||
except Exception as e:
|
||||
logger.warning(f"An error occurred saving comment: {str(e)}")
|
||||
logger.warning(f"An error occurred saving note: {str(e)}")
|
||||
return Response(
|
||||
{
|
||||
"error": "Error saving comment, check logs for more detail.",
|
||||
"error": "Error saving note, check logs for more detail.",
|
||||
},
|
||||
)
|
||||
elif request.method == "DELETE":
|
||||
comment = Comment.objects.get(id=int(request.GET.get("id")))
|
||||
comment.delete()
|
||||
note = Note.objects.get(id=int(request.GET.get("id")))
|
||||
note.delete()
|
||||
|
||||
from documents import index
|
||||
|
||||
index.add_or_update_document(self.get_object())
|
||||
|
||||
return Response(self.getComments(doc))
|
||||
return Response(self.getNotes(doc))
|
||||
|
||||
return Response(
|
||||
{
|
||||
@ -516,14 +516,14 @@ class DocumentViewSet(
|
||||
class SearchResultSerializer(DocumentSerializer, PassUserMixin):
|
||||
def to_representation(self, instance):
|
||||
doc = Document.objects.get(id=instance["id"])
|
||||
comments = ",".join(
|
||||
[str(c.comment) for c in Comment.objects.filter(document=instance["id"])],
|
||||
notes = ",".join(
|
||||
[str(c.note) for c in Note.objects.filter(document=instance["id"])],
|
||||
)
|
||||
r = super().to_representation(doc)
|
||||
r["__search_hit__"] = {
|
||||
"score": instance.score,
|
||||
"highlights": instance.highlights("content", text=doc.content),
|
||||
"comment_highlights": instance.highlights("comments", text=comments)
|
||||
"note_highlights": instance.highlights("notes", text=notes)
|
||||
if doc
|
||||
else None,
|
||||
"rank": instance.rank,
|
||||
|
Loading…
x
Reference in New Issue
Block a user