mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Merge branch 'dev' into dev
This commit is contained in:
commit
fb38aacde4
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">
|
||||
@ -55,16 +55,16 @@
|
||||
<path fill-rule="evenodd" d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
|
||||
</svg> <ng-container i18n>Download</ng-container>
|
||||
</a>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<small class="text-muted ml-auto" i18n>Score:</small>
|
||||
|
||||
<ngb-progressbar *ngIf="searchScore" [type]="searchScoreClass" [value]="searchScore" class="search-score-bar mx-2" [max]="1"></ngb-progressbar>
|
||||
|
||||
|
||||
<small class="text-muted" i18n>Created: {{document.created | date}}</small>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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,30 +10,45 @@
|
||||
<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">
|
||||
<span i18n>Items per page</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
|
||||
|
||||
<select class="form-control" formControlName="documentListItemPerPage">
|
||||
<option [ngValue]="10">10</option>
|
||||
<option [ngValue]="25">25</option>
|
||||
<option [ngValue]="50">50</option>
|
||||
<option [ngValue]="100">100</option>
|
||||
</select>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</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>
|
||||
@ -42,7 +57,7 @@
|
||||
<ng-template ngbNavContent>
|
||||
|
||||
<div formGroupName="savedViews">
|
||||
|
||||
|
||||
<div *ngFor="let view of savedViews" [formGroupName]="view.id" class="form-row">
|
||||
<div class="form-group col-4 mr-3">
|
||||
<label for="name_{{view.id}}" i18n>Name</label>
|
||||
@ -68,7 +83,7 @@
|
||||
</div>
|
||||
|
||||
<div *ngIf="savedViews.length == 0" i18n>No saved views defined.</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</ng-template>
|
||||
@ -78,4 +93,4 @@
|
||||
<div [ngbNavOutlet]="nav" class="border-left border-right border-bottom p-3 mb-3 shadow"></div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</form>
|
||||
</form>
|
||||
|
@ -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",
|
||||
},
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user