Compare commits

..

1 Commits

Author SHA1 Message Date
shamoon
e09fc54722 Enforce email enabled for mandatory verification 2025-11-03 17:34:29 -08:00
19 changed files with 37 additions and 137 deletions

View File

@@ -32,7 +32,7 @@ RUN set -eux \
# Purpose: Installs s6-overlay and rootfs # Purpose: Installs s6-overlay and rootfs
# Comments: # Comments:
# - Don't leave anything extra in here either # - Don't leave anything extra in here either
FROM ghcr.io/astral-sh/uv:0.9.7-python3.12-bookworm-slim AS s6-overlay-base FROM ghcr.io/astral-sh/uv:0.9.4-python3.12-bookworm-slim AS s6-overlay-base
WORKDIR /usr/src/s6 WORKDIR /usr/src/s6

View File

@@ -1,41 +1,5 @@
# Changelog # Changelog
## paperless-ngx 2.19.4
### Bug Fixes
- Fix: use original_file when attaching docs to workflow emails with added trigger [@shamoon](https://github.com/shamoon) ([#11266](https://github.com/paperless-ngx/paperless-ngx/pull/11266))
- Fix: mark 'Select' button in doc list for translation [@shamoon](https://github.com/shamoon) ([#11278](https://github.com/paperless-ngx/paperless-ngx/pull/11278))
- Fix: respect fields parameter for created field [@shamoon](https://github.com/shamoon) ([#11251](https://github.com/paperless-ngx/paperless-ngx/pull/11251))
- Fix: improve legibility of processed mail error popover in light mode [@shamoon](https://github.com/shamoon) ([#11258](https://github.com/paperless-ngx/paperless-ngx/pull/11258))
- Fixhancement: truncate large logs, improve auto-scroll [@shamoon](https://github.com/shamoon) ([#11239](https://github.com/paperless-ngx/paperless-ngx/pull/11239))
- Chore: add max-height and overflow to processedmail error popover [@shamoon](https://github.com/shamoon) ([#11252](https://github.com/paperless-ngx/paperless-ngx/pull/11252))
- Fix: delay iframe DOM removal, handle onafterprint error for print in FF [@shamoon](https://github.com/shamoon) ([#11237](https://github.com/paperless-ngx/paperless-ngx/pull/11237))
- Fix: de-deduplicate children in tag list when filtering [@shamoon](https://github.com/shamoon) ([#11229](https://github.com/paperless-ngx/paperless-ngx/pull/11229))
### Performance
- Performance: re-enable virtual scroll, bump ng-select [@shamoon](https://github.com/shamoon) ([#11279](https://github.com/paperless-ngx/paperless-ngx/pull/11279))
- Performance: use virtual scroll container and log level parsing for logs view [@MickLesk](https://github.com/MickLesk) ([#11233](https://github.com/paperless-ngx/paperless-ngx/pull/11233))
### All App Changes
<details>
<summary>11 changes</summary>
- Performance: re-enable virtual scroll, bump ng-select [@shamoon](https://github.com/shamoon) ([#11279](https://github.com/paperless-ngx/paperless-ngx/pull/11279))
- Fix: use original_file when attaching docs to workflow emails with added trigger [@shamoon](https://github.com/shamoon) ([#11266](https://github.com/paperless-ngx/paperless-ngx/pull/11266))
- Fix: mark 'Select' button in doc list for translation [@shamoon](https://github.com/shamoon) ([#11278](https://github.com/paperless-ngx/paperless-ngx/pull/11278))
- Fix: respect fields parameter for created field [@shamoon](https://github.com/shamoon) ([#11251](https://github.com/paperless-ngx/paperless-ngx/pull/11251))
- Fix: improve legibility of processed mail error popover in light mode [@shamoon](https://github.com/shamoon) ([#11258](https://github.com/paperless-ngx/paperless-ngx/pull/11258))
- Fixhancement: truncate large logs, improve auto-scroll [@shamoon](https://github.com/shamoon) ([#11239](https://github.com/paperless-ngx/paperless-ngx/pull/11239))
- Chore: add max-height and overflow to processedmail error popover [@shamoon](https://github.com/shamoon) ([#11252](https://github.com/paperless-ngx/paperless-ngx/pull/11252))
- Fix: delay iframe DOM removal, handle onafterprint error for print in FF [@shamoon](https://github.com/shamoon) ([#11237](https://github.com/paperless-ngx/paperless-ngx/pull/11237))
- Performance: use virtual scroll container and log level parsing for logs view [@MickLesk](https://github.com/MickLesk) ([#11233](https://github.com/paperless-ngx/paperless-ngx/pull/11233))
- Chore: cache Github version check for 15 minutes [@shamoon](https://github.com/shamoon) ([#11235](https://github.com/paperless-ngx/paperless-ngx/pull/11235))
- Fix: de-deduplicate children in tag list when filtering [@shamoon](https://github.com/shamoon) ([#11229](https://github.com/paperless-ngx/paperless-ngx/pull/11229))
</details>
## paperless-ngx 2.19.3 ## paperless-ngx 2.19.3
### Bug Fixes ### Bug Fixes

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "paperless-ngx" name = "paperless-ngx"
version = "2.19.5" version = "2.19.3"
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents" description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"

View File

@@ -1,6 +1,6 @@
{ {
"name": "paperless-ngx-ui", "name": "paperless-ngx-ui",
"version": "2.19.5", "version": "2.19.3",
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
"ng": "ng", "ng": "ng",

View File

@@ -354,13 +354,5 @@ describe('CustomFieldsQueryDropdownComponent', () => {
model.removeElement(atom) model.removeElement(atom)
expect(completeSpy).toHaveBeenCalled() expect(completeSpy).toHaveBeenCalled()
}) })
it('should subscribe to existing elements when queries are assigned', () => {
const expression = new CustomFieldQueryExpression()
const nextSpy = jest.spyOn(model.changed, 'next')
model.queries = [expression]
expression.changed.next(expression)
expect(nextSpy).toHaveBeenCalledWith(model)
})
}) })
}) })

View File

@@ -17,7 +17,7 @@ import {
} from '@ng-bootstrap/ng-bootstrap' } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectComponent, NgSelectModule } from '@ng-select/ng-select' import { NgSelectComponent, NgSelectModule } from '@ng-select/ng-select'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { first, Subject, Subscription, takeUntil } from 'rxjs' import { first, Subject, takeUntil } from 'rxjs'
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field' import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
import { import {
CUSTOM_FIELD_QUERY_MAX_ATOMS, CUSTOM_FIELD_QUERY_MAX_ATOMS,
@@ -41,27 +41,10 @@ import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.comp
import { DocumentLinkComponent } from '../input/document-link/document-link.component' import { DocumentLinkComponent } from '../input/document-link/document-link.component'
export class CustomFieldQueriesModel { export class CustomFieldQueriesModel {
private _queries: CustomFieldQueryElement[] = [] public queries: CustomFieldQueryElement[] = []
private rootSubscriptions: Subscription[] = []
public readonly changed = new Subject<CustomFieldQueriesModel>() public readonly changed = new Subject<CustomFieldQueriesModel>()
public get queries(): CustomFieldQueryElement[] {
return this._queries
}
public set queries(value: CustomFieldQueryElement[]) {
this.teardownRootSubscriptions()
this._queries = value ?? []
for (const element of this._queries) {
this.rootSubscriptions.push(
element.changed.subscribe(() => {
this.changed.next(this)
})
)
}
}
public clear(fireEvent = true) { public clear(fireEvent = true) {
this.queries = [] this.queries = []
if (fireEvent) { if (fireEvent) {
@@ -124,14 +107,14 @@ export class CustomFieldQueriesModel {
public addExpression( public addExpression(
expression: CustomFieldQueryExpression = new CustomFieldQueryExpression() expression: CustomFieldQueryExpression = new CustomFieldQueryExpression()
) { ) {
if (this.queries.length === 0) { if (this.queries.length > 0) {
this.queries = [expression] ;(
return (this.queries[0] as CustomFieldQueryExpression)
.value as CustomFieldQueryElement[]
).push(expression)
} else {
this.queries.push(expression)
} }
;(
(this.queries[0] as CustomFieldQueryExpression)
.value as CustomFieldQueryElement[]
).push(expression)
expression.changed.subscribe(() => { expression.changed.subscribe(() => {
this.changed.next(this) this.changed.next(this)
}) })
@@ -183,13 +166,6 @@ export class CustomFieldQueriesModel {
this.changed.next(this) this.changed.next(this)
} }
} }
private teardownRootSubscriptions() {
for (const subscription of this.rootSubscriptions) {
subscription.unsubscribe()
}
this.rootSubscriptions = []
}
} }
@Component({ @Component({

View File

@@ -1,3 +1,4 @@
import { fakeAsync, tick } from '@angular/core/testing'
import { import {
CustomFieldQueryElementType, CustomFieldQueryElementType,
CustomFieldQueryLogicalOperator, CustomFieldQueryLogicalOperator,
@@ -110,38 +111,13 @@ describe('CustomFieldQueryAtom', () => {
expect(atom.serialize()).toEqual([1, 'operator', 'value']) expect(atom.serialize()).toEqual([1, 'operator', 'value'])
}) })
it('should emit changed on value change immediately', () => { it('should emit changed on value change after debounce', fakeAsync(() => {
const atom = new CustomFieldQueryAtom() const atom = new CustomFieldQueryAtom()
const changeSpy = jest.spyOn(atom.changed, 'next') const changeSpy = jest.spyOn(atom.changed, 'next')
atom.value = 'new value' atom.value = 'new value'
tick(1000)
expect(changeSpy).toHaveBeenCalled() expect(changeSpy).toHaveBeenCalled()
}) }))
it('should ignore duplicate array emissions', () => {
const atom = new CustomFieldQueryAtom()
atom.operator = CustomFieldQueryOperator.In
const changeSpy = jest.fn()
atom.changed.subscribe(changeSpy)
atom.value = [1, 2]
expect(changeSpy).toHaveBeenCalledTimes(1)
changeSpy.mockClear()
atom.value = [1, 2]
expect(changeSpy).not.toHaveBeenCalled()
})
it('should emit when array values differ while length matches', () => {
const atom = new CustomFieldQueryAtom()
atom.operator = CustomFieldQueryOperator.In
const changeSpy = jest.fn()
atom.changed.subscribe(changeSpy)
atom.value = [1, 2]
changeSpy.mockClear()
atom.value = [1, 3]
expect(changeSpy).toHaveBeenCalledTimes(1)
})
}) })
describe('CustomFieldQueryExpression', () => { describe('CustomFieldQueryExpression', () => {

View File

@@ -1,4 +1,4 @@
import { Subject, distinctUntilChanged } from 'rxjs' import { Subject, debounceTime, distinctUntilChanged } from 'rxjs'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { import {
CUSTOM_FIELD_QUERY_VALUE_TYPES_BY_OPERATOR, CUSTOM_FIELD_QUERY_VALUE_TYPES_BY_OPERATOR,
@@ -110,22 +110,7 @@ export class CustomFieldQueryAtom extends CustomFieldQueryElement {
protected override connectValueModelChanged(): void { protected override connectValueModelChanged(): void {
this.valueModelChanged this.valueModelChanged
.pipe( .pipe(debounceTime(1000), distinctUntilChanged())
distinctUntilChanged((previous, current) => {
if (Array.isArray(previous) && Array.isArray(current)) {
if (previous.length !== current.length) {
return false
}
for (let i = 0; i < previous.length; i++) {
if (previous[i] !== current[i]) {
return false
}
}
return true
}
return previous === current
})
)
.subscribe(() => { .subscribe(() => {
this.changed.next(this) this.changed.next(this)
}) })

View File

@@ -6,7 +6,7 @@ export const environment = {
apiVersion: '9', // match src/paperless/settings.py apiVersion: '9', // match src/paperless/settings.py
appTitle: 'Paperless-ngx', appTitle: 'Paperless-ngx',
tag: 'prod', tag: 'prod',
version: '2.19.5', version: '2.19.3',
webSocketHost: window.location.host, webSocketHost: window.location.host,
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:', webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
webSocketBaseUrl: base_url.pathname + 'ws/', webSocketBaseUrl: base_url.pathname + 'ws/',

View File

@@ -764,7 +764,7 @@
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context> <context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
<context context-type="linenumber">17</context> <context context-type="linenumber">17</context>
</context-group> </context-group>
<target state="translated">línies</target> <target state="needs-translation">lines</target>
</trans-unit> </trans-unit>
<trans-unit id="8838884664569764142" datatype="html"> <trans-unit id="8838884664569764142" datatype="html">
<source>Auto refresh</source> <source>Auto refresh</source>
@@ -8700,7 +8700,7 @@
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">18</context> <context context-type="linenumber">18</context>
</context-group> </context-group>
<target state="translated">Selecciona:</target> <target state="needs-translation">Select:</target>
</trans-unit> </trans-unit>
<trans-unit id="6252070156626006029" datatype="html"> <trans-unit id="6252070156626006029" datatype="html">
<source>None</source> <source>None</source>

View File

@@ -764,7 +764,7 @@
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context> <context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
<context context-type="linenumber">17</context> <context context-type="linenumber">17</context>
</context-group> </context-group>
<target state="translated">lignes</target> <target state="needs-translation">lines</target>
</trans-unit> </trans-unit>
<trans-unit id="8838884664569764142" datatype="html" approved="yes"> <trans-unit id="8838884664569764142" datatype="html" approved="yes">
<source>Auto refresh</source> <source>Auto refresh</source>
@@ -8699,7 +8699,7 @@
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">18</context> <context context-type="linenumber">18</context>
</context-group> </context-group>
<target state="translated">Sélectionner :</target> <target state="needs-translation">Select:</target>
</trans-unit> </trans-unit>
<trans-unit id="6252070156626006029" datatype="html" approved="yes"> <trans-unit id="6252070156626006029" datatype="html" approved="yes">
<source>None</source> <source>None</source>

View File

@@ -8700,7 +8700,7 @@
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">18</context> <context context-type="linenumber">18</context>
</context-group> </context-group>
<target state="translated">選取:</target> <target state="needs-translation">Select:</target>
</trans-unit> </trans-unit>
<trans-unit id="6252070156626006029" datatype="html"> <trans-unit id="6252070156626006029" datatype="html">
<source>None</source> <source>None</source>

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n" "Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-28 18:06+0000\n" "POT-Creation-Date: 2025-10-28 18:06+0000\n"
"PO-Revision-Date: 2025-11-04 12:15\n" "PO-Revision-Date: 2025-10-28 18:07\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Catalan\n" "Language-Team: Catalan\n"
"Language: ca_ES\n" "Language: ca_ES\n"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n" "Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-28 18:06+0000\n" "POT-Creation-Date: 2025-10-28 18:06+0000\n"
"PO-Revision-Date: 2025-11-06 00:35\n" "PO-Revision-Date: 2025-11-03 17:49\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: German\n" "Language-Team: German\n"
"Language: de_DE\n" "Language: de_DE\n"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n" "Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-28 18:06+0000\n" "POT-Creation-Date: 2025-10-28 18:06+0000\n"
"PO-Revision-Date: 2025-11-06 12:14\n" "PO-Revision-Date: 2025-10-29 12:14\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: French\n" "Language-Team: French\n"
"Language: fr_FR\n" "Language: fr_FR\n"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n" "Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-28 18:06+0000\n" "POT-Creation-Date: 2025-10-28 18:06+0000\n"
"PO-Revision-Date: 2025-11-06 00:35\n" "PO-Revision-Date: 2025-10-28 18:07\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Chinese Traditional\n" "Language-Team: Chinese Traditional\n"
"Language: zh_TW\n" "Language: zh_TW\n"

View File

@@ -537,6 +537,13 @@ ACCOUNT_EMAIL_VERIFICATION = (
) )
) )
if ACCOUNT_EMAIL_VERIFICATION == "mandatory":
ACCOUNT_EMAIL_REQUIRED = True
if not EMAIL_ENABLED:
raise ValueError(
"Email must be enabled if ACCOUNT_EMAIL_VERIFICATION is mandatory",
)
ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS = __get_boolean( ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS = __get_boolean(
"PAPERLESS_ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS", "PAPERLESS_ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS",
"True", "True",

View File

@@ -1,6 +1,6 @@
from typing import Final from typing import Final
__version__: Final[tuple[int, int, int]] = (2, 19, 5) __version__: Final[tuple[int, int, int]] = (2, 19, 3)
# Version string like X.Y.Z # Version string like X.Y.Z
__full_version_str__: Final[str] = ".".join(map(str, __version__)) __full_version_str__: Final[str] = ".".join(map(str, __version__))
# Version string like X.Y # Version string like X.Y

2
uv.lock generated
View File

@@ -2115,7 +2115,7 @@ wheels = [
[[package]] [[package]]
name = "paperless-ngx" name = "paperless-ngx"
version = "2.19.5" version = "2.19.3"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "babel", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "babel", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },