mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-07-09 13:35:10 +00:00
All these
This commit is contained in:
@@ -9,16 +9,16 @@
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
[(ngModel)]="query"
|
||||
(ngModelChange)="this.queryDebounce.next($event)"
|
||||
(ngModelChange)="queryDebounce.next($event)"
|
||||
(keydown)="searchInputKeyDown($event)"
|
||||
ngbDropdownAnchor>
|
||||
<div class="position-absolute top-50 end-0 translate-middle">
|
||||
@if (loading) {
|
||||
@if (loading()) {
|
||||
<div class="spinner-border spinner-border-sm text-muted mt-1"></div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (query) {
|
||||
@if (query()) {
|
||||
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="runFullSearch()">
|
||||
@if (useAdvancedForFullSearch) {
|
||||
<ng-container i18n>Advanced search</ng-container>
|
||||
@@ -77,88 +77,88 @@
|
||||
|
||||
<div ngbDropdownMenu class="w-100 mh-75 overflow-y-scroll shadow-lg">
|
||||
<div (keydown)="dropdownKeyDown($event)">
|
||||
@if (searchResults?.total === 0) {
|
||||
@if (searchResults()?.total === 0) {
|
||||
<h6 class="dropdown-header" i18n="@@searchResults.noResults">No results</h6>
|
||||
} @else {
|
||||
@if (searchResults?.documents.length) {
|
||||
@if (searchResults()?.documents.length) {
|
||||
<h6 class="dropdown-header" i18n="@@searchResults.documents">Documents</h6>
|
||||
@for (document of searchResults.documents; track document.id) {
|
||||
@for (document of searchResults().documents; track document.id) {
|
||||
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: document, nameProp: 'title', type: DataType.Document, icon: 'file-text', date: document.created}"></ng-container>
|
||||
}
|
||||
}
|
||||
@if (searchResults?.saved_views.length) {
|
||||
@if (searchResults()?.saved_views.length) {
|
||||
<h6 class="dropdown-header" i18n="@@searchResults.saved_views">Saved Views</h6>
|
||||
@for (saved_view of searchResults.saved_views; track saved_view.id) {
|
||||
@for (saved_view of searchResults().saved_views; track saved_view.id) {
|
||||
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: saved_view, nameProp: 'name', type: DataType.SavedView, icon: 'funnel'}"></ng-container>
|
||||
}
|
||||
}
|
||||
|
||||
@if (searchResults?.tags.length) {
|
||||
@if (searchResults()?.tags.length) {
|
||||
<h6 class="dropdown-header" i18n="@@searchResults.tags">Tags</h6>
|
||||
@for (tag of searchResults.tags; track tag.id) {
|
||||
@for (tag of searchResults().tags; track tag.id) {
|
||||
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: tag, nameProp: 'name', type: DataType.Tag, icon: 'tag'}"></ng-container>
|
||||
}
|
||||
}
|
||||
|
||||
@if (searchResults?.correspondents.length) {
|
||||
@if (searchResults()?.correspondents.length) {
|
||||
<h6 class="dropdown-header" i18n="@@searchResults.correspondents">Correspondents</h6>
|
||||
@for (correspondent of searchResults.correspondents; track correspondent.id) {
|
||||
@for (correspondent of searchResults().correspondents; track correspondent.id) {
|
||||
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: correspondent, nameProp: 'name', type: DataType.Correspondent, icon: 'person'}"></ng-container>
|
||||
}
|
||||
}
|
||||
|
||||
@if (searchResults?.document_types.length) {
|
||||
@if (searchResults()?.document_types.length) {
|
||||
<h6 class="dropdown-header" i18n="@@searchResults.documentTypes">Document types</h6>
|
||||
@for (documentType of searchResults.document_types; track documentType.id) {
|
||||
@for (documentType of searchResults().document_types; track documentType.id) {
|
||||
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: documentType, nameProp: 'name', type: DataType.DocumentType, icon: 'file-earmark'}"></ng-container>
|
||||
}
|
||||
}
|
||||
|
||||
@if (searchResults?.storage_paths.length) {
|
||||
@if (searchResults()?.storage_paths.length) {
|
||||
<h6 class="dropdown-header" i18n="@@searchResults.storagePaths">Storage paths</h6>
|
||||
@for (storagePath of searchResults.storage_paths; track storagePath.id) {
|
||||
@for (storagePath of searchResults().storage_paths; track storagePath.id) {
|
||||
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: storagePath, nameProp: 'name', type: DataType.StoragePath, icon: 'folder'}"></ng-container>
|
||||
}
|
||||
}
|
||||
|
||||
@if (searchResults?.users.length) {
|
||||
@if (searchResults()?.users.length) {
|
||||
<h6 class="dropdown-header" i18n="@@searchResults.users">Users</h6>
|
||||
@for (user of searchResults.users; track user.id) {
|
||||
@for (user of searchResults().users; track user.id) {
|
||||
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: user, nameProp: 'username', type: DataType.User, icon: 'person-square'}"></ng-container>
|
||||
}
|
||||
}
|
||||
|
||||
@if (searchResults?.groups.length) {
|
||||
@if (searchResults()?.groups.length) {
|
||||
<h6 class="dropdown-header" i18n="@@searchResults.groups">Groups</h6>
|
||||
@for (group of searchResults.groups; track group.id) {
|
||||
@for (group of searchResults().groups; track group.id) {
|
||||
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: group, nameProp: 'name', type: DataType.Group, icon: 'people'}"></ng-container>
|
||||
}
|
||||
}
|
||||
|
||||
@if (searchResults?.custom_fields.length) {
|
||||
@if (searchResults()?.custom_fields.length) {
|
||||
<h6 class="dropdown-header" i18n="@@searchResults.customFields">Custom fields</h6>
|
||||
@for (customField of searchResults.custom_fields; track customField.id) {
|
||||
@for (customField of searchResults().custom_fields; track customField.id) {
|
||||
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: customField, nameProp: 'name', type: DataType.CustomField, icon: 'ui-radios'}"></ng-container>
|
||||
}
|
||||
}
|
||||
|
||||
@if (searchResults?.mail_accounts.length) {
|
||||
@if (searchResults()?.mail_accounts.length) {
|
||||
<h6 class="dropdown-header" i18n="@@searchResults.mailAccounts">Mail accounts</h6>
|
||||
@for (mailAccount of searchResults.mail_accounts; track mailAccount.id) {
|
||||
@for (mailAccount of searchResults().mail_accounts; track mailAccount.id) {
|
||||
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: mailAccount, nameProp: 'name', type: DataType.MailAccount, icon: 'envelope-at'}"></ng-container>
|
||||
}
|
||||
}
|
||||
|
||||
@if (searchResults?.mail_rules.length) {
|
||||
@if (searchResults()?.mail_rules.length) {
|
||||
<h6 class="dropdown-header" i18n="@@searchResults.mailRules">Mail rules</h6>
|
||||
@for (mailRule of searchResults.mail_rules; track mailRule.id) {
|
||||
@for (mailRule of searchResults().mail_rules; track mailRule.id) {
|
||||
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: mailRule, nameProp: 'name', type: DataType.MailRule, icon: 'envelope'}"></ng-container>
|
||||
}
|
||||
}
|
||||
|
||||
@if (searchResults?.workflows.length) {
|
||||
@if (searchResults()?.workflows.length) {
|
||||
<h6 class="dropdown-header" i18n="@@searchResults.workflows">Workflows</h6>
|
||||
@for (workflow of searchResults.workflows; track workflow.id) {
|
||||
@for (workflow of searchResults().workflows; track workflow.id) {
|
||||
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: workflow, nameProp: 'name', type: DataType.Workflow, icon: 'boxes'}"></ng-container>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ describe('GlobalSearchComponent', () => {
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: '/' }))
|
||||
expect(focusSpy).toHaveBeenCalled()
|
||||
|
||||
component.searchResults = searchResults as any
|
||||
component.searchResults.set(searchResults as any)
|
||||
component.resultsDropdown.open()
|
||||
fixture.detectChanges()
|
||||
|
||||
@@ -238,26 +238,26 @@ describe('GlobalSearchComponent', () => {
|
||||
)
|
||||
expect(component['currentItemIndex']).toBe(0)
|
||||
|
||||
component.searchResults = { total: 1 } as any
|
||||
component.searchResults.set({ total: 1 } as any)
|
||||
const primaryActionSpy = jest.spyOn(component, 'primaryAction')
|
||||
component.searchInputKeyDown(new KeyboardEvent('keydown', { key: 'Enter' }))
|
||||
expect(primaryActionSpy).toHaveBeenCalled()
|
||||
|
||||
component.query = 'test'
|
||||
component.query.set('test')
|
||||
const resetSpy = jest.spyOn(GlobalSearchComponent.prototype as any, 'reset')
|
||||
component.searchInputKeyDown(
|
||||
new KeyboardEvent('keydown', { key: 'Escape' })
|
||||
)
|
||||
expect(resetSpy).toHaveBeenCalled()
|
||||
|
||||
component.query = ''
|
||||
component.query.set('')
|
||||
const blurSpy = jest.spyOn(component.searchInput.nativeElement, 'blur')
|
||||
component.searchInputKeyDown(
|
||||
new KeyboardEvent('keydown', { key: 'Escape' })
|
||||
)
|
||||
expect(blurSpy).toHaveBeenCalled()
|
||||
|
||||
component.searchResults = { total: 1 } as any
|
||||
component.searchResults.set({ total: 1 } as any)
|
||||
component.resultsDropdown.open()
|
||||
|
||||
component.searchInputKeyDown(
|
||||
@@ -268,9 +268,9 @@ describe('GlobalSearchComponent', () => {
|
||||
component.dropdownKeyDown(new KeyboardEvent('keydown', { key: 'Escape' }))
|
||||
expect(closeSpy).toHaveBeenCalled()
|
||||
|
||||
component.searchResults = searchResults as any
|
||||
component.searchResults.set(searchResults as any)
|
||||
component.resultsDropdown.open()
|
||||
component.query = 'test'
|
||||
component.query.set('test')
|
||||
const advancedSearchSpy = jest.spyOn(component, 'runFullSearch')
|
||||
component.searchInputKeyDown(new KeyboardEvent('keydown', { key: 'Enter' }))
|
||||
expect(advancedSearchSpy).toHaveBeenCalled()
|
||||
@@ -471,7 +471,7 @@ describe('GlobalSearchComponent', () => {
|
||||
})
|
||||
|
||||
it('should support focus current item', () => {
|
||||
component.searchResults = searchResults as any
|
||||
component.searchResults.set(searchResults as any)
|
||||
fixture.detectChanges()
|
||||
const focusSpy = jest.spyOn(
|
||||
component.primaryButtons.get(0).nativeElement,
|
||||
@@ -489,7 +489,7 @@ describe('GlobalSearchComponent', () => {
|
||||
})
|
||||
|
||||
it('should focus button on dropdown item hover', () => {
|
||||
component.searchResults = searchResults as any
|
||||
component.searchResults.set(searchResults as any)
|
||||
fixture.detectChanges()
|
||||
const item: ElementRef = component.resultItems.first
|
||||
const focusSpy = jest.spyOn(
|
||||
@@ -515,7 +515,7 @@ describe('GlobalSearchComponent', () => {
|
||||
component.primaryAction(DataType.Document, { id: 2 }, event as any)
|
||||
expect(openSpy).toHaveBeenCalledWith('/documents/2', '_blank')
|
||||
|
||||
component.searchResults = searchResults as any
|
||||
component.searchResults.set(searchResults as any)
|
||||
component.resultsDropdown.open()
|
||||
fixture.detectChanges()
|
||||
|
||||
@@ -542,7 +542,7 @@ describe('GlobalSearchComponent', () => {
|
||||
|
||||
it('should support title content search and advanced search', () => {
|
||||
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
|
||||
component.query = 'test'
|
||||
component.query.set('test')
|
||||
component.runFullSearch()
|
||||
expect(qfSpy).toHaveBeenCalledWith([
|
||||
{ rule_type: FILTER_SIMPLE_TEXT, value: 'test' },
|
||||
@@ -552,7 +552,7 @@ describe('GlobalSearchComponent', () => {
|
||||
SETTINGS_KEYS.SEARCH_FULL_TYPE,
|
||||
GlobalSearchType.ADVANCED
|
||||
)
|
||||
component.query = 'test'
|
||||
component.query.set('test')
|
||||
component.runFullSearch()
|
||||
expect(qfSpy).toHaveBeenCalledWith([
|
||||
{ rule_type: FILTER_FULLTEXT_QUERY, value: 'test' },
|
||||
|
||||
@@ -83,36 +83,12 @@ export class GlobalSearchComponent implements OnInit {
|
||||
private locationStrategy = inject(LocationStrategy)
|
||||
|
||||
public DataType = DataType
|
||||
private querySignal = signal<string>(null)
|
||||
readonly query = signal<string>(null)
|
||||
public queryDebounce: Subject<string>
|
||||
private searchResultsSignal = signal<GlobalSearchResult>(null)
|
||||
readonly searchResults = signal<GlobalSearchResult>(null)
|
||||
private currentItemIndex: number = -1
|
||||
private domIndex: number = -1
|
||||
private loadingSignal = signal(false)
|
||||
|
||||
public get query(): string {
|
||||
return this.querySignal()
|
||||
}
|
||||
|
||||
public set query(value: string) {
|
||||
this.querySignal.set(value)
|
||||
}
|
||||
|
||||
public get searchResults(): GlobalSearchResult {
|
||||
return this.searchResultsSignal()
|
||||
}
|
||||
|
||||
public set searchResults(value: GlobalSearchResult) {
|
||||
this.searchResultsSignal.set(value)
|
||||
}
|
||||
|
||||
public get loading(): boolean {
|
||||
return this.loadingSignal()
|
||||
}
|
||||
|
||||
public set loading(value: boolean) {
|
||||
this.loadingSignal.set(value)
|
||||
}
|
||||
readonly loading = signal(false)
|
||||
|
||||
@ViewChild('searchInput') searchInput: ElementRef
|
||||
@ViewChild('resultsDropdown') resultsDropdown: NgbDropdown
|
||||
@@ -138,7 +114,7 @@ export class GlobalSearchComponent implements OnInit {
|
||||
distinctUntilChanged()
|
||||
)
|
||||
.subscribe((text) => {
|
||||
this.query = text
|
||||
this.query.set(text)
|
||||
if (text) this.search(text)
|
||||
})
|
||||
}
|
||||
@@ -152,10 +128,10 @@ export class GlobalSearchComponent implements OnInit {
|
||||
}
|
||||
|
||||
private search(query: string) {
|
||||
this.loading = true
|
||||
this.loading.set(true)
|
||||
this.searchService.globalSearch(query.trim()).subscribe((results) => {
|
||||
this.searchResults = results
|
||||
this.loading = false
|
||||
this.searchResults.set(results)
|
||||
this.loading.set(false)
|
||||
this.resultsDropdown.open()
|
||||
})
|
||||
}
|
||||
@@ -282,8 +258,8 @@ export class GlobalSearchComponent implements OnInit {
|
||||
|
||||
private reset(close: boolean = false) {
|
||||
this.queryDebounce.next(null)
|
||||
this.query = null
|
||||
this.searchResults = null
|
||||
this.query.set(null)
|
||||
this.searchResults.set(null)
|
||||
this.currentItemIndex = -1
|
||||
if (close) {
|
||||
this.resultsDropdown.close()
|
||||
@@ -318,7 +294,7 @@ export class GlobalSearchComponent implements OnInit {
|
||||
public searchInputKeyDown(event: KeyboardEvent) {
|
||||
if (
|
||||
event.key === 'ArrowDown' &&
|
||||
this.searchResults?.total &&
|
||||
this.searchResults()?.total &&
|
||||
this.resultsDropdown.isOpen()
|
||||
) {
|
||||
event.preventDefault()
|
||||
@@ -326,22 +302,22 @@ export class GlobalSearchComponent implements OnInit {
|
||||
this.setCurrentItem()
|
||||
} else if (
|
||||
event.key === 'ArrowUp' &&
|
||||
this.searchResults?.total &&
|
||||
this.searchResults()?.total &&
|
||||
this.resultsDropdown.isOpen()
|
||||
) {
|
||||
event.preventDefault()
|
||||
this.currentItemIndex = this.searchResults.total - 1
|
||||
this.currentItemIndex = this.searchResults()?.total - 1
|
||||
this.setCurrentItem()
|
||||
} else if (event.key === 'Enter') {
|
||||
if (this.searchResults?.total === 1 && this.resultsDropdown.isOpen()) {
|
||||
if (this.searchResults()?.total === 1 && this.resultsDropdown.isOpen()) {
|
||||
this.primaryButtons.first.nativeElement.click()
|
||||
this.searchInput.nativeElement.blur()
|
||||
} else if (this.query?.length) {
|
||||
} else if (this.query()?.length) {
|
||||
this.runFullSearch()
|
||||
this.reset(true)
|
||||
}
|
||||
} else if (event.key === 'Escape' && !this.resultsDropdown.isOpen()) {
|
||||
if (this.query?.length) {
|
||||
if (this.query()?.length) {
|
||||
this.reset(true)
|
||||
} else {
|
||||
this.searchInput.nativeElement.blur()
|
||||
@@ -351,14 +327,14 @@ export class GlobalSearchComponent implements OnInit {
|
||||
|
||||
public dropdownKeyDown(event: KeyboardEvent) {
|
||||
if (
|
||||
this.searchResults?.total &&
|
||||
this.searchResults()?.total &&
|
||||
this.resultsDropdown.isOpen() &&
|
||||
document.activeElement !== this.searchInput.nativeElement
|
||||
) {
|
||||
if (event.key === 'ArrowDown') {
|
||||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
if (this.currentItemIndex < this.searchResults.total - 1) {
|
||||
if (this.currentItemIndex < this.searchResults()?.total - 1) {
|
||||
this.currentItemIndex++
|
||||
this.setCurrentItem()
|
||||
} else {
|
||||
@@ -438,10 +414,10 @@ export class GlobalSearchComponent implements OnInit {
|
||||
? FILTER_FULLTEXT_QUERY
|
||||
: FILTER_SIMPLE_TEXT
|
||||
this.documentService.searchQuery = this.useAdvancedForFullSearch
|
||||
? this.query
|
||||
? this.query()
|
||||
: ''
|
||||
this.documentListViewService.quickFilter([
|
||||
{ rule_type: ruleType, value: this.query },
|
||||
{ rule_type: ruleType, value: this.query() },
|
||||
])
|
||||
this.reset(true)
|
||||
}
|
||||
|
||||
@@ -2,31 +2,31 @@
|
||||
<div class="col-md text-truncate">
|
||||
<h3 class="d-flex align-items-center mb-1" style="line-height: 1.4">
|
||||
<span class="text-truncate">{{title}}</span>
|
||||
@if (id) {
|
||||
@if (id()) {
|
||||
<span class="badge bg-primary text-primary-text-contrast ms-3 small fs-normal cursor-pointer" (click)="copyID()">
|
||||
@if (copied) {
|
||||
@if (copied()) {
|
||||
<i-bs width="1em" height="1em" name="clipboard-check" class="me-1"></i-bs><ng-container i18n>Copied!</ng-container>
|
||||
} @else {
|
||||
ID: {{id}}
|
||||
ID: {{id()}}
|
||||
}
|
||||
</span>
|
||||
}
|
||||
@if (subTitle) {
|
||||
<span class="h6 mb-0 mt-1 d-block d-md-inline fw-normal ms-md-3 text-truncate" style="line-height: 1.4">{{subTitle}}</span>
|
||||
@if (subTitle()) {
|
||||
<span class="h6 mb-0 mt-1 d-block d-md-inline fw-normal ms-md-3 text-truncate" style="line-height: 1.4">{{subTitle()}}</span>
|
||||
}
|
||||
@if (info) {
|
||||
@if (info()) {
|
||||
<button class="btn btn-sm btn-link text-muted p-0 p-md-2" title="What's this?" i18n-title type="button" [ngbPopover]="infoPopover" [autoClose]="true">
|
||||
<i-bs name="question-circle"></i-bs>
|
||||
</button>
|
||||
<ng-template #infoPopover>
|
||||
<p [class.mb-0]="!infoLink" [innerHTML]="info"></p>
|
||||
@if (infoLink) {
|
||||
<a href="https://docs.paperless-ngx.com/{{infoLink}}" target="_blank" referrerpolicy="noopener noreferrer" i18n>Read more</a>
|
||||
<p [class.mb-0]="!infoLink()" [innerHTML]="info()"></p>
|
||||
@if (infoLink()) {
|
||||
<a href="https://docs.paperless-ngx.com/{{infoLink()}}" target="_blank" referrerpolicy="noopener noreferrer" i18n>Read more</a>
|
||||
<i-bs class="ms-1" width=".8em" height=".8em" name="box-arrow-up-right"></i-bs>
|
||||
}
|
||||
</ng-template>
|
||||
}
|
||||
@if (loading) {
|
||||
@if (loading()) {
|
||||
<output class="spinner-border spinner-border-sm fs-6 fw-normal" aria-hidden="true"><span class="visually-hidden" i18n>Loading...</span></output>
|
||||
}
|
||||
</h3>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Clipboard } from '@angular/cdk/clipboard'
|
||||
import { Component, Input, inject, signal } from '@angular/core'
|
||||
import { Component, Input, inject, input, signal } from '@angular/core'
|
||||
import { Title } from '@angular/platform-browser'
|
||||
import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
@@ -16,83 +16,31 @@ export class PageHeaderComponent {
|
||||
private titleService = inject(Title)
|
||||
private clipboard = inject(Clipboard)
|
||||
|
||||
private titleSignal = signal('')
|
||||
private idSignal = signal<number>(undefined)
|
||||
private subTitleSignal = signal('')
|
||||
private infoSignal = signal<string>(undefined)
|
||||
private infoLinkSignal = signal<string>(undefined)
|
||||
private loadingSignal = signal(false)
|
||||
private copiedSignal = signal(false)
|
||||
readonly id = input<number>(undefined)
|
||||
readonly subTitle = input('')
|
||||
readonly info = input<string>(undefined)
|
||||
readonly infoLink = input<string>(undefined)
|
||||
readonly loading = input(false)
|
||||
readonly copied = signal(false)
|
||||
private copyTimeout: any
|
||||
|
||||
public get copied(): boolean {
|
||||
return this.copiedSignal()
|
||||
}
|
||||
|
||||
public set copied(copied: boolean) {
|
||||
this.copiedSignal.set(copied)
|
||||
}
|
||||
private readonly titleSignal = signal('')
|
||||
|
||||
@Input()
|
||||
set title(title: string) {
|
||||
this.titleSignal.set(title)
|
||||
this.titleService.setTitle(`${this.title} - ${environment.appTitle}`)
|
||||
this.titleService.setTitle(`${title} - ${environment.appTitle}`)
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this.titleSignal()
|
||||
}
|
||||
|
||||
@Input()
|
||||
get id(): number {
|
||||
return this.idSignal()
|
||||
}
|
||||
|
||||
set id(id: number) {
|
||||
this.idSignal.set(id)
|
||||
}
|
||||
|
||||
@Input()
|
||||
get subTitle(): string {
|
||||
return this.subTitleSignal()
|
||||
}
|
||||
|
||||
set subTitle(subTitle: string) {
|
||||
this.subTitleSignal.set(subTitle)
|
||||
}
|
||||
|
||||
@Input()
|
||||
get info(): string {
|
||||
return this.infoSignal()
|
||||
}
|
||||
|
||||
set info(info: string) {
|
||||
this.infoSignal.set(info)
|
||||
}
|
||||
|
||||
@Input()
|
||||
get infoLink(): string {
|
||||
return this.infoLinkSignal()
|
||||
}
|
||||
|
||||
set infoLink(infoLink: string) {
|
||||
this.infoLinkSignal.set(infoLink)
|
||||
}
|
||||
|
||||
@Input()
|
||||
get loading(): boolean {
|
||||
return this.loadingSignal()
|
||||
}
|
||||
|
||||
set loading(loading: boolean) {
|
||||
this.loadingSignal.set(loading)
|
||||
}
|
||||
|
||||
public copyID() {
|
||||
this.copied = this.clipboard.copy(this.id.toString())
|
||||
this.copied.set(this.clipboard.copy(this.id().toString()))
|
||||
clearTimeout(this.copyTimeout)
|
||||
this.copyTimeout = setTimeout(() => {
|
||||
this.copied = false
|
||||
this.copied.set(false)
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -1,15 +1,15 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
|
||||
<h4 class="modal-title" id="modal-basic-title">{{title()}}</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0">
|
||||
<ul class="list-group list-group-flush">
|
||||
@if (!shareLinks || shareLinks.length === 0) {
|
||||
@if (!shareLinks() || shareLinks().length === 0) {
|
||||
<li class="list-group-item fst-italic small text-center text-secondary" i18n>
|
||||
No existing links
|
||||
</li>
|
||||
}
|
||||
@for (link of shareLinks; track link) {
|
||||
@for (link of shareLinks(); track link) {
|
||||
<li class="list-group-item">
|
||||
<div class="input-group w-100">
|
||||
<input type="text" class="form-control" aria-label="Share link" [value]="getShareUrl(link)" readonly>
|
||||
@@ -19,10 +19,10 @@
|
||||
</span>
|
||||
}
|
||||
<button type="button" class="btn btn-outline-primary" (click)="copy(link)">
|
||||
@if (copied !== link.id) {
|
||||
@if (copied() !== link.id) {
|
||||
<i-bs width="1.2em" height="1.2em" name="clipboard-fill"></i-bs>
|
||||
}
|
||||
@if (copied === link.id) {
|
||||
@if (copied() === link.id) {
|
||||
<i-bs width="1.2em" height="1.2em" name="clipboard-check-fill"></i-bs>
|
||||
}
|
||||
<span class="visually-hidden" i18n>Copy</span>
|
||||
@@ -36,7 +36,7 @@
|
||||
<i-bs width="1.2em" height="1.2em" name="trash"></i-bs><span class="visually-hidden" i18n>Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
<span class="badge copied-badge bg-primary small fade ms-4 position-absolute top-50 translate-middle-y pe-none z-3" [class.show]="copied === link.id" i18n>Copied!</span>
|
||||
<span class="badge copied-badge bg-primary small fade ms-4 position-absolute top-50 translate-middle-y pe-none z-3" [class.show]="copied() === link.id" i18n>Copied!</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
@@ -44,7 +44,7 @@
|
||||
<div class="modal-footer">
|
||||
<div class="input-group w-100">
|
||||
<div class="form-check form-switch ms-auto">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="versionSwitch" [disabled]="!hasArchiveVersion" [(ngModel)]="useArchiveVersion">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="versionSwitch" [disabled]="!hasArchiveVersion()" [(ngModel)]="useArchiveVersion">
|
||||
<label class="form-check-label" for="versionSwitch" i18n>Share archive version</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,11 +55,11 @@
|
||||
<option [ngValue]="option.value">{{ option.label }}</option>
|
||||
}
|
||||
</select>
|
||||
<button class="btn btn-outline-primary ms-auto" type="button" (click)="createLink()" [disabled]="loading">
|
||||
@if (loading) {
|
||||
<button class="btn btn-outline-primary ms-auto" type="button" (click)="createLink()" [disabled]="loading()">
|
||||
@if (loading()) {
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
}
|
||||
@if (!loading) {
|
||||
@if (!loading()) {
|
||||
<i-bs name="plus"></i-bs>
|
||||
}
|
||||
<ng-container i18n>Create</ng-container>
|
||||
|
||||
+29
-68
@@ -1,5 +1,5 @@
|
||||
import { Clipboard } from '@angular/cdk/clipboard'
|
||||
import { Component, Input, OnInit, inject, signal } from '@angular/core'
|
||||
import { Component, OnInit, effect, inject, input, signal } from '@angular/core'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
@@ -27,79 +27,40 @@ export class ShareLinksDialogComponent implements OnInit {
|
||||
|
||||
readonly expirationOptions = SHARE_LINK_EXPIRATION_OPTIONS
|
||||
|
||||
private titleSignal = signal($localize`Share Links`)
|
||||
private documentIdSignal = signal<number>(undefined)
|
||||
private hasArchiveVersionSignal = signal(true)
|
||||
private shareLinksSignal = signal<ShareLink[]>(undefined)
|
||||
private copiedSignal = signal<number>(undefined)
|
||||
|
||||
@Input()
|
||||
get title(): string {
|
||||
return this.titleSignal()
|
||||
}
|
||||
|
||||
set title(title: string) {
|
||||
this.titleSignal.set(title)
|
||||
}
|
||||
|
||||
@Input()
|
||||
set documentId(id: number) {
|
||||
if (id !== undefined) {
|
||||
this.documentIdSignal.set(id)
|
||||
this.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
get documentId(): number {
|
||||
return this.documentIdSignal()
|
||||
}
|
||||
|
||||
@Input()
|
||||
set hasArchiveVersion(value: boolean) {
|
||||
this.hasArchiveVersionSignal.set(value)
|
||||
this.useArchiveVersion = value
|
||||
}
|
||||
|
||||
get hasArchiveVersion(): boolean {
|
||||
return this.hasArchiveVersionSignal()
|
||||
}
|
||||
|
||||
get shareLinks(): ShareLink[] {
|
||||
return this.shareLinksSignal()
|
||||
}
|
||||
|
||||
set shareLinks(shareLinks: ShareLink[]) {
|
||||
this.shareLinksSignal.set(shareLinks)
|
||||
}
|
||||
|
||||
loading: boolean = false
|
||||
|
||||
get copied(): number {
|
||||
return this.copiedSignal()
|
||||
}
|
||||
|
||||
set copied(copied: number) {
|
||||
this.copiedSignal.set(copied)
|
||||
}
|
||||
readonly title = input($localize`Share Links`)
|
||||
readonly documentId = input<number>(undefined)
|
||||
readonly hasArchiveVersion = input(true)
|
||||
readonly shareLinks = signal<ShareLink[]>(undefined)
|
||||
readonly loading = signal(false)
|
||||
readonly copied = signal<number>(undefined)
|
||||
|
||||
expirationDays: number = 7
|
||||
|
||||
useArchiveVersion: boolean = true
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const id = this.documentId()
|
||||
if (id !== undefined) this.refresh()
|
||||
})
|
||||
effect(() => {
|
||||
this.useArchiveVersion = this.hasArchiveVersion()
|
||||
})
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.documentId !== undefined) this.refresh()
|
||||
if (this.documentId() !== undefined) this.refresh()
|
||||
}
|
||||
|
||||
refresh() {
|
||||
if (this.documentId === undefined) return
|
||||
this.loading = true
|
||||
if (this.documentId() === undefined) return
|
||||
this.loading.set(true)
|
||||
this.shareLinkService
|
||||
.getLinksForDocument(this.documentId)
|
||||
.getLinksForDocument(this.documentId())
|
||||
.pipe(first())
|
||||
.subscribe({
|
||||
next: (results) => {
|
||||
this.loading = false
|
||||
this.shareLinks = results
|
||||
this.loading.set(false)
|
||||
this.shareLinks.set(results)
|
||||
},
|
||||
error: (e) => {
|
||||
this.toastService.showError(
|
||||
@@ -128,9 +89,9 @@ export class ShareLinksDialogComponent implements OnInit {
|
||||
copy(link: ShareLink) {
|
||||
const success = this.clipboard.copy(this.getShareUrl(link))
|
||||
if (success) {
|
||||
this.copied = link.id
|
||||
this.copied.set(link.id)
|
||||
setTimeout(() => {
|
||||
this.copied = null
|
||||
this.copied.set(null)
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
@@ -162,23 +123,23 @@ export class ShareLinksDialogComponent implements OnInit {
|
||||
expiration = new Date()
|
||||
expiration.setDate(expiration.getDate() + this.expirationDays)
|
||||
}
|
||||
this.loading = true
|
||||
this.loading.set(true)
|
||||
this.shareLinkService
|
||||
.createLinkForDocument(
|
||||
this.documentId,
|
||||
this.documentId(),
|
||||
this.useArchiveVersion ? FileVersion.Archive : FileVersion.Original,
|
||||
expiration
|
||||
)
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
setTimeout(() => {
|
||||
this.copy(result)
|
||||
}, 10)
|
||||
this.refresh()
|
||||
},
|
||||
error: (e) => {
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
this.toastService.showError($localize`Error creating link`, 10000, e)
|
||||
},
|
||||
})
|
||||
|
||||
+10
-10
@@ -1,6 +1,6 @@
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="clickSuggest()" [disabled]="disabled || loading || (suggestions && !aiEnabled)">
|
||||
@if (loading) {
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="clickSuggest()" [disabled]="disabled() || loading() || (suggestions() && !aiEnabled())">
|
||||
@if (loading()) {
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
} @else {
|
||||
<i-bs width="1.2em" height="1.2em" name="stars"></i-bs>
|
||||
@@ -13,32 +13,32 @@
|
||||
|
||||
@if (aiEnabled) {
|
||||
<div class="btn-group" ngbDropdown #dropdown="ngbDropdown" [popperOptions]="popperOptions">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" ngbDropdownToggle [disabled]="disabled || loading || !suggestions" aria-expanded="false" aria-controls="suggestionsDropdown" aria-label="Suggestions dropdown">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" ngbDropdownToggle [disabled]="disabled() || loading() || !suggestions()" aria-expanded="false" aria-controls="suggestionsDropdown" aria-label="Suggestions dropdown">
|
||||
<span class="visually-hidden" i18n>Show suggestions</span>
|
||||
</button>
|
||||
|
||||
<div ngbDropdownMenu aria-labelledby="suggestionsDropdown" class="shadow suggestions-dropdown">
|
||||
<div class="list-group list-group-flush small pb-0">
|
||||
@if (!suggestions?.suggested_tags && !suggestions?.suggested_document_types && !suggestions?.suggested_correspondents) {
|
||||
@if (!suggestions()?.suggested_tags && !suggestions()?.suggested_document_types && !suggestions()?.suggested_correspondents) {
|
||||
<div class="list-group-item text-muted fst-italic">
|
||||
<small class="text-muted small fst-italic" i18n>No novel suggestions</small>
|
||||
</div>
|
||||
}
|
||||
@if (suggestions?.suggested_tags.length > 0) {
|
||||
@if (suggestions()?.suggested_tags.length > 0) {
|
||||
<small class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="tags"></i-bs><ng-container i18n>Tags</ng-container></small>
|
||||
@for (tag of suggestions.suggested_tags; track tag) {
|
||||
@for (tag of suggestions().suggested_tags; track tag) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addTag.emit(tag)">{{ tag }}</button>
|
||||
}
|
||||
}
|
||||
@if (suggestions?.suggested_document_types.length > 0) {
|
||||
@if (suggestions()?.suggested_document_types.length > 0) {
|
||||
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="hash"></i-bs><ng-container i18n>Document Types</ng-container></div>
|
||||
@for (type of suggestions.suggested_document_types; track type) {
|
||||
@for (type of suggestions().suggested_document_types; track type) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addDocumentType.emit(type)">{{ type }}</button>
|
||||
}
|
||||
}
|
||||
@if (suggestions?.suggested_correspondents.length > 0) {
|
||||
@if (suggestions()?.suggested_correspondents.length > 0) {
|
||||
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="person"></i-bs><ng-container i18n>Correspondents</ng-container></div>
|
||||
@for (correspondent of suggestions.suggested_correspondents; track correspondent) {
|
||||
@for (correspondent of suggestions().suggested_correspondents; track correspondent) {
|
||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addCorrespondent.emit(correspondent)">{{ correspondent }}</button>
|
||||
}
|
||||
}
|
||||
|
||||
+12
-49
@@ -1,10 +1,9 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
ViewChild,
|
||||
signal,
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
@@ -21,46 +20,10 @@ export class SuggestionsDropdownComponent {
|
||||
public popperOptions = pngxPopperOptions
|
||||
|
||||
@ViewChild('dropdown') dropdown: NgbDropdown
|
||||
private suggestionsSignal = signal<DocumentSuggestions>(null)
|
||||
private aiEnabledSignal = signal(false)
|
||||
private loadingSignal = signal(false)
|
||||
private disabledSignal = signal(false)
|
||||
|
||||
@Input()
|
||||
get suggestions(): DocumentSuggestions {
|
||||
return this.suggestionsSignal()
|
||||
}
|
||||
|
||||
set suggestions(suggestions: DocumentSuggestions) {
|
||||
this.suggestionsSignal.set(suggestions)
|
||||
}
|
||||
|
||||
@Input()
|
||||
get aiEnabled(): boolean {
|
||||
return this.aiEnabledSignal()
|
||||
}
|
||||
|
||||
set aiEnabled(aiEnabled: boolean) {
|
||||
this.aiEnabledSignal.set(aiEnabled)
|
||||
}
|
||||
|
||||
@Input()
|
||||
get loading(): boolean {
|
||||
return this.loadingSignal()
|
||||
}
|
||||
|
||||
set loading(loading: boolean) {
|
||||
this.loadingSignal.set(loading)
|
||||
}
|
||||
|
||||
@Input()
|
||||
get disabled(): boolean {
|
||||
return this.disabledSignal()
|
||||
}
|
||||
|
||||
set disabled(disabled: boolean) {
|
||||
this.disabledSignal.set(disabled)
|
||||
}
|
||||
readonly suggestions = input<DocumentSuggestions>(null)
|
||||
readonly aiEnabled = input(false)
|
||||
readonly loading = input(false)
|
||||
readonly disabled = input(false)
|
||||
|
||||
@Output()
|
||||
getSuggestions: EventEmitter<SuggestionsDropdownComponent> =
|
||||
@@ -77,14 +40,14 @@ export class SuggestionsDropdownComponent {
|
||||
|
||||
public clickSuggest(): void {
|
||||
if (
|
||||
this.disabled ||
|
||||
this.loading ||
|
||||
(this.suggestions && !this.aiEnabled)
|
||||
this.disabled() ||
|
||||
this.loading() ||
|
||||
(this.suggestions() && !this.aiEnabled())
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.suggestions) {
|
||||
if (!this.suggestions()) {
|
||||
this.getSuggestions.emit(this)
|
||||
} else {
|
||||
this.dropdown?.toggle()
|
||||
@@ -93,9 +56,9 @@ export class SuggestionsDropdownComponent {
|
||||
|
||||
get totalSuggestions(): number {
|
||||
return (
|
||||
this.suggestions?.suggested_correspondents?.length +
|
||||
this.suggestions?.suggested_tags?.length +
|
||||
this.suggestions?.suggested_document_types?.length || 0
|
||||
this.suggestions()?.suggested_correspondents?.length +
|
||||
this.suggestions()?.suggested_tags?.length +
|
||||
this.suggestions()?.suggested_document_types?.length || 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,17 +1,17 @@
|
||||
@if (loading) {
|
||||
@if (loading()) {
|
||||
<div class="d-flex">
|
||||
<output class="spinner-border spinner-border-sm fw-normal" role="status"></output>
|
||||
</div>
|
||||
} @else {
|
||||
<ul class="list-group">
|
||||
@if (entries.length === 0) {
|
||||
@if (entries().length === 0) {
|
||||
<li class="list-group-item">
|
||||
<div class="d-flex justify-content-center">
|
||||
<span class="fst-italic" i18n>No entries found.</span>
|
||||
</div>
|
||||
</li>
|
||||
} @else {
|
||||
@for (entry of entries; track entry.id) {
|
||||
@for (entry of entries(); track entry.id) {
|
||||
<li class="list-group-item">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<ng-template #timestamp>
|
||||
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
import { AsyncPipe, KeyValuePipe, TitleCasePipe } from '@angular/common'
|
||||
import { Component, Input, OnInit, inject } from '@angular/core'
|
||||
import { Component, Input, OnInit, inject, signal } from '@angular/core'
|
||||
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { Observable, first, map, of, shareReplay } from 'rxjs'
|
||||
@@ -44,8 +44,8 @@ export class DocumentHistoryComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
public loading: boolean = true
|
||||
public entries: AuditLogEntry[] = []
|
||||
readonly loading = signal(true)
|
||||
readonly entries = signal<AuditLogEntry[]>([])
|
||||
|
||||
private readonly prettyNameCache = new Map<string, Observable<string>>()
|
||||
|
||||
@@ -55,10 +55,10 @@ export class DocumentHistoryComponent implements OnInit {
|
||||
|
||||
private loadHistory(): void {
|
||||
if (this._documentId) {
|
||||
this.loading = true
|
||||
this.loading.set(true)
|
||||
this.documentService.getHistory(this._documentId).subscribe((entries) => {
|
||||
this.entries = entries
|
||||
this.loading = false
|
||||
this.entries.set(entries)
|
||||
this.loading.set(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -10,13 +10,13 @@
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@if (loading) {
|
||||
@if (loading()) {
|
||||
<div class="text-center my-5">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden" i18n>Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
} @else if (processedMails.length === 0) {
|
||||
} @else if (processedMails().length === 0) {
|
||||
<span i18n>No processed email messages found.</span>
|
||||
} @else {
|
||||
<div class="table-responsive">
|
||||
@@ -25,7 +25,7 @@
|
||||
<tr>
|
||||
<th scope="col" style="width: 40px;">
|
||||
<div class="form-check m-0 ms-2 me-n2">
|
||||
<input type="checkbox" class="form-check-input" id="all-objects" [(ngModel)]="toggleAllEnabled" [disabled]="processedMails.length === 0" (click)="toggleAll($event); $event.stopPropagation();">
|
||||
<input type="checkbox" class="form-check-input" id="all-objects" [ngModel]="toggleAllEnabled()" (ngModelChange)="toggleAllEnabled.set($event)" [disabled]="processedMails().length === 0" (click)="toggleAll($event); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="all-objects"></label>
|
||||
</div>
|
||||
</th>
|
||||
@@ -37,7 +37,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (mail of processedMails; track mail.id) {
|
||||
@for (mail of processedMails(); track mail.id) {
|
||||
<ng-template #statusTooltip>
|
||||
<div class="small text-light font-monospace">
|
||||
{{mail.status}}
|
||||
@@ -46,7 +46,7 @@
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-check m-0 ms-2 me-n2">
|
||||
<input type="checkbox" class="form-check-input" [id]="mail.id" [checked]="selectedMailIds.has(mail.id)" (click)="toggleSelected(mail); $event.stopPropagation();">
|
||||
<input type="checkbox" class="form-check-input" [id]="mail.id" [checked]="selectedMailIds().has(mail.id)" (click)="toggleSelected(mail); $event.stopPropagation();">
|
||||
<label class="form-check-label" [for]="mail.id"></label>
|
||||
</div>
|
||||
</td>
|
||||
@@ -82,7 +82,7 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="btn-toolbar">
|
||||
<button type="button" class="btn btn-outline-secondary me-2" (click)="clearSelection()" [disabled]="selectedMailIds.size === 0" i18n>Clear</button>
|
||||
<button type="button" class="btn btn-outline-secondary me-2" (click)="clearSelection()" [disabled]="selectedMailIds().size === 0" i18n>Clear</button>
|
||||
<pngx-confirm-button
|
||||
label="Delete selected"
|
||||
i18n-label
|
||||
@@ -90,12 +90,12 @@
|
||||
i18n-title
|
||||
buttonClasses="btn-outline-danger"
|
||||
iconName="trash"
|
||||
[disabled]="selectedMailIds.size === 0"
|
||||
[disabled]="selectedMailIds().size === 0"
|
||||
(confirm)="deleteSelected()">
|
||||
</pngx-confirm-button>
|
||||
<div class="ms-auto">
|
||||
<ngb-pagination
|
||||
[collectionSize]="processedMails.length"
|
||||
[collectionSize]="processedMails().length"
|
||||
[(page)]="page"
|
||||
[pageSize]="50"
|
||||
[maxSize]="5"
|
||||
|
||||
+15
-43
@@ -36,39 +36,11 @@ export class ProcessedMailDialogComponent implements OnInit {
|
||||
private readonly processedMailService = inject(ProcessedMailService)
|
||||
private readonly toastService = inject(ToastService)
|
||||
|
||||
private ruleSignal = signal<MailRule>(undefined)
|
||||
private processedMailsSignal = signal<ProcessedMail[]>([])
|
||||
private loadingSignal = signal(true)
|
||||
private toggleAllEnabledSignal = signal(false)
|
||||
private selectedMailIdsSignal = signal<Set<number>>(new Set())
|
||||
|
||||
public get processedMails(): ProcessedMail[] {
|
||||
return this.processedMailsSignal()
|
||||
}
|
||||
|
||||
public set processedMails(processedMails: ProcessedMail[]) {
|
||||
this.processedMailsSignal.set(processedMails)
|
||||
}
|
||||
|
||||
public get loading(): boolean {
|
||||
return this.loadingSignal()
|
||||
}
|
||||
|
||||
public set loading(loading: boolean) {
|
||||
this.loadingSignal.set(loading)
|
||||
}
|
||||
|
||||
public get toggleAllEnabled(): boolean {
|
||||
return this.toggleAllEnabledSignal()
|
||||
}
|
||||
|
||||
public set toggleAllEnabled(toggleAllEnabled: boolean) {
|
||||
this.toggleAllEnabledSignal.set(toggleAllEnabled)
|
||||
}
|
||||
|
||||
public get selectedMailIds(): Set<number> {
|
||||
return this.selectedMailIdsSignal()
|
||||
}
|
||||
private readonly ruleSignal = signal<MailRule>(undefined)
|
||||
readonly processedMails = signal<ProcessedMail[]>([])
|
||||
readonly loading = signal(true)
|
||||
readonly toggleAllEnabled = signal(false)
|
||||
readonly selectedMailIds = signal<Set<number>>(new Set())
|
||||
|
||||
public page: number = 1
|
||||
|
||||
@@ -90,19 +62,19 @@ export class ProcessedMailDialogComponent implements OnInit {
|
||||
}
|
||||
|
||||
private loadProcessedMails(): void {
|
||||
this.loading = true
|
||||
this.loading.set(true)
|
||||
this.clearSelection()
|
||||
this.processedMailService
|
||||
.list(this.page, 50, 'processed_at', true, { rule: this.rule.id })
|
||||
.subscribe((result) => {
|
||||
this.processedMails = result.results
|
||||
this.loading = false
|
||||
this.processedMails.set(result.results)
|
||||
this.loading.set(false)
|
||||
})
|
||||
}
|
||||
|
||||
public deleteSelected(): void {
|
||||
this.processedMailService
|
||||
.bulk_delete(Array.from(this.selectedMailIds))
|
||||
.bulk_delete(Array.from(this.selectedMailIds()))
|
||||
.subscribe(() => {
|
||||
this.toastService.showInfo($localize`Processed mail(s) deleted`)
|
||||
this.loadProcessedMails()
|
||||
@@ -112,23 +84,23 @@ export class ProcessedMailDialogComponent implements OnInit {
|
||||
public toggleAll(event: PointerEvent) {
|
||||
const selectedMailIds = new Set<number>()
|
||||
if ((event.target as HTMLInputElement).checked) {
|
||||
this.processedMails.forEach((mail) => selectedMailIds.add(mail.id))
|
||||
this.selectedMailIdsSignal.set(selectedMailIds)
|
||||
this.processedMails().forEach((mail) => selectedMailIds.add(mail.id))
|
||||
this.selectedMailIds.set(selectedMailIds)
|
||||
} else {
|
||||
this.clearSelection()
|
||||
}
|
||||
}
|
||||
|
||||
public clearSelection() {
|
||||
this.toggleAllEnabled = false
|
||||
this.selectedMailIdsSignal.set(new Set())
|
||||
this.toggleAllEnabled.set(false)
|
||||
this.selectedMailIds.set(new Set())
|
||||
}
|
||||
|
||||
public toggleSelected(mail: ProcessedMail) {
|
||||
const selectedMailIds = new Set(this.selectedMailIds)
|
||||
const selectedMailIds = new Set(this.selectedMailIds())
|
||||
selectedMailIds.has(mail.id)
|
||||
? selectedMailIds.delete(mail.id)
|
||||
: selectedMailIds.add(mail.id)
|
||||
this.selectedMailIdsSignal.set(selectedMailIds)
|
||||
this.selectedMailIds.set(selectedMailIds)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user