Compare commits

..

4 Commits

Author SHA1 Message Date
sidey79
fc4134e15c Development: clean devcontainer .venv dir (#10705) 2025-09-02 20:13:14 +00:00
GitHub Actions
ac1b420966 Auto translate strings 2025-09-02 18:48:36 +00:00
shamoon
80595899c1 Performance fix: add paging for custom field select options (#10755) 2025-09-02 11:46:54 -07:00
github-actions[bot]
9463a8fd26 Documentation: Add v2.18.3 changelog (#10734)
* Changelog v2.18.3 - GHA

* Update changelog for version 2.18.3

Removed feature enhancements section and duplicate performance entry from changelog.

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-09-01 18:45:46 -07:00
7 changed files with 176 additions and 23 deletions

View File

@@ -3,7 +3,7 @@
"dockerComposeFile": "docker-compose.devcontainer.sqlite-tika.yml", "dockerComposeFile": "docker-compose.devcontainer.sqlite-tika.yml",
"service": "paperless-development", "service": "paperless-development",
"workspaceFolder": "/usr/src/paperless/paperless-ngx", "workspaceFolder": "/usr/src/paperless/paperless-ngx",
"postCreateCommand": "/bin/bash -c 'uv sync --group dev && uv run pre-commit install'", "postCreateCommand": "/bin/bash -c 'rm -rf .venv/.* && uv sync --group dev && uv run pre-commit install'",
"customizations": { "customizations": {
"vscode": { "vscode": {
"extensions": [ "extensions": [

View File

@@ -1,5 +1,48 @@
# Changelog # Changelog
## paperless-ngx 2.18.3
### Bug Fixes
- Fix: include application config language settings for dateparser auto-detection [@shamoon](https://github.com/shamoon) ([#10722](https://github.com/paperless-ngx/paperless-ngx/pull/10722))
- Fix: hide sidebar counts during saved views organization [@shamoon](https://github.com/shamoon) ([#10716](https://github.com/paperless-ngx/paperless-ngx/pull/10716))
- Fix: wrap long view titles in sidebar [@shamoon](https://github.com/shamoon) ([#10715](https://github.com/paperless-ngx/paperless-ngx/pull/10715))
- Fixhancement: more saved view count refreshes [@shamoon](https://github.com/shamoon) ([#10694](https://github.com/paperless-ngx/paperless-ngx/pull/10694))
- Fix: include pagination array items for valid openapi schema [@shamoon](https://github.com/shamoon) ([#10682](https://github.com/paperless-ngx/paperless-ngx/pull/10682))
- Fix: prevent scroll for view name in sidebar [@shamoon](https://github.com/shamoon) ([#10676](https://github.com/paperless-ngx/paperless-ngx/pull/10676))
- Tweak: center document close button in app frame [@shamoon](https://github.com/shamoon) ([#10661](https://github.com/paperless-ngx/paperless-ngx/pull/10661))
- Performance: Enable virtual scrolling for large custom field selects @david-loe ([#10708](https://github.com/paperless-ngx/paperless-ngx/pull/10708))
### Dependencies
<details>
<summary>5 changes</summary>
- Chore(deps): Update granian[uvloop] requirement from ~=2.4.1 to ~=2.5.1 @[dependabot[bot]](https://github.com/apps/dependabot) ([#10529](https://github.com/paperless-ngx/paperless-ngx/pull/10529))
- Chore(deps): Bump the small-changes group across 1 directory with 6 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#10714](https://github.com/paperless-ngx/paperless-ngx/pull/10714))
- docker-compose(deps): Bump library/mariadb from 11 to 12 in /docker/compose @[dependabot[bot]](https://github.com/apps/dependabot) ([#10621](https://github.com/paperless-ngx/paperless-ngx/pull/10621))
- docker-compose(deps): Bump gotenberg/gotenberg from 8.20 to 8.22 in /docker/compose @[dependabot[bot]](https://github.com/apps/dependabot) ([#10687](https://github.com/paperless-ngx/paperless-ngx/pull/10687))
- docker(deps): Bump astral-sh/uv from 0.8.8-python3.12-bookworm-slim to 0.8.13-python3.12-bookworm-slim @[dependabot[bot]](https://github.com/apps/dependabot) ([#10685](https://github.com/paperless-ngx/paperless-ngx/pull/10685))
</details>
### All App Changes
<details>
<summary>11 changes</summary>
- Fix: include application config language settings for dateparser auto-detection [@shamoon](https://github.com/shamoon) ([#10722](https://github.com/paperless-ngx/paperless-ngx/pull/10722))
- Chore(deps): Update granian[uvloop] requirement from ~=2.4.1 to ~=2.5.1 @[dependabot[bot]](https://github.com/apps/dependabot) ([#10529](https://github.com/paperless-ngx/paperless-ngx/pull/10529))
- Chore(deps): Bump the small-changes group across 1 directory with 6 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#10714](https://github.com/paperless-ngx/paperless-ngx/pull/10714))
- Fix: hide sidebar counts during saved views organization [@shamoon](https://github.com/shamoon) ([#10716](https://github.com/paperless-ngx/paperless-ngx/pull/10716))
- Fix: wrap long view titles in sidebar [@shamoon](https://github.com/shamoon) ([#10715](https://github.com/paperless-ngx/paperless-ngx/pull/10715))
- Performance: Enable virtual scrolling for large custom field selects @david-loe ([#10708](https://github.com/paperless-ngx/paperless-ngx/pull/10708))
- Chore: refactor document details component [@shamoon](https://github.com/shamoon) ([#10662](https://github.com/paperless-ngx/paperless-ngx/pull/10662))
- Fixhancement: more saved view count refreshes [@shamoon](https://github.com/shamoon) ([#10694](https://github.com/paperless-ngx/paperless-ngx/pull/10694))
- Fix: include pagination array items for valid openapi schema [@shamoon](https://github.com/shamoon) ([#10682](https://github.com/paperless-ngx/paperless-ngx/pull/10682))
- Fix: prevent scroll for view name in sidebar [@shamoon](https://github.com/shamoon) ([#10676](https://github.com/paperless-ngx/paperless-ngx/pull/10676))
- Tweak: center document close button in app frame [@shamoon](https://github.com/shamoon) ([#10661](https://github.com/paperless-ngx/paperless-ngx/pull/10661))
</details>
## paperless-ngx 2.18.2 ## paperless-ngx 2.18.2
### Bug Fixes ### Bug Fixes

View File

@@ -553,7 +553,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
<context context-type="linenumber">45</context> <context context-type="linenumber">55</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html</context>
@@ -1444,7 +1444,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">54</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html</context>
@@ -3673,42 +3673,42 @@
<source>Warning: existing instances of this field will retain their current value index (e.g. option #1, #2, #3) after editing the options here</source> <source>Warning: existing instances of this field will retain their current value index (e.g. option #1, #2, #3) after editing the options here</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
<context context-type="linenumber">32</context> <context context-type="linenumber">42</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2739003406164860877" datatype="html"> <trans-unit id="2739003406164860877" datatype="html">
<source>Default Currency</source> <source>Default Currency</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
<context context-type="linenumber">37</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7615210738790237590" datatype="html"> <trans-unit id="7615210738790237590" datatype="html">
<source>3-character currency code</source> <source>3-character currency code</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
<context context-type="linenumber">37</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="607636736207886379" datatype="html"> <trans-unit id="607636736207886379" datatype="html">
<source>Use locale</source> <source>Use locale</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
<context context-type="linenumber">37</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="528950215505228201" datatype="html"> <trans-unit id="528950215505228201" datatype="html">
<source>Create new custom field</source> <source>Create new custom field</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.ts</context>
<context context-type="linenumber">93</context> <context context-type="linenumber">118</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8751213029607178010" datatype="html"> <trans-unit id="8751213029607178010" datatype="html">
<source>Edit custom field</source> <source>Edit custom field</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.ts</context>
<context context-type="linenumber">97</context> <context context-type="linenumber">122</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6672809941092516947" datatype="html"> <trans-unit id="6672809941092516947" datatype="html">

View File

@@ -28,6 +28,16 @@
</div> </div>
} }
</div> </div>
@if (allSelectOptions.length > SELECT_OPTION_PAGE_SIZE) {
<ngb-pagination
class="d-flex justify-content-end"
[pageSize]="SELECT_OPTION_PAGE_SIZE"
[collectionSize]="allSelectOptions.length"
[(page)]="selectOptionsPage"
[maxSize]="5"
size="sm"
></ngb-pagination>
}
@if (object?.id) { @if (object?.id) {
<small class="d-block mt-2" i18n>Warning: existing instances of this field will retain their current value index (e.g. option #1, #2, #3) after editing the options here</small> <small class="d-block mt-2" i18n>Warning: existing instances of this field will retain their current value index (e.g. option #1, #2, #3) after editing the options here</small>
} }

View File

@@ -125,4 +125,42 @@ describe('CustomFieldEditDialogComponent', () => {
fixture.detectChanges() fixture.detectChanges()
expect(document.activeElement).toBe(selectOptionInputs.last.nativeElement) expect(document.activeElement).toBe(selectOptionInputs.last.nativeElement)
}) })
it('should send all select options including those changed in form on save', () => {
component.dialogMode = EditDialogMode.EDIT
component.object = {
id: 1,
name: 'Field 1',
data_type: CustomFieldDataType.Select,
extra_data: {
select_options: Array.from({ length: 50 }, (_, i) => ({
label: `Option ${i + 1}`,
id: `${i + 1}-xyz`,
})),
},
}
fixture.detectChanges()
component.ngOnInit()
component.selectOptionsPage = 2
fixture.detectChanges()
component.objectForm
.get('extra_data')
.get('select_options')
.get('0')
.get('label')
.setValue('Updated Option 9')
const formValues = (component as any).getFormValues()
// first item unchanged
expect(formValues.extra_data.select_options[0]).toEqual({
label: 'Option 1',
id: '1-xyz',
})
// page 2 first item updated
expect(
formValues.extra_data.select_options[component.SELECT_OPTION_PAGE_SIZE]
).toEqual({
label: 'Updated Option 9',
id: '9-xyz',
})
})
}) })

View File

@@ -14,6 +14,7 @@ import {
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms' } from '@angular/forms'
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { takeUntil } from 'rxjs' import { takeUntil } from 'rxjs'
import { import {
@@ -28,6 +29,8 @@ import { SelectComponent } from '../../input/select/select.component'
import { TextComponent } from '../../input/text/text.component' import { TextComponent } from '../../input/text/text.component'
import { EditDialogComponent, EditDialogMode } from '../edit-dialog.component' import { EditDialogComponent, EditDialogMode } from '../edit-dialog.component'
const SELECT_OPTION_PAGE_SIZE = 8
@Component({ @Component({
selector: 'pngx-custom-field-edit-dialog', selector: 'pngx-custom-field-edit-dialog',
templateUrl: './custom-field-edit-dialog.component.html', templateUrl: './custom-field-edit-dialog.component.html',
@@ -37,6 +40,7 @@ import { EditDialogComponent, EditDialogMode } from '../edit-dialog.component'
TextComponent, TextComponent,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
NgbPaginationModule,
NgxBootstrapIconsModule, NgxBootstrapIconsModule,
], ],
}) })
@@ -45,6 +49,21 @@ export class CustomFieldEditDialogComponent
implements OnInit, AfterViewInit implements OnInit, AfterViewInit
{ {
CustomFieldDataType = CustomFieldDataType CustomFieldDataType = CustomFieldDataType
SELECT_OPTION_PAGE_SIZE = SELECT_OPTION_PAGE_SIZE
private _allSelectOptions: any[] = []
public get allSelectOptions(): any[] {
return this._allSelectOptions
}
private _selectOptionsPage: number
public get selectOptionsPage(): number {
return this._selectOptionsPage
}
public set selectOptionsPage(v: number) {
this._selectOptionsPage = v
this.updateSelectOptions()
}
@ViewChildren('selectOption') @ViewChildren('selectOption')
private selectOptionInputs: QueryList<ElementRef> private selectOptionInputs: QueryList<ElementRef>
@@ -67,17 +86,10 @@ export class CustomFieldEditDialogComponent
this.objectForm.get('data_type').disable() this.objectForm.get('data_type').disable()
} }
if (this.object?.data_type === CustomFieldDataType.Select) { if (this.object?.data_type === CustomFieldDataType.Select) {
this.selectOptions.clear() this._allSelectOptions = [
this.object.extra_data.select_options ...(this.object.extra_data.select_options ?? []),
.filter((option) => option) ]
.forEach((option) => this.selectOptionsPage = 1
this.selectOptions.push(
new FormGroup({
label: new FormControl(option.label),
id: new FormControl(option.id),
})
)
)
} }
} }
@@ -87,6 +99,19 @@ export class CustomFieldEditDialogComponent
.subscribe(() => { .subscribe(() => {
this.selectOptionInputs.last?.nativeElement.focus() this.selectOptionInputs.last?.nativeElement.focus()
}) })
this.objectForm.valueChanges
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((change) => {
// Update the relevant select options values if changed in the form, which is only a page of the entire list
this.objectForm
.get('extra_data.select_options')
?.value.forEach((option, index) => {
this._allSelectOptions[
index + (this.selectOptionsPage - 1) * SELECT_OPTION_PAGE_SIZE
] = option
})
})
} }
getCreateTitle() { getCreateTitle() {
@@ -108,6 +133,17 @@ export class CustomFieldEditDialogComponent
}) })
} }
protected getFormValues() {
const formValues = super.getFormValues()
if (
this.objectForm.get('data_type')?.value === CustomFieldDataType.Select
) {
// Make sure we send all select options, with updated values
formValues.extra_data.select_options = this._allSelectOptions
}
return formValues
}
getDataTypes() { getDataTypes() {
return DATA_TYPE_LABELS return DATA_TYPE_LABELS
} }
@@ -116,13 +152,35 @@ export class CustomFieldEditDialogComponent
return this.dialogMode === EditDialogMode.EDIT return this.dialogMode === EditDialogMode.EDIT
} }
private updateSelectOptions() {
this.selectOptions.clear()
this._allSelectOptions
.slice(
(this.selectOptionsPage - 1) * SELECT_OPTION_PAGE_SIZE,
this.selectOptionsPage * SELECT_OPTION_PAGE_SIZE
)
.forEach((option) =>
this.selectOptions.push(
new FormGroup({
label: new FormControl(option.label),
id: new FormControl(option.id),
})
)
)
}
public addSelectOption() { public addSelectOption() {
this.selectOptions.push( this._allSelectOptions.push({ label: null, id: null })
new FormGroup({ label: new FormControl(null), id: new FormControl(null) }) this.selectOptionsPage = Math.ceil(
this.allSelectOptions.length / SELECT_OPTION_PAGE_SIZE
) )
} }
public removeSelectOption(index: number) { public removeSelectOption(index: number) {
this.selectOptions.removeAt(index) this.selectOptions.removeAt(index)
this._allSelectOptions.splice(
index + (this.selectOptionsPage - 1) * SELECT_OPTION_PAGE_SIZE,
1
)
} }
} }

View File

@@ -147,9 +147,13 @@ export abstract class EditDialogComponent<
) )
} }
protected getFormValues(): any {
return Object.assign({}, this.objectForm.value)
}
save() { save() {
this.error = null this.error = null
const formValues = Object.assign({}, this.objectForm.value) const formValues = this.getFormValues()
const permissionsObject: PermissionsFormObject = const permissionsObject: PermissionsFormObject =
this.objectForm.get('permissions_form')?.value this.objectForm.get('permissions_form')?.value
if (permissionsObject) { if (permissionsObject) {