mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-04-19 11:11:18 +00:00
Compare commits
7 Commits
9f5f333f57
...
def0552f24
Author | SHA1 | Date | |
---|---|---|---|
![]() |
def0552f24 | ||
![]() |
de9e3edf5c | ||
![]() |
1e0e942d93 | ||
![]() |
6da84338dc | ||
![]() |
1c27944329 | ||
![]() |
5a0567cf6a | ||
![]() |
63a9e40aa9 |
@ -134,7 +134,7 @@ Stirling-PDF currently supports 39 languages!
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||
| Irish (Gaeilge) (ga_IE) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Norwegian (Norsk) (no_NB) |  |
|
||||
|
@ -29,7 +29,7 @@ ext {
|
||||
}
|
||||
|
||||
group = "stirling.software"
|
||||
version = "0.45.5"
|
||||
version = "0.45.6"
|
||||
|
||||
java {
|
||||
// 17 is lowest but we support and recommend 21
|
||||
@ -516,7 +516,7 @@ dependencies {
|
||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
||||
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
||||
implementation "io.micrometer:micrometer-core:1.14.5"
|
||||
implementation "io.micrometer:micrometer-core:1.14.6"
|
||||
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||
implementation "org.commonmark:commonmark:0.24.0"
|
||||
|
@ -109,33 +109,6 @@ public class AppConfig {
|
||||
return (rateLimit != null) ? Boolean.valueOf(rateLimit) : false;
|
||||
}
|
||||
|
||||
@Bean(name = "uploadLimit")
|
||||
public long uploadLimit() {
|
||||
String maxUploadSize =
|
||||
applicationProperties.getSystem().getFileUploadLimit() != null
|
||||
? applicationProperties.getSystem().getFileUploadLimit()
|
||||
: "";
|
||||
|
||||
if (maxUploadSize.isEmpty()) {
|
||||
return 0;
|
||||
} else if (!new Regex("^[1-9][0-9]{0,2}[KMGkmg][Bb]$").matches(maxUploadSize)) {
|
||||
log.error(
|
||||
"Invalid maxUploadSize format. Expected format: [1-9][0-9]{0,2}[KMGkmg][Bb], but got: {}",
|
||||
maxUploadSize);
|
||||
return 0;
|
||||
} else {
|
||||
String unit = maxUploadSize.replaceAll("[1-9][0-9]{0,2}", "").toUpperCase();
|
||||
String number = maxUploadSize.replaceAll("[KMGkmg][Bb]", "");
|
||||
long size = Long.parseLong(number);
|
||||
return switch (unit) {
|
||||
case "KB" -> size * 1024;
|
||||
case "MB" -> size * 1024 * 1024;
|
||||
case "GB" -> size * 1024 * 1024 * 1024;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Bean(name = "RunningInDocker")
|
||||
public boolean runningInDocker() {
|
||||
return Files.exists(Paths.get("/.dockerenv"));
|
||||
|
@ -1,30 +0,0 @@
|
||||
package stirling.software.SPDF.controller.web;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
|
||||
@Component
|
||||
@ControllerAdvice
|
||||
public class GlobalUploadLimitWebController {
|
||||
|
||||
@Autowired() private long uploadLimit;
|
||||
|
||||
@ModelAttribute("uploadLimit")
|
||||
public long populateUploadLimit() {
|
||||
return uploadLimit;
|
||||
}
|
||||
|
||||
@ModelAttribute("uploadLimitReadable")
|
||||
public String populateReadableLimit() {
|
||||
return humanReadableByteCount(uploadLimit);
|
||||
}
|
||||
|
||||
private String humanReadableByteCount(long bytes) {
|
||||
if (bytes < 1024) return bytes + " B";
|
||||
int exp = (int) (Math.log(bytes) / Math.log(1024));
|
||||
String pre = "KMGTPE".charAt(exp - 1) + "B";
|
||||
return String.format("%.1f %s", bytes / Math.pow(1024, exp), pre);
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package stirling.software.SPDF.controller.web;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class UploadLimitService {
|
||||
|
||||
@Autowired
|
||||
private ApplicationProperties applicationProperties;
|
||||
|
||||
public long getUploadLimit() {
|
||||
String maxUploadSize =
|
||||
applicationProperties.getSystem().getFileUploadLimit() != null
|
||||
? applicationProperties.getSystem().getFileUploadLimit()
|
||||
: "";
|
||||
|
||||
if (maxUploadSize.isEmpty()) {
|
||||
return 0;
|
||||
} else if (!Pattern.compile("^[1-9][0-9]{0,2}[KMGkmg][Bb]$").matcher(maxUploadSize).matches()) {
|
||||
log.error(
|
||||
"Invalid maxUploadSize format. Expected format: [1-9][0-9]{0,2}[KMGkmg][Bb], but got: {}",
|
||||
maxUploadSize);
|
||||
return 0;
|
||||
} else {
|
||||
String unit = maxUploadSize.replaceAll("[1-9][0-9]{0,2}", "").toUpperCase();
|
||||
String number = maxUploadSize.replaceAll("[KMGkmg][Bb]", "");
|
||||
long size = Long.parseLong(number);
|
||||
return switch (unit) {
|
||||
case "KB" -> size * 1024;
|
||||
case "MB" -> size * 1024 * 1024;
|
||||
case "GB" -> size * 1024 * 1024 * 1024;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: why do this server side not client?
|
||||
public String getReadableUploadLimit() {
|
||||
return humanReadableByteCount(getUploadLimit());
|
||||
}
|
||||
|
||||
private String humanReadableByteCount(long bytes) {
|
||||
if (bytes < 1024) return bytes + " B";
|
||||
int exp = (int) (Math.log(bytes) / Math.log(1024));
|
||||
String pre = "KMGTPE".charAt(exp - 1) + "B";
|
||||
return String.format("%.1f %s", bytes / Math.pow(1024, exp), pre);
|
||||
}
|
||||
}
|
@ -10,9 +10,9 @@ multiPdfPrompt=Scegli 2 o più PDF
|
||||
multiPdfDropPrompt=Scegli (o trascina e rilascia) uno o più PDF
|
||||
imgPrompt=Scegli immagine/i
|
||||
genericSubmit=Invia
|
||||
uploadLimit=Maximum file size:
|
||||
uploadLimitExceededSingular=is too large. Maximum allowed size is
|
||||
uploadLimitExceededPlural=are too large. Maximum allowed size is
|
||||
uploadLimit=Dimensione massima del file:
|
||||
uploadLimitExceededSingular=è troppo grande. La dimensione massima consentita è
|
||||
uploadLimitExceededPlural=sono troppo grandi. La dimensione massima consentita è
|
||||
processTimeWarning=Nota: Questo processo potrebbe richiedere fino a un minuto in base alla dimensione dei file
|
||||
pageOrderPrompt=Ordine delle pagine (inserisci una lista di numeri separati da virgola):
|
||||
pageSelectionPrompt=Selezione pagina personalizzata (inserisci un elenco separato da virgole di numeri di pagina 1,5,6 o funzioni come 2n+1) :
|
||||
@ -93,7 +93,7 @@ legal.terms=Termini e Condizioni
|
||||
legal.accessibility=Accessibilità
|
||||
legal.cookie=Informativa sui cookie
|
||||
legal.impressum=Informazioni legali
|
||||
legal.showCookieBanner=Cookie Preferences
|
||||
legal.showCookieBanner=Preferenze sui cookie
|
||||
|
||||
###############
|
||||
# Pipeline #
|
||||
|
@ -553,7 +553,7 @@
|
||||
{
|
||||
"moduleName": "io.micrometer:micrometer-core",
|
||||
"moduleUrl": "https://github.com/micrometer-metrics/micrometer",
|
||||
"moduleVersion": "1.14.5",
|
||||
"moduleVersion": "1.14.6",
|
||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
|
@ -132,7 +132,9 @@
|
||||
}
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
showGameBtn.style.display = 'none';
|
||||
if(showGameBtn){
|
||||
showGameBtn.style.display = 'none';
|
||||
}
|
||||
submitButton.textContent = originalButtonText;
|
||||
submitButton.disabled = false;
|
||||
handleDownloadError(error);
|
||||
|
@ -170,7 +170,7 @@ function setupFileInput(chooser) {
|
||||
inputContainer.querySelector('#fileInputText').innerHTML = window.fileInput.loading;
|
||||
|
||||
async function checkZipFile() {
|
||||
const hasZipFiles = allFiles.some(file => zipTypes.includes(file.type));
|
||||
const hasZipFiles = allFiles.some(file => file.type && zipTypes.includes(file.type));
|
||||
|
||||
// Only change to extractPDF message if we actually have zip files
|
||||
if (hasZipFiles) {
|
||||
|
@ -255,5 +255,12 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
}, 500);
|
||||
|
||||
Array.from(document.querySelectorAll('.feature-group-header')).forEach((header) => {
|
||||
const parent = header.parentNode;
|
||||
header.onclick = () => {
|
||||
expandCollapseToggle(parent);
|
||||
};
|
||||
});
|
||||
|
||||
showFavoritesOnly();
|
||||
});
|
||||
|
@ -241,10 +241,5 @@ document.addEventListener('DOMContentLoaded', async function () {
|
||||
console.error('Material Symbols Rounded font failed to load.');
|
||||
});
|
||||
|
||||
Array.from(document.querySelectorAll('.feature-group-header')).forEach((header) => {
|
||||
const parent = header.parentNode;
|
||||
header.onclick = () => {
|
||||
expandCollapseToggle(parent);
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -57,11 +57,15 @@ function initLanguageSettings() {
|
||||
|
||||
function sortLanguageDropdown() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const dropdownMenu = document.querySelector('.dropdown-menu .dropdown-item.lang_dropdown-item').parentElement;
|
||||
const dropdownMenu = document.getElementById('languageSelection');
|
||||
if (dropdownMenu) {
|
||||
const items = Array.from(dropdownMenu.children).filter((child) => child.matches('a'));
|
||||
const items = Array.from(dropdownMenu.children).filter((child) => child.querySelector('a'));
|
||||
items
|
||||
.sort((a, b) => a.dataset.bsLanguageCode.localeCompare(b.dataset.bsLanguageCode))
|
||||
.sort((wrapperA, wrapperB) => {
|
||||
const a = wrapperA.querySelector('a');
|
||||
const b = wrapperB.querySelector('a');
|
||||
return a.dataset.bsLanguageCode.localeCompare(b.dataset.bsLanguageCode);
|
||||
})
|
||||
.forEach((node) => dropdownMenu.appendChild(node));
|
||||
}
|
||||
});
|
||||
|
@ -21,12 +21,10 @@ export class DeletePageCommand extends Command {
|
||||
this.pagesContainer.removeChild(this.element);
|
||||
if (this.pagesContainer.childElementCount === 0) {
|
||||
const filenameInput = document.getElementById("filename-input");
|
||||
const filenameParagraph = document.getElementById("filename");
|
||||
const downloadBtn = document.getElementById("export-button");
|
||||
|
||||
filenameInput.disabled = true;
|
||||
filenameInput.value = "";
|
||||
filenameParagraph.innerText = "";
|
||||
|
||||
downloadBtn.disabled = true;
|
||||
}
|
||||
@ -43,13 +41,10 @@ export class DeletePageCommand extends Command {
|
||||
}
|
||||
|
||||
const filenameInput = document.getElementById("filename-input");
|
||||
const filenameParagraph = document.getElementById("filename");
|
||||
const downloadBtn = document.getElementById("export-button");
|
||||
|
||||
filenameInput.disabled = false;
|
||||
filenameInput.value = this.filenameInputValue;
|
||||
if (this.filenameParagraph)
|
||||
filenameParagraph.innerText = this.filenameParagraphText;
|
||||
|
||||
downloadBtn.disabled = false;
|
||||
}
|
||||
@ -63,12 +58,10 @@ export class DeletePageCommand extends Command {
|
||||
this.pagesContainer.removeChild(this.element);
|
||||
if (this.pagesContainer.childElementCount === 0) {
|
||||
const filenameInput = document.getElementById("filename-input");
|
||||
const filenameParagraph = document.getElementById("filename");
|
||||
const downloadBtn = document.getElementById("export-button");
|
||||
|
||||
filenameInput.disabled = true;
|
||||
filenameInput.value = "";
|
||||
filenameParagraph.innerText = "";
|
||||
|
||||
downloadBtn.disabled = true;
|
||||
}
|
||||
|
@ -112,10 +112,10 @@ function setAsDefault(value) {
|
||||
|
||||
function adjustVisibleElements() {
|
||||
const container = document.querySelector('.recent-features');
|
||||
if(!container) return;
|
||||
const subElements = Array.from(container.children);
|
||||
|
||||
let totalWidth = 0;
|
||||
const containerWidth = container.offsetWidth;
|
||||
|
||||
subElements.forEach((element) => {
|
||||
totalWidth += 12 * parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
|
@ -26,7 +26,6 @@ window.addEventListener("keydown", (event) => {
|
||||
|
||||
function undoDraw() {
|
||||
const data = signaturePad.toData();
|
||||
|
||||
if (data && data.length > 0) {
|
||||
const removed = data.pop();
|
||||
undoData.push(removed);
|
||||
@ -35,7 +34,6 @@ function undoDraw() {
|
||||
}
|
||||
|
||||
function redoDraw() {
|
||||
|
||||
if (undoData.length > 0) {
|
||||
const data = signaturePad.toData();
|
||||
data.push(undoData.pop());
|
||||
@ -52,24 +50,18 @@ function addDraggableFromPad() {
|
||||
}
|
||||
|
||||
function getCroppedCanvasDataUrl(canvas) {
|
||||
let originalCtx = canvas.getContext('2d');
|
||||
let originalCtx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
let originalWidth = canvas.width;
|
||||
let originalHeight = canvas.height;
|
||||
let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
|
||||
|
||||
let minX = originalWidth + 1,
|
||||
maxX = -1,
|
||||
minY = originalHeight + 1,
|
||||
maxY = -1,
|
||||
x = 0,
|
||||
y = 0,
|
||||
currentPixelColorValueIndex;
|
||||
let minX = originalWidth + 1, maxX = -1, minY = originalHeight + 1, maxY = -1;
|
||||
|
||||
for (y = 0; y < originalHeight; y++) {
|
||||
for (x = 0; x < originalWidth; x++) {
|
||||
currentPixelColorValueIndex = (y * originalWidth + x) * 4;
|
||||
let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
|
||||
if (currentPixelAlphaValue > 0) {
|
||||
for (let y = 0; y < originalHeight; y++) {
|
||||
for (let x = 0; x < originalWidth; x++) {
|
||||
let idx = (y * originalWidth + x) * 4;
|
||||
let alpha = imageData.data[idx + 3];
|
||||
if (alpha > 0) {
|
||||
if (minX > x) minX = x;
|
||||
if (maxX < x) maxX = x;
|
||||
if (minY > y) minY = y;
|
||||
@ -81,14 +73,14 @@ function getCroppedCanvasDataUrl(canvas) {
|
||||
let croppedWidth = maxX - minX;
|
||||
let croppedHeight = maxY - minY;
|
||||
if (croppedWidth < 0 || croppedHeight < 0) return null;
|
||||
let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
|
||||
let cutImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
|
||||
|
||||
let croppedCanvas = document.createElement('canvas'),
|
||||
croppedCtx = croppedCanvas.getContext('2d');
|
||||
let croppedCanvas = document.createElement('canvas');
|
||||
let croppedCtx = croppedCanvas.getContext('2d');
|
||||
|
||||
croppedCanvas.width = croppedWidth;
|
||||
croppedCanvas.height = croppedHeight;
|
||||
croppedCtx.putImageData(cuttedImageData, 0, 0);
|
||||
croppedCtx.putImageData(cutImageData, 0, 0);
|
||||
|
||||
return croppedCanvas.toDataURL();
|
||||
}
|
||||
@ -114,10 +106,20 @@ function resizeCanvas() {
|
||||
signaturePad.clear();
|
||||
}
|
||||
|
||||
new IntersectionObserver((entries, observer) => {
|
||||
const debounce = (fn, delay = 100) => {
|
||||
let timer;
|
||||
return (...args) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => fn(...args), delay);
|
||||
};
|
||||
};
|
||||
|
||||
const debouncedResize = debounce(resizeCanvas, 200);
|
||||
|
||||
new IntersectionObserver((entries) => {
|
||||
if (entries.some((entry) => entry.intersectionRatio > 0)) {
|
||||
resizeCanvas();
|
||||
debouncedResize();
|
||||
}
|
||||
}).observe(signaturePadCanvas);
|
||||
|
||||
new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);
|
||||
new ResizeObserver(debouncedResize).observe(signaturePadCanvas);
|
||||
|
@ -240,8 +240,8 @@
|
||||
window.stirlingPDF.sessionExpired = /*[[#{session.expired}]]*/ '';
|
||||
window.stirlingPDF.refreshPage = /*[[#{session.refreshPage}]]*/ 'Refresh Page';
|
||||
window.stirlingPDF.error = /*[[#{error}]]*/ "Error";
|
||||
window.stirlingPDF.uploadLimit = /*[[${uploadLimit}]]*/ 0;
|
||||
window.stirlingPDF.uploadLimitReadable = /*[[${uploadLimitReadable}]]*/ 'Unlimited';
|
||||
window.stirlingPDF.uploadLimitReadable = /*[[${@uploadLimitService.getReadableUploadLimit()}]]*/ 'Unlimited';
|
||||
window.stirlingPDF.uploadLimit = /*[[${@uploadLimitService.getUploadLimit()}]]*/ 0;
|
||||
window.stirlingPDF.uploadLimitExceededSingular = /*[[#{uploadLimitExceededSingular}]]*/ 'is too large. Maximum allowed size is';
|
||||
window.stirlingPDF.uploadLimitExceededPlural = /*[[#{uploadLimitExceededPlural}]]*/ 'are too large. Maximum allowed size is';
|
||||
})();
|
||||
@ -292,10 +292,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="selected-files flex-wrap"></div>
|
||||
<div class="text-muted small mt-0 text-end w-100" th:if="${uploadLimit != 0}">
|
||||
<span th:text="#{uploadLimit}">Maximum file size: </span>
|
||||
<span th:text="${uploadLimitReadable}"></span>
|
||||
</div>
|
||||
<div class="text-muted small mt-0 text-end w-100" th:if="${@uploadLimitService.getUploadLimit() != 0}">
|
||||
<span th:text="#{uploadLimit}">Maximum file size: </span>
|
||||
<span th:text="${@uploadLimitService.getReadableUploadLimit()}"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progressBarContainer" style="display: none; position: relative;">
|
||||
<div class="progress" style="height: 1rem;">
|
||||
|
@ -143,7 +143,7 @@
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-tp" aria-labelledby="languageDropdown">
|
||||
<div class="dropdown-menu-wrapper px-xl-2 px-2">
|
||||
<div class="scrollable-y lang_dropdown-mw scalable-languages-container">
|
||||
<div id="languageSelection" class="scrollable-y lang_dropdown-mw scalable-languages-container">
|
||||
<th:block th:insert="~{fragments/languages :: langs}"></th:block>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -143,7 +143,7 @@
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="languageDropdown">
|
||||
<!-- Here's where the fragment will be included -->
|
||||
<div class="scrollable-y">
|
||||
<div id="languageSelection" class="scrollable-y" >
|
||||
<th:block th:replace="~{fragments/languages :: langs}"></th:block>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -27,7 +27,7 @@
|
||||
</div>
|
||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||
<script type="module" th:src="@{'/js/pages/add-image.js'}"></script>
|
||||
<div class="tab-group show-on-file-selected">
|
||||
<div class="show-on-file-selected">
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=false, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}">
|
||||
</div>
|
||||
|
@ -43,13 +43,13 @@
|
||||
</div>
|
||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||
<div class="tab-group show-on-file-selected">
|
||||
<div class="tab-container" th:title="#{sign.upload}" th:data-title="#{sign.upload}">
|
||||
<div class="tab-container"th:data-title="#{sign.upload}">
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=false, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-container drawing-pad-container" th:title="#{sign.draw}" th:data-title="#{sign.draw}">
|
||||
<div class="tab-container drawing-pad-container" th:data-title="#{sign.draw}">
|
||||
<canvas id="drawing-pad-canvas"></canvas>
|
||||
<br>
|
||||
<button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()"
|
||||
@ -62,7 +62,7 @@
|
||||
onclick="redoDraw()"></button>
|
||||
</div>
|
||||
|
||||
<div class="tab-container" th:title="#{sign.saved}" th:data-title="#{sign.saved}">
|
||||
<div class="tab-container" th:data-title="#{sign.saved}">
|
||||
<div class="saved-signatures-section" th:if="${not #lists.isEmpty(signatures)}">
|
||||
|
||||
<!-- Preview Modal -->
|
||||
@ -134,7 +134,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-container" th:title="#{sign.text}" th:data-title="#{sign.text}">
|
||||
<div class="tab-container" th:data-title="#{sign.text}">
|
||||
<label class="form-check-label" for="sigText" th:text="#{text}"></label>
|
||||
<textarea class="form-control" id="sigText" name="sigText" rows="3"></textarea>
|
||||
<label th:text="#{font}"></label>
|
||||
|
Loading…
x
Reference in New Issue
Block a user