Compare commits

..

No commits in common. "fa8df329dfcd0299d533fd6dd4e92a52975a6564" and "fe71ab01155674731d11fc35e8583878add86870" have entirely different histories.

21 changed files with 166 additions and 167 deletions

View File

@ -134,14 +134,14 @@ Stirling-PDF currently supports 39 languages!
| Hungarian (Magyar) (hu_HU) | ![89%](https://geps.dev/progress/89) | | Hungarian (Magyar) (hu_HU) | ![89%](https://geps.dev/progress/89) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![81%](https://geps.dev/progress/81) | | Indonesian (Bahasa Indonesia) (id_ID) | ![81%](https://geps.dev/progress/81) |
| Irish (Gaeilge) (ga_IE) | ![92%](https://geps.dev/progress/92) | | Irish (Gaeilge) (ga_IE) | ![92%](https://geps.dev/progress/92) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) | | Italian (Italiano) (it_IT) | ![98%](https://geps.dev/progress/98) |
| Japanese (日本語) (ja_JP) | ![89%](https://geps.dev/progress/89) | | Japanese (日本語) (ja_JP) | ![89%](https://geps.dev/progress/89) |
| Korean (한국어) (ko_KR) | ![92%](https://geps.dev/progress/92) | | Korean (한국어) (ko_KR) | ![92%](https://geps.dev/progress/92) |
| Norwegian (Norsk) (no_NB) | ![86%](https://geps.dev/progress/86) | | Norwegian (Norsk) (no_NB) | ![86%](https://geps.dev/progress/86) |
| Persian (فارسی) (fa_IR) | ![88%](https://geps.dev/progress/88) | | Persian (فارسی) (fa_IR) | ![88%](https://geps.dev/progress/88) |
| Polish (Polski) (pl_PL) | ![96%](https://geps.dev/progress/96) | | Polish (Polski) (pl_PL) | ![96%](https://geps.dev/progress/96) |
| Portuguese (Português) (pt_PT) | ![91%](https://geps.dev/progress/91) | | Portuguese (Português) (pt_PT) | ![91%](https://geps.dev/progress/91) |
| Portuguese Brazilian (Português) (pt_BR) | ![98%](https://geps.dev/progress/98) | | Portuguese Brazilian (Português) (pt_BR) | ![94%](https://geps.dev/progress/94) |
| Romanian (Română) (ro_RO) | ![75%](https://geps.dev/progress/75) | | Romanian (Română) (ro_RO) | ![75%](https://geps.dev/progress/75) |
| Russian (Русский) (ru_RU) | ![94%](https://geps.dev/progress/94) | | Russian (Русский) (ru_RU) | ![94%](https://geps.dev/progress/94) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![60%](https://geps.dev/progress/60) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![60%](https://geps.dev/progress/60) |

View File

@ -29,7 +29,7 @@ ext {
} }
group = "stirling.software" group = "stirling.software"
version = "0.45.6" version = "0.45.5"
java { java {
// 17 is lowest but we support and recommend 21 // 17 is lowest but we support and recommend 21
@ -518,7 +518,7 @@ dependencies {
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
implementation "io.micrometer:micrometer-core:1.14.6" implementation "io.micrometer:micrometer-core:1.14.5"
implementation group: "com.google.zxing", name: "core", version: "3.5.3" implementation group: "com.google.zxing", name: "core", version: "3.5.3"
// https://mvnrepository.com/artifact/org.commonmark/commonmark // https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark:0.24.0" implementation "org.commonmark:commonmark:0.24.0"

View File

@ -109,6 +109,33 @@ public class AppConfig {
return (rateLimit != null) ? Boolean.valueOf(rateLimit) : false; 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") @Bean(name = "RunningInDocker")
public boolean runningInDocker() { public boolean runningInDocker() {
return Files.exists(Paths.get("/.dockerenv")); return Files.exists(Paths.get("/.dockerenv"));

View File

@ -0,0 +1,30 @@
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);
}
}

View File

@ -1,55 +0,0 @@
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);
}
}

View File

@ -10,9 +10,9 @@ multiPdfPrompt=Scegli 2 o più PDF
multiPdfDropPrompt=Scegli (o trascina e rilascia) uno o più PDF multiPdfDropPrompt=Scegli (o trascina e rilascia) uno o più PDF
imgPrompt=Scegli immagine/i imgPrompt=Scegli immagine/i
genericSubmit=Invia genericSubmit=Invia
uploadLimit=Dimensione massima del file: uploadLimit=Maximum file size:
uploadLimitExceededSingular=è troppo grande. La dimensione massima consentita è uploadLimitExceededSingular=is too large. Maximum allowed size is
uploadLimitExceededPlural=sono troppo grandi. La dimensione massima consentita è uploadLimitExceededPlural=are too large. Maximum allowed size is
processTimeWarning=Nota: Questo processo potrebbe richiedere fino a un minuto in base alla dimensione dei file 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): 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) : 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.accessibility=Accessibilità
legal.cookie=Informativa sui cookie legal.cookie=Informativa sui cookie
legal.impressum=Informazioni legali legal.impressum=Informazioni legali
legal.showCookieBanner=Preferenze sui cookie legal.showCookieBanner=Cookie Preferences
############### ###############
# Pipeline # # Pipeline #

View File

@ -10,9 +10,9 @@ multiPdfPrompt=Selecione os PDFs (2+)
multiPdfDropPrompt=Selecione (ou arraste e solte) todos os PDFs desejados: multiPdfDropPrompt=Selecione (ou arraste e solte) todos os PDFs desejados:
imgPrompt=Selecione a(s) Imagem(ns) imgPrompt=Selecione a(s) Imagem(ns)
genericSubmit=Enviar genericSubmit=Enviar
uploadLimit=Tamanho máximo do arquivo: uploadLimit=Maximum file size:
uploadLimitExceededSingular=está acima do limite. Tamanho máximo permitido é uploadLimitExceededSingular=is too large. Maximum allowed size is
uploadLimitExceededPlural=estão acima do limite. Tamanho máximo permitido é uploadLimitExceededPlural=are too large. Maximum allowed size is
processTimeWarning=Aviso: Este processo pode levar até um minuto, dependendo do tamanho do arquivo processTimeWarning=Aviso: Este processo pode levar até um minuto, dependendo do tamanho do arquivo
pageOrderPrompt=Ordem de Página Personalizada (Digite uma lista de números de páginas, separadas por vírgula ou funções como 2n+1): pageOrderPrompt=Ordem de Página Personalizada (Digite uma lista de números de páginas, separadas por vírgula ou funções como 2n+1):
pageSelectionPrompt=Seleção de Página Personalizada (Digite uma lista de números de páginas, separadas por vírgula como 1,5,6 ou funções como 2n+1): pageSelectionPrompt=Seleção de Página Personalizada (Digite uma lista de números de páginas, separadas por vírgula como 1,5,6 ou funções como 2n+1):
@ -86,14 +86,14 @@ loading=Carregando...
addToDoc=Adicionar ao Documento addToDoc=Adicionar ao Documento
reset=Reiniciar reset=Reiniciar
apply=Aplicar apply=Aplicar
noFileSelected=Nenhum arquivo selecionado. Por favo, envie um arquivo. noFileSelected=No file selected. Please upload one.
legal.privacy=Política de Privacidade legal.privacy=Política de Privacidade
legal.terms=Termos e Condições legal.terms=Termos e Condições
legal.accessibility=Acessibilidade legal.accessibility=Acessibilidade
legal.cookie=Política de Cookies legal.cookie=Política de Cookies
legal.impressum=Informações legais legal.impressum=Informações legais
legal.showCookieBanner=Preferências de Cookies legal.showCookieBanner=Cookie Preferences
############### ###############
# Pipeline # # Pipeline #
@ -237,31 +237,31 @@ adminUserSettings.activeUsers=Usuários Ativos:
adminUserSettings.disabledUsers=Usuários Desabilitados: adminUserSettings.disabledUsers=Usuários Desabilitados:
adminUserSettings.totalUsers=Total de Usuários: adminUserSettings.totalUsers=Total de Usuários:
adminUserSettings.lastRequest=Última solicitação adminUserSettings.lastRequest=Última solicitação
adminUserSettings.usage=Ver Utilização adminUserSettings.usage=View Usage
endpointStatistics.title=Estatísticas de Endpoints endpointStatistics.title=Endpoint Statistics
endpointStatistics.header=Estatísticas de Endpoints endpointStatistics.header=Endpoint Statistics
endpointStatistics.top10=Top 10 endpointStatistics.top10=Top 10
endpointStatistics.top20=Top 20 endpointStatistics.top20=Top 20
endpointStatistics.all=Todos endpointStatistics.all=All
endpointStatistics.refresh=Atualizar endpointStatistics.refresh=Refresh
endpointStatistics.includeHomepage=Incluir Página Inicial ('/') endpointStatistics.includeHomepage=Include Homepage ('/')
endpointStatistics.includeLoginPage=Incluir Página de Login ('/login') endpointStatistics.includeLoginPage=Include Login Page ('/login')
endpointStatistics.totalEndpoints=Total de Endpoints endpointStatistics.totalEndpoints=Total Endpoints
endpointStatistics.totalVisits=Total de Visitas endpointStatistics.totalVisits=Total Visits
endpointStatistics.showing=Mostrando endpointStatistics.showing=Showing
endpointStatistics.selectedVisits=Visitas Selecionadas endpointStatistics.selectedVisits=Selected Visits
endpointStatistics.endpoint=Endpoint endpointStatistics.endpoint=Endpoint
endpointStatistics.visits=Visitas endpointStatistics.visits=Visits
endpointStatistics.percentage=Percentagem endpointStatistics.percentage=Percentage
endpointStatistics.loading=Carregando... endpointStatistics.loading=Loading...
endpointStatistics.failedToLoad=Falha ao carregar dados do Endpoint. Por favor, tente atualizar. endpointStatistics.failedToLoad=Failed to load endpoint data. Please try refreshing.
endpointStatistics.home=Home endpointStatistics.home=Home
endpointStatistics.login=Login endpointStatistics.login=Login
endpointStatistics.top=Top endpointStatistics.top=Top
endpointStatistics.numberOfVisits=Número de Visitas endpointStatistics.numberOfVisits=Number of Visits
endpointStatistics.visitsTooltip=Visitas: {0} ({1}% do total) endpointStatistics.visitsTooltip=Visits: {0} ({1}% of total)
endpointStatistics.retry=Tentar novamente endpointStatistics.retry=Retry
database.title=Importar/Exportar banco de dados database.title=Importar/Exportar banco de dados
database.header=Importar/Exportar banco de dados database.header=Importar/Exportar banco de dados
@ -739,10 +739,10 @@ sanitizePDF.title=Higienizar
sanitizePDF.header=Higienizar sanitizePDF.header=Higienizar
sanitizePDF.selectText.1=Remover scripts de JavaScript. sanitizePDF.selectText.1=Remover scripts de JavaScript.
sanitizePDF.selectText.2=Remover arquivos embutidos. sanitizePDF.selectText.2=Remover arquivos embutidos.
sanitizePDF.selectText.3=Remover metadados XMP. sanitizePDF.selectText.3=Remove XMP metadata
sanitizePDF.selectText.4=Remover links. sanitizePDF.selectText.4=Remover links.
sanitizePDF.selectText.5=Remover fontes. sanitizePDF.selectText.5=Remover fontes.
sanitizePDF.selectText.6=Remover metadados de informações do documento. sanitizePDF.selectText.6=Remove Document Info Metadata
sanitizePDF.submit=Higienizar PDF sanitizePDF.submit=Higienizar PDF
@ -1408,25 +1408,25 @@ validateSignature.cert.bits=bits
#################### ####################
# Cookie banner # # Cookie banner #
#################### ####################
cookieBanner.popUp.title=Como nós utilizamos Cookies: cookieBanner.popUp.title=How we use Cookies
cookieBanner.popUp.description.1=Nós utilizamos cookies e outras tecnologias para melhorar o Stirling PDF, ajude-nos para que possamos desenvolver novas funcionalidades que você irá amar. cookieBanner.popUp.description.1=We use cookies and other technologies to make Stirling PDF work better for you—helping us improve our tools and keep building features you'll love.
cookieBanner.popUp.description.2=Se você não tiver interesse, clicando em "Não, Obrigado" será habilitado apenas cookies essenciais, para o site funcionar sem problemas. cookieBanner.popUp.description.2=If youd rather not, clicking 'No Thanks' will only enable the essential cookies needed to keep things running smoothly.
cookieBanner.popUp.acceptAllBtn=Aceito cookieBanner.popUp.acceptAllBtn=Okay
cookieBanner.popUp.acceptNecessaryBtn=Não, Obrigado cookieBanner.popUp.acceptNecessaryBtn=No Thanks
cookieBanner.popUp.showPreferencesBtn=Gerenciar Preferências cookieBanner.popUp.showPreferencesBtn=Manage preferences
cookieBanner.preferencesModal.title=Central de Preferências de Consentimento cookieBanner.preferencesModal.title=Consent Preferences Center
cookieBanner.preferencesModal.acceptAllBtn=Aceitar tudo cookieBanner.preferencesModal.acceptAllBtn=Accept all
cookieBanner.preferencesModal.acceptNecessaryBtn=Rejeitar tudo cookieBanner.preferencesModal.acceptNecessaryBtn=Reject all
cookieBanner.preferencesModal.savePreferencesBtn=Salvar preferências cookieBanner.preferencesModal.savePreferencesBtn=Save preferences
cookieBanner.preferencesModal.closeIconLabel=Fechar janela cookieBanner.preferencesModal.closeIconLabel=Close modal
cookieBanner.preferencesModal.serviceCounterLabel=Serviço|Serviços cookieBanner.preferencesModal.serviceCounterLabel=Service|Services
cookieBanner.preferencesModal.subtitle=Uso de Cookies cookieBanner.preferencesModal.subtitle=Cookie Usage
cookieBanner.preferencesModal.description.1=Stirling PDF utiliza cookies e tecnologias semelhantes para aprimorar sua experiência e entender como nossas ferramentas são utilizadas. Isso nos ajuda a melhorar o desempenho, desenvolver os recursos de seu interesse e fornecer suporte contínuo aos nossos usuários. cookieBanner.preferencesModal.description.1=Stirling PDF uses cookies and similar technologies to enhance your experience and understand how our tools are used. This helps us improve performance, develop the features you care about, and provide ongoing support to our users.
cookieBanner.preferencesModal.description.2=O Stirling PDF não pode e nunca irá rastrear ou acessar o conteúdo dos documentos que você manipula. cookieBanner.preferencesModal.description.2=Stirling PDF cannot—and will never—track or access the content of the documents you use.
cookieBanner.preferencesModal.description.3=Sua privacidade e confiança são prioridades para nós. cookieBanner.preferencesModal.description.3=Your privacy and trust are at the core of what we do.
cookieBanner.preferencesModal.necessary.title.1=Cookies Estritamente Necessários cookieBanner.preferencesModal.necessary.title.1=Strictly Necessary Cookies
cookieBanner.preferencesModal.necessary.title.2=Sempre Ativado cookieBanner.preferencesModal.necessary.title.2=Always Enabled
cookieBanner.preferencesModal.necessary.description=Estes cookies são essenciais para o bom funcionamento do site. Eles habilitam recursos básicos como definir suas preferências de privacidade, realizar login e preencher formulários e é por isso que não podem ser desativados. cookieBanner.preferencesModal.necessary.description=These cookies are essential for the website to function properly. They enable core features like setting your privacy preferences, logging in, and filling out forms—which is why they cant be turned off.
cookieBanner.preferencesModal.analytics.title=Cookies Analíticos cookieBanner.preferencesModal.analytics.title=Analytics
cookieBanner.preferencesModal.analytics.description=Estes cookies nos ajudam a entender como nossas ferramentas estão sendo utilizadas, para que possamos nos concentrar na construção dos recursos que nossa comunidade mais valoriza. Fique tranquilo: o Stirling PDF não pode e nunca rastreará o conteúdo dos documentos com os quais você manipula. cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.

View File

@ -553,7 +553,7 @@
{ {
"moduleName": "io.micrometer:micrometer-core", "moduleName": "io.micrometer:micrometer-core",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer", "moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.14.6", "moduleVersion": "1.14.5",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },

View File

@ -132,9 +132,7 @@
} }
} catch (error) { } catch (error) {
clearTimeout(timeoutId); clearTimeout(timeoutId);
if(showGameBtn){ showGameBtn.style.display = 'none';
showGameBtn.style.display = 'none';
}
submitButton.textContent = originalButtonText; submitButton.textContent = originalButtonText;
submitButton.disabled = false; submitButton.disabled = false;
handleDownloadError(error); handleDownloadError(error);

View File

@ -170,7 +170,7 @@ function setupFileInput(chooser) {
inputContainer.querySelector('#fileInputText').innerHTML = window.fileInput.loading; inputContainer.querySelector('#fileInputText').innerHTML = window.fileInput.loading;
async function checkZipFile() { async function checkZipFile() {
const hasZipFiles = allFiles.some(file => file.type && zipTypes.includes(file.type)); const hasZipFiles = allFiles.some(file => zipTypes.includes(file.type));
// Only change to extractPDF message if we actually have zip files // Only change to extractPDF message if we actually have zip files
if (hasZipFiles) { if (hasZipFiles) {

View File

@ -255,12 +255,5 @@ document.addEventListener('DOMContentLoaded', function () {
}); });
}, 500); }, 500);
Array.from(document.querySelectorAll('.feature-group-header')).forEach((header) => {
const parent = header.parentNode;
header.onclick = () => {
expandCollapseToggle(parent);
};
});
showFavoritesOnly(); showFavoritesOnly();
}); });

View File

@ -241,5 +241,10 @@ document.addEventListener('DOMContentLoaded', async function () {
console.error('Material Symbols Rounded font failed to load.'); 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);
};
});
}); });

View File

@ -57,15 +57,11 @@ function initLanguageSettings() {
function sortLanguageDropdown() { function sortLanguageDropdown() {
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
const dropdownMenu = document.getElementById('languageSelection'); const dropdownMenu = document.querySelector('.dropdown-menu .dropdown-item.lang_dropdown-item').parentElement;
if (dropdownMenu) { if (dropdownMenu) {
const items = Array.from(dropdownMenu.children).filter((child) => child.querySelector('a')); const items = Array.from(dropdownMenu.children).filter((child) => child.matches('a'));
items items
.sort((wrapperA, wrapperB) => { .sort((a, b) => a.dataset.bsLanguageCode.localeCompare(b.dataset.bsLanguageCode))
const a = wrapperA.querySelector('a');
const b = wrapperB.querySelector('a');
return a.dataset.bsLanguageCode.localeCompare(b.dataset.bsLanguageCode);
})
.forEach((node) => dropdownMenu.appendChild(node)); .forEach((node) => dropdownMenu.appendChild(node));
} }
}); });

View File

@ -21,10 +21,12 @@ export class DeletePageCommand extends Command {
this.pagesContainer.removeChild(this.element); this.pagesContainer.removeChild(this.element);
if (this.pagesContainer.childElementCount === 0) { if (this.pagesContainer.childElementCount === 0) {
const filenameInput = document.getElementById("filename-input"); const filenameInput = document.getElementById("filename-input");
const filenameParagraph = document.getElementById("filename");
const downloadBtn = document.getElementById("export-button"); const downloadBtn = document.getElementById("export-button");
filenameInput.disabled = true; filenameInput.disabled = true;
filenameInput.value = ""; filenameInput.value = "";
filenameParagraph.innerText = "";
downloadBtn.disabled = true; downloadBtn.disabled = true;
} }
@ -41,10 +43,13 @@ export class DeletePageCommand extends Command {
} }
const filenameInput = document.getElementById("filename-input"); const filenameInput = document.getElementById("filename-input");
const filenameParagraph = document.getElementById("filename");
const downloadBtn = document.getElementById("export-button"); const downloadBtn = document.getElementById("export-button");
filenameInput.disabled = false; filenameInput.disabled = false;
filenameInput.value = this.filenameInputValue; filenameInput.value = this.filenameInputValue;
if (this.filenameParagraph)
filenameParagraph.innerText = this.filenameParagraphText;
downloadBtn.disabled = false; downloadBtn.disabled = false;
} }
@ -58,10 +63,12 @@ export class DeletePageCommand extends Command {
this.pagesContainer.removeChild(this.element); this.pagesContainer.removeChild(this.element);
if (this.pagesContainer.childElementCount === 0) { if (this.pagesContainer.childElementCount === 0) {
const filenameInput = document.getElementById("filename-input"); const filenameInput = document.getElementById("filename-input");
const filenameParagraph = document.getElementById("filename");
const downloadBtn = document.getElementById("export-button"); const downloadBtn = document.getElementById("export-button");
filenameInput.disabled = true; filenameInput.disabled = true;
filenameInput.value = ""; filenameInput.value = "";
filenameParagraph.innerText = "";
downloadBtn.disabled = true; downloadBtn.disabled = true;
} }

View File

@ -112,10 +112,10 @@ function setAsDefault(value) {
function adjustVisibleElements() { function adjustVisibleElements() {
const container = document.querySelector('.recent-features'); const container = document.querySelector('.recent-features');
if(!container) return;
const subElements = Array.from(container.children); const subElements = Array.from(container.children);
let totalWidth = 0; let totalWidth = 0;
const containerWidth = container.offsetWidth;
subElements.forEach((element) => { subElements.forEach((element) => {
totalWidth += 12 * parseFloat(getComputedStyle(document.documentElement).fontSize); totalWidth += 12 * parseFloat(getComputedStyle(document.documentElement).fontSize);

View File

@ -26,6 +26,7 @@ window.addEventListener("keydown", (event) => {
function undoDraw() { function undoDraw() {
const data = signaturePad.toData(); const data = signaturePad.toData();
if (data && data.length > 0) { if (data && data.length > 0) {
const removed = data.pop(); const removed = data.pop();
undoData.push(removed); undoData.push(removed);
@ -34,6 +35,7 @@ function undoDraw() {
} }
function redoDraw() { function redoDraw() {
if (undoData.length > 0) { if (undoData.length > 0) {
const data = signaturePad.toData(); const data = signaturePad.toData();
data.push(undoData.pop()); data.push(undoData.pop());
@ -50,18 +52,24 @@ function addDraggableFromPad() {
} }
function getCroppedCanvasDataUrl(canvas) { function getCroppedCanvasDataUrl(canvas) {
let originalCtx = canvas.getContext('2d', { willReadFrequently: true }); let originalCtx = canvas.getContext('2d');
let originalWidth = canvas.width; let originalWidth = canvas.width;
let originalHeight = canvas.height; let originalHeight = canvas.height;
let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight); let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
let minX = originalWidth + 1, maxX = -1, minY = originalHeight + 1, maxY = -1; let minX = originalWidth + 1,
maxX = -1,
minY = originalHeight + 1,
maxY = -1,
x = 0,
y = 0,
currentPixelColorValueIndex;
for (let y = 0; y < originalHeight; y++) { for (y = 0; y < originalHeight; y++) {
for (let x = 0; x < originalWidth; x++) { for (x = 0; x < originalWidth; x++) {
let idx = (y * originalWidth + x) * 4; currentPixelColorValueIndex = (y * originalWidth + x) * 4;
let alpha = imageData.data[idx + 3]; let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
if (alpha > 0) { if (currentPixelAlphaValue > 0) {
if (minX > x) minX = x; if (minX > x) minX = x;
if (maxX < x) maxX = x; if (maxX < x) maxX = x;
if (minY > y) minY = y; if (minY > y) minY = y;
@ -73,14 +81,14 @@ function getCroppedCanvasDataUrl(canvas) {
let croppedWidth = maxX - minX; let croppedWidth = maxX - minX;
let croppedHeight = maxY - minY; let croppedHeight = maxY - minY;
if (croppedWidth < 0 || croppedHeight < 0) return null; if (croppedWidth < 0 || croppedHeight < 0) return null;
let cutImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight); let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
let croppedCanvas = document.createElement('canvas'); let croppedCanvas = document.createElement('canvas'),
let croppedCtx = croppedCanvas.getContext('2d'); croppedCtx = croppedCanvas.getContext('2d');
croppedCanvas.width = croppedWidth; croppedCanvas.width = croppedWidth;
croppedCanvas.height = croppedHeight; croppedCanvas.height = croppedHeight;
croppedCtx.putImageData(cutImageData, 0, 0); croppedCtx.putImageData(cuttedImageData, 0, 0);
return croppedCanvas.toDataURL(); return croppedCanvas.toDataURL();
} }
@ -106,20 +114,10 @@ function resizeCanvas() {
signaturePad.clear(); signaturePad.clear();
} }
const debounce = (fn, delay = 100) => { new IntersectionObserver((entries, observer) => {
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)) { if (entries.some((entry) => entry.intersectionRatio > 0)) {
debouncedResize(); resizeCanvas();
} }
}).observe(signaturePadCanvas); }).observe(signaturePadCanvas);
new ResizeObserver(debouncedResize).observe(signaturePadCanvas); new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);

View File

@ -240,8 +240,8 @@
window.stirlingPDF.sessionExpired = /*[[#{session.expired}]]*/ ''; window.stirlingPDF.sessionExpired = /*[[#{session.expired}]]*/ '';
window.stirlingPDF.refreshPage = /*[[#{session.refreshPage}]]*/ 'Refresh Page'; window.stirlingPDF.refreshPage = /*[[#{session.refreshPage}]]*/ 'Refresh Page';
window.stirlingPDF.error = /*[[#{error}]]*/ "Error"; window.stirlingPDF.error = /*[[#{error}]]*/ "Error";
window.stirlingPDF.uploadLimitReadable = /*[[${@uploadLimitService.getReadableUploadLimit()}]]*/ 'Unlimited'; window.stirlingPDF.uploadLimit = /*[[${uploadLimit}]]*/ 0;
window.stirlingPDF.uploadLimit = /*[[${@uploadLimitService.getUploadLimit()}]]*/ 0; window.stirlingPDF.uploadLimitReadable = /*[[${uploadLimitReadable}]]*/ 'Unlimited';
window.stirlingPDF.uploadLimitExceededSingular = /*[[#{uploadLimitExceededSingular}]]*/ 'is too large. Maximum allowed size is'; window.stirlingPDF.uploadLimitExceededSingular = /*[[#{uploadLimitExceededSingular}]]*/ 'is too large. Maximum allowed size is';
window.stirlingPDF.uploadLimitExceededPlural = /*[[#{uploadLimitExceededPlural}]]*/ 'are too large. Maximum allowed size is'; window.stirlingPDF.uploadLimitExceededPlural = /*[[#{uploadLimitExceededPlural}]]*/ 'are too large. Maximum allowed size is';
})(); })();
@ -292,10 +292,10 @@
</div> </div>
</div> </div>
<div class="selected-files flex-wrap"></div> <div class="selected-files flex-wrap"></div>
<div class="text-muted small mt-0 text-end w-100" th:if="${@uploadLimitService.getUploadLimit() != 0}"> <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="#{uploadLimit}">Maximum file size: </span>
<span th:text="${@uploadLimitService.getReadableUploadLimit()}"></span> <span th:text="${uploadLimitReadable}"></span>
</div> </div>
</div> </div>
<div class="progressBarContainer" style="display: none; position: relative;"> <div class="progressBarContainer" style="display: none; position: relative;">
<div class="progress" style="height: 1rem;"> <div class="progress" style="height: 1rem;">

View File

@ -143,7 +143,7 @@
</a> </a>
<div class="dropdown-menu dropdown-menu-tp" aria-labelledby="languageDropdown"> <div class="dropdown-menu dropdown-menu-tp" aria-labelledby="languageDropdown">
<div class="dropdown-menu-wrapper px-xl-2 px-2"> <div class="dropdown-menu-wrapper px-xl-2 px-2">
<div id="languageSelection" class="scrollable-y lang_dropdown-mw scalable-languages-container"> <div class="scrollable-y lang_dropdown-mw scalable-languages-container">
<th:block th:insert="~{fragments/languages :: langs}"></th:block> <th:block th:insert="~{fragments/languages :: langs}"></th:block>
</div> </div>
</div> </div>

View File

@ -143,7 +143,7 @@
</button> </button>
<div class="dropdown-menu" aria-labelledby="languageDropdown"> <div class="dropdown-menu" aria-labelledby="languageDropdown">
<!-- Here's where the fragment will be included --> <!-- Here's where the fragment will be included -->
<div id="languageSelection" class="scrollable-y" > <div class="scrollable-y">
<th:block th:replace="~{fragments/languages :: langs}"></th:block> <th:block th:replace="~{fragments/languages :: langs}"></th:block>
</div> </div>
</div> </div>

View File

@ -27,7 +27,7 @@
</div> </div>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script> <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script type="module" th:src="@{'/js/pages/add-image.js'}"></script> <script type="module" th:src="@{'/js/pages/add-image.js'}"></script>
<div class="show-on-file-selected"> <div class="tab-group show-on-file-selected">
<div <div
th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=false, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}"> th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=false, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}">
</div> </div>

View File

@ -43,13 +43,13 @@
</div> </div>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script> <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<div class="tab-group show-on-file-selected"> <div class="tab-group show-on-file-selected">
<div class="tab-container"th:data-title="#{sign.upload}"> <div class="tab-container" th:title="#{sign.upload}" th:data-title="#{sign.upload}">
<div <div
th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=false, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}"> th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=false, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}">
</div> </div>
</div> </div>
<div class="tab-container drawing-pad-container" th:data-title="#{sign.draw}"> <div class="tab-container drawing-pad-container" th:title="#{sign.draw}" th:data-title="#{sign.draw}">
<canvas id="drawing-pad-canvas"></canvas> <canvas id="drawing-pad-canvas"></canvas>
<br> <br>
<button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()" <button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()"
@ -62,7 +62,7 @@
onclick="redoDraw()"></button> onclick="redoDraw()"></button>
</div> </div>
<div class="tab-container" th:data-title="#{sign.saved}"> <div class="tab-container" th:title="#{sign.saved}" th:data-title="#{sign.saved}">
<div class="saved-signatures-section" th:if="${not #lists.isEmpty(signatures)}"> <div class="saved-signatures-section" th:if="${not #lists.isEmpty(signatures)}">
<!-- Preview Modal --> <!-- Preview Modal -->
@ -134,7 +134,7 @@
</div> </div>
</div> </div>
<div class="tab-container" th:data-title="#{sign.text}"> <div class="tab-container" th:title="#{sign.text}" th:data-title="#{sign.text}">
<label class="form-check-label" for="sigText" th:text="#{text}"></label> <label class="form-check-label" for="sigText" th:text="#{text}"></label>
<textarea class="form-control" id="sigText" name="sigText" rows="3"></textarea> <textarea class="form-control" id="sigText" name="sigText" rows="3"></textarea>
<label th:text="#{font}"></label> <label th:text="#{font}"></label>