Merge pull request #919 from paperless-ngx/feature-settings-saved-to-db

Feature: frontend settings saved to database
This commit is contained in:
shamoon 2022-05-18 11:33:17 -07:00 committed by GitHub
commit 998ca64c1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 639 additions and 260 deletions

View File

@ -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 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. 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>`_. 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. 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``. Extract the archive to a place from where you wish to execute it, such as ``/opt/paperless``.

View 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
}
}
}

View File

@ -2,6 +2,9 @@ describe('document-detail', () => {
beforeEach(() => { beforeEach(() => {
this.modifiedDocuments = [] this.modifiedDocuments = []
cy.intercept('http://localhost:8000/api/ui_settings/', {
fixture: 'ui_settings/settings.json',
})
cy.fixture('documents/documents.json').then((documentsJson) => { cy.fixture('documents/documents.json').then((documentsJson) => {
cy.intercept('GET', 'http://localhost:8000/api/documents/1/', (req) => { cy.intercept('GET', 'http://localhost:8000/api/documents/1/', (req) => {
let response = { ...documentsJson } let response = { ...documentsJson }

View File

@ -3,6 +3,9 @@ describe('documents-list', () => {
this.bulkEdits = {} this.bulkEdits = {}
// mock API methods // mock API methods
cy.intercept('http://localhost:8000/api/ui_settings/', {
fixture: 'ui_settings/settings.json',
})
cy.fixture('documents/documents.json').then((documentsJson) => { cy.fixture('documents/documents.json').then((documentsJson) => {
// bulk edit // bulk edit
cy.intercept( cy.intercept(

View File

@ -1,5 +1,8 @@
describe('manage', () => { describe('manage', () => {
beforeEach(() => { beforeEach(() => {
cy.intercept('http://localhost:8000/api/ui_settings/', {
fixture: 'ui_settings/settings.json',
})
cy.intercept('http://localhost:8000/api/correspondents/*', { cy.intercept('http://localhost:8000/api/correspondents/*', {
fixture: 'correspondents/correspondents.json', fixture: 'correspondents/correspondents.json',
}) })

View File

@ -3,45 +3,53 @@ describe('settings', () => {
this.modifiedViews = [] this.modifiedViews = []
// mock API methods // mock API methods
cy.fixture('saved_views/savedviews.json').then((savedViewsJson) => { cy.intercept('http://localhost:8000/api/ui_settings/', {
// saved views PATCH fixture: 'ui_settings/settings.json',
cy.intercept( }).then(() => {
'PATCH', cy.fixture('saved_views/savedviews.json').then((savedViewsJson) => {
'http://localhost:8000/api/saved_views/*', // saved views PATCH
(req) => { cy.intercept(
this.modifiedViews.push(req.body) // store this for later 'PATCH',
req.reply({ result: 'OK' }) '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) => { cy.intercept(
let response = { ...savedViewsJson } 'GET',
if (this.modifiedViews.length) { 'http://localhost:8000/api/saved_views/*',
response.results = response.results.map((v) => { (req) => {
if (this.modifiedViews.find((mv) => mv.id == v.id)) let response = { ...savedViewsJson }
v = this.modifiedViews.find((mv) => mv.id == v.id) if (this.modifiedViews.length) {
return v 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) req.reply(response)
}).as('savedViews') }
}) ).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)
}) })
})
cy.intercept('http://localhost:8000/api/documents/1/metadata/', { cy.fixture('documents/documents.json').then((documentsJson) => {
fixture: 'documents/1/metadata.json', 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/', { cy.intercept('http://localhost:8000/api/documents/1/metadata/', {
fixture: 'documents/1/suggestions.json', fixture: 'documents/1/metadata.json',
})
cy.intercept('http://localhost:8000/api/documents/1/suggestions/', {
fixture: 'documents/1/suggestions.json',
})
}) })
cy.viewport(1024, 1024) cy.viewport(1024, 1024)

View File

@ -256,21 +256,21 @@
<source>Document added</source> <source>Document added</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context> <context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">72</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9204248378636247318" datatype="html"> <trans-unit id="9204248378636247318" datatype="html">
<source>Document <x id="PH" equiv-text="status.filename"/> was added to paperless.</source> <source>Document <x id="PH" equiv-text="status.filename"/> was added to paperless.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context> <context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">73</context> <context context-type="linenumber">74</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1931214133925051574" datatype="html"> <trans-unit id="1931214133925051574" datatype="html">
<source>Open document</source> <source>Open document</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context> <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>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context> <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> <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-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context> <context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">89</context> <context context-type="linenumber">90</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1710712016675379662" datatype="html"> <trans-unit id="1710712016675379662" datatype="html">
<source>New document detected</source> <source>New document detected</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context> <context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">103</context> <context context-type="linenumber">104</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="587031278561344416" datatype="html"> <trans-unit id="587031278561344416" datatype="html">
<source>Document <x id="PH" equiv-text="status.filename"/> is being processed by paperless.</source> <source>Document <x id="PH" equiv-text="status.filename"/> is being processed by paperless.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context> <context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">105</context> <context context-type="linenumber">106</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5749300816154614125" datatype="html"> <trans-unit id="5749300816154614125" datatype="html">
<source>Initiating upload...</source> <source>Initiating upload...</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context> <context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">140</context> <context context-type="linenumber">141</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2173456130768795374" datatype="html"> <trans-unit id="2173456130768795374" datatype="html">
@ -321,7 +321,7 @@
</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="{{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">34</context> <context context-type="linenumber">34</context>
@ -368,7 +368,7 @@
</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>
<context context-type="linenumber">68</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/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
@ -551,15 +551,15 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6371576811194810854" datatype="html"> <trans-unit id="6371576811194810854" datatype="html">
@ -942,17 +942,17 @@
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5412339817978503936" datatype="html"> <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-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">27</context> <context context-type="linenumber">19</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="795745990148149834" datatype="html"> <trans-unit id="795745990148149834" datatype="html">
<source>Welcome to Paperless-ngx!</source> <source>Welcome to Paperless-ngx!</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">29</context> <context context-type="linenumber">21</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2946624699882754313" datatype="html"> <trans-unit id="2946624699882754313" datatype="html">
@ -1095,7 +1095,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
<note priority="1" from="description">this string is used to separate processing, failed and added on the file upload widget</note> <note priority="1" from="description">this string is used to separate processing, failed and added on the file upload widget</note>
</trans-unit> </trans-unit>
@ -1458,7 +1458,7 @@
<source>Confirm delete</source> <source>Confirm delete</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <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>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> <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 &quot;<x id="PH" equiv-text="this.document.title"/>&quot;?</source> <source>Do you really want to delete document &quot;<x id="PH" equiv-text="this.document.title"/>&quot;?</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6691075929777935948" datatype="html"> <trans-unit id="6691075929777935948" datatype="html">
<source>The files for this document will be deleted permanently. This operation cannot be undone.</source> <source>The files for this document will be deleted permanently. This operation cannot be undone.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="719892092227206532" datatype="html"> <trans-unit id="719892092227206532" datatype="html">
<source>Delete document</source> <source>Delete document</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1844801255494293730" datatype="html"> <trans-unit id="1844801255494293730" datatype="html">
<source>Error deleting document: <x id="PH" equiv-text="JSON.stringify(error)"/></source> <source>Error deleting document: <x id="PH" equiv-text="JSON.stringify(error)"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6857598786757174736" datatype="html"> <trans-unit id="6857598786757174736" datatype="html">
@ -1560,25 +1560,25 @@
)"/></source> )"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7894972847287473517" datatype="html"> <trans-unit id="7894972847287473517" datatype="html">
<source>&quot;<x id="PH" equiv-text="items[0].name"/>&quot;</source> <source>&quot;<x id="PH" equiv-text="items[0].name"/>&quot;</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8639884465898458690" datatype="html"> <trans-unit id="8639884465898458690" datatype="html">
<source>&quot;<x id="PH" equiv-text="items[0].name"/>&quot; and &quot;<x id="PH_1" equiv-text="items[1].name"/>&quot;</source> <source>&quot;<x id="PH" equiv-text="items[0].name"/>&quot; and &quot;<x id="PH_1" equiv-text="items[1].name"/>&quot;</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
<note priority="1" from="description">This is for messages like &apos;modify &quot;tag1&quot; and &quot;tag2&quot;&apos;</note> <note priority="1" from="description">This is for messages like &apos;modify &quot;tag1&quot; and &quot;tag2&quot;&apos;</note>
</trans-unit> </trans-unit>
@ -1586,7 +1586,7 @@
<source><x id="PH" equiv-text="list"/> and &quot;<x id="PH_1" equiv-text="items[items.length - 1].name"/>&quot;</source> <source><x id="PH" equiv-text="list"/> and &quot;<x id="PH_1" equiv-text="items[items.length - 1].name"/>&quot;</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
<note priority="1" from="description">this is for messages like &apos;modify &quot;tag1&quot;, &quot;tag2&quot; and &quot;tag3&quot;&apos;</note> <note priority="1" from="description">this is for messages like &apos;modify &quot;tag1&quot;, &quot;tag2&quot; and &quot;tag3&quot;&apos;</note>
</trans-unit> </trans-unit>
@ -1594,14 +1594,14 @@
<source>Confirm tags assignment</source> <source>Confirm tags assignment</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6619516195038467207" datatype="html"> <trans-unit id="6619516195038467207" datatype="html">
<source>This operation will add the tag &quot;<x id="PH" equiv-text="tag.name"/>&quot; to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source> <source>This operation will add the tag &quot;<x id="PH" equiv-text="tag.name"/>&quot; to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1894412783609570695" datatype="html"> <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> )"/> to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7181166515756808573" datatype="html"> <trans-unit id="7181166515756808573" datatype="html">
<source>This operation will remove the tag &quot;<x id="PH" equiv-text="tag.name"/>&quot; from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source> <source>This operation will remove the tag &quot;<x id="PH" equiv-text="tag.name"/>&quot; from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3819792277998068944" datatype="html"> <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> )"/> from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2739066218579571288" datatype="html"> <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> )"/> on <x id="PH_2" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2996713129519325161" datatype="html"> <trans-unit id="2996713129519325161" datatype="html">
<source>Confirm correspondent assignment</source> <source>Confirm correspondent assignment</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6900893559485781849" datatype="html"> <trans-unit id="6900893559485781849" datatype="html">
<source>This operation will assign the correspondent &quot;<x id="PH" equiv-text="correspondent.name"/>&quot; to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source> <source>This operation will assign the correspondent &quot;<x id="PH" equiv-text="correspondent.name"/>&quot; to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1257522660364398440" datatype="html"> <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> <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-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5393409374423140648" datatype="html"> <trans-unit id="5393409374423140648" datatype="html">
<source>Confirm document type assignment</source> <source>Confirm document type assignment</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="332180123895325027" datatype="html"> <trans-unit id="332180123895325027" datatype="html">
<source>This operation will assign the document type &quot;<x id="PH" equiv-text="documentType.name"/>&quot; to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source> <source>This operation will assign the document type &quot;<x id="PH" equiv-text="documentType.name"/>&quot; to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2236642492594872779" datatype="html"> <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> <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-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="749430623564850405" datatype="html"> <trans-unit id="749430623564850405" datatype="html">
<source>Delete confirm</source> <source>Delete confirm</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4303174930844518780" datatype="html"> <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> <source>This operation will permanently delete <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5641451190833696892" datatype="html"> <trans-unit id="5641451190833696892" datatype="html">
<source>This operation cannot be undone.</source> <source>This operation cannot be undone.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6734339521247847366" datatype="html"> <trans-unit id="6734339521247847366" datatype="html">
<source>Delete document(s)</source> <source>Delete document(s)</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8076495233090006322" datatype="html"> <trans-unit id="8076495233090006322" datatype="html">
@ -1913,14 +1913,14 @@
<source>View &quot;<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>&quot; saved successfully.</source> <source>View &quot;<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>&quot; saved successfully.</source>
<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>
<context context-type="linenumber">197</context> <context context-type="linenumber">198</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6837554170707123455" datatype="html"> <trans-unit id="6837554170707123455" datatype="html">
<source>View &quot;<x id="PH" equiv-text="savedView.name"/>&quot; created successfully.</source> <source>View &quot;<x id="PH" equiv-text="savedView.name"/>&quot; created successfully.</source>
<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>
<context context-type="linenumber">227</context> <context context-type="linenumber">228</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6849725902312323996" datatype="html"> <trans-unit id="6849725902312323996" datatype="html">
@ -2512,21 +2512,28 @@
<source>Settings saved successfully.</source> <source>Settings saved successfully.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6839066544204061364" datatype="html"> <trans-unit id="6839066544204061364" datatype="html">
<source>Use system language</source> <source>Use system language</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7729897675462249787" datatype="html"> <trans-unit id="7729897675462249787" datatype="html">
<source>Use date format of display language</source> <source>Use date format of display language</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8488620293789898901" datatype="html"> <trans-unit id="8488620293789898901" datatype="html">
@ -2535,7 +2542,7 @@
)"/></source> )"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6402703264596649214" datatype="html"> <trans-unit id="6402703264596649214" datatype="html">
@ -2853,154 +2860,168 @@
<source>English (US)</source> <source>English (US)</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3098941349689899577" datatype="html"> <trans-unit id="3098941349689899577" datatype="html">
<source>Belarusian</source> <source>Belarusian</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2719780722934172508" datatype="html"> <trans-unit id="2719780722934172508" datatype="html">
<source>Czech</source> <source>Czech</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2924289692679201020" datatype="html"> <trans-unit id="2924289692679201020" datatype="html">
<source>Danish</source> <source>Danish</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1858110241312746425" datatype="html"> <trans-unit id="1858110241312746425" datatype="html">
<source>German</source> <source>German</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6987083569809053351" datatype="html"> <trans-unit id="6987083569809053351" datatype="html">
<source>English (GB)</source> <source>English (GB)</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5190825892106392539" datatype="html"> <trans-unit id="5190825892106392539" datatype="html">
<source>Spanish</source> <source>Spanish</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7633754075223722162" datatype="html"> <trans-unit id="7633754075223722162" datatype="html">
<source>French</source> <source>French</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2935232983274991580" datatype="html"> <trans-unit id="2935232983274991580" datatype="html">
<source>Italian</source> <source>Italian</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1334425850005897370" datatype="html"> <trans-unit id="1334425850005897370" datatype="html">
<source>Luxembourgish</source> <source>Luxembourgish</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3071065188816255493" datatype="html"> <trans-unit id="3071065188816255493" datatype="html">
<source>Dutch</source> <source>Dutch</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="792060551707690640" datatype="html"> <trans-unit id="792060551707690640" datatype="html">
<source>Polish</source> <source>Polish</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9184513005098760425" datatype="html"> <trans-unit id="9184513005098760425" datatype="html">
<source>Portuguese (Brazil)</source> <source>Portuguese (Brazil)</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="153799456510623899" datatype="html"> <trans-unit id="153799456510623899" datatype="html">
<source>Portuguese</source> <source>Portuguese</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8118856427047826368" datatype="html"> <trans-unit id="8118856427047826368" datatype="html">
<source>Romanian</source> <source>Romanian</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7137419789978325708" datatype="html"> <trans-unit id="7137419789978325708" datatype="html">
<source>Russian</source> <source>Russian</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4287008301409320881" datatype="html"> <trans-unit id="4287008301409320881" datatype="html">
<source>Slovenian</source> <source>Slovenian</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8608389829607915090" datatype="html"> <trans-unit id="8608389829607915090" datatype="html">
<source>Serbian</source> <source>Serbian</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="499386805970351976" datatype="html"> <trans-unit id="499386805970351976" datatype="html">
<source>Swedish</source> <source>Swedish</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5682359291233237791" datatype="html"> <trans-unit id="5682359291233237791" datatype="html">
<source>Turkish</source> <source>Turkish</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4689443708886954687" datatype="html"> <trans-unit id="4689443708886954687" datatype="html">
<source>Chinese Simplified</source> <source>Chinese Simplified</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4912706592792948707" datatype="html"> <trans-unit id="4912706592792948707" datatype="html">
<source>ISO 8601</source> <source>ISO 8601</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context> <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> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1519954996184640001" datatype="html"> <trans-unit id="1519954996184640001" datatype="html">

View File

@ -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 { Component, OnDestroy, OnInit } from '@angular/core'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'

View File

@ -1,5 +1,5 @@
import { BrowserModule } from '@angular/platform-browser' 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 { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component' import { AppComponent } from './app.component'
import { import {
@ -87,6 +87,7 @@ import localeSr from '@angular/common/locales/sr'
import localeSv from '@angular/common/locales/sv' import localeSv from '@angular/common/locales/sv'
import localeTr from '@angular/common/locales/tr' import localeTr from '@angular/common/locales/tr'
import localeZh from '@angular/common/locales/zh' import localeZh from '@angular/common/locales/zh'
import { SettingsService } from './services/settings.service'
registerLocaleData(localeBe) registerLocaleData(localeBe)
registerLocaleData(localeCs) registerLocaleData(localeCs)
@ -109,6 +110,12 @@ registerLocaleData(localeSv)
registerLocaleData(localeTr) registerLocaleData(localeTr)
registerLocaleData(localeZh) registerLocaleData(localeZh)
function initializeApp(settings: SettingsService) {
return () => {
return settings.initializeSettings()
}
}
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
@ -174,6 +181,12 @@ registerLocaleData(localeZh)
ColorSliderModule, ColorSliderModule,
], ],
providers: [ providers: [
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
deps: [SettingsService],
multi: true,
},
DatePipe, DatePipe,
CookieService, CookieService,
{ {

View File

@ -21,17 +21,17 @@
</div> </div>
<ul ngbNav class="order-sm-3"> <ul ngbNav class="order-sm-3">
<li ngbDropdown class="nav-item dropdown"> <li ngbDropdown class="nav-item dropdown">
<button class="btn text-light" id="userDropdown" ngbDropdownToggle> <button class="btn" id="userDropdown" ngbDropdownToggle>
<span *ngIf="displayName" class="navbar-text small me-2 text-light d-none d-sm-inline"> <span class="small me-2 d-none d-sm-inline">
{{displayName}} {{this.settingsService.displayName}}
</span> </span>
<svg width="1.3em" height="1.3em" fill="currentColor"> <svg width="1.3em" height="1.3em" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#person-circle"/> <use xlink:href="assets/bootstrap-icons.svg#person-circle"/>
</svg> </svg>
</button> </button>
<div ngbDropdownMenu class="dropdown-menu-end shadow me-2" aria-labelledby="userDropdown"> <div ngbDropdownMenu class="dropdown-menu-end shadow me-2" aria-labelledby="userDropdown">
<div *ngIf="displayName" class="d-sm-none"> <div class="d-sm-none">
<p class="small mb-0 px-3 text-muted" i18n>Logged in as {{displayName}}</p> <p class="small mb-0 px-3 text-muted" i18n>Logged in as {{this.settingsService.displayName}}</p>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
</div> </div>
<a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()"> <a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()">

View File

@ -23,6 +23,7 @@ import {
AppRemoteVersion, AppRemoteVersion,
} from 'src/app/services/rest/remote-version.service' } from 'src/app/services/rest/remote-version.service'
import { QueryParamsService } from 'src/app/services/query-params.service' import { QueryParamsService } from 'src/app/services/query-params.service'
import { SettingsService } from 'src/app/services/settings.service'
@Component({ @Component({
selector: 'app-app-frame', selector: 'app-app-frame',
@ -36,10 +37,9 @@ export class AppFrameComponent {
private openDocumentsService: OpenDocumentsService, private openDocumentsService: OpenDocumentsService,
private searchService: SearchService, private searchService: SearchService,
public savedViewService: SavedViewService, public savedViewService: SavedViewService,
private list: DocumentListViewService,
private meta: Meta,
private remoteVersionService: RemoteVersionService, private remoteVersionService: RemoteVersionService,
private queryParamsService: QueryParamsService private queryParamsService: QueryParamsService,
public settingsService: SettingsService
) { ) {
this.remoteVersionService this.remoteVersionService
.checkForUpdates() .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
}
}
} }

View File

@ -1,6 +1,7 @@
import { Component, OnInit } from '@angular/core' import { Component, OnInit } from '@angular/core'
import { Meta } from '@angular/platform-browser' import { Meta } from '@angular/platform-browser'
import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service'
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
@ -8,23 +9,14 @@ import { SavedViewService } from 'src/app/services/rest/saved-view.service'
styleUrls: ['./dashboard.component.scss'], styleUrls: ['./dashboard.component.scss'],
}) })
export class DashboardComponent { export class DashboardComponent {
constructor(public savedViewService: SavedViewService, private meta: Meta) {} constructor(
public savedViewService: SavedViewService,
get displayName() { public settingsService: SettingsService
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
}
}
get subtitle() { get subtitle() {
if (this.displayName) { if (this.settingsService.displayName) {
return $localize`Hello ${this.displayName}, welcome to Paperless-ngx!` return $localize`Hello ${this.settingsService.displayName}, welcome to Paperless-ngx!`
} else { } else {
return $localize`Welcome to Paperless-ngx!` return $localize`Welcome to Paperless-ngx!`
} }

View File

@ -18,10 +18,7 @@ import { DocumentTypeEditDialogComponent } from '../common/edit-dialog/document-
import { PDFDocumentProxy } from 'ng2-pdf-viewer' import { PDFDocumentProxy } from 'ng2-pdf-viewer'
import { ToastService } from 'src/app/services/toast.service' import { ToastService } from 'src/app/services/toast.service'
import { TextComponent } from '../common/input/text/text.component' import { TextComponent } from '../common/input/text/text.component'
import { import { SettingsService } from 'src/app/services/settings.service'
SettingsService,
SETTINGS_KEYS,
} from 'src/app/services/settings.service'
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms' import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
import { Observable, Subject, BehaviorSubject } from 'rxjs' import { Observable, Subject, BehaviorSubject } from 'rxjs'
import { 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 { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
import { normalizeDateStr } from 'src/app/utils/date' import { normalizeDateStr } from 'src/app/utils/date'
import { QueryParamsService } from 'src/app/services/query-params.service' import { QueryParamsService } from 'src/app/services/query-params.service'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
@Component({ @Component({
selector: 'app-document-detail', selector: 'app-document-detail',

View File

@ -19,12 +19,10 @@ import {
} from '../../common/filterable-dropdown/filterable-dropdown.component' } from '../../common/filterable-dropdown/filterable-dropdown.component'
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component' import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
import { MatchingModel } from 'src/app/data/matching-model' import { MatchingModel } from 'src/app/data/matching-model'
import { import { SettingsService } from 'src/app/services/settings.service'
SettingsService,
SETTINGS_KEYS,
} from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service' import { ToastService } from 'src/app/services/toast.service'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
@Component({ @Component({
selector: 'app-bulk-editor', selector: 'app-bulk-editor',

View File

@ -8,12 +8,12 @@ import {
} from '@angular/core' } from '@angular/core'
import { PaperlessDocument } from 'src/app/data/paperless-document' import { PaperlessDocument } from 'src/app/data/paperless-document'
import { DocumentService } from 'src/app/services/rest/document.service' import { DocumentService } from 'src/app/services/rest/document.service'
import { import { SettingsService } from 'src/app/services/settings.service'
SettingsService,
SETTINGS_KEYS,
} from 'src/app/services/settings.service'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
import { OpenDocumentsService } from 'src/app/services/open-documents.service' 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({ @Component({
selector: 'app-document-card-large', selector: 'app-document-card-large',

View File

@ -9,12 +9,10 @@ import {
import { map } from 'rxjs/operators' import { map } from 'rxjs/operators'
import { PaperlessDocument } from 'src/app/data/paperless-document' import { PaperlessDocument } from 'src/app/data/paperless-document'
import { DocumentService } from 'src/app/services/rest/document.service' import { DocumentService } from 'src/app/services/rest/document.service'
import { import { SettingsService } from 'src/app/services/settings.service'
SettingsService,
SETTINGS_KEYS,
} from 'src/app/services/settings.service'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
import { OpenDocumentsService } from 'src/app/services/open-documents.service' import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
@Component({ @Component({
selector: 'app-document-card-small', selector: 'app-document-card-small',

View File

@ -13,11 +13,11 @@ import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { import {
LanguageOption, LanguageOption,
SettingsService, SettingsService,
SETTINGS_KEYS,
} from 'src/app/services/settings.service' } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service' import { ToastService } from 'src/app/services/toast.service'
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms' 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({ @Component({
selector: 'app-settings', selector: 'app-settings',
@ -227,10 +227,23 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
this.settingsForm.value.notificationsConsumerSuppressOnDashboard this.settingsForm.value.notificationsConsumerSuppressOnDashboard
) )
this.settings.setLanguage(this.settingsForm.value.displayLanguage) this.settings.setLanguage(this.settingsForm.value.displayLanguage)
this.store.next(this.settingsForm.value) this.settings
this.documentListViewService.updatePageSize() .storeSettings()
this.settings.updateAppearanceSettings() .pipe(first())
this.toastService.showInfo($localize`Settings saved successfully.`) .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[] { get displayLanguageOptions(): LanguageOption[] {

View 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,
},
]

View File

@ -1,6 +1,7 @@
import { DatePipe } from '@angular/common' import { DatePipe } from '@angular/common'
import { Inject, LOCALE_ID, Pipe, PipeTransform } from '@angular/core' 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' import { normalizeDateStr } from '../utils/date'
const FORMAT_TO_ISO_FORMAT = { const FORMAT_TO_ISO_FORMAT = {

View File

@ -8,9 +8,10 @@ import {
} from '../data/filter-rule' } from '../data/filter-rule'
import { PaperlessDocument } from '../data/paperless-document' import { PaperlessDocument } from '../data/paperless-document'
import { PaperlessSavedView } from '../data/paperless-saved-view' import { PaperlessSavedView } from '../data/paperless-saved-view'
import { SETTINGS_KEYS } from '../data/paperless-uisettings'
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys' import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
import { DocumentService, DOCUMENT_SORT_FIELDS } from './rest/document.service' 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. * Captures the current state of the list view.

View File

@ -1,4 +1,5 @@
import { DOCUMENT } from '@angular/common' import { DOCUMENT } from '@angular/common'
import { HttpClient } from '@angular/common/http'
import { import {
Inject, Inject,
Injectable, Injectable,
@ -9,17 +10,19 @@ import {
} from '@angular/core' } from '@angular/core'
import { Meta } from '@angular/platform-browser' import { Meta } from '@angular/platform-browser'
import { CookieService } from 'ngx-cookie-service' import { CookieService } from 'ngx-cookie-service'
import { first, Observable, tap } from 'rxjs'
import { import {
BRIGHTNESS, BRIGHTNESS,
estimateBrightnessForColor, estimateBrightnessForColor,
hexToHsl, hexToHsl,
} from 'src/app/utils/color' } from 'src/app/utils/color'
import { environment } from 'src/environments/environment'
export interface PaperlessSettings { import {
key: string PaperlessUiSettings,
type: string SETTINGS,
default: any SETTINGS_KEYS,
} } from '../data/paperless-uisettings'
import { ToastService } from './toast.service'
export interface LanguageOption { export interface LanguageOption {
code: string code: string
@ -32,89 +35,42 @@ export interface LanguageOption {
dateInputFormat?: string 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({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class SettingsService { export class SettingsService {
private renderer: Renderer2 private renderer: Renderer2
protected baseUrl: string = environment.apiBaseUrl + 'ui_settings/'
private settings: Object = {}
public displayName: string
constructor( constructor(
private rendererFactory: RendererFactory2, rendererFactory: RendererFactory2,
@Inject(DOCUMENT) private document, @Inject(DOCUMENT) private document,
private cookieService: CookieService, private cookieService: CookieService,
private meta: Meta, 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.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( public updateAppearanceSettings(
@ -333,11 +289,13 @@ export class SettingsService {
} }
getLanguage(): string { getLanguage(): string {
return this.cookieService.get(this.getLanguageCookieName()) return this.get(SETTINGS_KEYS.LANGUAGE)
} }
setLanguage(language: string) { setLanguage(language: string) {
if (language) { this.set(SETTINGS_KEYS.LANGUAGE, language)
if (language?.length) {
// for Django
this.cookieService.set(this.getLanguageCookieName(), language) this.cookieService.set(this.getLanguageCookieName(), language)
} else { } else {
this.cookieService.delete(this.getLanguageCookieName()) this.cookieService.delete(this.getLanguageCookieName())
@ -362,7 +320,16 @@ export class SettingsService {
return null 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) { if (value != null) {
switch (setting.type) { switch (setting.type) {
@ -381,10 +348,57 @@ export class SettingsService {
} }
set(key: string, value: any) { 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) { storeSettings(): Observable<any> {
localStorage.removeItem(key) 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)
},
})
}
} }
} }

View File

@ -18,6 +18,7 @@ from documents.models import DocumentType
from documents.models import SavedView from documents.models import SavedView
from documents.models import SavedViewFilterRule from documents.models import SavedViewFilterRule
from documents.models import Tag from documents.models import Tag
from documents.models import UiSettings
from documents.settings import EXPORTER_ARCHIVE_NAME from documents.settings import EXPORTER_ARCHIVE_NAME
from documents.settings import EXPORTER_FILE_NAME from documents.settings import EXPORTER_FILE_NAME
from documents.settings import EXPORTER_THUMBNAIL_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), map(lambda f: os.path.abspath(os.path.join(root, f)), files),
) )
# 2. Create manifest, containing all correspondents, types, tags and # 2. Create manifest, containing all correspondents, types, tags,
# documents # documents and ui_settings
with transaction.atomic(): with transaction.atomic():
manifest = json.loads( manifest = json.loads(
serializers.serialize("json", Correspondent.objects.all()), 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", User.objects.all()))
manifest += json.loads(
serializers.serialize("json", UiSettings.objects.all()),
)
# 3. Export files from each document # 3. Export files from each document
for index, document_dict in tqdm.tqdm( for index, document_dict in tqdm.tqdm(
enumerate(document_manifest), enumerate(document_manifest),

View 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,
),
),
],
),
]

View File

@ -465,3 +465,17 @@ class FileInfo:
cls._mangle_property(properties, "created") cls._mangle_property(properties, "created")
cls._mangle_property(properties, "title") cls._mangle_property(properties, "title")
return cls(**properties) 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

View File

@ -15,6 +15,7 @@ from .models import MatchingModel
from .models import SavedView from .models import SavedView
from .models import SavedViewFilterRule from .models import SavedViewFilterRule
from .models import Tag from .models import Tag
from .models import UiSettings
from .parsers import is_mime_type_supported from .parsers import is_mime_type_supported
@ -505,3 +506,24 @@ class BulkDownloadSerializer(DocumentListSerializer):
"bzip2": zipfile.ZIP_BZIP2, "bzip2": zipfile.ZIP_BZIP2,
"lzma": zipfile.ZIP_LZMA, "lzma": zipfile.ZIP_LZMA,
}[compression] }[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

View File

@ -9,8 +9,6 @@
<title>Paperless-ngx</title> <title>Paperless-ngx</title>
<base href="{% url 'base' %}"> <base href="{% url 'base' %}">
<meta name="viewport" content="width=device-width, initial-scale=1"> <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="cookie_prefix" content="{{cookie_prefix}}">
<meta name="robots" content="noindex,nofollow"> <meta name="robots" content="noindex,nofollow">
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">

View File

@ -27,6 +27,7 @@ from documents.models import DocumentType
from documents.models import MatchingModel from documents.models import MatchingModel
from documents.models import SavedView from documents.models import SavedView
from documents.models import Tag from documents.models import Tag
from documents.models import UiSettings
from documents.tests.utils import DirectoriesMixin from documents.tests.utils import DirectoriesMixin
from paperless import version from paperless import version
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
@ -1398,6 +1399,41 @@ class TestDocumentApiV2(DirectoriesMixin, APITestCase):
"#000000", "#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): class TestBulkEdit(DirectoriesMixin, APITestCase):
def setUp(self): def setUp(self):

View File

@ -11,6 +11,7 @@ from unicodedata import normalize
from urllib.parse import quote from urllib.parse import quote
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User
from django.db.models import Case from django.db.models import Case
from django.db.models import Count from django.db.models import Count
from django.db.models import IntegerField from django.db.models import IntegerField
@ -74,6 +75,7 @@ from .serialisers import PostDocumentSerializer
from .serialisers import SavedViewSerializer from .serialisers import SavedViewSerializer
from .serialisers import TagSerializer from .serialisers import TagSerializer
from .serialisers import TagSerializerVersion1 from .serialisers import TagSerializerVersion1
from .serialisers import UiSettingsViewSerializer
logger = logging.getLogger("paperless.api") logger = logging.getLogger("paperless.api")
@ -81,12 +83,18 @@ logger = logging.getLogger("paperless.api")
class IndexView(TemplateView): class IndexView(TemplateView):
template_name = "index.html" 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: # This is here for the following reason:
# Django identifies languages in the form "en-us" # Django identifies languages in the form "en-us"
# However, angular generates locales as "en-US". # However, angular generates locales as "en-US".
# this translates between these two forms. # this translates between these two forms.
lang = get_language()
if "-" in lang: if "-" in lang:
first = lang[: lang.index("-")] first = lang[: lang.index("-")]
second = lang[lang.index("-") + 1 :] second = lang[lang.index("-") + 1 :]
@ -99,16 +107,18 @@ class IndexView(TemplateView):
context["cookie_prefix"] = settings.COOKIE_PREFIX context["cookie_prefix"] = settings.COOKIE_PREFIX
context["username"] = self.request.user.username context["username"] = self.request.user.username
context["full_name"] = self.request.user.get_full_name() context["full_name"] = self.request.user.get_full_name()
context["styles_css"] = f"frontend/{self.get_language()}/styles.css" context["styles_css"] = f"frontend/{self.get_frontend_language()}/styles.css"
context["runtime_js"] = f"frontend/{self.get_language()}/runtime.js" context["runtime_js"] = f"frontend/{self.get_frontend_language()}/runtime.js"
context["polyfills_js"] = f"frontend/{self.get_language()}/polyfills.js" context[
context["main_js"] = f"frontend/{self.get_language()}/main.js" "polyfills_js"
] = f"frontend/{self.get_frontend_language()}/polyfills.js"
context["main_js"] = f"frontend/{self.get_frontend_language()}/main.js"
context[ context[
"webmanifest" "webmanifest"
] = f"frontend/{self.get_language()}/manifest.webmanifest" # noqa: E501 ] = f"frontend/{self.get_frontend_language()}/manifest.webmanifest" # noqa: E501
context[ context[
"apple_touch_icon" "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 return context
@ -717,3 +727,41 @@ class RemoteVersionView(GenericAPIView):
"feature_is_set": feature_is_set, "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,
},
)

View File

@ -20,6 +20,7 @@ from documents.views import SearchAutoCompleteView
from documents.views import SelectionDataView from documents.views import SelectionDataView
from documents.views import StatisticsView from documents.views import StatisticsView
from documents.views import TagViewSet from documents.views import TagViewSet
from documents.views import UiSettingsView
from documents.views import UnifiedSearchViewSet from documents.views import UnifiedSearchViewSet
from paperless.consumers import StatusConsumer from paperless.consumers import StatusConsumer
from paperless.views import FaviconView from paperless.views import FaviconView
@ -78,6 +79,11 @@ urlpatterns = [
RemoteVersionView.as_view(), RemoteVersionView.as_view(),
name="remoteversion", name="remoteversion",
), ),
re_path(
r"^ui_settings/",
UiSettingsView.as_view(),
name="ui_settings",
),
path("token/", views.obtain_auth_token), path("token/", views.obtain_auth_token),
] ]
+ api_router.urls, + api_router.urls,