1
0
Fork 0
mirror of https://github.com/24eme/signaturepdf synced 2026-03-14 13:55:44 +01:00

Merge remote-tracking branch 'origin/master'

This commit is contained in:
Hosted Weblate 2025-07-01 14:26:44 +02:00
commit a1de37f95a
No known key found for this signature in database
GPG key ID: A3FAAA06E6569B4C
5 changed files with 118 additions and 64 deletions

View file

@ -39,6 +39,7 @@ Open-source software under the AGPL V3 license.
- [Disabling the Organize Mode](#disabling-the-organize-mode)
- [Hiding or Modifying the Demo PDF Link](#hiding-or-modifying-the-demo-pdf-link)
- [Customize the CSS](#customize-the-css)
- [Custom retention period for shared PDF](#custom-retention-period-for-shared-pdf)
- [Default Fields for Metadata Editing](#default-fields-for-metadata-editing)
- [Update](#update)
- [Tests](#tests)
@ -141,6 +142,29 @@ It's possible to add a custom CSS file to the location `public/css/app-specific.
If this file exists, it will be loaded automatically.
### Custom retention period for shared PDF
In `config/config.ini` file activate these options :
```
[signature]
; Enable custom retention period for shared PDF
; This override the default retention periods
; Warning: Text on the right of the colon will be translated according to the .po files
;retention[+1 year]="for one year"
;retention[+6 months]="for six months"
;retention[+1 month]="for one month"
;retention[+1 week]="for one week"
;retention[+1 day]="for one day"
;retention[+1 hour]="for one hour"
retention[+10 days]="for ten days"
```
This configuration allows the retention of the PDF for only 10 days
### Default Fields for Metadata Editing
In the `config/config.ini` file, you can add as many fields as you want with the HTML input type (text, date, number, email, etc.) that will be preloaded for each PDF.

View file

@ -66,6 +66,14 @@
left: 6px;
}
#color-picker {
display: block; position: fixed; height:28px; width: 28px; border-radius: 24px; background: #000; top: 14px; right: 366px; border: 2px solid #fff; color: #fff; font-size: 10px; text-align: center; line-height: 20px; opacity: 0.25;
}
#color-picker:hover {
opacity: 1;
}
.canvas-container .btn-drag, .canvas-container .btn-rotate, .canvas-container .btn-delete, .canvas-container .btn-select, .canvas-container .btn-download, .canvas-container .btn-restore, .canvas-container .btn-drag-here, .canvas-container .btn-drag-here_mobile, .canvas-container .btn-cancel {
font-size: 30px;
cursor: move;

View file

@ -22,50 +22,49 @@ function responsiveDisplay() {
async function loadPDF(pdfBlob) {
showLoading('Loading')
let filename = pdfBlob.name;
let url = await URL.createObjectURL(pdfBlob);
const filename = pdfBlob.name;
const url = URL.createObjectURL(pdfBlob);
document.title = filename + ' - ' + document.title;
document.querySelector('#text_document_name span').innerText = filename;
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) {
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);
}
const loadingTask = pdfjsLib.getDocument(url);
const pdf = await loadingTask.promise;
const metadata = await pdf.getMetadata()
for(metaKey in metadata.info.Custom) {
if(metaKey == "sha256") {
continue;
}
for(fieldKey in defaultFields) {
addMetadata(fieldKey, null, defaultFields[fieldKey]['type'], false);
}
addMetadata(metaKey, metadata.info.Custom[metaKey], "text", 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(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();
}
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);
});
}, function (reason) {
console.error(reason);
});
}
if(document.querySelector('.input-metadata input')) {
document.querySelector('.input-metadata input').focus();
} else {
document.getElementById('input_metadata_key').focus();
}
endLoading();
@ -319,7 +318,9 @@ async function pageMetadata(url) {
responsiveDisplay();
createEventsListener();
await convertInputFileImagesToPDF(document.getElementById('input_pdf_upload'));
loadPDF(document.getElementById('input_pdf_upload').files[0]);
await loadPDF(document.getElementById('input_pdf_upload').files[0]).catch(function (reason) {
console.error(reason);
});
};

View file

@ -21,6 +21,7 @@ let penColor = localStorage.getItem('penColor') ?? '#000000'
let nblayers = null;
let hasModifications = false;
let currentTextScale = 1;
const defaultScale = 1.5;
async function loadPDF(pdfBlob) {
let filename = pdfBlob.name;
@ -549,8 +550,20 @@ function createAndAddSvgInCanvas(canvas, item, x, y, height = null) {
fill: penColor
});
fabric.Textbox.prototype.customEnterAction = function (e) {
if (e.ctrlKey) {
this.insertChars('\n', undefined, this.selectionStart)
this.exitEditing();
this.enterEditing();
this.moveCursorRight(e);
return
}
this.exitEditing()
}
addObjectInCanvas(canvas, textbox).setActiveObject(textbox);
textbox.keysMap[13] = "exitEditing";
textbox.keysMap[13] = "customEnterAction";
textbox.lockScalingFlip = true;
textbox.scaleX = currentTextScale;
textbox.scaleY = currentTextScale;
@ -617,7 +630,6 @@ function createAndAddSvgInCanvas(canvas, item, x, y, height = null) {
function autoZoom() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(resizePDF, 100);
updateWatermark();
};
function zoomChange(inOrOut) {
@ -843,28 +855,15 @@ function createEventsListener() {
document.querySelector('input[name=watermark]')?.addEventListener('keyup', debounce(function (e) {
setIsChanged(hasModifications || !!e.target.value)
updateFlatten();
// Pourquoi 27 : 40 / 1.5 = 26.6666
// fontSize ^ ^ currentScale par défaut
// Comme ça le texte de l'overlay ne bouge pas au zoom
const text = new fabric.Text(e.target.value, {angle: -40, fill: "#0009", fontSize: 27 * currentScale})
const overlay = new fabric.Rect({
fill: new fabric.Pattern({
source: text.toCanvasElement(),
}),
})
canvasEditions.forEach(function (canvas) {
overlay.height = canvas.height
overlay.width = canvas.width
canvas.objectCaching = false
canvas.setOverlayImage(overlay, canvas.renderAll.bind(canvas), {
objectCaching: false
})
})
updateWatermark();
}, 750))
document.querySelector('input[name=watermark]')?.addEventListener('change', function (e) {
setIsChanged(hasModifications || !!e.target.value)
updateFlatten();
updateWatermark();
});
if(document.querySelector('#alert-signature-help')) {
document.getElementById('btn-signature-help').addEventListener('click', function(event) {
document.querySelector('#alert-signature-help').classList.remove('d-none');
@ -878,6 +877,11 @@ function createEventsListener() {
if(document.getElementById('save')) {
document.getElementById('save').addEventListener('click', function(event) {
let previousScale = currentScale;
if(currentScale != defaultScale) {
resizePDF(defaultScale)
while(!renderComplete) { }
}
let dataTransfer = new DataTransfer();
canvasEditions.forEach(function(canvasEdition, index) {
dataTransfer.items.add(new File([canvasEdition.toSVG()], index+'.svg', {
@ -885,6 +889,11 @@ function createEventsListener() {
}));
})
document.getElementById('input_svg').files = dataTransfer.files;
if(previousScale != currentScale) {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(resizePDF(previousScale), 100);
}
hasModifications = false;
});
}
@ -1032,6 +1041,10 @@ function createEventsListener() {
storePenColor(penColorPicker.value)
})
document.getElementById('color-picker').addEventListener('click', function(event) {
penColorPicker.click();
})
window.addEventListener('beforeunload', function(event) {
if(!hasModifications) {
return;
@ -1225,11 +1238,18 @@ function storePenColor(color) {
penColor = color
penColorPicker.value = color
localStorage.setItem('penColor', penColor)
document.getElementById('color-picker').style.backgroundColor = penColor;
if(penColor != "#000000") {
document.getElementById('color-picker').style.opacity = 1;
} else {
document.getElementById('color-picker').style.opacity = 0.25;
}
}
const toolBox = (function () {
const _coloricon = document.createElement('img')
_coloricon.src = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-palette" viewBox="0 0 16 16"><path d="M8 5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3m4 3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3M5.5 7a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m.5 6a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3"/><path d="M16 8c0 3.15-1.866 2.585-3.567 2.07C11.42 9.763 10.465 9.473 10 10c-.603.683-.475 1.819-.351 2.92C9.826 14.495 9.996 16 8 16a8 8 0 1 1 8-8m-8 7c.611 0 .654-.171.655-.176.078-.146.124-.464.07-1.119-.014-.168-.037-.37-.061-.591-.052-.464-.112-1.005-.118-1.462-.01-.707.083-1.61.704-2.314.369-.417.845-.578 1.272-.618.404-.038.812.026 1.16.104.343.077.702.186 1.025.284l.028.008c.346.105.658.199.953.266.653.148.904.083.991.024C14.717 9.38 15 9.161 15 8a7 7 0 1 0-7 7"/></svg>'
_coloricon.src = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-droplet-fill" viewBox="0 0 16 16"><path d="M8 16a6 6 0 0 0 6-6c0-1.655-1.122-2.904-2.432-4.362C10.254 4.176 8.75 2.503 8 0c0 0-6 5.686-6 10a6 6 0 0 0 6 6M6.646 4.646l.708.708c-.29.29-1.128 1.311-1.907 2.87l-.894-.448c.82-1.641 1.717-2.753 2.093-3.13"/></svg>'
function _renderIcon(icon) {
return function renderIcon(ctx, left, top, styleOverride, fabricObject) {

View file

@ -131,6 +131,7 @@
</div>
</div>
</div>
<button id="color-picker" class="shadow-sm" title="Changer la couleur de l'encre"><i class="bi bi-droplet-fill"></i></button>
<div class="position-fixed top-0 start-0 bg-white w-100 p-2 shadow-sm d-md-none">
<div class="d-grid gap-2">
<button id="btn_svn_select" class="btn btn-light btn-lg" data-bs-toggle="offcanvas" data-bs-target="#sidebarTools" aria-controls="sidebarTools"><?php echo sprintf(_("%s Select a signature"), '<i class="bi bi-hand-index"></i>'); ?></button>