"content": {
"size": -1,
"mimeType": "application/json",
- "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true}]}"
+ "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]}]}"
},
"headersSize": -1,
"bodySize": -1,
"content": {
"size": -1,
"mimeType": "application/json",
- "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true}]}"
+ "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]}]}"
},
"headersSize": -1,
"bodySize": -1,
"content": {
"size": -1,
"mimeType": "application/json",
- "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true}]}"
+ "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]}]}"
},
"headersSize": -1,
"bodySize": -1,
"content": {
"size": -1,
"mimeType": "application/json",
- "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true}]}"
+ "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]}]}"
},
"headersSize": -1,
"bodySize": -1,
test('change views', async ({ page }) => {
await page.routeFromHAR(REQUESTS_HAR5, { notFound: 'fallback' })
await page.goto('/documents')
- await page.locator('pngx-page-header label').first().click()
+ await page.locator('.btn-group label').first().click()
await expect(page.locator('pngx-document-list table')).toBeVisible()
- await page.locator('pngx-page-header label').nth(1).click()
+ await page.locator('.btn-group label').nth(1).click()
await expect(page.locator('pngx-document-card-small').first()).toBeAttached()
- await page.locator('pngx-page-header label').nth(2).click()
+ await page.locator('.btn-group label').nth(2).click()
await expect(page.locator('pngx-document-card-large').first()).toBeAttached()
})
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
<context context-type="linenumber">14,15</context>
</context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/input/drag-drop-select/drag-drop-select.component.html</context>
+ <context context-type="linenumber">12</context>
+ </context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/tags/tags.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
- <context context-type="linenumber">376</context>
+ <context context-type="linenumber">395</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
- <context context-type="linenumber">364</context>
+ <context context-type="linenumber">383</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</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">93</context>
+ <context context-type="linenumber">109</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</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">175</context>
+ <context context-type="linenumber">209</context>
</context-group>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
- <context context-type="linenumber">33</context>
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">62</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">95</context>
</context-group>
</trans-unit>
<trans-unit id="293524471897878391" datatype="html">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
- <context context-type="linenumber">54</context>
- </context-group>
- </trans-unit>
- <trans-unit id="8953033926734869941" datatype="html">
- <source>Name</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
- <context context-type="linenumber">328</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
- <context context-type="linenumber">36</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.html</context>
- <context context-type="linenumber">21</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.html</context>
- <context context-type="linenumber">58</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html</context>
- <context context-type="linenumber">12</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
- <context context-type="linenumber">11</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html</context>
- <context context-type="linenumber">13</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component.html</context>
- <context context-type="linenumber">13</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html</context>
- <context context-type="linenumber">13</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
- <context context-type="linenumber">13</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html</context>
- <context context-type="linenumber">12</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html</context>
- <context context-type="linenumber">11</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
- <context context-type="linenumber">13</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html</context>
- <context context-type="linenumber">8</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.html</context>
- <context context-type="linenumber">17</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context>
- <context context-type="linenumber">20</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context>
- <context context-type="linenumber">64</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
- <context context-type="linenumber">20</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
- <context context-type="linenumber">20</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
- <context context-type="linenumber">20</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
- <context context-type="linenumber">20</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
- <context context-type="linenumber">37</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
- <context context-type="linenumber">37</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
- <context context-type="linenumber">37</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
- <context context-type="linenumber">37</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context>
- <context context-type="linenumber">17</context>
- </context-group>
- </trans-unit>
- <trans-unit id="9187755754633397589" datatype="html">
- <source>Â <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="visually-hidden">"/>Appears on<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/></source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
- <context context-type="linenumber">332,333</context>
+ <context context-type="linenumber">70</context>
</context-group>
</trans-unit>
<trans-unit id="4104807402967139762" datatype="html">
<source>Delete</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
- <context context-type="linenumber">346</context>
+ <context context-type="linenumber">345</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.html</context>
<context context-type="linenumber">38</context>
</context-group>
</trans-unit>
+ <trans-unit id="6338800642797811873" datatype="html">
+ <source>Documents page size</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
+ <context context-type="linenumber">356</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="4729108320774167624" datatype="html">
+ <source>Display as</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
+ <context context-type="linenumber">359</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="1358239534403218079" datatype="html">
+ <source>Table</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
+ <context context-type="linenumber">361</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="4236040382842528005" datatype="html">
+ <source>Small Cards</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
+ <context context-type="linenumber">362</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="8569593958139569111" datatype="html">
+ <source>Large Cards</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
+ <context context-type="linenumber">363</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="8461842260159597706" datatype="html">
+ <source>Show</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
+ <context context-type="linenumber">367</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">17</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="5607669932062416162" datatype="html">
+ <source>Default</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
+ <context context-type="linenumber">367</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
+ <context context-type="linenumber">113</context>
+ </context-group>
+ </trans-unit>
<trans-unit id="7877440816920439876" datatype="html">
<source>No saved views defined.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
- <context context-type="linenumber">358</context>
+ <context context-type="linenumber">376</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="2159130950882492111" datatype="html">
+ <source>Cancel</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
+ <context context-type="linenumber">396</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/confirm-dialog/confirm-dialog.component.ts</context>
+ <context context-type="linenumber">44</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html</context>
+ <context context-type="linenumber">27</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
+ <context context-type="linenumber">18</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html</context>
+ <context context-type="linenumber">29</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component.html</context>
+ <context context-type="linenumber">19</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html</context>
+ <context context-type="linenumber">39</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
+ <context context-type="linenumber">50</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html</context>
+ <context context-type="linenumber">28</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html</context>
+ <context context-type="linenumber">30</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html</context>
+ <context context-type="linenumber">42</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
+ <context context-type="linenumber">113</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context>
+ <context context-type="linenumber">25</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
+ <context context-type="linenumber">98</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/select-dialog/select-dialog.component.html</context>
+ <context context-type="linenumber">12</context>
+ </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">4</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html</context>
+ <context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit id="6839066544204061364" datatype="html">
<source>Use system language</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
- <context context-type="linenumber">61</context>
+ <context context-type="linenumber">62</context>
</context-group>
</trans-unit>
<trans-unit id="7729897675462249787" datatype="html">
<source>Use date format of display language</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
- <context context-type="linenumber">64</context>
+ <context context-type="linenumber">65</context>
</context-group>
</trans-unit>
<trans-unit id="1235706724900303689" datatype="html">
<source>Error retrieving users</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
- <context context-type="linenumber">183</context>
+ <context context-type="linenumber">188</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
<source>Error retrieving groups</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
- <context context-type="linenumber">202</context>
+ <context context-type="linenumber">207</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
<source>Saved view "<x id="PH" equiv-text="savedView.name"/>" deleted.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
- <context context-type="linenumber">410</context>
+ <context context-type="linenumber">421</context>
</context-group>
</trans-unit>
<trans-unit id="7217000812750597833" datatype="html">
<source>Settings were saved successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
- <context context-type="linenumber">536</context>
+ <context context-type="linenumber">547</context>
</context-group>
</trans-unit>
<trans-unit id="525012668859298131" datatype="html">
<source>Settings were saved successfully. Reload is required to apply some changes.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
- <context context-type="linenumber">540</context>
+ <context context-type="linenumber">551</context>
</context-group>
</trans-unit>
<trans-unit id="8491974984518503778" datatype="html">
<source>Reload now</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
- <context context-type="linenumber">541</context>
+ <context context-type="linenumber">552</context>
</context-group>
</trans-unit>
<trans-unit id="3011185103048412841" datatype="html">
<source>An error occurred while saving settings.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
- <context context-type="linenumber">551</context>
+ <context context-type="linenumber">562</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
<source>Error while storing settings on server.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.ts</context>
- <context context-type="linenumber">585</context>
+ <context context-type="linenumber">596</context>
</context-group>
</trans-unit>
<trans-unit id="2991443309752293110" datatype="html">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
- <context context-type="linenumber">3</context>
+ <context context-type="linenumber">3</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
+ <context context-type="linenumber">3</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="8953033926734869941" datatype="html">
+ <source>Name</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
+ <context context-type="linenumber">36</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.html</context>
+ <context context-type="linenumber">21</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.html</context>
+ <context context-type="linenumber">58</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html</context>
+ <context context-type="linenumber">12</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
+ <context context-type="linenumber">11</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html</context>
+ <context context-type="linenumber">13</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component.html</context>
+ <context context-type="linenumber">13</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html</context>
+ <context context-type="linenumber">13</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
+ <context context-type="linenumber">13</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html</context>
+ <context context-type="linenumber">12</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html</context>
+ <context context-type="linenumber">11</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
+ <context context-type="linenumber">13</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html</context>
+ <context context-type="linenumber">8</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.html</context>
+ <context context-type="linenumber">17</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context>
+ <context context-type="linenumber">20</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context>
+ <context context-type="linenumber">64</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
+ <context context-type="linenumber">20</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
+ <context context-type="linenumber">20</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
+ <context context-type="linenumber">20</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
+ <context context-type="linenumber">20</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
+ <context context-type="linenumber">37</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
+ <context context-type="linenumber">37</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
+ <context context-type="linenumber">37</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
+ <context context-type="linenumber">37</context>
</context-group>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
- <context context-type="linenumber">3</context>
+ <context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context>
+ <context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit id="4207916966377787111" datatype="html">
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
<context context-type="linenumber">37</context>
</context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
- <context context-type="linenumber">16</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">201</context>
+ <context context-type="linenumber">236</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">76</context>
</context-group>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
- <context context-type="linenumber">30</context>
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">38</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">92</context>
</context-group>
</trans-unit>
<trans-unit id="5968132631442328843" datatype="html">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
- <context context-type="linenumber">33</context>
+ <context context-type="linenumber">43</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>
- <context context-type="linenumber">53</context>
+ <context context-type="linenumber">57</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
- <context context-type="linenumber">91</context>
+ <context context-type="linenumber">120</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.html</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
- <context context-type="linenumber">99</context>
+ <context context-type="linenumber">126</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="sourcefile">src/app/components/common/input/tags/tags.component.ts</context>
<context context-type="linenumber">63</context>
</context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
- <context context-type="linenumber">19</context>
- </context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
<context context-type="linenumber">58</context>
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
<context context-type="linenumber">21</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">191</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">33</context>
</context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">46</context>
+ </context-group>
</trans-unit>
<trans-unit id="4369111787961525769" datatype="html">
<source>Document Types</source>
<context context-type="linenumber">483</context>
</context-group>
</trans-unit>
- <trans-unit id="2159130950882492111" datatype="html">
- <source>Cancel</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/confirm-dialog/confirm-dialog.component.ts</context>
- <context context-type="linenumber">44</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html</context>
- <context context-type="linenumber">27</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
- <context context-type="linenumber">18</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html</context>
- <context context-type="linenumber">29</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component.html</context>
- <context context-type="linenumber">19</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html</context>
- <context context-type="linenumber">39</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
- <context context-type="linenumber">50</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html</context>
- <context context-type="linenumber">28</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html</context>
- <context context-type="linenumber">30</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html</context>
- <context context-type="linenumber">42</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
- <context context-type="linenumber">113</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context>
- <context context-type="linenumber">25</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
- <context context-type="linenumber">98</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/select-dialog/select-dialog.component.html</context>
- <context context-type="linenumber">12</context>
- </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">4</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html</context>
- <context context-type="linenumber">20</context>
- </context-group>
- </trans-unit>
<trans-unit id="994016933065248559" datatype="html">
<source>Documents:</source>
<context-group purpose="location">
<context context-type="linenumber">28</context>
</context-group>
</trans-unit>
+ <trans-unit id="2509141182388535183" datatype="html">
+ <source>View</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/custom-field-display/custom-field-display.component.html</context>
+ <context context-type="linenumber">15</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
+ <context context-type="linenumber">34</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">10</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>
+ <context context-type="linenumber">62</context>
+ </context-group>
+ </trans-unit>
<trans-unit id="3972154626835212608" datatype="html">
<source>Create New Field</source>
<context-group purpose="location">
<context context-type="linenumber">44</context>
</context-group>
</trans-unit>
+ <trans-unit id="8627133593113147800" datatype="html">
+ <source>Selected items</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/input/drag-drop-select/drag-drop-select.component.ts</context>
+ <context context-type="linenumber">23</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="528456107179161277" datatype="html">
+ <source>No items selected</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/common/input/drag-drop-select/drag-drop-select.component.ts</context>
+ <context context-type="linenumber">29</context>
+ </context-group>
+ </trans-unit>
<trans-unit id="6932865105766151309" datatype="html">
<source>Upload</source>
<context-group purpose="location">
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
- <trans-unit id="2509141182388535183" datatype="html">
- <source>View</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
- <context context-type="linenumber">34</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">10</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>
- <context context-type="linenumber">58</context>
- </context-group>
- </trans-unit>
<trans-unit id="2722549756198502062" datatype="html">
<source>Add item</source>
<context-group purpose="location">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.ts</context>
- <context context-type="linenumber">77</context>
+ <context context-type="linenumber">86</context>
</context-group>
</trans-unit>
<trans-unit id="2504502765849142619" datatype="html">
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
- <trans-unit id="5701618810648052610" datatype="html">
- <source>Title</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
- <context context-type="linenumber">17</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
- <context context-type="linenumber">104</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">160</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">115</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
- <context context-type="linenumber">28</context>
- </context-group>
- </trans-unit>
- <trans-unit id="2691296884221415710" datatype="html">
- <source>Correspondent</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
- <context context-type="linenumber">22</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
- <context context-type="linenumber">108</context>
- </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">36</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">151</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">44</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
- <context context-type="linenumber">27</context>
- </context-group>
- </trans-unit>
<trans-unit id="8911158217491828773" datatype="html">
<source>View Preview</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
- <context context-type="linenumber">47</context>
+ <context context-type="linenumber">71</context>
</context-group>
</trans-unit>
<trans-unit id="3099741642167775297" datatype="html">
<source>Download</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
- <context context-type="linenumber">55</context>
+ <context context-type="linenumber">79</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</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>
- <context context-type="linenumber">64</context>
+ <context context-type="linenumber">68</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
- <context context-type="linenumber">102</context>
+ <context context-type="linenumber">131</context>
</context-group>
</trans-unit>
<trans-unit id="872092479747931526" datatype="html">
<source>No documents</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
- <context context-type="linenumber">65</context>
+ <context context-type="linenumber">121</context>
</context-group>
</trans-unit>
<trans-unit id="1069523139277190436" datatype="html">
</context-group>
<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">50</context>
+ <context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="2434944824726929798" datatype="html">
<context context-type="linenumber">101</context>
</context-group>
</trans-unit>
+ <trans-unit id="5701618810648052610" datatype="html">
+ <source>Title</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
+ <context context-type="linenumber">104</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">188</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">115</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">34</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">90</context>
+ </context-group>
+ </trans-unit>
<trans-unit id="1379170675585571971" datatype="html">
<source>Archive serial number</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">105</context>
</context-group>
- </trans-unit>
- <trans-unit id="5114742157723900905" datatype="html">
- <source>Date created</source>
+ </trans-unit>
+ <trans-unit id="5114742157723900905" datatype="html">
+ <source>Date created</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
+ <context context-type="linenumber">106</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="2691296884221415710" datatype="html">
+ <source>Correspondent</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
+ <context context-type="linenumber">108</context>
+ </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">36</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">179</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">44</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">50</context>
+ </context-group>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
- <context context-type="linenumber">106</context>
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">89</context>
</context-group>
</trans-unit>
<trans-unit id="5066119607229701477" datatype="html">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
- <context context-type="linenumber">184</context>
+ <context context-type="linenumber">218</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">54</context>
</context-group>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
- <context context-type="linenumber">29</context>
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">54</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">91</context>
</context-group>
</trans-unit>
<trans-unit id="2091353339965748767" datatype="html">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
- <context context-type="linenumber">193</context>
+ <context context-type="linenumber">227</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">64</context>
</context-group>
- </trans-unit>
- <trans-unit id="5607669932062416162" datatype="html">
- <source>Default</source>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
- <context context-type="linenumber">113</context>
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">58</context>
</context-group>
</trans-unit>
<trans-unit id="6205355627445317276" datatype="html">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
- <context context-type="linenumber">225</context>
+ <context context-type="linenumber">275</context>
</context-group>
</trans-unit>
<trans-unit id="2784168796433474565" datatype="html">
<source>Filter by tag</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">28</context>
+ <context context-type="linenumber">31</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">232</context>
+ <context context-type="linenumber">286</context>
</context-group>
</trans-unit>
<trans-unit id="106713086593101376" datatype="html">
<source>View notes</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
- <context context-type="linenumber">70</context>
+ <context context-type="linenumber">74</context>
</context-group>
</trans-unit>
<trans-unit id="8778002102373462277" datatype="html">
<source><x id="INTERPOLATION" equiv-text="ocument.notes.length}}"/> Notes</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
- <context context-type="linenumber">71</context>
+ <context context-type="linenumber">75</context>
</context-group>
</trans-unit>
<trans-unit id="78870852467682010" datatype="html">
<source>Filter by document type</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
- <context context-type="linenumber">75</context>
+ <context context-type="linenumber">79</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">252</context>
+ <context context-type="linenumber">310</context>
</context-group>
</trans-unit>
<trans-unit id="157572966557284263" datatype="html">
<source>Filter by storage path</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
- <context context-type="linenumber">81</context>
+ <context context-type="linenumber">85</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">259</context>
+ <context context-type="linenumber">317</context>
</context-group>
</trans-unit>
<trans-unit id="3727324658595204357" datatype="html">
<source>Created: <x id="INTERPOLATION" equiv-text="{{ document.created | customDate }}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
- <context context-type="linenumber">93,94</context>
+ <context context-type="linenumber">98,99</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
+ <context context-type="linenumber">65,66</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
- <context context-type="linenumber">60,61</context>
+ <context context-type="linenumber">80,81</context>
</context-group>
</trans-unit>
<trans-unit id="2030261243264601523" datatype="html">
<source>Added: <x id="INTERPOLATION" equiv-text="{{ document.added | customDate }}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
- <context context-type="linenumber">94,95</context>
+ <context context-type="linenumber">99,100</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
+ <context context-type="linenumber">66,67</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
- <context context-type="linenumber">61,62</context>
+ <context context-type="linenumber">81,82</context>
</context-group>
</trans-unit>
<trans-unit id="4235671847487610290" datatype="html">
<source>Modified: <x id="INTERPOLATION" equiv-text="{{ document.modified | customDate }}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
- <context context-type="linenumber">95,96</context>
+ <context context-type="linenumber">100,101</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
+ <context context-type="linenumber">67,68</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
- <context context-type="linenumber">62,63</context>
+ <context context-type="linenumber">82,83</context>
</context-group>
</trans-unit>
<trans-unit id="5739581984228459958" datatype="html">
<source>Shared</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">108</context>
+ <context context-type="linenumber">121</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
- <context context-type="linenumber">85</context>
+ <context context-type="linenumber">106</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">70</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/username.pipe.ts</context>
<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">113</context>
+ <context context-type="linenumber">126</context>
</context-group>
</trans-unit>
<trans-unit id="3661756380991326939" datatype="html">
<source>Toggle tag filter</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
- <context context-type="linenumber">15</context>
+ <context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="4648526799630820486" datatype="html">
<source>Toggle correspondent filter</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
- <context context-type="linenumber">36</context>
+ <context context-type="linenumber">38</context>
</context-group>
</trans-unit>
<trans-unit id="5319701482646590642" datatype="html">
<source>Toggle document type filter</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
- <context context-type="linenumber">44</context>
+ <context context-type="linenumber">48</context>
</context-group>
</trans-unit>
<trans-unit id="8950368321707344185" datatype="html">
<source>Toggle storage path filter</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
- <context context-type="linenumber">51</context>
+ <context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="5145213156408463657" datatype="html">
<source>Sort</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
- <context context-type="linenumber">30</context>
+ <context context-type="linenumber">46</context>
</context-group>
</trans-unit>
<trans-unit id="1233494216161906927" datatype="html">
<source>Save "<x id="INTERPOLATION" equiv-text="{{list.activeSavedViewTitle}}"/>"</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
- <context context-type="linenumber">73</context>
+ <context context-type="linenumber">89</context>
</context-group>
</trans-unit>
<trans-unit id="2276119452079372898" datatype="html">
<source>Save as...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
- <context context-type="linenumber">76</context>
+ <context context-type="linenumber">92</context>
</context-group>
</trans-unit>
<trans-unit id="8786996283897742947" datatype="html">
<source>{VAR_PLURAL, plural, =1 {Selected <x id="INTERPOLATION"/> of one document} other {Selected <x id="INTERPOLATION"/> of <x id="INTERPOLATION_1"/> documents}}</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
- <context context-type="linenumber">96</context>
+ <context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit id="6600548268163632449" datatype="html">
<source>{VAR_PLURAL, plural, =1 {One document} other {<x id="INTERPOLATION"/> documents}}</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
- <context context-type="linenumber">100</context>
+ <context context-type="linenumber">116</context>
</context-group>
</trans-unit>
<trans-unit id="2243770355958919528" datatype="html">
<source>(filtered)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
- <context context-type="linenumber">102</context>
+ <context context-type="linenumber">118</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">107</context>
+ <context context-type="linenumber">123</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
<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">123</context>
+ <context context-type="linenumber">139</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">139</context>
+ <context context-type="linenumber">166</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">143</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.ts</context>
<context context-type="linenumber">120</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 context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">74</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">88</context>
</context-group>
</trans-unit>
<trans-unit id="6954625430271090777" datatype="html">
<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">147</context>
+ <context context-type="linenumber">175</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">155</context>
+ <context context-type="linenumber">184</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">163</context>
+ <context context-type="linenumber">196</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">167</context>
+ <context context-type="linenumber">200</context>
</context-group>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
- <context context-type="linenumber">34</context>
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">66</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">96</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">171</context>
+ <context context-type="linenumber">205</context>
</context-group>
</trans-unit>
<trans-unit id="5499001829734502606" datatype="html">
<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">180</context>
+ <context context-type="linenumber">214</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">189</context>
+ <context context-type="linenumber">223</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">197</context>
+ <context context-type="linenumber">232</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">204</context>
+ <context context-type="linenumber">241</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">208</context>
+ <context context-type="linenumber">245</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">82</context>
</context-group>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
- <context context-type="linenumber">31</context>
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">42</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">93</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="329406837759048287" datatype="html">
+ <source> Shared </source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
+ <context context-type="linenumber">248,250</context>
</context-group>
</trans-unit>
<trans-unit id="2179847500064178686" datatype="html">
<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">230</context>
+ <context context-type="linenumber">282</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="2807800733729323332" datatype="html">
+ <source>Yes</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
+ <context context-type="linenumber">333</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/pipes/yes-no.pipe.ts</context>
+ <context context-type="linenumber">8</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="3542042671420335679" datatype="html">
+ <source>No</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
+ <context context-type="linenumber">333</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/pipes/yes-no.pipe.ts</context>
+ <context context-type="linenumber">8</context>
</context-group>
</trans-unit>
<trans-unit id="2155249406916744630" datatype="html">
<source>View "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" saved successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
- <context context-type="linenumber">209</context>
+ <context context-type="linenumber">242</context>
</context-group>
</trans-unit>
<trans-unit id="6837554170707123455" datatype="html">
<source>View "<x id="PH" equiv-text="savedView.name"/>" created successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
- <context context-type="linenumber">250</context>
+ <context context-type="linenumber">285</context>
</context-group>
</trans-unit>
<trans-unit id="3100631071441658964" datatype="html">
<context context-type="linenumber">45</context>
</context-group>
</trans-unit>
+ <trans-unit id="3553216189604488439" datatype="html">
+ <source>Modified</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">94</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="4460262093225954455" datatype="html">
+ <source>Search score</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/data/document.ts</context>
+ <context context-type="linenumber">102</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>
<trans-unit id="2167862279705099846" datatype="html">
<source>Auto: Learn matching automatically</source>
<context-group purpose="location">
<context context-type="linenumber">11</context>
</context-group>
</trans-unit>
- <trans-unit id="2807800733729323332" datatype="html">
- <source>Yes</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/pipes/yes-no.pipe.ts</context>
- <context context-type="linenumber">8</context>
- </context-group>
- </trans-unit>
- <trans-unit id="3542042671420335679" datatype="html">
- <source>No</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/pipes/yes-no.pipe.ts</context>
- <context context-type="linenumber">8</context>
- </context-group>
- </trans-unit>
<trans-unit id="2119857572761283468" datatype="html">
<source>Document already exists.</source>
<context-group purpose="location">
<context context-type="linenumber">135</context>
</context-group>
</trans-unit>
- <trans-unit id="3553216189604488439" datatype="html">
- <source>Modified</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
- <context context-type="linenumber">32</context>
- </context-group>
- </trans-unit>
- <trans-unit id="4460262093225954455" datatype="html">
- <source>Search score</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
- <context context-type="linenumber">41</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>
<trans-unit id="1206520795340730278" datatype="html">
<source>English (US)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">40</context>
+ <context context-type="linenumber">46</context>
</context-group>
</trans-unit>
<trans-unit id="7318555235181361185" datatype="html">
<source>Afrikaans</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">46</context>
+ <context context-type="linenumber">52</context>
</context-group>
</trans-unit>
<trans-unit id="6269202464699193298" datatype="html">
<source>Arabic</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">52</context>
+ <context context-type="linenumber">58</context>
</context-group>
</trans-unit>
<trans-unit id="3098941349689899577" datatype="html">
<source>Belarusian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">58</context>
+ <context context-type="linenumber">64</context>
</context-group>
</trans-unit>
<trans-unit id="6821856961727142928" datatype="html">
<source>Bulgarian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">64</context>
+ <context context-type="linenumber">70</context>
</context-group>
</trans-unit>
<trans-unit id="1001043467371963032" datatype="html">
<source>Catalan</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">70</context>
+ <context context-type="linenumber">76</context>
</context-group>
</trans-unit>
<trans-unit id="2719780722934172508" datatype="html">
<source>Czech</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">76</context>
+ <context context-type="linenumber">82</context>
</context-group>
</trans-unit>
<trans-unit id="2924289692679201020" datatype="html">
<source>Danish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">82</context>
+ <context context-type="linenumber">88</context>
</context-group>
</trans-unit>
<trans-unit id="1858110241312746425" datatype="html">
<source>German</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">88</context>
+ <context context-type="linenumber">94</context>
</context-group>
</trans-unit>
<trans-unit id="7067741492320440272" datatype="html">
<source>Greek</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">94</context>
+ <context context-type="linenumber">100</context>
</context-group>
</trans-unit>
<trans-unit id="6987083569809053351" datatype="html">
<source>English (GB)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">100</context>
+ <context context-type="linenumber">106</context>
</context-group>
</trans-unit>
<trans-unit id="5190825892106392539" datatype="html">
<source>Spanish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">106</context>
+ <context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit id="861663369293303028" datatype="html">
<source>Finnish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">112</context>
+ <context context-type="linenumber">118</context>
</context-group>
</trans-unit>
<trans-unit id="7633754075223722162" datatype="html">
<source>French</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">118</context>
+ <context context-type="linenumber">124</context>
</context-group>
</trans-unit>
<trans-unit id="7891809788881004730" datatype="html">
<source>Hungarian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">124</context>
+ <context context-type="linenumber">130</context>
</context-group>
</trans-unit>
<trans-unit id="2935232983274991580" datatype="html">
<source>Italian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">130</context>
+ <context context-type="linenumber">136</context>
</context-group>
</trans-unit>
<trans-unit id="6924606686202701860" datatype="html">
<source>Japanese</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">136</context>
+ <context context-type="linenumber">142</context>
</context-group>
</trans-unit>
<trans-unit id="1334425850005897370" datatype="html">
<source>Luxembourgish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">142</context>
+ <context context-type="linenumber">148</context>
</context-group>
</trans-unit>
<trans-unit id="3071065188816255493" datatype="html">
<source>Dutch</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">148</context>
+ <context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="8069284467804715623" datatype="html">
<source>Norwegian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">154</context>
+ <context context-type="linenumber">160</context>
</context-group>
</trans-unit>
<trans-unit id="792060551707690640" datatype="html">
<source>Polish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">160</context>
+ <context context-type="linenumber">166</context>
</context-group>
</trans-unit>
<trans-unit id="9184513005098760425" datatype="html">
<source>Portuguese (Brazil)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">166</context>
+ <context context-type="linenumber">172</context>
</context-group>
</trans-unit>
<trans-unit id="153799456510623899" datatype="html">
<source>Portuguese</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">172</context>
+ <context context-type="linenumber">178</context>
</context-group>
</trans-unit>
<trans-unit id="8118856427047826368" datatype="html">
<source>Romanian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">178</context>
+ <context context-type="linenumber">184</context>
</context-group>
</trans-unit>
<trans-unit id="7137419789978325708" datatype="html">
<source>Russian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">184</context>
+ <context context-type="linenumber">190</context>
</context-group>
</trans-unit>
<trans-unit id="9102963095355753902" datatype="html">
<source>Slovak</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">190</context>
+ <context context-type="linenumber">196</context>
</context-group>
</trans-unit>
<trans-unit id="4287008301409320881" datatype="html">
<source>Slovenian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">196</context>
+ <context context-type="linenumber">202</context>
</context-group>
</trans-unit>
<trans-unit id="8608389829607915090" datatype="html">
<source>Serbian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">202</context>
+ <context context-type="linenumber">208</context>
</context-group>
</trans-unit>
<trans-unit id="499386805970351976" datatype="html">
<source>Swedish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">208</context>
+ <context context-type="linenumber">214</context>
</context-group>
</trans-unit>
<trans-unit id="5682359291233237791" datatype="html">
<source>Turkish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">214</context>
+ <context context-type="linenumber">220</context>
</context-group>
</trans-unit>
<trans-unit id="3578644052206125685" datatype="html">
<source>Ukrainian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">220</context>
+ <context context-type="linenumber">226</context>
</context-group>
</trans-unit>
<trans-unit id="4689443708886954687" datatype="html">
<source>Chinese Simplified</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">226</context>
+ <context context-type="linenumber">232</context>
</context-group>
</trans-unit>
<trans-unit id="4912706592792948707" datatype="html">
<source>ISO 8601</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">234</context>
+ <context context-type="linenumber">240</context>
</context-group>
</trans-unit>
<trans-unit id="313643372755303297" datatype="html">
<source>Successfully completed one-time migratration of settings to the database!</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">477</context>
+ <context context-type="linenumber">550</context>
</context-group>
</trans-unit>
<trans-unit id="5558341108007064934" datatype="html">
<source>Unable to migrate settings to the database, please try saving manually.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">478</context>
+ <context context-type="linenumber">551</context>
</context-group>
</trans-unit>
<trans-unit id="1168781785897678748" datatype="html">
<source>You can restart the tour from the settings page.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
- <context context-type="linenumber">548</context>
+ <context context-type="linenumber">621</context>
</context-group>
</trans-unit>
<trans-unit id="3852289441366561594" datatype="html">
import { MergeConfirmDialogComponent } from './components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component'
import { SplitConfirmDialogComponent } from './components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component'
import { DocumentHistoryComponent } from './components/document-history/document-history.component'
+import { DragDropSelectComponent } from './components/common/input/drag-drop-select/drag-drop-select.component'
+import { CustomFieldDisplayComponent } from './components/common/custom-field-display/custom-field-display.component'
import {
airplane,
archive,
calendar,
calendarEvent,
cardChecklist,
+ cardHeading,
caretDown,
caretUp,
chatLeftText,
calendar,
calendarEvent,
cardChecklist,
+ cardHeading,
caretDown,
caretUp,
chatLeftText,
MergeConfirmDialogComponent,
SplitConfirmDialogComponent,
DocumentHistoryComponent,
+ DragDropSelectComponent,
+ CustomFieldDisplayComponent,
],
imports: [
BrowserModule,
</div>
<h4 i18n>Views</h4>
- <div formGroupName="savedViews">
+ <ul class="list-group" formGroupName="savedViews">
@for (view of savedViews; track view) {
+ <li class="list-group-item py-3">
<div [formGroupName]="view.id" class="row">
- <div class="mb-3 col">
- <label class="form-label" for="name_{{view.id}}" i18n>Name</label>
- <input type="text" class="form-control" formControlName="name" id="name_{{view.id}}">
- </div>
- <div class="mb-2 col">
- <label class="form-label" for="show_on_dashboard_{{view.id}}" i18n> <span class="visually-hidden">Appears on</span></label>
- <div class="form-check form-switch">
- <input type="checkbox" class="form-check-input" id="show_on_dashboard_{{view.id}}" formControlName="show_on_dashboard">
- <label class="form-check-label" for="show_on_dashboard_{{view.id}}" i18n>Show on dashboard</label>
+ <div class="row">
+ <div class="col">
+ <pngx-input-text title="Name" formControlName="name"></pngx-input-text>
</div>
- <div class="form-check form-switch">
- <input type="checkbox" class="form-check-input" id="show_in_sidebar_{{view.id}}" formControlName="show_in_sidebar">
- <label class="form-check-label" for="show_in_sidebar_{{view.id}}" i18n>Show in sidebar</label>
+ <div class="col">
+ <div class="form-check form-switch mt-3">
+ <input type="checkbox" class="form-check-input" id="show_on_dashboard_{{view.id}}" formControlName="show_on_dashboard">
+ <label class="form-check-label" for="show_on_dashboard_{{view.id}}" i18n>Show on dashboard</label>
+ </div>
+ <div class="form-check form-switch">
+ <input type="checkbox" class="form-check-input" id="show_in_sidebar_{{view.id}}" formControlName="show_in_sidebar">
+ <label class="form-check-label" for="show_in_sidebar_{{view.id}}" i18n>Show in sidebar</label>
+ </div>
+ </div>
+ <div class="col-auto">
+ <label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
+ <pngx-confirm-button
+ label="Delete"
+ i18n-label
+ (confirm)="deleteSavedView(view)"
+ *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.SavedView }"
+ buttonClasses="btn-sm btn-outline-danger form-control"
+ iconName="trash">
+ </pngx-confirm-button>
</div>
</div>
- <div class="mb-2 col-auto">
- <label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
-
- <pngx-confirm-button
- label="Delete"
- i18n-label
- (confirm)="deleteSavedView(view)"
- *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.SavedView }"
- buttonClasses="btn-sm btn-outline-danger form-control"
- iconName="trash">
- </pngx-confirm-button>
- </div>
+ <div class="row">
+ <div class="col">
+ <pngx-input-number i18n-title title="Documents page size" [showAdd]="false" formControlName="page_size"></pngx-input-number>
+ </div>
+ <div class="col">
+ <label class="form-label" for="display_mode_{{view.id}}" i18n>Display as</label>
+ <select class="form-select" formControlName="display_mode">
+ <option [ngValue]="DisplayMode.TABLE" i18n>Table</option>
+ <option [ngValue]="DisplayMode.SMALL_CARDS" i18n>Small Cards</option>
+ <option [ngValue]="DisplayMode.LARGE_CARDS" i18n>Large Cards</option>
+ </select>
+ </div>
+ @if (displayFields) {
+ <pngx-input-drag-drop-select i18n-title title="Show" i18n-emptyText emptyText="Default" [items]="displayFields" formControlName="display_fields"></pngx-input-drag-drop-select>
+ }
+ </div>
</div>
+ </li>
}
@if (savedViews && savedViews.length === 0) {
- <div i18n>No saved views defined.</div>
+ <li class="list-group-item">
+ <div i18n>No saved views defined.</div>
+ </li>
}
@if (!savedViews) {
- <div>
+ <li class="list-group-item">
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
<div class="visually-hidden" i18n>Loading...</div>
- </div>
+ </li>
}
- </div>
+ </ul>
</ng-template>
</li>
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
<button type="submit" class="btn btn-primary mb-2" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
+ <button type="button" (click)="reset()" class="btn btn-secondary ms-2 mb-2" [disabled]="(isDirty$ | async) === false" i18n>Cancel</button>
</form>
InstallType,
SystemStatusItemStatus,
} from 'src/app/data/system-status'
+import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component'
+import { DragDropModule } from '@angular/cdk/drag-drop'
const savedViews = [
{ id: 1, name: 'view1', show_in_sidebar: true, show_on_dashboard: true },
PermissionsGroupComponent,
IfOwnerDirective,
ConfirmButtonComponent,
+ DragDropSelectComponent,
],
providers: [CustomDatePipe, DatePipe, PermissionsGuard],
imports: [
NgSelectModule,
NgxBootstrapIconsModule.pick(allIcons),
NgbModalModule,
+ DragDropModule,
],
}).compileComponents()
size: 'xl',
})
})
+
+ it('should support reset', () => {
+ completeSetup()
+ component.settingsForm.get('themeColor').setValue('#ff0000')
+ component.reset()
+ expect(component.settingsForm.get('themeColor').value).toEqual('')
+ })
})
SystemStatusItemStatus,
SystemStatus,
} from 'src/app/data/system-status'
+import { DisplayMode } from 'src/app/data/document'
enum SettingsNavIDs {
General = 1,
extends ComponentWithPermissions
implements OnInit, AfterViewInit, OnDestroy, DirtyComponent
{
- SettingsNavIDs = SettingsNavIDs
activeNavID: number
+ DisplayMode = DisplayMode
savedViewGroup = new FormGroup({})
})
savedViews: SavedView[]
+ SettingsNavIDs = SettingsNavIDs
+ get displayFields() {
+ return this.settings.allDisplayFields
+ }
store: BehaviorSubject<any>
storeSub: Subscription
name: view.name,
show_on_dashboard: view.show_on_dashboard,
show_in_sidebar: view.show_in_sidebar,
+ page_size: view.page_size,
+ display_mode: view.display_mode,
+ display_fields: view.display_fields,
}
this.savedViewGroup.addControl(
view.id.toString(),
name: new FormControl(null),
show_on_dashboard: new FormControl(null),
show_in_sidebar: new FormControl(null),
+ page_size: new FormControl(null),
+ display_mode: new FormControl(null),
+ display_fields: new FormControl([]),
})
)
}
.subscribe({
next: () => {
this.store.next(this.settingsForm.value)
- this.documentListViewService.updatePageSize()
this.settings.updateAppearanceSettings()
+ this.settings.initializeDisplayFields()
let savedToast: Toast = {
content: $localize`Settings were saved successfully.`,
delay: 5000,
}
}
+ reset() {
+ this.settingsForm.patchValue(this.store.getValue())
+ }
+
clearThemeColor() {
this.settingsForm.get('themeColor').patchValue('')
}
</h6>
}
<ul class="nav flex-column mb-2" cdkDropList (cdkDropListDropped)="onDrop($event)">
- @for (view of savedViewService.sidebarViews; track view) {
+ @for (view of savedViewService.sidebarViews; track view.id) {
<li class="nav-item w-100 app-link" cdkDrag [cdkDragDisabled]="!settingsService.organizingSidebarSavedViews"
cdkDragPreviewContainer="parent" cdkDragPreviewClass="navItemDrag" (cdkDragStarted)="onDragStart($event)"
(cdkDragEnded)="onDragEnd($event)">
--- /dev/null
+@if (field) {
+ @switch (field.data_type) {
+ @case (CustomFieldDataType.Monetary) {
+ <span>{{value | currency: currency}}</span>
+ }
+ @case (CustomFieldDataType.Date) {
+ <span>{{value | customDate}}</span>
+ }
+ @case (CustomFieldDataType.Url) {
+ <a [href]="value" class="btn-link text-dark text-decoration-none" target="_blank">{{value}}</a>
+ }
+ @case (CustomFieldDataType.DocumentLink) {
+ <div class="d-flex gap-1 flex-wrap">
+ @for (docId of value; track docId) {
+ <a routerLink="/documents/{{docId}}" class="badge bg-dark text-primary" title="View" i18n-title>
+ <i-bs width="0.9em" height="0.9em" name="file-text"></i-bs> <span>{{ getDocumentTitle(docId) }}</span>
+ </a>
+ }
+ </div>
+ }
+ @default {
+ <span>{{value}}</span>
+ }
+ }
+}
--- /dev/null
+import { ComponentFixture, TestBed } from '@angular/core/testing'
+import { of } from 'rxjs'
+import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
+import { DocumentService } from 'src/app/services/rest/document.service'
+import { CustomFieldDisplayComponent } from './custom-field-display.component'
+import { DisplayField, Document } from 'src/app/data/document'
+import { HttpClientTestingModule } from '@angular/common/http/testing'
+import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
+
+const customFields: CustomField[] = [
+ { id: 1, name: 'Field 1', data_type: CustomFieldDataType.String },
+ { id: 2, name: 'Field 2', data_type: CustomFieldDataType.Monetary },
+ { id: 3, name: 'Field 3', data_type: CustomFieldDataType.DocumentLink },
+]
+const document: Document = {
+ id: 1,
+ title: 'Doc 1',
+ custom_fields: [
+ { field: 1, document: 1, created: null, value: 'Text value' },
+ { field: 2, document: 1, created: null, value: '100 USD' },
+ { field: 3, document: 1, created: null, value: '1,2,3' },
+ ],
+}
+
+describe('CustomFieldDisplayComponent', () => {
+ let component: CustomFieldDisplayComponent
+ let fixture: ComponentFixture<CustomFieldDisplayComponent>
+ let documentService: DocumentService
+ let customFieldService: CustomFieldsService
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [CustomFieldDisplayComponent],
+ providers: [DocumentService],
+ imports: [HttpClientTestingModule],
+ }).compileComponents()
+ })
+
+ beforeEach(() => {
+ documentService = TestBed.inject(DocumentService)
+ customFieldService = TestBed.inject(CustomFieldsService)
+ jest
+ .spyOn(customFieldService, 'listAll')
+ .mockReturnValue(of({ results: customFields } as any))
+ fixture = TestBed.createComponent(CustomFieldDisplayComponent)
+ component = fixture.componentInstance
+ fixture.detectChanges()
+ })
+
+ it('should create', () => {
+ expect(component).toBeTruthy()
+ })
+
+ it('should initialize component', () => {
+ jest
+ .spyOn(documentService, 'getFew')
+ .mockReturnValue(of({ results: [] } as any))
+
+ component.fieldDisplayKey = DisplayField.CUSTOM_FIELD + '2'
+ expect(component.fieldId).toEqual(2)
+ component.document = document
+ expect(component.document.title).toEqual('Doc 1')
+
+ expect(component.field).toEqual(customFields[1])
+ expect(component.value).toEqual(100)
+ expect(component.currency).toEqual('USD')
+ })
+
+ it('should get document titles', () => {
+ const docLinkDocuments: Document[] = [
+ { id: 1, title: 'Document 1' } as any,
+ { id: 2, title: 'Document 2' } as any,
+ { id: 3, title: 'Document 3' } as any,
+ ]
+ jest
+ .spyOn(documentService, 'getFew')
+ .mockReturnValue(of({ results: docLinkDocuments } as any))
+ component.fieldId = 3
+ component.document = document
+
+ const title1 = component.getDocumentTitle(1)
+ const title2 = component.getDocumentTitle(2)
+ const title3 = component.getDocumentTitle(3)
+
+ expect(title1).toEqual('Document 1')
+ expect(title2).toEqual('Document 2')
+ expect(title3).toEqual('Document 3')
+ })
+})
--- /dev/null
+import { Component, Input, OnDestroy, OnInit } from '@angular/core'
+import { Subject, takeUntil } from 'rxjs'
+import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
+import { DisplayField, Document } from 'src/app/data/document'
+import { Results } from 'src/app/data/results'
+import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
+import { DocumentService } from 'src/app/services/rest/document.service'
+
+@Component({
+ selector: 'pngx-custom-field-display',
+ templateUrl: './custom-field-display.component.html',
+ styleUrl: './custom-field-display.component.scss',
+})
+export class CustomFieldDisplayComponent implements OnInit, OnDestroy {
+ CustomFieldDataType = CustomFieldDataType
+
+ private _document: Document
+ @Input()
+ set document(document: Document) {
+ this._document = document
+ this.init()
+ }
+
+ get document(): Document {
+ return this._document
+ }
+
+ private _fieldId: number
+ @Input()
+ set fieldId(id: number) {
+ this._fieldId = id
+ this.init()
+ }
+
+ get fieldId(): number {
+ return this._fieldId
+ }
+
+ @Input()
+ set fieldDisplayKey(key: string) {
+ this.fieldId = parseInt(key.replace(DisplayField.CUSTOM_FIELD, ''), 10)
+ }
+
+ value: any
+ currency: string
+
+ private customFields: CustomField[] = []
+
+ public field: CustomField
+
+ private docLinkDocuments: Document[] = []
+
+ private unsubscribeNotifier: Subject<any> = new Subject()
+
+ constructor(
+ private customFieldService: CustomFieldsService,
+ private documentService: DocumentService
+ ) {
+ this.customFieldService.listAll().subscribe((r) => {
+ this.customFields = r.results
+ this.init()
+ })
+ }
+
+ ngOnInit(): void {
+ this.init()
+ }
+
+ private init() {
+ if (this.value || !this._fieldId || !this._document || !this.customFields) {
+ return
+ }
+ this.field = this.customFields.find((f) => f.id === this._fieldId)
+ this.value = this._document.custom_fields.find(
+ (f) => f.field === this._fieldId
+ )?.value
+ if (this.value && this.field.data_type === CustomFieldDataType.Monetary) {
+ this.currency = this.value.match(/([A-Z]{3})/)?.[0]
+ this.value = parseFloat(this.value.replace(this.currency, ''))
+ } else if (
+ this.value?.length &&
+ this.field.data_type === CustomFieldDataType.DocumentLink
+ ) {
+ this.getDocuments()
+ }
+ }
+
+ private getDocuments() {
+ this.documentService
+ .getFew(this.value, { fields: 'id,title' })
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe((result: Results<Document>) => {
+ this.docLinkDocuments = result.results
+ })
+ }
+
+ public getDocumentTitle(docId: number): string {
+ return this.docLinkDocuments?.find((d) => d.id === docId)?.title
+ }
+
+ ngOnDestroy(): void {
+ this.unsubscribeNotifier.next(true)
+ this.unsubscribeNotifier.complete()
+ }
+}
--- /dev/null
+<div class="d-flex flex-row mt-2 align-items-center">
+ <span class="me-2">{{title}}:</span>
+ <div class="d-flex flex-row gap-2 w-100 mh-1" style="min-height: 1em;"
+ cdkDropList #selectedList="cdkDropList"
+ cdkDropListOrientation="horizontal"
+ (cdkDropListDropped)="drop($event)"
+ [cdkDropListConnectedTo]="[unselectedList]">
+ @for (item of selectedItems; track item.id) {
+ <div class="badge bg-primary" cdkDrag>{{item.name}}</div>
+ }
+ @if (selectedItems.length === 0) {
+ <div class="badge bg-light fst-italic" i18n>{{emptyText}}</div>
+ }
+ </div>
+</div>
+<div class="d-flex flex-row mt-2 align-items-center bg-light p-2">
+ <div class="d-flex flex-row gap-2 w-100 mh-1" style="min-height: 1em;"
+ cdkDropList #unselectedList="cdkDropList"
+ cdkDropListOrientation="horizontal"
+ (cdkDropListDropped)="drop($event)"
+ [cdkDropListConnectedTo]="[selectedList]">
+ @for (item of unselectedItems; track item.id) {
+ <div class="badge bg-secondary opacity-50" cdkDrag>{{item.name}}</div>
+ }
+ </div>
+</div>
--- /dev/null
+.badge {
+ cursor: move;
+}
+
+.d-flex {
+ overflow-x: scroll;
+}
--- /dev/null
+import { ComponentFixture, TestBed } from '@angular/core/testing'
+import { DragDropModule } from '@angular/cdk/drag-drop'
+import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'
+import { DragDropSelectComponent } from './drag-drop-select.component'
+
+describe('DragDropSelectComponent', () => {
+ let component: DragDropSelectComponent
+ let fixture: ComponentFixture<DragDropSelectComponent>
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [DragDropModule, FormsModule],
+ declarations: [DragDropSelectComponent],
+ }).compileComponents()
+
+ fixture = TestBed.createComponent(DragDropSelectComponent)
+ component = fixture.componentInstance
+ fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
+ fixture.detectChanges()
+ })
+
+ it('should update selectedItems when writeValue is called', () => {
+ const newValue = ['1', '2', '3']
+ component.items = [
+ { id: '1', name: 'Item 1' },
+ { id: '2', name: 'Item 2' },
+ { id: '3', name: 'Item 3' },
+ ]
+ component.writeValue(newValue)
+ expect(component.selectedItems).toEqual([
+ { id: '1', name: 'Item 1' },
+ { id: '2', name: 'Item 2' },
+ { id: '3', name: 'Item 3' },
+ ])
+
+ component.writeValue(null)
+ expect(component.selectedItems).toEqual([])
+ })
+
+ it('should update selectedItems when an item is dropped within selectedList', () => {
+ component.items = [
+ { id: '1', name: 'Item 1' },
+ { id: '2', name: 'Item 2' },
+ { id: '3', name: 'Item 3' },
+ { id: '4', name: 'Item 4' },
+ ]
+ component.writeValue(['1', '2', '3'])
+ const event = {
+ previousContainer: component.selectedList,
+ container: component.selectedList,
+ previousIndex: 1,
+ currentIndex: 2,
+ }
+ component.drop(event as any)
+ expect(component.selectedItems).toEqual([
+ { id: '1', name: 'Item 1' },
+ { id: '3', name: 'Item 3' },
+ { id: '2', name: 'Item 2' },
+ ])
+ })
+
+ it('should update selectedItems when an item is dropped from unselectedList to selectedList', () => {
+ component.items = [
+ { id: '1', name: 'Item 1' },
+ { id: '2', name: 'Item 2' },
+ { id: '3', name: 'Item 3' },
+ ]
+ component.writeValue(['1', '2'])
+ const event = {
+ previousContainer: component.unselectedList,
+ container: component.selectedList,
+ previousIndex: 0,
+ currentIndex: 2,
+ }
+ component.drop(event as any)
+ expect(component.selectedItems).toEqual([
+ { id: '1', name: 'Item 1' },
+ { id: '2', name: 'Item 2' },
+ { id: '3', name: 'Item 3' },
+ ])
+ })
+
+ it('should update selectedItems when an item is dropped from selectedList to unselectedList', () => {
+ component.items = [
+ { id: '1', name: 'Item 1' },
+ { id: '2', name: 'Item 2' },
+ { id: '3', name: 'Item 3' },
+ ]
+ component.writeValue(['1', '2', '3'])
+ const event = {
+ previousContainer: component.selectedList,
+ container: component.unselectedList,
+ previousIndex: 1,
+ currentIndex: 0,
+ }
+ component.drop(event as any)
+ expect(component.selectedItems).toEqual([
+ { id: '1', name: 'Item 1' },
+ { id: '3', name: 'Item 3' },
+ ])
+ })
+})
--- /dev/null
+import { Component, Input, ViewChild, forwardRef } from '@angular/core'
+import { NG_VALUE_ACCESSOR } from '@angular/forms'
+import { AbstractInputComponent } from '../abstract-input'
+import {
+ CdkDragDrop,
+ CdkDropList,
+ moveItemInArray,
+} from '@angular/cdk/drag-drop'
+
+@Component({
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => DragDropSelectComponent),
+ multi: true,
+ },
+ ],
+ selector: 'pngx-input-drag-drop-select',
+ templateUrl: './drag-drop-select.component.html',
+ styleUrl: './drag-drop-select.component.scss',
+})
+export class DragDropSelectComponent extends AbstractInputComponent<string[]> {
+ @Input() title: string = $localize`Selected items`
+
+ @Input() items: { id: string; name: string }[] = []
+ public selectedItems: { id: string; name: string }[] = []
+
+ @Input()
+ emptyText = $localize`No items selected`
+
+ @ViewChild('selectedList') selectedList: CdkDropList
+ @ViewChild('unselectedList') unselectedList: CdkDropList
+
+ get unselectedItems(): { id: string; name: string }[] {
+ return this.items.filter((i) => !this.selectedItems.includes(i))
+ }
+
+ writeValue(newValue: string[]): void {
+ super.writeValue(newValue)
+ this.selectedItems =
+ newValue?.map((id) => this.items.find((i) => i.id === id)) ?? []
+ }
+
+ public drop(event: CdkDragDrop<string[]>) {
+ if (
+ event.previousContainer === event.container &&
+ event.container === this.selectedList
+ ) {
+ moveItemInArray(
+ this.selectedItems,
+ event.previousIndex,
+ event.currentIndex
+ )
+ } else if (event.container === this.selectedList) {
+ this.selectedItems.splice(
+ event.currentIndex,
+ 0,
+ this.unselectedItems[event.previousIndex]
+ )
+ } else if (
+ event.container === this.unselectedList &&
+ event.previousContainer === this.selectedList
+ ) {
+ this.selectedItems.splice(event.previousIndex, 1)
+ }
+ this.onChange(this.selectedItems.map((i) => i.id))
+ }
+}
}
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
- @for (v of dashboardViews; track v) {
+ @for (v of dashboardViews; track v.id) {
<div class="col">
<pngx-saved-view-widget
[savedView]="v"
<a class="btn-link text-decoration-none" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a>
}
- @if (documents.length) {
- <table content class="table table-hover mb-0 align-middle">
+ @if (documents.length && displayMode === DisplayMode.TABLE) {
+ <table content class="table table-hover mb-0 mt-n2 align-middle">
<thead>
<tr>
- <th scope="col" i18n>Created</th>
- <th scope="col" i18n>Title</th>
- @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Tag)) {
- <th scope="col" class="d-none d-md-table-cell" i18n>Tags</th>
- }
- @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
- <th scope="col" class="d-none d-md-table-cell" i18n>Correspondent</th>
- } @else {
- <th scope="col" class="d-none d-md-table-cell"></th>
+ @for (field of displayFields; track field; let i = $index) {
+ @if (displayFields.includes(field)) {
+ <th
+ scope="col"
+ [ngClass]="{
+ 'd-none d-md-table-cell': i > 1,
+ 'w-25': field === DisplayField.CREATED || field === DisplayField.ADDED
+ }">
+ {{ getColumnTitle(field) }}
+ </th>
+ }
}
</tr>
</thead>
<tbody>
- @for (doc of documents; track doc) {
- <tr (mouseleave)="maybeClosePopover()">
- <td class="py-2 py-md-3"><a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.created_date | customDate}}</a></td>
- <td class="py-2 py-md-3">
- <a routerLink="/documents/{{doc.id}}" title="Edit" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a>
- </td>
- @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Tag)) {
- <td class="py-2 py-md-3 d-none d-md-table-cell">
- @for (t of doc.tags$ | async; track t) {
- <pngx-tag [tag]="t" class="ms-1" (click)="clickTag(t, $event)"></pngx-tag>
+ @for (doc of documents; track doc.id) {
+ <tr>
+ @for (field of displayFields; track field; let i = $index) {
+ <td class="py-2 py-md-3 position-relative" [ngClass]="{ 'd-none d-md-table-cell': i > 1 }">
+ @switch (field) {
+ @case (DisplayField.ADDED) {
+ <a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.added | customDate}}</a>
+ }
+ @case (DisplayField.CREATED) {
+ <a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.created_date | customDate}}</a>
+ }
+ @case (DisplayField.TITLE) {
+ <a routerLink="/documents/{{doc.id}}" title="Edit" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a>
+ }
+ @case (DisplayField.CORRESPONDENT) {
+ @if (doc.correspondent) {
+ <a class="btn-link text-dark text-decoration-none" type="button" (click)="clickCorrespondent(doc.correspondent, $event)">{{(doc.correspondent$ | async)?.name}}</a>
+ }
+ }
+ @case (DisplayField.TAGS) {
+ @for (t of doc.tags$ | async; track t) {
+ <pngx-tag [tag]="t" class="ms-1" (click)="clickTag(t.id, $event)"></pngx-tag>
+ }
+ }
+ @case (DisplayField.DOCUMENT_TYPE) {
+ @if (doc.document_type) {
+ <a class="btn-link text-dark text-decoration-none" type="button" (click)="clickDocType(doc.document_type, $event)">{{(doc.document_type$ | async)?.name}}</a>
+ }
+ }
+ @case (DisplayField.STORAGE_PATH) {
+ @if (doc.storage_path) {
+ <a class="btn-link text-dark text-decoration-none" type="button" (click)="clickStoragePath(doc.storage_path, $event)">{{(doc.storage_path$ | async)?.name}}</a>
+ }
+ }
+ }
+ @if (field.startsWith(DisplayField.CUSTOM_FIELD)) {
+ <pngx-custom-field-display [document]="doc" [fieldDisplayKey]="field"></pngx-custom-field-display>
+ }
+ @if (i === displayFields.length - 1) {
+ <div class="btn-group position-absolute top-50 end-0 translate-middle-y">
+ <a [href]="getPreviewUrl(doc)" title="View Preview" i18n-title target="_blank" class="btn px-4 btn-dark border-dark-subtle"
+ [ngbPopover]="previewContent" [popoverTitle]="doc.title | documentTitle"
+ autoClose="true" popoverClass="shadow popover-preview" container="body" (mouseenter)="mouseEnterPreviewButton(doc)" (mouseleave)="mouseLeavePreviewButton()" #popover="ngbPopover">
+ <i-bs width="0.8em" height="0.8em" name="eye"></i-bs>
+ </a>
+ <ng-template #previewContent>
+ <pngx-preview-popup [document]="doc" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()"></pngx-preview-popup>
+ </ng-template>
+ <a [href]="getDownloadUrl(doc)" class="btn px-4 btn-dark border-dark-subtle" title="Download" i18n-title (click)="$event.stopPropagation()">
+ <i-bs width="0.8em" height="0.8em" name="download"></i-bs>
+ </a>
+ </div>
}
</td>
}
- <td class="position-relative py-2 py-md-3 d-none d-md-table-cell">
- @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Correspondent) && doc.correspondent !== null) {
- <a class="btn-link text-dark text-decoration-none py-2 py-md-3" routerLink="/documents" [queryParams]="getCorrespondentQueryParams(doc.correspondent)">{{(doc.correspondent$ | async)?.name}}</a>
- }
- <div class="btn-group position-absolute top-50 end-0 translate-middle-y">
- <a [href]="getPreviewUrl(doc)" title="View Preview" i18n-title target="_blank" class="btn px-4 btn-dark border-dark-subtle"
- [ngbPopover]="previewContent" [popoverTitle]="doc.title | documentTitle"
- autoClose="true" popoverClass="shadow popover-preview" container="body" (mouseenter)="mouseEnterPreviewButton(doc)" (mouseleave)="mouseLeavePreviewButton()" #popover="ngbPopover">
- <i-bs width="0.8em" height="0.8em" name="eye"></i-bs>
- </a>
- <ng-template #previewContent>
- <pngx-preview-popup [document]="doc" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()"></pngx-preview-popup>
- </ng-template>
- <a [href]="getDownloadUrl(doc)" class="btn px-4 btn-dark border-dark-subtle" title="Download" i18n-title (click)="$event.stopPropagation()">
- <i-bs width="0.8em" height="0.8em" name="download"></i-bs>
- </a>
- </div>
- </td>
</tr>
}
</tbody>
</table>
+ } @else if (documents.length && displayMode === DisplayMode.SMALL_CARDS) {
+ <div class="row row-cols-paperless-cards my-n2">
+ @for (d of documents; track d.id) {
+ <pngx-document-card-small
+ class="p-0"
+ (dblClickDocument)="openDocumentDetail(d)"
+ [document]="d"
+ [displayFields]="displayFields"
+ (clickTag)="clickTag($event)"
+ (clickCorrespondent)="clickCorrespondent($event)"
+ (clickStoragePath)="clickStoragePath($event)"
+ (clickDocumentType)="clickDocumentType($event)">
+ </pngx-document-card-small>
+ }
+ </div>
+ } @else if (documents.length && displayMode === DisplayMode.LARGE_CARDS) {
+ <div class="row my-n2">
+ @for (d of documents; track d.id) {
+ <pngx-document-card-large
+ (dblClickDocument)="openDocumentDetail(d)"
+ [document]="d"
+ [displayFields]="displayFields"
+ (clickTag)="clickTag($event)"
+ (clickCorrespondent)="clickCorrespondent($event)"
+ (clickStoragePath)="clickStoragePath($event)"
+ (clickDocumentType)="clickDocumentType($event)"
+ (clickMoreLike)="clickMoreLike(d.id)">
+ </pngx-document-card-large>
+ }
+ </div>
} @else {
<p i18n class="text-center text-muted mb-0 fst-italic">No documents</p>
}
table-layout: fixed;
}
-th:first-child {
- width: 25%;
- @media (min-width: 768px) {
- width: 15%;
+@media (min-width: 768px) {
+ th.w-25 {
+ width: 15% !important;
}
}
padding-top: 0.75em !important;
padding-bottom: 0.75em !important;
}
+
+$paperless-card-breakpoints: (
+ // 0: 2, // xs is manual for slim-sidebar
+ 768px: 2, //md
+ 992px: 2, //lg
+ 1200px: 3, //xl
+ 1600px: 4,
+ 1800px: 5,
+ 2000px: 6
+);
+
+.row-cols-paperless-cards {
+ // xs, we dont want in .col-slim block
+ > * {
+ flex: 0 0 auto;
+ width: calc(100% / 2);
+ }
+
+ @each $width, $n_cols in $paperless-card-breakpoints {
+ @media(min-width: $width) {
+ > * {
+ flex: 0 0 auto;
+ width: calc(100% / $n-cols);
+ }
+ }
+ }
+}
+
+::ng-deep .col-slim .row-cols-paperless-cards {
+ @each $width, $n_cols in $paperless-card-breakpoints {
+ @media(min-width: $width) {
+ > * {
+ flex: 0 0 auto;
+ width: calc(100% / ($n-cols + 1)) !important;
+ }
+ }
+ }
+}
+
+::ng-deep .document-card-check {
+ display: none !important; // override for dashboard
+}
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { of, Subject } from 'rxjs'
import { routes } from 'src/app/app-routing.module'
-import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
+import {
+ FILTER_CORRESPONDENT,
+ FILTER_DOCUMENT_TYPE,
+ FILTER_FULLTEXT_MORELIKE,
+ FILTER_HAS_TAGS_ALL,
+ FILTER_STORAGE_PATH,
+} from 'src/app/data/filter-rule-type'
import { SavedView } from 'src/app/data/saved-view'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { DragDropModule } from '@angular/cdk/drag-drop'
import { PreviewPopupComponent } from 'src/app/components/common/preview-popup/preview-popup.component'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
+import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
+import { CustomFieldDataType } from 'src/app/data/custom-field'
+import { CustomFieldDisplayComponent } from 'src/app/components/common/custom-field-display/custom-field-display.component'
+import { DisplayMode, DisplayField } from 'src/app/data/document'
const savedView: SavedView = {
id: 1,
value: '1,2',
},
],
+ page_size: 20,
+ display_mode: DisplayMode.TABLE,
+ display_fields: [
+ DisplayField.CREATED,
+ DisplayField.TITLE,
+ DisplayField.TAGS,
+ DisplayField.CORRESPONDENT,
+ DisplayField.DOCUMENT_TYPE,
+ DisplayField.STORAGE_PATH,
+ `${DisplayField.CUSTOM_FIELD}11` as any,
+ `${DisplayField.CUSTOM_FIELD}15` as any,
+ ],
}
const documentResults = [
{
id: 2,
title: 'doc2',
+ custom_fields: [
+ { id: 1, field: 11, created: new Date(), value: 'custom', document: 2 },
+ ],
},
{
id: 3,
title: 'doc3',
correspondent: 0,
+ custom_fields: [],
+ },
+ {
+ id: 4,
+ title: 'doc4',
+ custom_fields: [
+ { id: 32, field: 3, created: new Date(), value: 'EUR123', document: 4 },
+ ],
+ },
+ {
+ id: 5,
+ title: 'doc5',
+ custom_fields: [
+ {
+ id: 22,
+ field: 15,
+ created: new Date(),
+ value: [123, 456, 789],
+ document: 5,
+ },
+ ],
},
]
DocumentTitlePipe,
SafeUrlPipe,
PreviewPopupComponent,
+ CustomFieldDisplayComponent,
],
providers: [
PermissionsGuard,
},
CustomDatePipe,
DatePipe,
+ {
+ provide: CustomFieldsService,
+ useValue: {
+ listAll: () =>
+ of({
+ all: [3, 11, 15],
+ count: 3,
+ results: [
+ {
+ id: 3,
+ name: 'Custom field 3',
+ data_type: CustomFieldDataType.Monetary,
+ },
+ {
+ id: 11,
+ name: 'Custom Field 11',
+ data_type: CustomFieldDataType.String,
+ },
+ {
+ id: 15,
+ name: 'Custom Field 15',
+ data_type: CustomFieldDataType.DocumentLink,
+ },
+ ],
+ }),
+ },
+ },
],
imports: [
HttpClientTestingModule,
component.ngOnInit()
expect(listAllSpy).toHaveBeenCalledWith(
1,
- 10,
+ 20,
savedView.sort_field,
savedView.sort_reverse,
savedView.filter_rules,
})
})
+ it('should navigate to document', () => {
+ const routerSpy = jest.spyOn(router, 'navigate')
+ component.openDocumentDetail(documentResults[0])
+ expect(routerSpy).toHaveBeenCalledWith(['documents', documentResults[0].id])
+ })
+
it('should navigate via quickfilter on click tag', () => {
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
- component.clickTag({ id: 11, name: 'Tag11' }, new MouseEvent('click'))
+ component.clickTag(11, new MouseEvent('click'))
expect(qfSpy).toHaveBeenCalledWith([
{ rule_type: FILTER_HAS_TAGS_ALL, value: '11' },
])
+ component.clickTag(11) // coverage
+ })
+
+ it('should navigate via quickfilter on click correspondent', () => {
+ const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
+ component.clickCorrespondent(11, new MouseEvent('click'))
+ expect(qfSpy).toHaveBeenCalledWith([
+ { rule_type: FILTER_CORRESPONDENT, value: '11' },
+ ])
+ component.clickCorrespondent(11) // coverage
+ })
+
+ it('should navigate via quickfilter on click doc type', () => {
+ const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
+ component.clickDocType(11, new MouseEvent('click'))
+ expect(qfSpy).toHaveBeenCalledWith([
+ { rule_type: FILTER_DOCUMENT_TYPE, value: '11' },
+ ])
+ component.clickDocType(11) // coverage
+ })
+
+ it('should navigate via quickfilter on click storage path', () => {
+ const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
+ component.clickStoragePath(11, new MouseEvent('click'))
+ expect(qfSpy).toHaveBeenCalledWith([
+ { rule_type: FILTER_STORAGE_PATH, value: '11' },
+ ])
+ component.clickStoragePath(11) // coverage
+ })
+
+ it('should navigate via quickfilter on click more like', () => {
+ const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
+ component.clickMoreLike(11)
+ expect(qfSpy).toHaveBeenCalledWith([
+ { rule_type: FILTER_FULLTEXT_MORELIKE, value: '11' },
+ ])
+ })
+
+ it('should get correct column title', () => {
+ expect(component.getColumnTitle(DisplayField.TITLE)).toEqual('Title')
+ expect(component.getColumnTitle(DisplayField.CREATED)).toEqual('Created')
+ expect(component.getColumnTitle(DisplayField.ADDED)).toEqual('Added')
+ expect(component.getColumnTitle(DisplayField.TAGS)).toEqual('Tags')
+ expect(component.getColumnTitle(DisplayField.CORRESPONDENT)).toEqual(
+ 'Correspondent'
+ )
+ expect(component.getColumnTitle(DisplayField.DOCUMENT_TYPE)).toEqual(
+ 'Document type'
+ )
+ expect(component.getColumnTitle(DisplayField.STORAGE_PATH)).toEqual(
+ 'Storage path'
+ )
+ })
+
+ it('should get correct column title for custom field', () => {
+ expect(
+ component.getColumnTitle((DisplayField.CUSTOM_FIELD + 11) as any)
+ ).toEqual('Custom Field 11')
+ expect(
+ component.getColumnTitle((DisplayField.CUSTOM_FIELD + 15) as any)
+ ).toEqual('Custom Field 15')
})
})
QueryList,
ViewChildren,
} from '@angular/core'
-import { Params, Router } from '@angular/router'
+import { Router } from '@angular/router'
import { Subject, takeUntil } from 'rxjs'
-import { Document } from 'src/app/data/document'
+import {
+ DEFAULT_DASHBOARD_DISPLAY_FIELDS,
+ DEFAULT_DASHBOARD_VIEW_PAGE_SIZE,
+ DEFAULT_DISPLAY_FIELDS,
+ DisplayField,
+ DisplayMode,
+ Document,
+} from 'src/app/data/document'
import { SavedView } from 'src/app/data/saved-view'
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
import { DocumentService } from 'src/app/services/rest/document.service'
-import { Tag } from 'src/app/data/tag'
import {
FILTER_CORRESPONDENT,
+ FILTER_DOCUMENT_TYPE,
+ FILTER_FULLTEXT_MORELIKE,
FILTER_HAS_TAGS_ALL,
+ FILTER_STORAGE_PATH,
} from 'src/app/data/filter-rule-type'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
-import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
-import { PermissionsService } from 'src/app/services/permissions.service'
+import {
+ PermissionAction,
+ PermissionType,
+ PermissionsService,
+} from 'src/app/services/permissions.service'
+import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
+import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
+import { SettingsService } from 'src/app/services/settings.service'
@Component({
selector: 'pngx-saved-view-widget',
extends ComponentWithPermissions
implements OnInit, OnDestroy
{
+ public DisplayMode = DisplayMode
+ public DisplayField = DisplayField
+ public CustomFieldDataType = CustomFieldDataType
+
loading: boolean = true
+ private customFields: CustomField[] = []
+
constructor(
private documentService: DocumentService,
private router: Router,
private consumerStatusService: ConsumerStatusService,
public openDocumentsService: OpenDocumentsService,
public documentListViewService: DocumentListViewService,
- public permissionsService: PermissionsService
+ public permissionsService: PermissionsService,
+ private settingsService: SettingsService,
+ private customFieldService: CustomFieldsService
) {
super()
}
mouseOnPreview = false
popoverHidden = true
+ displayMode: DisplayMode
+
+ displayFields: DisplayField[] = DEFAULT_DASHBOARD_DISPLAY_FIELDS
+
ngOnInit(): void {
this.reload()
+ this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE
this.consumerStatusService
.onDocumentConsumptionFinished()
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
this.reload()
})
+
+ if (
+ this.permissionsService.currentUserCan(
+ PermissionAction.View,
+ PermissionType.CustomField
+ )
+ ) {
+ this.customFieldService
+ .listAll()
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe((customFields) => {
+ this.customFields = customFields.results
+ })
+ }
+
+ if (this.savedView.display_fields) {
+ this.displayFields = this.savedView.display_fields
+ }
+
+ // filter by perms etc
+ this.displayFields = this.displayFields.filter(
+ (field) =>
+ this.settingsService.allDisplayFields.find((f) => f.id === field) !==
+ undefined
+ )
}
ngOnDestroy(): void {
this.documentService
.listFiltered(
1,
- 10,
+ this.savedView.page_size ?? DEFAULT_DASHBOARD_VIEW_PAGE_SIZE,
this.savedView.sort_field,
this.savedView.sort_reverse,
this.savedView.filter_rules,
}
}
- clickTag(tag: Tag, event: MouseEvent) {
- event.preventDefault()
- event.stopImmediatePropagation()
+ clickTag(tagID: number, event: MouseEvent = null) {
+ event?.preventDefault()
+ event?.stopImmediatePropagation()
+
+ this.list.quickFilter([
+ { rule_type: FILTER_HAS_TAGS_ALL, value: tagID.toString() },
+ ])
+ }
+
+ clickCorrespondent(correspondentId: number, event: MouseEvent = null) {
+ event?.preventDefault()
+ event?.stopImmediatePropagation()
this.list.quickFilter([
- { rule_type: FILTER_HAS_TAGS_ALL, value: tag.id.toString() },
+ { rule_type: FILTER_CORRESPONDENT, value: correspondentId.toString() },
])
}
+ clickDocType(docTypeId: number, event: MouseEvent = null) {
+ event?.preventDefault()
+ event?.stopImmediatePropagation()
+
+ this.list.quickFilter([
+ { rule_type: FILTER_DOCUMENT_TYPE, value: docTypeId.toString() },
+ ])
+ }
+
+ clickStoragePath(storagePathId: number, event: MouseEvent = null) {
+ event?.preventDefault()
+ event?.stopImmediatePropagation()
+
+ this.list.quickFilter([
+ { rule_type: FILTER_STORAGE_PATH, value: storagePathId.toString() },
+ ])
+ }
+
+ clickMoreLike(documentID: number) {
+ this.list.quickFilter([
+ { rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString() },
+ ])
+ }
+
+ openDocumentDetail(document: Document) {
+ this.router.navigate(['documents', document.id])
+ }
+
getPreviewUrl(document: Document): string {
return this.documentService.getPreviewUrl(document.id)
}
}, 300)
}
- getCorrespondentQueryParams(correspondentId: number): Params {
- return correspondentId !== undefined
- ? queryParamsFromFilterRules([
- {
- rule_type: FILTER_CORRESPONDENT,
- value: correspondentId.toString(),
- },
- ])
- : null
+ public getColumnTitle(field: DisplayField): string {
+ if (field.startsWith(DisplayField.CUSTOM_FIELD)) {
+ const id = field.split('_')[2]
+ return this.customFields.find((f) => f.id === parseInt(id))?.name
+ }
+ return DEFAULT_DISPLAY_FIELDS.find((f) => f.id === field)?.name
}
}
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
<div class="visually-hidden" i18n>Loading...</div>
}
- <ng-content select ="[header-buttons]"></ng-content>
+ <ng-content select="[header-buttons]"></ng-content>
</div>
</div>
<div class="card-body text-dark">
- <ng-content select ="[content]"></ng-content>
+ <ng-content select="[content]"></ng-content>
</div>
</div>
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<h5 class="card-title">
- @if (document.correspondent) {
+ @if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) {
@if (clickCorrespondent.observers.length ) {
<a title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name}}</a>
} @else {
}
:
}
- {{document.title | documentTitle}}
- @for (t of document.tags$ | async; track t) {
- <pngx-tag [tag]="t" linkTitle="Filter by tag" i18n-linkTitle class="ms-1" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="clickTag.observers.length"></pngx-tag>
+ @if (displayFields.includes(DisplayField.TITLE)) {
+ {{document.title | documentTitle}}
+ }
+ @if (displayFields.includes(DisplayField.TAGS)) {
+ @for (t of document.tags$ | async; track t) {
+ <pngx-tag [tag]="t" linkTitle="Filter by tag" i18n-linkTitle class="ms-1" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="clickTag.observers.length"></pngx-tag>
+ }
}
</h5>
</div>
<p class="card-text">
- @if (document.__search_hit__ && document.__search_hit__.highlights) {
+ @if (document.__search_hit__?.score && document.__search_hit__.highlights) {
<span [innerHtml]="document.__search_hit__.highlights"></span>
}
@for (highlight of searchNoteHighlights; track highlight) {
<span [innerHtml]="highlight"></span>
</span>
}
- @if (!document.__search_hit__) {
+ @if (!document.__search_hit__?.score) {
<span class="result-content">{{contentTrimmed}}</span>
}
</p>
</div>
<div class="list-group list-group-horizontal border-0 card-info ms-md-auto mt-2 mt-md-0">
- @if (notesEnabled && document.notes.length) {
+ @if (displayFields.includes(DisplayField.NOTES) && notesEnabled && document.notes.length) {
<button routerLink="/documents/{{document.id}}/notes" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="View notes" i18n-title>
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="chat-left-text"></i-bs><small i18n>{{document.notes.length}} Notes</small>
</button>
}
- @if (document.document_type) {
+ @if (displayFields.includes(DisplayField.DOCUMENT_TYPE) && document.document_type) {
<button type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="Filter by document type" i18n-title
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="file-earmark"></i-bs><small>{{(document.document_type$ | async)?.name}}</small>
</button>
}
- @if (document.storage_path) {
+ @if (displayFields.includes(DisplayField.STORAGE_PATH) && document.storage_path) {
<button type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="Filter by storage path" i18n-title
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="archive"></i-bs><small>{{(document.storage_path$ | async)?.name}}</small>
</button>
}
- @if (document.archive_serial_number | isNumber) {
+ @if (displayFields.includes(DisplayField.ASN) && document.archive_serial_number | isNumber) {
<div class="list-group-item me-2 bg-light text-dark p-1 border-0 d-flex align-items-center">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="upc-scan"></i-bs><small>#{{document.archive_serial_number}}</small>
</div>
}
- <ng-template #dateTooltip>
- <div class="d-flex flex-column text-light">
- <span i18n>Created: {{ document.created | customDate }}</span>
- <span i18n>Added: {{ document.added | customDate }}</span>
- <span i18n>Modified: {{ document.modified | customDate }}</span>
- </div>
- </ng-template>
- <div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
- <i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.created_date | customDate:'mediumDate'}}</small>
- </div>
- @if (document.owner && document.owner !== settingsService.currentUser.id) {
+ @if (displayFields.includes(DisplayField.CREATED) || displayFields.includes(DisplayField.ADDED)) {
+ <ng-template #dateTooltip>
+ <div class="d-flex flex-column text-light">
+ <span i18n>Created: {{ document.created | customDate }}</span>
+ <span i18n>Added: {{ document.added | customDate }}</span>
+ <span i18n>Modified: {{ document.modified | customDate }}</span>
+ </div>
+ </ng-template>
+ @if (displayFields.includes(DisplayField.CREATED)) {
+ <div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
+ <i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.created_date | customDate:'mediumDate'}}</small>
+ </div>
+ }
+ @if (displayFields.includes(DisplayField.ADDED)) {
+ <div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
+ <i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.added | customDate:'mediumDate'}}</small>
+ </div>
+ }
+ }
+ @if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="person-fill-lock"></i-bs><small>{{document.owner | username}}</small>
</div>
}
- @if (document.is_shared_by_requester) {
+ @if (displayFields.includes(DisplayField.SHARED) && document.is_shared_by_requester) {
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="people-fill"></i-bs><small i18n>Shared</small>
</div>
<ngb-progressbar [type]="searchScoreClass" [value]="document.__search_hit__.score" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar>
</div>
}
+ @for (field of document.custom_fields; track field.id) {
+ @if (displayFields.includes(DisplayField.CUSTOM_FIELD + field.field)) {
+ <div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
+ <i-bs width=".9em" height=".9em" class="me-2 text-muted" name="ui-radios"></i-bs>
+ <small>
+ <pngx-custom-field-display [document]="document" [fieldId]="field.field"></pngx-custom-field-display>
+ </small>
+ </div>
+ }
+ }
</div>
</div>
</div>
import { IsNumberPipe } from 'src/app/pipes/is-number.pipe'
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
+import { CustomFieldDisplayComponent } from '../../common/custom-field-display/custom-field-display.component'
const doc = {
id: 10,
SafeUrlPipe,
IsNumberPipe,
PreviewPopupComponent,
+ CustomFieldDisplayComponent,
],
providers: [DatePipe],
imports: [
Output,
ViewChild,
} from '@angular/core'
-import { Document } from 'src/app/data/document'
+import {
+ DEFAULT_DISPLAY_FIELDS,
+ DisplayField,
+ Document,
+} from 'src/app/data/document'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SettingsService } from 'src/app/services/settings.service'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
styleUrls: ['./document-card-large.component.scss'],
})
export class DocumentCardLargeComponent extends ComponentWithPermissions {
+ DisplayField = DisplayField
+
constructor(
private documentService: DocumentService,
public settingsService: SettingsService
@Input()
selected = false
+ @Input()
+ displayFields: string[] = DEFAULT_DISPLAY_FIELDS.map((f) => f.id)
+
@Output()
toggleSelected = new EventEmitter()
</div>
</div>
- <div class="tags d-flex flex-column text-end position-absolute me-1 fs-6">
- @for (t of getTagsLimited$() | async; track t) {
- <pngx-tag [tag]="t" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="true" linkTitle="Toggle tag filter" i18n-linkTitle></pngx-tag>
- }
- @if (moreTags) {
- <div>
- <span class="badge text-dark">+ {{moreTags}}</span>
- </div>
- }
- </div>
+ @if (displayFields?.includes(DisplayField.TAGS)) {
+ <div class="tags d-flex flex-column text-end position-absolute me-1 fs-6">
+ @for (t of getTagsLimited$() | async; track t) {
+ <pngx-tag [tag]="t" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="true" linkTitle="Toggle tag filter" i18n-linkTitle></pngx-tag>
+ }
+ @if (moreTags) {
+ <div>
+ <span class="badge text-dark">+ {{moreTags}}</span>
+ </div>
+ }
+ </div>
+ }
</div>
- @if (notesEnabled && document.notes.length) {
+ @if (displayFields.includes(DisplayField.NOTES) && notesEnabled && document.notes.length) {
<a routerLink="/documents/{{document.id}}/notes" class="document-card-notes py-2 px-1">
<span class="badge rounded-pill bg-light border text-primary">
<i-bs width="1.2em" height="1.2em" class="ms-1 me-1" name="chat-left-text"></i-bs>
<div class="card-body bg-light p-2">
<p class="card-text">
- @if (document.correspondent) {
+ @if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) {
<a title="Toggle correspondent filter" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name ?? privateName}}</a>:
}
- {{document.title | documentTitle}}
+ @if (displayFields.includes(DisplayField.TITLE)) {
+ {{document.title | documentTitle}}
+ }
</p>
</div>
<div class="card-footer pt-0 pb-2 px-2">
<div class="list-group list-group-flush border-0 pt-1 pb-2 card-info">
- @if (document.document_type) {
+ @if (displayFields.includes(DisplayField.DOCUMENT_TYPE) && document.document_type) {
<button 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()">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="file-earmark"></i-bs>
<small>{{(document.document_type$ | async)?.name ?? privateName}}</small>
</button>
}
- @if (document.storage_path) {
+ @if (displayFields.includes(DisplayField.STORAGE_PATH) && document.storage_path) {
<button 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()">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="folder"></i-bs>
<small>{{(document.storage_path$ | async)?.name ?? privateName}}</small>
</button>
}
- <div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
- <ng-template #dateTooltip>
- <div class="d-flex flex-column text-light">
- <span i18n>Created: {{ document.created | customDate }}</span>
- <span i18n>Added: {{ document.added | customDate }}</span>
- <span i18n>Modified: {{ document.modified | customDate }}</span>
+ @if (displayFields.includes(DisplayField.CREATED)) {
+ <div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
+ <ng-template #dateTooltip>
+ <div class="d-flex flex-column text-light">
+ <span i18n>Created: {{ document.created | customDate }}</span>
+ <span i18n>Added: {{ document.added | customDate }}</span>
+ <span i18n>Modified: {{ document.modified | customDate }}</span>
+ </div>
+ </ng-template>
+ <div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
+ <i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs>
+ <small>{{document.created | customDate:'mediumDate'}}</small>
</div>
- </ng-template>
- <div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
- <i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs>
- <small>{{document.created_date | customDate:'mediumDate'}}</small>
</div>
- </div>
- @if (document.archive_serial_number | isNumber) {
+ }
+ @if (displayFields.includes(DisplayField.ADDED)) {
+ <div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
+ <ng-template #dateTooltip>
+ <div class="d-flex flex-column text-light">
+ <span i18n>Created: {{ document.created | customDate }}</span>
+ <span i18n>Added: {{ document.added | customDate }}</span>
+ <span i18n>Modified: {{ document.modified | customDate }}</span>
+ </div>
+ </ng-template>
+ <div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
+ <i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs>
+ <small>{{document.added | customDate:'mediumDate'}}</small>
+ </div>
+ </div>
+ }
+ @if (displayFields.includes(DisplayField.ASN) && document.archive_serial_number | isNumber) {
<div class="ps-0 p-1">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="upc-scan"></i-bs>
<small>#{{document.archive_serial_number}}</small>
</div>
}
- @if (document.owner && document.owner !== settingsService.currentUser.id) {
+ @if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
<div class="ps-0 p-1">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="person-fill-lock"></i-bs>
<small>{{document.owner | username}}</small>
</div>
}
- @if (document.is_shared_by_requester) {
+ @if (displayFields.includes(DisplayField.SHARED) && document.is_shared_by_requester) {
<div class="ps-0 p-1">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="people-fill"></i-bs>
<small i18n>Shared</small>
</div>
}
+ @for (field of document.custom_fields; track field.id) {
+ @if (displayFields.includes(DisplayField.CUSTOM_FIELD + field.field)) {
+ <div class="ps-0 p-1 d-flex align-items-center overflow-hidden">
+ <i-bs width="1em" height="1em" class="me-2 text-muted" name="ui-radios"></i-bs>
+ <small><pngx-custom-field-display [document]="document" [fieldId]="field.field"></pngx-custom-field-display></small>
+ </div>
+ }
+ }
</div>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group w-100">
import { IsNumberPipe } from 'src/app/pipes/is-number.pipe'
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
+import { CustomFieldDisplayComponent } from '../../common/custom-field-display/custom-field-display.component'
const doc = {
id: 10,
TagComponent,
IsNumberPipe,
PreviewPopupComponent,
+ CustomFieldDisplayComponent,
],
providers: [DatePipe],
imports: [
ViewChild,
} from '@angular/core'
import { map } from 'rxjs/operators'
-import { Document } from 'src/app/data/document'
+import {
+ DEFAULT_DISPLAY_FIELDS,
+ DisplayField,
+ Document,
+} from 'src/app/data/document'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SettingsService } from 'src/app/services/settings.service'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
styleUrls: ['./document-card-small.component.scss'],
})
export class DocumentCardSmallComponent extends ComponentWithPermissions {
+ DisplayField = DisplayField
+
constructor(
private documentService: DocumentService,
public settingsService: SettingsService
@Input()
document: Document
+ @Input()
+ displayFields: string[] = DEFAULT_DISPLAY_FIELDS.map((f) => f.id)
+
@Output()
dblClickDocument = new EventEmitter()
<button ngbDropdownItem (click)="list.selectAll()" i18n>Select all</button>
</div>
</div>
+ <div ngbDropdown class="d-flex">
+ <button class="btn btn-sm btn-outline-primary" id="dropdownDisplayFields" ngbDropdownToggle>
+ <i-bs name="card-heading"></i-bs>
+ <div class="d-none d-sm-inline"> <ng-container i18n>Show</ng-container></div>
+ </button>
+ <div ngbDropdownMenu aria-labelledby="dropdownDisplayFields" class="shadow">
+ <div class="px-3">
+ @for (field of settingsService.allDisplayFields; track field.id) {
+ <div class="form-check my-1">
+ <input class="form-check-input mt-1" type="checkbox" id="displayField{{field.id}}" [checked]="activeDisplayFields.includes(field.id)" (change)="toggleDisplayField(field.id)">
+ <label class="form-check-label" for="displayField{{field.id}}">{{field.name}}</label>
+ </div>
+ }
+ </div>
+ </div>
+ </div>
<div class="btn-group flex-fill" role="group">
- <input type="radio" class="btn-check" [(ngModel)]="displayMode" value="details" (ngModelChange)="saveDisplayMode()" id="displayModeDetails" name="displayModeDetails">
+ <input type="radio" class="btn-check" [(ngModel)]="list.displayMode" value="table" id="displayModeDetails" name="displayModeDetails">
<label for="displayModeDetails" class="btn btn-outline-primary btn-sm">
<i-bs name="list-ul"></i-bs>
</label>
- <input type="radio" class="btn-check" [(ngModel)]="displayMode" value="smallCards" (ngModelChange)="saveDisplayMode()" id="displayModeSmall" name="displayModeSmall">
+ <input type="radio" class="btn-check" [(ngModel)]="list.displayMode" value="smallCards" id="displayModeSmall" name="displayModeSmall">
<label for="displayModeSmall" class="btn btn-outline-primary btn-sm">
<i-bs name="grid"></i-bs>
</label>
- <input type="radio" class="btn-check" [(ngModel)]="displayMode" value="largeCards" (ngModelChange)="saveDisplayMode()" id="displayModeLarge" name="displayModeLarge">
+ <input type="radio" class="btn-check" [(ngModel)]="list.displayMode" value="largeCards" id="displayModeLarge" name="displayModeLarge">
<label for="displayModeLarge" class="btn btn-outline-primary btn-sm">
<i-bs name="hdd-stack"></i-bs>
</label>
</div>
<div>
@for (f of getSortFields(); track f) {
- <button ngbDropdownItem (click)="setSortField(f.field)"
+ <button ngbDropdownItem (click)="list.sortField = f.field"
[class.active]="list.sortField === f.field">{{f.name}}
</button>
}
}
</div>
@if (list.collectionSize) {
- <ngb-pagination [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
+ <ngb-pagination [pageSize]="list.pageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
[rotate]="true" aria-label="Default pagination" size="sm"></ngb-pagination>
}
</div>
@if (list.error ) {
<div class="alert alert-danger" role="alert"><ng-container i18n>Error while loading documents</ng-container>: {{list.error}}</div>
} @else {
- @if (displayMode === 'largeCards') {
+ @if (list.displayMode === DisplayMode.LARGE_CARDS) {
<div>
@for (d of list.documents; track trackByDocumentId($index, d)) {
- <pngx-document-card-large [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" (dblClickDocument)="openDocumentDetail(d)" [document]="d" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)" (clickStoragePath)="clickStoragePath($event)" (clickMoreLike)="clickMoreLike(d.id)">
+ <pngx-document-card-large
+ [selected]="list.isSelected(d)"
+ (toggleSelected)="toggleSelected(d, $event)"
+ (dblClickDocument)="openDocumentDetail(d)"
+ [document]="d"
+ [displayFields]="activeDisplayFields"
+ (clickTag)="clickTag($event)"
+ (clickCorrespondent)="clickCorrespondent($event)"
+ (clickDocumentType)="clickDocumentType($event)"
+ (clickStoragePath)="clickStoragePath($event)"
+ (clickMoreLike)="clickMoreLike(d.id)">
</pngx-document-card-large>
}
</div>
}
- @if (displayMode === 'details') {
+ @if (list.displayMode === DisplayMode.TABLE) {
<table class="table table-sm align-middle border shadow-sm">
<thead>
<th></th>
- <th class="d-none d-lg-table-cell"
- pngxSortable="archive_serial_number"
- title="Sort by ASN" i18n-title
- [currentSortField]="list.sortField"
- [currentSortReverse]="list.sortReverse"
- (sort)="onSort($event)"
- i18n>ASN</th>
- @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
+ @if (activeDisplayFields.includes(DisplayField.ASN)) {
+ <th class="d-none d-lg-table-cell"
+ pngxSortable="archive_serial_number"
+ title="Sort by ASN" i18n-title
+ [currentSortField]="list.sortField"
+ [currentSortReverse]="list.sortReverse"
+ (sort)="onSort($event)"
+ i18n>ASN</th>
+ }
+ @if (activeDisplayFields.includes(DisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
<th class="d-none d-md-table-cell"
pngxSortable="correspondent__name"
title="Sort by correspondent" i18n-title
(sort)="onSort($event)"
i18n>Correspondent</th>
}
- <th
- pngxSortable="title"
- title="Sort by title" i18n-title
- class="w-40"
- [currentSortField]="list.sortField"
- [currentSortReverse]="list.sortReverse"
- (sort)="onSort($event)"
- i18n>Title</th>
- <th class="d-none d-xl-table-cell"
- pngxSortable="owner"
- title="Sort by owner" i18n-title
- [currentSortField]="list.sortField"
- [currentSortReverse]="list.sortReverse"
- (sort)="onSort($event)"
- i18n>Owner</th>
- @if (notesEnabled) {
+ @if (activeDisplayFields.includes(DisplayField.TITLE)) {
+ <th
+ pngxSortable="title"
+ title="Sort by title" i18n-title
+ [currentSortField]="list.sortField"
+ [currentSortReverse]="list.sortReverse"
+ (sort)="onSort($event)"
+ i18n>Title</th>
+ }
+ @if (activeDisplayFields.includes(DisplayField.TAGS) && !activeDisplayFields.includes(DisplayField.TITLE)) {
+ <th i18n>Tags</th>
+ }
+ @if (activeDisplayFields.includes(DisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
+ <th class="d-none d-xl-table-cell"
+ pngxSortable="owner"
+ title="Sort by owner" i18n-title
+ [currentSortField]="list.sortField"
+ [currentSortReverse]="list.sortReverse"
+ (sort)="onSort($event)"
+ i18n>Owner</th>
+ }
+ @if (activeDisplayFields.includes(DisplayField.NOTES) && notesEnabled) {
<th class="d-none d-xl-table-cell"
pngxSortable="num_notes"
title="Sort by notes" i18n-title
(sort)="onSort($event)"
i18n>Notes</th>
}
- @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
+ @if (activeDisplayFields.includes(DisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
<th class="d-none d-xl-table-cell"
pngxSortable="document_type__name"
title="Sort by document type" i18n-title
(sort)="onSort($event)"
i18n>Document type</th>
}
- @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
+ @if (activeDisplayFields.includes(DisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
<th class="d-none d-xl-table-cell"
pngxSortable="storage_path__name"
title="Sort by storage path" i18n-title
(sort)="onSort($event)"
i18n>Storage path</th>
}
- <th
- pngxSortable="created"
- title="Sort by created date" i18n-title
- [currentSortField]="list.sortField"
- [currentSortReverse]="list.sortReverse"
- (sort)="onSort($event)"
- i18n>Created</th>
- <th class="d-none d-xl-table-cell"
- pngxSortable="added"
- title="Sort by added date" i18n-title
- [currentSortField]="list.sortField"
- [currentSortReverse]="list.sortReverse"
- (sort)="onSort($event)"
- i18n>Added</th>
+ @if (activeDisplayFields.includes(DisplayField.CREATED)) {
+ <th
+ pngxSortable="created"
+ title="Sort by created date" i18n-title
+ [currentSortField]="list.sortField"
+ [currentSortReverse]="list.sortReverse"
+ (sort)="onSort($event)"
+ i18n>Created</th>
+ }
+ @if (activeDisplayFields.includes(DisplayField.ADDED)) {
+ <th
+ pngxSortable="added"
+ title="Sort by added date" i18n-title
+ [currentSortField]="list.sortField"
+ [currentSortReverse]="list.sortReverse"
+ (sort)="onSort($event)"
+ i18n>Added</th>
+ }
+ @if (activeDisplayFields.includes(DisplayField.SHARED)) {
+ <th i18n>
+ Shared
+ </th>
+ }
+ @for (field of activeDisplayCustomFields; track field) {
+ <th>
+ {{getDisplayCustomFieldTitle(field)}}
+ </th>
+ }
</thead>
<tbody>
@for (d of list.documents; track trackByDocumentId($index, d)) {
<label class="form-check-label" for="docCheck{{d.id}}"></label>
</div>
</td>
- <td class="d-none d-lg-table-cell">
- {{d.archive_serial_number}}
- </td>
- @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
- <td class="d-none d-md-table-cell">
+ @if (activeDisplayFields.includes(DisplayField.ASN)) {
+ <td class="d-none d-xl-table-cell">
+ {{d.archive_serial_number}}
+ </td>
+ }
+ @if (activeDisplayFields.includes(DisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
+ <td class="d-none d-xl-table-cell">
@if (d.correspondent) {
<a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{(d.correspondent$ | async)?.name}}</a>
}
</td>
}
- <td>
- <a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
- @for (t of d.tags$ | async; track t) {
- <pngx-tag [tag]="t" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></pngx-tag>
- }
- </td>
- <td>
- {{d.owner | username}}
- </td>
- @if (notesEnabled) {
+ @if (activeDisplayFields.includes(DisplayField.TITLE) || activeDisplayFields.includes(DisplayField.TAGS)) {
+ <td>
+ @if (activeDisplayFields.includes(DisplayField.TITLE)) {
+ <a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
+ }
+ @if (activeDisplayFields.includes(DisplayField.TAGS)) {
+ @for (t of d.tags$ | async; track t) {
+ <pngx-tag [tag]="t" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></pngx-tag>
+ }
+ }
+ </td>
+ }
+ @if (activeDisplayFields.includes(DisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
+ <td>
+ {{d.owner | username}}
+ </td>
+ }
+ @if (activeDisplayFields.includes(DisplayField.NOTES) && notesEnabled) {
<td class="d-none d-xl-table-cell">
@if (d.notes.length) {
<a routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
}
</td>
}
- @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
+ @if (activeDisplayFields.includes(DisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
<td class="d-none d-xl-table-cell">
@if (d.document_type) {
<a (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type" i18n-title>{{(d.document_type$ | async)?.name}}</a>
}
</td>
}
- @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
+ @if (activeDisplayFields.includes(DisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
<td class="d-none d-xl-table-cell">
@if (d.storage_path) {
<a (click)="clickStoragePath(d.storage_path);$event.stopPropagation()" title="Filter by storage path" i18n-title>{{(d.storage_path$ | async)?.name}}</a>
}
</td>
}
- <td>
- {{d.created_date | customDate}}
- </td>
- <td class="d-none d-xl-table-cell">
- {{d.added | customDate}}
- </td>
+ @if (activeDisplayFields.includes(DisplayField.CREATED)) {
+ <td>
+ {{d.created_date | customDate}}
+ </td>
+ }
+ @if (activeDisplayFields.includes(DisplayField.ADDED)) {
+ <td>
+ {{d.added | customDate}}
+ </td>
+ }
+ @if (activeDisplayFields.includes(DisplayField.SHARED)) {
+ <td>
+ @if (d.is_shared_by_requester) { <ng-container i18n>Yes</ng-container> } @else { <ng-container i18n>No</ng-container> }
+ </td>
+ }
+ @for (field of activeDisplayCustomFields; track field) {
+ <td class="d-none d-xl-table-cell">
+ <pngx-custom-field-display [document]="d" [fieldDisplayKey]="field"></pngx-custom-field-display>
+ </td>
+ }
</tr>
}
</tbody>
</table>
}
- @if (displayMode === 'smallCards') {
+ @if (list.displayMode === DisplayMode.SMALL_CARDS) {
<div class="row row-cols-paperless-cards">
@for (d of list.documents; track trackByDocumentId($index, d)) {
- <pngx-document-card-small class="p-0" [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" (dblClickDocument)="openDocumentDetail(d)" [document]="d" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickStoragePath)="clickStoragePath($event)" (clickDocumentType)="clickDocumentType($event)"></pngx-document-card-small>
+ <pngx-document-card-small class="p-0"
+ [selected]="list.isSelected(d)"
+ (toggleSelected)="toggleSelected(d, $event)"
+ (dblClickDocument)="openDocumentDetail(d)"
+ [document]="d"
+ (clickTag)="clickTag($event)"
+ [displayFields]="activeDisplayFields"
+ (clickCorrespondent)="clickCorrespondent($event)"
+ (clickStoragePath)="clickStoragePath($event)"
+ (clickDocumentType)="clickDocumentType($event)">
+ </pngx-document-card-small>
}
</div>
}
cursor: pointer;
}
-th.w-40 {
- width: 40%;
-}
-
.table-row-selected {
background-color: var(--pngx-primary-faded);
}
a {
cursor: pointer;
}
+
+pngx-page-header .dropdown-menu {
+ --bs-dropdown-min-width: 12em;
+}
import { DocumentCardLargeComponent } from './document-card-large/document-card-large.component'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { UsernamePipe } from 'src/app/pipes/username.pipe'
-import { Document } from 'src/app/data/document'
import {
- DOCUMENT_SORT_FIELDS,
- DOCUMENT_SORT_FIELDS_FULLTEXT,
- DocumentService,
-} from 'src/app/services/rest/document.service'
+ DEFAULT_DISPLAY_FIELDS,
+ DisplayField,
+ DisplayMode,
+ Document,
+} from 'src/app/data/document'
+import { DocumentService } from 'src/app/services/rest/document.service'
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'
component = fixture.componentInstance
})
- it('should load display mode from local storage', () => {
- window.localStorage.setItem('document-list:displayMode', 'largeCards')
- fixture.detectChanges()
- expect(component.displayMode).toEqual('largeCards')
- component.displayMode = 'smallCards'
- component.saveDisplayMode()
- expect(window.localStorage.getItem('document-list:displayMode')).toEqual(
- 'smallCards'
- )
- })
-
it('should reload on new document consumed', () => {
const reloadSpy = jest.spyOn(documentListService, 'reload')
const fileStatusSubject = new Subject<FileStatus>()
},
]
fixture.detectChanges()
- expect(component.getSortFields()).toEqual(DOCUMENT_SORT_FIELDS)
+ expect(component.getSortFields()).toEqual(documentListService.sortFields)
documentListService.filterRules = [
{
},
]
fixture.detectChanges()
- expect(component.getSortFields()).toEqual(DOCUMENT_SORT_FIELDS_FULLTEXT)
+ expect(component.getSortFields()).toEqual(
+ documentListService.sortFieldsFullText
+ )
})
it('should determine if filtered, support reset', () => {
const displayModeButtons = fixture.debugElement.queryAll(
By.css('input[type="radio"]')
)
- expect(component.displayMode).toEqual('smallCards')
+ expect(component.list.displayMode).toEqual('smallCards')
displayModeButtons[0].nativeElement.checked = true
displayModeButtons[0].triggerEventHandler('change')
fixture.detectChanges()
- expect(component.displayMode).toEqual('details')
+ expect(component.list.displayMode).toEqual('table')
expect(fixture.debugElement.queryAll(By.css('tr'))).toHaveLength(3)
displayModeButtons[1].nativeElement.checked = true
displayModeButtons[1].triggerEventHandler('change')
fixture.detectChanges()
- expect(component.displayMode).toEqual('smallCards')
+ expect(component.list.displayMode).toEqual('smallCards')
expect(
fixture.debugElement.queryAll(By.directive(DocumentCardSmallComponent))
).toHaveLength(3)
displayModeButtons[2].nativeElement.checked = true
displayModeButtons[2].triggerEventHandler('change')
fixture.detectChanges()
- expect(component.displayMode).toEqual('largeCards')
+ expect(component.list.displayMode).toEqual('largeCards')
expect(
fixture.debugElement.queryAll(By.directive(DocumentCardLargeComponent))
).toHaveLength(3)
fixture.detectChanges()
const sortDropdown = fixture.debugElement.queryAll(
By.directive(NgbDropdown)
- )[1]
+ )[2]
const asnSortFieldButton = sortDropdown.query(By.directive(NgbDropdownItem))
asnSortFieldButton.triggerEventHandler('click')
})
it('should support setting sort field by table head', () => {
+ component.activeDisplayFields = [DisplayField.ASN]
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
fixture.detectChanges()
expect(documentListService.sortField).toEqual('created')
detailsDisplayModeButton.nativeElement.checked = true
detailsDisplayModeButton.triggerEventHandler('change')
fixture.detectChanges()
- expect(component.displayMode).toEqual('details')
+ expect(component.list.displayMode).toEqual(DisplayMode.TABLE)
const sortTh = fixture.debugElement.query(By.directive(SortableDirective))
sortTh.triggerEventHandler('click')
value: '20',
},
],
+ display_mode: DisplayMode.SMALL_CARDS,
+ display_fields: [DisplayField.TITLE],
}
jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
const queryParams = { view: view.id.toString() }
expect(openModal.componentInstance.error).toEqual({ filter_rules: ['11'] })
})
+ it('should detect saved view changes', () => {
+ const view: SavedView = {
+ id: 10,
+ name: 'Saved View 10',
+ sort_field: 'added',
+ sort_reverse: true,
+ filter_rules: [
+ {
+ rule_type: FILTER_HAS_TAGS_ANY,
+ value: '20',
+ },
+ ],
+ page_size: 5,
+ display_mode: DisplayMode.SMALL_CARDS,
+ display_fields: [DisplayField.TITLE],
+ }
+ jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
+ const queryParams = { view: view.id.toString() }
+ jest
+ .spyOn(activatedRoute, 'queryParamMap', 'get')
+ .mockReturnValue(of(convertToParamMap(queryParams)))
+ activatedRoute.snapshot.queryParams = queryParams
+ router.routerState.snapshot.url = '/view/10/'
+ fixture.detectChanges()
+ expect(documentListService.activeSavedViewId).toEqual(10)
+
+ component.list.displayFields = [DisplayField.ASN]
+ expect(component.savedViewIsModified).toBeTruthy()
+ component.list.displayFields = [DisplayField.TITLE]
+ expect(component.savedViewIsModified).toBeFalsy()
+ component.list.displayMode = DisplayMode.TABLE
+ expect(component.savedViewIsModified).toBeTruthy()
+ component.list.displayMode = DisplayMode.SMALL_CARDS
+ expect(component.savedViewIsModified).toBeFalsy()
+ })
+
it('should navigate to a document', () => {
fixture.detectChanges()
const routerSpy = jest.spyOn(router, 'navigate')
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
expect(documentListService.sortField).toEqual('created')
- component.displayMode = 'details'
+ component.list.displayMode = DisplayMode.TABLE
+ component.list.displayFields = DEFAULT_DISPLAY_FIELDS.map((f) => f.id)
fixture.detectChanges()
expect(
fixture.detectChanges()
expect(
fixture.debugElement.queryAll(By.directive(SortableDirective))
- ).toHaveLength(5)
+ ).toHaveLength(4)
})
it('should support toggle on document objects', () => {
{ rule_type: FILTER_FULLTEXT_MORELIKE, value: '99' },
])
})
+
+ it('should support toggling display fields', () => {
+ fixture.detectChanges()
+ component.activeDisplayFields = [DisplayField.ASN]
+ component.toggleDisplayField(DisplayField.TITLE)
+ expect(component.activeDisplayFields).toEqual([
+ DisplayField.ASN,
+ DisplayField.TITLE,
+ ])
+ component.toggleDisplayField(DisplayField.ASN)
+ expect(component.activeDisplayFields).toEqual([DisplayField.TITLE])
+ })
+
+ it('should get custom field title', () => {
+ fixture.detectChanges()
+ jest
+ .spyOn(settingsService, 'allDisplayFields', 'get')
+ .mockReturnValue([
+ { id: 'custom_field_1' as any, name: 'Custom Field 1' },
+ ])
+ expect(component.getDisplayCustomFieldTitle('custom_field_1')).toEqual(
+ 'Custom Field 1'
+ )
+ })
})
isFullTextFilterRule,
} from 'src/app/utils/filter-rules'
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
-import { Document } from 'src/app/data/document'
+import { DisplayField, DisplayMode, Document } from 'src/app/data/document'
import { SavedView } from 'src/app/data/saved-view'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import {
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
-import {
- DOCUMENT_SORT_FIELDS,
- DOCUMENT_SORT_FIELDS_FULLTEXT,
-} from 'src/app/services/rest/document.service'
import { PermissionsService } from 'src/app/services/permissions.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service'
extends ComponentWithPermissions
implements OnInit, OnDestroy
{
+ DisplayField = DisplayField
+ DisplayMode = DisplayMode
+
constructor(
public list: DocumentListViewService,
public savedViewService: SavedViewService,
private modalService: NgbModal,
private consumerStatusService: ConsumerStatusService,
public openDocumentsService: OpenDocumentsService,
- private settingsService: SettingsService,
+ public settingsService: SettingsService,
public permissionService: PermissionsService
) {
super()
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
- displayMode = 'smallCards' // largeCards, smallCards, details
+ get activeDisplayFields(): DisplayField[] {
+ return this.list.displayFields
+ }
+
+ set activeDisplayFields(fields: DisplayField[]) {
+ this.list.displayFields = fields
+ this.updateDisplayCustomFields()
+ }
+ activeDisplayCustomFields: Set<string> = new Set()
+
+ public updateDisplayCustomFields() {
+ this.activeDisplayCustomFields = new Set(
+ Array.from(this.activeDisplayFields).filter(
+ (field) =>
+ typeof field === 'string' &&
+ field.startsWith(DisplayField.CUSTOM_FIELD)
+ )
+ )
+ }
unmodifiedFilterRules: FilterRule[] = []
private unmodifiedSavedView: SavedView
return (
this.unmodifiedSavedView.sort_field !== this.list.sortField ||
this.unmodifiedSavedView.sort_reverse !== this.list.sortReverse ||
+ (this.unmodifiedSavedView.page_size &&
+ this.unmodifiedSavedView.page_size !== this.list.pageSize) ||
+ (this.unmodifiedSavedView.display_mode &&
+ this.unmodifiedSavedView.display_mode !== this.list.displayMode) ||
+ // if the saved view has no display mode, we assume it's small cards
+ (!this.unmodifiedSavedView.display_mode &&
+ this.list.displayMode !== DisplayMode.SMALL_CARDS) ||
+ (this.unmodifiedSavedView.display_fields &&
+ this.unmodifiedSavedView.display_fields.join(',') !==
+ this.activeDisplayFields.join(',')) ||
filterRulesDiffer(
this.unmodifiedSavedView.filter_rules,
this.list.filterRules
getSortFields() {
return isFullTextFilterRule(this.list.filterRules)
- ? DOCUMENT_SORT_FIELDS_FULLTEXT
- : DOCUMENT_SORT_FIELDS
+ ? this.list.sortFieldsFullText
+ : this.list.sortFields
}
set listSortReverse(reverse: boolean) {
return this.list.sortReverse
}
- setSortField(field: string) {
- this.list.sortField = field
- }
-
onSort(event: SortEvent) {
this.list.setSort(event.column, event.reverse)
}
return this.list.selected.size > 0
}
- saveDisplayMode() {
- localStorage.setItem('document-list:displayMode', this.displayMode)
+ toggleDisplayField(field: DisplayField) {
+ if (this.activeDisplayFields.includes(field)) {
+ this.activeDisplayFields = this.activeDisplayFields.filter(
+ (f) => f !== field
+ )
+ } else {
+ this.activeDisplayFields = [...this.activeDisplayFields, field]
+ }
+ this.updateDisplayCustomFields()
}
- ngOnInit(): void {
- if (localStorage.getItem('document-list:displayMode') != null) {
- this.displayMode = localStorage.getItem('document-list:displayMode')
- }
+ public getDisplayCustomFieldTitle(field: string) {
+ return this.settingsService.allDisplayFields.find((f) => f.id === field)
+ ?.name
+ }
+ ngOnInit(): void {
this.consumerStatusService
.onDocumentConsumptionFinished()
.pipe(takeUntil(this.unsubscribeNotifier))
filter_rules: this.list.filterRules,
sort_field: this.list.sortField,
sort_reverse: this.list.sortReverse,
+ display_mode: this.list.displayMode,
+ display_fields: this.activeDisplayFields,
}
this.savedViewService
.patch(savedView)
filter_rules: this.list.filterRules,
sort_reverse: this.list.sortReverse,
sort_field: this.list.sortField,
+ display_mode: this.list.displayMode,
+ display_fields: this.activeDisplayFields,
}
this.savedViewService
import { DocumentNote } from './document-note'
import { CustomFieldInstance } from './custom-field-instance'
+export enum DisplayMode {
+ TABLE = 'table',
+ SMALL_CARDS = 'smallCards',
+ LARGE_CARDS = 'largeCards',
+}
+
+export enum DisplayField {
+ TITLE = 'title',
+ CREATED = 'created',
+ ADDED = 'added',
+ TAGS = 'tag',
+ CORRESPONDENT = 'correspondent',
+ DOCUMENT_TYPE = 'documenttype',
+ STORAGE_PATH = 'storagepath',
+ CUSTOM_FIELD = 'custom_field_',
+ NOTES = 'note',
+ OWNER = 'owner',
+ SHARED = 'shared',
+ ASN = 'asn',
+}
+
+export const DEFAULT_DISPLAY_FIELDS = [
+ {
+ id: DisplayField.TITLE,
+ name: $localize`Title`,
+ },
+ {
+ id: DisplayField.CREATED,
+ name: $localize`Created`,
+ },
+ {
+ id: DisplayField.ADDED,
+ name: $localize`Added`,
+ },
+ {
+ id: DisplayField.TAGS,
+ name: $localize`Tags`,
+ },
+ {
+ id: DisplayField.CORRESPONDENT,
+ name: $localize`Correspondent`,
+ },
+ {
+ id: DisplayField.DOCUMENT_TYPE,
+ name: $localize`Document type`,
+ },
+ {
+ id: DisplayField.STORAGE_PATH,
+ name: $localize`Storage path`,
+ },
+ {
+ id: DisplayField.NOTES,
+ name: $localize`Notes`,
+ },
+ {
+ id: DisplayField.OWNER,
+ name: $localize`Owner`,
+ },
+ {
+ id: DisplayField.SHARED,
+ name: $localize`Shared`,
+ },
+ {
+ id: DisplayField.ASN,
+ name: $localize`ASN`,
+ },
+]
+
+export const DEFAULT_DASHBOARD_VIEW_PAGE_SIZE = 10
+
+export const DEFAULT_DASHBOARD_DISPLAY_FIELDS = [
+ DisplayField.CREATED,
+ DisplayField.TITLE,
+ DisplayField.TAGS,
+ DisplayField.CORRESPONDENT,
+]
+
+export const DOCUMENT_SORT_FIELDS = [
+ { field: 'archive_serial_number', name: $localize`ASN` },
+ { field: 'correspondent__name', name: $localize`Correspondent` },
+ { field: 'title', name: $localize`Title` },
+ { field: 'document_type__name', name: $localize`Document type` },
+ { field: 'created', name: $localize`Created` },
+ { 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 = [
+ {
+ field: 'score',
+ name: $localize`:Score is a value returned by the full text search engine and specifies how well a result matches the given query:Search score`,
+ },
+]
+
export interface SearchHit {
score?: number
rank?: number
+import { DisplayMode, DisplayField } from './document'
import { FilterRule } from './filter-rule'
import { ObjectWithPermissions } from './object-with-permissions'
sort_reverse: boolean
filter_rules: FilterRule[]
+
+ page_size?: number
+
+ display_mode?: DisplayMode
+
+ display_fields?: DisplayField[]
}
let httpTestingController: HttpTestingController
let consumerStatusService: ConsumerStatusService
let documentService: DocumentService
+ let settingsService: SettingsService
+
const server = new WS(
`${environment.webSocketProtocol}//${environment.webSocketHost}${environment.webSocketBaseUrl}status/`,
{ jsonProtocol: true }
beforeEach(() => {
TestBed.configureTestingModule({
- providers: [
- ConsumerStatusService,
- DocumentService,
- SettingsService,
- {
- provide: SettingsService,
- useValue: {
- currentUser: {
- id: 1,
- username: 'testuser',
- is_superuser: false,
- },
- },
- },
- ],
+ providers: [ConsumerStatusService, DocumentService, SettingsService],
imports: [HttpClientTestingModule],
})
httpTestingController = TestBed.inject(HttpTestingController)
+ settingsService = TestBed.inject(SettingsService)
+ settingsService.currentUser = {
+ id: 1,
+ username: 'testuser',
+ is_superuser: false,
+ }
consumerStatusService = TestBed.inject(ConsumerStatusService)
documentService = TestBed.inject(DocumentService)
})
import { PermissionsGuard } from '../guards/permissions.guard'
import { SettingsService } from './settings.service'
import { SETTINGS_KEYS } from '../data/ui-settings'
+import {
+ DisplayMode,
+ DisplayField,
+ DEFAULT_DISPLAY_FIELDS,
+} from '../data/document'
const documents = [
{
documentListViewService.loadFromQueryParams(convertToParamMap(params))
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/?page=${page}&page_size=${
- documentListViewService.currentPageSize
+ documentListViewService.pageSize
}&ordering=${reverse ? '-' : ''}${sort}&truncate_content=true`
)
expect(req.request.method).toEqual('GET')
}
documentListViewService.loadFromQueryParams(convertToParamMap(params))
let req = httpTestingController.expectOne(
- `${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.currentPageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
+ `${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.pageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
)
expect(req.request.method).toEqual('GET')
expect(documentListViewService.filterRules).toEqual([
it('should use filter rules to update query params', () => {
documentListViewService.filterRules = filterRules
const req = httpTestingController.expectOne(
- `${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.currentPageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
+ `${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.pageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
)
expect(req.request.method).toEqual('GET')
})
it('should support quick filter', () => {
documentListViewService.quickFilter(filterRules)
const req = httpTestingController.expectOne(
- `${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.currentPageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
+ `${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.pageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
)
expect(req.request.method).toEqual('GET')
})
convertToParamMap(params)
)
let req = httpTestingController.expectOne(
- `${environment.apiBaseUrl}documents/?page=${page}&page_size=${documentListViewService.currentPageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
+ `${environment.apiBaseUrl}documents/?page=${page}&page_size=${documentListViewService.pageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
)
expect(req.request.method).toEqual('GET')
// reset the list
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
)
expect(documentListViewService.currentPage).toEqual(1)
- documentListViewService.currentPageSize = 3
- documentListViewService.reload()
+ documentListViewService.pageSize = 3
req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
)
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue(documents)
expect(documentListViewService.currentPage).toEqual(1)
- documentListViewService.currentPageSize = 3
+ documentListViewService.pageSize = 3
+ httpTestingController.match(
+ `${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
+ )
jest
.spyOn(documentListViewService, 'getLastPage')
.mockReturnValue(Math.ceil(documents.length / 3))
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue(documents)
documentListViewService.currentPage = 2
- documentListViewService.currentPageSize = 3
+ httpTestingController.match(
+ `${environment.apiBaseUrl}documents/?page=2&page_size=50&ordering=-created&truncate_content=true`
+ )
+ documentListViewService.pageSize = 3
+ httpTestingController.match(
+ `${environment.apiBaseUrl}documents/?page=2&page_size=3&ordering=-created&truncate_content=true`
+ )
const reloadSpy = jest.spyOn(documentListViewService, 'reload')
documentListViewService.getPrevious(1).subscribe({
next: () => {},
it('should update page size from settings', () => {
settingsService.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, 10)
- documentListViewService.updatePageSize()
- expect(documentListViewService.currentPageSize).toEqual(10)
+ expect(documentListViewService.pageSize).toEqual(10)
})
it('should support select a document', () => {
})
it('should support select page', () => {
- documentListViewService.currentPageSize = 3
- documentListViewService.reload()
+ documentListViewService.pageSize = 3
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
)
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
)
})
+
+ it('should update default view state when display mode changes', () => {
+ const localStorageSpy = jest.spyOn(localStorage, 'setItem')
+ expect(documentListViewService.displayMode).toEqual(DisplayMode.SMALL_CARDS)
+ documentListViewService.displayMode = DisplayMode.LARGE_CARDS
+ expect(documentListViewService.displayMode).toEqual(DisplayMode.LARGE_CARDS)
+ documentListViewService.displayMode = 'details' as any // legacy
+ expect(documentListViewService.displayMode).toEqual(DisplayMode.TABLE)
+ expect(localStorageSpy).toHaveBeenCalledTimes(2)
+ })
+
+ it('should update default view state when display fields change', () => {
+ const localStorageSpy = jest.spyOn(localStorage, 'setItem')
+ documentListViewService.displayFields = [
+ DisplayField.ADDED,
+ DisplayField.TITLE,
+ ]
+ expect(documentListViewService.displayFields).toEqual([
+ DisplayField.ADDED,
+ DisplayField.TITLE,
+ ])
+ expect(localStorageSpy).toHaveBeenCalled()
+ // reload triggered
+ httpTestingController.match(
+ `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
+ )
+ documentListViewService.displayFields = null
+ httpTestingController.match(
+ `${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
+ )
+ expect(documentListViewService.displayFields).toEqual(
+ DEFAULT_DISPLAY_FIELDS.filter((f) => f.id !== DisplayField.ADDED).map(
+ (f) => f.id
+ )
+ )
+ })
})
cloneFilterRules,
isFullTextFilterRule,
} from '../utils/filter-rules'
-import { Document } from '../data/document'
+import {
+ DEFAULT_DISPLAY_FIELDS,
+ DisplayField,
+ DisplayMode,
+ Document,
+} from '../data/document'
import { SavedView } from '../data/saved-view'
import { SETTINGS_KEYS } from '../data/ui-settings'
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
import { paramsFromViewState, paramsToViewState } from '../utils/query-params'
-import {
- DocumentService,
- DOCUMENT_SORT_FIELDS,
- SelectionData,
-} from './rest/document.service'
+import { DocumentService, SelectionData } from './rest/document.service'
import { SettingsService } from './settings.service'
/**
* Contains the IDs of all selected documents.
*/
selected?: Set<number>
+
+ /**
+ * The page size of the list view.
+ */
+ pageSize?: number
+
+ /**
+ * Display mode of the list view.
+ */
+ displayMode?: DisplayMode
+
+ /**
+ * The fields to display in the document list.
+ */
+ displayFields?: DisplayField[]
}
/**
selectionData?: SelectionData
- currentPageSize: number = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
-
private unsubscribeNotifier: Subject<any> = new Subject()
private listViewStates: Map<number, ListViewState> = new Map()
delete savedState[k]
}
})
- //only use restored state attributes instead of defaults if they are not null
+ // only use restored state attributes instead of defaults if they are not null
let newState = Object.assign(this.defaultListViewState(), savedState)
this.listViewStates.set(null, newState)
} catch (e) {
if (this._activeSavedViewId) {
this.activeListViewState.title = view.name
}
+ this.activeListViewState.displayMode = view.display_mode
+ this.activeListViewState.pageSize = view.page_size
+ this.activeListViewState.displayFields = view.display_fields
this.reduceSelectionToFilter()
this.documentService
.listFiltered(
activeListViewState.currentPage,
- this.currentPageSize,
+ activeListViewState.pageSize ?? this.pageSize,
activeListViewState.sortField,
activeListViewState.sortReverse,
activeListViewState.filterRules,
errorMessage = Object.keys(error.error)
.map((fieldName) => {
const fieldError: Array<string> = error.error[fieldName]
- return `${DOCUMENT_SORT_FIELDS.find(
- (f) => f.field == fieldName
- )?.name}: ${fieldError[0]}`
+ return `${this.sortFields.find((f) => f.field == fieldName)
+ ?.name}: ${fieldError[0]}`
})
.join(', ')
} else {
return this.activeListViewState.filterRules
}
+ get sortFields(): any[] {
+ return this.documentService.sortFields
+ }
+
+ get sortFieldsFullText(): any[] {
+ return this.documentService.sortFieldsFullText
+ }
+
set sortField(field: string) {
this.activeListViewState.sortField = field
this.reload()
this.saveDocumentListView()
}
+ set displayMode(mode: DisplayMode) {
+ this.activeListViewState.displayMode = mode
+ this.saveDocumentListView()
+ }
+
+ get displayMode(): DisplayMode {
+ const mode = this.activeListViewState.displayMode ?? DisplayMode.SMALL_CARDS
+ if (mode === ('details' as any)) {
+ // legacy
+ return DisplayMode.TABLE
+ }
+ return mode
+ }
+
+ set pageSize(size: number) {
+ this.activeListViewState.pageSize = size
+ this.reload()
+ this.saveDocumentListView()
+ }
+
+ get pageSize(): number {
+ return (
+ this.activeListViewState.pageSize ??
+ this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
+ )
+ }
+
+ get displayFields(): DisplayField[] {
+ let fields =
+ this.activeListViewState.displayFields ??
+ DEFAULT_DISPLAY_FIELDS.map((f) => f.id)
+ if (!this.activeListViewState.displayFields) {
+ fields = fields.filter((f) => f !== DisplayField.ADDED)
+ }
+ return fields.filter(
+ (field) =>
+ this.settings.allDisplayFields.find((f) => f.id === field) !== undefined
+ )
+ }
+
+ set displayFields(fields: DisplayField[]) {
+ this.activeListViewState.displayFields = fields
+ this.saveDocumentListView()
+ }
+
private saveDocumentListView() {
if (this._activeSavedViewId == null) {
let savedState: ListViewState = {
filterRules: this.activeListViewState.filterRules,
sortField: this.activeListViewState.sortField,
sortReverse: this.activeListViewState.sortReverse,
+ displayMode: this.activeListViewState.displayMode,
+ displayFields: this.activeListViewState.displayFields,
}
localStorage.setItem(
DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG,
}
getLastPage(): number {
- return Math.ceil(this.collectionSize / this.currentPageSize)
+ return Math.ceil(this.collectionSize / this.pageSize)
}
hasNext(doc: number) {
})
}
- updatePageSize() {
- let newPageSize = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
- if (newPageSize != this.currentPageSize) {
- this.currentPageSize = newPageSize
- }
- }
-
selectNone() {
this.selected.clear()
this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null
import { Injectable } from '@angular/core'
-import { HttpClient, HttpParams } from '@angular/common/http'
+import { HttpClient } from '@angular/common/http'
import { AbstractPaperlessService } from './abstract-paperless-service'
-import { Observable } from 'rxjs'
import { CustomField } from 'src/app/data/custom-field'
-import { CustomFieldInstance } from 'src/app/data/custom-field-instance'
@Injectable({
providedIn: 'root',
import { FILTER_TITLE } from 'src/app/data/filter-rule-type'
import { SettingsService } from '../settings.service'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
+import {
+ DOCUMENT_SORT_FIELDS,
+ DOCUMENT_SORT_FIELDS_FULLTEXT,
+} from 'src/app/data/document'
+import { PermissionsService } from '../permissions.service'
let httpTestingController: HttpTestingController
let service: DocumentService
let subscription: Subscription
let settingsService: SettingsService
+
const endpoint = 'documents'
const documents = [
{
})
})
+it('should construct sort fields respecting permissions', () => {
+ expect(
+ service.sortFields.find((f) => f.field === 'correspondent__name')
+ ).toBeUndefined()
+ expect(
+ service.sortFields.find((f) => f.field === 'document_type__name')
+ ).toBeUndefined()
+
+ const permissionsService: PermissionsService =
+ TestBed.inject(PermissionsService)
+ jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
+ service['setupSortFields']()
+ expect(service.sortFields).toEqual(DOCUMENT_SORT_FIELDS)
+ expect(service.sortFieldsFullText).toEqual([
+ ...DOCUMENT_SORT_FIELDS,
+ ...DOCUMENT_SORT_FIELDS_FULLTEXT,
+ ])
+
+ settingsService.set(SETTINGS_KEYS.NOTES_ENABLED, false)
+ service['setupSortFields']()
+ expect(
+ service.sortFields.find((f) => f.field === 'num_notes')
+ ).toBeUndefined()
+})
+
afterEach(() => {
subscription?.unsubscribe()
httpTestingController.verify()
import { Injectable } from '@angular/core'
-import { Document } from 'src/app/data/document'
+import {
+ DOCUMENT_SORT_FIELDS,
+ DOCUMENT_SORT_FIELDS_FULLTEXT,
+ Document,
+} from 'src/app/data/document'
import { DocumentMetadata } from 'src/app/data/document-metadata'
import { AbstractPaperlessService } from './abstract-paperless-service'
import { HttpClient } from '@angular/common/http'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { AuditLogEntry } from 'src/app/data/auditlog-entry'
-export const DOCUMENT_SORT_FIELDS = [
- { field: 'archive_serial_number', name: $localize`ASN` },
- { field: 'correspondent__name', name: $localize`Correspondent` },
- { field: 'title', name: $localize`Title` },
- { field: 'document_type__name', name: $localize`Document type` },
- { field: 'created', name: $localize`Created` },
- { 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 = [
- ...DOCUMENT_SORT_FIELDS,
- {
- field: 'score',
- name: $localize`:Score is a value returned by the full text search engine and specifies how well a result matches the given query:Search score`,
- },
-]
-
export interface SelectionDataItem {
id: number
document_count: number
export class DocumentService extends AbstractPaperlessService<Document> {
private _searchQuery: string
+ private _sortFields
+ get sortFields() {
+ return this._sortFields
+ }
+
+ private _sortFieldsFullText
+ get sortFieldsFullText() {
+ return this._sortFieldsFullText
+ }
+
constructor(
http: HttpClient,
private correspondentService: CorrespondentService,
private settingsService: SettingsService
) {
super(http, 'documents')
+ this.setupSortFields()
+ }
+
+ private setupSortFields() {
+ this._sortFields = [...DOCUMENT_SORT_FIELDS]
+ let excludes = []
+ if (
+ !this.permissionsService.currentUserCan(
+ PermissionAction.View,
+ PermissionType.Correspondent
+ )
+ ) {
+ excludes.push('correspondent__name')
+ }
+ if (
+ !this.permissionsService.currentUserCan(
+ PermissionAction.View,
+ PermissionType.DocumentType
+ )
+ ) {
+ excludes.push('document_type__name')
+ }
+ if (
+ !this.permissionsService.currentUserCan(
+ PermissionAction.View,
+ PermissionType.User
+ )
+ ) {
+ excludes.push('owner')
+ }
+ if (!this.settingsService.get(SETTINGS_KEYS.NOTES_ENABLED)) {
+ excludes.push('num_notes')
+ }
+ this._sortFields = this._sortFields.filter(
+ (field) => !excludes.includes(field.field)
+ )
+ this._sortFieldsFullText = [
+ ...this._sortFields,
+ ...DOCUMENT_SORT_FIELDS_FULLTEXT,
+ ]
}
addObservablesToDocument(doc: Document) {
import { RouterTestingModule } from '@angular/router/testing'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { CookieService } from 'ngx-cookie-service'
-import { Subscription } from 'rxjs'
+import { Subscription, of } from 'rxjs'
import { environment } from 'src/environments/environment'
import { AppModule } from '../app.module'
import { UiSettings, SETTINGS_KEYS } from '../data/ui-settings'
import { SettingsService } from './settings.service'
import { SavedView } from '../data/saved-view'
+import { CustomFieldsService } from './rest/custom-fields.service'
+import { CustomFieldDataType } from '../data/custom-field'
+import { PermissionsService } from './permissions.service'
+import { DEFAULT_DISPLAY_FIELDS, DisplayField } from '../data/document'
+
+const customFields = [
+ {
+ id: 1,
+ name: 'Field 1',
+ created: new Date(),
+ data_type: CustomFieldDataType.Monetary,
+ },
+ {
+ id: 2,
+ name: 'Field 2',
+ created: new Date(),
+ data_type: CustomFieldDataType.String,
+ },
+]
describe('SettingsService', () => {
let httpTestingController: HttpTestingController
let settingsService: SettingsService
let cookieService: CookieService
+ let customFieldsService: CustomFieldsService
+ let permissionService: PermissionsService
let subscription: Subscription
const ui_settings: UiSettings = {
httpTestingController = TestBed.inject(HttpTestingController)
cookieService = TestBed.inject(CookieService)
+ customFieldsService = TestBed.inject(CustomFieldsService)
+ permissionService = TestBed.inject(PermissionsService)
settingsService = TestBed.inject(SettingsService)
})
afterEach(() => {
subscription?.unsubscribe()
- httpTestingController.verify()
+ // httpTestingController.verify()
})
it('calls ui_settings api endpoint on initialize', () => {
// post for migrate
httpTestingController.expectOne(`${environment.apiBaseUrl}ui_settings/`)
})
+
+ it('should hide fields if no perms or disabled', () => {
+ jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(false)
+ const req = httpTestingController.expectOne(
+ `${environment.apiBaseUrl}ui_settings/`
+ )
+ req.flush(ui_settings)
+ settingsService.initializeDisplayFields()
+ expect(
+ settingsService.allDisplayFields.includes(DEFAULT_DISPLAY_FIELDS[0])
+ ).toBeTruthy() // title
+ expect(
+ settingsService.allDisplayFields.includes(DEFAULT_DISPLAY_FIELDS[4])
+ ).toBeFalsy() // correspondent
+
+ settingsService.set(SETTINGS_KEYS.NOTES_ENABLED, false)
+ settingsService.initializeDisplayFields()
+ expect(
+ settingsService.allDisplayFields.includes(DEFAULT_DISPLAY_FIELDS[8])
+ ).toBeFalsy() // notes
+
+ jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(true)
+ settingsService.initializeDisplayFields()
+ expect(
+ settingsService.allDisplayFields.includes(DEFAULT_DISPLAY_FIELDS[4])
+ ).toBeTruthy() // correspondent
+ })
+
+ it('should dynamically create display fields options including custom fields', () => {
+ jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(true)
+ jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
+ of({
+ all: customFields.map((f) => f.id),
+ count: customFields.length,
+ results: customFields.concat([]),
+ })
+ )
+ settingsService.initializeDisplayFields()
+ expect(
+ settingsService.allDisplayFields.includes(DEFAULT_DISPLAY_FIELDS[0])
+ ).toBeTruthy()
+ expect(
+ settingsService.allDisplayFields.find(
+ (f) => f.id === `${DisplayField.CUSTOM_FIELD}${customFields[0].id}`
+ ).name
+ ).toEqual(customFields[0].name)
+ })
})
import { environment } from 'src/environments/environment'
import { UiSettings, SETTINGS, SETTINGS_KEYS } from '../data/ui-settings'
import { User } from '../data/user'
-import { PermissionsService } from './permissions.service'
+import {
+ PermissionAction,
+ PermissionType,
+ PermissionsService,
+} from './permissions.service'
import { ToastService } from './toast.service'
import { SavedView } from '../data/saved-view'
+import { CustomFieldsService } from './rest/custom-fields.service'
+import { DEFAULT_DISPLAY_FIELDS, DisplayField } from '../data/document'
export interface LanguageOption {
code: string
public globalDropzoneActive: boolean = false
public organizingSidebarSavedViews: boolean = false
+ private _allDisplayFields: Array<{ id: DisplayField; name: string }> =
+ DEFAULT_DISPLAY_FIELDS
+ public get allDisplayFields(): Array<{ id: DisplayField; name: string }> {
+ return this._allDisplayFields
+ }
+
constructor(
rendererFactory: RendererFactory2,
@Inject(DOCUMENT) private document,
@Inject(LOCALE_ID) private localeId: string,
protected http: HttpClient,
private toastService: ToastService,
- private permissionsService: PermissionsService
+ private permissionsService: PermissionsService,
+ private customFieldsService: CustomFieldsService
) {
this._renderer = rendererFactory.createRenderer(null, null)
}
uisettings.permissions,
this.currentUser
)
+
+ this.initializeDisplayFields()
})
)
}
+ public initializeDisplayFields() {
+ this._allDisplayFields = DEFAULT_DISPLAY_FIELDS
+
+ this._allDisplayFields = this._allDisplayFields
+ ?.map((field) => {
+ if (
+ field.id === DisplayField.NOTES &&
+ !this.get(SETTINGS_KEYS.NOTES_ENABLED)
+ ) {
+ return null
+ }
+
+ if (
+ [
+ DisplayField.TITLE,
+ DisplayField.CREATED,
+ DisplayField.ADDED,
+ DisplayField.ASN,
+ DisplayField.SHARED,
+ ].includes(field.id)
+ ) {
+ return field
+ }
+
+ let type: PermissionType = Object.values(PermissionType).find((t) =>
+ t.includes(field.id)
+ )
+ if (field.id === DisplayField.OWNER) {
+ type = PermissionType.User
+ }
+ return this.permissionsService.currentUserCan(
+ PermissionAction.View,
+ type
+ )
+ ? field
+ : null
+ })
+ .filter((f) => f)
+
+ if (
+ this.permissionsService.currentUserCan(
+ PermissionAction.View,
+ PermissionType.CustomField
+ )
+ ) {
+ this.customFieldsService.listAll().subscribe((r) => {
+ this._allDisplayFields = this._allDisplayFields.concat(
+ r.results.map((field) => {
+ return {
+ id: `${DisplayField.CUSTOM_FIELD}${field.id}` as any,
+ name: field.name,
+ }
+ })
+ )
+ })
+ }
+ }
+
get displayName(): string {
return (
this.currentUser.first_name ??
--- /dev/null
+# Generated by Django 4.2.11 on 2024-04-16 18:35
+
+import django.core.validators
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("documents", "1046_workflowaction_remove_all_correspondents_and_more"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="savedview",
+ name="display_mode",
+ field=models.CharField(
+ blank=True,
+ choices=[
+ ("table", "Table"),
+ ("smallCards", "Small Cards"),
+ ("largeCards", "Large Cards"),
+ ],
+ max_length=128,
+ null=True,
+ verbose_name="View display mode",
+ ),
+ ),
+ migrations.AddField(
+ model_name="savedview",
+ name="page_size",
+ field=models.PositiveIntegerField(
+ blank=True,
+ null=True,
+ validators=[django.core.validators.MinValueValidator(1)],
+ verbose_name="View page size",
+ ),
+ ),
+ migrations.AddField(
+ model_name="savedview",
+ name="display_fields",
+ field=models.JSONField(
+ blank=True,
+ null=True,
+ verbose_name="Document display fields",
+ ),
+ ),
+ ]
class SavedView(ModelWithOwner):
+ class DisplayMode(models.TextChoices):
+ TABLE = ("table", _("Table"))
+ SMALL_CARDS = ("smallCards", _("Small Cards"))
+ LARGE_CARDS = ("largeCards", _("Large Cards"))
+
+ class DisplayFields(models.TextChoices):
+ TITLE = ("title", _("Title"))
+ CREATED = ("created", _("Created"))
+ ADDED = ("added", _("Added"))
+ TAGS = ("tag"), _("Tags")
+ CORRESPONDENT = ("correspondent", _("Correspondent"))
+ DOCUMENT_TYPE = ("documenttype", _("Document Type"))
+ STORAGE_PATH = ("storagepath", _("Storage Path"))
+ NOTES = ("note", _("Note"))
+ OWNER = ("owner", _("Owner"))
+ SHARED = ("shared", _("Shared"))
+ ASN = ("asn", _("ASN"))
+ CUSTOM_FIELD = ("custom_field_%d", ("Custom Field"))
+
name = models.CharField(_("name"), max_length=128)
show_on_dashboard = models.BooleanField(
)
sort_reverse = models.BooleanField(_("sort reverse"), default=False)
+ page_size = models.PositiveIntegerField(
+ _("View page size"),
+ null=True,
+ blank=True,
+ validators=[MinValueValidator(1)],
+ )
+
+ display_mode = models.CharField(
+ max_length=128,
+ verbose_name=_("View display mode"),
+ choices=DisplayMode.choices,
+ null=True,
+ blank=True,
+ )
+
+ display_fields = models.JSONField(
+ verbose_name=_("Document display fields"),
+ null=True,
+ blank=True,
+ )
+
class Meta:
ordering = ("name",)
verbose_name = _("saved view")
"sort_field",
"sort_reverse",
"filter_rules",
+ "page_size",
+ "display_mode",
+ "display_fields",
"owner",
"permissions",
"user_can_change",
"set_permissions",
]
+ def validate(self, attrs):
+ attrs = super().validate(attrs)
+ if "display_fields" in attrs and attrs["display_fields"] is not None:
+ for field in attrs["display_fields"]:
+ if (
+ SavedView.DisplayFields.CUSTOM_FIELD[:-2] in field
+ ): # i.e. check for 'custom_field_' prefix
+ field_id = int(re.search(r"\d+", field)[0])
+ if not CustomField.objects.filter(id=field_id).exists():
+ raise serializers.ValidationError(
+ f"Invalid field: {field}",
+ )
+ elif field not in SavedView.DisplayFields.values:
+ raise serializers.ValidationError(
+ f"Invalid field: {field}",
+ )
+ return attrs
+
def update(self, instance, validated_data):
if "filter_rules" in validated_data:
rules_data = validated_data.pop("filter_rules")
status.HTTP_404_NOT_FOUND,
)
- def test_create_update_patch(self):
+ def test_saved_view_create_update_patch(self):
User.objects.create_user("user1")
view = {
v1 = SavedView.objects.get(id=v1.id)
self.assertEqual(v1.filter_rules.count(), 0)
+ def test_saved_view_display_options(self):
+ """
+ GIVEN:
+ - Saved view
+ WHEN:
+ - Updating display options
+ THEN:
+ - Display options are updated
+ - Display fields are validated
+ """
+ User.objects.create_user("user1")
+
+ view = {
+ "name": "test",
+ "show_on_dashboard": True,
+ "show_in_sidebar": True,
+ "sort_field": "created2",
+ "filter_rules": [{"rule_type": 4, "value": "test"}],
+ "page_size": 20,
+ "display_mode": SavedView.DisplayMode.SMALL_CARDS,
+ "display_fields": [
+ SavedView.DisplayFields.TITLE,
+ SavedView.DisplayFields.CREATED,
+ ],
+ }
+
+ response = self.client.post("/api/saved_views/", view, format="json")
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ v1 = SavedView.objects.get(name="test")
+ self.assertEqual(v1.page_size, 20)
+ self.assertEqual(
+ v1.display_mode,
+ SavedView.DisplayMode.SMALL_CARDS,
+ )
+ self.assertEqual(
+ v1.display_fields,
+ [
+ SavedView.DisplayFields.TITLE,
+ SavedView.DisplayFields.CREATED,
+ ],
+ )
+
+ response = self.client.patch(
+ f"/api/saved_views/{v1.id}/",
+ {
+ "display_fields": [
+ SavedView.DisplayFields.TAGS,
+ SavedView.DisplayFields.TITLE,
+ SavedView.DisplayFields.CREATED,
+ ],
+ },
+ format="json",
+ )
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ v1.refresh_from_db()
+ self.assertEqual(
+ v1.display_fields,
+ [
+ SavedView.DisplayFields.TAGS,
+ SavedView.DisplayFields.TITLE,
+ SavedView.DisplayFields.CREATED,
+ ],
+ )
+
+ # Invalid display field
+ response = self.client.patch(
+ f"/api/saved_views/{v1.id}/",
+ {
+ "display_fields": [
+ "foobar",
+ ],
+ },
+ format="json",
+ )
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ def test_saved_view_display_customfields(self):
+ """
+ GIVEN:
+ - Saved view
+ WHEN:
+ - Updating display options with custom fields
+ THEN:
+ - Display filds for custom fields are updated
+ - Display fields for custom fields are validated
+ """
+ view = {
+ "name": "test",
+ "show_on_dashboard": True,
+ "show_in_sidebar": True,
+ "sort_field": "created2",
+ "filter_rules": [{"rule_type": 4, "value": "test"}],
+ "page_size": 20,
+ "display_mode": SavedView.DisplayMode.SMALL_CARDS,
+ "display_fields": [
+ SavedView.DisplayFields.TITLE,
+ SavedView.DisplayFields.CREATED,
+ ],
+ }
+
+ response = self.client.post("/api/saved_views/", view, format="json")
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ v1 = SavedView.objects.get(name="test")
+
+ custom_field = CustomField.objects.create(
+ name="stringfield",
+ data_type=CustomField.FieldDataType.STRING,
+ )
+
+ response = self.client.patch(
+ f"/api/saved_views/{v1.id}/",
+ {
+ "display_fields": [
+ SavedView.DisplayFields.TITLE,
+ SavedView.DisplayFields.CREATED,
+ SavedView.DisplayFields.CUSTOM_FIELD % custom_field.id,
+ ],
+ },
+ format="json",
+ )
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ v1.refresh_from_db()
+ self.assertEqual(
+ v1.display_fields,
+ [
+ str(SavedView.DisplayFields.TITLE),
+ str(SavedView.DisplayFields.CREATED),
+ SavedView.DisplayFields.CUSTOM_FIELD % custom_field.id,
+ ],
+ )
+
+ # Custom field not found
+ response = self.client.patch(
+ f"/api/saved_views/{v1.id}/",
+ {
+ "display_fields": [
+ SavedView.DisplayFields.TITLE,
+ SavedView.DisplayFields.CREATED,
+ SavedView.DisplayFields.CUSTOM_FIELD % 99,
+ ],
+ },
+ format="json",
+ )
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
def test_get_logs(self):
log_data = "test\ntest2\n"
with open(os.path.join(settings.LOGGING_DIR, "mail.log"), "w") as f:
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-04-19 01:15-0700\n"
+"POT-Creation-Date: 2024-04-24 22:54-0700\n"
"PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n"
"Language-Team: English\n"
msgid "Documents"
msgstr ""
-#: documents/models.py:36 documents/models.py:739
+#: documents/models.py:36 documents/models.py:779
msgid "owner"
msgstr ""
-#: documents/models.py:53 documents/models.py:902
+#: documents/models.py:53 documents/models.py:942
msgid "None"
msgstr ""
-#: documents/models.py:54 documents/models.py:903
+#: documents/models.py:54 documents/models.py:943
msgid "Any word"
msgstr ""
-#: documents/models.py:55 documents/models.py:904
+#: documents/models.py:55 documents/models.py:944
msgid "All words"
msgstr ""
-#: documents/models.py:56 documents/models.py:905
+#: documents/models.py:56 documents/models.py:945
msgid "Exact match"
msgstr ""
-#: documents/models.py:57 documents/models.py:906
+#: documents/models.py:57 documents/models.py:946
msgid "Regular expression"
msgstr ""
-#: documents/models.py:58 documents/models.py:907
+#: documents/models.py:58 documents/models.py:947
msgid "Fuzzy word"
msgstr ""
msgid "Automatic"
msgstr ""
-#: documents/models.py:62 documents/models.py:397 documents/models.py:1223
+#: documents/models.py:62 documents/models.py:416 documents/models.py:1263
#: paperless_mail/models.py:18 paperless_mail/models.py:93
msgid "name"
msgstr ""
-#: documents/models.py:64 documents/models.py:963
+#: documents/models.py:64 documents/models.py:1003
msgid "match"
msgstr ""
-#: documents/models.py:67 documents/models.py:966
+#: documents/models.py:67 documents/models.py:1006
msgid "matching algorithm"
msgstr ""
-#: documents/models.py:72 documents/models.py:971
+#: documents/models.py:72 documents/models.py:1011
msgid "is insensitive"
msgstr ""
msgid "title"
msgstr ""
-#: documents/models.py:171 documents/models.py:653
+#: documents/models.py:171 documents/models.py:693
msgid "content"
msgstr ""
msgid "The checksum of the archived document."
msgstr ""
-#: documents/models.py:205 documents/models.py:385 documents/models.py:659
-#: documents/models.py:697 documents/models.py:767 documents/models.py:804
+#: documents/models.py:205 documents/models.py:385 documents/models.py:699
+#: documents/models.py:737 documents/models.py:807 documents/models.py:844
msgid "created"
msgstr ""
msgid "The position of this document in your physical document archive."
msgstr ""
-#: documents/models.py:279 documents/models.py:670 documents/models.py:724
+#: documents/models.py:279 documents/models.py:710 documents/models.py:764
msgid "document"
msgstr ""
msgid "logs"
msgstr ""
+#: documents/models.py:398
+msgid "Table"
+msgstr ""
+
+#: documents/models.py:399
+msgid "Small Cards"
+msgstr ""
+
#: documents/models.py:400
-msgid "show on dashboard"
+msgid "Large Cards"
msgstr ""
#: documents/models.py:403
-msgid "show in sidebar"
+msgid "Title"
+msgstr ""
+
+#: documents/models.py:404
+msgid "Created"
+msgstr ""
+
+#: documents/models.py:405
+msgid "Added"
+msgstr ""
+
+#: documents/models.py:406
+msgid "Tags"
msgstr ""
#: documents/models.py:407
-msgid "sort field"
+msgid "Correspondent"
+msgstr ""
+
+#: documents/models.py:408
+msgid "Document Type"
+msgstr ""
+
+#: documents/models.py:409
+msgid "Storage Path"
+msgstr ""
+
+#: documents/models.py:410
+msgid "Note"
+msgstr ""
+
+#: documents/models.py:411
+msgid "Owner"
msgstr ""
#: documents/models.py:412
+msgid "Shared"
+msgstr ""
+
+#: documents/models.py:413
+msgid "ASN"
+msgstr ""
+
+#: documents/models.py:419
+msgid "show on dashboard"
+msgstr ""
+
+#: documents/models.py:422
+msgid "show in sidebar"
+msgstr ""
+
+#: documents/models.py:426
+msgid "sort field"
+msgstr ""
+
+#: documents/models.py:431
msgid "sort reverse"
msgstr ""
-#: documents/models.py:416 documents/models.py:469
+#: documents/models.py:434
+msgid "View page size"
+msgstr ""
+
+#: documents/models.py:442
+msgid "View display mode"
+msgstr ""
+
+#: documents/models.py:449
+msgid "Document display fields"
+msgstr ""
+
+#: documents/models.py:456 documents/models.py:509
msgid "saved view"
msgstr ""
-#: documents/models.py:417
+#: documents/models.py:457
msgid "saved views"
msgstr ""
-#: documents/models.py:425
+#: documents/models.py:465
msgid "title contains"
msgstr ""
-#: documents/models.py:426
+#: documents/models.py:466
msgid "content contains"
msgstr ""
-#: documents/models.py:427
+#: documents/models.py:467
msgid "ASN is"
msgstr ""
-#: documents/models.py:428
+#: documents/models.py:468
msgid "correspondent is"
msgstr ""
-#: documents/models.py:429
+#: documents/models.py:469
msgid "document type is"
msgstr ""
-#: documents/models.py:430
+#: documents/models.py:470
msgid "is in inbox"
msgstr ""
-#: documents/models.py:431
+#: documents/models.py:471
msgid "has tag"
msgstr ""
-#: documents/models.py:432
+#: documents/models.py:472
msgid "has any tag"
msgstr ""
-#: documents/models.py:433
+#: documents/models.py:473
msgid "created before"
msgstr ""
-#: documents/models.py:434
+#: documents/models.py:474
msgid "created after"
msgstr ""
-#: documents/models.py:435
+#: documents/models.py:475
msgid "created year is"
msgstr ""
-#: documents/models.py:436
+#: documents/models.py:476
msgid "created month is"
msgstr ""
-#: documents/models.py:437
+#: documents/models.py:477
msgid "created day is"
msgstr ""
-#: documents/models.py:438
+#: documents/models.py:478
msgid "added before"
msgstr ""
-#: documents/models.py:439
+#: documents/models.py:479
msgid "added after"
msgstr ""
-#: documents/models.py:440
+#: documents/models.py:480
msgid "modified before"
msgstr ""
-#: documents/models.py:441
+#: documents/models.py:481
msgid "modified after"
msgstr ""
-#: documents/models.py:442
+#: documents/models.py:482
msgid "does not have tag"
msgstr ""
-#: documents/models.py:443
+#: documents/models.py:483
msgid "does not have ASN"
msgstr ""
-#: documents/models.py:444
+#: documents/models.py:484
msgid "title or content contains"
msgstr ""
-#: documents/models.py:445
+#: documents/models.py:485
msgid "fulltext query"
msgstr ""
-#: documents/models.py:446
+#: documents/models.py:486
msgid "more like this"
msgstr ""
-#: documents/models.py:447
+#: documents/models.py:487
msgid "has tags in"
msgstr ""
-#: documents/models.py:448
+#: documents/models.py:488
msgid "ASN greater than"
msgstr ""
-#: documents/models.py:449
+#: documents/models.py:489
msgid "ASN less than"
msgstr ""
-#: documents/models.py:450
+#: documents/models.py:490
msgid "storage path is"
msgstr ""
-#: documents/models.py:451
+#: documents/models.py:491
msgid "has correspondent in"
msgstr ""
-#: documents/models.py:452
+#: documents/models.py:492
msgid "does not have correspondent in"
msgstr ""
-#: documents/models.py:453
+#: documents/models.py:493
msgid "has document type in"
msgstr ""
-#: documents/models.py:454
+#: documents/models.py:494
msgid "does not have document type in"
msgstr ""
-#: documents/models.py:455
+#: documents/models.py:495
msgid "has storage path in"
msgstr ""
-#: documents/models.py:456
+#: documents/models.py:496
msgid "does not have storage path in"
msgstr ""
-#: documents/models.py:457
+#: documents/models.py:497
msgid "owner is"
msgstr ""
-#: documents/models.py:458
+#: documents/models.py:498
msgid "has owner in"
msgstr ""
-#: documents/models.py:459
+#: documents/models.py:499
msgid "does not have owner"
msgstr ""
-#: documents/models.py:460
+#: documents/models.py:500
msgid "does not have owner in"
msgstr ""
-#: documents/models.py:461
+#: documents/models.py:501
msgid "has custom field value"
msgstr ""
-#: documents/models.py:462
+#: documents/models.py:502
msgid "is shared by me"
msgstr ""
-#: documents/models.py:472
+#: documents/models.py:512
msgid "rule type"
msgstr ""
-#: documents/models.py:474
+#: documents/models.py:514
msgid "value"
msgstr ""
-#: documents/models.py:477
+#: documents/models.py:517
msgid "filter rule"
msgstr ""
-#: documents/models.py:478
+#: documents/models.py:518
msgid "filter rules"
msgstr ""
-#: documents/models.py:589
+#: documents/models.py:629
msgid "Task ID"
msgstr ""
-#: documents/models.py:590
+#: documents/models.py:630
msgid "Celery ID for the Task that was run"
msgstr ""
-#: documents/models.py:595
+#: documents/models.py:635
msgid "Acknowledged"
msgstr ""
-#: documents/models.py:596
+#: documents/models.py:636
msgid "If the task is acknowledged via the frontend or API"
msgstr ""
-#: documents/models.py:602
+#: documents/models.py:642
msgid "Task Filename"
msgstr ""
-#: documents/models.py:603
+#: documents/models.py:643
msgid "Name of the file which the Task was run for"
msgstr ""
-#: documents/models.py:609
+#: documents/models.py:649
msgid "Task Name"
msgstr ""
-#: documents/models.py:610
+#: documents/models.py:650
msgid "Name of the Task which was run"
msgstr ""
-#: documents/models.py:617
+#: documents/models.py:657
msgid "Task State"
msgstr ""
-#: documents/models.py:618
+#: documents/models.py:658
msgid "Current state of the task being run"
msgstr ""
-#: documents/models.py:623
+#: documents/models.py:663
msgid "Created DateTime"
msgstr ""
-#: documents/models.py:624
+#: documents/models.py:664
msgid "Datetime field when the task result was created in UTC"
msgstr ""
-#: documents/models.py:629
+#: documents/models.py:669
msgid "Started DateTime"
msgstr ""
-#: documents/models.py:630
+#: documents/models.py:670
msgid "Datetime field when the task was started in UTC"
msgstr ""
-#: documents/models.py:635
+#: documents/models.py:675
msgid "Completed DateTime"
msgstr ""
-#: documents/models.py:636
+#: documents/models.py:676
msgid "Datetime field when the task was completed in UTC"
msgstr ""
-#: documents/models.py:641
+#: documents/models.py:681
msgid "Result Data"
msgstr ""
-#: documents/models.py:643
+#: documents/models.py:683
msgid "The data returned by the task"
msgstr ""
-#: documents/models.py:655
+#: documents/models.py:695
msgid "Note for the document"
msgstr ""
-#: documents/models.py:679
+#: documents/models.py:719
msgid "user"
msgstr ""
-#: documents/models.py:684
+#: documents/models.py:724
msgid "note"
msgstr ""
-#: documents/models.py:685
+#: documents/models.py:725
msgid "notes"
msgstr ""
-#: documents/models.py:693
+#: documents/models.py:733
msgid "Archive"
msgstr ""
-#: documents/models.py:694
+#: documents/models.py:734
msgid "Original"
msgstr ""
-#: documents/models.py:705
+#: documents/models.py:745
msgid "expiration"
msgstr ""
-#: documents/models.py:712
+#: documents/models.py:752
msgid "slug"
msgstr ""
-#: documents/models.py:744
+#: documents/models.py:784
msgid "share link"
msgstr ""
-#: documents/models.py:745
+#: documents/models.py:785
msgid "share links"
msgstr ""
-#: documents/models.py:757
+#: documents/models.py:797
msgid "String"
msgstr ""
-#: documents/models.py:758
+#: documents/models.py:798
msgid "URL"
msgstr ""
-#: documents/models.py:759
+#: documents/models.py:799
msgid "Date"
msgstr ""
-#: documents/models.py:760
+#: documents/models.py:800
msgid "Boolean"
msgstr ""
-#: documents/models.py:761
+#: documents/models.py:801
msgid "Integer"
msgstr ""
-#: documents/models.py:762
+#: documents/models.py:802
msgid "Float"
msgstr ""
-#: documents/models.py:763
+#: documents/models.py:803
msgid "Monetary"
msgstr ""
-#: documents/models.py:764
+#: documents/models.py:804
msgid "Document Link"
msgstr ""
-#: documents/models.py:776
+#: documents/models.py:816
msgid "data type"
msgstr ""
-#: documents/models.py:784
+#: documents/models.py:824
msgid "custom field"
msgstr ""
-#: documents/models.py:785
+#: documents/models.py:825
msgid "custom fields"
msgstr ""
-#: documents/models.py:847
+#: documents/models.py:887
msgid "custom field instance"
msgstr ""
-#: documents/models.py:848
+#: documents/models.py:888
msgid "custom field instances"
msgstr ""
-#: documents/models.py:910
+#: documents/models.py:950
msgid "Consumption Started"
msgstr ""
-#: documents/models.py:911
+#: documents/models.py:951
msgid "Document Added"
msgstr ""
-#: documents/models.py:912
+#: documents/models.py:952
msgid "Document Updated"
msgstr ""
-#: documents/models.py:915
+#: documents/models.py:955
msgid "Consume Folder"
msgstr ""
-#: documents/models.py:916
+#: documents/models.py:956
msgid "Api Upload"
msgstr ""
-#: documents/models.py:917
+#: documents/models.py:957
msgid "Mail Fetch"
msgstr ""
-#: documents/models.py:920
+#: documents/models.py:960
msgid "Workflow Trigger Type"
msgstr ""
-#: documents/models.py:932
+#: documents/models.py:972
msgid "filter path"
msgstr ""
-#: documents/models.py:937
+#: documents/models.py:977
msgid ""
"Only consume documents with a path that matches this if specified. Wildcards "
"specified as * are allowed. Case insensitive."
msgstr ""
-#: documents/models.py:944
+#: documents/models.py:984
msgid "filter filename"
msgstr ""
-#: documents/models.py:949 paperless_mail/models.py:148
+#: documents/models.py:989 paperless_mail/models.py:148
msgid ""
"Only consume documents which entirely match this filename if specified. "
"Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
msgstr ""
-#: documents/models.py:960
+#: documents/models.py:1000
msgid "filter documents from this mail rule"
msgstr ""
-#: documents/models.py:976
+#: documents/models.py:1016
msgid "has these tag(s)"
msgstr ""
-#: documents/models.py:984
+#: documents/models.py:1024
msgid "has this document type"
msgstr ""
-#: documents/models.py:992
+#: documents/models.py:1032
msgid "has this correspondent"
msgstr ""
-#: documents/models.py:996
+#: documents/models.py:1036
msgid "workflow trigger"
msgstr ""
-#: documents/models.py:997
+#: documents/models.py:1037
msgid "workflow triggers"
msgstr ""
-#: documents/models.py:1007
+#: documents/models.py:1047
msgid "Assignment"
msgstr ""
-#: documents/models.py:1011
+#: documents/models.py:1051
msgid "Removal"
msgstr ""
-#: documents/models.py:1015
+#: documents/models.py:1055
msgid "Workflow Action Type"
msgstr ""
-#: documents/models.py:1021
+#: documents/models.py:1061
msgid "assign title"
msgstr ""
-#: documents/models.py:1026
+#: documents/models.py:1066
msgid ""
"Assign a document title, can include some placeholders, see documentation."
msgstr ""
-#: documents/models.py:1035 paperless_mail/models.py:216
+#: documents/models.py:1075 paperless_mail/models.py:216
msgid "assign this tag"
msgstr ""
-#: documents/models.py:1044 paperless_mail/models.py:224
+#: documents/models.py:1084 paperless_mail/models.py:224
msgid "assign this document type"
msgstr ""
-#: documents/models.py:1053 paperless_mail/models.py:238
+#: documents/models.py:1093 paperless_mail/models.py:238
msgid "assign this correspondent"
msgstr ""
-#: documents/models.py:1062
+#: documents/models.py:1102
msgid "assign this storage path"
msgstr ""
-#: documents/models.py:1071
+#: documents/models.py:1111
msgid "assign this owner"
msgstr ""
-#: documents/models.py:1078
+#: documents/models.py:1118
msgid "grant view permissions to these users"
msgstr ""
-#: documents/models.py:1085
+#: documents/models.py:1125
msgid "grant view permissions to these groups"
msgstr ""
-#: documents/models.py:1092
+#: documents/models.py:1132
msgid "grant change permissions to these users"
msgstr ""
-#: documents/models.py:1099
+#: documents/models.py:1139
msgid "grant change permissions to these groups"
msgstr ""
-#: documents/models.py:1106
+#: documents/models.py:1146
msgid "assign these custom fields"
msgstr ""
-#: documents/models.py:1113
+#: documents/models.py:1153
msgid "remove these tag(s)"
msgstr ""
-#: documents/models.py:1118
+#: documents/models.py:1158
msgid "remove all tags"
msgstr ""
-#: documents/models.py:1125
+#: documents/models.py:1165
msgid "remove these document type(s)"
msgstr ""
-#: documents/models.py:1130
+#: documents/models.py:1170
msgid "remove all document types"
msgstr ""
-#: documents/models.py:1137
+#: documents/models.py:1177
msgid "remove these correspondent(s)"
msgstr ""
-#: documents/models.py:1142
+#: documents/models.py:1182
msgid "remove all correspondents"
msgstr ""
-#: documents/models.py:1149
+#: documents/models.py:1189
msgid "remove these storage path(s)"
msgstr ""
-#: documents/models.py:1154
+#: documents/models.py:1194
msgid "remove all storage paths"
msgstr ""
-#: documents/models.py:1161
+#: documents/models.py:1201
msgid "remove these owner(s)"
msgstr ""
-#: documents/models.py:1166
+#: documents/models.py:1206
msgid "remove all owners"
msgstr ""
-#: documents/models.py:1173
+#: documents/models.py:1213
msgid "remove view permissions for these users"
msgstr ""
-#: documents/models.py:1180
+#: documents/models.py:1220
msgid "remove view permissions for these groups"
msgstr ""
-#: documents/models.py:1187
+#: documents/models.py:1227
msgid "remove change permissions for these users"
msgstr ""
-#: documents/models.py:1194
+#: documents/models.py:1234
msgid "remove change permissions for these groups"
msgstr ""
-#: documents/models.py:1199
+#: documents/models.py:1239
msgid "remove all permissions"
msgstr ""
-#: documents/models.py:1206
+#: documents/models.py:1246
msgid "remove these custom fields"
msgstr ""
-#: documents/models.py:1211
+#: documents/models.py:1251
msgid "remove all custom fields"
msgstr ""
-#: documents/models.py:1215
+#: documents/models.py:1255
msgid "workflow action"
msgstr ""
-#: documents/models.py:1216
+#: documents/models.py:1256
msgid "workflow actions"
msgstr ""
-#: documents/models.py:1225 paperless_mail/models.py:95
+#: documents/models.py:1265 paperless_mail/models.py:95
msgid "order"
msgstr ""
-#: documents/models.py:1231
+#: documents/models.py:1271
msgid "triggers"
msgstr ""
-#: documents/models.py:1238
+#: documents/models.py:1278
msgid "actions"
msgstr ""
-#: documents/models.py:1241
+#: documents/models.py:1281
msgid "enabled"
msgstr ""
msgid "Invalid color."
msgstr ""
-#: documents/serialisers.py:1148
+#: documents/serialisers.py:1169
#, python-format
msgid "File type %(type)s not supported"
msgstr ""
-#: documents/serialisers.py:1257
+#: documents/serialisers.py:1278
msgid "Invalid variable detected."
msgstr ""