Textfeld für Unterschriftoverlay und gitignore
This commit is contained in:
126
.gitignore
vendored
Normal file
126
.gitignore
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
*~
|
||||
|
||||
# IDE files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*.swn
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
*.tmp
|
||||
@@ -312,6 +312,21 @@ body {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.signature-overlay .text-overlay-content {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
max-width: calc(100% - 20px);
|
||||
word-wrap: break-word;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.signature-overlay .handle {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
@@ -339,6 +354,14 @@ body {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.text-input-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background: #f8f9ff;
|
||||
border-radius: 12px;
|
||||
border: 2px solid #667eea;
|
||||
}
|
||||
|
||||
.signature-controls {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
|
||||
@@ -14,7 +14,13 @@ services:
|
||||
networks:
|
||||
- pdf-signature-network
|
||||
healthcheck:
|
||||
test: ["CMD", "node", "-e", "require('http').get('http://localhost:8080', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"node",
|
||||
"-e",
|
||||
"require('http').get('http://pdf-signature:8080', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
|
||||
@@ -39,6 +39,14 @@
|
||||
Warte auf Unterschrift von der Signatur-Station...
|
||||
</div>
|
||||
|
||||
<div class="text-input-section" id="textInputSection" style="display: none;">
|
||||
<label for="textInput" style="display: block; margin-bottom: 10px; font-weight: 500; color: #667eea;">
|
||||
📝 Text für Unterschrifts-Overlay (wird oben links angezeigt):
|
||||
</label>
|
||||
<input type="text" id="textInput"
|
||||
style="width: 100%; padding: 12px; border: 2px solid #667eea; border-radius: 8px; font-size: 1em;">
|
||||
</div>
|
||||
|
||||
<!-- Page Navigation -->
|
||||
<div class="page-navigation" id="pageNavigation" style="display: none;">
|
||||
<button class="nav-button" id="prevPageBtn" onclick="changePage(-1)">◀ Zurück</button>
|
||||
@@ -64,6 +72,7 @@
|
||||
</div>
|
||||
<div class="signature-overlay" id="signatureOverlay">
|
||||
<div class="handle" onclick="removeSignatureOverlay()" title="Unterschrift entfernen">×</div>
|
||||
<div class="text-overlay-content" id="textOverlayContent" style="display: none;"></div>
|
||||
<img id="signatureImage" src="" alt="Unterschrift">
|
||||
<div class="resize-handle" id="resizeHandle"></div>
|
||||
</div>
|
||||
@@ -91,7 +100,7 @@
|
||||
🗑️ Unterschrift entfernen
|
||||
</button>
|
||||
<button class="upload-button" id="placeSignatureButton" style="flex: 2; background: linear-gradient(135deg, #28a745 0%, #20c997 100%);">
|
||||
✓ Unterschrift platzieren & PDF erstellen
|
||||
✓ Unterschrift & Text platzieren & PDF erstellen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
104
js/master.js
104
js/master.js
@@ -16,6 +16,10 @@ let signatureDataUrl = null;
|
||||
let signaturePos = { x: 0.5, y: 0.1 }; // Relative position (0-1)
|
||||
let signatureScale = 0.3;
|
||||
|
||||
// Text overlay state (now part of signature overlay)
|
||||
let textOverlayText = '';
|
||||
let textFontSize = 14;
|
||||
|
||||
// WebSocket connection
|
||||
let ws = null;
|
||||
|
||||
@@ -114,6 +118,10 @@ document.getElementById('pdfInput').addEventListener('change', async (e) => {
|
||||
document.getElementById('uploadSection').style.display = 'none';
|
||||
document.getElementById('statusSection').style.display = 'block';
|
||||
|
||||
// Show text input section and update with current date
|
||||
document.getElementById('textInputSection').style.display = 'block';
|
||||
updateTextInputWithDate();
|
||||
|
||||
// Show signature placeholder
|
||||
document.getElementById('signaturePlaceholder').style.display = 'block';
|
||||
|
||||
@@ -298,6 +306,9 @@ async function renderPdfPreview(pdfBytes, pageNum = 1) {
|
||||
placeholder.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Update text in overlay if it exists
|
||||
updateTextInOverlay();
|
||||
}, 300); // Increased delay to ensure canvas is ready
|
||||
|
||||
console.log('PDF Seite', pageNum, 'erfolgreich gerendert');
|
||||
@@ -504,6 +515,7 @@ async function changePage(direction) {
|
||||
console.log('Overlay nach Seitenwechsel positioniert:', overlay.style.left, overlay.style.top);
|
||||
}
|
||||
}
|
||||
|
||||
}, 100);
|
||||
|
||||
// Send page change to signature station
|
||||
@@ -567,6 +579,9 @@ async function showSignatureOverlay(signatureDataUrl) {
|
||||
overlay.style.position = 'absolute'; // Ensure positioning works
|
||||
overlay.style.zIndex = '10'; // Ensure it's on top
|
||||
|
||||
// Update text in overlay if text input has value
|
||||
updateTextInOverlay();
|
||||
|
||||
document.getElementById('signatureControls').classList.add('show');
|
||||
|
||||
// Show page selector if multi-page
|
||||
@@ -779,7 +794,7 @@ async function addSignatureToPdf() {
|
||||
// Embed signature image once
|
||||
const signatureImage = await pdfLibDoc.embedPng(signatureDataUrl);
|
||||
|
||||
// Add signature to each selected page
|
||||
// Add signature and text to each selected page
|
||||
for (const pageNum of pagesToSign) {
|
||||
const page = pages[pageNum - 1]; // 0-indexed
|
||||
const { width, height } = page.getSize();
|
||||
@@ -818,6 +833,28 @@ async function addSignatureToPdf() {
|
||||
});
|
||||
|
||||
console.log('✓ Unterschrift zu Seite', pageNum, 'hinzugefügt');
|
||||
|
||||
// Add text overlay if it exists (text is part of signature overlay, positioned top-left)
|
||||
if (textOverlayText) {
|
||||
// Text is positioned relative to signature overlay (top-left)
|
||||
// Calculate text position: overlay left position + small offset, overlay top position - small offset
|
||||
const textOffsetX = 5 * scaleX; // 5px offset from left
|
||||
const textOffsetY = -5 * scaleY; // 5px offset from top (negative because PDF y is bottom-up)
|
||||
const textX = x + textOffsetX;
|
||||
// overlayTop is from top of canvas, convert to PDF coordinates (bottom-up)
|
||||
const overlayTopInPdf = height - (overlayTop * scaleY);
|
||||
const textY = overlayTopInPdf + textOffsetY; // Top of overlay - small offset
|
||||
|
||||
// Add text to page
|
||||
page.drawText(textOverlayText, {
|
||||
x: textX,
|
||||
y: textY,
|
||||
size: textFontSize * scaleY,
|
||||
color: rgb(0, 0, 0),
|
||||
});
|
||||
|
||||
console.log('✓ Text zu Seite', pageNum, 'hinzugefügt');
|
||||
}
|
||||
}
|
||||
|
||||
// Save PDF
|
||||
@@ -846,6 +883,38 @@ async function addSignatureToPdf() {
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentDateString() {
|
||||
const today = new Date();
|
||||
const day = String(today.getDate()).padStart(2, '0');
|
||||
const month = String(today.getMonth() + 1).padStart(2, '0');
|
||||
const year = today.getFullYear();
|
||||
return `${day}.${month}.${year}`;
|
||||
}
|
||||
|
||||
function updateTextInputWithDate() {
|
||||
const textInput = document.getElementById('textInput');
|
||||
if (textInput) {
|
||||
const currentDate = getCurrentDateString();
|
||||
textInput.value = `Abgeholt am ${currentDate} durch:`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateTextInOverlay() {
|
||||
const textInput = document.getElementById('textInput');
|
||||
const text = textInput ? textInput.value : '';
|
||||
textOverlayText = text;
|
||||
|
||||
const textOverlayContent = document.getElementById('textOverlayContent');
|
||||
if (textOverlayContent) {
|
||||
if (text && text.trim()) {
|
||||
textOverlayContent.textContent = text;
|
||||
textOverlayContent.style.display = 'block';
|
||||
} else {
|
||||
textOverlayContent.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeSignatureOverlay() {
|
||||
// Only remove the overlay, keep signedPdfBytes intact
|
||||
const overlay = document.getElementById('signatureOverlay');
|
||||
@@ -853,6 +922,12 @@ function removeSignatureOverlay() {
|
||||
overlay.style.display = 'none'; // Explicitly hide overlay
|
||||
document.getElementById('signatureControls').classList.remove('show');
|
||||
|
||||
// Hide text in overlay
|
||||
const textOverlayContent = document.getElementById('textOverlayContent');
|
||||
if (textOverlayContent) {
|
||||
textOverlayContent.style.display = 'none';
|
||||
}
|
||||
|
||||
// Reset placeholder to default state
|
||||
const placeholder = document.getElementById('signaturePlaceholder');
|
||||
const placeholderImg = document.getElementById('placeholderSignatureImage');
|
||||
@@ -1003,9 +1078,15 @@ document.getElementById('downloadButton').addEventListener('click', async () =>
|
||||
// Reset for next PDF
|
||||
document.getElementById('uploadSection').style.display = 'block';
|
||||
document.getElementById('statusSection').style.display = 'none';
|
||||
|
||||
// Hide text input section
|
||||
document.getElementById('textInputSection').style.display = 'none';
|
||||
|
||||
// Reset file input
|
||||
document.getElementById('pdfInput').value = '';
|
||||
|
||||
// Reset text input to default with current date
|
||||
updateTextInputWithDate();
|
||||
|
||||
// Reset place signature button
|
||||
const placeBtn = document.getElementById('placeSignatureButton');
|
||||
@@ -1029,6 +1110,7 @@ document.getElementById('downloadButton').addEventListener('click', async () =>
|
||||
pdfFileName = '';
|
||||
pdfDoc = null;
|
||||
currentPage = null;
|
||||
textOverlayText = '';
|
||||
|
||||
console.log('Alles zurückgesetzt für nächste PDF');
|
||||
} catch (error) {
|
||||
@@ -1043,9 +1125,15 @@ function resetToStart() {
|
||||
// Show upload section, hide status section
|
||||
document.getElementById('uploadSection').style.display = 'block';
|
||||
document.getElementById('statusSection').style.display = 'none';
|
||||
|
||||
// Hide text input section
|
||||
document.getElementById('textInputSection').style.display = 'none';
|
||||
|
||||
// Reset file input
|
||||
document.getElementById('pdfInput').value = '';
|
||||
|
||||
// Reset text input to default with current date
|
||||
updateTextInputWithDate();
|
||||
|
||||
// Reset place signature button
|
||||
const placeBtn = document.getElementById('placeSignatureButton');
|
||||
@@ -1079,6 +1167,10 @@ function resetToStart() {
|
||||
pdfDoc = null;
|
||||
currentPage = null;
|
||||
signatureDataUrl = null;
|
||||
textOverlayText = '';
|
||||
|
||||
// Reset text input to default with current date
|
||||
updateTextInputWithDate();
|
||||
|
||||
console.log('Zurück zum Anfang - bereit für neue PDF');
|
||||
}
|
||||
@@ -1112,5 +1204,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Update text in overlay when text input changes
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const textInput = document.getElementById('textInput');
|
||||
if (textInput) {
|
||||
textInput.addEventListener('input', () => {
|
||||
updateTextInOverlay();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Connect WebSocket on load
|
||||
connectWebSocket();
|
||||
|
||||
Reference in New Issue
Block a user