mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Merge pull request #3309 from paperless-ngx/feature-owner-filtering
Feature: owner filtering
This commit is contained in:
commit
09086e574d
@ -150,7 +150,7 @@ describe('documents-list', () => {
|
||||
cy.contains('button', 'Corresp 11').click()
|
||||
cy.contains('label', 'Exclude').click()
|
||||
})
|
||||
cy.contains('One document')
|
||||
cy.contains('3 documents')
|
||||
})
|
||||
|
||||
it('should apply tags', () => {
|
||||
|
@ -190,6 +190,36 @@ describe('documents query params', () => {
|
||||
response.count = response.results.length
|
||||
}
|
||||
|
||||
if (req.query.hasOwnProperty('owner__id')) {
|
||||
response.results = (
|
||||
documentsJson.results as Array<PaperlessDocument>
|
||||
).filter((d) => d.owner == req.query['owner__id'])
|
||||
response.count = response.results.length
|
||||
} else if (req.query.hasOwnProperty('owner__id__in')) {
|
||||
const owners = req.query['owner__id__in']
|
||||
.toString()
|
||||
.split(',')
|
||||
.map((o) => parseInt(o))
|
||||
response.results = (
|
||||
documentsJson.results as Array<PaperlessDocument>
|
||||
).filter((d) => owners.includes(d.owner))
|
||||
response.count = response.results.length
|
||||
} else if (req.query.hasOwnProperty('owner__id__none')) {
|
||||
const owners = req.query['owner__id__none']
|
||||
.toString()
|
||||
.split(',')
|
||||
.map((o) => parseInt(o))
|
||||
response.results = (
|
||||
documentsJson.results as Array<PaperlessDocument>
|
||||
).filter((d) => !owners.includes(d.owner))
|
||||
response.count = response.results.length
|
||||
} else if (req.query.hasOwnProperty('owner__isnull')) {
|
||||
response.results = (
|
||||
documentsJson.results as Array<PaperlessDocument>
|
||||
).filter((d) => d.owner === null)
|
||||
response.count = response.results.length
|
||||
}
|
||||
|
||||
req.reply(response)
|
||||
})
|
||||
})
|
||||
@ -202,7 +232,7 @@ describe('documents query params', () => {
|
||||
|
||||
it('should show a list of documents reverse sorted by created', () => {
|
||||
cy.visit('/documents?sort=created&reverse=true')
|
||||
cy.get('app-document-card-small').first().contains('sit amet')
|
||||
cy.get('app-document-card-small').first().contains('Doc 6')
|
||||
})
|
||||
|
||||
it('should show a list of documents sorted by added', () => {
|
||||
@ -212,7 +242,7 @@ describe('documents query params', () => {
|
||||
|
||||
it('should show a list of documents reverse sorted by added', () => {
|
||||
cy.visit('/documents?sort=added&reverse=true')
|
||||
cy.get('app-document-card-small').first().contains('sit amet')
|
||||
cy.get('app-document-card-small').first().contains('Doc 6')
|
||||
})
|
||||
|
||||
it('should show a list of documents filtered by any tags', () => {
|
||||
@ -222,12 +252,12 @@ describe('documents query params', () => {
|
||||
|
||||
it('should show a list of documents filtered by excluded tags', () => {
|
||||
cy.visit('/documents?sort=created&reverse=true&tags__id__none=2,4')
|
||||
cy.contains('One document')
|
||||
cy.contains('3 documents')
|
||||
})
|
||||
|
||||
it('should show a list of documents filtered by no tags', () => {
|
||||
cy.visit('/documents?sort=created&reverse=true&is_tagged=0')
|
||||
cy.contains('One document')
|
||||
cy.contains('3 documents')
|
||||
})
|
||||
|
||||
it('should show a list of documents filtered by document type', () => {
|
||||
@ -242,7 +272,7 @@ describe('documents query params', () => {
|
||||
|
||||
it('should show a list of documents filtered by no document type', () => {
|
||||
cy.visit('/documents?sort=created&reverse=true&document_type__isnull=1')
|
||||
cy.contains('One document')
|
||||
cy.contains('3 documents')
|
||||
})
|
||||
|
||||
it('should show a list of documents filtered by correspondent', () => {
|
||||
@ -257,7 +287,7 @@ describe('documents query params', () => {
|
||||
|
||||
it('should show a list of documents filtered by no correspondent', () => {
|
||||
cy.visit('/documents?sort=created&reverse=true&correspondent__isnull=1')
|
||||
cy.contains('One document')
|
||||
cy.contains('3 documents')
|
||||
})
|
||||
|
||||
it('should show a list of documents filtered by storage path', () => {
|
||||
@ -267,7 +297,7 @@ describe('documents query params', () => {
|
||||
|
||||
it('should show a list of documents filtered by no storage path', () => {
|
||||
cy.visit('/documents?sort=created&reverse=true&storage_path__isnull=1')
|
||||
cy.contains('3 documents')
|
||||
cy.contains('5 documents')
|
||||
})
|
||||
|
||||
it('should show a list of documents filtered by title or content', () => {
|
||||
@ -312,7 +342,7 @@ describe('documents query params', () => {
|
||||
cy.visit(
|
||||
'/documents?sort=created&reverse=true&created__date__gt=2022-03-23'
|
||||
)
|
||||
cy.contains('3 documents')
|
||||
cy.contains('5 documents')
|
||||
})
|
||||
|
||||
it('should show a list of documents filtered by created date less than', () => {
|
||||
@ -324,7 +354,7 @@ describe('documents query params', () => {
|
||||
|
||||
it('should show a list of documents filtered by added date greater than', () => {
|
||||
cy.visit('/documents?sort=created&reverse=true&added__date__gt=2022-03-24')
|
||||
cy.contains('2 documents')
|
||||
cy.contains('4 documents')
|
||||
})
|
||||
|
||||
it('should show a list of documents filtered by added date less than', () => {
|
||||
@ -338,4 +368,24 @@ describe('documents query params', () => {
|
||||
)
|
||||
cy.contains('2 documents')
|
||||
})
|
||||
|
||||
it('should show a list of documents filtered by owner', () => {
|
||||
cy.visit('/documents?owner__id=15')
|
||||
cy.contains('One document')
|
||||
})
|
||||
|
||||
it('should show a list of documents filtered by multiple owners', () => {
|
||||
cy.visit('/documents?owner__id__in=6,15')
|
||||
cy.contains('2 documents')
|
||||
})
|
||||
|
||||
it('should show a list of documents filtered by excluded owners', () => {
|
||||
cy.visit('/documents?owner__id__none=6')
|
||||
cy.contains('5 documents')
|
||||
})
|
||||
|
||||
it('should show a list of documents filtered by null owner', () => {
|
||||
cy.visit('/documents?owner__isnull=true')
|
||||
cy.contains('4 documents')
|
||||
})
|
||||
})
|
||||
|
@ -143,6 +143,64 @@
|
||||
}
|
||||
},
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"correspondent": null,
|
||||
"document_type": null,
|
||||
"storage_path": null,
|
||||
"title": "Doc 5",
|
||||
"content": "Test document 5",
|
||||
"tags": [],
|
||||
"created": "2023-05-01T07:24:18Z",
|
||||
"created_date": "2023-05-02",
|
||||
"modified": "2023-05-02T07:24:23.264859Z",
|
||||
"added": "2023-05-02T07:24:22.922631Z",
|
||||
"archive_serial_number": null,
|
||||
"original_file_name": "doc5.pdf",
|
||||
"archived_file_name": "doc5.pdf",
|
||||
"owner": 15,
|
||||
"user_can_change": true,
|
||||
"permissions": {
|
||||
"view": {
|
||||
"users": [1],
|
||||
"groups": []
|
||||
},
|
||||
"change": {
|
||||
"users": [],
|
||||
"groups": []
|
||||
}
|
||||
},
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"correspondent": null,
|
||||
"document_type": null,
|
||||
"storage_path": null,
|
||||
"title": "Doc 6",
|
||||
"content": "Test document 6",
|
||||
"tags": [],
|
||||
"created": "2023-05-01T10:24:18Z",
|
||||
"created_date": "2023-05-02",
|
||||
"modified": "2023-05-02T10:24:23.264859Z",
|
||||
"added": "2023-05-02T10:24:22.922631Z",
|
||||
"archive_serial_number": null,
|
||||
"original_file_name": "doc6.pdf",
|
||||
"archived_file_name": "doc6.pdf",
|
||||
"owner": 6,
|
||||
"user_can_change": true,
|
||||
"permissions": {
|
||||
"view": {
|
||||
"users": [1],
|
||||
"groups": []
|
||||
},
|
||||
"change": {
|
||||
"users": [],
|
||||
"groups": []
|
||||
}
|
||||
},
|
||||
"notes": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -574,7 +574,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">28</context>
|
||||
<context context-type="linenumber">26</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
@ -1146,7 +1146,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">81</context>
|
||||
<context context-type="linenumber">78</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
<context context-type="linenumber">77</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7878445132438733225" datatype="html">
|
||||
@ -1547,7 +1551,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">141</context>
|
||||
<context context-type="linenumber">138</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
||||
@ -1852,6 +1856,10 @@
|
||||
<context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.html</context>
|
||||
<context context-type="linenumber">16</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html</context>
|
||||
<context context-type="linenumber">18</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/permissions-select/permissions-select.component.html</context>
|
||||
<context context-type="linenumber">6</context>
|
||||
@ -2000,7 +2008,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">78</context>
|
||||
<context context-type="linenumber">83</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
||||
@ -2102,6 +2110,45 @@
|
||||
<context context-type="linenumber">43</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5947558132119506443" datatype="html">
|
||||
<source>My documents</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html</context>
|
||||
<context context-type="linenumber">28</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="231920238966427751" datatype="html">
|
||||
<source>Shared with me</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html</context>
|
||||
<context context-type="linenumber">38</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5151074932731293042" datatype="html">
|
||||
<source>Unowned</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html</context>
|
||||
<context context-type="linenumber">48</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4555457172864212828" datatype="html">
|
||||
<source>Users</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html</context>
|
||||
<context context-type="linenumber">68</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">332</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8999708063434507268" datatype="html">
|
||||
<source>Hide unowned</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html</context>
|
||||
<context context-type="linenumber">77</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8650499415827640724" datatype="html">
|
||||
<source>Type</source>
|
||||
<context-group purpose="location">
|
||||
@ -2196,7 +2243,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">172</context>
|
||||
<context context-type="linenumber">184</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
@ -2223,11 +2270,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">144</context>
|
||||
<context context-type="linenumber">149</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">172</context>
|
||||
<context context-type="linenumber">191</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
|
||||
@ -2421,7 +2468,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">105</context>
|
||||
<context context-type="linenumber">102</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
|
||||
@ -2429,7 +2476,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">94</context>
|
||||
<context context-type="linenumber">99</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8659635229098859487" datatype="html">
|
||||
@ -2447,7 +2494,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">92</context>
|
||||
<context context-type="linenumber">89</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1418444397960583910" datatype="html">
|
||||
@ -2508,11 +2555,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">40</context>
|
||||
<context context-type="linenumber">38</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">137</context>
|
||||
<context context-type="linenumber">142</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
@ -2531,11 +2578,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">51</context>
|
||||
<context context-type="linenumber">49</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">158</context>
|
||||
<context context-type="linenumber">170</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
@ -2554,11 +2601,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">62</context>
|
||||
<context context-type="linenumber">60</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">165</context>
|
||||
<context context-type="linenumber">177</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
@ -2891,14 +2938,14 @@
|
||||
<source>Edit:</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">27</context>
|
||||
<context context-type="linenumber">25</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7001227209911602786" datatype="html">
|
||||
<source>Filter tags</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">29</context>
|
||||
<context context-type="linenumber">27</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
@ -2909,7 +2956,7 @@
|
||||
<source>Filter correspondents</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">41</context>
|
||||
<context context-type="linenumber">39</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
@ -2920,7 +2967,7 @@
|
||||
<source>Filter document types</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">52</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
@ -2931,7 +2978,7 @@
|
||||
<source>Filter storage paths</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">63</context>
|
||||
<context context-type="linenumber">61</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
@ -2942,7 +2989,7 @@
|
||||
<source>Actions</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">89</context>
|
||||
<context context-type="linenumber">86</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
||||
@ -2989,28 +3036,28 @@
|
||||
<source>Include:</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">111</context>
|
||||
<context context-type="linenumber">108</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1208547554603365604" datatype="html">
|
||||
<source> Archived files </source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">115,117</context>
|
||||
<context context-type="linenumber">112,114</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6791570188945688785" datatype="html">
|
||||
<source> Original files </source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">121,123</context>
|
||||
<context context-type="linenumber">118,120</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3608345051493493574" datatype="html">
|
||||
<source> Use formatted filename </source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">128,130</context>
|
||||
<context context-type="linenumber">125,127</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7985804062689412812" datatype="html">
|
||||
@ -3198,7 +3245,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">194</context>
|
||||
<context context-type="linenumber">206</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2784168796433474565" datatype="html">
|
||||
@ -3209,7 +3256,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">199</context>
|
||||
<context context-type="linenumber">211</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="106713086593101376" datatype="html">
|
||||
@ -3234,7 +3281,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">212</context>
|
||||
<context context-type="linenumber">227</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="157572966557284263" datatype="html">
|
||||
@ -3245,7 +3292,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">217</context>
|
||||
<context context-type="linenumber">232</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3727324658595204357" datatype="html">
|
||||
@ -3285,7 +3332,7 @@
|
||||
<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">110</context>
|
||||
<context context-type="linenumber">116</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3661756380991326939" datatype="html">
|
||||
@ -3390,29 +3437,40 @@
|
||||
<context context-type="linenumber">99</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6849725902312323996" datatype="html">
|
||||
<source>Reset filters</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">104</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
<context context-type="linenumber">85</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1559883523769732271" datatype="html">
|
||||
<source>Error while loading documents</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">112</context>
|
||||
<context context-type="linenumber">117</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="494022736054110363" datatype="html">
|
||||
<source>Sort by ASN</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">126</context>
|
||||
<context context-type="linenumber">131</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7517688192215738656" datatype="html">
|
||||
<source>ASN</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">131,130</context>
|
||||
<context context-type="linenumber">136,135</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">177</context>
|
||||
<context context-type="linenumber">196</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
|
||||
@ -3423,28 +3481,46 @@
|
||||
<source>Sort by correspondent</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">133</context>
|
||||
<context context-type="linenumber">138</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2066713941761361709" datatype="html">
|
||||
<source>Sort by title</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">140</context>
|
||||
<context context-type="linenumber">145</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6232673011753681091" datatype="html">
|
||||
<source>Sort by owner</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">152</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3715596725146409911" datatype="html">
|
||||
<source>Owner</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">156</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
|
||||
<context context-type="linenumber">26</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3557446856808034218" datatype="html">
|
||||
<source>Sort by 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 context-type="linenumber">159</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">151</context>
|
||||
<context context-type="linenumber">163</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
||||
@ -3459,35 +3535,35 @@
|
||||
<source>Sort by document type</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">154</context>
|
||||
<context context-type="linenumber">166</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6213829731736042759" datatype="html">
|
||||
<source>Sort by storage path</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">161</context>
|
||||
<context context-type="linenumber">173</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3406167410329973166" datatype="html">
|
||||
<source>Sort by created date</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">168</context>
|
||||
<context context-type="linenumber">180</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3769035778779263084" datatype="html">
|
||||
<source>Sort by added date</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">175</context>
|
||||
<context context-type="linenumber">187</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">179</context>
|
||||
<context context-type="linenumber">191</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
@ -3502,7 +3578,7 @@
|
||||
<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">198</context>
|
||||
<context context-type="linenumber">210</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2155249406916744630" datatype="html">
|
||||
@ -3519,123 +3595,137 @@
|
||||
<context context-type="linenumber">246</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6849725902312323996" datatype="html">
|
||||
<source>Reset filters</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||
<context context-type="linenumber">82</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5195932016807797291" datatype="html">
|
||||
<source>Correspondent: <x id="PH" equiv-text="this.correspondents.find((c) => c.id == +rule.value)?.name"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">108,110</context>
|
||||
<context context-type="linenumber">117,119</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8170755470576301659" datatype="html">
|
||||
<source>Without correspondent</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">112</context>
|
||||
<context context-type="linenumber">121</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8705701325879965907" datatype="html">
|
||||
<source>Type: <x id="PH" equiv-text="this.documentTypes.find((dt) => dt.id == +rule.value)?.name"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">117,119</context>
|
||||
<context context-type="linenumber">126,128</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4362173610367509215" datatype="html">
|
||||
<source>Without document type</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">121</context>
|
||||
<context context-type="linenumber">130</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8180755793012580465" datatype="html">
|
||||
<source>Tag: <x id="PH" equiv-text="this.tags.find((t) => t.id == +rule.value)?.name"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">125,127</context>
|
||||
<context context-type="linenumber">134,136</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6494566478302448576" datatype="html">
|
||||
<source>Without any tag</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">131</context>
|
||||
<context context-type="linenumber">140</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6523384805359286307" datatype="html">
|
||||
<source>Title: <x id="PH" equiv-text="rule.value"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">135</context>
|
||||
<context context-type="linenumber">144</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1872523635812236432" datatype="html">
|
||||
<source>ASN: <x id="PH" equiv-text="rule.value"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">138</context>
|
||||
<context context-type="linenumber">147</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="102674688969746976" datatype="html">
|
||||
<source>Owner: <x id="PH" equiv-text="rule.value"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">150</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3550877650686009106" datatype="html">
|
||||
<source>Owner not in: <x id="PH" equiv-text="rule.value"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">153</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1082034558646673343" datatype="html">
|
||||
<source>Without an owner</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">156</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3100631071441658964" datatype="html">
|
||||
<source>Title & content</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">175</context>
|
||||
<context context-type="linenumber">194</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1010505078885609376" datatype="html">
|
||||
<source>Advanced search</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">180</context>
|
||||
<context context-type="linenumber">199</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2649431021108393503" datatype="html">
|
||||
<source>More like</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">186</context>
|
||||
<context context-type="linenumber">205</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3697582909018473071" datatype="html">
|
||||
<source>equals</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">205</context>
|
||||
<context context-type="linenumber">224</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5325481293405718739" datatype="html">
|
||||
<source>is empty</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">209</context>
|
||||
<context context-type="linenumber">228</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6166785695326182482" datatype="html">
|
||||
<source>is not empty</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">213</context>
|
||||
<context context-type="linenumber">232</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4686622206659266699" datatype="html">
|
||||
<source>greater than</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">217</context>
|
||||
<context context-type="linenumber">236</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8014012170270529279" datatype="html">
|
||||
<source>less than</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">221</context>
|
||||
<context context-type="linenumber">240</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7210076240260527720" datatype="html">
|
||||
@ -4338,13 +4428,6 @@
|
||||
<context context-type="linenumber">327</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4555457172864212828" datatype="html">
|
||||
<source>Users</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">332</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2941198503117307737" datatype="html">
|
||||
<source>Add User</source>
|
||||
<context-group purpose="location">
|
||||
@ -4917,6 +5000,13 @@
|
||||
<context context-type="linenumber">11</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5739581984228459958" datatype="html">
|
||||
<source>Shared</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/pipes/username.pipe.ts</context>
|
||||
<context context-type="linenumber">33</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2807800733729323332" datatype="html">
|
||||
<source>Yes</source>
|
||||
<context-group purpose="location">
|
||||
@ -5079,7 +5169,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">32</context>
|
||||
<context context-type="linenumber">33</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>
|
||||
|
@ -88,6 +88,10 @@ import { PermissionsUserComponent } from './components/common/input/permissions/
|
||||
import { PermissionsGroupComponent } from './components/common/input/permissions/permissions-group/permissions-group.component'
|
||||
import { IfOwnerDirective } from './directives/if-owner.directive'
|
||||
import { IfObjectPermissionsDirective } from './directives/if-object-permissions.directive'
|
||||
import { PermissionsDialogComponent } from './components/common/permissions-dialog/permissions-dialog.component'
|
||||
import { PermissionsFormComponent } from './components/common/input/permissions/permissions-form/permissions-form.component'
|
||||
import { PermissionsFilterDropdownComponent } from './components/common/permissions-filter-dropdown/permissions-filter-dropdown.component'
|
||||
import { UsernamePipe } from './pipes/username.pipe'
|
||||
|
||||
import localeAr from '@angular/common/locales/ar'
|
||||
import localeBe from '@angular/common/locales/be'
|
||||
@ -111,8 +115,6 @@ import localeSr from '@angular/common/locales/sr'
|
||||
import localeSv from '@angular/common/locales/sv'
|
||||
import localeTr from '@angular/common/locales/tr'
|
||||
import localeZh from '@angular/common/locales/zh'
|
||||
import { PermissionsDialogComponent } from './components/common/permissions-dialog/permissions-dialog.component'
|
||||
import { PermissionsFormComponent } from './components/common/input/permissions/permissions-form/permissions-form.component'
|
||||
|
||||
registerLocaleData(localeAr)
|
||||
registerLocaleData(localeBe)
|
||||
@ -213,6 +215,8 @@ function initializeApp(settings: SettingsService) {
|
||||
IfObjectPermissionsDirective,
|
||||
PermissionsDialogComponent,
|
||||
PermissionsFormComponent,
|
||||
PermissionsFilterDropdownComponent,
|
||||
UsernamePipe,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@ -253,6 +257,7 @@ function initializeApp(settings: SettingsService) {
|
||||
PermissionsGuard,
|
||||
DirtyDocGuard,
|
||||
DirtySavedViewGuard,
|
||||
UsernamePipe,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
|
@ -18,8 +18,8 @@
|
||||
<input class="form-control form-control-sm" type="text" placeholder="Search documents" aria-label="Search"
|
||||
[formControl]="searchField" [ngbTypeahead]="searchAutoComplete" (keyup)="searchFieldKeyup($event)" (selectItem)="itemSelected($event)" i18n-placeholder>
|
||||
<button type="button" *ngIf="!searchFieldEmpty" class="btn btn-link btn-sm px-0 position-absolute top-0 end-0" (click)="resetSearchField()">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x me-1" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
|
||||
<svg fill="currentColor" class="buttonicon-sm me-1">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
@ -107,7 +107,7 @@
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
|
||||
</svg><span> {{d.title | documentTitle}}</span>
|
||||
<span class="close" (click)="closeDocument(d); $event.preventDefault()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16">
|
||||
<svg fill="currentColor" class="toolbaricon">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
</span>
|
||||
|
@ -6,10 +6,10 @@
|
||||
<div class="dropdown-menu date-dropdown shadow pt-0" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||
<div class="list-group list-group-flush">
|
||||
<button *ngFor="let rd of relativeDates" class="list-group-item small list-goup list-group-item-action d-flex p-2" role="menuitem" (click)="setRelativeDate(rd.date)">
|
||||
<div _ngcontent-hga-c166="" class="selected-icon me-1">
|
||||
<svg *ngIf="relativeDate === rd.date" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-check" viewBox="0 0 16 16">
|
||||
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z"/>
|
||||
</svg>
|
||||
<div class="selected-icon me-1">
|
||||
<svg *ngIf="relativeDate === rd.date" fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
{{rd.name}}
|
||||
</button>
|
||||
@ -18,8 +18,8 @@
|
||||
<div class="mb-2 d-flex flex-row w-100 justify-content-between small">
|
||||
<div i18n>After</div>
|
||||
<a *ngIf="dateAfter" class="btn btn-link p-0 m-0" (click)="clearAfter()">
|
||||
<svg width="0.8em" height="0.8em" viewBox="0 0 16 16" class="bi bi-x" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
<small i18n>Clear</small>
|
||||
</a>
|
||||
@ -29,8 +29,8 @@
|
||||
<input class="form-control" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="dateAfter" ngbDatepicker #dateAfterPicker="ngbDatepicker">
|
||||
<button class="btn btn-outline-secondary" (click)="dateAfterPicker.toggle()" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
|
||||
<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 fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#calendar"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@ -41,8 +41,8 @@
|
||||
<div class="mb-2 d-flex flex-row w-100 justify-content-between small">
|
||||
<div i18n>Before</div>
|
||||
<a *ngIf="dateBefore" class="btn btn-link p-0 m-0" (click)="clearBefore()">
|
||||
<svg width="0.8em" height="0.8em" viewBox="0 0 16 16" class="bi bi-x" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
<small i18n>Clear</small>
|
||||
</a>
|
||||
@ -52,8 +52,8 @@
|
||||
<input class="form-control" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="dateBefore" ngbDatepicker #dateBeforePicker="ngbDatepicker">
|
||||
<button class="btn btn-outline-secondary" (click)="dateBeforePicker.toggle()" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
|
||||
<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 fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#calendar"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1,18 +1,18 @@
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="toggleItem($event)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
<ng-container *ngIf="isChecked()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-check" viewBox="0 0 16 16">
|
||||
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z"/>
|
||||
<svg fill="currentColor" class="buttonicon-sm bi-check">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="isPartiallyChecked()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-dash" viewBox="0 0 16 16">
|
||||
<path d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z"/>
|
||||
<svg fill="currentColor" class="buttonicon-sm bi-dash">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#dash"/>
|
||||
</svg>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="isExcluded()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16">
|
||||
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
|
||||
<svg fill="currentColor" class="buttonicon-sm bi-x">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
@ -0,0 +1,82 @@
|
||||
<div class="btn-group w-100" ngbDropdown role="group">
|
||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="isActive ? 'btn-primary' : 'btn-outline-primary'">
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock" />
|
||||
</svg>
|
||||
<div class="d-none d-sm-inline"> {{title}}</div>
|
||||
<app-clearable-badge [selected]="isActive" (cleared)="reset()"></app-clearable-badge><span class="visually-hidden">selected</span>
|
||||
</button>
|
||||
<div class="dropdown-menu permission-filter-dropdown shadow py-0 w-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||
<div class="list-group list-group-flush">
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.NONE)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
<svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.NONE" fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>All</small>
|
||||
</div>
|
||||
</button>
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.SELF)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
<svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.SELF" fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>My documents</small>
|
||||
</div>
|
||||
</button>
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.NOT_SELF)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
<svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.NOT_SELF" fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>Shared with me</small>
|
||||
</div>
|
||||
</button>
|
||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.UNOWNED)" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
<svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.UNOWNED" fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="me-1">
|
||||
<small i18n>Unowned</small>
|
||||
</div>
|
||||
</button>
|
||||
<button *appIfPermissions="{ action: PermissionAction.Add, type: PermissionType.User }" class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" [disabled]="disabled">
|
||||
<div class="selected-icon me-1">
|
||||
<svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.OTHERS" fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="me-1 w-100">
|
||||
<ng-select
|
||||
name="user"
|
||||
class="user-select small"
|
||||
[(ngModel)]="selectionModel.includeUsers"
|
||||
[disabled]="disabled"
|
||||
[clearable]="false"
|
||||
[items]="users"
|
||||
bindLabel="username"
|
||||
multiple="true"
|
||||
bindValue="id"
|
||||
placeholder="Users"
|
||||
i18n-placeholder
|
||||
(change)="onUserSelect()">
|
||||
</ng-select>
|
||||
</div>
|
||||
</button>
|
||||
<div *ngIf="selectionModel.ownerFilter === OwnerFilterType.NONE || selectionModel.ownerFilter === OwnerFilterType.NOT_SELF" class="list-group-item list-group-item-action d-flex align-items-center p-2 ps-3 border-bottom-0 border-start-0 border-end-0">
|
||||
<div class="form-check form-switch w-100">
|
||||
<input type="checkbox" class="form-check-input" id="hideUnowned" [(ngModel)]="this.selectionModel.hideUnowned" (change)="onChange()" [disabled]="disabled">
|
||||
<label class="form-check-label w-100" for="hideUnowned"><small i18n>Hide unowned</small></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,8 @@
|
||||
.user-select {
|
||||
min-width: 15rem;
|
||||
}
|
||||
|
||||
.selected-icon {
|
||||
min-width: 1em;
|
||||
min-height: 1em;
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
||||
import { first } from 'rxjs'
|
||||
import { PaperlessUser } from 'src/app/data/paperless-user'
|
||||
import {
|
||||
PermissionAction,
|
||||
PermissionType,
|
||||
PermissionsService,
|
||||
} from 'src/app/services/permissions.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
|
||||
export class PermissionsSelectionModel {
|
||||
ownerFilter: OwnerFilterType
|
||||
hideUnowned: boolean
|
||||
userID: number
|
||||
includeUsers: number[]
|
||||
excludeUsers: number[]
|
||||
|
||||
clear() {
|
||||
this.ownerFilter = OwnerFilterType.NONE
|
||||
this.userID = null
|
||||
this.hideUnowned = false
|
||||
this.includeUsers = []
|
||||
this.excludeUsers = []
|
||||
}
|
||||
}
|
||||
|
||||
export enum OwnerFilterType {
|
||||
NONE = 0,
|
||||
SELF = 1,
|
||||
NOT_SELF = 2,
|
||||
OTHERS = 3,
|
||||
UNOWNED = 4,
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-permissions-filter-dropdown',
|
||||
templateUrl: './permissions-filter-dropdown.component.html',
|
||||
styleUrls: ['./permissions-filter-dropdown.component.scss'],
|
||||
})
|
||||
export class PermissionsFilterDropdownComponent {
|
||||
public PermissionAction = PermissionAction
|
||||
public PermissionType = PermissionType
|
||||
public OwnerFilterType = OwnerFilterType
|
||||
|
||||
@Input()
|
||||
title: string
|
||||
|
||||
@Input()
|
||||
disabled = false
|
||||
|
||||
@Input()
|
||||
selectionModel: PermissionsSelectionModel
|
||||
|
||||
@Output()
|
||||
ownerFilterSet = new EventEmitter<PermissionsSelectionModel>()
|
||||
|
||||
users: PaperlessUser[]
|
||||
|
||||
hideUnowned: boolean
|
||||
|
||||
get isActive(): boolean {
|
||||
return (
|
||||
this.selectionModel.ownerFilter !== OwnerFilterType.NONE ||
|
||||
this.selectionModel.hideUnowned
|
||||
)
|
||||
}
|
||||
|
||||
constructor(
|
||||
permissionsService: PermissionsService,
|
||||
userService: UserService,
|
||||
private settingsService: SettingsService
|
||||
) {
|
||||
if (
|
||||
permissionsService.currentUserCan(
|
||||
PermissionAction.View,
|
||||
PermissionType.User
|
||||
)
|
||||
) {
|
||||
userService
|
||||
.listAll()
|
||||
.pipe(first())
|
||||
.subscribe({
|
||||
next: (result) => (this.users = result.results),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.selectionModel.clear()
|
||||
this.onChange()
|
||||
}
|
||||
|
||||
setFilter(type: OwnerFilterType) {
|
||||
this.selectionModel.ownerFilter = type
|
||||
if (this.selectionModel.ownerFilter === OwnerFilterType.SELF) {
|
||||
this.selectionModel.includeUsers = []
|
||||
this.selectionModel.excludeUsers = []
|
||||
this.selectionModel.userID = this.settingsService.currentUser.id
|
||||
this.selectionModel.hideUnowned = false
|
||||
} else if (this.selectionModel.ownerFilter === OwnerFilterType.NOT_SELF) {
|
||||
this.selectionModel.userID = null
|
||||
this.selectionModel.includeUsers = []
|
||||
this.selectionModel.excludeUsers = [this.settingsService.currentUser.id]
|
||||
this.selectionModel.hideUnowned = false
|
||||
} else if (this.selectionModel.ownerFilter === OwnerFilterType.NONE) {
|
||||
this.selectionModel.userID = null
|
||||
this.selectionModel.includeUsers = []
|
||||
this.selectionModel.excludeUsers = []
|
||||
this.selectionModel.hideUnowned = false
|
||||
} else if (this.selectionModel.ownerFilter === OwnerFilterType.UNOWNED) {
|
||||
this.selectionModel.userID = null
|
||||
this.selectionModel.includeUsers = []
|
||||
this.selectionModel.excludeUsers = []
|
||||
this.selectionModel.hideUnowned = false
|
||||
}
|
||||
this.onChange()
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.ownerFilterSet.emit(this.selectionModel)
|
||||
}
|
||||
|
||||
onUserSelect() {
|
||||
if (this.selectionModel.includeUsers?.length) {
|
||||
this.selectionModel.ownerFilter = OwnerFilterType.OTHERS
|
||||
} else {
|
||||
this.selectionModel.ownerFilter = OwnerFilterType.NONE
|
||||
}
|
||||
this.onChange()
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
<div class="row">
|
||||
<div class="col-auto mb-2 mb-xl-0" role="group" aria-label="Select">
|
||||
<div class="d-flex flex-wrap gap-4">
|
||||
<div class="d-flex align-items-center" role="group" aria-label="Select">
|
||||
<button class="btn btn-sm btn-outline-secondary" (click)="list.selectNone()">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#slash-circle" />
|
||||
</svg> <ng-container i18n>Cancel</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto mb-2 mb-xl-0 ms-auto ms-md-0" role="group" aria-label="Select">
|
||||
<label class="me-2 mb-0" i18n>Select:</label>
|
||||
<div class="d-flex align-items-center gap-2" role="group" aria-label="Select">
|
||||
<label class="me-2" i18n>Select:</label>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-outline-primary" (click)="list.selectPage()">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
@ -21,11 +21,9 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-100 d-xl-none"></div>
|
||||
<div class="col-auto mb-2 mb-xl-0">
|
||||
<div class="d-flex" *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||
<label class="ms-auto mt-1 mb-0 me-2" i18n>Edit:</label>
|
||||
<app-filterable-dropdown class="me-2 me-md-3" title="Tags" icon="tag-fill" i18n-title
|
||||
<div class="d-flex align-items-center gap-2" *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||
<label class="me-2" i18n>Edit:</label>
|
||||
<app-filterable-dropdown title="Tags" icon="tag-fill" i18n-title
|
||||
filterPlaceholder="Filter tags" i18n-filterPlaceholder
|
||||
[items]="tags"
|
||||
[disabled]="!userCanEditAll"
|
||||
@ -37,7 +35,7 @@
|
||||
[documentCounts]="tagDocumentCounts"
|
||||
(apply)="setTags($event)">
|
||||
</app-filterable-dropdown>
|
||||
<app-filterable-dropdown class="me-2 me-md-3" title="Correspondent" icon="person-fill" i18n-title
|
||||
<app-filterable-dropdown title="Correspondent" icon="person-fill" i18n-title
|
||||
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
|
||||
[items]="correspondents"
|
||||
[disabled]="!userCanEditAll"
|
||||
@ -48,7 +46,7 @@
|
||||
[documentCounts]="correspondentDocumentCounts"
|
||||
(apply)="setCorrespondents($event)">
|
||||
</app-filterable-dropdown>
|
||||
<app-filterable-dropdown class="me-2 me-md-3" title="Document type" icon="file-earmark-fill" i18n-title
|
||||
<app-filterable-dropdown title="Document type" icon="file-earmark-fill" i18n-title
|
||||
filterPlaceholder="Filter document types" i18n-filterPlaceholder
|
||||
[items]="documentTypes"
|
||||
[disabled]="!userCanEditAll"
|
||||
@ -59,7 +57,7 @@
|
||||
[documentCounts]="documentTypeDocumentCounts"
|
||||
(apply)="setDocumentTypes($event)">
|
||||
</app-filterable-dropdown>
|
||||
<app-filterable-dropdown class="me-2 me-md-3" title="Storage path" icon="folder-fill" i18n-title
|
||||
<app-filterable-dropdown title="Storage path" icon="folder-fill" i18n-title
|
||||
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
|
||||
[items]="storagePaths"
|
||||
[disabled]="!userCanEditAll"
|
||||
@ -70,18 +68,17 @@
|
||||
[documentCounts]="storagePathDocumentCounts"
|
||||
(apply)="setStoragePaths($event)">
|
||||
</app-filterable-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto ms-auto mb-2 mb-xl-0 d-flex">
|
||||
<div class="btn-toolbar me-2">
|
||||
<div class="d-flex align-items-center gap-2 ms-auto">
|
||||
<div class="btn-toolbar">
|
||||
|
||||
<button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="setPermissions()" [disabled]="!userOwnsAll || !userCanEditAll">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock" />
|
||||
</svg> <ng-container i18n>Permissions</ng-container>
|
||||
</svg><div class="d-none d-sm-inline"> <ng-container i18n>Permissions</ng-container></div>
|
||||
</button>
|
||||
|
||||
<div ngbDropdown class="me-2 d-flex">
|
||||
<div ngbDropdown>
|
||||
<button class="btn btn-sm btn-outline-primary" id="dropdownSelect" ngbDropdownToggle>
|
||||
<svg class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#three-dots" />
|
||||
@ -94,7 +91,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm me-2">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-sm btn-outline-primary" [disabled]="awaitingDownload" (click)="downloadSelected()">
|
||||
<svg *ngIf="!awaitingDownload" class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-down" />
|
||||
@ -134,7 +131,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm me-2">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()" *appIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }" [disabled]="!userOwnsAll">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
|
@ -106,6 +106,12 @@
|
||||
</svg>
|
||||
<small>{{document.created_date | customDate:'mediumDate'}}</small>
|
||||
</div>
|
||||
<div *ngIf="document.owner && document.owner !== settingsService.currentUser.id" class="list-group-item bg-light text-dark p-1 border-0">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock"/>
|
||||
</svg>
|
||||
<small>{{document.owner | username}}</small>
|
||||
</div>
|
||||
<div *ngIf="document.__search_hit__?.score" class="list-group-item bg-light text-dark border-0 d-flex p-0 ps-4 search-score">
|
||||
<small class="text-muted" i18n>Score:</small>
|
||||
<ngb-progressbar [type]="searchScoreClass" [value]="document.__search_hit__.score" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar>
|
||||
|
@ -23,7 +23,7 @@ import { ComponentWithPermissions } from '../../with-permissions/with-permission
|
||||
export class DocumentCardLargeComponent extends ComponentWithPermissions {
|
||||
constructor(
|
||||
private documentService: DocumentService,
|
||||
private settingsService: SettingsService
|
||||
public settingsService: SettingsService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
@ -38,15 +38,15 @@
|
||||
<div class="list-group list-group-flush border-0 pt-1 pb-2 card-info">
|
||||
<button *ngIf="document.document_type" type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle document type filter" 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 list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle storage path filter" 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#folder"/>
|
||||
</svg>
|
||||
<small>{{(document.storage_path$ | async)?.name}}</small>
|
||||
</button>
|
||||
@ -59,18 +59,23 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="ps-0 p-1" placement="top" [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>
|
||||
<div *ngIf="document.archive_serial_number" class="ps-0 p-1">
|
||||
<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>
|
||||
<small>#{{document.archive_serial_number}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="document.archive_serial_number" class="ps-0 p-1">
|
||||
<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>
|
||||
<div *ngIf="document.owner && document.owner !== settingsService.currentUser.id" class="ps-0 p-1">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock"/>
|
||||
</svg>
|
||||
<small>{{document.owner | username}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
|
@ -24,7 +24,7 @@ import { ComponentWithPermissions } from '../../with-permissions/with-permission
|
||||
export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
||||
constructor(
|
||||
private documentService: DocumentService,
|
||||
private settingsService: SettingsService
|
||||
public settingsService: SettingsService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
@ -81,15 +81,15 @@
|
||||
|
||||
</app-page-header>
|
||||
|
||||
<div class="row sticky-top pt-3 pt-sm-4 pb-2 pb-lg-4 bg-body">
|
||||
<div class="row sticky-top pt-3 pt-sm-4 pb-3 pb-lg-4 bg-body">
|
||||
<app-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" [selectionData]="list.selectionData" #filterEditor></app-filter-editor>
|
||||
<app-bulk-editor [hidden]="!isBulkEditing"></app-bulk-editor>
|
||||
</div>
|
||||
|
||||
|
||||
<ng-template #pagination>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<p>
|
||||
<div class="d-flex flex-wrap gap-3 justify-content-between align-items-center mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<ng-container *ngIf="list.isReloading">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
@ -98,9 +98,14 @@
|
||||
<ng-container *ngIf="!list.isReloading">
|
||||
<span i18n *ngIf="list.selected.size === 0">{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</span> <span i18n *ngIf="isFiltered">(filtered)</span>
|
||||
</ng-container>
|
||||
</p>
|
||||
<button *ngIf="!list.isReloading && isFiltered" class="btn btn-link py-0" (click)="resetFilters()">
|
||||
<svg fill="currentColor" class="buttonicon-sm">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg><small i18n>Reset filters</small>
|
||||
</button>
|
||||
</div>
|
||||
<ngb-pagination *ngIf="list.collectionSize" [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
|
||||
[rotate]="true" aria-label="Default pagination"></ngb-pagination>
|
||||
[rotate]="true" aria-label="Default pagination" size="sm"></ngb-pagination>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@ -142,6 +147,13 @@
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Title</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
appSortable="owner"
|
||||
title="Sort by owner" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Owner</th>
|
||||
<th *ngIf="notesEnabled" class="d-none d-xl-table-cell"
|
||||
appSortable="num_notes"
|
||||
title="Sort by notes" i18n-title
|
||||
@ -198,6 +210,9 @@
|
||||
<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>
|
||||
{{d.owner | username}}
|
||||
</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">
|
||||
|
@ -300,4 +300,8 @@ export class DocumentListComponent
|
||||
get notesEnabled(): boolean {
|
||||
return this.settingsService.get(SETTINGS_KEYS.NOTES_ENABLED)
|
||||
}
|
||||
|
||||
resetFilters() {
|
||||
this.filterEditor.resetSelected()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div class="row flex-wrap" tourAnchor="tour.documents-filter-editor">
|
||||
<div class="col mb-2 mb-xxl-0">
|
||||
<div class="col mb-3 mb-xxl-0">
|
||||
<div class="form-inline d-flex align-items-center">
|
||||
<div class="input-group input-group-sm flex-fill w-auto flex-nowrap">
|
||||
<div ngbDropdown>
|
||||
@ -12,8 +12,8 @@
|
||||
<option *ngFor="let m of textFilterModifiers" ngbDropdownItem [value]="m.id">{{m.label}}</option>
|
||||
</select>
|
||||
<button *ngIf="_textFilter" class="btn btn-link btn-sm px-0 position-absolute top-0 end-0 z-10" (click)="resetTextField()">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x me-1" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
|
||||
<svg fill="currentColor" class="buttonicon-sm me-1">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
</button>
|
||||
<input #textFilterInput class="form-control form-control-sm" type="text" [disabled]="textFilterModifierIsNull" [(ngModel)]="textFilter" (keyup)="textFilterKeyup($event)" [readonly]="textFilterTarget === 'fulltext-morelike'">
|
||||
@ -22,8 +22,8 @@
|
||||
</div>
|
||||
<div class="w-100 d-xxl-none"></div>
|
||||
<div class="col col-xl-auto">
|
||||
<div class="d-flex flex-wrap">
|
||||
<div class="d-flex flex-wrap mb-2 mb-xxl-0">
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<app-filterable-dropdown class="flex-fill" title="Tags" icon="tag-fill" i18n-title
|
||||
filterPlaceholder="Filter tags" i18n-filterPlaceholder
|
||||
[items]="tags"
|
||||
@ -49,7 +49,7 @@
|
||||
(opened)="onDocumentTypeDropdownOpen()"
|
||||
[documentCounts]="documentTypeDocumentCounts"
|
||||
[allowSelectNone]="true"></app-filterable-dropdown>
|
||||
<app-filterable-dropdown class="me-2 flex-fill" title="Storage path" icon="folder-fill" i18n-title
|
||||
<app-filterable-dropdown class="flex-fill" title="Storage path" icon="folder-fill" i18n-title
|
||||
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
|
||||
[items]="storagePaths"
|
||||
[(selectionModel)]="storagePathSelectionModel"
|
||||
@ -58,28 +58,33 @@
|
||||
[documentCounts]="storagePathDocumentCounts"
|
||||
[allowSelectNone]="true"></app-filterable-dropdown>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap">
|
||||
<app-date-dropdown class="mb-2 mb-xl-0"
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<app-date-dropdown
|
||||
title="Created" i18n-title
|
||||
(datesSet)="updateRules()"
|
||||
[(dateBefore)]="dateCreatedBefore"
|
||||
[(dateAfter)]="dateCreatedAfter"
|
||||
[(relativeDate)]="dateCreatedRelativeDate"></app-date-dropdown>
|
||||
<app-date-dropdown class="mb-2 mb-xl-0"
|
||||
<app-date-dropdown
|
||||
title="Added" i18n-title
|
||||
(datesSet)="updateRules()"
|
||||
[(dateBefore)]="dateAddedBefore"
|
||||
[(dateAfter)]="dateAddedAfter"
|
||||
[(relativeDate)]="dateAddedRelativeDate"></app-date-dropdown>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap">
|
||||
<app-permissions-filter-dropdown
|
||||
title="Permissions" i18n-title
|
||||
(ownerFilterSet)="updateRules()"
|
||||
[(selectionModel)]="permissionsSelectionModel"></app-permissions-filter-dropdown>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap d-none d-sm-inline-block">
|
||||
<button class="btn btn-outline-secondary btn-sm" [disabled]="!rulesModified" (click)="resetSelected()">
|
||||
<svg class="toolbaricon ms-n1" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"></use>
|
||||
</svg><ng-container i18n>Reset filters</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-100 d-xxl-none"></div>
|
||||
<div class="col col-xl-auto ps-xxl-0">
|
||||
<button class="btn btn-link btn-sm px-0" [disabled]="!rulesModified" (click)="resetSelected()">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x me-1 ms-n1" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
|
||||
</svg><ng-container i18n>Reset filters</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -43,6 +43,10 @@ import {
|
||||
FILTER_DOCUMENT_TYPE,
|
||||
FILTER_CORRESPONDENT,
|
||||
FILTER_STORAGE_PATH,
|
||||
FILTER_OWNER,
|
||||
FILTER_OWNER_DOES_NOT_INCLUDE,
|
||||
FILTER_OWNER_ISNULL,
|
||||
FILTER_OWNER_ANY,
|
||||
} from 'src/app/data/filter-rule-type'
|
||||
import {
|
||||
FilterableDropdownSelectionModel,
|
||||
@ -59,6 +63,11 @@ import { PaperlessDocument } from 'src/app/data/paperless-document'
|
||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||
import { RelativeDate } from '../../common/date-dropdown/date-dropdown.component'
|
||||
import {
|
||||
OwnerFilterType,
|
||||
PermissionsSelectionModel,
|
||||
} from '../../common/permissions-filter-dropdown/permissions-filter-dropdown.component'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
|
||||
const TEXT_FILTER_TARGET_TITLE = 'title'
|
||||
const TEXT_FILTER_TARGET_TITLE_CONTENT = 'title-content'
|
||||
@ -136,6 +145,15 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
|
||||
case FILTER_ASN:
|
||||
return $localize`ASN: ${rule.value}`
|
||||
|
||||
case FILTER_OWNER:
|
||||
return $localize`Owner: ${rule.value}`
|
||||
|
||||
case FILTER_OWNER_DOES_NOT_INCLUDE:
|
||||
return $localize`Owner not in: ${rule.value}`
|
||||
|
||||
case FILTER_OWNER_ISNULL:
|
||||
return $localize`Without an owner`
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +165,8 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
private tagService: TagService,
|
||||
private correspondentService: CorrespondentService,
|
||||
private documentService: DocumentService,
|
||||
private storagePathService: StoragePathService
|
||||
private storagePathService: StoragePathService,
|
||||
private settingsService: SettingsService
|
||||
) {}
|
||||
|
||||
@ViewChild('textFilterInput')
|
||||
@ -241,6 +260,8 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
dateCreatedRelativeDate: RelativeDate
|
||||
dateAddedRelativeDate: RelativeDate
|
||||
|
||||
permissionsSelectionModel = new PermissionsSelectionModel()
|
||||
|
||||
_unmodifiedFilterRules: FilterRule[] = []
|
||||
_filterRules: FilterRule[] = []
|
||||
|
||||
@ -274,6 +295,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
this.dateCreatedRelativeDate = null
|
||||
this.dateAddedRelativeDate = null
|
||||
this.textFilterModifier = TEXT_FILTER_MODIFIER_EQUALS
|
||||
this.permissionsSelectionModel.clear()
|
||||
|
||||
value.forEach((rule) => {
|
||||
switch (rule.rule_type) {
|
||||
@ -441,6 +463,35 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
this.textFilterModifier = TEXT_FILTER_MODIFIER_LT
|
||||
this._textFilter = rule.value
|
||||
break
|
||||
case FILTER_OWNER:
|
||||
this.permissionsSelectionModel.ownerFilter = OwnerFilterType.SELF
|
||||
this.permissionsSelectionModel.hideUnowned = false
|
||||
if (rule.value)
|
||||
this.permissionsSelectionModel.userID = parseInt(rule.value, 10)
|
||||
break
|
||||
case FILTER_OWNER_ANY:
|
||||
this.permissionsSelectionModel.ownerFilter = OwnerFilterType.OTHERS
|
||||
if (rule.value)
|
||||
this.permissionsSelectionModel.includeUsers.push(
|
||||
parseInt(rule.value, 10)
|
||||
)
|
||||
break
|
||||
case FILTER_OWNER_DOES_NOT_INCLUDE:
|
||||
this.permissionsSelectionModel.ownerFilter = OwnerFilterType.NOT_SELF
|
||||
if (rule.value)
|
||||
this.permissionsSelectionModel.excludeUsers.push(
|
||||
parseInt(rule.value, 10)
|
||||
)
|
||||
break
|
||||
case FILTER_OWNER_ISNULL:
|
||||
if (rule.value === 'true' || rule.value === '1') {
|
||||
this.permissionsSelectionModel.hideUnowned = false
|
||||
this.permissionsSelectionModel.ownerFilter = OwnerFilterType.UNOWNED
|
||||
} else {
|
||||
this.permissionsSelectionModel.hideUnowned =
|
||||
rule.value === 'false' || rule.value === '0'
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
this.rulesModified = filterRulesDiffer(
|
||||
@ -702,6 +753,40 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.permissionsSelectionModel.ownerFilter == OwnerFilterType.SELF) {
|
||||
filterRules.push({
|
||||
rule_type: FILTER_OWNER,
|
||||
value: this.permissionsSelectionModel.userID.toString(),
|
||||
})
|
||||
} else if (
|
||||
this.permissionsSelectionModel.ownerFilter == OwnerFilterType.NOT_SELF
|
||||
) {
|
||||
filterRules.push({
|
||||
rule_type: FILTER_OWNER_DOES_NOT_INCLUDE,
|
||||
value: this.permissionsSelectionModel.excludeUsers?.join(','),
|
||||
})
|
||||
} else if (
|
||||
this.permissionsSelectionModel.ownerFilter == OwnerFilterType.OTHERS
|
||||
) {
|
||||
filterRules.push({
|
||||
rule_type: FILTER_OWNER_ANY,
|
||||
value: this.permissionsSelectionModel.includeUsers?.join(','),
|
||||
})
|
||||
} else if (
|
||||
this.permissionsSelectionModel.ownerFilter == OwnerFilterType.UNOWNED
|
||||
) {
|
||||
filterRules.push({
|
||||
rule_type: FILTER_OWNER_ISNULL,
|
||||
value: 'true',
|
||||
})
|
||||
}
|
||||
|
||||
if (this.permissionsSelectionModel.hideUnowned) {
|
||||
filterRules.push({
|
||||
rule_type: FILTER_OWNER_ISNULL,
|
||||
value: 'false',
|
||||
})
|
||||
}
|
||||
return filterRules
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" *appIfPermissions="{ action: PermissionAction.Add, type: permissionType }" i18n>Create</button>
|
||||
</app-page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md mb-2 mb-xl-0">
|
||||
<div class="form-inline d-flex align-items-center">
|
||||
<label class="text-muted me-2 mb-0" i18n>Filter by:</label>
|
||||
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ngb-pagination class="col-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" aria-label="Default pagination"></ngb-pagination>
|
||||
<ngb-pagination class="col-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped align-middle border shadow-sm">
|
||||
@ -72,5 +72,5 @@
|
||||
|
||||
<div class="d-flex">
|
||||
<div i18n *ngIf="collectionSize > 0">{collectionSize, plural, =1 {One {{typeName}}} other {{{collectionSize || 0}} total {{typeNamePlural}}}}</div>
|
||||
<ngb-pagination *ngIf="collectionSize > 20" class="ms-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" aria-label="Default pagination"></ngb-pagination>
|
||||
<ngb-pagination *ngIf="collectionSize > 20" class="ms-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||
</div>
|
||||
|
@ -125,8 +125,8 @@
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<button class="btn btn-link btn-sm pt-2 ps-0" [disabled]="!this.settingsForm.get('themeColor').value" (click)="clearThemeColor()">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x me-1" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
|
||||
<svg fill="currentColor" class="buttonicon-sm me-1">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg><ng-container i18n>Reset</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -41,6 +41,11 @@ export const FILTER_TITLE_CONTENT = 19
|
||||
export const FILTER_FULLTEXT_QUERY = 20
|
||||
export const FILTER_FULLTEXT_MORELIKE = 21
|
||||
|
||||
export const FILTER_OWNER = 32
|
||||
export const FILTER_OWNER_ANY = 33
|
||||
export const FILTER_OWNER_ISNULL = 34
|
||||
export const FILTER_OWNER_DOES_NOT_INCLUDE = 35
|
||||
|
||||
export const FILTER_RULE_TYPES: FilterRuleType[] = [
|
||||
{
|
||||
id: FILTER_TITLE,
|
||||
@ -242,6 +247,30 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [
|
||||
datatype: 'number',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
id: FILTER_OWNER,
|
||||
filtervar: 'owner__id',
|
||||
datatype: 'number',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
id: FILTER_OWNER_ANY,
|
||||
filtervar: 'owner__id__in',
|
||||
datatype: 'number',
|
||||
multi: true,
|
||||
},
|
||||
{
|
||||
id: FILTER_OWNER_ISNULL,
|
||||
filtervar: 'owner__isnull',
|
||||
datatype: 'boolean',
|
||||
multi: false,
|
||||
},
|
||||
{
|
||||
id: FILTER_OWNER_DOES_NOT_INCLUDE,
|
||||
filtervar: 'owner__id__none',
|
||||
datatype: 'number',
|
||||
multi: true,
|
||||
},
|
||||
]
|
||||
|
||||
export interface FilterRuleType {
|
||||
|
42
src-ui/src/app/pipes/username.pipe.ts
Normal file
42
src-ui/src/app/pipes/username.pipe.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { UserService } from '../services/rest/user.service'
|
||||
import {
|
||||
PermissionAction,
|
||||
PermissionType,
|
||||
PermissionsService,
|
||||
} from '../services/permissions.service'
|
||||
import { PaperlessUser } from '../data/paperless-user'
|
||||
|
||||
@Pipe({
|
||||
name: 'username',
|
||||
})
|
||||
export class UsernamePipe implements PipeTransform {
|
||||
users: PaperlessUser[]
|
||||
|
||||
constructor(
|
||||
permissionsService: PermissionsService,
|
||||
userService: UserService
|
||||
) {
|
||||
if (
|
||||
permissionsService.currentUserCan(
|
||||
PermissionAction.View,
|
||||
PermissionType.User
|
||||
)
|
||||
) {
|
||||
userService.listAll().subscribe((r) => (this.users = r.results))
|
||||
}
|
||||
}
|
||||
|
||||
transform(userID: number): string {
|
||||
return this.users
|
||||
? this.getName(this.users.find((u) => u.id === userID)) ?? ''
|
||||
: $localize`Shared`
|
||||
}
|
||||
|
||||
getName(user: PaperlessUser): string {
|
||||
if (!user) return ''
|
||||
const name = [user.first_name, user.last_name].join(' ')
|
||||
if (name.length > 1) return name.trim()
|
||||
return user.username
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ export const DOCUMENT_SORT_FIELDS = [
|
||||
{ field: 'added', name: $localize`Added` },
|
||||
{ field: 'modified', name: $localize`Modified` },
|
||||
{ field: 'num_notes', name: $localize`Notes` },
|
||||
{ field: 'owner', name: $localize`Owner` },
|
||||
]
|
||||
|
||||
export const DOCUMENT_SORT_FIELDS_FULLTEXT = [
|
||||
|
@ -304,6 +304,10 @@ textarea,
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
ul.pagination {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.page-link {
|
||||
color: var(--bs-secondary);
|
||||
background-color: var(--bs-body-bg);
|
||||
@ -317,7 +321,6 @@ textarea,
|
||||
|
||||
.page-item.active .page-link {
|
||||
background-color: var(--bs-primary);
|
||||
border-color: var(--bs-primary) !important;
|
||||
color: var(--bs-light);
|
||||
}
|
||||
|
||||
@ -425,6 +428,11 @@ textarea,
|
||||
height: 1.2em;
|
||||
}
|
||||
|
||||
.buttonicon-sm {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.sidebaricon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
@ -75,6 +75,10 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='htt
|
||||
color: var(--bs-body-color) !important;
|
||||
}
|
||||
|
||||
.btn {
|
||||
--bs-btn-disabled-opacity: 0.35;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
&:hover, &:focus, &.active, &:active {
|
||||
color: var(--bs-body-color) !important;
|
||||
|
@ -105,6 +105,8 @@ class DocumentFilterSet(FilterSet):
|
||||
|
||||
title_content = TitleContentFilter()
|
||||
|
||||
owner__id__none = ObjectFilter(field_name="owner", exclude=True)
|
||||
|
||||
class Meta:
|
||||
model = Document
|
||||
fields = {
|
||||
@ -125,6 +127,8 @@ class DocumentFilterSet(FilterSet):
|
||||
"storage_path": ["isnull"],
|
||||
"storage_path__id": ID_KWARGS,
|
||||
"storage_path__name": CHAR_KWARGS,
|
||||
"owner": ["isnull"],
|
||||
"owner__id": ID_KWARGS,
|
||||
}
|
||||
|
||||
|
||||
|
@ -208,11 +208,13 @@ class DelayedQuery:
|
||||
for document_type_id in v.split(","):
|
||||
criterias.append(query.Not(query.Term("type_id", document_type_id)))
|
||||
elif k == "correspondent__isnull":
|
||||
criterias.append(query.Term("has_correspondent", v == "false"))
|
||||
criterias.append(
|
||||
query.Term("has_correspondent", self.evalBoolean(v) is False),
|
||||
)
|
||||
elif k == "is_tagged":
|
||||
criterias.append(query.Term("has_tag", v == "true"))
|
||||
criterias.append(query.Term("has_tag", self.evalBoolean(v)))
|
||||
elif k == "document_type__isnull":
|
||||
criterias.append(query.Term("has_type", v == "false"))
|
||||
criterias.append(query.Term("has_type", self.evalBoolean(v) is False))
|
||||
elif k == "created__date__lt":
|
||||
criterias.append(
|
||||
query.DateRange("created", start=None, end=isoparse(v)),
|
||||
@ -236,7 +238,19 @@ class DelayedQuery:
|
||||
for storage_path_id in v.split(","):
|
||||
criterias.append(query.Not(query.Term("path_id", storage_path_id)))
|
||||
elif k == "storage_path__isnull":
|
||||
criterias.append(query.Term("has_path", v == "false"))
|
||||
criterias.append(query.Term("has_path", self.evalBoolean(v) is False))
|
||||
elif k == "owner__isnull":
|
||||
criterias.append(query.Term("has_owner", self.evalBoolean(v) is False))
|
||||
elif k == "owner__id":
|
||||
criterias.append(query.Term("owner_id", v))
|
||||
elif k == "owner__id__in":
|
||||
owners_in = []
|
||||
for owner_id in v.split(","):
|
||||
owners_in.append(query.Term("owner_id", owner_id))
|
||||
criterias.append(query.Or(owners_in))
|
||||
elif k == "owner__id__none":
|
||||
for owner_id in v.split(","):
|
||||
criterias.append(query.Not(query.Term("owner_id", owner_id)))
|
||||
|
||||
user_criterias = [query.Term("has_owner", False)]
|
||||
if "user" in self.query_params:
|
||||
@ -254,6 +268,9 @@ class DelayedQuery:
|
||||
else:
|
||||
return query.Or(user_criterias) if len(user_criterias) > 0 else None
|
||||
|
||||
def evalBoolean(self, val):
|
||||
return val.lower() in {"true", "1"}
|
||||
|
||||
def _get_query_sortedby(self):
|
||||
if "ordering" not in self.query_params:
|
||||
return None, False
|
||||
@ -269,6 +286,7 @@ class DelayedQuery:
|
||||
"document_type__name": "type",
|
||||
"archive_serial_number": "asn",
|
||||
"num_notes": "num_notes",
|
||||
"owner": "owner",
|
||||
}
|
||||
|
||||
if field.startswith("-"):
|
||||
|
@ -0,0 +1,58 @@
|
||||
# Generated by Django 4.1.7 on 2023-05-04 04:11
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("documents", "1035_rename_comment_note"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="savedviewfilterrule",
|
||||
name="rule_type",
|
||||
field=models.PositiveIntegerField(
|
||||
choices=[
|
||||
(0, "title contains"),
|
||||
(1, "content contains"),
|
||||
(2, "ASN is"),
|
||||
(3, "correspondent is"),
|
||||
(4, "document type is"),
|
||||
(5, "is in inbox"),
|
||||
(6, "has tag"),
|
||||
(7, "has any tag"),
|
||||
(8, "created before"),
|
||||
(9, "created after"),
|
||||
(10, "created year is"),
|
||||
(11, "created month is"),
|
||||
(12, "created day is"),
|
||||
(13, "added before"),
|
||||
(14, "added after"),
|
||||
(15, "modified before"),
|
||||
(16, "modified after"),
|
||||
(17, "does not have tag"),
|
||||
(18, "does not have ASN"),
|
||||
(19, "title or content contains"),
|
||||
(20, "fulltext query"),
|
||||
(21, "more like this"),
|
||||
(22, "has tags in"),
|
||||
(23, "ASN greater than"),
|
||||
(24, "ASN less than"),
|
||||
(25, "storage path is"),
|
||||
(26, "has correspondent in"),
|
||||
(27, "does not have correspondent in"),
|
||||
(28, "has document type in"),
|
||||
(29, "does not have document type in"),
|
||||
(30, "has storage path in"),
|
||||
(31, "does not have storage path in"),
|
||||
(32, "owner is"),
|
||||
(33, "has owner in"),
|
||||
(34, "does not have owner"),
|
||||
(35, "does not have owner in"),
|
||||
],
|
||||
verbose_name="rule type",
|
||||
),
|
||||
),
|
||||
]
|
@ -448,6 +448,10 @@ class SavedViewFilterRule(models.Model):
|
||||
(29, _("does not have document type in")),
|
||||
(30, _("has storage path in")),
|
||||
(31, _("does not have storage path in")),
|
||||
(32, _("owner is")),
|
||||
(33, _("has owner in")),
|
||||
(34, _("does not have owner")),
|
||||
(35, _("does not have owner in")),
|
||||
]
|
||||
|
||||
saved_view = models.ForeignKey(
|
||||
|
@ -469,6 +469,98 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
results = response.data["results"]
|
||||
self.assertEqual(len(results), 0)
|
||||
|
||||
def test_document_owner_filters(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Documents with owners, with and without granted permissions
|
||||
WHEN:
|
||||
- User filters by owner
|
||||
THEN:
|
||||
- Owner filters work correctly but still respect permissions
|
||||
"""
|
||||
u1 = User.objects.create_user("user1")
|
||||
u2 = User.objects.create_user("user2")
|
||||
u1.user_permissions.add(*Permission.objects.filter(codename="view_document"))
|
||||
u2.user_permissions.add(*Permission.objects.filter(codename="view_document"))
|
||||
|
||||
u1_doc1 = Document.objects.create(
|
||||
title="none1",
|
||||
checksum="A",
|
||||
mime_type="application/pdf",
|
||||
owner=u1,
|
||||
)
|
||||
Document.objects.create(
|
||||
title="none2",
|
||||
checksum="B",
|
||||
mime_type="application/pdf",
|
||||
owner=u2,
|
||||
)
|
||||
u0_doc1 = Document.objects.create(
|
||||
title="none3",
|
||||
checksum="C",
|
||||
mime_type="application/pdf",
|
||||
)
|
||||
u1_doc2 = Document.objects.create(
|
||||
title="none4",
|
||||
checksum="D",
|
||||
mime_type="application/pdf",
|
||||
owner=u1,
|
||||
)
|
||||
u2_doc2 = Document.objects.create(
|
||||
title="none5",
|
||||
checksum="E",
|
||||
mime_type="application/pdf",
|
||||
owner=u2,
|
||||
)
|
||||
|
||||
self.client.force_authenticate(user=u1)
|
||||
assign_perm("view_document", u1, u2_doc2)
|
||||
|
||||
# Will not show any u1 docs or u2_doc1 which isn't shared
|
||||
response = self.client.get(f"/api/documents/?owner__id__none={u1.id}")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
results = response.data["results"]
|
||||
self.assertEqual(len(results), 2)
|
||||
self.assertCountEqual(
|
||||
[results[0]["id"], results[1]["id"]],
|
||||
[u0_doc1.id, u2_doc2.id],
|
||||
)
|
||||
|
||||
# Will not show any u1 docs, u0_doc1 which has no owner or u2_doc1 which isn't shared
|
||||
response = self.client.get(
|
||||
f"/api/documents/?owner__id__none={u1.id}&owner__isnull=false",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
results = response.data["results"]
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertCountEqual([results[0]["id"]], [u2_doc2.id])
|
||||
|
||||
# Will not show any u1 docs, u2_doc2 which is shared but has owner
|
||||
response = self.client.get(
|
||||
f"/api/documents/?owner__id__none={u1.id}&owner__isnull=true",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
results = response.data["results"]
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertCountEqual([results[0]["id"]], [u0_doc1.id])
|
||||
|
||||
# Will not show any u1 docs or u2_doc1 which is not shared
|
||||
response = self.client.get(f"/api/documents/?owner__id={u2.id}")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
results = response.data["results"]
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertCountEqual([results[0]["id"]], [u2_doc2.id])
|
||||
|
||||
# Will not show u2_doc1 which is not shared
|
||||
response = self.client.get(f"/api/documents/?owner__id__in={u1.id},{u2.id}")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
results = response.data["results"]
|
||||
self.assertEqual(len(results), 3)
|
||||
self.assertCountEqual(
|
||||
[results[0]["id"], results[1]["id"], results[2]["id"]],
|
||||
[u1_doc1.id, u1_doc2.id, u2_doc2.id],
|
||||
)
|
||||
|
||||
def test_search(self):
|
||||
d1 = Document.objects.create(
|
||||
title="invoice",
|
||||
@ -996,15 +1088,15 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
)
|
||||
|
||||
self.assertCountEqual(
|
||||
search_query("&storage_path__isnull"),
|
||||
search_query("&storage_path__isnull=true"),
|
||||
[d1.id, d2.id, d3.id, d4.id, d5.id],
|
||||
)
|
||||
self.assertCountEqual(
|
||||
search_query("&correspondent__isnull"),
|
||||
search_query("&correspondent__isnull=true"),
|
||||
[d2.id, d3.id, d4.id, d5.id, d7.id],
|
||||
)
|
||||
self.assertCountEqual(
|
||||
search_query("&document_type__isnull"),
|
||||
search_query("&document_type__isnull=true"),
|
||||
[d1.id, d3.id, d4.id, d5.id, d7.id],
|
||||
)
|
||||
self.assertCountEqual(
|
||||
@ -1116,18 +1208,30 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
self.assertEqual(r.data["count"], 2)
|
||||
r = self.client.get("/api/documents/?query=test&document_type__id__none=1")
|
||||
self.assertEqual(r.data["count"], 2)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__none={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__in={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get(
|
||||
f"/api/documents/?query=test&owner__id__none={u1.id}&owner__isnull=true",
|
||||
)
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
|
||||
self.client.force_authenticate(user=u2)
|
||||
r = self.client.get("/api/documents/?query=test")
|
||||
self.assertEqual(r.data["count"], 3)
|
||||
r = self.client.get("/api/documents/?query=test&document_type__id__none=1")
|
||||
self.assertEqual(r.data["count"], 3)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__none={u2.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
|
||||
self.client.force_authenticate(user=superuser)
|
||||
r = self.client.get("/api/documents/?query=test")
|
||||
self.assertEqual(r.data["count"], 4)
|
||||
r = self.client.get("/api/documents/?query=test&document_type__id__none=1")
|
||||
self.assertEqual(r.data["count"], 4)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__none={u1.id}")
|
||||
self.assertEqual(r.data["count"], 3)
|
||||
|
||||
def test_search_filtering_with_object_perms(self):
|
||||
"""
|
||||
@ -1157,6 +1261,14 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
self.assertEqual(r.data["count"], 2)
|
||||
r = self.client.get("/api/documents/?query=test&document_type__id__none=1")
|
||||
self.assertEqual(r.data["count"], 2)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__none={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__in={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get("/api/documents/?query=test&owner__isnull=true")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
|
||||
assign_perm("view_document", u1, d2)
|
||||
assign_perm("view_document", u1, d3)
|
||||
@ -1170,6 +1282,14 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
self.assertEqual(r.data["count"], 4)
|
||||
r = self.client.get("/api/documents/?query=test&document_type__id__none=1")
|
||||
self.assertEqual(r.data["count"], 4)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__none={u1.id}")
|
||||
self.assertEqual(r.data["count"], 3)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__in={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get("/api/documents/?query=test&owner__isnull=true")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
|
||||
def test_search_sorting(self):
|
||||
u1 = User.objects.create_user("user1")
|
||||
@ -1251,6 +1371,14 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
search_query("&ordering=-num_notes"),
|
||||
[d1.id, d3.id, d2.id],
|
||||
)
|
||||
self.assertListEqual(
|
||||
search_query("&ordering=owner"),
|
||||
[d1.id, d2.id, d3.id],
|
||||
)
|
||||
self.assertListEqual(
|
||||
search_query("&ordering=-owner"),
|
||||
[d3.id, d2.id, d1.id],
|
||||
)
|
||||
|
||||
def test_pagination_all(self):
|
||||
"""
|
||||
|
@ -264,6 +264,7 @@ class DocumentViewSet(
|
||||
"added",
|
||||
"archive_serial_number",
|
||||
"num_notes",
|
||||
"owner",
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user