Compare commits

..
7 Commits
Author SHA1 Message Date
shamoon da82f26c14 Add the clear to permissions dropdown 2026-09-02 20:27:32 -07:00
shamoon 405cad4617 Add a couple e2e tests just in case 2026-09-02 20:27:32 -07:00
shamoon 3b4db07488 CSS fixes 2026-09-02 20:27:32 -07:00
shamoon d0195f888f And the CF dropdown 2026-09-02 20:27:31 -07:00
shamoon 3003b9e05e Same type of fix for permissions filter dropdown 2026-09-02 20:27:31 -07:00
shamoon 2fa48494c2 Fix the date dropdown 2026-09-02 20:27:31 -07:00
shamoon b0540b5fdc Bump ng-select 2026-09-02 20:27:30 -07:00
118 changed files with 26863 additions and 26541 deletions
+1
View File
@@ -38,6 +38,7 @@ body:
label: Installation method
options:
- Docker - official image
- Docker - linuxserver.io image
- Bare metal
- Other (please describe above)
- type: textarea
+2
View File
@@ -87,8 +87,10 @@ body:
label: Installation method
options:
- Docker - official image
- Docker - linuxserver.io image
- Bare metal
- Other (please describe above)
description: Note there are significant differences from the official image and linuxserver.io, please check if your issue is specific to the third-party image.
validations:
required: true
- type: textarea
+2 -2
View File
@@ -24,7 +24,7 @@ services:
network_mode: host
restart: unless-stopped
greenmail:
image: docker.io/greenmail/standalone:2.1.13
image: docker.io/greenmail/standalone:2.1.11
hostname: greenmail
container_name: greenmail
environment:
@@ -35,7 +35,7 @@ services:
- "3143:3143" # IMAP
restart: unless-stopped
nginx:
image: docker.io/nginx:1.31.5-alpine
image: docker.io/nginx:1.31.3-alpine
hostname: nginx
container_name: nginx
ports:
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "paperless-ngx"
version = "3.1.3"
version = "3.1.2"
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
readme = "README.md"
requires-python = ">=3.11"
+11
View File
@@ -48,6 +48,7 @@ def seed_database() -> None:
from django.utils import timezone
from documents.models import Correspondent
from documents.models import CustomField
from documents.models import Document
from documents.models import DocumentType
from documents.models import Note
@@ -81,6 +82,16 @@ def seed_database() -> None:
path="e2e/{created_year}/{title}",
owner=admin,
)
CustomField.objects.create(
name="Test Select Field",
data_type=CustomField.FieldDataType.SELECT,
extra_data={
"select_options": [
{"id": "abc123", "label": "Alpha"},
{"id": "def456", "label": "Beta"},
],
},
)
today = timezone.localdate()
documents = []
@@ -207,3 +207,41 @@ test('bulk edit', async ({ page }) => {
await page.getByRole('button', { name: 'Confirm' }).click()
await bulkEditPromise
})
test('permissions dropdown stays open when selecting a user', async ({
page,
}) => {
await page.goto('/documents')
await page.getByRole('button', { name: 'Permissions' }).click()
const dropdown = page.locator(
'pngx-permissions-filter-dropdown .dropdown-menu'
)
await expect(dropdown).toBeVisible()
await dropdown.locator('ng-select').click()
await page.getByRole('option', { name: 'playwright' }).click()
await expect(dropdown).toBeVisible()
await expect(dropdown.locator('.ng-value-label')).toHaveText('playwright')
})
test('custom fields query dropdown stays open when building a query', async ({
page,
}) => {
await page.goto('/documents')
await page.getByRole('button', { name: 'Custom fields' }).click()
const dropdown = page.locator(
'pngx-custom-fields-query-dropdown .dropdown-menu'
)
await expect(dropdown).toBeVisible()
// field picker
await dropdown.locator('ng-select').first().click()
await page.getByRole('option', { name: 'Test Select Field' }).click()
await expect(dropdown).toBeVisible()
// value picker, shown once the operator takes a list of options
await dropdown.locator('select').first().selectOption({ label: 'In' })
await dropdown.locator('ng-select').last().click()
await page.getByRole('option', { name: 'Alpha' }).click()
await expect(dropdown).toBeVisible()
await expect(dropdown.locator('.ng-value-label').last()).toHaveText('Alpha')
})
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "paperless-ngx-ui",
"version": "3.1.3",
"version": "3.1.2",
"scripts": {
"preinstall": "npx only-allow pnpm",
"ng": "ng",
@@ -24,7 +24,7 @@
"@angular/platform-browser": "~22.1.3",
"@angular/router": "~22.1.3",
"@ng-bootstrap/ng-bootstrap": "^21.0.0",
"@ng-select/ng-select": "~23.6.0",
"@ng-select/ng-select": "~24.0.2",
"@ngneat/dirty-check-forms": "^3.0.3",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.8",
+7 -5
View File
@@ -36,8 +36,8 @@ importers:
specifier: ^21.0.0
version: 21.0.0(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/forms@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/platform-browser@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2)))(rxjs@7.8.2))(@angular/localize@22.1.3(@angular/compiler-cli@22.1.3(@angular/compiler@22.1.3)(typescript@6.0.3))(@angular/compiler@22.1.3))(@popperjs/core@2.11.8)(rxjs@7.8.2)
'@ng-select/ng-select':
specifier: ~23.6.0
version: 23.6.0(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/forms@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/platform-browser@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2)))(rxjs@7.8.2))
specifier: ~24.0.2
version: 24.0.2(@angular/cdk@22.1.4(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/platform-browser@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2)))(rxjs@7.8.2))(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/forms@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/platform-browser@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2)))(rxjs@7.8.2))
'@ngneat/dirty-check-forms':
specifier: ^3.0.3
version: 3.0.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/forms@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/platform-browser@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2)))(rxjs@7.8.2))(@angular/router@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/platform-browser@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2)))(rxjs@7.8.2))(lodash-es@4.18.1)(rxjs@7.8.2)
@@ -1490,10 +1490,11 @@ packages:
'@popperjs/core': ^2.11.8
rxjs: ^6.5.3 || ^7.4.0
'@ng-select/ng-select@23.6.0':
resolution: {integrity: sha512-0I84OZ9gncphNR26zuqOwpB/XWhPav+IP+EfJd9lDNeu3UT9Y0JhTBburCXEXcbltwQkiN6bMCKdRF93hrFHxw==}
'@ng-select/ng-select@24.0.2':
resolution: {integrity: sha512-EcOQanLp5dNxkDOxK75z7+B3gZW8LvQofzwL5AgWUn04EkJOXRI7Wi6Ylp7+jIAi9yPsTwjznrRd+wSl9EZGAQ==}
engines: {node: ^22.12.0 || ^24.0.0 || ^26.0.0}
peerDependencies:
'@angular/cdk': ^22.0.0
'@angular/common': ^22.0.0
'@angular/core': ^22.0.0
'@angular/forms': ^22.0.0
@@ -5769,8 +5770,9 @@ snapshots:
rxjs: 7.8.2
tslib: 2.8.1
'@ng-select/ng-select@23.6.0(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/forms@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/platform-browser@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2)))(rxjs@7.8.2))':
'@ng-select/ng-select@24.0.2(@angular/cdk@22.1.4(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/platform-browser@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2)))(rxjs@7.8.2))(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/forms@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/platform-browser@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2)))(rxjs@7.8.2))':
dependencies:
'@angular/cdk': 22.1.4(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/platform-browser@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2)))(rxjs@7.8.2)
'@angular/common': 22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2)
'@angular/core': 22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2)
'@angular/forms': 22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/platform-browser@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2)))(rxjs@7.8.2)
@@ -6,7 +6,7 @@
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge>
}
</button>
<div class="px-3 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown_{{name}}">
<div [id]="dropdownMenuId" class="px-3 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown_{{name}}">
<ng-container *ngTemplateOutlet="list; context: { queries: selectionModel.queries }"></ng-container>
</div>
</div>
@@ -64,10 +64,11 @@
[disabled]="disabled"
[virtualScroll]="getSelectOptionsForField(atom.field)?.length > 100"
[searchFn]="selectOptionSearchFn"
[appendTo]="selectAppendTo"
(mousedown)="$event.stopImmediatePropagation()"
></ng-select>
} @else if (getCustomFieldByID(atom.field)?.data_type === CustomFieldDataType.DocumentLink) {
<pngx-input-document-link [(ngModel)]="atom.value" class="w-25 form-select doc-link-select p-0" placeholder="Search docs..." i18n-placeholder [minimal]="true"></pngx-input-document-link>
<pngx-input-document-link [(ngModel)]="atom.value" class="w-25 form-select doc-link-select p-0" placeholder="Search docs..." i18n-placeholder [minimal]="true" [appendTo]="selectAppendTo"></pngx-input-document-link>
} @else if (getCustomFieldByID(atom.field)?.data_type === CustomFieldDataType.Monetary) {
<input class="w-25 form-control rounded-end" type="text" inputmode="decimal"
[ngModel]="atom.value"
@@ -88,6 +89,7 @@
bindLabel="name"
bindValue="id"
[searchFn]="customFieldSearchFn"
[appendTo]="selectAppendTo"
(mousedown)="$event.stopImmediatePropagation()"
></ng-select>
<select class="w-25 form-select" [(ngModel)]="atom.operator" [disabled]="disabled">
@@ -121,7 +123,7 @@
<ng-container *ngTemplateOutlet="comparisonValueTemplate; context: { atom: atom }"></ng-container>
}
@case (CustomFieldQueryOperator.Contains) {
<pngx-input-document-link [(ngModel)]="atom.value" class="w-25 form-select doc-link-select p-0" placeholder="Search docs..." i18n-placeholder [minimal]="true"></pngx-input-document-link>
<pngx-input-document-link [(ngModel)]="atom.value" class="w-25 form-select doc-link-select p-0" placeholder="Search docs..." i18n-placeholder [minimal]="true" [appendTo]="selectAppendTo"></pngx-input-document-link>
}
@case (CustomFieldQueryOperator.In) {
<ng-select
@@ -133,6 +135,7 @@
[disabled]="disabled"
[multiple]="true"
[searchFn]="selectOptionSearchFn"
[appendTo]="selectAppendTo"
(mousedown)="$event.stopImmediatePropagation()"
></ng-select>
}
@@ -1,3 +1,4 @@
import { _IdGenerator } from '@angular/cdk/a11y'
import {
getLocaleNumberSymbol,
NgClass,
@@ -250,6 +251,18 @@ export class CustomFieldsQueryDropdownComponent extends LoadingComponentWithPerm
@Input()
useDropdown: boolean = true
private readonly idGenerator = inject(_IdGenerator)
public readonly dropdownMenuId = this.idGenerator.getId(
'pngx-custom-fields-query-dropdown-'
)
/**
* Keep ng-select dropdown panels inside the dropdown menu
*/
get selectAppendTo(): string {
return this.useDropdown ? `#${this.dropdownMenuId}` : null
}
get name(): string {
return this.title ? this.title.replace(/\s/g, '_').toLowerCase() : null
}
@@ -3,7 +3,7 @@
<i-bs width="1em" height="1em" name="calendar-event-fill"></i-bs><div class="d-none d-sm-inline ms-1">{{title}}</div>
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge><span class="visually-hidden">selected</span>
</button>
<div class="dropdown-menu date-dropdown shadow p-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
<div [id]="dropdownMenuId" class="dropdown-menu date-dropdown shadow p-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
<h6 class="dropdown-header border-bottom" i18n>Created</h6>
<div class="list-group list-group-flush">
<div class="list-group-item d-flex p-2 select-item" role="menuitem">
@@ -21,6 +21,7 @@
bindValue="id"
bindLabel="name"
clearable="false"
[appendTo]="'#' + dropdownMenuId"
placeholder="Relative dates"
i18n-placeholder
(change)="onSetCreatedRelativeDate($event)">
@@ -97,6 +98,7 @@
bindValue="id"
bindLabel="name"
clearable="false"
[appendTo]="'#' + dropdownMenuId"
placeholder="Relative dates"
i18n-placeholder
(change)="onSetAddedRelativeDate($event)">
@@ -23,6 +23,7 @@ import { SettingsService } from 'src/app/services/settings.service'
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
import { pngxPopperOptions } from 'src/app/utils/popper-options'
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
import { _IdGenerator } from '@angular/cdk/a11y'
export interface DateSelection {
createdTo?: string
@@ -68,6 +69,10 @@ export enum RelativeDate {
})
export class DatesDropdownComponent implements OnInit, OnDestroy {
public popperOptions = pngxPopperOptions
private readonly idGenerator = inject(_IdGenerator)
public readonly dropdownMenuId = this.idGenerator.getId(
'pngx-dates-dropdown-'
)
constructor() {
const settings = inject(SettingsService)
@@ -33,6 +33,7 @@
[compareWith]="compareDocuments"
[trackByFn]="trackByFn"
[minTermLength]="2"
[appendTo]="appendTo"
[loading]="loading()"
[typeahead]="documentsInput$"
(mousedown)="$event.stopImmediatePropagation()"
@@ -81,6 +81,12 @@ export class DocumentLinkComponent
@Input()
placeholder: string = $localize`Search for documents`
/**
* Parent for ng-select dropdown, needed to prevent close on click.
*/
@Input()
appendTo: string = null
get selectedDocumentIDs(): number[] {
return this.selectedDocuments.map((d) => d.id)
}
@@ -22,7 +22,7 @@
}
// Dropdown hierarchy reveal for ng-select options
:host ::ng-deep .ng-dropdown-panel .ng-option {
::ng-deep .ng-dropdown-panel .ng-option {
overflow-x: auto !important;
.tag-option-row {
@@ -41,12 +41,12 @@
}
}
:host ::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-reveal,
:host ::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-reveal {
::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-reveal,
::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-reveal {
max-width: 1000px;
}
::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-indicator,
:host ::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-indicator {
::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-indicator {
background: transparent;
}
@@ -3,7 +3,7 @@
<i-bs name="person-fill-lock"></i-bs><div class="d-none d-sm-inline ms-1">{{title}}</div>
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge><span class="visually-hidden">selected</span>
</button>
<div class="dropdown-menu permission-filter-dropdown shadow py-0 w-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
<div [id]="dropdownMenuId" class="dropdown-menu permission-filter-dropdown shadow py-0 w-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
<div class="list-group list-group-flush">
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.NONE)" [disabled]="disabled">
<div class="selected-icon me-1">
@@ -58,7 +58,10 @@
<button *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }" class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" [disabled]="disabled">
<div class="selected-icon me-1">
@if (selectionModel.ownerFilter() === OwnerFilterType.OTHERS) {
<i-bs width="1em" height="1em" name="check"></i-bs>
<a class="text-reset focus-variants" href="javascript:void(0)" (click)="clearIncludeUsers()" aria-label="Clear selected users" i18n-aria-label>
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
</a>
}
</div>
<div class="me-1 w-100">
@@ -69,6 +72,7 @@
(ngModelChange)="selectionModel.includeUsers.set($event)"
[disabled]="disabled"
[clearable]="false"
[appendTo]="'#' + dropdownMenuId"
[items]="users()"
bindLabel="username"
multiple="true"
@@ -6,3 +6,18 @@
min-width: 1em;
min-height: 1em;
}
.focus-variants {
.variant-focused {
display: none;
}
&:hover, &:focus {
.variant-unfocused {
display: none;
}
.variant-focused {
display: block;
}
}
}
@@ -171,6 +171,21 @@ describe('PermissionsFilterDropdownComponent', () => {
expect(component.selectionModel.ownerFilter()).toEqual(OwnerFilterType.NONE)
})
it('should clear selected users', () => {
component.selectionModel.includeUsers.set([12])
component.onUserSelect()
component.selectionModel.hideUnowned.set(true)
expect(component.selectionModel.ownerFilter()).toEqual(
OwnerFilterType.OTHERS
)
component.clearIncludeUsers()
expect(component.selectionModel.includeUsers()).toEqual([])
expect(component.selectionModel.ownerFilter()).toEqual(OwnerFilterType.NONE)
expect(component.selectionModel.hideUnowned()).toBeTruthy()
})
it('should emit a selection model depending on the type of owner filter set', () => {
const emitted = () => ({
excludeUsers: ownerFilterSetResult.excludeUsers(),
@@ -1,3 +1,4 @@
import { _IdGenerator } from '@angular/cdk/a11y'
import { NgClass } from '@angular/common'
import {
Component,
@@ -70,6 +71,11 @@ export class PermissionsFilterDropdownComponent extends ComponentWithPermissions
public OwnerFilterType = OwnerFilterType
private readonly idGenerator = inject(_IdGenerator)
public readonly dropdownMenuId = this.idGenerator.getId(
'pngx-permissions-filter-dropdown-'
)
@Input()
title: string
@@ -205,6 +211,11 @@ export class PermissionsFilterDropdownComponent extends ComponentWithPermissions
this.ownerFilterSet.emit(this.selectionModel)
}
clearIncludeUsers() {
this.selectionModel.includeUsers.set([])
this.onUserSelect()
}
onUserSelect() {
this.selectionModel.ownerFilter.set(
this.selectionModel.includeUsers()?.length
+1 -1
View File
@@ -8,7 +8,7 @@ export const environment = {
apiVersion: '10', // match src/paperless/settings.py
appTitle: DEFAULT_APP_TITLE,
tag: 'prod',
version: '3.1.3',
version: '3.1.2',
webSocketHost: window.location.host,
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
webSocketBaseUrl: base_url.pathname + 'ws/',
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+16 -11
View File
@@ -374,17 +374,6 @@ th[pngxSortable] {
}
}
.ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-marked {
background-color: var(--pngx-bg-darker) !important;
color: var(--pngx-body-color-accent) !important;
}
.ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected,
.ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected.ng-option-marked {
font-weight: bold;
background-color: var(--pngx-bg-alt2) !important;
}
&.ng-select-multiple .ng-select-container .ng-value-container .ng-placeholder,
.ng-select-container .ng-value-container .ng-placeholder {
top: 7px;
@@ -402,6 +391,22 @@ th[pngxSortable] {
}
}
// ng-select renders the dropdown panel in a CDK overlay outside the component
.ng-dropdown-panel {
font-size: var(--pngx-body-font-size);
.ng-dropdown-panel-items .ng-option.ng-option-marked {
background-color: var(--pngx-bg-darker) !important;
color: var(--pngx-body-color-accent) !important;
}
.ng-dropdown-panel-items .ng-option.ng-option-selected,
.ng-dropdown-panel-items .ng-option.ng-option-selected.ng-option-marked {
font-weight: bold;
background-color: var(--pngx-bg-alt2) !important;
}
}
.paperless-input-tags {
.ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value {
background-color: transparent;
+2 -2
View File
@@ -237,8 +237,8 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,%3csvg xmlns='h
mix-blend-mode: luminosity;
}
.paperless-input-select .ng-select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option:not(.ng-option-selected):hover,
.paperless-input-select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-marked {
.ng-dropdown-panel .ng-dropdown-panel-items .ng-option:not(.ng-option-selected):hover,
.ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-marked {
background-color: var(--bs-light) !important;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More