mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-13 19:47:13 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05321b10d3 |
No files matched your search
@@ -29,8 +29,13 @@
|
||||
</div>
|
||||
<div class="search-container flex-grow-1 py-2 pb-3 pb-sm-2 me-sm-auto order-3 order-sm-1"
|
||||
[class.mobile-hidden]="mobileSearchHidden()">
|
||||
<div class="col-12 header-search mx-auto">
|
||||
<pngx-global-search></pngx-global-search>
|
||||
<div class="col-12 header-search mx-auto d-flex align-items-stretch gap-3">
|
||||
<pngx-global-search class="flex-grow-1"></pngx-global-search>
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Document }"
|
||||
class="btn btn-sm header-upload border-0 d-flex align-items-center justify-content-center flex-shrink-0 py-0 px-2" type="button"
|
||||
(click)="fileUpload.click()" aria-label="Upload documents" i18n-aria-label>
|
||||
<i-bs class="d-flex" width="1.1em" height="1.1em" name="file-earmark-arrow-up"></i-bs><span class="ms-2 d-sm-none d-md-inline" i18n>Upload</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul ngbNav class="order-sm-3">
|
||||
@@ -70,6 +75,8 @@
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<input type="file" class="visually-hidden" (change)="onFileSelected($event)" multiple #fileUpload tabindex="-1" aria-hidden="true">
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<nav id="sidebarMenu" class="d-md-block bg-light sidebar collapse"
|
||||
|
||||
@@ -532,7 +532,23 @@ main {
|
||||
|
||||
.header-search {
|
||||
width: 100%;
|
||||
max-width: 55rem;
|
||||
max-width: 50rem;
|
||||
}
|
||||
|
||||
.header-search pngx-global-search {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
// light w shadow to stand out against search
|
||||
.header-upload {
|
||||
--bs-btn-bg: rgba(255, 255, 255, .15);
|
||||
--bs-btn-hover-bg: rgba(255, 255, 255, .22);
|
||||
--bs-btn-active-bg: rgba(255, 255, 255, .3);
|
||||
color: var(--pngx-primary-text-contrast) !important; // .navbar .dropdown > .btn in styles.scss
|
||||
|
||||
&:not(:focus) {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .35), 0 1px 1px rgba(0, 0, 0, .2); // leave room for the .btn:focus ring
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 575.98px) {
|
||||
@@ -662,3 +678,7 @@ main {
|
||||
::ng-deep .navItemDrag .position-absolute i-bs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.smaller {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import { SearchService } from 'src/app/services/rest/search.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { TasksService } from 'src/app/services/tasks.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
|
||||
import { DocumentDetailComponent } from '../document-detail/document-detail.component'
|
||||
@@ -462,6 +463,29 @@ describe('AppFrameComponent', () => {
|
||||
expect(routerSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should upload files selected from the navbar upload button', () => {
|
||||
const uploadDocumentsService = TestBed.inject(UploadDocumentsService)
|
||||
const uploadSpy = jest
|
||||
.spyOn(uploadDocumentsService, 'uploadFiles')
|
||||
.mockImplementation(() => {})
|
||||
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
||||
const file = new File(['testing'], 'file.pdf')
|
||||
const input = window.document.createElement('input')
|
||||
jest.spyOn(input, 'files', 'get').mockReturnValue([file] as any)
|
||||
|
||||
component.onFileSelected({ target: input } as unknown as Event)
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
expect(uploadSpy).toHaveBeenCalledWith([file])
|
||||
})
|
||||
|
||||
it('should not upload when no files are selected from the navbar', () => {
|
||||
const uploadDocumentsService = TestBed.inject(UploadDocumentsService)
|
||||
const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFiles')
|
||||
const input = window.document.createElement('input')
|
||||
component.onFileSelected({ target: input } as unknown as Event)
|
||||
expect(uploadSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should close all documents on logout', () => {
|
||||
const closeAllSpy = jest.spyOn(openDocumentsService, 'closeAll')
|
||||
component.onLogout()
|
||||
|
||||
@@ -48,12 +48,13 @@ import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { TasksService } from 'src/app/services/tasks.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { ChatComponent } from '../chat/chat/chat.component'
|
||||
import { SwitchComponent } from '../common/input/switch/switch.component'
|
||||
import { BrandMarkComponent } from '../common/logo/brand-mark/brand-mark.component'
|
||||
import { LogoComponent } from '../common/logo/logo.component'
|
||||
import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
|
||||
import { SwitchComponent } from '../common/input/switch/switch.component'
|
||||
import { DocumentDetailComponent } from '../document-detail/document-detail.component'
|
||||
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
|
||||
import { GlobalSearchComponent } from './global-search/global-search.component'
|
||||
@@ -101,6 +102,7 @@ export class AppFrameComponent
|
||||
private modalService = inject(NgbModal)
|
||||
permissionsService = inject(PermissionsService)
|
||||
private djangoMessagesService = inject(DjangoMessagesService)
|
||||
private uploadDocumentsService = inject(UploadDocumentsService)
|
||||
|
||||
readonly appRemoteVersion = signal<AppRemoteVersion>(null)
|
||||
readonly isMenuCollapsed = signal(true)
|
||||
@@ -511,6 +513,15 @@ export class AppFrameComponent
|
||||
}
|
||||
}
|
||||
|
||||
onFileSelected(event: Event) {
|
||||
const input = event.target as HTMLInputElement
|
||||
const files = Array.from(input.files ?? [])
|
||||
input.value = ''
|
||||
if (!files.length) return
|
||||
this.toastService.showInfo($localize`Initiating upload...`, 3000)
|
||||
this.uploadDocumentsService.uploadFiles(files)
|
||||
}
|
||||
|
||||
onLogout() {
|
||||
this.openDocumentsService.closeAll()
|
||||
}
|
||||
|
||||
+4
-8
@@ -85,15 +85,11 @@ describe('UploadFileWidgetComponent', () => {
|
||||
'file.pdf'
|
||||
)
|
||||
const fileInput = fixture.debugElement.query(By.css('input'))
|
||||
jest.spyOn(fileInput.nativeElement, 'files', 'get').mockReturnValue({
|
||||
item: () => file,
|
||||
length: 1,
|
||||
[Symbol.iterator]: () => ({
|
||||
next: () => ({ done: false, value: file }),
|
||||
}),
|
||||
} as any)
|
||||
jest
|
||||
.spyOn(fileInput.nativeElement, 'files', 'get')
|
||||
.mockReturnValue([file] as any)
|
||||
fileInput.nativeElement.dispatchEvent(new Event('change'))
|
||||
expect(uploadSpy).toHaveBeenCalled()
|
||||
expect(uploadSpy).toHaveBeenCalledWith(file)
|
||||
})
|
||||
|
||||
it('should generate stats summary', () => {
|
||||
|
||||
+3
-5
@@ -130,11 +130,9 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
|
||||
}
|
||||
|
||||
public onFileSelected(event: Event) {
|
||||
const files = (event.target as HTMLInputElement).files
|
||||
for (let i = 0; i < files?.length; i++) {
|
||||
const file = files.item(i)
|
||||
file && this.uploadDocumentsService.uploadFile(file)
|
||||
}
|
||||
const input = event.target as HTMLInputElement
|
||||
this.uploadDocumentsService.uploadFiles(Array.from(input.files ?? []))
|
||||
input.value = ''
|
||||
}
|
||||
|
||||
get slimSidebarEnabled(): boolean {
|
||||
|
||||
@@ -135,7 +135,7 @@ export class FileDropComponent {
|
||||
.then((results) => {
|
||||
files.push(...[].concat(...results))
|
||||
this.toastService.showInfo($localize`Initiating upload...`, 3000)
|
||||
files.forEach((file) => this.uploadDocumentsService.uploadFile(file))
|
||||
this.uploadDocumentsService.uploadFiles(files)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.toastService.showError(
|
||||
@@ -144,7 +144,7 @@ export class FileDropComponent {
|
||||
})
|
||||
} else if (files.length) {
|
||||
this.toastService.showInfo($localize`Initiating upload...`, 3000)
|
||||
files.forEach((file) => this.uploadDocumentsService.uploadFile(file))
|
||||
this.uploadDocumentsService.uploadFiles(files)
|
||||
}
|
||||
|
||||
this.onDragLeave(event, true)
|
||||
|
||||
@@ -54,6 +54,18 @@ describe('UploadDocumentsService', () => {
|
||||
req[0].flush('123-456')
|
||||
})
|
||||
|
||||
it('uploads each file when given multiple files', () => {
|
||||
const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFile')
|
||||
const file1 = new File(['one'], 'one.pdf')
|
||||
const file2 = new File(['two'], 'two.pdf')
|
||||
uploadDocumentsService.uploadFiles([file1, file2])
|
||||
expect(uploadSpy).toHaveBeenCalledWith(file1)
|
||||
expect(uploadSpy).toHaveBeenCalledWith(file2)
|
||||
httpTestingController
|
||||
.match(`${environment.apiBaseUrl}documents/post_document/`)
|
||||
.forEach((req) => req.flush('123-456'))
|
||||
})
|
||||
|
||||
it('updates progress during upload and failure', () => {
|
||||
const file = new File(
|
||||
[new Blob(['testing'], { type: 'application/pdf' })],
|
||||
|
||||
@@ -16,6 +16,12 @@ export class UploadDocumentsService {
|
||||
|
||||
private uploadSubscriptions: Array<Subscription> = []
|
||||
|
||||
public uploadFiles(files: Iterable<File>) {
|
||||
for (const file of files) {
|
||||
this.uploadFile(file)
|
||||
}
|
||||
}
|
||||
|
||||
public uploadFile(file: File) {
|
||||
let formData = new FormData()
|
||||
formData.append('document', file, file.name)
|
||||
|
||||
@@ -90,6 +90,7 @@ import {
|
||||
exclamationTriangleFill,
|
||||
eye,
|
||||
fileEarmark,
|
||||
fileEarmarkArrowUp,
|
||||
fileEarmarkCheck,
|
||||
fileEarmarkDiff,
|
||||
fileEarmarkFill,
|
||||
@@ -341,6 +342,7 @@ const icons = {
|
||||
exclamationTriangleFill,
|
||||
eye,
|
||||
fileEarmark,
|
||||
fileEarmarkArrowUp,
|
||||
fileEarmarkCheck,
|
||||
fileEarmarkDiff,
|
||||
fileEarmarkFill,
|
||||
|
||||
Reference in new issue
Block a user