mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-09-16 21:55:37 -05:00
Merge branch 'dev' into feature-ai
This commit is contained in:
@@ -176,6 +176,7 @@
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<pngx-input-check i18n-title title="Show warning when closing saved views with unsaved changes" formControlName="savedViewsWarnOnUnsavedChange"></pngx-input-check>
|
||||
<pngx-input-check i18n-title title="Show document counts in sidebar saved views" formControlName="sidebarViewsShowCount"></pngx-input-check>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@@ -31,10 +31,12 @@ import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||
import { GroupService } from 'src/app/services/rest/group.service'
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { SystemStatusService } from 'src/app/services/system-status.service'
|
||||
import { Toast, ToastService } from 'src/app/services/toast.service'
|
||||
import * as navUtils from 'src/app/utils/navigation'
|
||||
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
|
||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||
import { CheckComponent } from '../../common/input/check/check.component'
|
||||
@@ -72,6 +74,7 @@ describe('SettingsComponent', () => {
|
||||
let groupService: GroupService
|
||||
let modalService: NgbModal
|
||||
let systemStatusService: SystemStatusService
|
||||
let savedViewsService: SavedViewService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -122,6 +125,7 @@ describe('SettingsComponent', () => {
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
modalService = TestBed.inject(NgbModal)
|
||||
systemStatusService = TestBed.inject(SystemStatusService)
|
||||
savedViewsService = TestBed.inject(SavedViewService)
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
jest
|
||||
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
|
||||
@@ -212,7 +216,7 @@ describe('SettingsComponent', () => {
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
expect(storeSpy).toHaveBeenCalled()
|
||||
expect(appearanceSettingsSpy).not.toHaveBeenCalled()
|
||||
expect(setSpy).toHaveBeenCalledTimes(29)
|
||||
expect(setSpy).toHaveBeenCalledTimes(30)
|
||||
|
||||
// succeed
|
||||
storeSpy.mockReturnValueOnce(of(true))
|
||||
@@ -222,6 +226,9 @@ describe('SettingsComponent', () => {
|
||||
})
|
||||
|
||||
it('should offer reload if settings changes require', () => {
|
||||
const reloadSpy = jest
|
||||
.spyOn(navUtils, 'locationReload')
|
||||
.mockImplementation(() => {})
|
||||
completeSetup()
|
||||
let toast: Toast
|
||||
toastService.getToasts().subscribe((t) => (toast = t[0]))
|
||||
@@ -238,6 +245,7 @@ describe('SettingsComponent', () => {
|
||||
|
||||
expect(toast.actionName).toEqual('Reload now')
|
||||
toast.action()
|
||||
expect(reloadSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should allow setting theme color, visually apply change immediately but not save', () => {
|
||||
@@ -266,7 +274,7 @@ describe('SettingsComponent', () => {
|
||||
)
|
||||
completeSetup(userService)
|
||||
fixture.detectChanges()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should show errors on load if load groups failure', () => {
|
||||
@@ -278,7 +286,7 @@ describe('SettingsComponent', () => {
|
||||
)
|
||||
completeSetup(groupService)
|
||||
fixture.detectChanges()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should load system status on initialize, show errors if needed', () => {
|
||||
@@ -348,4 +356,14 @@ describe('SettingsComponent', () => {
|
||||
component.reset()
|
||||
expect(component.settingsForm.get('themeColor').value).toEqual('')
|
||||
})
|
||||
|
||||
it('should trigger maybeRefreshDocumentCounts on settings save', () => {
|
||||
completeSetup()
|
||||
const maybeRefreshSpy = jest.spyOn(
|
||||
savedViewsService,
|
||||
'maybeRefreshDocumentCounts'
|
||||
)
|
||||
settingsService.settingsSaved.emit(true)
|
||||
expect(maybeRefreshSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
@@ -49,6 +49,7 @@ import {
|
||||
PermissionsService,
|
||||
} from 'src/app/services/permissions.service'
|
||||
import { GroupService } from 'src/app/services/rest/group.service'
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import {
|
||||
LanguageOption,
|
||||
@@ -56,6 +57,7 @@ import {
|
||||
} from 'src/app/services/settings.service'
|
||||
import { SystemStatusService } from 'src/app/services/system-status.service'
|
||||
import { Toast, ToastService } from 'src/app/services/toast.service'
|
||||
import { locationReload } from 'src/app/utils/navigation'
|
||||
import { CheckComponent } from '../../common/input/check/check.component'
|
||||
import { ColorComponent } from '../../common/input/color/color.component'
|
||||
import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component'
|
||||
@@ -117,6 +119,7 @@ export class SettingsComponent
|
||||
permissionsService = inject(PermissionsService)
|
||||
private modalService = inject(NgbModal)
|
||||
private systemStatusService = inject(SystemStatusService)
|
||||
private savedViewsService = inject(SavedViewService)
|
||||
|
||||
activeNavID: number
|
||||
|
||||
@@ -152,6 +155,7 @@ export class SettingsComponent
|
||||
notificationsConsumerSuppressOnDashboard: new FormControl(null),
|
||||
|
||||
savedViewsWarnOnUnsavedChange: new FormControl(null),
|
||||
sidebarViewsShowCount: new FormControl(null),
|
||||
})
|
||||
|
||||
SettingsNavIDs = SettingsNavIDs
|
||||
@@ -197,6 +201,7 @@ export class SettingsComponent
|
||||
super()
|
||||
this.settings.settingsSaved.subscribe(() => {
|
||||
if (!this.savePending) this.initialize()
|
||||
this.savedViewsService.maybeRefreshDocumentCounts()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -308,6 +313,9 @@ export class SettingsComponent
|
||||
savedViewsWarnOnUnsavedChange: this.settings.get(
|
||||
SETTINGS_KEYS.SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE
|
||||
),
|
||||
sidebarViewsShowCount: this.settings.get(
|
||||
SETTINGS_KEYS.SIDEBAR_VIEWS_SHOW_COUNT
|
||||
),
|
||||
defaultPermsOwner: this.settings.get(SETTINGS_KEYS.DEFAULT_PERMS_OWNER),
|
||||
defaultPermsViewUsers: this.settings.get(
|
||||
SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS
|
||||
@@ -485,6 +493,10 @@ export class SettingsComponent
|
||||
SETTINGS_KEYS.SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE,
|
||||
this.settingsForm.value.savedViewsWarnOnUnsavedChange
|
||||
)
|
||||
this.settings.set(
|
||||
SETTINGS_KEYS.SIDEBAR_VIEWS_SHOW_COUNT,
|
||||
this.settingsForm.value.sidebarViewsShowCount
|
||||
)
|
||||
this.settings.set(
|
||||
SETTINGS_KEYS.DEFAULT_PERMS_OWNER,
|
||||
this.settingsForm.value.defaultPermsOwner
|
||||
@@ -539,7 +551,7 @@ export class SettingsComponent
|
||||
savedToast.content = $localize`Settings were saved successfully. Reload is required to apply some changes.`
|
||||
savedToast.actionName = $localize`Reload now`
|
||||
savedToast.action = () => {
|
||||
location.reload()
|
||||
locationReload()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -19,6 +19,7 @@ import { GroupService } from 'src/app/services/rest/group.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import * as navUtils from 'src/app/utils/navigation'
|
||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
|
||||
import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
|
||||
@@ -107,7 +108,7 @@ describe('UsersAndGroupsComponent', () => {
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
editDialog.failed.emit()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
settingsService.currentUser = users[1] // simulate logged in as different user
|
||||
editDialog.succeeded.emit(users[0])
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith(
|
||||
@@ -130,7 +131,7 @@ describe('UsersAndGroupsComponent', () => {
|
||||
throwError(() => new Error('error deleting user'))
|
||||
)
|
||||
deleteDialog.confirm()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
deleteSpy.mockReturnValueOnce(of(true))
|
||||
deleteDialog.confirm()
|
||||
expect(listAllSpy).toHaveBeenCalled()
|
||||
@@ -142,19 +143,18 @@ describe('UsersAndGroupsComponent', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||
component.editUser(users[0])
|
||||
const navSpy = jest
|
||||
.spyOn(navUtils, 'setLocationHref')
|
||||
.mockImplementation(() => {})
|
||||
const editDialog = modal.componentInstance as UserEditDialogComponent
|
||||
editDialog.passwordIsSet = true
|
||||
settingsService.currentUser = users[0] // simulate logged in as same user
|
||||
editDialog.succeeded.emit(users[0])
|
||||
fixture.detectChanges()
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
href: 'http://localhost/',
|
||||
},
|
||||
writable: true, // possibility to override
|
||||
})
|
||||
tick(2600)
|
||||
expect(window.location.href).toContain('logout')
|
||||
expect(navSpy).toHaveBeenCalledWith(
|
||||
`${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
|
||||
)
|
||||
}))
|
||||
|
||||
it('should support edit / create group, show error if needed', () => {
|
||||
@@ -166,7 +166,7 @@ describe('UsersAndGroupsComponent', () => {
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
editDialog.failed.emit()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
editDialog.succeeded.emit(groups[0])
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith(
|
||||
`Saved group "${groups[0].name}".`
|
||||
@@ -188,7 +188,7 @@ describe('UsersAndGroupsComponent', () => {
|
||||
throwError(() => new Error('error deleting group'))
|
||||
)
|
||||
deleteDialog.confirm()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
deleteSpy.mockReturnValueOnce(of(true))
|
||||
deleteDialog.confirm()
|
||||
expect(listAllSpy).toHaveBeenCalled()
|
||||
@@ -210,7 +210,7 @@ describe('UsersAndGroupsComponent', () => {
|
||||
)
|
||||
completeSetup(userService)
|
||||
fixture.detectChanges()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should show errors on load if load groups failure', () => {
|
||||
@@ -222,6 +222,6 @@ describe('UsersAndGroupsComponent', () => {
|
||||
)
|
||||
completeSetup(groupService)
|
||||
fixture.detectChanges()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
@@ -10,6 +10,7 @@ import { GroupService } from 'src/app/services/rest/group.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { setLocationHref } from 'src/app/utils/navigation'
|
||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
|
||||
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
|
||||
@@ -93,7 +94,9 @@ export class UsersAndGroupsComponent
|
||||
$localize`Password has been changed, you will be logged out momentarily.`
|
||||
)
|
||||
setTimeout(() => {
|
||||
window.location.href = `${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
|
||||
setLocationHref(
|
||||
`${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
|
||||
)
|
||||
}, 2500)
|
||||
} else {
|
||||
this.toastService.showInfo(
|
||||
|
@@ -115,7 +115,14 @@
|
||||
routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name"
|
||||
[disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave"
|
||||
popoverClass="popover-slim">
|
||||
<i-bs class="me-1" name="funnel"></i-bs><span> {{view.name}}</span>
|
||||
<i-bs class="me-1" name="funnel"></i-bs><span> {{view.name}}
|
||||
@if (showSidebarCounts && !slimSidebarEnabled) {
|
||||
<span><span class="badge bg-info text-dark ms-2 d-inline">{{ savedViewService.getDocumentCount(view) }}</span></span>
|
||||
}
|
||||
</span>
|
||||
@if (showSidebarCounts && slimSidebarEnabled) {
|
||||
<span class="badge bg-info text-dark position-absolute top-0 end-0 d-none d-md-block">{{ savedViewService.getDocumentCount(view) }}</span>
|
||||
}
|
||||
</a>
|
||||
@if (settingsService.organizingSidebarSavedViews) {
|
||||
<div class="position-absolute end-0 top-0 px-3 py-2" [class.me-n3]="slimSidebarEnabled" cdkDragHandle>
|
||||
|
@@ -92,6 +92,7 @@ describe('AppFrameComponent', () => {
|
||||
let router: Router
|
||||
let savedViewSpy
|
||||
let modalService: NgbModal
|
||||
let maybeRefreshSpy
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -113,7 +114,11 @@ describe('AppFrameComponent', () => {
|
||||
{
|
||||
provide: SavedViewService,
|
||||
useValue: {
|
||||
reload: () => {},
|
||||
reload: (fn: any) => {
|
||||
if (fn) {
|
||||
fn()
|
||||
}
|
||||
},
|
||||
listAll: () =>
|
||||
of({
|
||||
all: [saved_views.map((v) => v.id)],
|
||||
@@ -121,6 +126,8 @@ describe('AppFrameComponent', () => {
|
||||
results: saved_views,
|
||||
}),
|
||||
sidebarViews: saved_views.filter((v) => v.show_in_sidebar),
|
||||
getDocumentCount: (view: SavedView) => 5,
|
||||
maybeRefreshDocumentCounts: () => {},
|
||||
},
|
||||
},
|
||||
PermissionsService,
|
||||
@@ -169,6 +176,7 @@ describe('AppFrameComponent', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
|
||||
savedViewSpy = jest.spyOn(savedViewService, 'reload')
|
||||
maybeRefreshSpy = jest.spyOn(savedViewService, 'maybeRefreshDocumentCounts')
|
||||
|
||||
fixture = TestBed.createComponent(AppFrameComponent)
|
||||
component = fixture.componentInstance
|
||||
@@ -359,4 +367,8 @@ describe('AppFrameComponent', () => {
|
||||
expect(toastErrorSpy).toHaveBeenCalledTimes(2)
|
||||
expect(toastInfoSpy).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('should call maybeRefreshDocumentCounts after saved views reload', () => {
|
||||
expect(maybeRefreshSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
@@ -104,7 +104,9 @@ export class AppFrameComponent
|
||||
PermissionType.SavedView
|
||||
)
|
||||
) {
|
||||
this.savedViewService.reload()
|
||||
this.savedViewService.reload(() => {
|
||||
this.savedViewService.maybeRefreshDocumentCounts()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,4 +291,8 @@ export class AppFrameComponent
|
||||
onLogout() {
|
||||
this.openDocumentsService.closeAll()
|
||||
}
|
||||
|
||||
get showSidebarCounts(): boolean {
|
||||
return this.settingsService.get(SETTINGS_KEYS.SIDEBAR_VIEWS_SHOW_COUNT)
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@
|
||||
}
|
||||
<div class="list-group-item">
|
||||
<div class="input-group input-group-sm">
|
||||
<input class="form-control" type="text" [(ngModel)]="filterText" [placeholder]="filterPlaceholder" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
|
||||
<input class="form-control" type="text" spellcheck="false" [(ngModel)]="filterText" [placeholder]="filterPlaceholder" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
|
||||
</div>
|
||||
</div>
|
||||
@if (selectionModel.items) {
|
||||
|
@@ -18,6 +18,7 @@ import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { of, throwError } from 'rxjs'
|
||||
import { ProfileService } from 'src/app/services/profile.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import * as navUtils from 'src/app/utils/navigation'
|
||||
import { ConfirmButtonComponent } from '../confirm-button/confirm-button.component'
|
||||
import { PasswordComponent } from '../input/password/password.component'
|
||||
import { TextComponent } from '../input/text/text.component'
|
||||
@@ -205,16 +206,15 @@ describe('ProfileEditDialogComponent', () => {
|
||||
|
||||
const updateSpy = jest.spyOn(profileService, 'update')
|
||||
updateSpy.mockReturnValue(of(null))
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
href: 'http://localhost/',
|
||||
},
|
||||
writable: true, // possibility to override
|
||||
})
|
||||
const navSpy = jest
|
||||
.spyOn(navUtils, 'setLocationHref')
|
||||
.mockImplementation(() => {})
|
||||
component.save()
|
||||
expect(updateSpy).toHaveBeenCalled()
|
||||
tick(2600)
|
||||
expect(window.location.href).toContain('logout')
|
||||
expect(navSpy).toHaveBeenCalledWith(
|
||||
`${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
|
||||
)
|
||||
}))
|
||||
|
||||
it('should support auth token copy', fakeAsync(() => {
|
||||
|
@@ -21,6 +21,7 @@ import {
|
||||
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
||||
import { ProfileService } from 'src/app/services/profile.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { setLocationHref } from 'src/app/utils/navigation'
|
||||
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
|
||||
import { ConfirmButtonComponent } from '../confirm-button/confirm-button.component'
|
||||
import { PasswordComponent } from '../input/password/password.component'
|
||||
@@ -194,7 +195,9 @@ export class ProfileEditDialogComponent
|
||||
$localize`Password has been changed, you will be logged out momentarily.`
|
||||
)
|
||||
setTimeout(() => {
|
||||
window.location.href = `${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
|
||||
setLocationHref(
|
||||
`${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
|
||||
)
|
||||
}, 2500)
|
||||
}
|
||||
this.activeModal.close()
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<pngx-widget-frame
|
||||
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }"
|
||||
[title]="savedView.name"
|
||||
[badge]="count"
|
||||
[loading]="loading"
|
||||
[draggable]="savedView"
|
||||
>
|
||||
|
@@ -118,6 +118,8 @@ export class SavedViewWidgetComponent
|
||||
|
||||
displayFields: DisplayField[] = DEFAULT_DASHBOARD_DISPLAY_FIELDS
|
||||
|
||||
count: number
|
||||
|
||||
ngOnInit(): void {
|
||||
this.reload()
|
||||
this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE
|
||||
@@ -178,6 +180,7 @@ export class SavedViewWidgetComponent
|
||||
tap((result) => {
|
||||
this.show = true
|
||||
this.documents = result.results
|
||||
this.count = result.count
|
||||
}),
|
||||
delay(500)
|
||||
)
|
||||
|
@@ -2,13 +2,16 @@
|
||||
<div class="card shadow-sm bg-light fade" [class.show]="show" cdkDrag [cdkDragDisabled]="!draggable" cdkDragPreviewContainer="parent">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex">
|
||||
<div class="d-flex align-items-center">
|
||||
@if (draggable) {
|
||||
<div class="ms-n2 me-1" cdkDragHandle>
|
||||
<i-bs name="grip-vertical"></i-bs>
|
||||
</div>
|
||||
}
|
||||
<h6 class="card-title mb-0">{{title}}</h6>
|
||||
@if (badge) {
|
||||
<span class="badge bg-info text-dark ms-2">{{badge}}</span>
|
||||
}
|
||||
</div>
|
||||
@if (loading) {
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
|
@@ -30,6 +30,9 @@ export class WidgetFrameComponent
|
||||
@Input()
|
||||
cardless: boolean = false
|
||||
|
||||
@Input()
|
||||
badge: string
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
setTimeout(() => {
|
||||
this.show = true
|
||||
|
@@ -1073,6 +1073,22 @@ describe('DocumentDetailComponent', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should restore changed fields and mark as dirty', () => {
|
||||
jest
|
||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap({ id: 3, section: 'details' })))
|
||||
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(doc))
|
||||
const docWithChanges = Object.assign({}, doc)
|
||||
docWithChanges.__changedFields = ['title', 'tags', 'owner']
|
||||
jest
|
||||
.spyOn(openDocumentsService, 'getOpenDocument')
|
||||
.mockReturnValue(docWithChanges)
|
||||
fixture.detectChanges() // calls ngOnInit
|
||||
expect(component.documentForm.get('title').dirty).toBeTruthy()
|
||||
expect(component.documentForm.get('tags').dirty).toBeTruthy()
|
||||
expect(component.documentForm.get('permissions_form').dirty).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should show custom field errors', () => {
|
||||
initNormally()
|
||||
component.error = {
|
||||
|
@@ -73,6 +73,7 @@ import { CorrespondentService } from 'src/app/services/rest/correspondent.servic
|
||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||
import { TagService } from 'src/app/services/rest/tag.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
@@ -200,6 +201,7 @@ export class DocumentDetailComponent
|
||||
private hotKeyService = inject(HotKeyService)
|
||||
private componentRouterService = inject(ComponentRouterService)
|
||||
private deviceDetectorService = inject(DeviceDetectorService)
|
||||
private savedViewService = inject(SavedViewService)
|
||||
|
||||
@ViewChild('inputTitle')
|
||||
titleInput: TextComponent
|
||||
@@ -461,6 +463,15 @@ export class DocumentDetailComponent
|
||||
]
|
||||
delete openDocument['permissions_form']
|
||||
}
|
||||
if (openDocument.__changedFields) {
|
||||
openDocument.__changedFields.forEach((field) => {
|
||||
if (field === 'owner' || field === 'set_permissions') {
|
||||
this.documentForm.get('permissions_form').markAsDirty()
|
||||
} else {
|
||||
this.documentForm.get(field)?.markAsDirty()
|
||||
}
|
||||
})
|
||||
}
|
||||
this.updateComponent(openDocument)
|
||||
} else {
|
||||
this.openDocumentService.openDocument(doc)
|
||||
@@ -524,7 +535,7 @@ export class DocumentDetailComponent
|
||||
)
|
||||
.subscribe({
|
||||
next: ({ doc, dirty }) => {
|
||||
this.openDocumentService.setDirty(doc, dirty)
|
||||
this.openDocumentService.setDirty(doc, dirty, this.getChangedFields())
|
||||
},
|
||||
error: (error) => {
|
||||
this.router.navigate(['404'], {
|
||||
@@ -902,6 +913,7 @@ export class DocumentDetailComponent
|
||||
} else {
|
||||
this.openDocumentService.refreshDocument(this.documentId)
|
||||
}
|
||||
this.savedViewService.maybeRefreshDocumentCounts()
|
||||
},
|
||||
error: (error) => {
|
||||
this.networkActive = false
|
||||
@@ -1249,6 +1261,7 @@ export class DocumentDetailComponent
|
||||
notesUpdated(notes: DocumentNote[]) {
|
||||
this.document.notes = notes
|
||||
this.openDocumentService.refreshDocument(this.documentId)
|
||||
this.savedViewService.maybeRefreshDocumentCounts()
|
||||
}
|
||||
|
||||
get userIsOwner(): boolean {
|
||||
|
@@ -32,6 +32,7 @@ import {
|
||||
DocumentService,
|
||||
SelectionDataItem,
|
||||
} from 'src/app/services/rest/document.service'
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||
import { TagService } from 'src/app/services/rest/tag.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
@@ -83,6 +84,7 @@ export class BulkEditorComponent
|
||||
private storagePathService = inject(StoragePathService)
|
||||
private customFieldService = inject(CustomFieldsService)
|
||||
private permissionService = inject(PermissionsService)
|
||||
private savedViewService = inject(SavedViewService)
|
||||
|
||||
tagSelectionModel = new FilterableDropdownSelectionModel(true)
|
||||
correspondentSelectionModel = new FilterableDropdownSelectionModel()
|
||||
@@ -270,6 +272,7 @@ export class BulkEditorComponent
|
||||
this.list.selected.forEach((id) => {
|
||||
this.openDocumentService.refreshDocument(id)
|
||||
})
|
||||
this.savedViewService.maybeRefreshDocumentCounts()
|
||||
if (modal) {
|
||||
modal.close()
|
||||
}
|
||||
|
@@ -188,7 +188,7 @@ describe('MailComponent', () => {
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
editDialog.failed.emit()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
editDialog.succeeded.emit(mailAccounts[0] as any)
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith(
|
||||
`Saved account "${mailAccounts[0].name}".`
|
||||
@@ -211,7 +211,7 @@ describe('MailComponent', () => {
|
||||
throwError(() => new Error('error deleting mail account'))
|
||||
)
|
||||
deleteDialog.confirm()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
deleteSpy.mockReturnValueOnce(of(true))
|
||||
deleteDialog.confirm()
|
||||
expect(listAllSpy).toHaveBeenCalled()
|
||||
@@ -246,7 +246,7 @@ describe('MailComponent', () => {
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
editDialog.failed.emit()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
editDialog.succeeded.emit(mailRules[0] as any)
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith(
|
||||
`Saved rule "${mailRules[0].name}".`
|
||||
@@ -280,7 +280,7 @@ describe('MailComponent', () => {
|
||||
throwError(() => new Error('error deleting mail rule "rule1"'))
|
||||
)
|
||||
deleteDialog.confirm()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
deleteSpy.mockReturnValueOnce(of(true))
|
||||
deleteDialog.confirm()
|
||||
expect(listAllSpy).toHaveBeenCalled()
|
||||
|
Reference in New Issue
Block a user