mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Feature: app branding (#5357)
This commit is contained in:
		| @@ -4,9 +4,9 @@ Paperless provides a wide range of customizations. Depending on how you | |||||||
| run paperless, these settings have to be defined in different places. | run paperless, these settings have to be defined in different places. | ||||||
|  |  | ||||||
| Certain configuration options may be set via the UI. This currently includes | Certain configuration options may be set via the UI. This currently includes | ||||||
| common [OCR](#ocr) related settings. If set, these will take preference over the | common [OCR](#ocr) related settings and some frontend settings. If set, these will take | ||||||
| settings via environment variables. If not set, the environment setting or applicable | preference over the settings via environment variables. If not set, the environment setting | ||||||
| default will be utilized instead. | or applicable default will be utilized instead. | ||||||
|  |  | ||||||
| - If you run paperless on docker, `paperless.conf` is not used. | - If you run paperless on docker, `paperless.conf` is not used. | ||||||
|   Rather, configure paperless by copying necessary options to |   Rather, configure paperless by copying necessary options to | ||||||
| @@ -1329,7 +1329,15 @@ started by the container. | |||||||
|  |  | ||||||
|     You can read more about this in the [advanced documentation](advanced_usage.md#celery-monitoring). |     You can read more about this in the [advanced documentation](advanced_usage.md#celery-monitoring). | ||||||
|  |  | ||||||
| ## Update Checking {#update-checking} | ## Frontend Settings | ||||||
|  |  | ||||||
|  | #### [`PAPERLESS_APP_TITLE=<bool>`](#PAPERLESS_APP_TITLE) {#PAPERLESS_APP_TITLE} | ||||||
|  |  | ||||||
|  | : If set, overrides the default name "Paperless-ngx" | ||||||
|  |  | ||||||
|  | #### [`PAPERLESS_APP_LOGO=<path>`](#PAPERLESS_APP_LOGO) {#PAPERLESS_APP_LOGO} | ||||||
|  |  | ||||||
|  | : Path to an image file in the /media/logo directory, must include 'logo', e.g. `/logo/Atari_logo.svg` | ||||||
|  |  | ||||||
| #### [`PAPERLESS_ENABLE_UPDATE_CHECK=<bool>`](#PAPERLESS_ENABLE_UPDATE_CHECK) {#PAPERLESS_ENABLE_UPDATE_CHECK} | #### [`PAPERLESS_ENABLE_UPDATE_CHECK=<bool>`](#PAPERLESS_ENABLE_UPDATE_CHECK) {#PAPERLESS_ENABLE_UPDATE_CHECK} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -439,7 +439,7 @@ | |||||||
|         <source>Discard</source> |         <source>Discard</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/admin/config/config.component.html</context> |           <context context-type="sourcefile">src/app/components/admin/config/config.component.html</context> | ||||||
|           <context context-type="linenumber">48</context> |           <context context-type="linenumber">49</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> |           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||||
| @@ -450,7 +450,7 @@ | |||||||
|         <source>Save</source> |         <source>Save</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/admin/config/config.component.html</context> |           <context context-type="sourcefile">src/app/components/admin/config/config.component.html</context> | ||||||
|           <context context-type="linenumber">51</context> |           <context context-type="linenumber">52</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context> |           <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context> | ||||||
| @@ -513,28 +513,42 @@ | |||||||
|         <source>Error retrieving config</source> |         <source>Error retrieving config</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context> |           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context> | ||||||
|           <context context-type="linenumber">79</context> |           <context context-type="linenumber">81</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1172622527269118932" datatype="html"> |       <trans-unit id="1172622527269118932" datatype="html"> | ||||||
|         <source>Invalid JSON</source> |         <source>Invalid JSON</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context> |           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context> | ||||||
|           <context context-type="linenumber">105</context> |           <context context-type="linenumber">107</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="5103146006962696736" datatype="html"> |       <trans-unit id="5103146006962696736" datatype="html"> | ||||||
|         <source>Configuration updated</source> |         <source>Configuration updated</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context> |           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context> | ||||||
|           <context context-type="linenumber">148</context> |           <context context-type="linenumber">151</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1664963291286452273" datatype="html"> |       <trans-unit id="1664963291286452273" datatype="html"> | ||||||
|         <source>An error occurred updating configuration</source> |         <source>An error occurred updating configuration</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context> |           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context> | ||||||
|           <context context-type="linenumber">153</context> |           <context context-type="linenumber">156</context> | ||||||
|  |         </context-group> | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="2653081282186526824" datatype="html"> | ||||||
|  |         <source>File successfully updated</source> | ||||||
|  |         <context-group purpose="location"> | ||||||
|  |           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context> | ||||||
|  |           <context context-type="linenumber">178</context> | ||||||
|  |         </context-group> | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="5902783625859504265" datatype="html"> | ||||||
|  |         <source>An error occurred uploading file</source> | ||||||
|  |         <context-group purpose="location"> | ||||||
|  |           <context context-type="sourcefile">src/app/components/admin/config/config.component.ts</context> | ||||||
|  |           <context context-type="linenumber">183</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="4804785061014590286" datatype="html"> |       <trans-unit id="4804785061014590286" datatype="html"> | ||||||
| @@ -545,11 +559,11 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">309</context> |           <context context-type="linenumber">319</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">314</context> |           <context context-type="linenumber">324</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8838884664569764142" datatype="html"> |       <trans-unit id="8838884664569764142" datatype="html"> | ||||||
| @@ -646,15 +660,15 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">59</context> |           <context context-type="linenumber">69</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">267</context> |           <context context-type="linenumber">277</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">271</context> |           <context context-type="linenumber">281</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1685061484835793745" datatype="html"> |       <trans-unit id="1685061484835793745" datatype="html"> | ||||||
| @@ -1123,7 +1137,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">116</context> |           <context context-type="linenumber">126</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1595668988802980095" datatype="html"> |       <trans-unit id="1595668988802980095" datatype="html"> | ||||||
| @@ -1517,7 +1531,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context> | ||||||
|           <context context-type="linenumber">117</context> |           <context context-type="linenumber">121</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="5260584511980773458" datatype="html"> |       <trans-unit id="5260584511980773458" datatype="html"> | ||||||
| @@ -1535,7 +1549,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">296</context> |           <context context-type="linenumber">306</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="103921551219467537" datatype="html"> |       <trans-unit id="103921551219467537" datatype="html"> | ||||||
| @@ -1722,11 +1736,11 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">285</context> |           <context context-type="linenumber">295</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">289</context> |           <context context-type="linenumber">299</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="4555457172864212828" datatype="html"> |       <trans-unit id="4555457172864212828" datatype="html"> | ||||||
| @@ -2035,66 +2049,65 @@ | |||||||
|           <context context-type="linenumber">180</context> |           <context context-type="linenumber">180</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="2173456130768795374" datatype="html"> |       <trans-unit id="7931334600001636863" datatype="html"> | ||||||
|         <source>Paperless-ngx</source> |         <source>by Paperless-ngx</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">15</context> |           <context context-type="linenumber">20</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <note priority="1" from="description">app title</note> |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="7100953725264790651" datatype="html"> |       <trans-unit id="7100953725264790651" datatype="html"> | ||||||
|         <source>Search documents</source> |         <source>Search documents</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">23</context> |           <context context-type="linenumber">33</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="2448391510242468907" datatype="html"> |       <trans-unit id="2448391510242468907" datatype="html"> | ||||||
|         <source>Logged in as <x id="INTERPOLATION" equiv-text="{{this.settingsService.displayName}}"/></source> |         <source>Logged in as <x id="INTERPOLATION" equiv-text="{{this.settingsService.displayName}}"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">47</context> |           <context context-type="linenumber">57</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="2127032578120864096" datatype="html"> |       <trans-unit id="2127032578120864096" datatype="html"> | ||||||
|         <source>My Profile</source> |         <source>My Profile</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">53</context> |           <context context-type="linenumber">63</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3797778920049399855" datatype="html"> |       <trans-unit id="3797778920049399855" datatype="html"> | ||||||
|         <source>Logout</source> |         <source>Logout</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">64</context> |           <context context-type="linenumber">74</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="4895326106573044490" datatype="html"> |       <trans-unit id="4895326106573044490" datatype="html"> | ||||||
|         <source>Documentation</source> |         <source>Documentation</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">71</context> |           <context context-type="linenumber">81</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">319</context> |           <context context-type="linenumber">329</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">324</context> |           <context context-type="linenumber">334</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6570363013146073520" datatype="html"> |       <trans-unit id="6570363013146073520" datatype="html"> | ||||||
|         <source>Dashboard</source> |         <source>Dashboard</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">96</context> |           <context context-type="linenumber">106</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">100</context> |           <context context-type="linenumber">110</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.html</context> |           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.html</context> | ||||||
| @@ -2105,11 +2118,11 @@ | |||||||
|         <source>Documents</source> |         <source>Documents</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">105</context> |           <context context-type="linenumber">115</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">109</context> |           <context context-type="linenumber">119</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> | ||||||
| @@ -2136,36 +2149,36 @@ | |||||||
|         <source>Open documents</source> |         <source>Open documents</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">150</context> |           <context context-type="linenumber">160</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="5687256342387781369" datatype="html"> |       <trans-unit id="5687256342387781369" datatype="html"> | ||||||
|         <source>Close all</source> |         <source>Close all</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">174</context> |           <context context-type="linenumber">184</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">178</context> |           <context context-type="linenumber">188</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3897348120591552265" datatype="html"> |       <trans-unit id="3897348120591552265" datatype="html"> | ||||||
|         <source>Manage</source> |         <source>Manage</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">186</context> |           <context context-type="linenumber">196</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="7437910965833684826" datatype="html"> |       <trans-unit id="7437910965833684826" datatype="html"> | ||||||
|         <source>Correspondents</source> |         <source>Correspondents</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">192</context> |           <context context-type="linenumber">202</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">196</context> |           <context context-type="linenumber">206</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context> |           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context> | ||||||
| @@ -2176,11 +2189,11 @@ | |||||||
|         <source>Tags</source> |         <source>Tags</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">201</context> |           <context context-type="linenumber">211</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">206</context> |           <context context-type="linenumber">216</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/common/input/tags/tags.component.ts</context> |           <context context-type="sourcefile">src/app/components/common/input/tags/tags.component.ts</context> | ||||||
| @@ -2207,11 +2220,11 @@ | |||||||
|         <source>Document Types</source> |         <source>Document Types</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">212</context> |           <context context-type="linenumber">222</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">216</context> |           <context context-type="linenumber">226</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context> |           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context> | ||||||
| @@ -2222,11 +2235,11 @@ | |||||||
|         <source>Storage Paths</source> |         <source>Storage Paths</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">221</context> |           <context context-type="linenumber">231</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">225</context> |           <context context-type="linenumber">235</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context> |           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context> | ||||||
| @@ -2237,11 +2250,11 @@ | |||||||
|         <source>Custom Fields</source> |         <source>Custom Fields</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">230</context> |           <context context-type="linenumber">240</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">234</context> |           <context context-type="linenumber">244</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.html</context> |           <context context-type="sourcefile">src/app/components/common/custom-fields-dropdown/custom-fields-dropdown.component.html</context> | ||||||
| @@ -2256,11 +2269,11 @@ | |||||||
|         <source>Workflows</source> |         <source>Workflows</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">241</context> |           <context context-type="linenumber">251</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">245</context> |           <context context-type="linenumber">255</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context> |           <context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context> | ||||||
| @@ -2271,99 +2284,99 @@ | |||||||
|         <source>Mail</source> |         <source>Mail</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">250</context> |           <context context-type="linenumber">260</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">255</context> |           <context context-type="linenumber">265</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="7844706011418789951" datatype="html"> |       <trans-unit id="7844706011418789951" datatype="html"> | ||||||
|         <source>Administration</source> |         <source>Administration</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">261</context> |           <context context-type="linenumber">271</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3008420115644088420" datatype="html"> |       <trans-unit id="3008420115644088420" datatype="html"> | ||||||
|         <source>Configuration</source> |         <source>Configuration</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">276</context> |           <context context-type="linenumber">286</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">280</context> |           <context context-type="linenumber">290</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6626289114556551491" datatype="html"> |       <trans-unit id="6626289114556551491" datatype="html"> | ||||||
|         <source>File Tasks<x id="START_BLOCK_IF" equiv-text="@if (tasksService.failedFileTasks.length > 0) {"/><x id="START_TAG_SPAN_1" ctype="x-span_1" equiv-text="<span>"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="badge bg-danger ms-2">"/><x id="INTERPOLATION" equiv-text="{{tasksService.failedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></source> |         <source>File Tasks<x id="START_BLOCK_IF" equiv-text="@if (tasksService.failedFileTasks.length > 0) {"/><x id="START_TAG_SPAN_1" ctype="x-span_1" equiv-text="<span>"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="badge bg-danger ms-2">"/><x id="INTERPOLATION" equiv-text="{{tasksService.failedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">303,305</context> |           <context context-type="linenumber">313,315</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1534029177398918729" datatype="html"> |       <trans-unit id="1534029177398918729" datatype="html"> | ||||||
|         <source>GitHub</source> |         <source>GitHub</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">331</context> |           <context context-type="linenumber">341</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="4112664765954374539" datatype="html"> |       <trans-unit id="4112664765954374539" datatype="html"> | ||||||
|         <source>is available.</source> |         <source>is available.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">340,341</context> |           <context context-type="linenumber">350,351</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1175891574282637937" datatype="html"> |       <trans-unit id="1175891574282637937" datatype="html"> | ||||||
|         <source>Click to view.</source> |         <source>Click to view.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">341</context> |           <context context-type="linenumber">351</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="9811291095862612" datatype="html"> |       <trans-unit id="9811291095862612" datatype="html"> | ||||||
|         <source>Paperless-ngx can automatically check for updates</source> |         <source>Paperless-ngx can automatically check for updates</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">345</context> |           <context context-type="linenumber">355</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="894819944961861800" datatype="html"> |       <trans-unit id="894819944961861800" datatype="html"> | ||||||
|         <source> How does this work? </source> |         <source> How does this work? </source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">352,354</context> |           <context context-type="linenumber">362,364</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="509090351011426949" datatype="html"> |       <trans-unit id="509090351011426949" datatype="html"> | ||||||
|         <source>Update available</source> |         <source>Update available</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||||
|           <context context-type="linenumber">368</context> |           <context context-type="linenumber">378</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1542489069631984294" datatype="html"> |       <trans-unit id="1542489069631984294" datatype="html"> | ||||||
|         <source>Sidebar views updated</source> |         <source>Sidebar views updated</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context> | ||||||
|           <context context-type="linenumber">259</context> |           <context context-type="linenumber">263</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3547923076537026828" datatype="html"> |       <trans-unit id="3547923076537026828" datatype="html"> | ||||||
|         <source>Error updating sidebar views</source> |         <source>Error updating sidebar views</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context> | ||||||
|           <context context-type="linenumber">262</context> |           <context context-type="linenumber">266</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="2526035785704676448" datatype="html"> |       <trans-unit id="2526035785704676448" datatype="html"> | ||||||
|         <source>An error occurred while saving update checking settings.</source> |         <source>An error occurred while saving update checking settings.</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context> |           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context> | ||||||
|           <context context-type="linenumber">283</context> |           <context context-type="linenumber">287</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8700121026680200191" datatype="html"> |       <trans-unit id="8700121026680200191" datatype="html"> | ||||||
| @@ -3696,6 +3709,14 @@ | |||||||
|           <context context-type="sourcefile">src/app/components/common/input/document-link/document-link.component.html</context> |           <context context-type="sourcefile">src/app/components/common/input/document-link/document-link.component.html</context> | ||||||
|           <context context-type="linenumber">11</context> |           <context context-type="linenumber">11</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|  |         <context-group purpose="location"> | ||||||
|  |           <context context-type="sourcefile">src/app/components/common/input/file/file.component.html</context> | ||||||
|  |           <context context-type="linenumber">11</context> | ||||||
|  |         </context-group> | ||||||
|  |         <context-group purpose="location"> | ||||||
|  |           <context context-type="sourcefile">src/app/components/common/input/file/file.component.html</context> | ||||||
|  |           <context context-type="linenumber">25</context> | ||||||
|  |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/common/input/number/number.component.html</context> |           <context context-type="sourcefile">src/app/components/common/input/number/number.component.html</context> | ||||||
|           <context context-type="linenumber">11</context> |           <context context-type="linenumber">11</context> | ||||||
| @@ -3757,6 +3778,13 @@ | |||||||
|           <context context-type="linenumber">44</context> |           <context context-type="linenumber">44</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|  |       <trans-unit id="6932865105766151309" datatype="html"> | ||||||
|  |         <source>Upload</source> | ||||||
|  |         <context-group purpose="location"> | ||||||
|  |           <context context-type="sourcefile">src/app/components/common/input/file/file.component.html</context> | ||||||
|  |           <context context-type="linenumber">17</context> | ||||||
|  |         </context-group> | ||||||
|  |       </trans-unit> | ||||||
|       <trans-unit id="5554528553553249088" datatype="html"> |       <trans-unit id="5554528553553249088" datatype="html"> | ||||||
|         <source>Show password</source> |         <source>Show password</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
| @@ -4188,32 +4216,32 @@ | |||||||
|           <context context-type="linenumber">44</context> |           <context context-type="linenumber">44</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1865646076514070962" datatype="html"> |       <trans-unit id="6581372518205328477" datatype="html"> | ||||||
|         <source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to Paperless-ngx</source> |         <source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to <x id="PH_1" equiv-text="appTitle"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> |           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> | ||||||
|           <context context-type="linenumber">38</context> |           <context context-type="linenumber">41</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="5334686081082652461" datatype="html"> |       <trans-unit id="2901300640157872718" datatype="html"> | ||||||
|         <source>Welcome to Paperless-ngx</source> |         <source>Welcome to <x id="PH" equiv-text="appTitle"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> |           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> | ||||||
|           <context context-type="linenumber">40</context> |           <context context-type="linenumber">43</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1325877348738783391" datatype="html"> |       <trans-unit id="1325877348738783391" datatype="html"> | ||||||
|         <source>Dashboard updated</source> |         <source>Dashboard updated</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> |           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> | ||||||
|           <context context-type="linenumber">71</context> |           <context context-type="linenumber">74</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3214475953924351473" datatype="html"> |       <trans-unit id="3214475953924351473" datatype="html"> | ||||||
|         <source>Error updating dashboard</source> |         <source>Error updating dashboard</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> |           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> | ||||||
|           <context context-type="linenumber">74</context> |           <context context-type="linenumber">77</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="2946624699882754313" datatype="html"> |       <trans-unit id="2946624699882754313" datatype="html"> | ||||||
| @@ -6443,102 +6471,123 @@ | |||||||
|           <context context-type="linenumber">46</context> |           <context context-type="linenumber">46</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|  |       <trans-unit id="432834967329800065" datatype="html"> | ||||||
|  |         <source>General Settings</source> | ||||||
|  |         <context-group purpose="location"> | ||||||
|  |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|  |           <context context-type="linenumber">50</context> | ||||||
|  |         </context-group> | ||||||
|  |       </trans-unit> | ||||||
|       <trans-unit id="2762851116637676072" datatype="html"> |       <trans-unit id="2762851116637676072" datatype="html"> | ||||||
|         <source>OCR Settings</source> |         <source>OCR Settings</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">49</context> |           <context context-type="linenumber">51</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1313137480169642057" datatype="html"> |       <trans-unit id="1313137480169642057" datatype="html"> | ||||||
|         <source>Output Type</source> |         <source>Output Type</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">73</context> |           <context context-type="linenumber">75</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="2826581353496868063" datatype="html"> |       <trans-unit id="2826581353496868063" datatype="html"> | ||||||
|         <source>Language</source> |         <source>Language</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">81</context> |           <context context-type="linenumber">83</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3817498941817715969" datatype="html"> |       <trans-unit id="3817498941817715969" datatype="html"> | ||||||
|         <source>Pages</source> |         <source>Pages</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">88</context> |           <context context-type="linenumber">90</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1713271461473302108" datatype="html"> |       <trans-unit id="1713271461473302108" datatype="html"> | ||||||
|         <source>Mode</source> |         <source>Mode</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">95</context> |           <context context-type="linenumber">97</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6114528299376689399" datatype="html"> |       <trans-unit id="6114528299376689399" datatype="html"> | ||||||
|         <source>Skip Archive File</source> |         <source>Skip Archive File</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">103</context> |           <context context-type="linenumber">105</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1115402553541327390" datatype="html"> |       <trans-unit id="1115402553541327390" datatype="html"> | ||||||
|         <source>Image DPI</source> |         <source>Image DPI</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">111</context> |           <context context-type="linenumber">113</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6352596107300820129" datatype="html"> |       <trans-unit id="6352596107300820129" datatype="html"> | ||||||
|         <source>Clean</source> |         <source>Clean</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">118</context> |           <context context-type="linenumber">120</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="725308589819024010" datatype="html"> |       <trans-unit id="725308589819024010" datatype="html"> | ||||||
|         <source>Deskew</source> |         <source>Deskew</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">126</context> |           <context context-type="linenumber">128</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6256076128297775802" datatype="html"> |       <trans-unit id="6256076128297775802" datatype="html"> | ||||||
|         <source>Rotate Pages</source> |         <source>Rotate Pages</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">133</context> |           <context context-type="linenumber">135</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8527188778859256947" datatype="html"> |       <trans-unit id="8527188778859256947" datatype="html"> | ||||||
|         <source>Rotate Pages Threshold</source> |         <source>Rotate Pages Threshold</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">140</context> |           <context context-type="linenumber">142</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3762131309176747817" datatype="html"> |       <trans-unit id="3762131309176747817" datatype="html"> | ||||||
|         <source>Max Image Pixels</source> |         <source>Max Image Pixels</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">147</context> |           <context context-type="linenumber">149</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="7846583355792281769" datatype="html"> |       <trans-unit id="7846583355792281769" datatype="html"> | ||||||
|         <source>Color Conversion Strategy</source> |         <source>Color Conversion Strategy</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">154</context> |           <context context-type="linenumber">156</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="4696480417479207939" datatype="html"> |       <trans-unit id="4696480417479207939" datatype="html"> | ||||||
|         <source>OCR Arguments</source> |         <source>OCR Arguments</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|           <context context-type="linenumber">162</context> |           <context context-type="linenumber">164</context> | ||||||
|  |         </context-group> | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="7106327322456204362" datatype="html"> | ||||||
|  |         <source>Application Logo</source> | ||||||
|  |         <context-group purpose="location"> | ||||||
|  |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|  |           <context context-type="linenumber">171</context> | ||||||
|  |         </context-group> | ||||||
|  |       </trans-unit> | ||||||
|  |       <trans-unit id="2684743776608068095" datatype="html"> | ||||||
|  |         <source>Application Title</source> | ||||||
|  |         <context-group purpose="location"> | ||||||
|  |           <context context-type="sourcefile">src/app/data/paperless-config.ts</context> | ||||||
|  |           <context context-type="linenumber">178</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="5948496158474272829" datatype="html"> |       <trans-unit id="5948496158474272829" datatype="html"> | ||||||
|   | |||||||
| @@ -110,6 +110,7 @@ import { DocumentLinkComponent } from './components/common/input/document-link/d | |||||||
| import { PreviewPopupComponent } from './components/common/preview-popup/preview-popup.component' | import { PreviewPopupComponent } from './components/common/preview-popup/preview-popup.component' | ||||||
| import { SwitchComponent } from './components/common/input/switch/switch.component' | import { SwitchComponent } from './components/common/input/switch/switch.component' | ||||||
| import { ConfigComponent } from './components/admin/config/config.component' | import { ConfigComponent } from './components/admin/config/config.component' | ||||||
|  | import { FileComponent } from './components/common/input/file/file.component' | ||||||
|  |  | ||||||
| import localeAf from '@angular/common/locales/af' | import localeAf from '@angular/common/locales/af' | ||||||
| import localeAr from '@angular/common/locales/ar' | import localeAr from '@angular/common/locales/ar' | ||||||
| @@ -267,6 +268,7 @@ function initializeApp(settings: SettingsService) { | |||||||
|     PreviewPopupComponent, |     PreviewPopupComponent, | ||||||
|     SwitchComponent, |     SwitchComponent, | ||||||
|     ConfigComponent, |     ConfigComponent, | ||||||
|  |     FileComponent, | ||||||
|   ], |   ], | ||||||
|   imports: [ |   imports: [ | ||||||
|     BrowserModule, |     BrowserModule, | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ | |||||||
|                                                     @case (ConfigOptionType.Boolean) { <pngx-input-switch [formControlName]="option.key" [error]="errors[option.key]" [showUnsetNote]="true" [horizontal]="true" title="Enable" i18n-title></pngx-input-switch> } |                                                     @case (ConfigOptionType.Boolean) { <pngx-input-switch [formControlName]="option.key" [error]="errors[option.key]" [showUnsetNote]="true" [horizontal]="true" title="Enable" i18n-title></pngx-input-switch> } | ||||||
|                                                     @case (ConfigOptionType.String) { <pngx-input-text [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-text> } |                                                     @case (ConfigOptionType.String) { <pngx-input-text [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-text> } | ||||||
|                                                     @case (ConfigOptionType.JSON) { <pngx-input-text [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-text> } |                                                     @case (ConfigOptionType.JSON) { <pngx-input-text [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-text> } | ||||||
|  |                                                     @case (ConfigOptionType.File) { <pngx-input-file [formControlName]="option.key" (upload)="uploadFile($event, option.key)" [error]="errors[option.key]"></pngx-input-file> } | ||||||
|                                                 } |                                                 } | ||||||
|                                             </div> |                                             </div> | ||||||
|                                         </div> |                                         </div> | ||||||
|   | |||||||
| @@ -15,12 +15,15 @@ import { SwitchComponent } from '../../common/input/switch/switch.component' | |||||||
| import { FormsModule, ReactiveFormsModule } from '@angular/forms' | import { FormsModule, ReactiveFormsModule } from '@angular/forms' | ||||||
| import { PageHeaderComponent } from '../../common/page-header/page-header.component' | import { PageHeaderComponent } from '../../common/page-header/page-header.component' | ||||||
| import { SelectComponent } from '../../common/input/select/select.component' | import { SelectComponent } from '../../common/input/select/select.component' | ||||||
|  | import { FileComponent } from '../../common/input/file/file.component' | ||||||
|  | import { SettingsService } from 'src/app/services/settings.service' | ||||||
|  |  | ||||||
| describe('ConfigComponent', () => { | describe('ConfigComponent', () => { | ||||||
|   let component: ConfigComponent |   let component: ConfigComponent | ||||||
|   let fixture: ComponentFixture<ConfigComponent> |   let fixture: ComponentFixture<ConfigComponent> | ||||||
|   let configService: ConfigService |   let configService: ConfigService | ||||||
|   let toastService: ToastService |   let toastService: ToastService | ||||||
|  |   let settingService: SettingsService | ||||||
|  |  | ||||||
|   beforeEach(async () => { |   beforeEach(async () => { | ||||||
|     await TestBed.configureTestingModule({ |     await TestBed.configureTestingModule({ | ||||||
| @@ -30,6 +33,7 @@ describe('ConfigComponent', () => { | |||||||
|         SelectComponent, |         SelectComponent, | ||||||
|         NumberComponent, |         NumberComponent, | ||||||
|         SwitchComponent, |         SwitchComponent, | ||||||
|  |         FileComponent, | ||||||
|         PageHeaderComponent, |         PageHeaderComponent, | ||||||
|       ], |       ], | ||||||
|       imports: [ |       imports: [ | ||||||
| @@ -44,6 +48,7 @@ describe('ConfigComponent', () => { | |||||||
|  |  | ||||||
|     configService = TestBed.inject(ConfigService) |     configService = TestBed.inject(ConfigService) | ||||||
|     toastService = TestBed.inject(ToastService) |     toastService = TestBed.inject(ToastService) | ||||||
|  |     settingService = TestBed.inject(SettingsService) | ||||||
|     fixture = TestBed.createComponent(ConfigComponent) |     fixture = TestBed.createComponent(ConfigComponent) | ||||||
|     component = fixture.componentInstance |     component = fixture.componentInstance | ||||||
|     fixture.detectChanges() |     fixture.detectChanges() | ||||||
| @@ -100,4 +105,39 @@ describe('ConfigComponent', () => { | |||||||
|     component.configForm.patchValue({ user_args: '{ "foo": "bar" }' }) |     component.configForm.patchValue({ user_args: '{ "foo": "bar" }' }) | ||||||
|     expect(component.errors).toEqual({ user_args: null }) |     expect(component.errors).toEqual({ user_args: null }) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|  |   it('should upload file, show error if necessary', () => { | ||||||
|  |     const uploadSpy = jest.spyOn(configService, 'uploadFile') | ||||||
|  |     const errorSpy = jest.spyOn(toastService, 'showError') | ||||||
|  |     uploadSpy.mockReturnValueOnce( | ||||||
|  |       throwError(() => new Error('Error uploading file')) | ||||||
|  |     ) | ||||||
|  |     component.uploadFile(new File([], 'test.png'), 'app_logo') | ||||||
|  |     expect(uploadSpy).toHaveBeenCalled() | ||||||
|  |     expect(errorSpy).toHaveBeenCalled() | ||||||
|  |     uploadSpy.mockReturnValueOnce( | ||||||
|  |       of({ app_logo: 'https://example.com/logo/test.png' } as any) | ||||||
|  |     ) | ||||||
|  |     component.uploadFile(new File([], 'test.png'), 'app_logo') | ||||||
|  |     expect(component.initialConfig).toEqual({ | ||||||
|  |       app_logo: 'https://example.com/logo/test.png', | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   it('should refresh ui settings after save or upload', () => { | ||||||
|  |     const saveSpy = jest.spyOn(configService, 'saveConfig') | ||||||
|  |     const initSpy = jest.spyOn(settingService, 'initializeSettings') | ||||||
|  |     saveSpy.mockReturnValueOnce( | ||||||
|  |       of({ output_type: OutputTypeConfig.PDF_A } as any) | ||||||
|  |     ) | ||||||
|  |     component.saveConfig() | ||||||
|  |     expect(initSpy).toHaveBeenCalled() | ||||||
|  |  | ||||||
|  |     const uploadSpy = jest.spyOn(configService, 'uploadFile') | ||||||
|  |     uploadSpy.mockReturnValueOnce( | ||||||
|  |       of({ app_logo: 'https://example.com/logo/test.png' } as any) | ||||||
|  |     ) | ||||||
|  |     component.uploadFile(new File([], 'test.png'), 'app_logo') | ||||||
|  |     expect(initSpy).toHaveBeenCalled() | ||||||
|  |   }) | ||||||
| }) | }) | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ import { ConfigService } from 'src/app/services/config.service' | |||||||
| import { ToastService } from 'src/app/services/toast.service' | import { ToastService } from 'src/app/services/toast.service' | ||||||
| import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component' | import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component' | ||||||
| import { DirtyComponent, dirtyCheck } from '@ngneat/dirty-check-forms' | import { DirtyComponent, dirtyCheck } from '@ngneat/dirty-check-forms' | ||||||
|  | import { SettingsService } from 'src/app/services/settings.service' | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'pngx-config', |   selector: 'pngx-config', | ||||||
| @@ -55,7 +56,8 @@ export class ConfigComponent | |||||||
|  |  | ||||||
|   constructor( |   constructor( | ||||||
|     private configService: ConfigService, |     private configService: ConfigService, | ||||||
|     private toastService: ToastService |     private toastService: ToastService, | ||||||
|  |     private settingsService: SettingsService | ||||||
|   ) { |   ) { | ||||||
|     super() |     super() | ||||||
|     this.configForm.addControl('id', new FormControl()) |     this.configForm.addControl('id', new FormControl()) | ||||||
| @@ -145,6 +147,7 @@ export class ConfigComponent | |||||||
|           this.loading = false |           this.loading = false | ||||||
|           this.initialize(config) |           this.initialize(config) | ||||||
|           this.store.next(config) |           this.store.next(config) | ||||||
|  |           this.settingsService.initializeSettings().subscribe() | ||||||
|           this.toastService.showInfo($localize`Configuration updated`) |           this.toastService.showInfo($localize`Configuration updated`) | ||||||
|         }, |         }, | ||||||
|         error: (e) => { |         error: (e) => { | ||||||
| @@ -160,4 +163,27 @@ export class ConfigComponent | |||||||
|   public discardChanges() { |   public discardChanges() { | ||||||
|     this.configForm.reset(this.initialConfig) |     this.configForm.reset(this.initialConfig) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public uploadFile(file: File, key: string) { | ||||||
|  |     this.loading = true | ||||||
|  |     this.configService | ||||||
|  |       .uploadFile(file, this.configForm.value['id'], key) | ||||||
|  |       .pipe(takeUntil(this.unsubscribeNotifier), first()) | ||||||
|  |       .subscribe({ | ||||||
|  |         next: (config) => { | ||||||
|  |           this.loading = false | ||||||
|  |           this.initialize(config) | ||||||
|  |           this.store.next(config) | ||||||
|  |           this.settingsService.initializeSettings().subscribe() | ||||||
|  |           this.toastService.showInfo($localize`File successfully updated`) | ||||||
|  |         }, | ||||||
|  |         error: (e) => { | ||||||
|  |           this.loading = false | ||||||
|  |           this.toastService.showError( | ||||||
|  |             $localize`An error occurred uploading file`, | ||||||
|  |             e | ||||||
|  |           ) | ||||||
|  |         }, | ||||||
|  |       }) | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,15 +4,25 @@ | |||||||
|     (click)="isMenuCollapsed = !isMenuCollapsed"> |     (click)="isMenuCollapsed = !isMenuCollapsed"> | ||||||
|     <span class="navbar-toggler-icon"></span> |     <span class="navbar-toggler-icon"></span> | ||||||
|   </button> |   </button> | ||||||
|   <a class="navbar-brand col-auto col-md-3 col-lg-2 me-0 px-3 py-3 order-sm-0" |   <a class="navbar-brand d-flex col-auto col-md-3 col-lg-2 me-0 px-3 py-3 order-sm-0" | ||||||
|     [ngClass]="slimSidebarEnabled ? 'slim' : 'col-auto col-md-3 col-lg-2'" routerLink="/dashboard" |     [ngClass]="{ 'slim': slimSidebarEnabled, 'd-flex col-auto col-md-3 col-lg-2' : !slimSidebarEnabled, 'py-3' : !customAppTitle?.length || slimSidebarEnabled, 'py-2': customAppTitle?.length }" | ||||||
|  |     routerLink="/dashboard" | ||||||
|     tourAnchor="tour.intro"> |     tourAnchor="tour.intro"> | ||||||
|     <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1em" class="me-2" fill="currentColor"> |     <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1em" class="me-2" fill="currentColor"> | ||||||
|       <path |       <path | ||||||
|         d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z" |         d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z" | ||||||
|         transform="translate(0 0)" /> |         transform="translate(0 0)" /> | ||||||
|     </svg> |     </svg> | ||||||
|     <span class="ms-2" [class.visually-hidden]="slimSidebarEnabled" i18n="app title">Paperless-ngx</span> |     <div class="ms-2 d-inline-block" [class.visually-hidden]="slimSidebarEnabled"> | ||||||
|  |       @if (customAppTitle?.length) { | ||||||
|  |         <div class="d-flex flex-column align-items-start"> | ||||||
|  |           <span class="title">{{customAppTitle}}</span> | ||||||
|  |           <span class="byline text-uppercase font-monospace" i18n>by Paperless-ngx</span> | ||||||
|  |         </div> | ||||||
|  |       } @else { | ||||||
|  |         Paperless-ngx | ||||||
|  |       } | ||||||
|  |     </div> | ||||||
|   </a> |   </a> | ||||||
|   <div class="search-form-container flex-grow-1 py-2 pb-3 pb-sm-2 px-3 ps-md-4 me-sm-auto order-3 order-sm-1" |   <div class="search-form-container flex-grow-1 py-2 pb-3 pb-sm-2 px-3 ps-md-4 me-sm-auto order-3 order-sm-1" | ||||||
|     *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }"> |     *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }"> | ||||||
|   | |||||||
| @@ -217,9 +217,16 @@ main { | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| .navbar-brand { | .navbar-brand { | ||||||
|   padding-top: 0.75rem; |  | ||||||
|   padding-bottom: 0.75rem; |  | ||||||
|   font-size: 1rem; |   font-size: 1rem; | ||||||
|  |  | ||||||
|  |   .flex-column { | ||||||
|  |     padding: 0.15rem 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .byline { | ||||||
|  |     font-size: 0.5rem; | ||||||
|  |     letter-spacing: 0.1rem; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @media screen and (min-width: 768px) { | @media screen and (min-width: 768px) { | ||||||
|   | |||||||
| @@ -102,6 +102,10 @@ export class AppFrameComponent | |||||||
|     }, 200) // slightly longer than css animation for slim sidebar |     }, 200) // slightly longer than css animation for slim sidebar | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   get customAppTitle(): string { | ||||||
|  |     return this.settingsService.get(SETTINGS_KEYS.APP_TITLE) | ||||||
|  |   } | ||||||
|  |  | ||||||
|   get slimSidebarEnabled(): boolean { |   get slimSidebarEnabled(): boolean { | ||||||
|     return this.settingsService.get(SETTINGS_KEYS.SLIM_SIDEBAR) |     return this.settingsService.get(SETTINGS_KEYS.SLIM_SIDEBAR) | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -0,0 +1,37 @@ | |||||||
|  | <div class="mb-3" [class.pb-3]="error"> | ||||||
|  |     <div class="row"> | ||||||
|  |       <div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal"> | ||||||
|  |         @if (title) { | ||||||
|  |           <label class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label> | ||||||
|  |         } | ||||||
|  |         @if (removable) { | ||||||
|  |           <button type="button" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)"> | ||||||
|  |             <svg class="sidebaricon" fill="currentColor"> | ||||||
|  |               <use xlink:href="assets/bootstrap-icons.svg#x"/> | ||||||
|  |               </svg> <ng-container i18n>Remove</ng-container> | ||||||
|  |             </button> | ||||||
|  |           } | ||||||
|  |         </div> | ||||||
|  |         <div class="input-group" [class.col-md-9]="horizontal" [class.is-invalid]="error"> | ||||||
|  |           <input #fileInput type="file" class="form-control" [id]="inputId" (change)="onFile($event)" [disabled]="disabled"> | ||||||
|  |           <button class="btn btn-outline-primary py-0" type="button" (click)="uploadClicked()" [disabled]="disabled || !file" i18n>Upload</button> | ||||||
|  |         </div> | ||||||
|  |         @if (filename) { | ||||||
|  |             <div class="form-text d-flex align-items-center"> | ||||||
|  |               <span class="text-muted">{{filename}}</span> | ||||||
|  |               <button type="button" class="btn btn-link btn-sm text-danger ms-2" (click)="clear()"> | ||||||
|  |                 <svg class="sidebaricon" fill="currentColor"> | ||||||
|  |                   <use xlink:href="assets/bootstrap-icons.svg#x"/> | ||||||
|  |                 </svg><small class="ms-1" i18n>Remove</small> | ||||||
|  |               </button> | ||||||
|  |             </div> | ||||||
|  |         } | ||||||
|  |         <input #inputField type="hidden" class="form-control small" [(ngModel)]="value" [disabled]="true"> | ||||||
|  |         @if (hint) { | ||||||
|  |             <small class="form-text text-muted" [innerHTML]="hint | safeHtml"></small> | ||||||
|  |         } | ||||||
|  |         <div class="invalid-feedback position-absolute top-100"> | ||||||
|  |             {{error}} | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||||
|  |  | ||||||
|  | import { FileComponent } from './file.component' | ||||||
|  | import { HttpClientTestingModule } from '@angular/common/http/testing' | ||||||
|  | import { FormsModule, ReactiveFormsModule } from '@angular/forms' | ||||||
|  |  | ||||||
|  | describe('FileComponent', () => { | ||||||
|  |   let component: FileComponent | ||||||
|  |   let fixture: ComponentFixture<FileComponent> | ||||||
|  |  | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     await TestBed.configureTestingModule({ | ||||||
|  |       declarations: [FileComponent], | ||||||
|  |       imports: [FormsModule, ReactiveFormsModule, HttpClientTestingModule], | ||||||
|  |     }).compileComponents() | ||||||
|  |  | ||||||
|  |     fixture = TestBed.createComponent(FileComponent) | ||||||
|  |     component = fixture.componentInstance | ||||||
|  |     fixture.detectChanges() | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   it('should update file on change', () => { | ||||||
|  |     const event = { target: { files: [new File([], 'test.png')] } } | ||||||
|  |     component.onFile(event as any) | ||||||
|  |     expect(component.file.name).toEqual('test.png') | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   it('should get filename', () => { | ||||||
|  |     component.value = 'https://example.com:8000/logo/filename.svg' | ||||||
|  |     expect(component.filename).toEqual('filename.svg') | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   it('should fire upload event', () => { | ||||||
|  |     let firedFile | ||||||
|  |     component.file = new File([], 'test.png') | ||||||
|  |     component.upload.subscribe((file) => (firedFile = file)) | ||||||
|  |     component.uploadClicked() | ||||||
|  |     expect(firedFile.name).toEqual('test.png') | ||||||
|  |     expect(component.file).toBeUndefined() | ||||||
|  |   }) | ||||||
|  | }) | ||||||
| @@ -0,0 +1,53 @@ | |||||||
|  | import { | ||||||
|  |   Component, | ||||||
|  |   ElementRef, | ||||||
|  |   EventEmitter, | ||||||
|  |   Output, | ||||||
|  |   ViewChild, | ||||||
|  |   forwardRef, | ||||||
|  | } from '@angular/core' | ||||||
|  | import { NG_VALUE_ACCESSOR } from '@angular/forms' | ||||||
|  | import { AbstractInputComponent } from '../abstract-input' | ||||||
|  |  | ||||||
|  | @Component({ | ||||||
|  |   providers: [ | ||||||
|  |     { | ||||||
|  |       provide: NG_VALUE_ACCESSOR, | ||||||
|  |       useExisting: forwardRef(() => FileComponent), | ||||||
|  |       multi: true, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |   selector: 'pngx-input-file', | ||||||
|  |   templateUrl: './file.component.html', | ||||||
|  |   styleUrl: './file.component.scss', | ||||||
|  | }) | ||||||
|  | export class FileComponent extends AbstractInputComponent<string> { | ||||||
|  |   @Output() | ||||||
|  |   upload = new EventEmitter<File>() | ||||||
|  |  | ||||||
|  |   public file: File | ||||||
|  |  | ||||||
|  |   @ViewChild('fileInput') fileInput: ElementRef | ||||||
|  |  | ||||||
|  |   get filename(): string { | ||||||
|  |     return this.value | ||||||
|  |       ? this.value.substring(this.value.lastIndexOf('/') + 1) | ||||||
|  |       : null | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   onFile(event: Event) { | ||||||
|  |     this.file = (event.target as HTMLInputElement).files[0] | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uploadClicked() { | ||||||
|  |     this.upload.emit(this.file) | ||||||
|  |     this.clear() | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   clear() { | ||||||
|  |     this.file = undefined | ||||||
|  |     this.fileInput.nativeElement.value = null | ||||||
|  |     this.writeValue(null) | ||||||
|  |     this.onChange(null) | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,4 +1,7 @@ | |||||||
| <svg [class]="getClasses()" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2897.4 896.6" [attr.style]="'height:'+height"> | @if (customLogo) { | ||||||
|  |     <img src="{{customLogo}}" height="100%" width="100%" [attr.style]="'height:'+height" /> | ||||||
|  | } @else { | ||||||
|  |     <svg [class]="getClasses()" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2897.4 896.6" [attr.style]="'height:'+height"> | ||||||
|         <path class="leaf" d="M140,713.7c-3.4-16.4-10.3-49.1-11.2-49.1c-145.7-87.1-128.4-238-80.2-324.2C59,449,251.2,524,139.1,656.8 c-0.9,1.7,5.2,22.4,10.3,41.4c22.4-37.9,56-83.6,54.3-87.9C65.9,273.9,496.9,248.1,586.6,39.4c40.5,201.8-20.7,513.9-367.2,593.2 c-1.7,0.9-62.9,108.6-65.5,109.5c0-1.7-25.9-0.9-22.4-9.5C133.1,727.4,136.6,720.6,140,713.7L140,713.7z M135.7,632.6 c44-50.9-7.8-137.9-38.8-166.4C149.5,556.7,146,609.3,135.7,632.6L135.7,632.6z" transform="translate(0)" style="fill:#17541f"/> |         <path class="leaf" d="M140,713.7c-3.4-16.4-10.3-49.1-11.2-49.1c-145.7-87.1-128.4-238-80.2-324.2C59,449,251.2,524,139.1,656.8 c-0.9,1.7,5.2,22.4,10.3,41.4c22.4-37.9,56-83.6,54.3-87.9C65.9,273.9,496.9,248.1,586.6,39.4c40.5,201.8-20.7,513.9-367.2,593.2 c-1.7,0.9-62.9,108.6-65.5,109.5c0-1.7-25.9-0.9-22.4-9.5C133.1,727.4,136.6,720.6,140,713.7L140,713.7z M135.7,632.6 c44-50.9-7.8-137.9-38.8-166.4C149.5,556.7,146,609.3,135.7,632.6L135.7,632.6z" transform="translate(0)" style="fill:#17541f"/> | ||||||
|         <g class="text" style="fill:#000"> |         <g class="text" style="fill:#000"> | ||||||
|             <path d="M1022.3,428.7c-17.8-19.9-42.7-29.8-74.7-29.8c-22.3,0-42.4,5.7-60.5,17.3c-18.1,11.6-32.3,27.5-42.5,47.8 s-15.3,42.9-15.3,67.8c0,24.9,5.1,47.5,15.3,67.8c10.3,20.3,24.4,36.2,42.5,47.8c18.1,11.5,38.3,17.3,60.5,17.3 c32,0,56.9-9.9,74.7-29.8v20.4v0.2h84.5V408.3h-84.5V428.7z M1010.5,575c-10.2,11.7-23.6,17.6-40.2,17.6s-29.9-5.9-40-17.6 s-15.1-26.1-15.1-43.3c0-17.1,5-31.6,15.1-43.3s23.4-17.6,40-17.6c16.6,0,30,5.9,40.2,17.6s15.3,26.1,15.3,43.3 S1020.7,563.3,1010.5,575z" transform="translate(0)"/> |             <path d="M1022.3,428.7c-17.8-19.9-42.7-29.8-74.7-29.8c-22.3,0-42.4,5.7-60.5,17.3c-18.1,11.6-32.3,27.5-42.5,47.8 s-15.3,42.9-15.3,67.8c0,24.9,5.1,47.5,15.3,67.8c10.3,20.3,24.4,36.2,42.5,47.8c18.1,11.5,38.3,17.3,60.5,17.3 c32,0,56.9-9.9,74.7-29.8v20.4v0.2h84.5V408.3h-84.5V428.7z M1010.5,575c-10.2,11.7-23.6,17.6-40.2,17.6s-29.9-5.9-40-17.6 s-15.1-26.1-15.1-43.3c0-17.1,5-31.6,15.1-43.3s23.4-17.6,40-17.6c16.6,0,30,5.9,40.2,17.6s15.3,26.1,15.3,43.3 S1020.7,563.3,1010.5,575z" transform="translate(0)"/> | ||||||
| @@ -15,4 +18,5 @@ | |||||||
|             <polygon points="2867.9,708.9 2846.5,708.9 2820.9,741.9 2795.5,708.9 2773.1,708.9 2809.1,755 2771.5,802.5 2792.9,802.5  2820.1,767.9 2847.2,802.6 2869.6,802.6 2832,754.4 	" transform="translate(0)"/> |             <polygon points="2867.9,708.9 2846.5,708.9 2820.9,741.9 2795.5,708.9 2773.1,708.9 2809.1,755 2771.5,802.5 2792.9,802.5  2820.1,767.9 2847.2,802.6 2869.6,802.6 2832,754.4 	" transform="translate(0)"/> | ||||||
|             <path d="M757.6,293.7c-20-10.8-42.6-16.2-67.8-16.2H600c-8.5,39.2-21.1,76.4-37.6,111.3c-9.9,20.8-21.1,40.6-33.6,59.4v207.2h88.9 V521.5h72c25.2,0,47.8-5.4,67.8-16.2s35.7-25.6,47.1-44.2c11.4-18.7,17.1-39.1,17.1-61.3c0.1-22.7-5.6-43.3-17-61.9 C793.3,319.2,777.6,304.5,757.6,293.7z M716.6,434.3c-9.3,8.9-21.6,13.3-36.7,13.3l-62.2,0.4v-92.5l62.2-0.4 c15.1,0,27.3,4.4,36.7,13.3c9.4,8.9,14,19.9,14,32.9C730.6,414.5,726,425.4,716.6,434.3z" transform="translate(0)"/> |             <path d="M757.6,293.7c-20-10.8-42.6-16.2-67.8-16.2H600c-8.5,39.2-21.1,76.4-37.6,111.3c-9.9,20.8-21.1,40.6-33.6,59.4v207.2h88.9 V521.5h72c25.2,0,47.8-5.4,67.8-16.2s35.7-25.6,47.1-44.2c11.4-18.7,17.1-39.1,17.1-61.3c0.1-22.7-5.6-43.3-17-61.9 C793.3,319.2,777.6,304.5,757.6,293.7z M716.6,434.3c-9.3,8.9-21.6,13.3-36.7,13.3l-62.2,0.4v-92.5l62.2-0.4 c15.1,0,27.3,4.4,36.7,13.3c9.4,8.9,14,19.9,14,32.9C730.6,414.5,726,425.4,716.6,434.3z" transform="translate(0)"/> | ||||||
|         </g> |         </g> | ||||||
| </svg> |     </svg> | ||||||
|  | } | ||||||
|   | |||||||
| Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.5 KiB | 
| @@ -2,15 +2,21 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' | |||||||
|  |  | ||||||
| import { LogoComponent } from './logo.component' | import { LogoComponent } from './logo.component' | ||||||
| import { By } from '@angular/platform-browser' | import { By } from '@angular/platform-browser' | ||||||
|  | import { HttpClientTestingModule } from '@angular/common/http/testing' | ||||||
|  | import { SettingsService } from 'src/app/services/settings.service' | ||||||
|  | import { SETTINGS_KEYS } from 'src/app/data/ui-settings' | ||||||
|  |  | ||||||
| describe('LogoComponent', () => { | describe('LogoComponent', () => { | ||||||
|   let component: LogoComponent |   let component: LogoComponent | ||||||
|   let fixture: ComponentFixture<LogoComponent> |   let fixture: ComponentFixture<LogoComponent> | ||||||
|  |   let settingsService: SettingsService | ||||||
|  |  | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     TestBed.configureTestingModule({ |     TestBed.configureTestingModule({ | ||||||
|       declarations: [LogoComponent], |       declarations: [LogoComponent], | ||||||
|  |       imports: [HttpClientTestingModule], | ||||||
|     }) |     }) | ||||||
|  |     settingsService = TestBed.inject(SettingsService) | ||||||
|     fixture = TestBed.createComponent(LogoComponent) |     fixture = TestBed.createComponent(LogoComponent) | ||||||
|     component = fixture.componentInstance |     component = fixture.componentInstance | ||||||
|     fixture.detectChanges() |     fixture.detectChanges() | ||||||
| @@ -33,4 +39,9 @@ describe('LogoComponent', () => { | |||||||
|       'height:10em' |       'height:10em' | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|  |   it('should support getting custom logo', () => { | ||||||
|  |     settingsService.set(SETTINGS_KEYS.APP_LOGO, '/logo/test.png') | ||||||
|  |     expect(component.customLogo).toEqual('http://localhost:8000/logo/test.png') | ||||||
|  |   }) | ||||||
| }) | }) | ||||||
|   | |||||||
| @@ -1,4 +1,7 @@ | |||||||
| import { Component, Input } from '@angular/core' | import { Component, Input } from '@angular/core' | ||||||
|  | import { SETTINGS_KEYS } from 'src/app/data/ui-settings' | ||||||
|  | import { SettingsService } from 'src/app/services/settings.service' | ||||||
|  | import { environment } from 'src/environments/environment' | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'pngx-logo', |   selector: 'pngx-logo', | ||||||
| @@ -12,6 +15,17 @@ export class LogoComponent { | |||||||
|   @Input() |   @Input() | ||||||
|   height = '6em' |   height = '6em' | ||||||
|  |  | ||||||
|  |   get customLogo(): string { | ||||||
|  |     return this.settingsService.get(SETTINGS_KEYS.APP_LOGO)?.length | ||||||
|  |       ? environment.apiBaseUrl.replace( | ||||||
|  |           /\/api\/$/, | ||||||
|  |           this.settingsService.get(SETTINGS_KEYS.APP_LOGO) | ||||||
|  |         ) | ||||||
|  |       : null | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   constructor(private settingsService: SettingsService) {} | ||||||
|  |  | ||||||
|   getClasses() { |   getClasses() { | ||||||
|     return ['logo'].concat(this.extra_classes).join(' ') |     return ['logo'].concat(this.extra_classes).join(' ') | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -5,13 +5,13 @@ import { ComponentWithPermissions } from '../with-permissions/with-permissions.c | |||||||
| import { TourService } from 'ngx-ui-tour-ng-bootstrap' | import { TourService } from 'ngx-ui-tour-ng-bootstrap' | ||||||
| import { SavedView } from 'src/app/data/saved-view' | import { SavedView } from 'src/app/data/saved-view' | ||||||
| import { ToastService } from 'src/app/services/toast.service' | import { ToastService } from 'src/app/services/toast.service' | ||||||
| import { SETTINGS_KEYS } from 'src/app/data/ui-settings' |  | ||||||
| import { | import { | ||||||
|   CdkDragDrop, |   CdkDragDrop, | ||||||
|   CdkDragEnd, |   CdkDragEnd, | ||||||
|   CdkDragStart, |   CdkDragStart, | ||||||
|   moveItemInArray, |   moveItemInArray, | ||||||
| } from '@angular/cdk/drag-drop' | } from '@angular/cdk/drag-drop' | ||||||
|  | import { environment } from 'src/environments/environment' | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'pngx-dashboard', |   selector: 'pngx-dashboard', | ||||||
| @@ -35,9 +35,9 @@ export class DashboardComponent extends ComponentWithPermissions { | |||||||
|  |  | ||||||
|   get subtitle() { |   get subtitle() { | ||||||
|     if (this.settingsService.displayName) { |     if (this.settingsService.displayName) { | ||||||
|       return $localize`Hello ${this.settingsService.displayName}, welcome to Paperless-ngx` |       return $localize`Hello ${this.settingsService.displayName}, welcome to ${environment.appTitle}` | ||||||
|     } else { |     } else { | ||||||
|       return $localize`Welcome to Paperless-ngx` |       return $localize`Welcome to ${environment.appTitle}` | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' | |||||||
| import { NotFoundComponent } from './not-found.component' | import { NotFoundComponent } from './not-found.component' | ||||||
| import { By } from '@angular/platform-browser' | import { By } from '@angular/platform-browser' | ||||||
| import { LogoComponent } from '../common/logo/logo.component' | import { LogoComponent } from '../common/logo/logo.component' | ||||||
|  | import { HttpClientTestingModule } from '@angular/common/http/testing' | ||||||
|  |  | ||||||
| describe('NotFoundComponent', () => { | describe('NotFoundComponent', () => { | ||||||
|   let component: NotFoundComponent |   let component: NotFoundComponent | ||||||
| @@ -10,6 +11,7 @@ describe('NotFoundComponent', () => { | |||||||
|   beforeEach(async () => { |   beforeEach(async () => { | ||||||
|     TestBed.configureTestingModule({ |     TestBed.configureTestingModule({ | ||||||
|       declarations: [NotFoundComponent, LogoComponent], |       declarations: [NotFoundComponent, LogoComponent], | ||||||
|  |       imports: [HttpClientTestingModule], | ||||||
|     }).compileComponents() |     }).compileComponents() | ||||||
|  |  | ||||||
|     fixture = TestBed.createComponent(NotFoundComponent) |     fixture = TestBed.createComponent(NotFoundComponent) | ||||||
|   | |||||||
| @@ -43,9 +43,11 @@ export enum ConfigOptionType { | |||||||
|   Select = 'select', |   Select = 'select', | ||||||
|   Boolean = 'boolean', |   Boolean = 'boolean', | ||||||
|   JSON = 'json', |   JSON = 'json', | ||||||
|  |   File = 'file', | ||||||
| } | } | ||||||
|  |  | ||||||
| export const ConfigCategory = { | export const ConfigCategory = { | ||||||
|  |   General: $localize`General Settings`, | ||||||
|   OCR: $localize`OCR Settings`, |   OCR: $localize`OCR Settings`, | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -164,6 +166,20 @@ export const PaperlessConfigOptions: ConfigOption[] = [ | |||||||
|     config_key: 'PAPERLESS_OCR_USER_ARGS', |     config_key: 'PAPERLESS_OCR_USER_ARGS', | ||||||
|     category: ConfigCategory.OCR, |     category: ConfigCategory.OCR, | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     key: 'app_logo', | ||||||
|  |     title: $localize`Application Logo`, | ||||||
|  |     type: ConfigOptionType.File, | ||||||
|  |     config_key: 'PAPERLESS_APP_LOGO', | ||||||
|  |     category: ConfigCategory.General, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     key: 'app_title', | ||||||
|  |     title: $localize`Application Title`, | ||||||
|  |     type: ConfigOptionType.String, | ||||||
|  |     config_key: 'PAPERLESS_APP_TITLE', | ||||||
|  |     category: ConfigCategory.General, | ||||||
|  |   }, | ||||||
| ] | ] | ||||||
|  |  | ||||||
| export interface PaperlessConfig extends ObjectWithId { | export interface PaperlessConfig extends ObjectWithId { | ||||||
| @@ -180,4 +196,6 @@ export interface PaperlessConfig extends ObjectWithId { | |||||||
|   max_image_pixels: number |   max_image_pixels: number | ||||||
|   color_conversion_strategy: ColorConvertConfig |   color_conversion_strategy: ColorConvertConfig | ||||||
|   user_args: object |   user_args: object | ||||||
|  |   app_logo: string | ||||||
|  |   app_title: string | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,6 +14,8 @@ export interface UiSetting { | |||||||
|  |  | ||||||
| export const SETTINGS_KEYS = { | export const SETTINGS_KEYS = { | ||||||
|   LANGUAGE: 'language', |   LANGUAGE: 'language', | ||||||
|  |   APP_LOGO: 'app_logo', | ||||||
|  |   APP_TITLE: 'app_title', | ||||||
|   // maintain old general-settings: for backwards compatibility |   // maintain old general-settings: for backwards compatibility | ||||||
|   BULK_EDIT_CONFIRMATION_DIALOGS: |   BULK_EDIT_CONFIRMATION_DIALOGS: | ||||||
|     'general-settings:bulk-edit:confirmation-dialogs', |     'general-settings:bulk-edit:confirmation-dialogs', | ||||||
| @@ -194,4 +196,14 @@ export const SETTINGS: UiSetting[] = [ | |||||||
|     type: 'array', |     type: 'array', | ||||||
|     default: [], |     default: [], | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     key: SETTINGS_KEYS.APP_LOGO, | ||||||
|  |     type: 'string', | ||||||
|  |     default: '', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     key: SETTINGS_KEYS.APP_TITLE, | ||||||
|  |     type: 'string', | ||||||
|  |     default: '', | ||||||
|  |   }, | ||||||
| ] | ] | ||||||
|   | |||||||
| @@ -39,4 +39,26 @@ describe('ConfigService', () => { | |||||||
|     ) |     ) | ||||||
|     expect(req.request.method).toEqual('PATCH') |     expect(req.request.method).toEqual('PATCH') | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|  |   it('should support upload file with form data', () => { | ||||||
|  |     service.uploadFile(new File([], 'test.png'), 1, 'app_logo').subscribe() | ||||||
|  |     const req = httpTestingController.expectOne( | ||||||
|  |       `${environment.apiBaseUrl}config/1/` | ||||||
|  |     ) | ||||||
|  |     expect(req.request.method).toEqual('PATCH') | ||||||
|  |     expect(req.request.body).not.toBeNull() | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   it('should not pass string app_logo', () => { | ||||||
|  |     service | ||||||
|  |       .saveConfig({ | ||||||
|  |         id: 1, | ||||||
|  |         app_logo: '/logo/foobar.png', | ||||||
|  |       } as PaperlessConfig) | ||||||
|  |       .subscribe() | ||||||
|  |     const req = httpTestingController.expectOne( | ||||||
|  |       `${environment.apiBaseUrl}config/1/` | ||||||
|  |     ) | ||||||
|  |     expect(req.request.body).toEqual({ id: 1 }) | ||||||
|  |   }) | ||||||
| }) | }) | ||||||
|   | |||||||
| @@ -20,8 +20,22 @@ export class ConfigService { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   saveConfig(config: PaperlessConfig): Observable<PaperlessConfig> { |   saveConfig(config: PaperlessConfig): Observable<PaperlessConfig> { | ||||||
|  |     // dont pass string | ||||||
|  |     if (typeof config.app_logo === 'string') delete config.app_logo | ||||||
|     return this.http |     return this.http | ||||||
|       .patch<PaperlessConfig>(`${this.baseUrl}${config.id}/`, config) |       .patch<PaperlessConfig>(`${this.baseUrl}${config.id}/`, config) | ||||||
|       .pipe(first()) |       .pipe(first()) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   uploadFile( | ||||||
|  |     file: File, | ||||||
|  |     configID: number, | ||||||
|  |     configKey: string | ||||||
|  |   ): Observable<PaperlessConfig> { | ||||||
|  |     let formData = new FormData() | ||||||
|  |     formData.append(configKey, file, file.name) | ||||||
|  |     return this.http | ||||||
|  |       .patch<PaperlessConfig>(`${this.baseUrl}${configID}/`, formData) | ||||||
|  |       .pipe(first()) | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -301,4 +301,16 @@ describe('SettingsService', () => { | |||||||
|       .expectOne(`${environment.apiBaseUrl}ui_settings/`) |       .expectOne(`${environment.apiBaseUrl}ui_settings/`) | ||||||
|       .flush(ui_settings) |       .flush(ui_settings) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|  |   it('should update environment app title if set', () => { | ||||||
|  |     const settings = Object.assign({}, ui_settings) | ||||||
|  |     settings.settings['app_title'] = 'FooBar' | ||||||
|  |     const req = httpTestingController.expectOne( | ||||||
|  |       `${environment.apiBaseUrl}ui_settings/` | ||||||
|  |     ) | ||||||
|  |     req.flush(settings) | ||||||
|  |     expect(environment.appTitle).toEqual('FooBar') | ||||||
|  |     // post for migrate | ||||||
|  |     httpTestingController.expectOne(`${environment.apiBaseUrl}ui_settings/`) | ||||||
|  |   }) | ||||||
| }) | }) | ||||||
|   | |||||||
| @@ -270,6 +270,9 @@ export class SettingsService { | |||||||
|       first(), |       first(), | ||||||
|       tap((uisettings) => { |       tap((uisettings) => { | ||||||
|         Object.assign(this.settings, uisettings.settings) |         Object.assign(this.settings, uisettings.settings) | ||||||
|  |         if (this.get(SETTINGS_KEYS.APP_TITLE)?.length) { | ||||||
|  |           environment.appTitle = this.get(SETTINGS_KEYS.APP_TITLE) | ||||||
|  |         } | ||||||
|         this.maybeMigrateSettings() |         this.maybeMigrateSettings() | ||||||
|         // to update lang cookie |         // to update lang cookie | ||||||
|         if (this.settings['language']?.length) |         if (this.settings['language']?.length) | ||||||
|   | |||||||
| @@ -11,6 +11,9 @@ $grid-breakpoints: ( | |||||||
|   xxxl: 2400px |   xxxl: 2400px | ||||||
| ); | ); | ||||||
|  |  | ||||||
|  | $form-file-button-bg: var(--bs-body-bg); | ||||||
|  | $form-file-button-hover-bg: var(--pngx-bg-alt); | ||||||
|  |  | ||||||
| @import "node_modules/bootstrap/scss/bootstrap"; | @import "node_modules/bootstrap/scss/bootstrap"; | ||||||
| @import "theme"; | @import "theme"; | ||||||
| @import "~@ng-select/ng-select/themes/default.theme.css"; | @import "~@ng-select/ng-select/themes/default.theme.css"; | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import json | import json | ||||||
|  | import os | ||||||
|  |  | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
| from rest_framework import status | from rest_framework import status | ||||||
| @@ -49,10 +50,34 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase): | |||||||
|                     "rotate_pages_threshold": None, |                     "rotate_pages_threshold": None, | ||||||
|                     "max_image_pixels": None, |                     "max_image_pixels": None, | ||||||
|                     "color_conversion_strategy": None, |                     "color_conversion_strategy": None, | ||||||
|  |                     "app_title": None, | ||||||
|  |                     "app_logo": None, | ||||||
|                 }, |                 }, | ||||||
|             ), |             ), | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |     def test_api_get_ui_settings_with_config(self): | ||||||
|  |         """ | ||||||
|  |         GIVEN: | ||||||
|  |             - Existing config with app_title, app_logo specified | ||||||
|  |         WHEN: | ||||||
|  |             - API to retrieve uisettings is called | ||||||
|  |         THEN: | ||||||
|  |             - app_title and app_logo are included | ||||||
|  |         """ | ||||||
|  |         config = ApplicationConfiguration.objects.first() | ||||||
|  |         config.app_title = "Fancy New Title" | ||||||
|  |         config.app_logo = "/logo/example.jpg" | ||||||
|  |         config.save() | ||||||
|  |         response = self.client.get("/api/ui_settings/", format="json") | ||||||
|  |         self.assertDictContainsSubset( | ||||||
|  |             { | ||||||
|  |                 "app_title": config.app_title, | ||||||
|  |                 "app_logo": config.app_logo, | ||||||
|  |             }, | ||||||
|  |             response.data["settings"], | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def test_api_update_config(self): |     def test_api_update_config(self): | ||||||
|         """ |         """ | ||||||
|         GIVEN: |         GIVEN: | ||||||
| @@ -100,3 +125,37 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase): | |||||||
|         config = ApplicationConfiguration.objects.first() |         config = ApplicationConfiguration.objects.first() | ||||||
|         self.assertEqual(config.user_args, None) |         self.assertEqual(config.user_args, None) | ||||||
|         self.assertEqual(config.language, None) |         self.assertEqual(config.language, None) | ||||||
|  |  | ||||||
|  |     def test_api_replace_app_logo(self): | ||||||
|  |         """ | ||||||
|  |         GIVEN: | ||||||
|  |             - Existing config with app_logo specified | ||||||
|  |         WHEN: | ||||||
|  |             - API to replace app_logo is called | ||||||
|  |         THEN: | ||||||
|  |             - old app_logo file is deleted | ||||||
|  |         """ | ||||||
|  |         with open( | ||||||
|  |             os.path.join(os.path.dirname(__file__), "samples", "simple.jpg"), | ||||||
|  |             "rb", | ||||||
|  |         ) as f: | ||||||
|  |             self.client.patch( | ||||||
|  |                 f"{self.ENDPOINT}1/", | ||||||
|  |                 { | ||||||
|  |                     "app_logo": f, | ||||||
|  |                 }, | ||||||
|  |             ) | ||||||
|  |         config = ApplicationConfiguration.objects.first() | ||||||
|  |         old_logo = config.app_logo | ||||||
|  |         self.assertTrue(os.path.exists(old_logo.path)) | ||||||
|  |         with open( | ||||||
|  |             os.path.join(os.path.dirname(__file__), "samples", "simple.png"), | ||||||
|  |             "rb", | ||||||
|  |         ) as f: | ||||||
|  |             self.client.patch( | ||||||
|  |                 f"{self.ENDPOINT}1/", | ||||||
|  |                 { | ||||||
|  |                     "app_logo": f, | ||||||
|  |                 }, | ||||||
|  |             ) | ||||||
|  |         self.assertFalse(os.path.exists(old_logo.path)) | ||||||
|   | |||||||
| @@ -35,6 +35,8 @@ class TestApiUiSettings(DirectoriesMixin, APITestCase): | |||||||
|         self.assertDictEqual( |         self.assertDictEqual( | ||||||
|             response.data["settings"], |             response.data["settings"], | ||||||
|             { |             { | ||||||
|  |                 "app_title": None, | ||||||
|  |                 "app_logo": None, | ||||||
|                 "update_checking": { |                 "update_checking": { | ||||||
|                     "backend_setting": "default", |                     "backend_setting": "default", | ||||||
|                 }, |                 }, | ||||||
|   | |||||||
| @@ -120,6 +120,7 @@ from documents.serialisers import WorkflowTriggerSerializer | |||||||
| from documents.signals import document_updated | from documents.signals import document_updated | ||||||
| from documents.tasks import consume_file | from documents.tasks import consume_file | ||||||
| from paperless import version | from paperless import version | ||||||
|  | from paperless.config import GeneralConfig | ||||||
| from paperless.db import GnuPG | from paperless.db import GnuPG | ||||||
| from paperless.views import StandardPagination | from paperless.views import StandardPagination | ||||||
|  |  | ||||||
| @@ -1164,6 +1165,16 @@ class UiSettingsView(GenericAPIView): | |||||||
|             ui_settings["update_checking"] = { |             ui_settings["update_checking"] = { | ||||||
|                 "backend_setting": settings.ENABLE_UPDATE_CHECK, |                 "backend_setting": settings.ENABLE_UPDATE_CHECK, | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |         general_config = GeneralConfig() | ||||||
|  |  | ||||||
|  |         ui_settings["app_title"] = settings.APP_TITLE | ||||||
|  |         if general_config.app_title is not None and len(general_config.app_title) > 0: | ||||||
|  |             ui_settings["app_title"] = general_config.app_title | ||||||
|  |         ui_settings["app_logo"] = settings.APP_LOGO | ||||||
|  |         if general_config.app_logo is not None and len(general_config.app_logo) > 0: | ||||||
|  |             ui_settings["app_logo"] = general_config.app_logo | ||||||
|  |  | ||||||
|         user_resp = { |         user_resp = { | ||||||
|             "id": user.id, |             "id": user.id, | ||||||
|             "username": user.username, |             "username": user.username, | ||||||
|   | |||||||
| @@ -8,13 +8,11 @@ from paperless.models import ApplicationConfiguration | |||||||
|  |  | ||||||
|  |  | ||||||
| @dataclasses.dataclass | @dataclasses.dataclass | ||||||
| class OutputTypeConfig: | class BaseConfig: | ||||||
|     """ |     """ | ||||||
|     Almost all parsers care about the chosen PDF output format |     Almost all parsers care about the chosen PDF output format | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     output_type: str = dataclasses.field(init=False) |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def _get_config_instance() -> ApplicationConfiguration: |     def _get_config_instance() -> ApplicationConfiguration: | ||||||
|         app_config = ApplicationConfiguration.objects.all().first() |         app_config = ApplicationConfiguration.objects.all().first() | ||||||
| @@ -24,6 +22,15 @@ class OutputTypeConfig: | |||||||
|             app_config = ApplicationConfiguration.objects.all().first() |             app_config = ApplicationConfiguration.objects.all().first() | ||||||
|         return app_config |         return app_config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclasses.dataclass | ||||||
|  | class OutputTypeConfig(BaseConfig): | ||||||
|  |     """ | ||||||
|  |     Almost all parsers care about the chosen PDF output format | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     output_type: str = dataclasses.field(init=False) | ||||||
|  |  | ||||||
|     def __post_init__(self) -> None: |     def __post_init__(self) -> None: | ||||||
|         app_config = self._get_config_instance() |         app_config = self._get_config_instance() | ||||||
|  |  | ||||||
| @@ -86,3 +93,19 @@ class OcrConfig(OutputTypeConfig): | |||||||
|                 user_args = {} |                 user_args = {} | ||||||
|  |  | ||||||
|         self.user_args = user_args |         self.user_args = user_args | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclasses.dataclass | ||||||
|  | class GeneralConfig(BaseConfig): | ||||||
|  |     """ | ||||||
|  |     General application settings that require global scope | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     app_title: str = dataclasses.field(init=False) | ||||||
|  |     app_logo: str = dataclasses.field(init=False) | ||||||
|  |  | ||||||
|  |     def __post_init__(self) -> None: | ||||||
|  |         app_config = self._get_config_instance() | ||||||
|  |  | ||||||
|  |         self.app_title = app_config.app_title or None | ||||||
|  |         self.app_logo = app_config.app_logo.url if app_config.app_logo else None | ||||||
|   | |||||||
| @@ -0,0 +1,33 @@ | |||||||
|  | # Generated by Django 4.2.9 on 2024-01-12 05:33 | ||||||
|  |  | ||||||
|  | from django.db import migrations | ||||||
|  | from django.db import models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |     dependencies = [ | ||||||
|  |         ("paperless", "0001_initial"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="applicationconfiguration", | ||||||
|  |             name="app_logo", | ||||||
|  |             field=models.FileField( | ||||||
|  |                 blank=True, | ||||||
|  |                 null=True, | ||||||
|  |                 upload_to="", | ||||||
|  |                 verbose_name="Application logo", | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="applicationconfiguration", | ||||||
|  |             name="app_title", | ||||||
|  |             field=models.CharField( | ||||||
|  |                 blank=True, | ||||||
|  |                 max_length=48, | ||||||
|  |                 null=True, | ||||||
|  |                 verbose_name="Application title", | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | from django.core.validators import FileExtensionValidator | ||||||
| from django.core.validators import MinValueValidator | from django.core.validators import MinValueValidator | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.utils.translation import gettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
| @@ -166,6 +167,23 @@ class ApplicationConfiguration(AbstractSingletonModel): | |||||||
|         null=True, |         null=True, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     app_title = models.CharField( | ||||||
|  |         verbose_name=_("Application title"), | ||||||
|  |         null=True, | ||||||
|  |         blank=True, | ||||||
|  |         max_length=48, | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     app_logo = models.FileField( | ||||||
|  |         verbose_name=_("Application logo"), | ||||||
|  |         null=True, | ||||||
|  |         blank=True, | ||||||
|  |         validators=[ | ||||||
|  |             FileExtensionValidator(allowed_extensions=["jpg", "png", "gif", "svg"]), | ||||||
|  |         ], | ||||||
|  |         upload_to="logo/", | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         verbose_name = _("paperless application settings") |         verbose_name = _("paperless application settings") | ||||||
|  |  | ||||||
|   | |||||||
| @@ -132,6 +132,11 @@ class ApplicationConfigurationSerializer(serializers.ModelSerializer): | |||||||
|             data["language"] = None |             data["language"] = None | ||||||
|         return super().run_validation(data) |         return super().run_validation(data) | ||||||
|  |  | ||||||
|  |     def update(self, instance, validated_data): | ||||||
|  |         if instance.app_logo and "app_logo" in validated_data: | ||||||
|  |             instance.app_logo.delete() | ||||||
|  |         return super().update(instance, validated_data) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = ApplicationConfiguration |         model = ApplicationConfiguration | ||||||
|         fields = "__all__" |         fields = "__all__" | ||||||
|   | |||||||
| @@ -367,6 +367,7 @@ STORAGES = { | |||||||
|     "staticfiles": { |     "staticfiles": { | ||||||
|         "BACKEND": _static_backend, |         "BACKEND": _static_backend, | ||||||
|     }, |     }, | ||||||
|  |     "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"}, | ||||||
| } | } | ||||||
|  |  | ||||||
| _CELERY_REDIS_URL, _CHANNELS_REDIS_URL = _parse_redis_url( | _CELERY_REDIS_URL, _CHANNELS_REDIS_URL = _parse_redis_url( | ||||||
| @@ -999,6 +1000,9 @@ ENABLE_UPDATE_CHECK = os.getenv("PAPERLESS_ENABLE_UPDATE_CHECK", "default") | |||||||
| if ENABLE_UPDATE_CHECK != "default": | if ENABLE_UPDATE_CHECK != "default": | ||||||
|     ENABLE_UPDATE_CHECK = __get_boolean("PAPERLESS_ENABLE_UPDATE_CHECK") |     ENABLE_UPDATE_CHECK = __get_boolean("PAPERLESS_ENABLE_UPDATE_CHECK") | ||||||
|  |  | ||||||
|  | APP_TITLE = os.getenv("PAPERLESS_APP_TITLE", None) | ||||||
|  | APP_LOGO = os.getenv("PAPERLESS_APP_LOGO", None) | ||||||
|  |  | ||||||
| ############################################################################### | ############################################################################### | ||||||
| # Machine Learning                                                            # | # Machine Learning                                                            # | ||||||
| ############################################################################### | ############################################################################### | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | import os | ||||||
|  |  | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.conf.urls import include | from django.conf.urls import include | ||||||
| from django.contrib import admin | from django.contrib import admin | ||||||
| @@ -8,6 +10,7 @@ from django.utils.translation import gettext_lazy as _ | |||||||
| from django.views.decorators.csrf import csrf_exempt | from django.views.decorators.csrf import csrf_exempt | ||||||
| from django.views.decorators.csrf import ensure_csrf_cookie | from django.views.decorators.csrf import ensure_csrf_cookie | ||||||
| from django.views.generic import RedirectView | from django.views.generic import RedirectView | ||||||
|  | from django.views.static import serve | ||||||
| from rest_framework.authtoken import views | from rest_framework.authtoken import views | ||||||
| from rest_framework.routers import DefaultRouter | from rest_framework.routers import DefaultRouter | ||||||
|  |  | ||||||
| @@ -181,6 +184,12 @@ urlpatterns = [ | |||||||
|             url=settings.STATIC_URL + "frontend/en-US/assets/%(path)s", |             url=settings.STATIC_URL + "frontend/en-US/assets/%(path)s", | ||||||
|         ), |         ), | ||||||
|     ), |     ), | ||||||
|  |     # App logo | ||||||
|  |     re_path( | ||||||
|  |         r"^logo(?P<path>.*)$", | ||||||
|  |         serve, | ||||||
|  |         kwargs={"document_root": os.path.join(settings.MEDIA_ROOT, "logo")}, | ||||||
|  |     ), | ||||||
|     # TODO: with localization, this is even worse! :/ |     # TODO: with localization, this is even worse! :/ | ||||||
|     # login, logout |     # login, logout | ||||||
|     path("accounts/", include("django.contrib.auth.urls")), |     path("accounts/", include("django.contrib.auth.urls")), | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon