mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Merge pull request #919 from paperless-ngx/feature-settings-saved-to-db
Feature: frontend settings saved to database
This commit is contained in:
		| @@ -332,6 +332,12 @@ writing. Windows is not and will never be supported. | ||||
| 3.  Optional. Install ``postgresql`` and configure a database, user and password for paperless. If you do not wish | ||||
|     to use PostgreSQL, SQLite is available as well. | ||||
|  | ||||
|     .. note:: | ||||
|  | ||||
|         On bare-metal installations using SQLite, ensure the | ||||
|         `JSON1 extension <https://code.djangoproject.com/wiki/JSON1Extension>`_ is enabled. This is | ||||
|         usually the case, but not always. | ||||
|  | ||||
| 4.  Get the release archive from `<https://github.com/paperless-ngx/paperless-ngx/releases>`_. | ||||
|     If you clone the git repo as it is, you also have to compile the front end by yourself. | ||||
|     Extract the archive to a place from where you wish to execute it, such as ``/opt/paperless``. | ||||
|   | ||||
							
								
								
									
										34
									
								
								src-ui/cypress/fixtures/ui_settings/settings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src-ui/cypress/fixtures/ui_settings/settings.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| { | ||||
|     "user_id": 1, | ||||
|     "username": "admin", | ||||
|     "display_name": "Admin", | ||||
|     "settings": { | ||||
|         "language": "", | ||||
|         "bulk_edit": { | ||||
|             "confirmation_dialogs": true, | ||||
|             "apply_on_close": false | ||||
|         }, | ||||
|         "documentListSize": 50, | ||||
|         "dark_mode": { | ||||
|             "use_system": true, | ||||
|             "enabled": "false", | ||||
|             "thumb_inverted": "true" | ||||
|         }, | ||||
|         "theme": { | ||||
|             "color": "#b198e5" | ||||
|         }, | ||||
|         "document_details": { | ||||
|             "native_pdf_viewer": false | ||||
|         }, | ||||
|         "date_display": { | ||||
|             "date_locale": "", | ||||
|             "date_format": "mediumDate" | ||||
|         }, | ||||
|         "notifications": { | ||||
|             "consumer_new_documents": true, | ||||
|             "consumer_success": true, | ||||
|             "consumer_failed": true, | ||||
|             "consumer_suppress_on_dashboard": true | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -2,6 +2,9 @@ describe('document-detail', () => { | ||||
|   beforeEach(() => { | ||||
|     this.modifiedDocuments = [] | ||||
|  | ||||
|     cy.intercept('http://localhost:8000/api/ui_settings/', { | ||||
|       fixture: 'ui_settings/settings.json', | ||||
|     }) | ||||
|     cy.fixture('documents/documents.json').then((documentsJson) => { | ||||
|       cy.intercept('GET', 'http://localhost:8000/api/documents/1/', (req) => { | ||||
|         let response = { ...documentsJson } | ||||
|   | ||||
| @@ -3,6 +3,9 @@ describe('documents-list', () => { | ||||
|     this.bulkEdits = {} | ||||
|  | ||||
|     // mock API methods | ||||
|     cy.intercept('http://localhost:8000/api/ui_settings/', { | ||||
|       fixture: 'ui_settings/settings.json', | ||||
|     }) | ||||
|     cy.fixture('documents/documents.json').then((documentsJson) => { | ||||
|       // bulk edit | ||||
|       cy.intercept( | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| describe('manage', () => { | ||||
|   beforeEach(() => { | ||||
|     cy.intercept('http://localhost:8000/api/ui_settings/', { | ||||
|       fixture: 'ui_settings/settings.json', | ||||
|     }) | ||||
|     cy.intercept('http://localhost:8000/api/correspondents/*', { | ||||
|       fixture: 'correspondents/correspondents.json', | ||||
|     }) | ||||
|   | ||||
| @@ -3,45 +3,53 @@ describe('settings', () => { | ||||
|     this.modifiedViews = [] | ||||
|  | ||||
|     // mock API methods | ||||
|     cy.fixture('saved_views/savedviews.json').then((savedViewsJson) => { | ||||
|       // saved views PATCH | ||||
|       cy.intercept( | ||||
|         'PATCH', | ||||
|         'http://localhost:8000/api/saved_views/*', | ||||
|         (req) => { | ||||
|           this.modifiedViews.push(req.body) // store this for later | ||||
|           req.reply({ result: 'OK' }) | ||||
|         } | ||||
|       ) | ||||
|     cy.intercept('http://localhost:8000/api/ui_settings/', { | ||||
|       fixture: 'ui_settings/settings.json', | ||||
|     }).then(() => { | ||||
|       cy.fixture('saved_views/savedviews.json').then((savedViewsJson) => { | ||||
|         // saved views PATCH | ||||
|         cy.intercept( | ||||
|           'PATCH', | ||||
|           'http://localhost:8000/api/saved_views/*', | ||||
|           (req) => { | ||||
|             this.modifiedViews.push(req.body) // store this for later | ||||
|             req.reply({ result: 'OK' }) | ||||
|           } | ||||
|         ) | ||||
|  | ||||
|       cy.intercept('GET', 'http://localhost:8000/api/saved_views/*', (req) => { | ||||
|         let response = { ...savedViewsJson } | ||||
|         if (this.modifiedViews.length) { | ||||
|           response.results = response.results.map((v) => { | ||||
|             if (this.modifiedViews.find((mv) => mv.id == v.id)) | ||||
|               v = this.modifiedViews.find((mv) => mv.id == v.id) | ||||
|             return v | ||||
|           }) | ||||
|         } | ||||
|         cy.intercept( | ||||
|           'GET', | ||||
|           'http://localhost:8000/api/saved_views/*', | ||||
|           (req) => { | ||||
|             let response = { ...savedViewsJson } | ||||
|             if (this.modifiedViews.length) { | ||||
|               response.results = response.results.map((v) => { | ||||
|                 if (this.modifiedViews.find((mv) => mv.id == v.id)) | ||||
|                   v = this.modifiedViews.find((mv) => mv.id == v.id) | ||||
|                 return v | ||||
|               }) | ||||
|             } | ||||
|  | ||||
|         req.reply(response) | ||||
|       }).as('savedViews') | ||||
|     }) | ||||
|  | ||||
|     cy.fixture('documents/documents.json').then((documentsJson) => { | ||||
|       cy.intercept('GET', 'http://localhost:8000/api/documents/1/', (req) => { | ||||
|         let response = { ...documentsJson } | ||||
|         response = response.results.find((d) => d.id == 1) | ||||
|         req.reply(response) | ||||
|             req.reply(response) | ||||
|           } | ||||
|         ).as('savedViews') | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|     cy.intercept('http://localhost:8000/api/documents/1/metadata/', { | ||||
|       fixture: 'documents/1/metadata.json', | ||||
|     }) | ||||
|       cy.fixture('documents/documents.json').then((documentsJson) => { | ||||
|         cy.intercept('GET', 'http://localhost:8000/api/documents/1/', (req) => { | ||||
|           let response = { ...documentsJson } | ||||
|           response = response.results.find((d) => d.id == 1) | ||||
|           req.reply(response) | ||||
|         }) | ||||
|       }) | ||||
|  | ||||
|     cy.intercept('http://localhost:8000/api/documents/1/suggestions/', { | ||||
|       fixture: 'documents/1/suggestions.json', | ||||
|       cy.intercept('http://localhost:8000/api/documents/1/metadata/', { | ||||
|         fixture: 'documents/1/metadata.json', | ||||
|       }) | ||||
|  | ||||
|       cy.intercept('http://localhost:8000/api/documents/1/suggestions/', { | ||||
|         fixture: 'documents/1/suggestions.json', | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|     cy.viewport(1024, 1024) | ||||
|   | ||||
| @@ -256,21 +256,21 @@ | ||||
|         <source>Document added</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/app.component.ts</context> | ||||
|           <context context-type="linenumber">71</context> | ||||
|           <context context-type="linenumber">72</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="9204248378636247318" datatype="html"> | ||||
|         <source>Document <x id="PH" equiv-text="status.filename"/> was added to paperless.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/app.component.ts</context> | ||||
|           <context context-type="linenumber">73</context> | ||||
|           <context context-type="linenumber">74</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1931214133925051574" datatype="html"> | ||||
|         <source>Open document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/app.component.ts</context> | ||||
|           <context context-type="linenumber">74</context> | ||||
|           <context context-type="linenumber">75</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context> | ||||
| @@ -281,28 +281,28 @@ | ||||
|         <source>Could not add <x id="PH" equiv-text="status.filename"/>: <x id="PH_1" equiv-text="status.message"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/app.component.ts</context> | ||||
|           <context context-type="linenumber">89</context> | ||||
|           <context context-type="linenumber">90</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1710712016675379662" datatype="html"> | ||||
|         <source>New document detected</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/app.component.ts</context> | ||||
|           <context context-type="linenumber">103</context> | ||||
|           <context context-type="linenumber">104</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="587031278561344416" datatype="html"> | ||||
|         <source>Document <x id="PH" equiv-text="status.filename"/> is being processed by paperless.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/app.component.ts</context> | ||||
|           <context context-type="linenumber">105</context> | ||||
|           <context context-type="linenumber">106</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5749300816154614125" datatype="html"> | ||||
|         <source>Initiating upload...</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/app.component.ts</context> | ||||
|           <context context-type="linenumber">140</context> | ||||
|           <context context-type="linenumber">141</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2173456130768795374" datatype="html"> | ||||
| @@ -321,7 +321,7 @@ | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2448391510242468907" datatype="html"> | ||||
|         <source>Logged in as <x id="INTERPOLATION" equiv-text="{{displayName}}"/></source> | ||||
|         <source>Logged in as <x id="INTERPOLATION" equiv-text="{{this.settingsService.displayName}}"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context> | ||||
|           <context context-type="linenumber">34</context> | ||||
| @@ -368,7 +368,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> | ||||
|           <context context-type="linenumber">68</context> | ||||
|           <context context-type="linenumber">69</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
| @@ -551,15 +551,15 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">217</context> | ||||
|           <context context-type="linenumber">215</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">255</context> | ||||
|           <context context-type="linenumber">253</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">291</context> | ||||
|           <context context-type="linenumber">289</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6371576811194810854" datatype="html"> | ||||
| @@ -942,17 +942,17 @@ | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5412339817978503936" datatype="html"> | ||||
|         <source>Hello <x id="PH" equiv-text="this.displayName"/>, welcome to Paperless-ngx!</source> | ||||
|         <source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to Paperless-ngx!</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> | ||||
|           <context context-type="linenumber">27</context> | ||||
|           <context context-type="linenumber">19</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="795745990148149834" datatype="html"> | ||||
|         <source>Welcome to Paperless-ngx!</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> | ||||
|           <context context-type="linenumber">29</context> | ||||
|           <context context-type="linenumber">21</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2946624699882754313" datatype="html"> | ||||
| @@ -1095,7 +1095,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">162</context> | ||||
|           <context context-type="linenumber">160</context> | ||||
|         </context-group> | ||||
|         <note priority="1" from="description">this string is used to separate processing, failed and added on the file upload widget</note> | ||||
|       </trans-unit> | ||||
| @@ -1458,7 +1458,7 @@ | ||||
|         <source>Confirm delete</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">423</context> | ||||
|           <context context-type="linenumber">421</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
| @@ -1469,28 +1469,28 @@ | ||||
|         <source>Do you really want to delete document "<x id="PH" equiv-text="this.document.title"/>"?</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">424</context> | ||||
|           <context context-type="linenumber">422</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6691075929777935948" datatype="html"> | ||||
|         <source>The files for this document will be deleted permanently. This operation cannot be undone.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">425</context> | ||||
|           <context context-type="linenumber">423</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="719892092227206532" datatype="html"> | ||||
|         <source>Delete document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">427</context> | ||||
|           <context context-type="linenumber">425</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1844801255494293730" datatype="html"> | ||||
|         <source>Error deleting document: <x id="PH" equiv-text="JSON.stringify(error)"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">443</context> | ||||
|           <context context-type="linenumber">441</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6857598786757174736" datatype="html"> | ||||
| @@ -1560,25 +1560,25 @@ | ||||
|             )"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">97,99</context> | ||||
|           <context context-type="linenumber">95,97</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7894972847287473517" datatype="html"> | ||||
|         <source>"<x id="PH" equiv-text="items[0].name"/>"</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">154</context> | ||||
|           <context context-type="linenumber">152</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">160</context> | ||||
|           <context context-type="linenumber">158</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8639884465898458690" datatype="html"> | ||||
|         <source>"<x id="PH" equiv-text="items[0].name"/>" and "<x id="PH_1" equiv-text="items[1].name"/>"</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">156</context> | ||||
|           <context context-type="linenumber">154</context> | ||||
|         </context-group> | ||||
|         <note priority="1" from="description">This is for messages like 'modify "tag1" and "tag2"'</note> | ||||
|       </trans-unit> | ||||
| @@ -1586,7 +1586,7 @@ | ||||
|         <source><x id="PH" equiv-text="list"/> and "<x id="PH_1" equiv-text="items[items.length - 1].name"/>"</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">164,166</context> | ||||
|           <context context-type="linenumber">162,164</context> | ||||
|         </context-group> | ||||
|         <note priority="1" from="description">this is for messages like 'modify "tag1", "tag2" and "tag3"'</note> | ||||
|       </trans-unit> | ||||
| @@ -1594,14 +1594,14 @@ | ||||
|         <source>Confirm tags assignment</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">181</context> | ||||
|           <context context-type="linenumber">179</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6619516195038467207" datatype="html"> | ||||
|         <source>This operation will add the tag "<x id="PH" equiv-text="tag.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">187</context> | ||||
|           <context context-type="linenumber">185</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1894412783609570695" datatype="html"> | ||||
| @@ -1610,14 +1610,14 @@ | ||||
|         )"/> to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">192,194</context> | ||||
|           <context context-type="linenumber">190,192</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7181166515756808573" datatype="html"> | ||||
|         <source>This operation will remove the tag "<x id="PH" equiv-text="tag.name"/>" from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">200</context> | ||||
|           <context context-type="linenumber">198</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3819792277998068944" datatype="html"> | ||||
| @@ -1626,7 +1626,7 @@ | ||||
|         )"/> from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">205,207</context> | ||||
|           <context context-type="linenumber">203,205</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2739066218579571288" datatype="html"> | ||||
| @@ -1637,77 +1637,77 @@ | ||||
|         )"/> on <x id="PH_2" equiv-text="this.list.selected.size"/> selected document(s).</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">209,213</context> | ||||
|           <context context-type="linenumber">207,211</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2996713129519325161" datatype="html"> | ||||
|         <source>Confirm correspondent assignment</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">248</context> | ||||
|           <context context-type="linenumber">246</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6900893559485781849" datatype="html"> | ||||
|         <source>This operation will assign the correspondent "<x id="PH" equiv-text="correspondent.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">250</context> | ||||
|           <context context-type="linenumber">248</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1257522660364398440" datatype="html"> | ||||
|         <source>This operation will remove the correspondent from <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">252</context> | ||||
|           <context context-type="linenumber">250</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5393409374423140648" datatype="html"> | ||||
|         <source>Confirm document type assignment</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">284</context> | ||||
|           <context context-type="linenumber">282</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="332180123895325027" datatype="html"> | ||||
|         <source>This operation will assign the document type "<x id="PH" equiv-text="documentType.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">286</context> | ||||
|           <context context-type="linenumber">284</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2236642492594872779" datatype="html"> | ||||
|         <source>This operation will remove the document type from <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">288</context> | ||||
|           <context context-type="linenumber">286</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="749430623564850405" datatype="html"> | ||||
|         <source>Delete confirm</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">309</context> | ||||
|           <context context-type="linenumber">307</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4303174930844518780" datatype="html"> | ||||
|         <source>This operation will permanently delete <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">310</context> | ||||
|           <context context-type="linenumber">308</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5641451190833696892" datatype="html"> | ||||
|         <source>This operation cannot be undone.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">311</context> | ||||
|           <context context-type="linenumber">309</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6734339521247847366" datatype="html"> | ||||
|         <source>Delete document(s)</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
|           <context context-type="linenumber">313</context> | ||||
|           <context context-type="linenumber">311</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8076495233090006322" datatype="html"> | ||||
| @@ -1913,14 +1913,14 @@ | ||||
|         <source>View "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" saved successfully.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> | ||||
|           <context context-type="linenumber">197</context> | ||||
|           <context context-type="linenumber">198</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6837554170707123455" datatype="html"> | ||||
|         <source>View "<x id="PH" equiv-text="savedView.name"/>" created successfully.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context> | ||||
|           <context context-type="linenumber">227</context> | ||||
|           <context context-type="linenumber">228</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6849725902312323996" datatype="html"> | ||||
| @@ -2512,21 +2512,28 @@ | ||||
|         <source>Settings saved successfully.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context> | ||||
|           <context context-type="linenumber">233</context> | ||||
|           <context context-type="linenumber">238</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3011185103048412841" datatype="html"> | ||||
|         <source>An error occurred while saving settings.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context> | ||||
|           <context context-type="linenumber">242</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6839066544204061364" datatype="html"> | ||||
|         <source>Use system language</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context> | ||||
|           <context context-type="linenumber">237</context> | ||||
|           <context context-type="linenumber">250</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7729897675462249787" datatype="html"> | ||||
|         <source>Use date format of display language</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context> | ||||
|           <context context-type="linenumber">244</context> | ||||
|           <context context-type="linenumber">257</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8488620293789898901" datatype="html"> | ||||
| @@ -2535,7 +2542,7 @@ | ||||
|             )"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context> | ||||
|           <context context-type="linenumber">264,266</context> | ||||
|           <context context-type="linenumber">277,279</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6402703264596649214" datatype="html"> | ||||
| @@ -2853,154 +2860,168 @@ | ||||
|         <source>English (US)</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">184</context> | ||||
|           <context context-type="linenumber">136</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3098941349689899577" datatype="html"> | ||||
|         <source>Belarusian</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">190</context> | ||||
|           <context context-type="linenumber">142</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2719780722934172508" datatype="html"> | ||||
|         <source>Czech</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">196</context> | ||||
|           <context context-type="linenumber">148</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2924289692679201020" datatype="html"> | ||||
|         <source>Danish</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">202</context> | ||||
|           <context context-type="linenumber">154</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1858110241312746425" datatype="html"> | ||||
|         <source>German</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">208</context> | ||||
|           <context context-type="linenumber">160</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6987083569809053351" datatype="html"> | ||||
|         <source>English (GB)</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">214</context> | ||||
|           <context context-type="linenumber">166</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5190825892106392539" datatype="html"> | ||||
|         <source>Spanish</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">220</context> | ||||
|           <context context-type="linenumber">172</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7633754075223722162" datatype="html"> | ||||
|         <source>French</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">226</context> | ||||
|           <context context-type="linenumber">178</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2935232983274991580" datatype="html"> | ||||
|         <source>Italian</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">232</context> | ||||
|           <context context-type="linenumber">184</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1334425850005897370" datatype="html"> | ||||
|         <source>Luxembourgish</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">238</context> | ||||
|           <context context-type="linenumber">190</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3071065188816255493" datatype="html"> | ||||
|         <source>Dutch</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">244</context> | ||||
|           <context context-type="linenumber">196</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="792060551707690640" datatype="html"> | ||||
|         <source>Polish</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">250</context> | ||||
|           <context context-type="linenumber">202</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="9184513005098760425" datatype="html"> | ||||
|         <source>Portuguese (Brazil)</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">256</context> | ||||
|           <context context-type="linenumber">208</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="153799456510623899" datatype="html"> | ||||
|         <source>Portuguese</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">262</context> | ||||
|           <context context-type="linenumber">214</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8118856427047826368" datatype="html"> | ||||
|         <source>Romanian</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">268</context> | ||||
|           <context context-type="linenumber">220</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7137419789978325708" datatype="html"> | ||||
|         <source>Russian</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">274</context> | ||||
|           <context context-type="linenumber">226</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4287008301409320881" datatype="html"> | ||||
|         <source>Slovenian</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">280</context> | ||||
|           <context context-type="linenumber">232</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8608389829607915090" datatype="html"> | ||||
|         <source>Serbian</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">286</context> | ||||
|           <context context-type="linenumber">238</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="499386805970351976" datatype="html"> | ||||
|         <source>Swedish</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">292</context> | ||||
|           <context context-type="linenumber">244</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5682359291233237791" datatype="html"> | ||||
|         <source>Turkish</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">298</context> | ||||
|           <context context-type="linenumber">250</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4689443708886954687" datatype="html"> | ||||
|         <source>Chinese Simplified</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">304</context> | ||||
|           <context context-type="linenumber">256</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4912706592792948707" datatype="html"> | ||||
|         <source>ISO 8601</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">321</context> | ||||
|           <context context-type="linenumber">273</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="313643372755303297" datatype="html"> | ||||
|         <source>Successfully completed one-time migratration of settings to the database!</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">368</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5558341108007064934" datatype="html"> | ||||
|         <source>Unable to migrate settings to the database, please try saving manually.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/settings.service.ts</context> | ||||
|           <context context-type="linenumber">369</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1519954996184640001" datatype="html"> | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import { SettingsService, SETTINGS_KEYS } from './services/settings.service' | ||||
| import { SettingsService } from './services/settings.service' | ||||
| import { SETTINGS_KEYS } from './data/paperless-uisettings' | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core' | ||||
| import { Router } from '@angular/router' | ||||
| import { Subscription } from 'rxjs' | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { BrowserModule } from '@angular/platform-browser' | ||||
| import { NgModule } from '@angular/core' | ||||
| import { APP_INITIALIZER, NgModule } from '@angular/core' | ||||
| import { AppRoutingModule } from './app-routing.module' | ||||
| import { AppComponent } from './app.component' | ||||
| import { | ||||
| @@ -87,6 +87,7 @@ import localeSr from '@angular/common/locales/sr' | ||||
| import localeSv from '@angular/common/locales/sv' | ||||
| import localeTr from '@angular/common/locales/tr' | ||||
| import localeZh from '@angular/common/locales/zh' | ||||
| import { SettingsService } from './services/settings.service' | ||||
|  | ||||
| registerLocaleData(localeBe) | ||||
| registerLocaleData(localeCs) | ||||
| @@ -109,6 +110,12 @@ registerLocaleData(localeSv) | ||||
| registerLocaleData(localeTr) | ||||
| registerLocaleData(localeZh) | ||||
|  | ||||
| function initializeApp(settings: SettingsService) { | ||||
|   return () => { | ||||
|     return settings.initializeSettings() | ||||
|   } | ||||
| } | ||||
|  | ||||
| @NgModule({ | ||||
|   declarations: [ | ||||
|     AppComponent, | ||||
| @@ -174,6 +181,12 @@ registerLocaleData(localeZh) | ||||
|     ColorSliderModule, | ||||
|   ], | ||||
|   providers: [ | ||||
|     { | ||||
|       provide: APP_INITIALIZER, | ||||
|       useFactory: initializeApp, | ||||
|       deps: [SettingsService], | ||||
|       multi: true, | ||||
|     }, | ||||
|     DatePipe, | ||||
|     CookieService, | ||||
|     { | ||||
|   | ||||
| @@ -21,17 +21,17 @@ | ||||
|   </div> | ||||
|   <ul ngbNav class="order-sm-3"> | ||||
|     <li ngbDropdown class="nav-item dropdown"> | ||||
|       <button class="btn text-light" id="userDropdown" ngbDropdownToggle> | ||||
|         <span *ngIf="displayName" class="navbar-text small me-2 text-light d-none d-sm-inline"> | ||||
|           {{displayName}} | ||||
|       <button class="btn" id="userDropdown" ngbDropdownToggle> | ||||
|         <span class="small me-2 d-none d-sm-inline"> | ||||
|           {{this.settingsService.displayName}} | ||||
|         </span> | ||||
|         <svg width="1.3em" height="1.3em" fill="currentColor"> | ||||
|           <use xlink:href="assets/bootstrap-icons.svg#person-circle"/> | ||||
|         </svg> | ||||
|       </button> | ||||
|       <div ngbDropdownMenu class="dropdown-menu-end shadow me-2" aria-labelledby="userDropdown"> | ||||
|         <div *ngIf="displayName" class="d-sm-none"> | ||||
|           <p class="small mb-0 px-3 text-muted" i18n>Logged in as {{displayName}}</p> | ||||
|         <div class="d-sm-none"> | ||||
|           <p class="small mb-0 px-3 text-muted" i18n>Logged in as {{this.settingsService.displayName}}</p> | ||||
|           <div class="dropdown-divider"></div> | ||||
|         </div> | ||||
|         <a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()"> | ||||
|   | ||||
| @@ -23,6 +23,7 @@ import { | ||||
|   AppRemoteVersion, | ||||
| } from 'src/app/services/rest/remote-version.service' | ||||
| import { QueryParamsService } from 'src/app/services/query-params.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-app-frame', | ||||
| @@ -36,10 +37,9 @@ export class AppFrameComponent { | ||||
|     private openDocumentsService: OpenDocumentsService, | ||||
|     private searchService: SearchService, | ||||
|     public savedViewService: SavedViewService, | ||||
|     private list: DocumentListViewService, | ||||
|     private meta: Meta, | ||||
|     private remoteVersionService: RemoteVersionService, | ||||
|     private queryParamsService: QueryParamsService | ||||
|     private queryParamsService: QueryParamsService, | ||||
|     public settingsService: SettingsService | ||||
|   ) { | ||||
|     this.remoteVersionService | ||||
|       .checkForUpdates() | ||||
| @@ -143,17 +143,4 @@ export class AppFrameComponent { | ||||
|         } | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   get displayName() { | ||||
|     // TODO: taken from dashboard component, is this the best way to pass around username? | ||||
|     let tagFullName = this.meta.getTag('name=full_name') | ||||
|     let tagUsername = this.meta.getTag('name=username') | ||||
|     if (tagFullName && tagFullName.content) { | ||||
|       return tagFullName.content | ||||
|     } else if (tagUsername && tagUsername.content) { | ||||
|       return tagUsername.content | ||||
|     } else { | ||||
|       return null | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { Component, OnInit } from '@angular/core' | ||||
| import { Meta } from '@angular/platform-browser' | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-dashboard', | ||||
| @@ -8,23 +9,14 @@ import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
|   styleUrls: ['./dashboard.component.scss'], | ||||
| }) | ||||
| export class DashboardComponent { | ||||
|   constructor(public savedViewService: SavedViewService, private meta: Meta) {} | ||||
|  | ||||
|   get displayName() { | ||||
|     let tagFullName = this.meta.getTag('name=full_name') | ||||
|     let tagUsername = this.meta.getTag('name=username') | ||||
|     if (tagFullName && tagFullName.content) { | ||||
|       return tagFullName.content | ||||
|     } else if (tagUsername && tagUsername.content) { | ||||
|       return tagUsername.content | ||||
|     } else { | ||||
|       return null | ||||
|     } | ||||
|   } | ||||
|   constructor( | ||||
|     public savedViewService: SavedViewService, | ||||
|     public settingsService: SettingsService | ||||
|   ) {} | ||||
|  | ||||
|   get subtitle() { | ||||
|     if (this.displayName) { | ||||
|       return $localize`Hello ${this.displayName}, welcome to Paperless-ngx!` | ||||
|     if (this.settingsService.displayName) { | ||||
|       return $localize`Hello ${this.settingsService.displayName}, welcome to Paperless-ngx!` | ||||
|     } else { | ||||
|       return $localize`Welcome to Paperless-ngx!` | ||||
|     } | ||||
|   | ||||
| @@ -18,10 +18,7 @@ import { DocumentTypeEditDialogComponent } from '../common/edit-dialog/document- | ||||
| import { PDFDocumentProxy } from 'ng2-pdf-viewer' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { TextComponent } from '../common/input/text/text.component' | ||||
| import { | ||||
|   SettingsService, | ||||
|   SETTINGS_KEYS, | ||||
| } from 'src/app/services/settings.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms' | ||||
| import { Observable, Subject, BehaviorSubject } from 'rxjs' | ||||
| import { | ||||
| @@ -36,6 +33,7 @@ import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-su | ||||
| import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type' | ||||
| import { normalizeDateStr } from 'src/app/utils/date' | ||||
| import { QueryParamsService } from 'src/app/services/query-params.service' | ||||
| import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-document-detail', | ||||
|   | ||||
| @@ -19,12 +19,10 @@ import { | ||||
| } from '../../common/filterable-dropdown/filterable-dropdown.component' | ||||
| import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component' | ||||
| import { MatchingModel } from 'src/app/data/matching-model' | ||||
| import { | ||||
|   SettingsService, | ||||
|   SETTINGS_KEYS, | ||||
| } from 'src/app/services/settings.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { saveAs } from 'file-saver' | ||||
| import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-bulk-editor', | ||||
|   | ||||
| @@ -8,12 +8,12 @@ import { | ||||
| } from '@angular/core' | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { | ||||
|   SettingsService, | ||||
|   SETTINGS_KEYS, | ||||
| } from 'src/app/services/settings.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type' | ||||
| import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-document-card-large', | ||||
|   | ||||
| @@ -9,12 +9,10 @@ import { | ||||
| import { map } from 'rxjs/operators' | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { | ||||
|   SettingsService, | ||||
|   SETTINGS_KEYS, | ||||
| } from 'src/app/services/settings.service' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service' | ||||
| import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-document-card-small', | ||||
|   | ||||
| @@ -13,11 +13,11 @@ import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { | ||||
|   LanguageOption, | ||||
|   SettingsService, | ||||
|   SETTINGS_KEYS, | ||||
| } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms' | ||||
| import { Observable, Subscription, BehaviorSubject } from 'rxjs' | ||||
| import { Observable, Subscription, BehaviorSubject, first } from 'rxjs' | ||||
| import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-settings', | ||||
| @@ -227,10 +227,23 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|       this.settingsForm.value.notificationsConsumerSuppressOnDashboard | ||||
|     ) | ||||
|     this.settings.setLanguage(this.settingsForm.value.displayLanguage) | ||||
|     this.store.next(this.settingsForm.value) | ||||
|     this.documentListViewService.updatePageSize() | ||||
|     this.settings.updateAppearanceSettings() | ||||
|     this.toastService.showInfo($localize`Settings saved successfully.`) | ||||
|     this.settings | ||||
|       .storeSettings() | ||||
|       .pipe(first()) | ||||
|       .subscribe({ | ||||
|         next: () => { | ||||
|           this.store.next(this.settingsForm.value) | ||||
|           this.documentListViewService.updatePageSize() | ||||
|           this.settings.updateAppearanceSettings() | ||||
|           this.toastService.showInfo($localize`Settings saved successfully.`) | ||||
|         }, | ||||
|         error: (error) => { | ||||
|           this.toastService.showError( | ||||
|             $localize`An error occurred while saving settings.` | ||||
|           ) | ||||
|           console.log(error) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   get displayLanguageOptions(): LanguageOption[] { | ||||
|   | ||||
							
								
								
									
										117
									
								
								src-ui/src/app/data/paperless-uisettings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src-ui/src/app/data/paperless-uisettings.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| export interface PaperlessUiSettings { | ||||
|   user_id: number | ||||
|  | ||||
|   username: string | ||||
|  | ||||
|   display_name: string | ||||
|  | ||||
|   settings: Object | ||||
| } | ||||
|  | ||||
| export interface PaperlessUiSetting { | ||||
|   key: string | ||||
|   type: string | ||||
|   default: any | ||||
| } | ||||
|  | ||||
| export const SETTINGS_KEYS = { | ||||
|   LANGUAGE: 'language', | ||||
|   // maintain old general-settings: for backwards compatibility | ||||
|   BULK_EDIT_CONFIRMATION_DIALOGS: | ||||
|     'general-settings:bulk-edit:confirmation-dialogs', | ||||
|   BULK_EDIT_APPLY_ON_CLOSE: 'general-settings:bulk-edit:apply-on-close', | ||||
|   DOCUMENT_LIST_SIZE: 'general-settings:documentListSize', | ||||
|   DARK_MODE_USE_SYSTEM: 'general-settings:dark-mode:use-system', | ||||
|   DARK_MODE_ENABLED: 'general-settings:dark-mode:enabled', | ||||
|   DARK_MODE_THUMB_INVERTED: 'general-settings:dark-mode:thumb-inverted', | ||||
|   THEME_COLOR: 'general-settings:theme:color', | ||||
|   USE_NATIVE_PDF_VIEWER: 'general-settings:document-details:native-pdf-viewer', | ||||
|   DATE_LOCALE: 'general-settings:date-display:date-locale', | ||||
|   DATE_FORMAT: 'general-settings:date-display:date-format', | ||||
|   NOTIFICATIONS_CONSUMER_NEW_DOCUMENT: | ||||
|     'general-settings:notifications:consumer-new-documents', | ||||
|   NOTIFICATIONS_CONSUMER_SUCCESS: | ||||
|     'general-settings:notifications:consumer-success', | ||||
|   NOTIFICATIONS_CONSUMER_FAILED: | ||||
|     'general-settings:notifications:consumer-failed', | ||||
|   NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD: | ||||
|     'general-settings:notifications:consumer-suppress-on-dashboard', | ||||
| } | ||||
|  | ||||
| export const SETTINGS: PaperlessUiSetting[] = [ | ||||
|   { | ||||
|     key: SETTINGS_KEYS.LANGUAGE, | ||||
|     type: 'string', | ||||
|     default: '', | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS, | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE, | ||||
|     type: 'boolean', | ||||
|     default: false, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.DOCUMENT_LIST_SIZE, | ||||
|     type: 'number', | ||||
|     default: 50, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.DARK_MODE_ENABLED, | ||||
|     type: 'boolean', | ||||
|     default: false, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED, | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.THEME_COLOR, | ||||
|     type: 'string', | ||||
|     default: '', | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, | ||||
|     type: 'boolean', | ||||
|     default: false, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.DATE_LOCALE, | ||||
|     type: 'string', | ||||
|     default: '', | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.DATE_FORMAT, | ||||
|     type: 'string', | ||||
|     default: 'mediumDate', | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT, | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS, | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED, | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD, | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
| ] | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { DatePipe } from '@angular/common' | ||||
| import { Inject, LOCALE_ID, Pipe, PipeTransform } from '@angular/core' | ||||
| import { SettingsService, SETTINGS_KEYS } from '../services/settings.service' | ||||
| import { SETTINGS_KEYS } from '../data/paperless-uisettings' | ||||
| import { SettingsService } from '../services/settings.service' | ||||
| import { normalizeDateStr } from '../utils/date' | ||||
|  | ||||
| const FORMAT_TO_ISO_FORMAT = { | ||||
|   | ||||
| @@ -8,9 +8,10 @@ import { | ||||
| } from '../data/filter-rule' | ||||
| import { PaperlessDocument } from '../data/paperless-document' | ||||
| import { PaperlessSavedView } from '../data/paperless-saved-view' | ||||
| import { SETTINGS_KEYS } from '../data/paperless-uisettings' | ||||
| import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys' | ||||
| import { DocumentService, DOCUMENT_SORT_FIELDS } from './rest/document.service' | ||||
| import { SettingsService, SETTINGS_KEYS } from './settings.service' | ||||
| import { SettingsService } from './settings.service' | ||||
|  | ||||
| /** | ||||
|  * Captures the current state of the list view. | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import { DOCUMENT } from '@angular/common' | ||||
| import { HttpClient } from '@angular/common/http' | ||||
| import { | ||||
|   Inject, | ||||
|   Injectable, | ||||
| @@ -9,17 +10,19 @@ import { | ||||
| } from '@angular/core' | ||||
| import { Meta } from '@angular/platform-browser' | ||||
| import { CookieService } from 'ngx-cookie-service' | ||||
| import { first, Observable, tap } from 'rxjs' | ||||
| import { | ||||
|   BRIGHTNESS, | ||||
|   estimateBrightnessForColor, | ||||
|   hexToHsl, | ||||
| } from 'src/app/utils/color' | ||||
|  | ||||
| export interface PaperlessSettings { | ||||
|   key: string | ||||
|   type: string | ||||
|   default: any | ||||
| } | ||||
| import { environment } from 'src/environments/environment' | ||||
| import { | ||||
|   PaperlessUiSettings, | ||||
|   SETTINGS, | ||||
|   SETTINGS_KEYS, | ||||
| } from '../data/paperless-uisettings' | ||||
| import { ToastService } from './toast.service' | ||||
|  | ||||
| export interface LanguageOption { | ||||
|   code: string | ||||
| @@ -32,89 +35,42 @@ export interface LanguageOption { | ||||
|   dateInputFormat?: string | ||||
| } | ||||
|  | ||||
| export const SETTINGS_KEYS = { | ||||
|   BULK_EDIT_CONFIRMATION_DIALOGS: | ||||
|     'general-settings:bulk-edit:confirmation-dialogs', | ||||
|   BULK_EDIT_APPLY_ON_CLOSE: 'general-settings:bulk-edit:apply-on-close', | ||||
|   DOCUMENT_LIST_SIZE: 'general-settings:documentListSize', | ||||
|   DARK_MODE_USE_SYSTEM: 'general-settings:dark-mode:use-system', | ||||
|   DARK_MODE_ENABLED: 'general-settings:dark-mode:enabled', | ||||
|   DARK_MODE_THUMB_INVERTED: 'general-settings:dark-mode:thumb-inverted', | ||||
|   THEME_COLOR: 'general-settings:theme:color', | ||||
|   USE_NATIVE_PDF_VIEWER: 'general-settings:document-details:native-pdf-viewer', | ||||
|   DATE_LOCALE: 'general-settings:date-display:date-locale', | ||||
|   DATE_FORMAT: 'general-settings:date-display:date-format', | ||||
|   NOTIFICATIONS_CONSUMER_NEW_DOCUMENT: | ||||
|     'general-settings:notifications:consumer-new-documents', | ||||
|   NOTIFICATIONS_CONSUMER_SUCCESS: | ||||
|     'general-settings:notifications:consumer-success', | ||||
|   NOTIFICATIONS_CONSUMER_FAILED: | ||||
|     'general-settings:notifications:consumer-failed', | ||||
|   NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD: | ||||
|     'general-settings:notifications:consumer-suppress-on-dashboard', | ||||
| } | ||||
|  | ||||
| const SETTINGS: PaperlessSettings[] = [ | ||||
|   { | ||||
|     key: SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS, | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE, | ||||
|     type: 'boolean', | ||||
|     default: false, | ||||
|   }, | ||||
|   { key: SETTINGS_KEYS.DOCUMENT_LIST_SIZE, type: 'number', default: 50 }, | ||||
|   { key: SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, type: 'boolean', default: true }, | ||||
|   { key: SETTINGS_KEYS.DARK_MODE_ENABLED, type: 'boolean', default: false }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED, | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
|   { key: SETTINGS_KEYS.THEME_COLOR, type: 'string', default: '' }, | ||||
|   { key: SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, type: 'boolean', default: false }, | ||||
|   { key: SETTINGS_KEYS.DATE_LOCALE, type: 'string', default: '' }, | ||||
|   { key: SETTINGS_KEYS.DATE_FORMAT, type: 'string', default: 'mediumDate' }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT, | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS, | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED, | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD, | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
| ] | ||||
|  | ||||
| @Injectable({ | ||||
|   providedIn: 'root', | ||||
| }) | ||||
| export class SettingsService { | ||||
|   private renderer: Renderer2 | ||||
|   protected baseUrl: string = environment.apiBaseUrl + 'ui_settings/' | ||||
|  | ||||
|   private settings: Object = {} | ||||
|  | ||||
|   public displayName: string | ||||
|  | ||||
|   constructor( | ||||
|     private rendererFactory: RendererFactory2, | ||||
|     rendererFactory: RendererFactory2, | ||||
|     @Inject(DOCUMENT) private document, | ||||
|     private cookieService: CookieService, | ||||
|     private meta: Meta, | ||||
|     @Inject(LOCALE_ID) private localeId: string | ||||
|     @Inject(LOCALE_ID) private localeId: string, | ||||
|     protected http: HttpClient, | ||||
|     private toastService: ToastService | ||||
|   ) { | ||||
|     this.renderer = rendererFactory.createRenderer(null, null) | ||||
|   } | ||||
|  | ||||
|     this.updateAppearanceSettings() | ||||
|   // this is called by the app initializer in app.module | ||||
|   public initializeSettings(): Observable<PaperlessUiSettings> { | ||||
|     return this.http.get<PaperlessUiSettings>(this.baseUrl).pipe( | ||||
|       first(), | ||||
|       tap((uisettings) => { | ||||
|         Object.assign(this.settings, uisettings.settings) | ||||
|         this.maybeMigrateSettings() | ||||
|         // to update lang cookie | ||||
|         if (this.settings['language']?.length) | ||||
|           this.setLanguage(this.settings['language']) | ||||
|         this.displayName = uisettings.display_name.trim() | ||||
|       }) | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   public updateAppearanceSettings( | ||||
| @@ -333,11 +289,13 @@ export class SettingsService { | ||||
|   } | ||||
|  | ||||
|   getLanguage(): string { | ||||
|     return this.cookieService.get(this.getLanguageCookieName()) | ||||
|     return this.get(SETTINGS_KEYS.LANGUAGE) | ||||
|   } | ||||
|  | ||||
|   setLanguage(language: string) { | ||||
|     if (language) { | ||||
|     this.set(SETTINGS_KEYS.LANGUAGE, language) | ||||
|     if (language?.length) { | ||||
|       // for Django | ||||
|       this.cookieService.set(this.getLanguageCookieName(), language) | ||||
|     } else { | ||||
|       this.cookieService.delete(this.getLanguageCookieName()) | ||||
| @@ -362,7 +320,16 @@ export class SettingsService { | ||||
|       return null | ||||
|     } | ||||
|  | ||||
|     let value = localStorage.getItem(key) | ||||
|     let value = null | ||||
|     // parse key:key:key into nested object | ||||
|     const keys = key.replace('general-settings:', '').split(':') | ||||
|     let settingObj = this.settings | ||||
|     keys.forEach((keyPart, index) => { | ||||
|       keyPart = keyPart.replace(/-/g, '_') | ||||
|       if (!settingObj.hasOwnProperty(keyPart)) return | ||||
|       if (index == keys.length - 1) value = settingObj[keyPart] | ||||
|       else settingObj = settingObj[keyPart] | ||||
|     }) | ||||
|  | ||||
|     if (value != null) { | ||||
|       switch (setting.type) { | ||||
| @@ -381,10 +348,57 @@ export class SettingsService { | ||||
|   } | ||||
|  | ||||
|   set(key: string, value: any) { | ||||
|     localStorage.setItem(key, value.toString()) | ||||
|     // parse key:key:key into nested object | ||||
|     let settingObj = this.settings | ||||
|     const keys = key.replace('general-settings:', '').split(':') | ||||
|     keys.forEach((keyPart, index) => { | ||||
|       keyPart = keyPart.replace(/-/g, '_') | ||||
|       if (!settingObj.hasOwnProperty(keyPart)) settingObj[keyPart] = {} | ||||
|       if (index == keys.length - 1) settingObj[keyPart] = value | ||||
|       else settingObj = settingObj[keyPart] | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   unset(key: string) { | ||||
|     localStorage.removeItem(key) | ||||
|   storeSettings(): Observable<any> { | ||||
|     return this.http.post(this.baseUrl, { settings: this.settings }) | ||||
|   } | ||||
|  | ||||
|   maybeMigrateSettings() { | ||||
|     if ( | ||||
|       !this.settings.hasOwnProperty('documentListSize') && | ||||
|       localStorage.getItem(SETTINGS_KEYS.DOCUMENT_LIST_SIZE) | ||||
|     ) { | ||||
|       // lets migrate | ||||
|       const successMessage = $localize`Successfully completed one-time migratration of settings to the database!` | ||||
|       const errorMessage = $localize`Unable to migrate settings to the database, please try saving manually.` | ||||
|  | ||||
|       try { | ||||
|         for (const setting in SETTINGS_KEYS) { | ||||
|           const key = SETTINGS_KEYS[setting] | ||||
|           const value = localStorage.getItem(key) | ||||
|           this.set(key, value) | ||||
|         } | ||||
|         this.set( | ||||
|           SETTINGS_KEYS.LANGUAGE, | ||||
|           this.cookieService.get(this.getLanguageCookieName()) | ||||
|         ) | ||||
|       } catch (error) { | ||||
|         this.toastService.showError(errorMessage) | ||||
|         console.log(error) | ||||
|       } | ||||
|  | ||||
|       this.storeSettings() | ||||
|         .pipe(first()) | ||||
|         .subscribe({ | ||||
|           next: () => { | ||||
|             this.updateAppearanceSettings() | ||||
|             this.toastService.showInfo(successMessage) | ||||
|           }, | ||||
|           error: (e) => { | ||||
|             this.toastService.showError(errorMessage) | ||||
|             console.log(e) | ||||
|           }, | ||||
|         }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -18,6 +18,7 @@ from documents.models import DocumentType | ||||
| from documents.models import SavedView | ||||
| from documents.models import SavedViewFilterRule | ||||
| from documents.models import Tag | ||||
| from documents.models import UiSettings | ||||
| from documents.settings import EXPORTER_ARCHIVE_NAME | ||||
| from documents.settings import EXPORTER_FILE_NAME | ||||
| from documents.settings import EXPORTER_THUMBNAIL_NAME | ||||
| @@ -112,8 +113,8 @@ class Command(BaseCommand): | ||||
|                 map(lambda f: os.path.abspath(os.path.join(root, f)), files), | ||||
|             ) | ||||
|  | ||||
|         # 2. Create manifest, containing all correspondents, types, tags and | ||||
|         # documents | ||||
|         # 2. Create manifest, containing all correspondents, types, tags, | ||||
|         # documents and ui_settings | ||||
|         with transaction.atomic(): | ||||
|             manifest = json.loads( | ||||
|                 serializers.serialize("json", Correspondent.objects.all()), | ||||
| @@ -150,6 +151,10 @@ class Command(BaseCommand): | ||||
|  | ||||
|             manifest += json.loads(serializers.serialize("json", User.objects.all())) | ||||
|  | ||||
|             manifest += json.loads( | ||||
|                 serializers.serialize("json", UiSettings.objects.all()), | ||||
|             ) | ||||
|  | ||||
|         # 3. Export files from each document | ||||
|         for index, document_dict in tqdm.tqdm( | ||||
|             enumerate(document_manifest), | ||||
|   | ||||
							
								
								
									
										39
									
								
								src/documents/migrations/1019_uisettings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/documents/migrations/1019_uisettings.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| # Generated by Django 4.0.4 on 2022-05-07 05:10 | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||||
|         ("documents", "1018_alter_savedviewfilterrule_value"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name="UiSettings", | ||||
|             fields=[ | ||||
|                 ( | ||||
|                     "id", | ||||
|                     models.AutoField( | ||||
|                         auto_created=True, | ||||
|                         primary_key=True, | ||||
|                         serialize=False, | ||||
|                         verbose_name="ID", | ||||
|                     ), | ||||
|                 ), | ||||
|                 ("settings", models.JSONField(null=True)), | ||||
|                 ( | ||||
|                     "user", | ||||
|                     models.OneToOneField( | ||||
|                         on_delete=django.db.models.deletion.CASCADE, | ||||
|                         related_name="ui_settings", | ||||
|                         to=settings.AUTH_USER_MODEL, | ||||
|                     ), | ||||
|                 ), | ||||
|             ], | ||||
|         ), | ||||
|     ] | ||||
| @@ -465,3 +465,17 @@ class FileInfo: | ||||
|                 cls._mangle_property(properties, "created") | ||||
|                 cls._mangle_property(properties, "title") | ||||
|                 return cls(**properties) | ||||
|  | ||||
|  | ||||
| # Extending User Model Using a One-To-One Link | ||||
| class UiSettings(models.Model): | ||||
|  | ||||
|     user = models.OneToOneField( | ||||
|         User, | ||||
|         on_delete=models.CASCADE, | ||||
|         related_name="ui_settings", | ||||
|     ) | ||||
|     settings = models.JSONField(null=True) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.user.username | ||||
|   | ||||
| @@ -15,6 +15,7 @@ from .models import MatchingModel | ||||
| from .models import SavedView | ||||
| from .models import SavedViewFilterRule | ||||
| from .models import Tag | ||||
| from .models import UiSettings | ||||
| from .parsers import is_mime_type_supported | ||||
|  | ||||
|  | ||||
| @@ -505,3 +506,24 @@ class BulkDownloadSerializer(DocumentListSerializer): | ||||
|             "bzip2": zipfile.ZIP_BZIP2, | ||||
|             "lzma": zipfile.ZIP_LZMA, | ||||
|         }[compression] | ||||
|  | ||||
|  | ||||
| class UiSettingsViewSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = UiSettings | ||||
|         depth = 1 | ||||
|         fields = [ | ||||
|             "id", | ||||
|             "settings", | ||||
|         ] | ||||
|  | ||||
|     def update(self, instance, validated_data): | ||||
|         super().update(instance, validated_data) | ||||
|         return instance | ||||
|  | ||||
|     def create(self, validated_data): | ||||
|         ui_settings = UiSettings.objects.update_or_create( | ||||
|             user=validated_data.get("user"), | ||||
|             defaults={"settings": validated_data.get("settings", None)}, | ||||
|         ) | ||||
|         return ui_settings | ||||
|   | ||||
| @@ -9,8 +9,6 @@ | ||||
|   <title>Paperless-ngx</title> | ||||
|   <base href="{% url 'base' %}"> | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 	<meta name="username" content="{{username}}"> | ||||
| 	<meta name="full_name" content="{{full_name}}"> | ||||
| 	<meta name="cookie_prefix" content="{{cookie_prefix}}"> | ||||
| 	<meta name="robots" content="noindex,nofollow"> | ||||
|   	<link rel="icon" type="image/x-icon" href="favicon.ico"> | ||||
|   | ||||
| @@ -27,6 +27,7 @@ from documents.models import DocumentType | ||||
| from documents.models import MatchingModel | ||||
| from documents.models import SavedView | ||||
| from documents.models import Tag | ||||
| from documents.models import UiSettings | ||||
| from documents.tests.utils import DirectoriesMixin | ||||
| from paperless import version | ||||
| from rest_framework.test import APITestCase | ||||
| @@ -1398,6 +1399,41 @@ class TestDocumentApiV2(DirectoriesMixin, APITestCase): | ||||
|             "#000000", | ||||
|         ) | ||||
|  | ||||
|     def test_ui_settings(self): | ||||
|         test_user = User.objects.create_superuser(username="test") | ||||
|         self.client.force_login(user=test_user) | ||||
|  | ||||
|         response = self.client.get("/api/ui_settings/", format="json") | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertDictEqual( | ||||
|             response.data["settings"], | ||||
|             {}, | ||||
|         ) | ||||
|  | ||||
|         settings = { | ||||
|             "settings": { | ||||
|                 "dark_mode": { | ||||
|                     "enabled": True, | ||||
|                 }, | ||||
|             }, | ||||
|         } | ||||
|  | ||||
|         response = self.client.post( | ||||
|             "/api/ui_settings/", | ||||
|             json.dumps(settings), | ||||
|             content_type="application/json", | ||||
|         ) | ||||
|  | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|         response = self.client.get("/api/ui_settings/", format="json") | ||||
|  | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertDictEqual( | ||||
|             response.data["settings"], | ||||
|             settings["settings"], | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class TestBulkEdit(DirectoriesMixin, APITestCase): | ||||
|     def setUp(self): | ||||
|   | ||||
| @@ -11,6 +11,7 @@ from unicodedata import normalize | ||||
| from urllib.parse import quote | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.contrib.auth.models import User | ||||
| from django.db.models import Case | ||||
| from django.db.models import Count | ||||
| from django.db.models import IntegerField | ||||
| @@ -74,6 +75,7 @@ from .serialisers import PostDocumentSerializer | ||||
| from .serialisers import SavedViewSerializer | ||||
| from .serialisers import TagSerializer | ||||
| from .serialisers import TagSerializerVersion1 | ||||
| from .serialisers import UiSettingsViewSerializer | ||||
|  | ||||
| logger = logging.getLogger("paperless.api") | ||||
|  | ||||
| @@ -81,12 +83,18 @@ logger = logging.getLogger("paperless.api") | ||||
| class IndexView(TemplateView): | ||||
|     template_name = "index.html" | ||||
|  | ||||
|     def get_language(self): | ||||
|     def get_frontend_language(self): | ||||
|         if hasattr( | ||||
|             self.request.user, | ||||
|             "ui_settings", | ||||
|         ) and self.request.user.ui_settings.settings.get("language"): | ||||
|             lang = self.request.user.ui_settings.settings.get("language") | ||||
|         else: | ||||
|             lang = get_language() | ||||
|         # This is here for the following reason: | ||||
|         # Django identifies languages in the form "en-us" | ||||
|         # However, angular generates locales as "en-US". | ||||
|         # this translates between these two forms. | ||||
|         lang = get_language() | ||||
|         if "-" in lang: | ||||
|             first = lang[: lang.index("-")] | ||||
|             second = lang[lang.index("-") + 1 :] | ||||
| @@ -99,16 +107,18 @@ class IndexView(TemplateView): | ||||
|         context["cookie_prefix"] = settings.COOKIE_PREFIX | ||||
|         context["username"] = self.request.user.username | ||||
|         context["full_name"] = self.request.user.get_full_name() | ||||
|         context["styles_css"] = f"frontend/{self.get_language()}/styles.css" | ||||
|         context["runtime_js"] = f"frontend/{self.get_language()}/runtime.js" | ||||
|         context["polyfills_js"] = f"frontend/{self.get_language()}/polyfills.js" | ||||
|         context["main_js"] = f"frontend/{self.get_language()}/main.js" | ||||
|         context["styles_css"] = f"frontend/{self.get_frontend_language()}/styles.css" | ||||
|         context["runtime_js"] = f"frontend/{self.get_frontend_language()}/runtime.js" | ||||
|         context[ | ||||
|             "polyfills_js" | ||||
|         ] = f"frontend/{self.get_frontend_language()}/polyfills.js" | ||||
|         context["main_js"] = f"frontend/{self.get_frontend_language()}/main.js" | ||||
|         context[ | ||||
|             "webmanifest" | ||||
|         ] = f"frontend/{self.get_language()}/manifest.webmanifest"  # noqa: E501 | ||||
|         ] = f"frontend/{self.get_frontend_language()}/manifest.webmanifest"  # noqa: E501 | ||||
|         context[ | ||||
|             "apple_touch_icon" | ||||
|         ] = f"frontend/{self.get_language()}/apple-touch-icon.png"  # noqa: E501 | ||||
|         ] = f"frontend/{self.get_frontend_language()}/apple-touch-icon.png"  # noqa: E501 | ||||
|         return context | ||||
|  | ||||
|  | ||||
| @@ -717,3 +727,41 @@ class RemoteVersionView(GenericAPIView): | ||||
|                 "feature_is_set": feature_is_set, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class UiSettingsView(GenericAPIView): | ||||
|  | ||||
|     permission_classes = (IsAuthenticated,) | ||||
|     serializer_class = UiSettingsViewSerializer | ||||
|  | ||||
|     def get(self, request, format=None): | ||||
|         serializer = self.get_serializer(data=request.data) | ||||
|         serializer.is_valid(raise_exception=True) | ||||
|  | ||||
|         user = User.objects.get(pk=request.user.id) | ||||
|         displayname = user.username | ||||
|         if user.first_name or user.last_name: | ||||
|             displayname = " ".join([user.first_name, user.last_name]) | ||||
|         settings = {} | ||||
|         if hasattr(user, "ui_settings"): | ||||
|             settings = user.ui_settings.settings | ||||
|         return Response( | ||||
|             { | ||||
|                 "user_id": user.id, | ||||
|                 "username": user.username, | ||||
|                 "display_name": displayname, | ||||
|                 "settings": settings, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     def post(self, request, format=None): | ||||
|         serializer = self.get_serializer(data=request.data) | ||||
|         serializer.is_valid(raise_exception=True) | ||||
|  | ||||
|         serializer.save(user=self.request.user) | ||||
|  | ||||
|         return Response( | ||||
|             { | ||||
|                 "success": True, | ||||
|             }, | ||||
|         ) | ||||
|   | ||||
| @@ -20,6 +20,7 @@ from documents.views import SearchAutoCompleteView | ||||
| from documents.views import SelectionDataView | ||||
| from documents.views import StatisticsView | ||||
| from documents.views import TagViewSet | ||||
| from documents.views import UiSettingsView | ||||
| from documents.views import UnifiedSearchViewSet | ||||
| from paperless.consumers import StatusConsumer | ||||
| from paperless.views import FaviconView | ||||
| @@ -78,6 +79,11 @@ urlpatterns = [ | ||||
|                     RemoteVersionView.as_view(), | ||||
|                     name="remoteversion", | ||||
|                 ), | ||||
|                 re_path( | ||||
|                     r"^ui_settings/", | ||||
|                     UiSettingsView.as_view(), | ||||
|                     name="ui_settings", | ||||
|                 ), | ||||
|                 path("token/", views.obtain_auth_token), | ||||
|             ] | ||||
|             + api_router.urls, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon