mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Merge branch 'dev' into dev
This commit is contained in:
		
							
								
								
									
										1
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Pipfile
									
									
									
									
									
								
							| @@ -42,6 +42,7 @@ whoosh="~=2.7.4" | ||||
| inotifyrecursive = "~=0.3.4" | ||||
| ocrmypdf = "*" | ||||
| tqdm = "*" | ||||
| tika = "*" | ||||
|  | ||||
| [dev-packages] | ||||
| coveralls = "*" | ||||
|   | ||||
							
								
								
									
										43
									
								
								docker/hub/docker-compose.tika.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								docker/hub/docker-compose.tika.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| version: "3.4" | ||||
| services: | ||||
|   broker: | ||||
|     image: redis:6.0 | ||||
|     restart: always | ||||
|  | ||||
|   webserver: | ||||
|     image: jonaswinkler/paperless-ng:0.9.9 | ||||
|     restart: always | ||||
|     depends_on: | ||||
|       - broker | ||||
|     ports: | ||||
|       - 8000:8000 | ||||
|     healthcheck: | ||||
|       test: ["CMD", "curl", "-f", "http://localhost:8000"] | ||||
|       interval: 30s | ||||
|       timeout: 10s | ||||
|       retries: 5 | ||||
|     volumes: | ||||
|       - data:/usr/src/paperless/data | ||||
|       - media:/usr/src/paperless/media | ||||
|       - ./export:/usr/src/paperless/export | ||||
|       - ./consume:/usr/src/paperless/consume | ||||
|     env_file: docker-compose.env | ||||
|     environment: | ||||
|       PAPERLESS_REDIS: redis://broker:6379 | ||||
|       PAPERLESS_TIKA_ENABLED: 1 | ||||
|       PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000 | ||||
|       PAPERLESS_TIKA_ENDPOINT: http://tika:9998 | ||||
|  | ||||
|   gotenberg: | ||||
|     image: thecodingmachine/gotenberg | ||||
|     restart: unless-stopped | ||||
|     environment: | ||||
|       DISABLE_GOOGLE_CHROME: 1 | ||||
|  | ||||
|   tika: | ||||
|     image: apache/tika | ||||
|     restart: unless-stopped | ||||
|  | ||||
| volumes: | ||||
|   data: | ||||
|   media: | ||||
							
								
								
									
										43
									
								
								docker/local/docker-compose.tika.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								docker/local/docker-compose.tika.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| version: "3.4" | ||||
| services: | ||||
|   broker: | ||||
|     image: redis:6.0 | ||||
|     restart: always | ||||
|  | ||||
|   webserver: | ||||
|     build: . | ||||
|     restart: always | ||||
|     depends_on: | ||||
|       - broker | ||||
|     ports: | ||||
|       - 8000:8000 | ||||
|     healthcheck: | ||||
|       test: ["CMD", "curl", "-f", "http://localhost:8000"] | ||||
|       interval: 30s | ||||
|       timeout: 10s | ||||
|       retries: 5 | ||||
|     volumes: | ||||
|       - data:/usr/src/paperless/data | ||||
|       - media:/usr/src/paperless/media | ||||
|       - ./export:/usr/src/paperless/export | ||||
|       - ./consume:/usr/src/paperless/consume | ||||
|     env_file: docker-compose.env | ||||
|     environment: | ||||
|       PAPERLESS_REDIS: redis://broker:6379 | ||||
|       PAPERLESS_TIKA_ENABLED: 1 | ||||
|       PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000 | ||||
|       PAPERLESS_TIKA_ENDPOINT: http://tika:9998 | ||||
|  | ||||
|   gotenberg: | ||||
|     image: thecodingmachine/gotenberg | ||||
|     restart: unless-stopped | ||||
|     environment: | ||||
|       DISABLE_GOOGLE_CHROME: 1 | ||||
|  | ||||
|   tika: | ||||
|     image: apache/tika | ||||
|     restart: unless-stopped | ||||
|  | ||||
| volumes: | ||||
|   data: | ||||
|   media: | ||||
| @@ -277,6 +277,35 @@ PAPERLESS_OCR_USER_ARG=<json> | ||||
|  | ||||
|         {"deskew": true, "optimize": 3, "unpaper_args": "--pre-rotate 90"}     | ||||
|      | ||||
| .. _configuration-tika: | ||||
|  | ||||
| Tika settings | ||||
| ############# | ||||
|  | ||||
| Paperless can make use of `Tika <https://tika.apache.org/>`_ and  | ||||
| `Gotenberg <https://thecodingmachine.github.io/gotenberg/>`_ for parsing and | ||||
| converting "Office" documents (such as ".doc", ".xlsx" and ".odt"). If you | ||||
| wish to use this, you must provide a Tika server and a Gotenberg server, | ||||
| configure their endpoints, and enable the feature. | ||||
|  | ||||
| If you run paperless on docker, you can add those services to the docker-compose | ||||
| file (see the examples provided). | ||||
|  | ||||
| PAPERLESS_TIKA_ENABLED=<bool> | ||||
|     Enable (or disable) the Tika parser. | ||||
|  | ||||
|     Defaults to false. | ||||
|  | ||||
| PAPERLESS_TIKA_ENDPOINT=<url> | ||||
|     Set the endpoint URL were Paperless can reach your Tika server. | ||||
|  | ||||
|     Defaults to "http://localhost:9998". | ||||
|  | ||||
| PAPERLESS_TIKA_GOTENBERG_ENDPOINT=<url> | ||||
|     Set the endpoint URL were Paperless can reach your Gotenberg server. | ||||
|  | ||||
|     Defaults to "http://localhost:3000". | ||||
|  | ||||
|      | ||||
| Software tweaks | ||||
| ############### | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import { Component } from '@angular/core'; | ||||
| import { AppViewService } from './services/app-view.service'; | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-root', | ||||
| @@ -7,7 +8,8 @@ import { Component } from '@angular/core'; | ||||
| }) | ||||
| export class AppComponent { | ||||
|  | ||||
|   constructor () { | ||||
|   constructor (appViewService: AppViewService) { | ||||
|     appViewService.updateDarkModeSettings() | ||||
|     (window as any).pdfWorkerSrc = '/assets/js/pdf.worker.min.js'; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| <nav class="navbar navbar-dark sticky-top bg-primary flex-md-nowrap p-0 shadow"> | ||||
|   <a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" routerLink="/dashboard"> | ||||
|     <img src="assets/logo-dark-notext.svg" height="18px" class="mr-2"> | ||||
|     <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="16px" class="mr-2" fill="currentColor"> | ||||
|       <path d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z" transform="translate(0 0)"/> | ||||
|     </svg> | ||||
|     <ng-container i18n="app title">Paperless-ng</ng-container> | ||||
|   </a> | ||||
|   <button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -1,7 +1,7 @@ | ||||
| <div class="card mb-3 bg-light shadow-sm" [class.card-selected]="selected" [class.document-card]="selectable"> | ||||
| <div class="card mb-3 shadow-sm" [class.card-selected]="selected" [class.document-card]="selectable"> | ||||
|   <div class="row no-gutters"> | ||||
|     <div class="col-md-2 d-none d-lg-block doc-img-background" [class.doc-img-background-selected]="selected"> | ||||
|       <img [src]="getThumbUrl()" class="card-img doc-img border-right" (click)="setSelected(selectable ? !selected : false)"> | ||||
|     <div class="col-md-2 d-none d-lg-block doc-img-background rounded-left" [class.doc-img-background-selected]="selected"> | ||||
|       <img [src]="getThumbUrl()" class="card-img doc-img border-right rounded-left" (click)="setSelected(selectable ? !selected : false)"> | ||||
|  | ||||
|       <div style="top: 0; left: 0" class="position-absolute border-right border-bottom bg-light p-1" [class.document-card-check]="!selected"> | ||||
|         <div class="custom-control custom-checkbox"> | ||||
| @@ -12,7 +12,7 @@ | ||||
|  | ||||
|     </div> | ||||
|     <div class="col"> | ||||
|       <div class="card-body"> | ||||
|       <div class="card-body bg-light"> | ||||
|  | ||||
|         <div class="d-flex justify-content-between align-items-center"> | ||||
|           <h5 class="card-title"> | ||||
|   | ||||
| @@ -30,10 +30,6 @@ | ||||
|   border-color: $primary; | ||||
| } | ||||
|  | ||||
| .doc-img-background { | ||||
|   background-color: white; | ||||
| } | ||||
|  | ||||
| .doc-img-background-selected { | ||||
|   background-color: $primaryFaded; | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| <div class="col p-2 h-100"> | ||||
|   <div class="card h-100 shadow-sm document-card" [class.card-selected]="selected"> | ||||
|     <div class="border-bottom" [class.doc-img-background-selected]="selected"> | ||||
|       <img class="card-img doc-img" [src]="getThumbUrl()" (click)="setSelected(!selected)"> | ||||
|     <div class="border-bottom doc-img-container" [class.doc-img-background-selected]="selected"> | ||||
|       <img class="card-img doc-img rounded-top" [src]="getThumbUrl()" (click)="setSelected(!selected)"> | ||||
|  | ||||
|       <div class="border-right border-bottom bg-light p-1 rounded document-card-check"> | ||||
|         <div class="custom-control custom-checkbox"> | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|       <a ngbNavLink i18n>General settings</a> | ||||
|       <ng-template ngbNavContent> | ||||
|  | ||||
|         <h4 i18n>Document list</h4> | ||||
|         <h4 i18n>Appearance</h4> | ||||
|  | ||||
|         <div class="form-row form-group"> | ||||
|           <div class="col-md-3 col-form-label"> | ||||
| @@ -26,14 +26,29 @@ | ||||
|             </select> | ||||
|  | ||||
|           </div> | ||||
|  | ||||
|    | ||||
|         </div> | ||||
|  | ||||
|         <h4 i18n>Bulk editing</h4> | ||||
|         <div class="form-row form-group"> | ||||
|           <div class="col-md-3 col-form-label"> | ||||
|             <span i18n>Dark mode</span> | ||||
|           </div> | ||||
|           <div class="col"> | ||||
|             <app-input-check i18n-title title="Use system settings" formControlName="darkModeUseSystem" (change)="toggleDarkModeSetting()"></app-input-check> | ||||
|             <div class="custom-control custom-switch" *ngIf="!settingsForm.value.darkModeUseSystem"> | ||||
|               <input type="checkbox" class="custom-control-input" id="darkModeEnabled" formControlName="darkModeEnabled" [checked]="settingsForm.value.darkModeEnabled"> | ||||
|               <label class="custom-control-label" for="darkModeEnabled">Enabled</label> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <app-input-check i18n-title title="Show confirmation dialogs" formControlName="bulkEditConfirmationDialogs" i18n-hint hint="Deleting documents will always ask for confirmation."></app-input-check> | ||||
|         <app-input-check i18n-title title="Apply on close" formControlName="bulkEditApplyOnClose"></app-input-check> | ||||
|         <h4 class="mt-4" i18n>Bulk editing</h4> | ||||
|  | ||||
|         <div class="form-row form-group"> | ||||
|           <div class="offset-md-3 col"> | ||||
|             <app-input-check i18n-title title="Show confirmation dialogs" formControlName="bulkEditConfirmationDialogs" i18n-hint hint="Deleting documents will always ask for confirmation."></app-input-check> | ||||
|             <app-input-check i18n-title title="Apply on close" formControlName="bulkEditApplyOnClose"></app-input-check> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|       </ng-template> | ||||
|     </li> | ||||
|   | ||||
| @@ -1,10 +1,11 @@ | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { Component, OnInit, Renderer2  } from '@angular/core'; | ||||
| import { FormControl, FormGroup } from '@angular/forms'; | ||||
| import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service'; | ||||
| import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
| import { AppViewService } from 'src/app/services/app-view.service'; | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-settings', | ||||
| @@ -19,18 +20,21 @@ export class SettingsComponent implements OnInit { | ||||
|     'bulkEditConfirmationDialogs': new FormControl(this.settings.get(SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS)), | ||||
|     'bulkEditApplyOnClose': new FormControl(this.settings.get(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE)), | ||||
|     'documentListItemPerPage': new FormControl(this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)), | ||||
|     'darkModeUseSystem': new FormControl(this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM)), | ||||
|     'darkModeEnabled': new FormControl(this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED)), | ||||
|     'savedViews': this.savedViewGroup | ||||
|   }) | ||||
|  | ||||
|   savedViews: PaperlessSavedView[] | ||||
|  | ||||
|   constructor( | ||||
|     public savedViewService: SavedViewService, | ||||
|     private documentListViewService: DocumentListViewService, | ||||
|     private toastService: ToastService, | ||||
|     private settings: SettingsService | ||||
|     private settings: SettingsService, | ||||
|     private appViewService: AppViewService | ||||
|   ) { } | ||||
|  | ||||
|   savedViews: PaperlessSavedView[] | ||||
|  | ||||
|   ngOnInit() { | ||||
|     this.savedViewService.listAll().subscribe(r => { | ||||
|       this.savedViews = r.results | ||||
| @@ -53,11 +57,22 @@ export class SettingsComponent implements OnInit { | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   toggleDarkModeSetting() { | ||||
|     if (this.settingsForm.value.darkModeUseSystem) { | ||||
|       (this.settingsForm.controls.darkModeEnabled as FormControl).disable() | ||||
|     } else { | ||||
|       (this.settingsForm.controls.darkModeEnabled as FormControl).enable() | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private saveLocalSettings() { | ||||
|     this.settings.set(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE, this.settingsForm.value.bulkEditApplyOnClose) | ||||
|     this.settings.set(SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS, this.settingsForm.value.bulkEditConfirmationDialogs) | ||||
|     this.settings.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, this.settingsForm.value.documentListItemPerPage) | ||||
|     this.settings.set(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, this.settingsForm.value.darkModeUseSystem) | ||||
|     this.settings.set(SETTINGS_KEYS.DARK_MODE_ENABLED, (this.settingsForm.value.darkModeEnabled == true).toString()) | ||||
|     this.documentListViewService.updatePageSize() | ||||
|     this.appViewService.updateDarkModeSettings() | ||||
|     this.toastService.showInfo($localize`Settings saved successfully.`) | ||||
|   } | ||||
|  | ||||
|   | ||||
							
								
								
									
										16
									
								
								src-ui/src/app/services/app-view.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src-ui/src/app/services/app-view.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import { TestBed } from '@angular/core/testing'; | ||||
|  | ||||
| import { AppViewService } from './app-view.service'; | ||||
|  | ||||
| describe('AppViewService', () => { | ||||
|   let service: AppViewService; | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     TestBed.configureTestingModule({}); | ||||
|     service = TestBed.inject(AppViewService); | ||||
|   }); | ||||
|  | ||||
|   it('should be created', () => { | ||||
|     expect(service).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										35
									
								
								src-ui/src/app/services/app-view.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src-ui/src/app/services/app-view.service.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core'; | ||||
| import { DOCUMENT } from '@angular/common'; | ||||
| import { SettingsService, SETTINGS_KEYS } from './settings.service'; | ||||
|  | ||||
| @Injectable({ | ||||
|   providedIn: 'root' | ||||
| }) | ||||
| export class AppViewService { | ||||
|   private renderer: Renderer2; | ||||
|  | ||||
|   constructor( | ||||
|     private settings: SettingsService, | ||||
|     private rendererFactory: RendererFactory2, | ||||
|     @Inject(DOCUMENT) private document | ||||
|   ) { | ||||
|     this.renderer = rendererFactory.createRenderer(null, null); | ||||
|  | ||||
|     this.updateDarkModeSettings() | ||||
|   } | ||||
|  | ||||
|   updateDarkModeSettings() { | ||||
|     let darkModeUseSystem = this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM) | ||||
|     let darkModeEnabled = this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED) | ||||
|  | ||||
|     if (darkModeUseSystem) { | ||||
|       this.renderer.addClass(this.document.body, 'color-scheme-system') | ||||
|       this.renderer.removeClass(this.document.body, 'color-scheme-dark') | ||||
|     } else { | ||||
|       this.renderer.removeClass(this.document.body, 'color-scheme-system') | ||||
|       darkModeEnabled ? this.renderer.addClass(this.document.body, 'color-scheme-dark') : this.renderer.removeClass(this.document.body, 'color-scheme-dark') | ||||
|     } | ||||
|  | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -10,12 +10,16 @@ export const SETTINGS_KEYS = { | ||||
|   BULK_EDIT_CONFIRMATION_DIALOGS: 'general-settings:bulk-edit:confirmation-dialogs', | ||||
|   BULK_EDIT_APPLY_ON_CLOSE: 'general-settings:bulk-edit:apply-on-close', | ||||
|   DOCUMENT_LIST_SIZE: 'general-settings:documentListSize', | ||||
|   DARK_MODE_USE_SYSTEM: 'general-settings:dark-mode:use-system', | ||||
|   DARK_MODE_ENABLED: 'general-settings:dark-mode:enabled' | ||||
| } | ||||
|  | ||||
| const SETTINGS: PaperlessSettings[] = [ | ||||
|   {key: SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS, type: "boolean", default: true}, | ||||
|   {key: SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE, type: "boolean", default: false}, | ||||
|   {key: SETTINGS_KEYS.DOCUMENT_LIST_SIZE, type: "number", default: 50} | ||||
|   {key: SETTINGS_KEYS.DOCUMENT_LIST_SIZE, type: "number", default: 50}, | ||||
|   {key: SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, type: "boolean", default: true}, | ||||
|   {key: SETTINGS_KEYS.DARK_MODE_ENABLED, type: "boolean", default: false} | ||||
| ] | ||||
|  | ||||
| @Injectable({ | ||||
|   | ||||
| @@ -1,69 +1,19 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    width="69.999977mm" | ||||
|    height="84.283669mm" | ||||
|    viewBox="0 0 69.999977 84.283669" | ||||
|    version="1.1" | ||||
|    id="svg4812" | ||||
|    inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" | ||||
|    sodipodi:docname="logo-dark-notext.svg"> | ||||
|   <defs | ||||
|      id="defs4806" /> | ||||
|   <sodipodi:namedview | ||||
|      id="base" | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="0.0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="0.98994949" | ||||
|      inkscape:cx="328.04904" | ||||
|      inkscape:cy="330.33332" | ||||
|      inkscape:document-units="mm" | ||||
|      inkscape:current-layer="SvgjsG1020" | ||||
|      inkscape:document-rotation="0" | ||||
|      showgrid="false" | ||||
|      inkscape:window-width="1920" | ||||
|      inkscape:window-height="1016" | ||||
|      inkscape:window-x="1280" | ||||
|      inkscape:window-y="27" | ||||
|      inkscape:window-maximized="1" /> | ||||
|   <metadata | ||||
|      id="metadata4809"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|         <dc:title></dc:title> | ||||
|       </cc:Work> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <g | ||||
|      inkscape:label="Layer 1" | ||||
|      inkscape:groupmode="layer" | ||||
|      id="layer1" | ||||
|      transform="translate(-9.9999792,-10.000082)"> | ||||
|     <g | ||||
|        id="SvgjsG1020" | ||||
|        featureKey="symbol1" | ||||
|        fill="#ffffff" | ||||
|        transform="matrix(0.10341565,0,0,0.10341565,1.2287665,8.3453496)"> | ||||
|       <path | ||||
|          id="path57" | ||||
|          style="fill:#ffffff;stroke-width:1.10017" | ||||
|          d="M 752.4375,82.365234 C 638.02019,348.60552 87.938206,381.6089 263.96484,810.67383 c 2.20034,5.50083 -40.70621,63.80947 -69.31054,112.21679 -6.601,-24.20366 -14.30329,-50.6063 -13.20313,-52.80664 C 324.47281,700.65835 79.135592,604.94324 65.933594,466.32227 4.3242706,576.33891 -17.678136,768.86756 168.25,879.98438 c 1.10017,-10e-6 9.90207,41.80777 14.30273,62.71093 -4.40066,8.80133 -8.80162,17.60213 -11.00195,24.20313 -4.40066,11.00166 28.60352,9.90123 28.60352,12.10156 3.3005,-1.10017 81.41295,-138.62054 83.61328,-139.7207 C 726.0345,738.06398 804.14532,339.80419 752.4375,82.365234 Z M 526.9043,362.90625 C 320.073,547.73422 284.86775,685.25508 291.46875,752.36523 222.15826,588.44043 425.68898,408.01308 526.9043,362.90625 Z M 127.54297,626.94727 c 39.60599,36.30549 105.6163,147.4222 49.50781,212.33203 13.202,-29.7045 17.60234,-96.81455 -49.50781,-212.33203 z" | ||||
|          transform="matrix(0.90895334,0,0,0.90895334,65.06894,-58.865357)" /> | ||||
|       <defs | ||||
|          id="defs14302" /> | ||||
|     </g> | ||||
|   </g> | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Generator: Adobe Illustrator 25.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||
| <svg version="1.1" | ||||
| 	 id="svg4812" inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" sodipodi:docname="logo-dark-notext.svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg" | ||||
| 	 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 198.4 238.9" | ||||
| 	 style="enable-background:new 0 0 198.4 238.9;" xml:space="preserve"> | ||||
| <sodipodi:namedview  bordercolor="#666666" borderopacity="1.0" id="base" inkscape:current-layer="SvgjsG1020" inkscape:cx="328.04904" inkscape:cy="330.33332" inkscape:document-rotation="0" inkscape:document-units="mm" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="1016" inkscape:window-maximized="1" inkscape:window-width="1920" inkscape:window-x="1280" inkscape:window-y="27" inkscape:zoom="0.98994949" pagecolor="#ffffff" showgrid="false"> | ||||
| 	</sodipodi:namedview> | ||||
| <g id="layer1" transform="translate(-9.9999792,-10.000082)" inkscape:groupmode="layer" inkscape:label="Layer 1"> | ||||
| 	<g id="SvgjsG1020" transform="matrix(0.10341565,0,0,0.10341565,1.2287665,8.3453496)"> | ||||
| 		<path id="path57" d="M1967.5,16C1672.7,702,255.4,787,709,1892.5c5.7,14.2-104.9,164.4-178.6,289.1c-17-62.4-36.9-130.4-34-136.1 | ||||
| 			c368.5-436.5-263.6-683.1-297.6-1040.3C40,1288.7-16.7,1784.8,462.3,2071.1c2.8,0,25.5,107.7,36.9,161.6 | ||||
| 			c-11.3,22.7-22.7,45.4-28.3,62.4c-11.3,28.3,73.7,25.5,73.7,31.2c8.5-2.8,209.8-357.2,215.4-360 | ||||
| 			C1899.5,1705.4,2100.8,679.3,1967.5,16z M1386.4,738.8C853.5,1215,762.8,1569.4,779.8,1742.3 | ||||
| 			C601.2,1319.9,1125.7,855,1386.4,738.8z M357.5,1419.1c102,93.5,272.1,379.8,127.6,547.1C519,1889.7,530.4,1716.8,357.5,1419.1z" | ||||
| 			/> | ||||
| 	</g> | ||||
| </g> | ||||
| </svg> | ||||
|   | ||||
| Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.0 KiB | 
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.8 KiB | 
							
								
								
									
										69
									
								
								src-ui/src/assets/logo-white-notext.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src-ui/src/assets/logo-white-notext.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    width="69.999977mm" | ||||
|    height="84.283669mm" | ||||
|    viewBox="0 0 69.999977 84.283669" | ||||
|    version="1.1" | ||||
|    id="svg4812" | ||||
|    inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" | ||||
|    sodipodi:docname="logo-dark-notext.svg"> | ||||
|   <defs | ||||
|      id="defs4806" /> | ||||
|   <sodipodi:namedview | ||||
|      id="base" | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="0.0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="0.98994949" | ||||
|      inkscape:cx="328.04904" | ||||
|      inkscape:cy="330.33332" | ||||
|      inkscape:document-units="mm" | ||||
|      inkscape:current-layer="SvgjsG1020" | ||||
|      inkscape:document-rotation="0" | ||||
|      showgrid="false" | ||||
|      inkscape:window-width="1920" | ||||
|      inkscape:window-height="1016" | ||||
|      inkscape:window-x="1280" | ||||
|      inkscape:window-y="27" | ||||
|      inkscape:window-maximized="1" /> | ||||
|   <metadata | ||||
|      id="metadata4809"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|         <dc:title></dc:title> | ||||
|       </cc:Work> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <g | ||||
|      inkscape:label="Layer 1" | ||||
|      inkscape:groupmode="layer" | ||||
|      id="layer1" | ||||
|      transform="translate(-9.9999792,-10.000082)"> | ||||
|     <g | ||||
|        id="SvgjsG1020" | ||||
|        featureKey="symbol1" | ||||
|        fill="#ffffff" | ||||
|        transform="matrix(0.10341565,0,0,0.10341565,1.2287665,8.3453496)"> | ||||
|       <path | ||||
|          id="path57" | ||||
|          style="fill:#ffffff;stroke-width:1.10017" | ||||
|          d="M 752.4375,82.365234 C 638.02019,348.60552 87.938206,381.6089 263.96484,810.67383 c 2.20034,5.50083 -40.70621,63.80947 -69.31054,112.21679 -6.601,-24.20366 -14.30329,-50.6063 -13.20313,-52.80664 C 324.47281,700.65835 79.135592,604.94324 65.933594,466.32227 4.3242706,576.33891 -17.678136,768.86756 168.25,879.98438 c 1.10017,-10e-6 9.90207,41.80777 14.30273,62.71093 -4.40066,8.80133 -8.80162,17.60213 -11.00195,24.20313 -4.40066,11.00166 28.60352,9.90123 28.60352,12.10156 3.3005,-1.10017 81.41295,-138.62054 83.61328,-139.7207 C 726.0345,738.06398 804.14532,339.80419 752.4375,82.365234 Z M 526.9043,362.90625 C 320.073,547.73422 284.86775,685.25508 291.46875,752.36523 222.15826,588.44043 425.68898,408.01308 526.9043,362.90625 Z M 127.54297,626.94727 c 39.60599,36.30549 105.6163,147.4222 49.50781,212.33203 13.202,-29.7045 17.60234,-96.81455 -49.50781,-212.33203 z" | ||||
|          transform="matrix(0.90895334,0,0,0.90895334,65.06894,-58.865357)" /> | ||||
|       <defs | ||||
|          id="defs14302" /> | ||||
|     </g> | ||||
|   </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 2.9 KiB | 
| @@ -5,11 +5,12 @@ | ||||
|   <title>Paperless-ng</title> | ||||
|   <base href="/"> | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||||
|   <meta name="color-scheme" content="dark light"> | ||||
|   <meta name="theme-color" content="#17541f" /> | ||||
|   <link rel="manifest" href="manifest.webmanifest"> | ||||
|   <link rel="icon" type="image/x-icon" href="favicon.ico"> | ||||
| </head> | ||||
| <body> | ||||
| <body class="color-scheme-system"> | ||||
|   <app-root></app-root> | ||||
| </body> | ||||
| </html> | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| @import "theme"; | ||||
| @import "theme_dark"; | ||||
| @import "node_modules/bootstrap/scss/bootstrap"; | ||||
| @import "~@ng-select/ng-select/themes/default.theme.css"; | ||||
|  | ||||
|   | ||||
							
								
								
									
										329
									
								
								src-ui/src/theme_dark.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										329
									
								
								src-ui/src/theme_dark.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,329 @@ | ||||
| $primary-dark-mode: #346e2c; | ||||
| $danger-dark-mode: #9c142a; | ||||
| $bg-dark-mode: #161618; | ||||
| $bg-light-dark-mode: #1c1c1f; | ||||
| $text-color-dark-mode: #abb2bf; | ||||
| $text-color-dark-mode-accent: lighten($text-color-dark-mode, 10%); | ||||
| $border-color-dark-mode: #47494f; | ||||
|  | ||||
| * { | ||||
|   transition: background-color 0.3s ease, border-color 0.3s ease; | ||||
| } | ||||
|  | ||||
| @mixin dark-mode { | ||||
|   background-color: $bg-dark-mode !important; | ||||
|   color: $text-color-dark-mode; | ||||
|  | ||||
|   .navbar-brand { | ||||
|     color: $text-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   svg.logo { | ||||
|     .leaf { | ||||
|       color: $primary-dark-mode !important; | ||||
|     } | ||||
|     .text { | ||||
|       fill: $text-color-dark-mode !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .bg-light { | ||||
|     background-color: $bg-light-dark-mode !important; | ||||
|  | ||||
|     a, | ||||
|     div { | ||||
|       color: $text-color-dark-mode; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .border { | ||||
|     border-color: $border-color-dark-mode !important; | ||||
|   } | ||||
|  | ||||
|   .border-right { | ||||
|     border-right: 1px solid $border-color-dark-mode !important; | ||||
|   } | ||||
|  | ||||
|   .border-left { | ||||
|     border-left: 1px solid $border-color-dark-mode !important; | ||||
|   } | ||||
|  | ||||
|   .border-bottom { | ||||
|     border-bottom: 1px solid $border-color-dark-mode !important; | ||||
|   } | ||||
|  | ||||
|   .nav-link { | ||||
|     color: $text-color-dark-mode !important; | ||||
|  | ||||
|     &.active { | ||||
|       background-color: $bg-dark-mode; | ||||
|       color: $text-color-dark-mode; | ||||
|       border-color: $border-color-dark-mode $border-color-dark-mode $bg-dark-mode; | ||||
|     } | ||||
|  | ||||
|     &:hover { | ||||
|       color: $text-color-dark-mode-accent !important; | ||||
|       border-color: $border-color-dark-mode $border-color-dark-mode $bg-dark-mode; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .nav-tabs { | ||||
|     border-color: $border-color-dark-mode; | ||||
|  | ||||
|     .nav-link { | ||||
|       color: $primary-dark-mode !important; | ||||
|  | ||||
|       &.active { | ||||
|         color: $text-color-dark-mode !important; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .dropdown-menu { | ||||
|     background-color: $bg-dark-mode; | ||||
|  | ||||
|     .dropdown-divider { | ||||
|       border-color: $border-color-dark-mode; | ||||
|     } | ||||
|  | ||||
|     .dropdown-item { | ||||
|       color: $text-color-dark-mode; | ||||
|  | ||||
|       &:hover { | ||||
|         background-color: $bg-light-dark-mode; | ||||
|         color: $text-color-dark-mode; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .dropdown-item.disabled { | ||||
|       color: darken($text-color-dark-mode, 20%); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .card { | ||||
|     background-color: $bg-light-dark-mode; | ||||
|  | ||||
|     .card-text { | ||||
|       color: $text-color-dark-mode; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .text-dark { | ||||
|     color: $text-color-dark-mode !important; | ||||
|   } | ||||
|  | ||||
|   .modal-content, .modal-header, .modal-body, .modal-footer { | ||||
|     background-color: $bg-light-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   app-tag .badge { | ||||
|     filter: brightness(.8); | ||||
|   } | ||||
|  | ||||
|   .badge-light { | ||||
|     background-color: darken($bg-dark-mode, 20%); | ||||
|     color: $text-color-dark-mode-accent; | ||||
|   } | ||||
|  | ||||
|   .doc-img-container { | ||||
|     border: none !important; | ||||
|     border-top-left-radius: .25rem; | ||||
|     border-top-right-radius: .25rem; | ||||
|     overflow: hidden; | ||||
|   } | ||||
|  | ||||
|   .doc-img { | ||||
|     mix-blend-mode: normal; | ||||
|     filter: invert(95%) hue-rotate(180deg); | ||||
|     border-radius: 0; | ||||
|     border-color: $bg-dark-mode; | ||||
|  | ||||
|     &.border-right { | ||||
|       border-right: none !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .card-selected .doc-img { | ||||
|     mix-blend-mode: luminosity; | ||||
|   } | ||||
|  | ||||
|   .toast { | ||||
|     background-color: opacify($bg-light-dark-mode, .85); | ||||
|   } | ||||
|  | ||||
|   .toast-header { | ||||
|     background-color: opacify($bg-dark-mode, .85); | ||||
|   } | ||||
|  | ||||
|   a { | ||||
|     color: $primary-dark-mode; | ||||
|  | ||||
|     &:hover { | ||||
|       color: lighten($primary, 10%); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   table { | ||||
|     background-color: $bg-light-dark-mode; | ||||
|     color: $text-color-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|  | ||||
|     tr:hover { | ||||
|       background-color: $bg-light-dark-mode; | ||||
|       color: $text-color-dark-mode-accent; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .table td, | ||||
|   .table th { | ||||
|     border-color: $border-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .table-row-selected { | ||||
|     background-color: $bg-light-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .close { | ||||
|     color: $text-color-dark-mode; | ||||
|     text-shadow: 0 1px 0 #666; | ||||
|   } | ||||
|  | ||||
|   .btn-outline-primary{ | ||||
|     border-color: $primary-dark-mode; | ||||
|     color: $primary-dark-mode; | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: darken($primary-dark-mode, 10%); | ||||
|       color: $text-color-dark-mode-accent; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .btn-outline-secondary { | ||||
|     border-color: $text-color-dark-mode; | ||||
|     color: $text-color-dark-mode; | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: $bg-dark-mode; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .btn-outline-danger { | ||||
|     border-color: $danger-dark-mode; | ||||
|     color: $danger-dark-mode; | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: darken($danger-dark-mode, 10%); | ||||
|       color: $text-color-dark-mode-accent; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .btn-outline-dark { | ||||
|     border-color: $border-color-dark-mode; | ||||
|     color: $text-color-dark-mode; | ||||
|  | ||||
|     &:hover { | ||||
|       color: $text-color-dark-mode-accent; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .btn-link:not(:disabled):not(.disabled) { | ||||
|     color: $primary-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .btn-link:hover, | ||||
|   .btn-outline-primary:not(:disabled):not(.disabled).active, | ||||
|   .btn-outline-primary:not(:disabled):not(.disabled):active, | ||||
|   .show > .btn-outline-primary.dropdown-toggle { | ||||
|     color: $text-color-dark-mode-accent; | ||||
|   } | ||||
|  | ||||
|   button.bg-light:hover { | ||||
|     background-color: $bg-dark-mode !important; | ||||
|   } | ||||
|  | ||||
|   .form-control, | ||||
|   input, | ||||
|   select, | ||||
|   textarea { | ||||
|     background-color: $bg-dark-mode; | ||||
|     color: $text-color-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|  | ||||
|     &::placeholder { | ||||
|       color: $text-color-dark-mode; | ||||
|     } | ||||
|  | ||||
|     &:focus { | ||||
|       background-color: $bg-light-dark-mode !important; | ||||
|       color: darken($text-color-dark-mode, 10%) !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .ng-select-container, | ||||
|   .ng-select.ng-select-opened > .ng-select-container, | ||||
|   .ng-dropdown-panel, | ||||
|   .ng-dropdown-panel .ng-dropdown-panel-items .ng-option { | ||||
|     background-color: $bg-dark-mode; | ||||
|     color: $text-color-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|  | ||||
|     input:focus { | ||||
|       background-color: transparent !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .ng-dropdown-panel .ng-dropdown-panel-items .ng-option:hover { | ||||
|     background-color: $bg-light-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .custom-control-label:before { | ||||
|     background-color: $bg-dark-mode; | ||||
|     color: $text-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .custom-control-input:checked ~ .custom-control-label::before { | ||||
|     color: $text-color-dark-mode-accent; | ||||
|   } | ||||
|  | ||||
|   .input-group-text { | ||||
|     color: $text-color-dark-mode; | ||||
|     background-color: $bg-light-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .list-group-item { | ||||
|     color: $text-color-dark-mode; | ||||
|     background-color: $bg-light-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .page-item.disabled .page-link { | ||||
|     background-color: $bg-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .list-group-item, | ||||
|   .page-link { | ||||
|     background-color: $bg-light-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .page-item.active .page-link { | ||||
|     border-color: $border-color-dark-mode; | ||||
|     color: $text-color-dark-mode-accent; | ||||
|   } | ||||
|  | ||||
|   .progress { | ||||
|     background-color: $border-color-dark-mode; | ||||
|   } | ||||
| } | ||||
|  | ||||
| body.color-scheme-dark { | ||||
|   @include dark-mode; | ||||
| } | ||||
| body.color-scheme-system { | ||||
|   @media (prefers-color-scheme: dark) { | ||||
|     @include dark-mode; | ||||
|   } | ||||
| } | ||||
| @@ -87,6 +87,7 @@ INSTALLED_APPS = [ | ||||
|     "documents.apps.DocumentsConfig", | ||||
|     "paperless_tesseract.apps.PaperlessTesseractConfig", | ||||
|     "paperless_text.apps.PaperlessTextConfig", | ||||
|     "paperless_tika.apps.PaperlessTikaConfig", | ||||
|     "paperless_mail.apps.PaperlessMailConfig", | ||||
|  | ||||
|     "django.contrib.admin", | ||||
| @@ -424,3 +425,10 @@ for t in json.loads(os.getenv("PAPERLESS_FILENAME_PARSE_TRANSFORMS", "[]")): | ||||
| PAPERLESS_FILENAME_FORMAT = os.getenv("PAPERLESS_FILENAME_FORMAT") | ||||
|  | ||||
| THUMBNAIL_FONT_NAME = os.getenv("PAPERLESS_THUMBNAIL_FONT_NAME", "/usr/share/fonts/liberation/LiberationSerif-Regular.ttf") | ||||
|  | ||||
| # Tika settings | ||||
| PAPERLESS_TIKA_ENABLED = __get_boolean("PAPERLESS_TIKA_ENABLED", "NO") | ||||
| PAPERLESS_TIKA_ENDPOINT = os.getenv("PAPERLESS_TIKA_ENDPOINT", "http://localhost:9998") | ||||
| PAPERLESS_TIKA_GOTENBERG_ENDPOINT = os.getenv( | ||||
|     "PAPERLESS_TIKA_GOTENBERG_ENDPOINT", "http://localhost:3000" | ||||
| ) | ||||
|   | ||||
							
								
								
									
										14
									
								
								src/paperless_tika/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/paperless_tika/apps.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| from django.apps import AppConfig | ||||
| from django.conf import settings | ||||
| from paperless_tika.signals import tika_consumer_declaration | ||||
|  | ||||
|  | ||||
| class PaperlessTikaConfig(AppConfig): | ||||
|     name = "paperless_tika" | ||||
|  | ||||
|     def ready(self): | ||||
|         from documents.signals import document_consumer_declaration | ||||
|  | ||||
|         if settings.PAPERLESS_TIKA_ENABLED: | ||||
|             document_consumer_declaration.connect(tika_consumer_declaration) | ||||
|         AppConfig.ready(self) | ||||
							
								
								
									
										115
									
								
								src/paperless_tika/parsers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								src/paperless_tika/parsers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | ||||
| import os | ||||
| import subprocess | ||||
| import tika | ||||
| import requests | ||||
| import dateutil.parser | ||||
|  | ||||
| from PIL import ImageDraw, ImageFont, Image | ||||
| from django.conf import settings | ||||
|  | ||||
| from documents.parsers import DocumentParser, ParseError, run_convert | ||||
| from paperless_tesseract.parsers import RasterisedDocumentParser | ||||
| from tika import parser | ||||
|  | ||||
|  | ||||
| class TikaDocumentParser(DocumentParser): | ||||
|     """ | ||||
|     This parser sends documents to a local tika server | ||||
|     """ | ||||
|  | ||||
|     def get_thumbnail(self, document_path, mime_type): | ||||
|         self.log("info", f"[TIKA_THUMB] Generating thumbnail for{document_path}") | ||||
|         archive_path = self.archive_path | ||||
|  | ||||
|         out_path = os.path.join(self.tempdir, "convert.png") | ||||
|  | ||||
|         # Run convert to get a decent thumbnail | ||||
|         try: | ||||
|             run_convert( | ||||
|                 density=300, | ||||
|                 scale="500x5000>", | ||||
|                 alpha="remove", | ||||
|                 strip=True, | ||||
|                 trim=False, | ||||
|                 input_file="{}[0]".format(archive_path), | ||||
|                 output_file=out_path, | ||||
|                 logging_group=self.logging_group, | ||||
|             ) | ||||
|         except ParseError: | ||||
|             # if convert fails, fall back to extracting | ||||
|             # the first PDF page as a PNG using Ghostscript | ||||
|             self.log( | ||||
|                 "warning", | ||||
|                 "Thumbnail generation with ImageMagick failed, falling back " | ||||
|                 "to ghostscript. Check your /etc/ImageMagick-x/policy.xml!", | ||||
|             ) | ||||
|             gs_out_path = os.path.join(self.tempdir, "gs_out.png") | ||||
|             cmd = [ | ||||
|                 settings.GS_BINARY, | ||||
|                 "-q", | ||||
|                 "-sDEVICE=pngalpha", | ||||
|                 "-o", | ||||
|                 gs_out_path, | ||||
|                 archive_path, | ||||
|             ] | ||||
|             if not subprocess.Popen(cmd).wait() == 0: | ||||
|                 raise ParseError("Thumbnail (gs) failed at {}".format(cmd)) | ||||
|             # then run convert on the output from gs | ||||
|             run_convert( | ||||
|                 density=300, | ||||
|                 scale="500x5000>", | ||||
|                 alpha="remove", | ||||
|                 strip=True, | ||||
|                 trim=False, | ||||
|                 input_file=gs_out_path, | ||||
|                 output_file=out_path, | ||||
|                 logging_group=self.logging_group, | ||||
|             ) | ||||
|  | ||||
|         return out_path | ||||
|  | ||||
|     def parse(self, document_path, mime_type): | ||||
|         self.log("info", f"[TIKA_PARSE] Sending {document_path} to Tika server") | ||||
|         tika_server = settings.PAPERLESS_TIKA_ENDPOINT | ||||
|  | ||||
|         try: | ||||
|             parsed = parser.from_file(document_path, tika_server) | ||||
|         except requests.exceptions.HTTPError as err: | ||||
|             raise ParseError( | ||||
|                 f"Could not parse {document_path} with tika server at {tika_server}: {err}" | ||||
|             ) | ||||
|  | ||||
|         try: | ||||
|             self.text = parsed["content"].strip() | ||||
|         except: | ||||
|             pass | ||||
|  | ||||
|         try: | ||||
|             self.date = dateutil.parser.isoparse(parsed["metadata"]["Creation-Date"]) | ||||
|         except: | ||||
|             pass | ||||
|  | ||||
|         archive_path = os.path.join(self.tempdir, "convert.pdf") | ||||
|         convert_to_pdf(document_path, archive_path) | ||||
|         self.archive_path = archive_path | ||||
|  | ||||
|     def convert_to_pdf(document_path, pdf_path): | ||||
|         pdf_path = os.path.join(self.tempdir, "convert.pdf") | ||||
|         gotenberg_server = settings.PAPERLESS_TIKA_GOTENBERG_ENDPOINT | ||||
|         url = gotenberg_server + "/convert/office" | ||||
|  | ||||
|         self.log("info", f"[TIKA] Converting {document_path} to PDF as {pdf_path}") | ||||
|         files = {"files": open(document_path, "rb")} | ||||
|         headers = {} | ||||
|  | ||||
|         try: | ||||
|             response = requests.post(url, files=files, headers=headers) | ||||
|             response.raise_for_status()  # ensure we notice bad responses | ||||
|         except requests.exceptions.HTTPError as err: | ||||
|             raise ParseError( | ||||
|                 f"Could not contact gotenberg server at {gotenberg_server}: {err}" | ||||
|             ) | ||||
|  | ||||
|         file = open(pdf_path, "wb") | ||||
|         file.write(response.content) | ||||
|         file.close() | ||||
							
								
								
									
										20
									
								
								src/paperless_tika/signals.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/paperless_tika/signals.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| from .parsers import TikaDocumentParser | ||||
|  | ||||
|  | ||||
| def tika_consumer_declaration(sender, **kwargs): | ||||
|     return { | ||||
|         "parser": TikaDocumentParser, | ||||
|         "weight": 10, | ||||
|         "mime_types": { | ||||
|             "application/msword": ".doc", | ||||
|             "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx", | ||||
|             "application/vnd.ms-excel": ".xls", | ||||
|             "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx", | ||||
|             "application/vnd.ms-powerpoint": ".ppt", | ||||
|             "application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx", | ||||
|             "application/vnd.openxmlformats-officedocument.presentationml.slideshow": ".ppsx", | ||||
|             "application/vnd.oasis.opendocument.presentation": ".odp", | ||||
|             "application/vnd.oasis.opendocument.spreadsheet": ".ods", | ||||
|             "application/vnd.oasis.opendocument.text": ".odt", | ||||
|         }, | ||||
|     } | ||||
		Reference in New Issue
	
	Block a user
	 Jonas Winkler
					Jonas Winkler