feat(#3453): Add frontend for Remove Header/Footer feature

- Implement form submission with options to remove header, footer, or both
- Add margin mode selection with visual preview of selected header/footer bounds
- Integrate PDF.js for live PDF view in margin mode
- Create dedicated CSS for styling the feature page
- Add localization keys for en-GB and en-US
- Implement JavaScript to handle PDF rendering, preview overlays, and submission
logic
This commit is contained in:
André Antunes Santos 2025-06-02 17:43:33 +01:00
parent b5bdf5b1c2
commit 61729d324c
6 changed files with 1044 additions and 45 deletions

View File

@ -1004,6 +1004,23 @@ crop.submit=Submit
remove-header-footer.title=Remove Header/Footer
remove-header-footer.header=Remove Header/Footer
remove-header-footer.submit=Remove
remove-header-footer.removeHeader=Header
remove-header-footer.removeFooter=Footer
remove-header-footer.headerMargin=Header Margin
remove-header-footer.footerMargin=Footer Margin
remove-header-footer.pages=Select Pages
remove-header-footer.selectMode=Select Mode
remove-header-footer.view-more=View More
remove-header-footer.previousPage=Previous Page
remove-header-footer.nextPage=Next Page
remove-header-footer.zoomOut=Zoom Out
remove-header-footer.zoomIn=Zoom In
remove-header-footer.close=Close
remove-header-footer.margin=Margin
remove-header-footer.auto=Auto
remove-header-footer.manual=Manual
remove-header-footer.enterValue=Enter Value
#autoSplitPDF
autoSplitPDF.title=Auto Split PDF

View File

@ -751,6 +751,10 @@ home.validateSignature.title=Validate PDF Signature
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
home.remove-header-footer.title=Remove Header/Footer
home.remove-header-footer.desc=Remove the headers and/or footers from a PDF document
remove-header-footer.tags=remove headers, remove footers, remove, header, footer
#replace-invert-color
replace-color.title=Replace-Invert-Color
replace-color.header=Replace-Invert Color PDF
@ -996,6 +1000,26 @@ crop.title=Crop
crop.header=Crop PDF
crop.submit=Submit
#remove-header-footer
remove-header-footer.title=Remove Header/Footer
remove-header-footer.header=Remove Header/Footer
remove-header-footer.submit=Remove
remove-header-footer.removeHeader=Header
remove-header-footer.removeFooter=Footer
remove-header-footer.headerMargin=Header Margin
remove-header-footer.footerMargin=Footer Margin
remove-header-footer.pages=Select Pages
remove-header-footer.selectMode=Select Mode
remove-header-footer.view-more=View More
remove-header-footer.previousPage=Previous Page
remove-header-footer.nextPage=Next Page
remove-header-footer.zoomOut=Zoom Out
remove-header-footer.zoomIn=Zoom In
remove-header-footer.close=Close
remove-header-footer.margin=Margin
remove-header-footer.auto=Auto
remove-header-footer.manual=Manual
remove-header-footer.enterValue=Enter Value
#autoSplitPDF
autoSplitPDF.title=Auto Split PDF

View File

@ -0,0 +1,157 @@
#previewContainer {
aspect-ratio: 1;
width: 100%;
border: 1px solid rgba(0, 0, 0, 0.125);
border-radius: 0.25rem;
margin: 1rem 0;
padding: 15px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
#pdf-preview {
max-width: calc(100% - 30px);
max-height: calc(100% - 30px);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.buttonContainer {
display: flex;
justify-content: space-around;
}
.preview-wrapper {
position: absolute;
top: 35%;
left: 35%;
transform: translate(-25%, -33%);
}
.margin-line {
position: absolute;
height: 0;
border-top: 2px dashed red;
z-index: 10;
pointer-events: none;
}
/* Fullscreen overlay */
#pdfOverlay {
display: none; /* Make it hidden by default */
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(30, 30, 30, 0.514);
display: none;
z-index: 1000;
overflow-y: auto;
flex-direction: column;
padding: 60px 0px 0px;
}
#mainContainer {
display: flex;
flex-direction: column;
height:100%;
width: 100%;
overflow: hidden;
}
.toolbar {
height: 4rem;
flex-shrink: 0;
z-index: 100;
}
.toolbarContainer {
height: 4rem !important;
}
.overlay-content {
flex-grow: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
position: relative;
}
.pagesView {
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
position: relative;
}
.pagesView canvas {
margin-bottom: 24px;
}
#zoomButton {
position: absolute;
top: 10px;
right: 10px;
color: white;
background: rgba(0, 0, 0, 0);
border: 1px solid #ffffff00;
border-radius: 50%;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
cursor: pointer;
z-index: 20;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.253);
transition: background 0.2s;
}
#zoomButton:hover {
background: rgba(0, 29, 41, 0.9);
}
button.close-button {
padding: 0;
border: none;
background: rgb(169 201 246);
width: 3rem;
height: 3rem;
border-radius: 15px;
margin-top: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.15s ease-in-out;
}
button.close-button .material-symbols-rounded {
font-size: 2rem;
line-height: 1;
color: rgb(58 94 134);
}
button.close-button:hover {
background: rgb(102, 102, 103);
}
button.close-button:hover .material-symbols-rounded {
color: #fff;
}
@media (max-width: 1125px) {
#toolbarViewerRight {
display: flex !important;
}
}

View File

@ -0,0 +1,603 @@
import * as pdfjsLib from '../../pdfjs-legacy/pdf.mjs';
import {PDFViewerApplication} from '../../pdfjs-legacy/js/viewer.mjs';
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
let loadedPdf = null;
let pdfFileUrl = null;
let pdfBlobUrl = null;
document.getElementById('removeHeaderFooterForm').addEventListener('submit', async function (event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
const responseContainer = document.getElementById('responseContainer');
try {
const response = await fetch(form.action, {
method: form.method,
body: formData,
});
const responseText = await response.text();
responseContainer.textContent = responseText;
responseContainer.className = 'alert alert-success';
} catch (error) {
responseContainer.textContent = 'An error occurred. Please try again.';
responseContainer.className = 'alert alert-danger';
}
});
document.addEventListener('DOMContentLoaded', () => {
PDFViewerApplication.run();
const fileInput = document.getElementById('fileInput-input');
const pagesInput = document.getElementById("pageNumbers");
const previewContainer = document.getElementById('previewContainer');
const modeSelect = document.querySelector('select[name="mode"]');
const overlayHeaderCheckbox = document.getElementById('overlay-removeHeader');
const overlayFooterCheckbox = document.getElementById('overlay-removeFooter');
const overlayHeaderMargin = document.getElementById('overlay-headerMargin');
const overlayFooterMargin = document.getElementById('overlay-footerMargin');
const customOverlayHeaderWrapper = document.getElementById("overlay-headerCustomMarginWrapper");
const customOverlayFooterWrapper = document.getElementById("overlay-footerCustomMarginWrapper");
const customOverlayHeaderInput = document.getElementById("overlay-headerCustomMarginInput");
const customOverlayFooterInput = document.getElementById("overlay-footerCustomMarginInput");
const marginOptions = document.getElementById("margin-options");
const headerMarginColumn = document.getElementById("header-margin-column");
const footerMarginColumn = document.getElementById("footer-margin-column");
const mainHeaderCheckbox = document.getElementById("removeHeader");
const mainFooterCheckbox = document.getElementById("removeFooter");
const mainHeaderMargin = document.querySelector('select[name="headerMargin"]');
const mainFooterMargin = document.querySelector('select[name="footerMargin"]');
const customMainHeaderWrapper = document.getElementById("headerCustomMarginWrapper");
const customMainFooterWrapper = document.getElementById("footerCustomMarginWrapper");
const customMainHeaderInput = document.getElementById("headerCustomMarginInput");
const customMainFooterInput = document.getElementById("footerCustomMarginInput");
const viewerContainer = document.getElementById('viewerContainer');
const viewer = document.getElementById('viewer');
const pageNumberInput = document.getElementById('pageNumber');
const numPagesLabel = document.getElementById('numPages');
const scaleSelect = document.getElementById('scaleSelect');
const zoomInBtn = document.getElementById('zoomIn');
const zoomOutBtn = document.getElementById('zoomOut');
const nextBtn = document.getElementById('next');
const prevBtn = document.getElementById('previous');
const closeBtn = document.getElementById('closeOverlay');
const zoomButton = document.getElementById('zoomButton');
const pageContainer = document.getElementById('page-container');
const outerContainer = document.getElementById('outerContainer');
let pdfDoc = null;
let currentPage = 1;
let currentScale = 1.0;
// Sync margin controls between overlay and main form
function syncMarginControls(fromOverlay) {
const copyState = (source, target) => {
target.headerCheckbox.checked = source.headerCheckbox.checked;
target.footerCheckbox.checked = source.footerCheckbox.checked;
target.headerMargin.value = source.headerMargin.value;
target.footerMargin.value = source.footerMargin.value;
target.headerMarginColumn.style.display = source.headerMarginColumn.style.display;
target.footerMarginColumn.style.display = source.footerMarginColumn.style.display;
target.customHeaderWrapper.style.display = source.customHeaderWrapper.style.display;
target.customHeaderInput.value = source.customHeaderInput.value;
target.customFooterWrapper.style.display = source.customFooterWrapper.style.display;
target.customFooterInput.value = source.customFooterInput.value;
};
const overlay = {
headerCheckbox: overlayHeaderCheckbox,
footerCheckbox: overlayFooterCheckbox,
headerMargin: overlayHeaderMargin,
footerMargin: overlayFooterMargin,
headerMarginColumn: overlayHeaderMargin,
footerMarginColumn: overlayFooterMargin,
customHeaderWrapper: customOverlayHeaderWrapper,
customHeaderInput: customOverlayHeaderInput,
customFooterWrapper: customOverlayFooterWrapper,
customFooterInput: customOverlayFooterInput
};
const main = {
headerCheckbox: mainHeaderCheckbox,
footerCheckbox: mainFooterCheckbox,
headerMargin: mainHeaderMargin,
footerMargin: mainFooterMargin,
headerMarginColumn: headerMarginColumn,
footerMarginColumn: footerMarginColumn,
customHeaderWrapper: customMainHeaderWrapper,
customHeaderInput: customMainHeaderInput,
customFooterWrapper: customMainFooterWrapper,
customFooterInput: customMainFooterInput
};
if (fromOverlay) {
copyState(overlay, main);
} else {
copyState(main, overlay);
}
}
function toggleOverlayMarginOptions() {
overlayHeaderMargin.style.display = overlayHeaderCheckbox.checked ? "block" : "none";
overlayFooterMargin.style.display = overlayFooterCheckbox.checked ? "block" : "none";
if(overlayHeaderMargin.value == "custom" && overlayHeaderCheckbox.checked){
customOverlayHeaderWrapper.style.display = "block";
}
else{
customOverlayHeaderWrapper.style.display = "none";
}
if(overlayFooterMargin.value == "custom" && overlayFooterCheckbox.checked){
customOverlayFooterWrapper.style.display = "block";
}
else{
customOverlayFooterWrapper.style.display = "none";
}
syncMarginControls(true);
}
function toggleMainMarginOptions() {
if (modeSelect.value === "margin") {
marginOptions.style.display = "flex";
headerMarginColumn.style.display = mainHeaderCheckbox.checked ? "block" : "none";
footerMarginColumn.style.display = mainFooterCheckbox.checked ? "block" : "none";
if(mainHeaderMargin.value == "custom"){
customMainHeaderWrapper.style.display = "block";
}
else{
customMainHeaderWrapper.style.display = "none";
}
if(mainFooterMargin.value == "custom"){
customMainFooterWrapper.style.display = "block";
}
else{
customMainFooterWrapper.style.display = "none";
}
if (loadedPdf) {
syncMarginControls(false);
}
} else {
marginOptions.style.display = "none";
}
}
// Initialize correctly on page load
toggleMainMarginOptions();
// Update when mode changes
modeSelect.addEventListener("change", toggleMainMarginOptions);
[
overlayHeaderCheckbox,
overlayFooterCheckbox,
overlayHeaderMargin,
overlayFooterMargin
].forEach(el => {
el.addEventListener('change', () => {
toggleOverlayMarginOptions();
renderAllPages();
});
});
fileInput.addEventListener("change", async function () {
const existingPreview = document.getElementById("pdf-preview");
if (existingPreview) existingPreview.remove();
const file = fileInput.files[0];
if (!file || file.type !== 'application/pdf') return;
if (pdfFileUrl) URL.revokeObjectURL(pdfFileUrl);
pdfFileUrl = URL.createObjectURL(file);
loadedPdf = await pdfjsLib.getDocument(pdfFileUrl).promise;
renderPreview();
});
pagesInput.addEventListener("input", () => {
if (modeSelect.value == "margin" && loadedPdf) {
renderPreview();
}
});
modeSelect.addEventListener("change", () => {
const preview = document.getElementById("pdf-preview");
if (modeSelect.value == "margin" && loadedPdf){
document.querySelector("#editSection").style.display = "block";
renderPreview();
}
else {
if (preview) preview.remove();
document.querySelector("#editSection").style.display = "none";
}
});
[
mainFooterCheckbox,
mainHeaderCheckbox,
mainFooterMargin,
mainHeaderMargin
].forEach(el => {
el.addEventListener("input", () => {
toggleMainMarginOptions();
if (modeSelect.value == "margin" && loadedPdf)
renderPreview();
});
});
[
customMainHeaderInput,
customMainFooterInput
].forEach(el => {
el.addEventListener("input", () => {
if (checkValue(el)) {
toggleMainMarginOptions();
renderPreview();
}
});
});
[
customOverlayHeaderInput,
customOverlayFooterInput
].forEach(el => {
el.addEventListener("input", () => {
if (checkValue(el)) {
toggleOverlayMarginOptions();
renderAllPages();
}
});
});
function checkValue(inputBlock) {
const value = parseInt(inputBlock.value, 10) || 0;
if (!isNaN(value) && value > 0) {
return true;
}
return false;
}
window.addEventListener("resize", () => {
if (modeSelect.value == "margin" && loadedPdf) {
renderPreview();
}
});
function drawMarginLine(ctx, y, width) {
if (y < 0 || y > ctx.canvas.height) return;
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(width, y);
ctx.stroke();
}
async function renderPreview() {
if (modeSelect.value != "margin" || !loadedPdf) {
if (preview) preview.remove();
document.querySelector("#editSection").style.display = "none";
return;
} else {
document.querySelector("#editSection").style.display = "block";
}
document.querySelectorAll(".margin-line").forEach(e => e.remove());
const mainHeaderMargin = document.querySelector('select[name="headerMargin"]').value === 'custom'
? parseInt(document.getElementById('headerCustomMarginInput').value, 10) || 0
: parseInt(document.querySelector('select[name="headerMargin"]').value, 10) || 0;
const mainFooterMargin = document.querySelector('select[name="footerMargin"]').value === 'custom'
? parseInt(document.getElementById('footerCustomMarginInput').value, 10) || 0
: parseInt(document.querySelector('select[name="footerMargin"]').value, 10) || 0;
const existingPreview = document.getElementById("pdf-preview");
if (existingPreview) existingPreview.remove();
const pageInput = pagesInput.value.trim();
let firstPageNumber = 1;
if (pageInput) {
const pages = pageInput.split(',').flatMap(part => {
if (part.includes('-')) {
const [start, end] = part.split('-').map(Number);
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
}
return [Number(part)];
}).filter(n => !isNaN(n) && n > 0);
if (pages.length > 0) {
firstPageNumber = pages[0];
}
}
const page = await loadedPdf.getPage(firstPageNumber);
const canvas = document.createElement("canvas");
if (page.rotate == 90 || page.rotate == 270) {
canvas.width = page.view[3];
canvas.height = page.view[2];
} else {
canvas.width = page.view[2];
canvas.height = page.view[3];
}
const ctx = canvas.getContext("2d");
const renderContext = {
canvasContext: ctx,
viewport: page.getViewport({ scale: 1 }),
};
await page.render(renderContext).promise;
const scale = canvas.height / page.view[3]; // PDF pts to pixels
const headerY = mainHeaderMargin * scale;
const footerY = canvas.height - mainFooterMargin * scale;
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
if (mainHeaderCheckbox.checked) {
drawMarginLine(ctx, headerY, canvas.width);
}
if (mainFooterCheckbox.checked) {
drawMarginLine(ctx, footerY, canvas.width);
}
const preview = document.createElement("img");
preview.id = "pdf-preview";
preview.alt = "preview";
preview.src = canvas.toDataURL();
preview.style.position = "absolute";
preview.style.top = "50%";
preview.style.left = "50%";
preview.style.transform = "translate(-50%, -50%)";
previewContainer.appendChild(preview);
URL.revokeObjectURL(pdfFileUrl);
};
function parsePagesInput(input, maxPage) {
if (!input) return Array.from({length: maxPage}, (_, i) => i + 1);
const parts = input.split(',');
const pages = new Set();
for (const part of parts) {
if (part.includes('-')) {
const [start, end] = part.split('-').map(Number);
if (!isNaN(start) && !isNaN(end) && start > 0 && end >= start && end <= maxPage) {
for (let i = start; i <= end; i++) pages.add(i);
}
} else {
const n = Number(part);
if (!isNaN(n) && n > 0 && n <= maxPage) pages.add(n);
}
}
return Array.from(pages).sort((a, b) => a - b);
}
function renderAllPages() {
viewer.innerHTML = '';
const headerMargin = document.querySelector('select[name="headerMargin"]').value === 'custom'
? parseInt(document.getElementById('headerCustomMarginInput').value, 10) || 0
: parseInt(document.querySelector('select[name="headerMargin"]').value, 10) || 0;
const footerMargin = document.querySelector('select[name="footerMargin"]').value === 'custom'
? parseInt(document.getElementById('footerCustomMarginInput').value, 10) || 0
: parseInt(document.querySelector('select[name="footerMargin"]').value, 10) || 0;
const removeHeaderChecked = document.getElementById('removeHeader').checked;
const removeFooterChecked = document.getElementById('removeFooter').checked;
const pageInput = document.getElementById('pageNumbers').value.trim();
const pagesToShow = parsePagesInput(pageInput, pdfDoc.numPages);
const renderPage = (num, idx) => {
pdfDoc.getPage(num).then(page => {
const viewport = page.getViewport({ scale: currentScale });
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
canvas.style.display = 'block';
canvas.style.margin = '0 auto 16px auto';
viewer.appendChild(canvas);
page.render({ canvasContext: ctx, viewport: viewport }).promise.then(() => {
if (idx === 0) {
pageNumberInput.value = pagesToShow[0];
numPagesLabel.textContent = `/ ${pagesToShow.length}`;
}
const scale = canvas.height / page.view[3];
const headerY = headerMargin * scale;
const footerY = canvas.height - footerMargin * scale;
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
if (removeHeaderChecked) {
drawMarginLine(ctx, headerY, canvas.width);
}
if (removeFooterChecked) {
drawMarginLine(ctx, footerY, canvas.width);
}
});
});
};
pagesToShow.forEach((pageNum, idx) => renderPage(pageNum, idx));
}
function scrollToPage(num) {
const canvases = viewer.querySelectorAll('canvas');
if (canvases[num - 1]) {
canvases[num - 1].scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
zoomButton.addEventListener('click', async () => {
if (!fileInput.files[0]) return;
if (pdfBlobUrl) URL.revokeObjectURL(pdfBlobUrl);
pdfBlobUrl = URL.createObjectURL(fileInput.files[0]);
syncMarginControls(false);
outerContainer.style.display = 'block';
pageContainer.style.display = 'none';
pdfjsLib.getDocument(pdfBlobUrl).promise.then(pdf => {
pdfDoc = pdf;
currentPage = 1;
currentScale = 1.0;
renderAllPages();
});
});
closeBtn.addEventListener('click', () => {
syncMarginControls(true);
outerContainer.style.display = 'none';
pageContainer.style.display = 'block';
viewer.innerHTML = '';
if (pdfBlobUrl) URL.revokeObjectURL(pdfBlobUrl);
pdfDoc = null;
renderPreview();
});
nextBtn.addEventListener('click', () => {
if (currentPage < pdfDoc.numPages) {
currentPage++;
pageNumberInput.value = currentPage;
scrollToPage(currentPage);
}
});
prevBtn.addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
pageNumberInput.value = currentPage;
scrollToPage(currentPage);
}
});
scaleSelect.addEventListener('change', async e => {
let val = e.target.value;
if (val === 'page-fit' || val === 'page-width' || val === 'page-actual') {
const scale = await getPageScale(val);
onZoomChange(scale);
scaleSelect.value = val;
} else if (val === 'auto') {
onZoomChange(1.25);
scaleSelect.value = val;
} else {
onZoomChange(Number(val));
}
});
pageNumberInput.addEventListener('change', e => {
let num = parseInt(e.target.value, 10);
if (!isNaN(num) && num >= 1 && num <= pdfDoc.numPages) {
currentPage = num;
scrollToPage(currentPage);
}
});
function setScaleSelectValue(scale) {
let found = false;
for (const opt of scaleSelect.options) {
if (Number(opt.value) === scale) {
scaleSelect.value = opt.value;
const customOpt = scaleSelect.querySelector('#customScaleOption');
if (customOpt) {
customOpt.hidden = true;
customOpt.disabled = true;
}
found = true;
break;
}
}
if (!found) {
let customOpt = scaleSelect.querySelector('#customScaleOption');
if (!customOpt) {
customOpt = document.createElement('option');
customOpt.id = 'customScaleOption';
scaleSelect.appendChild(customOpt);
}
customOpt.value = 'custom';
customOpt.textContent = `${Math.round(scale * 100)}%`;
customOpt.hidden = false;
customOpt.disabled = false;
scaleSelect.value = 'custom';
}
}
function onZoomChange(newScale) {
currentScale = newScale;
setScaleSelectValue(currentScale);
renderAllPages();
}
function getPageScale(mode) {
if (!pdfDoc) return 1.0;
const firstCanvas = viewer.querySelector('canvas');
if (!firstCanvas) return 1.0;
const container = viewerContainer;
const pageIndex = 1;
return pdfDoc.getPage(pageIndex).then(page => {
const viewport = page.getViewport({ scale: 1.0 });
if (mode === 'page-fit') {
// Fit page height to container height
return container.clientHeight / viewport.height;
} else if (mode === 'page-width') {
// Fit page width to container width
return container.clientWidth / viewport.width;
} else if (mode === 'page-actual') {
return 1.0;
}
return 1.0;
});
}
// Updates the page number with the visible page number during scroll
viewerContainer.addEventListener('scroll', () => {
const canvases = viewer.querySelectorAll('canvas');
let closest = 0;
let minDiff = Infinity;
const containerTop = viewerContainer.scrollTop;
for (let i = 0; i < canvases.length; i++) {
const rect = canvases[i].getBoundingClientRect();
const diff = Math.abs(rect.top - viewerContainer.getBoundingClientRect().top);
if (diff < minDiff) {
minDiff = diff;
closest = i;
}
}
if (pdfDoc && pageNumberInput.value != (closest + 1)) {
pageNumberInput.value = closest + 1;
currentPage = closest + 1;
}
});
zoomInBtn.addEventListener('click', () => {
onZoomChange(currentScale + 0.1);
});
zoomOutBtn.addEventListener('click', () => {
onZoomChange(currentScale - 0.1);
});
});

View File

@ -67,6 +67,7 @@
<link rel="stylesheet" th:href="@{'/css/licenses.css'}" th:if="${currentPage == 'licenses'}">
<link rel="stylesheet" th:href="@{'/css/multi-tool.css'}" th:if="${currentPage == 'multi-tool'}">
<link rel="stylesheet" th:href="@{'/css/rotate-pdf.css'}" th:if="${currentPage == 'rotate-pdf'}">
<link rel="stylesheet" th:href="@{'/css/remove-header-footer.css'}" th:if="${currentPage == 'remove-header-footer'}">
<link rel="stylesheet" th:href="@{'/css/stamp.css'}" th:if="${currentPage == 'stamp'}">
<link rel="stylesheet" th:href="@{'/css/fileSelect.css'}" th:if="${currentPage != 'home'}">
<link rel="stylesheet" th:href="@{'/css/footer.css'}">

View File

@ -1,14 +1,32 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{remove-header-footer.title}, header=#{remove-header-footer.header})}"></th:block>
<th:block th:insert="~{fragments/common :: head(title=#{remove-header-footer.title}, header=#{remove-header-footer.header})}">
</th:block>
<!-- Bootstrap -->
<script th:src="@{'/js/thirdParty/popper.min.js'}"></script>
<script th:src="@{'/js/thirdParty/bootstrap.min.js'}"></script>
<script th:src="@{'/js/thirdParty/jquery.min.js'}"></script>
<script th:src="@{'/js/thirdParty/jquery.validate.min.js'}"></script>
<link rel="stylesheet" th:href="@{'/css/theme/componentes.css'}">
<link rel="stylesheet" th:href="@{'/css/navbar.css'}">
<!-- This snippet is used in production (included from view-pdf.html) -->
<link rel="resource" type="application/l10n" th:href="@{'/pdfjs-legacy/locale/locale.json'}">
<script th:src="@{'/pdfjs-legacy/pdf.mjs'}" type="module"></script>
<link rel="stylesheet" th:href="@{'/pdfjs-legacy/css/viewer-redact.css'}">
<script th:src="@{'/pdfjs-legacy/js/viewer.mjs'}" type="module"></script>
<script type="module" th:src="@{'/js/pages/remove-header-footer.js'}"></script>
</head>
<body>
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<div id="page-container">
<br><br>
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6 bg-card">
@ -16,55 +34,234 @@
<span class="material-symbols-rounded tool-header-icon organize">toolbar</span>
<span class="tool-header-text" th:text="#{remove-header-footer.header}"></span>
</div>
<br><br>
<span>remove_header_footer test</span>
<form id="removeHeaderFooterForm" th:action="@{/api/v1/general/remove-header-footer}" method="post">
<div class="form-group">
<label for="msgInput">Input text</label>
<input type="text" id="msgInput" name="message" class="form-control" placeholder="Enter your message" required>
<form id="removeHeaderFooterForm" th:action="@{/api/v1/general/remove-header-footer}" method="post" enctype="multipart/form-data">
<div
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
</div>
<div class="mb-3">
<label for="pageNumbers" th:text="#{remove-header-footer.pages}"></label>
<input type="text" class="form-control" id="pageNumbers" name="pages" placeholder="1,3,5-10">
</div>
<div class="mb-4">
<label th:text="#{remove-header-footer.selectMode}"></label>
<select class="form-control" name="mode">
<option value="margin" th:text="#{remove-header-footer.margin}"></option>
<option value="auto" th:text="#{remove-header-footer.auto}"></option>
<option value="manual"th:text="#{remove-header-footer.manual}"></option>
</select>
</div>
<div class="d-flex">
<div class="form-check" style="flex: 0 0 50%;">
<input type="checkbox" id="removeHeader" name="removeHeader">
<label for="removeHeader" th:text="#{remove-header-footer.removeHeader}"></label>
</div>
<div class="form-check" style="display: flex; align-items: center;">
<input type="checkbox" id="removeFooter" name="removeFooter">
<label for="removeFooter" th:text="#{remove-header-footer.removeFooter}"></label>
</div>
</div>
<div id="margin-options" class="row mb-3" style="display: none;">
<div class="col" id="header-margin-column" style="display: none;">
<label th:text="#{remove-header-footer.headerMargin}"></label>
<select class="form-control" name="headerMargin">
<option value="custom">Custom</option>
<option value="20">20 pt</option>
<option value="30">30 pt</option>
<option value="40" selected>40 pt (default)</option>
<option value="50">50 pt</option>
<option value="60">60 pt</option>
</select>
<div class="custom-margin-wrapper" id="headerCustomMarginWrapper" style="display: none; margin-top: 8px;">
<input
type="number"
class="form-control custom-margin-input"
id="headerCustomMarginInput"
th:placeholder="#{remove-header-footer.enterValue}"
data-type="header"
min="0"
/>
</div>
</div>
<div class="col" id="footer-margin-column" style="display: none;">
<label th:text="#{remove-header-footer.footerMargin}"></label>
<select class="form-control" name="footerMargin">
<option value="custom">Custom</option>
<option value="20">20 pt</option>
<option value="30">30 pt</option>
<option value="40" selected>40 pt (default)</option>
<option value="50">50 pt</option>
<option value="60">60 pt</option>
</select>
<div class="custom-margin-wrapper" id="footerCustomMarginWrapper" style="display: none; margin-top: 8px;">
<input
type="number"
class="form-control custom-margin-input"
id="footerCustomMarginInput"
th:placeholder="#{remove-header-footer.enterValue}"
data-type="footer"
min="0"
/>
</div>
</div>
</div>
<div id="editSection" style="display: none">
<div id="previewContainer">
<!-- PDF preview will be rendered here -->
<button type="button" id="zoomButton" th:title="#{remove-header-footer.view-more}">
<span class="material-symbols-rounded">open_in_full</span>
</button>
</div>
</div>
<div class="buttonContainer">
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{remove-header-footer.submit}"></button>
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{remove-header-footer.submit}"></button>
</form>
<div id="responseContainer" class="mt-3"></div>
</div>
</div>
<script>
document.getElementById('removeHeaderFooterForm').addEventListener('submit', async function (event) {
event.preventDefault(); // Prevent the default form submission
const form = event.target;
const formData = new FormData(form);
const responseContainer = document.getElementById('responseContainer');
try {
// Send the form data to the server using fetch
const response = await fetch(form.action, {
method: form.method,
body: formData,
});
// Parse the response as text
const responseText = await response.text();
// Display the response in the response container
responseContainer.textContent = responseText;
responseContainer.className = 'alert alert-success'; // Add a success style
} catch (error) {
// Handle errors and display an error message
responseContainer.textContent = 'An error occurred. Please try again.';
responseContainer.className = 'alert alert-danger'; // Add an error style
}
});
</script>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<!--
<script type="module" th:src="@{'/js/pages/crop.js'}"></script>
-->
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
<!--PDF Viewer-->
<div id="outerContainer" style="display: none;">
<div id="sidebarContainer"></div>
<div id="mainContainer">
<div class="toolbar" style="position:sticky;">
<div id="toolbarContainer" style="height:4rem;" >
<div id="toolbarViewer">
<div id="toolbarViewerLeft">
<div class="splitToolbarButton hiddenSmallView">
<button class="toolbarButton btn-secondary" th:title="#{remove-header-footer.previousPage}" id="previous"
tabindex="13" data-l10n-id="pdfjs-previous-button">
</button>
<div class="splitToolbarButtonSeparator"></div>
<button class="toolbarButton btn-secondary" th:title="#{remove-header-footer.nextPage}" id="next" tabindex="14"
data-l10n-id="pdfjs-next-button">
</button>
</div>
<span class="loadingInput start">
<input type="number" id="pageNumber" class="toolbarField" value="1" min="1" tabindex="15"
data-l10n-id="pdfjs-page-input" autocomplete="off">
</span>
<span id="numPages" class="toolbarLabel"></span>
<img class="main-icon user-select-none" th:src="@{'/favicon.svg'}" alt="icon">
</div>
<div id="toolbarViewerMiddle" class="d-flex align-items-center justify-content-between w-100" style="max-width:900px; margin:0 auto;">
<div id="overlay-margin-options" class="d-flex align-items-center">
<div class="form-check" style="margin-bottom: 0px;" >
<input type="checkbox" id="overlay-removeHeader" style="margin-bottom: 4px; margin-left: 7px;" class="form-check-input">
<label for="overlay-removeHeader" class="form-check-label" style="padding-right: 2px;" th:text="#{remove-header-footer.removeHeader}"></label>
</div>
<span id="overlay-headerMargin-container">
<select class="form-select me-3" id="overlay-headerMargin" style="width:auto;">
<option value="custom">Custom</option>
<option value="20">20 pt</option>
<option value="30">30 pt</option>
<option value="40" selected>40 pt</option>
<option value="50">50 pt</option>
<option value="60">60 pt</option>
</select>
<div class="custom-margin-wrapper" id="overlay-headerCustomMarginWrapper" style="display: none; width: 150px;">
<input
type="number"
class="form-control custom-margin-input"
id="overlay-headerCustomMarginInput"
th:placeholder="#{remove-header-footer.enterValue}"
data-type="header"
min="0"
/>
</div>
</span>
</div>
<div class="d-flex align-items-center flex-row">
<div class="splitToolbarButton mb-2 mb-md-0 me-md-2">
<button id="zoomOut" th:title="#{remove-header-footer.zoomOut}" class="toolbarButton btn-primary" tabindex="21"
data-l10n-id="pdfjs-zoom-out-button">
</button>
<div class="splitToolbarButtonSeparator"></div>
<button id="zoomIn" th:title="#{remove-header-footer.zoomIn}" class="toolbarButton btn-primary" tabindex="22"
data-l10n-id="pdfjs-zoom-in-button">
</button>
</div>
<span id="scaleSelectContainer" class="dropdownToolbarButton">
<select id="scaleSelect" tabindex="23" data-l10n-id="pdfjs-zoom-select">
<option id="pageAutoOption" value="auto" selected="selected" data-l10n-id="pdfjs-page-scale-auto">
Automatic Zoom</option>
<option id="pageActualOption" value="page-actual" data-l10n-id="pdfjs-page-scale-actual">
Actual Size</option>
<option id="pageFitOption" value="page-fit" data-l10n-id="pdfjs-page-scale-fit">Page
Fit
</option>
<option id="pageWidthOption" value="page-width" data-l10n-id="pdfjs-page-scale-width">
Page Width</option>
<option value="0.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 50 }'>
50%</option>
<option value="0.75" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 75 }'>75%
</option>
<option value="1" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 100 }'>
100%</option>
<option value="1.25" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 125 }'>125%
</option>
<option value="1.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 150 }'>150%
</option>
<option value="2" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 200 }'>
200%</option>
<option value="3" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 300 }'>
300%</option>
</select>
</span>
</div>
<div id="overlay-margin-options" class="d-flex align-items-center">
<div class="form-check" style="margin-bottom: 0px;" >
<input type="checkbox" id="overlay-removeFooter" class="form-check-input" style=" margin-bottom: 4px; margin-left: 7px;">
<label for="overlay-removeFooter" class="form-check-label" style="padding-right: 2px; " th:text="#{remove-header-footer.removeFooter}"></label>
</div>
<span id="overlay-footerMargin-container">
<select class="form-select" id="overlay-footerMargin" style="width:auto;">
<option value="custom">Custom</option>
<option value="20">20 pt</option>
<option value="30">30 pt</option>
<option value="40" selected>40 pt</option>
<option value="50">50 pt</option>
<option value="60">60 pt</option>
</select>
<div class="custom-margin-wrapper" id="overlay-footerCustomMarginWrapper" style="display: none;max-width: 150px;">
<input
type="number"
class="form-control custom-margin-input"
id="overlay-footerCustomMarginInput"
th:placeholder="#{remove-header-footer.enterValue}"
data-type="footer"
min="0"
/>
</div>
</span>
</div>
</div>
<div id="toolbarViewerRight">
<div id="splitToolbarButton">
<button id="closeOverlay" class="close-button me-2" aria-label="Close" th:title="#{remove-header-footer.close}">
<span class="material-symbols-rounded">close</span>
</button>
</div>
</div>
</div>
</div>
</div> <!-- Toolbar -->
</div>
<div id="viewerContainer" tabindex="0" class="overlay-content">
<div id="viewer" class="pdfViewer ṕagesView">
<!-- PDF will be rendered here -->
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</div>
</body>
</html>