Compare commits
67 Commits
Author | SHA1 | Date |
---|---|---|
Vincent LAURENT | c4c2183132 | |
Gabriel Poma | c375b43b14 | |
Vincent LAURENT | 06bac144dd | |
Vincent LAURENT | d5f4eca0ff | |
Vincent LAURENT | f4bab4ac5f | |
Vincent LAURENT | 26aac0af94 | |
Vincent LAURENT | f9c413d85a | |
Gabriel | 00bf081319 | |
Gabriel | 1023ebd347 | |
Simon C | 5f703890ac | |
xgaia | 37a678e9db | |
Vincent LAURENT | 357bffc55f | |
Vincent LAURENT | fee27a0311 | |
Étienne Deparis | a1509a554f | |
Vincent LAURENT | 30e7ffef89 | |
Vincent LAURENT | ada82de930 | |
Vincent LAURENT | 4646ad35a7 | |
Vincent LAURENT | c19f1af646 | |
Vincent LAURENT | d6b6b43ac2 | |
xgaia | 7d4ae2907f | |
Gabriel Poma | 96b21fb850 | |
Gabriel Poma | 5925d4e4c6 | |
xgaia | c119d8d1cd | |
Vincent LAURENT | de66c05f2a | |
Vincent LAURENT | 9b36ae02e5 | |
Vincent LAURENT | e488e20fad | |
Vincent LAURENT | a04e26d2ce | |
Gabriel Poma | c89c8d53dd | |
Gabriel Poma | 60044ee371 | |
Gabriel Poma | c30c89ad60 | |
Gabriel Poma | 96c663493c | |
Vincent LAURENT | 841cc18ed9 | |
Gabriel Poma | 2653effbc8 | |
Vincent LAURENT | 9abd4e848b | |
Vincent LAURENT | 4b0ca82652 | |
Vincent LAURENT | 4965d7878b | |
Vincent LAURENT | b30be5b344 | |
Gabriel Poma | 8d9f49a3e8 | |
Gabriel Poma | 4f4d3a0583 | |
Vincent LAURENT | 49bf6e50ab | |
Vincent LAURENT | 6cc0461975 | |
Gabriel Poma | 784e15f252 | |
Gabriel Poma | 6bed0fdc1d | |
Vincent LAURENT | 75434aee71 | |
Vincent LAURENT | 6c28618fbe | |
Vincent LAURENT | a2843a329c | |
Vincent LAURENT | 9bdd28c7df | |
Gabriel Poma | eab2e0c127 | |
Gabriel Poma | 011c9094e1 | |
Gabriel Poma | cbea7de840 | |
Gabriel Poma | b037685c57 | |
Gabriel Poma | f9af7f969e | |
Vincent LAURENT | f0eb416ced | |
Vincent LAURENT | e2f9637426 | |
Vincent LAURENT | 186a9cc225 | |
Gabriel Poma | eec274bf0f | |
Vincent LAURENT | 9a63402a2a | |
Vincent LAURENT | 68be39ed23 | |
xgaia | c9f0d25027 | |
Vincent LAURENT | 7d7e666bc7 | |
Vincent LAURENT | f27abba511 | |
Gabriel Poma | 818f08d24c | |
Vincent LAURENT | 5a4309fcf3 | |
Vincent LAURENT | 1eb0f5101b | |
Jonas Chopin-Revel | 66cc24abd1 | |
Vincent LAURENT | 3ee536cff3 | |
Vincent LAURENT | 4f9697d409 |
13
Dockerfile
13
Dockerfile
|
@ -1,19 +1,20 @@
|
|||
FROM php:7.4-apache
|
||||
FROM php:8.2-apache
|
||||
|
||||
ENV SERVERNAME=localhost
|
||||
ENV UPLOAD_MAX_FILESIZE=24M
|
||||
ENV POST_MAX_SIZE=24M
|
||||
ENV MAX_FILE_UPLOADS=201
|
||||
ENV PDF_STORAGE_PATH=
|
||||
ENV PDF_STORAGE_PATH=/data
|
||||
ENV DISABLE_ORGANIZATION=false
|
||||
ENV PDF_DEMO_LINK=true
|
||||
|
||||
RUN apt update && \
|
||||
apt install -y gettext-base librsvg2-bin pdftk imagemagick potrace
|
||||
apt install -y gettext-base librsvg2-bin pdftk imagemagick potrace && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY . /usr/local/signaturepdf
|
||||
|
||||
RUN chown -R www-data:www-data /usr/local/signaturepdf && chmod 750 -R /usr/local/signaturepdf && \
|
||||
chmod 775 -R /usr/local/signaturepdf/entrypoint.sh && \
|
||||
envsubst < /usr/local/signaturepdf/config/php.ini > /usr/local/etc/php/conf.d/uploads.ini && \
|
||||
RUN envsubst < /usr/local/signaturepdf/config/php.ini > /usr/local/etc/php/conf.d/uploads.ini && \
|
||||
envsubst < /usr/local/signaturepdf/config/apache.conf > /etc/apache2/sites-available/signaturepdf.conf && \
|
||||
envsubst < /usr/local/signaturepdf/config/config.ini.tpl > /usr/local/signaturepdf/config/config.ini && \
|
||||
a2enmod rewrite && a2ensite signaturepdf
|
||||
|
|
62
README.md
62
README.md
|
@ -11,7 +11,7 @@ Liste des instances permettant d'utiliser ce logiciel :
|
|||
- [pdf.hostux.net](https://pdf.hostux.net)
|
||||
- [pdf.nebulae.co](https://pdf.nebulae.co)
|
||||
|
||||
*N'hésitez pas à rajouter la votre via une issue ou une pull request*
|
||||
_N'hésitez pas à rajouter la votre via une issue ou une pull request_
|
||||
|
||||
## License
|
||||
|
||||
|
@ -23,7 +23,7 @@ Logiciel libre sous license AGPL V3
|
|||
|
||||
Dépendances :
|
||||
|
||||
- php >= 5.6
|
||||
- php >= 5.6
|
||||
- rsvg-convert
|
||||
- pdftk
|
||||
- imagemagick
|
||||
|
@ -75,13 +75,13 @@ DocumentRoot /path/to/signaturepdf/public
|
|||
|
||||
```bash
|
||||
docker build -t signaturepdf .
|
||||
````
|
||||
```
|
||||
|
||||
#### Lancement d'un conteneur
|
||||
|
||||
```bash
|
||||
docker run -d --name=signaturepdf -p 8080:80 signaturepdf
|
||||
````
|
||||
```
|
||||
|
||||
[localhost:8080](http://localhost:8080)
|
||||
|
||||
|
@ -89,17 +89,19 @@ docker run -d --name=signaturepdf -p 8080:80 signaturepdf
|
|||
|
||||
Les variables suivantes permettent de configurer le déployement :
|
||||
|
||||
|Variable|description|exemple|defaut|
|
||||
|-----|-----|-----|-----|
|
||||
|`SERVERNAME`|url de déploiement|`pdf.24eme.fr`|localhost|
|
||||
|`UPLOAD_MAX_FILESIZE`|Taille maximum du fichier PDF à signer|48M|24M|
|
||||
|`POST_MAX_SIZE`|Taille maximum du fichier PDF à signer|48M|24M|
|
||||
|`MAX_FILE_UPLOADS`|Nombre de pages maximum du PDF, ici 200 pages + le PDF d'origine|401|201|
|
||||
|`PDF_STORAGE_PATH`|chemin vers lequel les fichiers pdf uploadés pourront être stockés|/data||
|
||||
| Variable | description | exemple | defaut |
|
||||
| ---------------------- | ------------------------------------------------------------------ | -------------------------------- | --------- |
|
||||
| `SERVERNAME` | url de déploiement | `pdf.24eme.fr` | localhost |
|
||||
| `UPLOAD_MAX_FILESIZE` | Taille maximum du fichier PDF à signer | 48M | 24M |
|
||||
| `POST_MAX_SIZE` | Taille maximum du fichier PDF à signer | 48M | 24M |
|
||||
| `MAX_FILE_UPLOADS` | Nombre de pages maximum du PDF, ici 200 pages + le PDF d'origine | 401 | 201 |
|
||||
| `PDF_STORAGE_PATH` | chemin vers lequel les fichiers pdf uploadés pourront être stockés | /data | /data |
|
||||
| `DISABLE_ORGANIZATION` | Desactiver la route Organiser | true | false |
|
||||
| `PDF_DEMO_LINK` | Afficher, retirer ou changer le lien de PDF de démo | false, `link` or `relative path` | true |
|
||||
|
||||
```bash
|
||||
docker run -d --name=signaturepdf -p 8080:80 -e SERVERNAME=pdf.example.org -e UPLOAD_MAX_FILESIZE=48M -e POST_MAX_SIZE=48M -e MAX_FILE_UPLOADS=401 -e PDF_STORAGE_PATH=/data signaturepdf
|
||||
````
|
||||
```
|
||||
|
||||
### Alpine
|
||||
|
||||
|
@ -107,6 +109,7 @@ Voici un script permettant d'installer la solution sous Linux Alpine (testé en
|
|||
Pensez à éditer la variable "domain" en début de script pour correspondre à l'URL avec laquelle elle sera appelée.
|
||||
|
||||
Les composants principaux sont :
|
||||
|
||||
- php 8 + php-fpm
|
||||
- Nginx
|
||||
- pdftk (installation "manuelle" nécessitant openjdk8)
|
||||
|
@ -115,6 +118,7 @@ Les composants principaux sont :
|
|||
- librsvg
|
||||
|
||||
Ce que fait le script :
|
||||
|
||||
- Installation des dépendances
|
||||
- Configuration de php et php-fpm
|
||||
- Configuration d'Nginx
|
||||
|
@ -126,7 +130,7 @@ Ce que fait le script :
|
|||
|
||||
domain='sign.example.com'
|
||||
|
||||
apk update
|
||||
apk update
|
||||
apk add bash nginx git php8 php8-fpm php8-session php8-gd php8-fileinfo openjdk8 imagemagick potrace librsvg
|
||||
|
||||
cd /tmp
|
||||
|
@ -162,7 +166,7 @@ server {
|
|||
server_name ${domain};
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
|
||||
root /var/www/signaturepdf/public/;
|
||||
|
||||
index index.php index.html;
|
||||
|
@ -183,7 +187,7 @@ server {
|
|||
fastcgi_buffers 128 128k;
|
||||
fastcgi_param PATH_INFO \$fastcgi_path_info;
|
||||
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
|
||||
}
|
||||
|
||||
|
@ -221,11 +225,13 @@ cp config/config.ini{.example,}
|
|||
```
|
||||
|
||||
Dans ce fichier `config/config.ini`, il suffit ce configurer la variable `PDF_STORAGE_PATH` avec le chemin vers lequel les fichiers pdf uploadés pourront être stockés :
|
||||
|
||||
```
|
||||
PDF_STORAGE_PATH=/path/to/folder
|
||||
```
|
||||
|
||||
Créer ce dossier :
|
||||
|
||||
```
|
||||
mkdir /path/to/folder
|
||||
```
|
||||
|
@ -233,10 +239,32 @@ mkdir /path/to/folder
|
|||
Le serveur web devra avoir les droits en écriture sur ce dossier.
|
||||
|
||||
Par exemple pour apache :
|
||||
|
||||
```
|
||||
chown www-data /path/to/folder/to/store/pdf
|
||||
```
|
||||
|
||||
### Desactivation du mode Organiser
|
||||
|
||||
Pour desactiver le mode Organiser, ajouter `DISABLE_ORGANIZATION=true` dans le fichier
|
||||
`config/config.ini`.
|
||||
|
||||
### Cacher ou modifier le lien de PDF de démo
|
||||
|
||||
Pour cacher le lien de pdf de démo, ajouter `PDF_DEMO_LINK=false` dans le fichier
|
||||
`config/config.ini`.
|
||||
|
||||
### Champs chargés par défaut pour l'édition de métadonnéés
|
||||
|
||||
Dans le fichier de configuration `config/config.ini` il est possible de rajouter autant de champs que l'on souhaite avec le type HTML de l'input (text, date, number email, etc ...) qui seront préchargées pour chaque PDF.
|
||||
|
||||
```
|
||||
METADATA_DEFAULT_FIELDS[field1].type = "text"
|
||||
METADATA_DEFAULT_FIELDS[field2].type = "text"
|
||||
METADATA_DEFAULT_FIELDS[field3].type = "date"
|
||||
METADATA_DEFAULT_FIELDS[field4].type = "number"
|
||||
```
|
||||
|
||||
## Mise à jour
|
||||
|
||||
La dernière version stable est sur la branche `master`, pour la mise à jour il suffit de récupérer les dernières modifications :
|
||||
|
@ -273,6 +301,7 @@ DEBUG=1 make test
|
|||
- **OpenType.js** outils de transformation d'un texte et sa police en chemin : https://github.com/opentypejs/opentype.js (MIT)
|
||||
- **ImageMagick** ensemble d'outils de manipulation d'images : https://imagemagick.org/ (Apache-2.0)
|
||||
- **Caveat** police de caractères style écriture à la main : https://github.com/googlefonts/caveat (OFL-1.1)
|
||||
- **PDF-LIB** librairie js permettant de manipuler un PDF qui est utilisé pour écrire dans les métadonnées : https://pdf-lib.js.org/ (MIT)
|
||||
|
||||
Pour les tests :
|
||||
|
||||
|
@ -290,6 +319,3 @@ Pour les tests :
|
|||
Logilab a apporté une contribution financière de 1 365 € TTC à la société 24ème pour développer le mode multi signature.
|
||||
|
||||
Le développement du logiciel a principalement été réalisé sur le temps de travail de salariés du 24ème.
|
||||
|
||||
|
||||
|
||||
|
|
41
app.php
41
app.php
|
@ -10,12 +10,23 @@ $f3->set('XFRAME', null); // Allow use in an iframe
|
|||
$f3->set('ROOT', __DIR__);
|
||||
$f3->set('UI', $f3->get('ROOT')."/templates/");
|
||||
$f3->set('UPLOADS', sys_get_temp_dir()."/");
|
||||
$f3->set('COMMIT', getCommit());
|
||||
|
||||
$f3->config(__DIR__.'/config/config.ini');
|
||||
|
||||
if($f3->get('PDF_STORAGE_PATH') && !preg_match('|/$|', $f3->get('PDF_STORAGE_PATH'))) {
|
||||
$f3->set('PDF_STORAGE_PATH', $f3->get('PDF_STORAGE_PATH').'/');
|
||||
}
|
||||
|
||||
if($f3->get('PDF_DEMO_LINK') === null || $f3->get('PDF_DEMO_LINK') === true) {
|
||||
$f3->set('PDF_DEMO_LINK', 'https://raw.githubusercontent.com/24eme/signaturepdf/master/tests/files/document.pdf');
|
||||
}
|
||||
|
||||
$f3->set('disableOrganization', false);
|
||||
if($f3->get('DISABLE_ORGANIZATION')) {
|
||||
$f3->set('disableOrganization', $f3->get('DISABLE_ORGANIZATION'));
|
||||
}
|
||||
|
||||
$f3->route('GET /',
|
||||
function($f3) {
|
||||
$f3->reroute('/signature');
|
||||
|
@ -294,6 +305,7 @@ $f3->route('GET /cron', function($f3) {
|
|||
}
|
||||
});
|
||||
|
||||
if (!$f3->get('disableOrganization')) {
|
||||
$f3->route('GET /organization',
|
||||
function($f3) {
|
||||
$f3->set('maxSize', min(array(convertPHPSizeToBytes(ini_get('post_max_size')), convertPHPSizeToBytes(ini_get('upload_max_filesize')))));
|
||||
|
@ -319,7 +331,7 @@ $f3->route('POST /organize',
|
|||
$filenames[] = str_replace('.pdf', '', $fileBaseName);
|
||||
|
||||
return basename($tmpfile).uniqid().".pdf";
|
||||
});
|
||||
});
|
||||
|
||||
if(!count($files)) {
|
||||
$f3->error(403);
|
||||
|
@ -341,6 +353,33 @@ $f3->route('POST /organize',
|
|||
array_map('unlink', glob($tmpfile."*"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$f3->route('GET /metadata',
|
||||
function($f3) {
|
||||
echo View::instance()->render('metadata.html.php');
|
||||
}
|
||||
);
|
||||
|
||||
function getCommit() {
|
||||
if(!file_exists(__DIR__.'/.git/HEAD')) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$head = str_replace(["ref: ", "\n"], "", file_get_contents(__DIR__.'/.git/HEAD'));
|
||||
$commit = null;
|
||||
|
||||
if(strpos($head, "refs/") !== 0) {
|
||||
$commit = $head;
|
||||
}
|
||||
|
||||
if(file_exists(__DIR__.'/.git/'.$head)) {
|
||||
$commit = str_replace("\n", "", file_get_contents(__DIR__.'/.git/'.$head));
|
||||
}
|
||||
|
||||
return substr($commit, 0, 7);
|
||||
}
|
||||
|
||||
function convertPHPSizeToBytes($sSize)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
[globals]
|
||||
|
||||
# Path to which stored pdf to activate the mode of sharing a signature to several.
|
||||
# To deactivate this mode, simply do not configure it or leave it empty
|
||||
PDF_STORAGE_PATH=/path/to/folder
|
||||
; Path to which stored pdf to activate the mode of sharing a signature to several.
|
||||
; To deactivate this mode, simply do not configure it or leave it empty
|
||||
PDF_STORAGE_PATH=/path/to/folder
|
||||
|
||||
; Disable organization tab and routes
|
||||
;DISABLE_ORGANIZATION=false
|
||||
|
||||
; Manage demo link pdf : true (by default, show with default link), false (hide), or custom link
|
||||
;PDF_DEMO_LINK=true
|
||||
|
||||
; Metadata default fields
|
||||
;METADATA_DEFAULT_FIELDS[metadata_key].type = "text"
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
[globals]
|
||||
|
||||
# Path to which stored pdf to activate the mode of sharing a signature to several.
|
||||
# To deactivate this mode, simply do not configure it or leave it empty
|
||||
; Path to which stored pdf to activate the mode of sharing a signature to several.
|
||||
; To deactivate this mode, simply do not configure it or leave it empty
|
||||
PDF_STORAGE_PATH=${PDF_STORAGE_PATH}
|
||||
|
||||
; Disable organization tab and routes
|
||||
DISABLE_ORGANIZATION=${DISABLE_ORGANIZATION}
|
||||
|
||||
; Manage demo link pdf : true (by default, show), false (hide), or custom link
|
||||
PDF_DEMO_LINK=${PDF_DEMO_LINK}
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
#! /bin/bash
|
||||
|
||||
envsubst < /usr/local/signaturepdf/config/apache.conf > /etc/apache2/sites-available/signaturepdf.conf
|
||||
|
||||
envsubst < /usr/local/signaturepdf/config/php.ini > /usr/local/etc/php/conf.d/uploads.ini
|
||||
|
||||
envsubst < /usr/local/signaturepdf/config/config.ini.tpl > /usr/local/signaturepdf/config/config.ini
|
||||
|
||||
chown -R www-data:www-data /usr/local/signaturepdf && chmod 750 -R /usr/local/signaturepdf
|
||||
|
||||
if [[ ! -z $PDF_STORAGE_PATH ]] ; then
|
||||
mkdir -p $PDF_STORAGE_PATH
|
||||
chown -R www-data:www-data $PDF_STORAGE_PATH
|
||||
chmod 750 -R $PDF_STORAGE_PATH
|
||||
chown www-data:www-data $PDF_STORAGE_PATH
|
||||
fi
|
||||
|
||||
apache2-foreground
|
||||
|
|
|
@ -63,6 +63,20 @@
|
|||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.delete-metadata {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
font-size: 1.2rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.input-metadata:hover > .delete-metadata {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.subtitle {
|
||||
font-size: .875em
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,300 @@
|
|||
var windowWidth = window.innerWidth;
|
||||
var menu = null;
|
||||
var menuOffcanvas = null;
|
||||
var is_mobile = function() {
|
||||
return !(window.getComputedStyle(document.getElementById('is_mobile')).display === "none");
|
||||
};
|
||||
|
||||
var responsiveDisplay = function() {
|
||||
if(is_mobile()) {
|
||||
menu.classList.remove('show');
|
||||
menuOffcanvas.hide();
|
||||
} else {
|
||||
menuOffcanvas.show();
|
||||
}
|
||||
menu.classList.remove('d-md-block');
|
||||
menu.classList.remove('d-none');
|
||||
};
|
||||
|
||||
var pdfjsLib = window['pdfjs-dist/build/pdf'];
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = '/vendor/pdf.worker.js?legacy';
|
||||
var nbPDF = 0;
|
||||
var pages = [];
|
||||
var pdfRenderTasks = [];
|
||||
let filename = null
|
||||
let pdffile = null
|
||||
let deletedMetadata = [];
|
||||
|
||||
var loadPDF = async function(pdfBlob, filename, pdfIndex) {
|
||||
let url = await URL.createObjectURL(pdfBlob);
|
||||
|
||||
pdffile = pdfBlob
|
||||
|
||||
let loadingTask = pdfjsLib.getDocument(url);
|
||||
document.querySelector('#text_document_name span').innerText = filename;
|
||||
await loadingTask.promise.then(function(pdf) {
|
||||
pdf.getMetadata().then(function(metadata) {
|
||||
console.log(metadata);
|
||||
for(fieldKey in defaultFields) {
|
||||
addMetadata(fieldKey, null, defaultFields[fieldKey]['type'], false);
|
||||
}
|
||||
|
||||
for(metaKey in metadata.info) {
|
||||
if(metaKey == "Custom" || metaKey == "PDFFormatVersion" || metaKey.match(/^Is/) || metaKey == "Trapped") {
|
||||
continue;
|
||||
}
|
||||
addMetadata(metaKey, metadata.info[metaKey], "text", false);
|
||||
}
|
||||
|
||||
for(metaKey in metadata.info.Custom) {
|
||||
if(metaKey == "sha256") {
|
||||
continue;
|
||||
}
|
||||
|
||||
addMetadata(metaKey, metadata.info.Custom[metaKey], "text", false);
|
||||
}
|
||||
|
||||
for(let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) {
|
||||
pdf.getPage(pageNumber).then(function(page) {
|
||||
let pageIndex = (page.pageNumber - 1);
|
||||
pages[pageIndex] = page;
|
||||
pageRender(pageIndex);
|
||||
});
|
||||
}
|
||||
if(document.querySelector('.input-metadata input')) {
|
||||
document.querySelector('.input-metadata input').focus();
|
||||
} else {
|
||||
document.getElementById('input_metadata_key').focus();
|
||||
}
|
||||
});
|
||||
}, function (reason) {
|
||||
console.error(reason);
|
||||
});
|
||||
|
||||
return loadingTask;
|
||||
}
|
||||
|
||||
var pageRender = async function(pageIndex) {
|
||||
|
||||
let page = pages[pageIndex];
|
||||
|
||||
let viewport = page.getViewport({scale: 1});
|
||||
let sizeWidth = document.getElementById('container-pages').offsetWidth;
|
||||
let scaleWidth = sizeWidth / viewport.width;
|
||||
let viewportWidth = page.getViewport({scale: scaleWidth });
|
||||
|
||||
viewport = viewportWidth;
|
||||
|
||||
let canvasPDF = document.createElement('canvas');
|
||||
canvasPDF.classList.add('shadow-sm');
|
||||
document.getElementById('container-pages').appendChild(canvasPDF);
|
||||
let context = canvasPDF.getContext('2d');
|
||||
canvasPDF.height = viewport.height;
|
||||
canvasPDF.width = viewport.width;
|
||||
|
||||
if(pdfRenderTasks[pageIndex]) {
|
||||
pdfRenderTasks[pageIndex].cancel();
|
||||
}
|
||||
pdfRenderTasks[pageIndex] = await page.render({
|
||||
canvasContext: context,
|
||||
viewport: viewport,
|
||||
});
|
||||
}
|
||||
|
||||
var addMetadata = function(key, value, type, focus) {
|
||||
let input = document.querySelector('.input-metadata input[name="'+key+'"]');
|
||||
|
||||
if(input && !input.value) {
|
||||
input.value = value;
|
||||
}
|
||||
if(input && focus) {
|
||||
input.focus();
|
||||
}
|
||||
if(input) {
|
||||
return;
|
||||
}
|
||||
|
||||
let div = document.createElement('div');
|
||||
div.classList.add('form-floating', 'mt-3', 'input-metadata');
|
||||
|
||||
input = document.createElement('input');
|
||||
input.value = value;
|
||||
input.type = type;
|
||||
input.name = key;
|
||||
input.classList.add('form-control');
|
||||
|
||||
let label = document.createElement('label');
|
||||
label.innerText = key;
|
||||
|
||||
let deleteButton = document.createElement('div')
|
||||
deleteButton.title = "Supprimer cette metadonnée"
|
||||
deleteButton.innerHTML = "×"
|
||||
deleteButton.classList.add('delete-metadata')
|
||||
|
||||
div.appendChild(input);
|
||||
div.appendChild(label);
|
||||
div.appendChild(deleteButton);
|
||||
document.getElementById('form-metadata-container').appendChild(div);
|
||||
|
||||
if(focus) {
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
const deleteMetadata = function(el) {
|
||||
if (confirm("Souhaitez-vous supprimer ce champ ?") === false) return;
|
||||
|
||||
const input = el.closest('.input-metadata')
|
||||
const label = input.querySelector('label').innerText
|
||||
deletedMetadata.push(label)
|
||||
input.remove()
|
||||
}
|
||||
|
||||
const DL = function (d,f) {
|
||||
let a = document.createElement("a"),
|
||||
u = URL.createObjectURL(d);
|
||||
a.download = f,
|
||||
a.href = u,
|
||||
a.click(),
|
||||
setTimeout(() => URL.revokeObjectURL(u))
|
||||
}
|
||||
|
||||
const save = async function () {
|
||||
const PDFDocument = window['PDFLib'].PDFDocument
|
||||
const PDFHexString = window['PDFLib'].PDFHexString
|
||||
const PDFName = window['PDFLib'].PDFName
|
||||
|
||||
const arrayBuffer = await pdffile.arrayBuffer();
|
||||
const pdf = await PDFDocument.load(arrayBuffer);
|
||||
|
||||
deletedMetadata.forEach(function (el) {
|
||||
pdf.getInfoDict().delete(PDFName.of(el))
|
||||
});
|
||||
|
||||
([...document.getElementsByClassName('input-metadata')] || []).forEach(function (el) {
|
||||
const label = el.querySelector('label').innerText
|
||||
const input = el.querySelector('input').value
|
||||
|
||||
pdf.getInfoDict().set(PDFName.of(label), PDFHexString.fromText(input));
|
||||
});
|
||||
|
||||
const newPDF = new Blob([await pdf.save()], {type: "application/pdf"});
|
||||
DL(newPDF, filename)
|
||||
}
|
||||
|
||||
var createEventsListener = function() {
|
||||
document.getElementById('form_metadata_add').addEventListener('submit', function(e) {
|
||||
let formData = new FormData(this);
|
||||
addMetadata(formData.get('metadata_key'), "", "text", true);
|
||||
this.classList.add('invisible');
|
||||
setTimeout(function() { document.getElementById('form_metadata_add').classList.remove('invisible'); }, 400);
|
||||
this.reset();
|
||||
e.preventDefault();
|
||||
});
|
||||
document.getElementById('input_metadata_value').addEventListener('focus', function(e) {
|
||||
if(document.getElementById('input_metadata_key').value) {
|
||||
document.querySelector('#form_metadata_add button').click();
|
||||
}
|
||||
});
|
||||
document.addEventListener('click', function (event) {
|
||||
if (event.target.closest(".delete-metadata")) {
|
||||
deleteMetadata(event.target)
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('save').addEventListener('click', function (e) {
|
||||
save()
|
||||
})
|
||||
}
|
||||
|
||||
async function getPDFBlobFromCache(cacheUrl) {
|
||||
const cache = await caches.open('pdf');
|
||||
let responsePdf = await cache.match(cacheUrl);
|
||||
|
||||
if(!responsePdf) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let pdfBlob = await responsePdf.blob();
|
||||
|
||||
return pdfBlob;
|
||||
}
|
||||
|
||||
async function uploadFromUrl(url) {
|
||||
history.replaceState({}, '', '/metadata');
|
||||
var response = await fetch(url);
|
||||
if(response.status != 200) {
|
||||
return;
|
||||
}
|
||||
var pdfBlob = await response.blob();
|
||||
|
||||
if(pdfBlob.type != 'application/pdf' && pdfBlob.type != 'application/octet-stream') {
|
||||
return;
|
||||
}
|
||||
let dataTransfer = new DataTransfer();
|
||||
let filename = url.replace(/^.*\//, '');
|
||||
dataTransfer.items.add(new File([pdfBlob], filename, {
|
||||
type: 'application/pdf'
|
||||
}));
|
||||
document.getElementById('input_pdf_upload').files = dataTransfer.files;
|
||||
document.getElementById('input_pdf_upload').dispatchEvent(new Event("change"));
|
||||
}
|
||||
|
||||
var pageUpload = async function() {
|
||||
document.querySelector('body').classList.remove('bg-light');
|
||||
document.getElementById('input_pdf_upload').value = '';
|
||||
document.getElementById('page-upload').classList.remove('d-none');
|
||||
document.getElementById('page-metadata').classList.add('d-none');
|
||||
document.getElementById('input_pdf_upload').focus();
|
||||
let cache;
|
||||
try {
|
||||
cache = await caches.open('pdf');
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
alert("Erreur d'accès au cache. Cette application ne fonctionne pas en mode de navigation privée");
|
||||
return;
|
||||
}
|
||||
document.getElementById('input_pdf_upload').addEventListener('change', async function(event) {
|
||||
let filename = document.getElementById('input_pdf_upload').files[0].name;
|
||||
let response = new Response(document.getElementById('input_pdf_upload').files[0], { "status" : 200, "statusText" : "OK" });
|
||||
let urlPdf = '/pdf/'+filename;
|
||||
await cache.put(urlPdf, response);
|
||||
history.pushState({}, '', '/metadata#'+filename);
|
||||
pageMetadata(urlPdf)
|
||||
});
|
||||
}
|
||||
|
||||
var pageMetadata = async function(url) {
|
||||
filename = url.replace('/pdf/', '');
|
||||
document.title = filename + ' - ' + document.title;
|
||||
document.querySelector('body').classList.add('bg-light');
|
||||
document.getElementById('page-upload').classList.add('d-none');
|
||||
document.getElementById('page-metadata').classList.remove('d-none');
|
||||
menu = document.getElementById('sidebarTools');
|
||||
menuOffcanvas = new bootstrap.Offcanvas(menu);
|
||||
responsiveDisplay();
|
||||
|
||||
let pdfBlob = await getPDFBlobFromCache(url);
|
||||
if(!pdfBlob) {
|
||||
document.location = '/metadata';
|
||||
return;
|
||||
}
|
||||
|
||||
createEventsListener();
|
||||
loadPDF(pdfBlob, filename, nbPDF);
|
||||
};
|
||||
|
||||
(function () {
|
||||
if(window.location.hash && window.location.hash.match(/^\#http/)) {
|
||||
let hashUrl = window.location.hash.replace(/^\#/, '');
|
||||
pageUpload();
|
||||
uploadFromUrl(hashUrl);
|
||||
} else if(window.location.hash) {
|
||||
pageMetadata('/pdf/'+window.location.hash.replace(/^\#/, ''));
|
||||
} else {
|
||||
pageUpload();
|
||||
}
|
||||
window.addEventListener('hashchange', function() {
|
||||
window.location.reload();
|
||||
})
|
||||
})();
|
|
@ -91,8 +91,8 @@ var loadPDF = async function(pdfBlob, filename, pdfIndex) {
|
|||
pageHTML += '<div title="Supprimer cette page" class="position-absolute top-50 start-0 translate-middle-y p-2 ps-3 pe-3 ms-2 rounded-circle btn-delete d-none"><i class="bi bi-trash"></i></div>';
|
||||
pageHTML += '<div title="Restaurer cette page" class="position-absolute top-50 start-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-restore d-none"><i class="bi bi-recycle"></i></div>';
|
||||
pageHTML += '<div title="Déplacer cette page" class="position-absolute top-50 start-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag d-none"><i class="bi bi-arrows-move"></i></div>';
|
||||
pageHTML += '<div title="Déplacer ici" class="position-absolute start-0 top-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag-here-left bg-white shadow d-none" style="left: -5px !important;"><i style="display: block; transform: rotate(90deg) !important; left: -5px !important;" class="bi bi-arrows-collapse"></i></div>';
|
||||
pageHTML += '<div title="Déplacer ici" class="position-absolute start-100 top-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag-here-right bg-white shadow d-none" style="margin-left: 3px !important;"><i style="display: block; transform: rotate(90deg) !important;" class="bi bi-arrows-collapse"></i></div>';
|
||||
pageHTML += '<div title="Déplacer ici" class="position-absolute start-0 top-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag-here-left bg-white shadow d-none" style="left: -5px !important;"><i class="bi bi-arrow-up-square"></i></div>';
|
||||
pageHTML += '<div title="Déplacer ici" class="position-absolute start-100 top-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag-here-right bg-white shadow d-none" style="margin-left: 3px !important;"><i class="bi bi-arrow-up-square"></i></div>';
|
||||
pageHTML += '<div title="Déplacer ici" class="position-absolute top-100 start-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag-here_mobile bg-white shadow d-none"><i class="bi bi-arrows-collapse"></i></div>';
|
||||
pageHTML += '<div title="Tourner cette page" class="position-absolute top-50 end-0 translate-middle-y p-2 ps-3 pe-3 me-2 rounded-circle container-rotate btn-rotate d-none"><i class="bi bi-arrow-clockwise"></i></div>';
|
||||
pageHTML += '<div title="Télécharger cette page" class="position-absolute bottom-0 start-50 translate-middle-x p-2 ps-3 pe-3 mb-3 rounded-circle btn-download d-none"><i class="bi bi-download"></i></div>';
|
||||
|
@ -481,12 +481,6 @@ var updatePageState = function(page) {
|
|||
page.querySelector('.btn-drag-here-left').classList.remove('d-none');
|
||||
page.querySelector('.btn-drag-here-right').classList.remove('d-none');
|
||||
}
|
||||
if(page.previousSibling && page.previousSibling.draggable && isPageDragged(page.previousSibling) && isDraggedMode()) {
|
||||
page.querySelector('.btn-drag-here-left').classList.add('d-none');
|
||||
}
|
||||
if(page.nextSibling && page.nextSibling.draggable && isPageDragged(page.nextSibling) && isDraggedMode()) {
|
||||
page.querySelector('.btn-drag-here-right').classList.add('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
var updateFilesState = function() {
|
||||
|
@ -523,6 +517,7 @@ var updateGlobalState = function() {
|
|||
document.querySelector('#top_bar_action_selection').classList.add('d-none');
|
||||
document.querySelector('#bottom_bar_action').classList.remove('d-none');
|
||||
document.querySelector('#bottom_bar_action_selection').classList.add('d-none');
|
||||
document.querySelector('#save').removeAttribute('disabled');
|
||||
|
||||
if(isSelectionMode()) {
|
||||
document.querySelector('#container_btn_select .card-header span').innerText = document.querySelectorAll('.canvas-container .input-select:checked').length;
|
||||
|
@ -544,6 +539,7 @@ var updateGlobalState = function() {
|
|||
document.querySelector('#top_bar_action').classList.add('d-none');
|
||||
document.querySelector('#bottom_bar_action_selection').classList.remove('d-none');
|
||||
document.querySelector('#bottom_bar_action').classList.add('d-none');
|
||||
document.querySelector('#save').setAttribute('disabled', 'disabled');
|
||||
}
|
||||
if(isDraggedMode()) {
|
||||
document.querySelector('#modalDrag .modal-body').insertAdjacentElement('afterbegin', document.querySelector('#container-pages'));
|
||||
|
@ -560,6 +556,24 @@ var degreesToOrientation = function(degrees) {
|
|||
return null;
|
||||
}
|
||||
|
||||
var uploadAndLoadPDF = async function(input_upload) {
|
||||
const cache = await caches.open('pdf');
|
||||
for (let i = 0; i < input_upload.files.length; i++) {
|
||||
if(input_upload.files[i].size > maxSize) {
|
||||
|
||||
alert("Le PDF ne doit pas dépasser " + Math.round(maxSize/1024/1024) + " Mo");
|
||||
break;
|
||||
}
|
||||
let filename = input_upload.files[i].name;
|
||||
let response = new Response(input_upload.files[i], { "status" : 200, "statusText" : "OK" });
|
||||
let urlPdf = '/pdf/'+filename;
|
||||
await cache.put(urlPdf, response);
|
||||
let pdfBlob = await getPDFBlobFromCache(urlPdf);
|
||||
nbPDF++;
|
||||
await loadPDF(pdfBlob, filename, nbPDF);
|
||||
}
|
||||
}
|
||||
|
||||
var createEventsListener = function() {
|
||||
document.getElementById('save-select_mobile').addEventListener('click', function(event) {
|
||||
document.getElementById('save').click();
|
||||
|
@ -596,21 +610,7 @@ var createEventsListener = function() {
|
|||
document.getElementById('save').click();
|
||||
});
|
||||
document.getElementById('input_pdf_upload_2').addEventListener('change', async function(event) {
|
||||
for (let i = 0; i < this.files.length; i++) {
|
||||
if(this.files[i].size > maxSize) {
|
||||
|
||||
alert("Le PDF ne doit pas dépasser " + Math.round(maxSize/1024/1024) + " Mo");
|
||||
break;
|
||||
}
|
||||
const cache = await caches.open('pdf');
|
||||
let filename = this.files[i].name;
|
||||
let response = new Response(this.files[i], { "status" : 200, "statusText" : "OK" });
|
||||
let urlPdf = '/pdf/'+filename;
|
||||
await cache.put(urlPdf, response);
|
||||
let pdfBlob = await getPDFBlobFromCache(urlPdf);
|
||||
nbPDF++;
|
||||
await loadPDF(pdfBlob, filename, nbPDF);
|
||||
}
|
||||
await uploadAndLoadPDF(this);
|
||||
this.value = '';
|
||||
});
|
||||
document.getElementById('btn-zoom-decrease').addEventListener('click', function(event) {
|
||||
|
@ -696,6 +696,13 @@ var createEventsListener = function() {
|
|||
document.querySelector('#modalDrag').addEventListener('hidden.bs.modal', event => {
|
||||
document.querySelector('#list_pdf_container').insertAdjacentElement('afterbegin', document.querySelector('#list_pdf'));
|
||||
});
|
||||
document.querySelector('body').addEventListener('click', function(event) {
|
||||
if(!event.originalTarget.classList.contains('offcanvas-header') && !event.originalTarget.classList.contains('offcanvas-body') && event.originalTarget.id != 'container-pages' && event.originalTarget.id != 'sidebarToolsLabel' && event.originalTarget.id != 'btn_container') {
|
||||
return;
|
||||
}
|
||||
document.getElementById('btn_cancel_select').click();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async function getPDFBlobFromCache(cacheUrl) {
|
||||
|
@ -737,41 +744,28 @@ var pageUpload = async function() {
|
|||
document.getElementById('page-upload').classList.remove('d-none');
|
||||
document.getElementById('page-organization').classList.add('d-none');
|
||||
document.getElementById('input_pdf_upload').focus();
|
||||
const cache = await caches.open('pdf');
|
||||
let cache;
|
||||
try {
|
||||
cache = await caches.open('pdf');
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
alert("Erreur d'accès au cache. Cette application ne fonctionne pas en mode de navigation privée");
|
||||
return;
|
||||
}
|
||||
document.getElementById('input_pdf_upload').addEventListener('change', async function(event) {
|
||||
if(document.getElementById('input_pdf_upload').files[0].size > maxSize) {
|
||||
|
||||
alert("Le PDF ne doit pas dépasser " + Math.round(maxSize/1024/1024) + " Mo");
|
||||
document.getElementById('input_pdf_upload').value = "";
|
||||
return;
|
||||
}
|
||||
let filename = document.getElementById('input_pdf_upload').files[0].name;
|
||||
let response = new Response(document.getElementById('input_pdf_upload').files[0], { "status" : 200, "statusText" : "OK" });
|
||||
let urlPdf = '/pdf/'+filename;
|
||||
await cache.put(urlPdf, response);
|
||||
history.pushState({}, '', '/organization#'+filename);
|
||||
pageOrganization(urlPdf)
|
||||
uploadAndLoadPDF(this);
|
||||
pageOrganization(null);
|
||||
});
|
||||
}
|
||||
|
||||
var pageOrganization = async function(url) {
|
||||
let filename = url.replace('/pdf/', '');
|
||||
document.title = filename + ' - ' + document.title;
|
||||
var pageOrganization = async function() {
|
||||
document.querySelector('body').classList.add('bg-light');
|
||||
document.getElementById('page-upload').classList.add('d-none');
|
||||
document.getElementById('page-organization').classList.remove('d-none');
|
||||
menu = document.getElementById('sidebarTools');
|
||||
menuOffcanvas = new bootstrap.Offcanvas(menu);
|
||||
responsiveDisplay();
|
||||
|
||||
let pdfBlob = await getPDFBlobFromCache(url);
|
||||
if(!pdfBlob) {
|
||||
document.location = '/organization';
|
||||
return;
|
||||
}
|
||||
|
||||
createEventsListener();
|
||||
loadPDF(pdfBlob, filename, nbPDF);
|
||||
};
|
||||
|
||||
(function () {
|
||||
|
@ -779,8 +773,6 @@ var pageOrganization = async function(url) {
|
|||
let hashUrl = window.location.hash.replace(/^\#/, '');
|
||||
pageUpload();
|
||||
uploadFromUrl(hashUrl);
|
||||
} else if(window.location.hash) {
|
||||
pageOrganization('/pdf/'+window.location.hash.replace(/^\#/, ''));
|
||||
} else {
|
||||
pageUpload();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ var menuOffcanvas = null;
|
|||
var currentCursor = null;
|
||||
var signaturePad = null;
|
||||
var nblayers = null;
|
||||
var hasModifications = false;
|
||||
var currentTextScale = 1;
|
||||
|
||||
var loadPDF = async function(pdfBlob, filename) {
|
||||
const pdfjsLib = window['pdfjs-dist/build/pdf'];
|
||||
|
@ -135,6 +137,10 @@ var loadPDF = async function(pdfBlob, filename) {
|
|||
}
|
||||
});
|
||||
canvasEdition.on('object:scaled', function(event) {
|
||||
if (event.target instanceof fabric.IText) {
|
||||
currentTextScale = event.target.scaleX;
|
||||
return;
|
||||
}
|
||||
var item = getSvgItem(event.target.svgOrigin);
|
||||
if(!item) {
|
||||
return;
|
||||
|
@ -495,9 +501,15 @@ var addObjectInCanvas = function(canvas, item) {
|
|||
};
|
||||
|
||||
var createAndAddSvgInCanvas = function(canvas, item, x, y, height = null) {
|
||||
if(document.querySelector('#alert-signature-help')) {
|
||||
document.querySelector('#alert-signature-help').classList.add('d-none');
|
||||
}
|
||||
if(document.getElementById('save')) {
|
||||
document.getElementById('save').removeAttribute('disabled');
|
||||
}
|
||||
|
||||
hasModifications = true;
|
||||
|
||||
if(document.getElementById('save_mobile')) {
|
||||
document.getElementById('save_mobile').removeAttribute('disabled');
|
||||
}
|
||||
|
@ -526,6 +538,8 @@ var createAndAddSvgInCanvas = function(canvas, item, x, y, height = null) {
|
|||
addObjectInCanvas(canvas, textbox).setActiveObject(textbox);
|
||||
textbox.keysMap[13] = "exitEditing";
|
||||
textbox.lockScalingFlip = true;
|
||||
textbox.scaleX = currentTextScale;
|
||||
textbox.scaleY = currentTextScale;
|
||||
textbox.enterEditing();
|
||||
textbox.selectAll();
|
||||
|
||||
|
@ -709,6 +723,10 @@ var createEventsListener = function() {
|
|||
}
|
||||
|
||||
document.querySelector('#'+svg_list_id+' label:last-child').click();
|
||||
|
||||
if(document.querySelector('#save').disabled && document.querySelector('#alert-signature-help.auto-open') && !is_mobile()) {
|
||||
document.querySelector('#alert-signature-help').classList.remove('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
@ -762,6 +780,17 @@ var createEventsListener = function() {
|
|||
event.preventDefault();
|
||||
});
|
||||
|
||||
if(document.querySelector('#alert-signature-help')) {
|
||||
document.getElementById('btn-signature-help').addEventListener('click', function(event) {
|
||||
document.querySelector('#alert-signature-help').classList.remove('d-none');
|
||||
event.preventDefault();
|
||||
});
|
||||
document.querySelector('#alert-signature-help .btn-close').addEventListener('click', function(event) {
|
||||
document.querySelector('#alert-signature-help').classList.add('d-none');
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
if(document.getElementById('save')) {
|
||||
document.getElementById('save').addEventListener('click', function(event) {
|
||||
let dataTransfer = new DataTransfer();
|
||||
|
@ -771,6 +800,7 @@ var createEventsListener = function() {
|
|||
}));
|
||||
})
|
||||
document.getElementById('input_svg').files = dataTransfer.files;
|
||||
hasModifications = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -785,6 +815,7 @@ var createEventsListener = function() {
|
|||
})
|
||||
}
|
||||
document.getElementById('input_svg_share').files = dataTransfer.files;
|
||||
hasModifications = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -903,6 +934,15 @@ var createEventsListener = function() {
|
|||
zoomChange(1)
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', function(event) {
|
||||
if(!hasModifications) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
return true;
|
||||
});
|
||||
|
||||
if(hash) {
|
||||
updateNbLayers();
|
||||
setInterval(function() {
|
||||
|
@ -996,7 +1036,14 @@ var pageUpload = async function() {
|
|||
document.getElementById('page-upload').classList.remove('d-none');
|
||||
document.getElementById('page-signature').classList.add('d-none');
|
||||
document.getElementById('input_pdf_upload').focus();
|
||||
const cache = await caches.open('pdf');
|
||||
let cache;
|
||||
try {
|
||||
cache = await caches.open('pdf');
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
alert("Erreur d'accès au cache. Cette application ne fonctionne pas en mode de navigation privée");
|
||||
return;
|
||||
}
|
||||
document.getElementById('input_pdf_upload').addEventListener('change', async function(event) {
|
||||
if(document.getElementById('input_pdf_upload').files[0].size > maxSize) {
|
||||
|
||||
|
@ -1050,6 +1097,10 @@ var pageSignature = async function(url) {
|
|||
svgCollections = JSON.parse(localStorage.getItem('svgCollections'));
|
||||
}
|
||||
|
||||
if(svgCollections.length == 0 && document.querySelector('#alert-signature-help')) {
|
||||
document.querySelector('#alert-signature-help').classList.add('auto-open');
|
||||
}
|
||||
|
||||
opentype.load('/vendor/fonts/Caveat-Regular.ttf', function(err, font) {
|
||||
fontCaveat = font;
|
||||
});
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg class="bi bi-vector-pen" width="128" height="128" fill="currentColor" version="1.1" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m84.48 1.8008a4.4307 4.4307 0 0 1 6.2738 0l35.445 35.445a4.4307 4.4307 0 0 1 0 6.2738l-16.854 16.854-7.3461 29.358a13.292 13.292 0 0 1-9.074 9.5082l-91.671 27.506 27.506-91.68a13.292 13.292 0 0 1 9.4994-9.0652l29.358-7.3372zm-15.95 25.769-28.117 7.0271a4.4307 4.4307 0 0 0-3.1724 3.0306l-22.774 75.898 75.915-22.774a4.4307 4.4307 0 0 0 3.0129-3.1635l7.0359-28.126-31.901-31.901z" fill-rule="evenodd" stroke-width="8.8614"/>
|
||||
<path d="m15.237 113.29 45.796-37.466a8.8614 8.8614 0 1 0-8.8614-8.8614l-37.466 45.796-0.2304 0.76208z" fill-rule="evenodd" stroke-width="8.8614"/>
|
||||
</svg>
|
After Width: | Height: | Size: 776 B |
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,107 @@
|
|||
<!doctype html>
|
||||
<html lang="fr_FR">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link href="/vendor/bootstrap.min.css?5.1.1" rel="stylesheet">
|
||||
<link href="/vendor/bootstrap-icons.css?1.5.0" rel="stylesheet">
|
||||
<link href="/css/app.css?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/css/app.css") ?>" rel="stylesheet">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon-metadata.ico">
|
||||
|
||||
<title>Édition des métadonnées d'un PDF</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<div class="alert alert-danger text-center" role="alert">
|
||||
<i class="bi bi-exclamation-triangle"></i> Site non fonctionnel sans JavaScript activé
|
||||
</div>
|
||||
</noscript>
|
||||
<div id="page-upload">
|
||||
<ul class="nav justify-content-center nav-tabs mt-2">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/signature"><i class="bi bi-vector-pen"></i> Signer</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/organization"><i class="bi bi-ui-checks-grid"></i> Organiser</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/metadata"><i class="bi bi-tags"></i> Metadonnées</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="px-4 py-4 text-center">
|
||||
<h1 class="display-5 fw-bold mb-0 mt-3"><i class="bi bi-tags"></i> Éditer les métadonnées</h1>
|
||||
<p class="fw-light mb-3 subtitle text-dark text-nowrap" style="overflow: hidden; text-overflow: ellipsis;">Ajouter, modifier ou supprimer les métadonnées d'un PDF</p>
|
||||
<div class="col-md-6 col-lg-5 col-xl-4 col-xxl-3 mx-auto">
|
||||
<div class="col-12">
|
||||
<label class="form-label mt-3" for="input_pdf_upload">Choisir un PDF</label>
|
||||
<input id="input_pdf_upload" placeholder="Choisir un PDF" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf" />
|
||||
<p class="mt-2 small fw-light text-dark"> </p>
|
||||
<?php if($PDF_DEMO_LINK): ?>
|
||||
<a class="btn btn-sm btn-link opacity-75" href="#<?php echo $PDF_DEMO_LINK ?>">Tester avec un PDF de démo</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="text-center text-muted mb-2 fixed-bottom opacity-75">
|
||||
<small>Logiciel libre <span class="d-none d-md-inline">sous license AGPL-3.0</span> : <a href="https://github.com/24eme/signaturepdf">voir le code source </a><?php if($COMMIT): ?> <span class="d-none d-md-inline small">[<a href="https://github.com/24eme/signaturepdf/tree/<?php echo $COMMIT ?>"><?php echo $COMMIT ?></a>]</span><?php endif; ?></small>
|
||||
</footer>
|
||||
</div>
|
||||
<div id="page-metadata" class="d-none">
|
||||
<div id="div-margin-top" style="height: 88px;" class="d-md-none"></div>
|
||||
<div style="width: 60%; overflow: auto;" class="vh-100" id="container-main">
|
||||
<div id="form-metadata" class="mx-auto w-75 pt-3 pb-5">
|
||||
<h3>Liste des métadonnées du PDF</h3>
|
||||
<div id="form-metadata-container">
|
||||
</div>
|
||||
<form id="form_metadata_add" class="position-relative">
|
||||
<hr class="text-muted mt-4 mb-3" />
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted" for="input_metadata_key">Ajouter une nouvelle métadonnée</label>
|
||||
<div class="form-floating">
|
||||
<input id="input_metadata_key" name="metadata_key" type="text" class="form-control" required value="" style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;">
|
||||
<label>Clé</label>
|
||||
</div>
|
||||
<input id="input_metadata_value" readonly="readonly" style="border-top: 0; border-top-right-radius: 0; border-top-left-radius: 0;" name="metadata_value" type="text" class="form-control bg-light opacity-50" value="" placeholder="Valeur" style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;">
|
||||
</div>
|
||||
<button type="submit" type="button" class="btn btn-outline-secondary float-end"><i class="bi bi-plus-circle"></i> Ajouter</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div-margin-bottom" style="height: 55px;" class="d-md-none"></div>
|
||||
<div style="width: 40%;" class="offcanvas offcanvas-end show d-none d-md-block shadow-sm" data-bs-backdrop="false" data-bs-scroll="true" data-bs-keyboard="false" tabindex="-1" id="sidebarTools" aria-labelledby="sidebarToolsLabel">
|
||||
<a class="btn btn-close btn-sm position-absolute opacity-25 d-none d-sm-none d-md-block" title="Fermer ce PDF et retourner à l'accueil" style="position: absolute; top: 2px; right: 2px; font-size: 10px;" href="/metadata"></a>
|
||||
<div class="offcanvas-header d-block mb-0 pb-0 border-bottom">
|
||||
<h5 class="mb-1 d-block w-100" id="sidebarToolsLabel">Édition des métadonnées<span class="float-end me-2"><i class="bi bi-tags"></i></span></h5>
|
||||
<button type="button" class="btn-close text-reset d-md-none" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
||||
<p id="text_document_name" class="text-muted mb-2" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden;" title=""><i class="bi bi-files"></i> <span></span></p>
|
||||
</div>
|
||||
<div class="offcanvas-body bg-light" style="padding-bottom: 60px;">
|
||||
<div id="container-pages">
|
||||
</div>
|
||||
</div>
|
||||
<div class="position-absolute bg-white bottom-0 pb-2 ps-2 pe-2 w-100 border-top shadow-lg">
|
||||
<div id="btn_container" class="d-grid gap-2 mt-2">
|
||||
<button class="btn btn-primary" type="submit" id="save"><i class="bi bi-download"></i> Enregistrer et télécharger le PDF</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="bottom_bar" class="position-fixed bottom-0 start-0 bg-white w-100 p-2 shadow-sm d-md-none">
|
||||
<div id="bottom_bar_action" class="d-grid gap-2">
|
||||
<button class="btn btn-primary" id="save_mobile"><i class="bi bi-download"></i> Télécharger le PDF</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span id="is_mobile" class="d-md-none"></span>
|
||||
|
||||
<script src="/vendor/bootstrap.min.js?5.1.3"></script>
|
||||
<script src="/vendor/pdf.js?legacy"></script>
|
||||
<script src="/vendor/pdf-lib.min.js?1.17.1"></script>
|
||||
<script>
|
||||
var defaultFields = <?php echo json_encode(isset($METADATA_DEFAULT_FIELDS) ? $METADATA_DEFAULT_FIELDS : array()); ?>;
|
||||
</script>
|
||||
<script src="/js/metadata.js?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/js/metadata.js") ?>"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<link href="/vendor/bootstrap.min.css?5.1.1" rel="stylesheet">
|
||||
<link href="/vendor/bootstrap-icons.css?1.5.0" rel="stylesheet">
|
||||
<link href="/css/app.css?202210080134" rel="stylesheet">
|
||||
<link href="/css/app.css?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/css/app.css") ?>" rel="stylesheet">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon-organization.ico">
|
||||
|
||||
<title>Organiser un PDF</title>
|
||||
|
@ -26,6 +26,9 @@
|
|||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/organization"><i class="bi bi-ui-checks-grid"></i> Organiser</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/metadata"><i class="bi bi-tags"></i> Metadonnées</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="px-4 py-4 text-center">
|
||||
<h1 class="display-5 fw-bold mb-0 mt-3"><i class="bi bi-ui-checks-grid"></i> Organiser des PDF</h1>
|
||||
|
@ -33,14 +36,16 @@
|
|||
<div class="col-md-6 col-lg-5 col-xl-4 col-xxl-3 mx-auto">
|
||||
<div class="col-12">
|
||||
<label class="form-label mt-3" for="input_pdf_upload">Choisir un PDF <small class="opacity-75" style="cursor: help" title="Le PDF ne doit pas dépasser <?php echo round($maxSize / 1024 / 1024) ?> Mo"><i class="bi bi-info-circle"></i></small></label>
|
||||
<input id="input_pdf_upload" placeholder="Choisir un PDF" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf" />
|
||||
<input id="input_pdf_upload" placeholder="Choisir un PDF" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf" multiple="true" />
|
||||
<p class="mt-2 small fw-light text-dark">Le PDF sera traité par le serveur sans être conservé ni stocké</p>
|
||||
<a class="btn btn-sm btn-link opacity-75" href="#https://raw.githubusercontent.com/24eme/signaturepdf/master/tests/files/document.pdf">Tester avec un PDF de démo</a>
|
||||
<?php if($PDF_DEMO_LINK): ?>
|
||||
<a class="btn btn-sm btn-link opacity-75" href="#<?php echo $PDF_DEMO_LINK ?>">Tester avec un PDF de démo</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="text-center text-muted mb-2 fixed-bottom opacity-75">
|
||||
<small>Logiciel libre <span class="d-none d-md-inline">sous license AGPL-3.0 </span>: <a href="https://github.com/24eme/signaturepdf">voir le code source</a></small>
|
||||
<small>Logiciel libre <span class="d-none d-md-inline">sous license AGPL-3.0</span> : <a href="https://github.com/24eme/signaturepdf">voir le code source </a><?php if($COMMIT): ?> <span class="d-none d-md-inline small">[<a href="https://github.com/24eme/signaturepdf/tree/<?php echo $COMMIT ?>"><?php echo $COMMIT ?></a>]</span><?php endif; ?></small>
|
||||
</footer>
|
||||
</div>
|
||||
<div id="page-organization" style="padding-right: 350px;" class="d-none">
|
||||
|
@ -77,7 +82,7 @@
|
|||
<button id="btn_rotate_select" disabled="disabled" type="button" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-clockwise"></i> Tourner de 90°</button>
|
||||
<button id="btn_drag_select" disabled="disabled" type="button" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrows-move"></i> Déplacer</button>
|
||||
<button id="btn_delete_select" disabled="disabled" type="button" class="btn btn-sm btn-outline-secondary"><i class="bi bi-trash"></i> Supprimer</button>
|
||||
<button id="save-select" class="btn btn-sm btn-outline-secondary" disabled="disabled" form="form_pdf" type="submit"><i class="bi bi-download"></i> Télécharger</button>
|
||||
<button id="save-select" class="btn btn-sm btn-outline-secondary" disabled="disabled" form="form_pdf" type="submit"><i class="bi bi-download"></i> Extraire et télécharger</button>
|
||||
</div>
|
||||
<div class="card-footer d-none small text-center p-1 border-primary bg-primary bg-opacity-25"><a id="btn_cancel_select_footer" type="button" aria-label="Close" style="text-decoration: none;" class="text-primary"><i class="bi bi-x-lg"></i> Annuler la sélection</a></div>
|
||||
</div>
|
||||
|
@ -86,7 +91,7 @@
|
|||
<form id="form_pdf" action="/organize" method="post" enctype="multipart/form-data">
|
||||
<input id="input_pdf" name="pdf[]" type="file" class="d-none" />
|
||||
<input id="input_pages" type="hidden" value="" name="pages" />
|
||||
<div class="d-grid gap-2 mt-2">
|
||||
<div id="btn_container" class="d-grid gap-2 mt-2">
|
||||
<button class="btn btn-primary" type="submit" id="save"><i class="bi bi-download"></i> Télécharger le PDF complet</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -152,6 +157,6 @@
|
|||
<script>
|
||||
var maxSize = <?php echo $maxSize ?>;
|
||||
</script>
|
||||
<script src="/js/organization.js?2022101800002"></script>
|
||||
<script src="/js/organization.js?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/js/organization.js") ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<meta name="description" content="Logiciel libre de signature de PDF en ligne">
|
||||
<link href="/vendor/bootstrap.min.css?5.1.1" rel="stylesheet">
|
||||
<link href="/vendor/bootstrap-icons.css?1.8.1" rel="stylesheet">
|
||||
<link href="/css/app.css" rel="stylesheet">
|
||||
<link href="/css/app.css?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/css/app.css") ?>" rel="stylesheet">
|
||||
<title>Signature PDF</title>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -17,6 +17,7 @@
|
|||
</div>
|
||||
</noscript>
|
||||
<div id="page-upload">
|
||||
<?php if(!$disableOrganization): ?>
|
||||
<ul class="nav justify-content-center nav-tabs mt-2">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/signature"><i class="bi bi-vector-pen"></i> Signer</a>
|
||||
|
@ -24,7 +25,11 @@
|
|||
<li class="nav-item">
|
||||
<a class="nav-link" href="/organization"><i class="bi bi-ui-checks-grid"></i> Organiser</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/metadata"><i class="bi bi-tags"></i> Metadonnées</a>
|
||||
</li>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
<div class="px-4 py-4 text-center">
|
||||
<h1 class="display-5 fw-bold mb-0 mt-3"><i class="bi bi-vector-pen"></i> Signer un PDF</h1>
|
||||
<p class="fw-light mb-3 subtitle text-dark text-nowrap" style="overflow: hidden; text-overflow: ellipsis;">Signer, parapher, tamponner, compléter un document</p>
|
||||
|
@ -33,15 +38,27 @@
|
|||
<label class="form-label mt-3" for="input_pdf_upload">Choisir un PDF <small class="opacity-75" style="cursor: help" title="Le PDF ne doit pas dépasser <?php echo round($maxSize / 1024 / 1024) ?> Mo et <?php echo $maxPage ?> pages"><i class="bi bi-info-circle"></i></small></label>
|
||||
<input id="input_pdf_upload" placeholder="Choisir un PDF" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf" />
|
||||
<p class="mt-2 small fw-light text-dark">Le PDF sera traité par le serveur sans être conservé ni stocké</p>
|
||||
<a class="btn btn-sm btn-link opacity-75" href="#https://raw.githubusercontent.com/24eme/signaturepdf/master/tests/files/document.pdf">Tester avec un PDF de démo</a>
|
||||
<?php if($PDF_DEMO_LINK): ?>
|
||||
<a class="btn btn-sm btn-link opacity-75" href="#<?php echo $PDF_DEMO_LINK ?>">Tester avec un PDF de démo</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="text-center text-muted mb-2 fixed-bottom opacity-75">
|
||||
<small>Logiciel libre <span class="d-none d-md-inline">sous license AGPL-3.0 </span>: <a href="https://github.com/24eme/signaturepdf">voir le code source</a></small>
|
||||
<small>Logiciel libre <span class="d-none d-md-inline">sous license AGPL-3.0</span> : <a href="https://github.com/24eme/signaturepdf">voir le code source </a><?php if($COMMIT): ?> <span class="d-none d-md-inline small">[<a href="https://github.com/24eme/signaturepdf/tree/<?php echo $COMMIT ?>"><?php echo $COMMIT ?></a>]</span><?php endif; ?></small>
|
||||
</footer>
|
||||
</div>
|
||||
<div id="page-signature" style="padding-right: 350px;" class="d-none">
|
||||
<?php if(isset($hash)): ?>
|
||||
<div id="alert-signature-help" class="position-relative d-none">
|
||||
<div class="alert alert-primary alert-dismissible position-absolute top-0 start-50 translate-middle-x text-center mt-4 pb-2 w-50 opacity-100" style="z-index: 100;" role="alert">
|
||||
<h5 class="alert-heading">Comment signer ?</h5>
|
||||
<strong>En cliquant directement sur la page du document</strong> pour insérer l'élément séléctionné dans la colonne de droite <small>(signature, paraphe, texte, tampon, etc ...)</small>
|
||||
<div class="mt-1 fs-3"><i class="bi bi-box-arrow-down"></i></div>
|
||||
<button type="button" class="btn-close btn-sm" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div style="height: 65px;" class="d-md-none"></div>
|
||||
<div id="container-pages" class="col-12 pt-1 pb-1 text-center vh-100">
|
||||
</div>
|
||||
|
@ -97,8 +114,10 @@
|
|||
<button class="btn btn-primary w-100 mt-2" disabled="disabled" type="submit" id="save"><i class="bi bi-download"></i> Télécharger le PDF signé</button>
|
||||
</form>
|
||||
<?php elseif(!isset($noSharingMode)): ?>
|
||||
<div class="d-none d-sm-none d-md-block">
|
||||
<p id="nblayers_text" class="small d-none mb-2 opacity-75">Vous êtes <span class="badge rounded-pill border border-dark text-dark"><span class="nblayers">0</span> <i class="bi bi-people-fill"></i></span> à avoir signé ce PDF</p></div>
|
||||
<div class="d-none d-sm-none d-md-block position-relative">
|
||||
<a id="btn-signature-help" class="position-absolute top-0 end-0 text-dark" href="" style="z-index: 5;"><i class="bi bi-question-circle"></i></a>
|
||||
<p id="nblayers_text" class="small d-none mb-2 opacity-75">Vous êtes <span class="badge rounded-pill border border-dark text-dark"><span class="nblayers">0</span> <i class="bi bi-people-fill"></i></span> à avoir signé ce PDF</p>
|
||||
</div>
|
||||
<div class="btn-group w-100">
|
||||
<a id="btn_download" class="btn btn-outline-dark w-100" href="/signature/<?php echo $hash ?>/pdf"><i class="bi bi-download"></i> Télécharger le PDF</a>
|
||||
<button class="btn btn-outline-dark" type="button" id="btn_share" data-bs-toggle="modal" data-bs-target="#modal-share-informations"><i class="bi bi-share"></i></button>
|
||||
|
@ -258,6 +277,6 @@
|
|||
hash = "<?php echo $hash ?>";
|
||||
<?php endif; ?>
|
||||
</script>
|
||||
<script src="/js/signature.js?202204270035"></script>
|
||||
<script src="/js/signature.js?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/js/signature.js") ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in New Issue