Compare commits
14 Commits
stirling-p
...
v0.32.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f92aa90ef | ||
|
|
ba8dd04086 | ||
|
|
c13509cf67 | ||
|
|
0ab02e6ceb | ||
|
|
af52652aee | ||
|
|
e534f022f5 | ||
|
|
84867a7ad7 | ||
|
|
e97cb9d49e | ||
|
|
1b0c1b6cff | ||
|
|
7eea7fb3cb | ||
|
|
c921b5d76f | ||
|
|
26ec0c5d77 | ||
|
|
404e31468e | ||
|
|
0c0f61aa0d |
@@ -210,7 +210,7 @@ Stirling-PDF currently supports 36 languages!
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Thai (ไทย) (th_TH) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
|
||||
@@ -22,7 +22,7 @@ ext {
|
||||
}
|
||||
|
||||
group = "stirling.software"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
|
||||
java {
|
||||
// 17 is lowest but we support and recommend 21
|
||||
@@ -78,7 +78,7 @@ launch4j {
|
||||
|
||||
errTitle="Encountered error, Do you have Java 21?"
|
||||
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
||||
variables=["BROWSER_OPEN=true", "ENDPOINTS_GROUPS_TO_REMOVE=CLI"]
|
||||
variables=["BROWSER_OPEN=true"]
|
||||
jreMinVersion="17"
|
||||
|
||||
mutexName="Stirling-PDF"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 0.31.1
|
||||
appVersion: 0.32.0
|
||||
description: locally hosted web application that allows you to perform various operations
|
||||
on PDF files
|
||||
home: https://github.com/Stirling-Tools/Stirling-PDF
|
||||
@@ -13,4 +13,4 @@ maintainers:
|
||||
name: stirling-pdf-chart
|
||||
sources:
|
||||
- https://github.com/Stirling-Tools/Stirling-PDF
|
||||
version: 1.0.1
|
||||
version: 1.1.0
|
||||
|
||||
@@ -191,6 +191,7 @@ public class EndpointConfiguration {
|
||||
// LibreOffice
|
||||
addEndpointToGroup("LibreOffice", "repair");
|
||||
addEndpointToGroup("LibreOffice", "file-to-pdf");
|
||||
addEndpointToGroup("Unoconv", "file-to-pdf");
|
||||
addEndpointToGroup("LibreOffice", "xlsx-to-pdf");
|
||||
addEndpointToGroup("LibreOffice", "pdf-to-word");
|
||||
addEndpointToGroup("LibreOffice", "pdf-to-presentation");
|
||||
|
||||
@@ -42,6 +42,7 @@ public class ExternalAppDepConfig {
|
||||
put("ocrmypdf", List.of("OCRmyPDF"));
|
||||
put("weasyprint", List.of("Weasyprint"));
|
||||
put("pdftohtml", List.of("Pdftohtml"));
|
||||
put("unoconv", List.of("Unoconv"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -101,6 +102,7 @@ public class ExternalAppDepConfig {
|
||||
checkDependencyAndDisableGroup("ocrmypdf");
|
||||
checkDependencyAndDisableGroup("weasyprint");
|
||||
checkDependencyAndDisableGroup("pdftohtml");
|
||||
checkDependencyAndDisableGroup("unoconv");
|
||||
|
||||
// Special handling for Python/OpenCV dependencies
|
||||
boolean pythonAvailable = isCommandAvailable("python3") || isCommandAvailable("python");
|
||||
|
||||
@@ -156,10 +156,14 @@ public class SecurityConfiguration {
|
||||
http.rememberMe(
|
||||
rememberMeConfigurer ->
|
||||
rememberMeConfigurer // Use the configurator directly
|
||||
.key("uniqueAndSecret")
|
||||
.tokenRepository(persistentTokenRepository())
|
||||
.tokenValiditySeconds(1209600) // 2 weeks
|
||||
);
|
||||
.tokenValiditySeconds(14 * 24 * 60 * 60) // 14 days
|
||||
.userDetailsService(
|
||||
userDetailsService) // Your existing UserDetailsService
|
||||
.useSecureCookie(true) // Enable secure cookie
|
||||
.rememberMeParameter("remember-me") // Form parameter name
|
||||
.rememberMeCookieName("remember-me") // Cookie name
|
||||
.alwaysRemember(false));
|
||||
http.authorizeHttpRequests(
|
||||
authz ->
|
||||
authz.requestMatchers(
|
||||
|
||||
@@ -47,6 +47,7 @@ public class ApplicationProperties {
|
||||
private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated();
|
||||
private EnterpriseEdition enterpriseEdition = new EnterpriseEdition();
|
||||
private AutoPipeline autoPipeline = new AutoPipeline();
|
||||
private ProcessExecutor processExecutor = new ProcessExecutor();
|
||||
|
||||
@Data
|
||||
public static class AutoPipeline {
|
||||
@@ -309,4 +310,98 @@ public class ApplicationProperties {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ProcessExecutor {
|
||||
private SessionLimit sessionLimit = new SessionLimit();
|
||||
private TimeoutMinutes timeoutMinutes = new TimeoutMinutes();
|
||||
|
||||
@Data
|
||||
public static class SessionLimit {
|
||||
private int libreOfficeSessionLimit;
|
||||
private int pdfToHtmlSessionLimit;
|
||||
private int ocrMyPdfSessionLimit;
|
||||
private int pythonOpenCvSessionLimit;
|
||||
private int ghostScriptSessionLimit;
|
||||
private int weasyPrintSessionLimit;
|
||||
private int installAppSessionLimit;
|
||||
private int calibreSessionLimit;
|
||||
|
||||
public int getLibreOfficeSessionLimit() {
|
||||
return libreOfficeSessionLimit > 0 ? libreOfficeSessionLimit : 1;
|
||||
}
|
||||
|
||||
public int getPdfToHtmlSessionLimit() {
|
||||
return pdfToHtmlSessionLimit > 0 ? pdfToHtmlSessionLimit : 1;
|
||||
}
|
||||
|
||||
public int getOcrMyPdfSessionLimit() {
|
||||
return ocrMyPdfSessionLimit > 0 ? ocrMyPdfSessionLimit : 2;
|
||||
}
|
||||
|
||||
public int getPythonOpenCvSessionLimit() {
|
||||
return pythonOpenCvSessionLimit > 0 ? pythonOpenCvSessionLimit : 8;
|
||||
}
|
||||
|
||||
public int getGhostScriptSessionLimit() {
|
||||
return ghostScriptSessionLimit > 0 ? ghostScriptSessionLimit : 16;
|
||||
}
|
||||
|
||||
public int getWeasyPrintSessionLimit() {
|
||||
return weasyPrintSessionLimit > 0 ? weasyPrintSessionLimit : 16;
|
||||
}
|
||||
|
||||
public int getInstallAppSessionLimit() {
|
||||
return installAppSessionLimit > 0 ? installAppSessionLimit : 1;
|
||||
}
|
||||
|
||||
public int getCalibreSessionLimit() {
|
||||
return calibreSessionLimit > 0 ? calibreSessionLimit : 1;
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class TimeoutMinutes {
|
||||
private long libreOfficeTimeoutMinutes;
|
||||
private long pdfToHtmlTimeoutMinutes;
|
||||
private long ocrMyPdfTimeoutMinutes;
|
||||
private long pythonOpenCvTimeoutMinutes;
|
||||
private long ghostScriptTimeoutMinutes;
|
||||
private long weasyPrintTimeoutMinutes;
|
||||
private long installAppTimeoutMinutes;
|
||||
private long calibreTimeoutMinutes;
|
||||
|
||||
public long getLibreOfficeTimeoutMinutes() {
|
||||
return libreOfficeTimeoutMinutes > 0 ? libreOfficeTimeoutMinutes : 30;
|
||||
}
|
||||
|
||||
public long getPdfToHtmlTimeoutMinutes() {
|
||||
return pdfToHtmlTimeoutMinutes > 0 ? pdfToHtmlTimeoutMinutes : 20;
|
||||
}
|
||||
|
||||
public long getOcrMyPdfTimeoutMinutes() {
|
||||
return ocrMyPdfTimeoutMinutes > 0 ? ocrMyPdfTimeoutMinutes : 30;
|
||||
}
|
||||
|
||||
public long getPythonOpenCvTimeoutMinutes() {
|
||||
return pythonOpenCvTimeoutMinutes > 0 ? pythonOpenCvTimeoutMinutes : 30;
|
||||
}
|
||||
|
||||
public long getGhostScriptTimeoutMinutes() {
|
||||
return ghostScriptTimeoutMinutes > 0 ? ghostScriptTimeoutMinutes : 30;
|
||||
}
|
||||
|
||||
public long getWeasyPrintTimeoutMinutes() {
|
||||
return weasyPrintTimeoutMinutes > 0 ? weasyPrintTimeoutMinutes : 30;
|
||||
}
|
||||
|
||||
public long getInstallAppTimeoutMinutes() {
|
||||
return installAppTimeoutMinutes > 0 ? installAppTimeoutMinutes : 60;
|
||||
}
|
||||
|
||||
public long getCalibreTimeoutMinutes() {
|
||||
return calibreTimeoutMinutes > 0 ? calibreTimeoutMinutes : 30;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Date;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
|
||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import stirling.software.SPDF.model.PersistentLogin;
|
||||
|
||||
@@ -13,6 +14,7 @@ public class JPATokenRepositoryImpl implements PersistentTokenRepository {
|
||||
@Autowired private PersistentLoginRepository persistentLoginRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void createNewToken(PersistentRememberMeToken token) {
|
||||
PersistentLogin newToken = new PersistentLogin();
|
||||
newToken.setSeries(token.getSeries());
|
||||
@@ -23,6 +25,7 @@ public class JPATokenRepositoryImpl implements PersistentTokenRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateToken(String series, String tokenValue, Date lastUsed) {
|
||||
PersistentLogin existingToken = persistentLoginRepository.findById(series).orElse(null);
|
||||
if (existingToken != null) {
|
||||
@@ -43,11 +46,11 @@ public class JPATokenRepositoryImpl implements PersistentTokenRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void removeUserTokens(String username) {
|
||||
for (PersistentLogin token : persistentLoginRepository.findAll()) {
|
||||
if (token.getUsername().equals(username)) {
|
||||
persistentLoginRepository.delete(token);
|
||||
}
|
||||
try {
|
||||
persistentLoginRepository.deleteByUsername(username);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,6 @@ import org.springframework.stereotype.Repository;
|
||||
import stirling.software.SPDF.model.PersistentLogin;
|
||||
|
||||
@Repository
|
||||
public interface PersistentLoginRepository extends JpaRepository<PersistentLogin, String> {}
|
||||
public interface PersistentLoginRepository extends JpaRepository<PersistentLogin, String> {
|
||||
void deleteByUsername(String username);
|
||||
}
|
||||
|
||||
@@ -18,10 +18,14 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.pixee.security.BoundedLineReader;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
public class ProcessExecutor {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProcessExecutor.class);
|
||||
|
||||
private static ApplicationProperties applicationProperties = new ApplicationProperties();
|
||||
|
||||
public enum Processes {
|
||||
LIBRE_OFFICE,
|
||||
PDFTOHTML,
|
||||
@@ -45,26 +49,90 @@ public class ProcessExecutor {
|
||||
key -> {
|
||||
int semaphoreLimit =
|
||||
switch (key) {
|
||||
case LIBRE_OFFICE -> 1;
|
||||
case PDFTOHTML -> 1;
|
||||
case OCR_MY_PDF -> 2;
|
||||
case PYTHON_OPENCV -> 8;
|
||||
case GHOSTSCRIPT -> 16;
|
||||
case WEASYPRINT -> 16;
|
||||
case INSTALL_APP -> 1;
|
||||
case CALIBRE -> 1;
|
||||
case LIBRE_OFFICE ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getSessionLimit()
|
||||
.getLibreOfficeSessionLimit();
|
||||
case PDFTOHTML ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getSessionLimit()
|
||||
.getPdfToHtmlSessionLimit();
|
||||
case OCR_MY_PDF ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getSessionLimit()
|
||||
.getOcrMyPdfSessionLimit();
|
||||
case PYTHON_OPENCV ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getSessionLimit()
|
||||
.getPythonOpenCvSessionLimit();
|
||||
case GHOSTSCRIPT ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getSessionLimit()
|
||||
.getGhostScriptSessionLimit();
|
||||
case WEASYPRINT ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getSessionLimit()
|
||||
.getWeasyPrintSessionLimit();
|
||||
case INSTALL_APP ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getSessionLimit()
|
||||
.getInstallAppSessionLimit();
|
||||
case CALIBRE ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getSessionLimit()
|
||||
.getCalibreSessionLimit();
|
||||
};
|
||||
|
||||
long timeoutMinutes =
|
||||
switch (key) {
|
||||
case LIBRE_OFFICE -> 30;
|
||||
case PDFTOHTML -> 20;
|
||||
case OCR_MY_PDF -> 30;
|
||||
case PYTHON_OPENCV -> 30;
|
||||
case GHOSTSCRIPT -> 30;
|
||||
case WEASYPRINT -> 30;
|
||||
case INSTALL_APP -> 60;
|
||||
case CALIBRE -> 30;
|
||||
case LIBRE_OFFICE ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getTimeoutMinutes()
|
||||
.getLibreOfficeTimeoutMinutes();
|
||||
case PDFTOHTML ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getTimeoutMinutes()
|
||||
.getPdfToHtmlTimeoutMinutes();
|
||||
case OCR_MY_PDF ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getTimeoutMinutes()
|
||||
.getOcrMyPdfTimeoutMinutes();
|
||||
case PYTHON_OPENCV ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getTimeoutMinutes()
|
||||
.getPythonOpenCvTimeoutMinutes();
|
||||
case GHOSTSCRIPT ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getTimeoutMinutes()
|
||||
.getGhostScriptTimeoutMinutes();
|
||||
case WEASYPRINT ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getTimeoutMinutes()
|
||||
.getWeasyPrintTimeoutMinutes();
|
||||
case INSTALL_APP ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getTimeoutMinutes()
|
||||
.getInstallAppTimeoutMinutes();
|
||||
case CALIBRE ->
|
||||
applicationProperties
|
||||
.getProcessExecutor()
|
||||
.getTimeoutMinutes()
|
||||
.getCalibreTimeoutMinutes();
|
||||
};
|
||||
return new ProcessExecutor(semaphoreLimit, liveUpdates, timeoutMinutes);
|
||||
});
|
||||
|
||||
@@ -50,4 +50,4 @@ springdoc.swagger-ui.url=/v1/api-docs
|
||||
|
||||
|
||||
posthog.api.key=phc_fiR65u5j6qmXTYL56MNrLZSWqLaDW74OrZH0Insd2xq
|
||||
posthog.host=https://eu.i.posthog.com
|
||||
posthog.host=https://eu.i.posthog.com
|
||||
|
||||
@@ -527,7 +527,7 @@ replace-color.selectText.8=Zlutý text na černém pozadí
|
||||
replace-color.selectText.9=Zelený text na černém pozadí
|
||||
replace-color.selectText.10=Vyberte barvu textu
|
||||
replace-color.selectText.11=Vyberte barvu pozadí
|
||||
replace-color.submit= Nahradit
|
||||
replace-color.submit=Nahradit
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ pipelineOptions.validateButton=Validér
|
||||
enterpriseEdition.button=Opgrader til Pro
|
||||
enterpriseEdition.warning=Denne funktion er kun tilgængelig for Pro-brugere.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro understøtter YAML-konfigurationsfiler og andre SSO-funktioner.
|
||||
enterpriseEdition.ssoAdvert= søger du flere funktioner til brugerstyring? Prøv Stirling PDF Pro
|
||||
enterpriseEdition.ssoAdvert=søger du flere funktioner til brugerstyring? Prøv Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
|
||||
@@ -97,7 +97,7 @@ pipeline.configureButton=Configurar
|
||||
pipeline.defaultOption=Personalizar
|
||||
pipeline.submitButton=Submeter
|
||||
pipeline.help=Pipeline Help
|
||||
pipeline.scanHelp= Ajuda ao Escaneamento de Pastas
|
||||
pipeline.scanHelp=Ajuda ao Escaneamento de Pastas
|
||||
pipeline.deletePrompt=Tem a certeza que quer eliminar o pipeline?
|
||||
|
||||
######################
|
||||
@@ -191,7 +191,7 @@ account.changePassword=Alterar a Senha
|
||||
account.confirmNewPassword=Confirmar Nova Senha
|
||||
account.signOut=Sair do Sistema
|
||||
account.yourApiKey=Sua Chave API
|
||||
account.syncTitle= sincronizar definições do navegador com a conta
|
||||
account.syncTitle=sincronizar definições do navegador com a conta
|
||||
account.settingsCompare=Comparação das Definições:
|
||||
account.property=Propriedade
|
||||
account.webBrowserSettings=Configurações do Navegador Web
|
||||
|
||||
@@ -359,7 +359,7 @@ PDFToHTML.tags=web content,browser friendly
|
||||
|
||||
home.PDFToXML.title=PDF в XML
|
||||
home.PDFToXML.desc=Преобразование PDF в формат XML
|
||||
PDFToXML.tags= extraksi data,структурированный контент,interop,преобразование,конвертация
|
||||
PDFToXML.tags=extraksi data,структурированный контент,interop,преобразование,конвертация
|
||||
|
||||
home.ScannerImageSplit.title=Обнаружение/разделение отсканированных фотографий
|
||||
home.ScannerImageSplit.desc=Разделяет несколько фотографий из фото/PDF
|
||||
|
||||
@@ -79,8 +79,8 @@ info=資訊
|
||||
pro=專業版
|
||||
page=頁面
|
||||
pages=頁面
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
loading=載入中...
|
||||
addToDoc=新增至文件
|
||||
|
||||
legal.privacy=隱私權政策
|
||||
legal.terms=使用條款
|
||||
@@ -140,7 +140,7 @@ navbar.darkmode=深色模式
|
||||
navbar.language=語言
|
||||
navbar.settings=設定
|
||||
navbar.allTools=工具
|
||||
navbar.multiTool=多功能工具
|
||||
navbar.multiTool=複合工具
|
||||
navbar.sections.organize=整理
|
||||
navbar.sections.convertTo=轉換為 PDF
|
||||
navbar.sections.convertFrom=從 PDF 轉換
|
||||
@@ -246,7 +246,7 @@ database.fileNullOrEmpty=檔案不得為空或空白
|
||||
database.failedImportFile=匯入檔案失敗
|
||||
|
||||
session.expired=您的工作階段已過期。請重新整理頁面並再試一次。
|
||||
session.refreshPage=Refresh Page
|
||||
session.refreshPage=重新整理頁面
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
@@ -751,7 +751,7 @@ certSign.showSig=顯示簽章
|
||||
certSign.reason=原因
|
||||
certSign.location=位置
|
||||
certSign.name=名稱
|
||||
certSign.showLogo=Show Logo
|
||||
certSign.showLogo=顯示 Logo
|
||||
certSign.submit=簽章 PDF
|
||||
|
||||
|
||||
@@ -786,9 +786,9 @@ compare.highlightColor.2=標示顏色 2:
|
||||
compare.document.1=文件 1
|
||||
compare.document.2=文件 2
|
||||
compare.submit=比較
|
||||
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced
|
||||
compare.large.file.message=One or Both of the provided documents are too large to process
|
||||
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.
|
||||
compare.complex.message=選擇的檔案大小太大(其中一個或兩者皆是),可能會影響比較的精確度
|
||||
compare.large.file.message=選擇的檔案大小超出系統限制(其中一個或兩者皆是),無法處理
|
||||
compare.no.text.message=選擇的 PDF 檔案未包含文字(其中一個或兩者皆是)。請選擇含有文字的 PDF 進行比較
|
||||
|
||||
#BookToPDF
|
||||
BookToPDF.title=電子書和漫畫轉 PDF
|
||||
@@ -805,17 +805,17 @@ PDFToBook.submit=轉換
|
||||
|
||||
#sign
|
||||
sign.title=簽章
|
||||
sign.header=簽章 PDF
|
||||
sign.header=簽署 PDF
|
||||
sign.upload=上傳影像
|
||||
sign.draw=繪製簽章
|
||||
sign.text=文字輸入
|
||||
sign.clear=清除
|
||||
sign.add=新增
|
||||
sign.saved=Saved Signatures
|
||||
sign.save=Save Signature
|
||||
sign.personalSigs=Personal Signatures
|
||||
sign.sharedSigs=Shared Signatures
|
||||
sign.noSavedSigs=No saved signatures found
|
||||
sign.saved=已儲存的簽章
|
||||
sign.save=儲存簽章
|
||||
sign.personalSigs=個人簽章
|
||||
sign.sharedSigs=共用簽章
|
||||
sign.noSavedSigs=尚未儲存任何簽章
|
||||
|
||||
|
||||
#repair
|
||||
|
||||
@@ -102,3 +102,22 @@ metrics:
|
||||
AutomaticallyGenerated:
|
||||
key: example
|
||||
UUID: example
|
||||
|
||||
processExecutor:
|
||||
sessionLimit: # Process executor instances limits
|
||||
libreOfficeSessionLimit: 1
|
||||
pdfToHtmlSessionLimit: 1
|
||||
ocrMyPdfSessionLimit: 2
|
||||
pythonOpenCvSessionLimit: 8
|
||||
ghostScriptSessionLimit: 16
|
||||
weasyPrintSessionLimit: 16
|
||||
installAppSessionLimit: 1
|
||||
calibreSessionLimit: 1
|
||||
timeoutMinutes: # Process executor timeout in minutes
|
||||
libreOfficetimeoutMinutes: 30
|
||||
pdfToHtmltimeoutMinutes: 20
|
||||
pythonOpenCvtimeoutMinutes: 30
|
||||
ghostScripttimeoutMinutes: 30
|
||||
weasyPrinttimeoutMinutes: 30
|
||||
installApptimeoutMinutes: 60
|
||||
calibretimeoutMinutes: 30
|
||||
|
||||
@@ -126,3 +126,12 @@ html[dir="rtl"] .pdf-actions_container:last-child>.pdf-actions_insert-file-butto
|
||||
aspect-ratio: 1;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.pdf-actions_insert-file-blank-button {
|
||||
position: absolute;
|
||||
top: 75%;
|
||||
right: 50%;
|
||||
translate: 0% -50%;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
@@ -83,14 +83,16 @@ function syncFavorites() {
|
||||
cards.forEach(card => {
|
||||
const isFavorite = localStorage.getItem(card.id) === "favorite";
|
||||
const starIcon = card.querySelector(".favorite-icon span.material-symbols-rounded");
|
||||
if (isFavorite) {
|
||||
starIcon.classList.remove("no-fill");
|
||||
starIcon.classList.add("fill");
|
||||
card.classList.add("favorite");
|
||||
} else {
|
||||
starIcon.classList.remove("fill");
|
||||
starIcon.classList.add("no-fill");
|
||||
card.classList.remove("favorite");
|
||||
if (starIcon) {
|
||||
if (isFavorite) {
|
||||
starIcon.classList.remove("no-fill");
|
||||
starIcon.classList.add("fill");
|
||||
card.classList.add("favorite");
|
||||
} else {
|
||||
starIcon.classList.remove("fill");
|
||||
starIcon.classList.add("no-fill");
|
||||
card.classList.remove("favorite");
|
||||
}
|
||||
}
|
||||
});
|
||||
updateFavoritesSection();
|
||||
@@ -260,4 +262,4 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
}, 500);
|
||||
|
||||
showFavoritesOnly();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,27 +21,55 @@ async function displayFiles(files) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const pageCount = await getPDFPageCount(files[i]);
|
||||
const pageLabel = pageCount === 1 ? pageTranslation : pagesTranslation;
|
||||
|
||||
// Create list item
|
||||
const item = document.createElement("li");
|
||||
item.className = "list-group-item";
|
||||
item.innerHTML = `
|
||||
<div class="d-flex justify-content-between align-items-center w-100">
|
||||
<div class="filename">${files[i].name}</div>
|
||||
<div class="page-info">
|
||||
<span class="page-count">${pageCount} ${pageLabel}</span>
|
||||
</div>
|
||||
<div class="arrows d-flex">
|
||||
<button class="btn btn-secondary move-up"><span>↑</span></button>
|
||||
<button class="btn btn-secondary move-down"><span>↓</span></button>
|
||||
<button class="btn btn-danger remove-file"><span>×</span></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Create filename div and set textContent to sanitize
|
||||
const fileNameDiv = document.createElement("div");
|
||||
fileNameDiv.className = "filename";
|
||||
fileNameDiv.textContent = files[i].name;
|
||||
|
||||
// Create page info div and set textContent to sanitize
|
||||
const pageInfoDiv = document.createElement("div");
|
||||
pageInfoDiv.className = "page-info";
|
||||
const pageCountSpan = document.createElement("span");
|
||||
pageCountSpan.className = "page-count";
|
||||
pageCountSpan.textContent = `${pageCount} ${pageLabel}`;
|
||||
pageInfoDiv.appendChild(pageCountSpan);
|
||||
|
||||
// Create arrows div with buttons
|
||||
const arrowsDiv = document.createElement("div");
|
||||
arrowsDiv.className = "arrows d-flex";
|
||||
|
||||
const moveUpButton = document.createElement("button");
|
||||
moveUpButton.className = "btn btn-secondary move-up";
|
||||
moveUpButton.innerHTML = "<span>↑</span>";
|
||||
|
||||
const moveDownButton = document.createElement("button");
|
||||
moveDownButton.className = "btn btn-secondary move-down";
|
||||
moveDownButton.innerHTML = "<span>↓</span>";
|
||||
|
||||
const removeButton = document.createElement("button");
|
||||
removeButton.className = "btn btn-danger remove-file";
|
||||
removeButton.innerHTML = "<span>×</span>";
|
||||
|
||||
arrowsDiv.append(moveUpButton, moveDownButton, removeButton);
|
||||
|
||||
// Append elements to item and then to list
|
||||
const itemContainer = document.createElement("div");
|
||||
itemContainer.className = "d-flex justify-content-between align-items-center w-100";
|
||||
itemContainer.append(fileNameDiv, pageInfoDiv, arrowsDiv);
|
||||
|
||||
item.appendChild(itemContainer);
|
||||
list.appendChild(item);
|
||||
}
|
||||
|
||||
attachMoveButtons();
|
||||
}
|
||||
|
||||
|
||||
async function getPDFPageCount(file) {
|
||||
const blobUrl = URL.createObjectURL(file);
|
||||
const pdf = await pdfjsLib.getDocument(blobUrl).promise;
|
||||
|
||||
@@ -73,6 +73,11 @@ class PdfActionsManager {
|
||||
this.addFiles(imgContainer);
|
||||
}
|
||||
|
||||
insertFileBlankButtonCallback(e) {
|
||||
var imgContainer = this.getPageContainer(e.target);
|
||||
this.addFiles(imgContainer, true);
|
||||
}
|
||||
|
||||
splitFileButtonCallback(e) {
|
||||
var imgContainer = this.getPageContainer(e.target);
|
||||
imgContainer.classList.toggle("split-before");
|
||||
@@ -89,6 +94,7 @@ class PdfActionsManager {
|
||||
this.rotateCWButtonCallback = this.rotateCWButtonCallback.bind(this);
|
||||
this.deletePageButtonCallback = this.deletePageButtonCallback.bind(this);
|
||||
this.insertFileButtonCallback = this.insertFileButtonCallback.bind(this);
|
||||
this.insertFileBlankButtonCallback = this.insertFileBlankButtonCallback.bind(this);
|
||||
this.splitFileButtonCallback = this.splitFileButtonCallback.bind(this);
|
||||
}
|
||||
|
||||
@@ -152,6 +158,12 @@ class PdfActionsManager {
|
||||
splitFileButton.onclick = this.splitFileButtonCallback;
|
||||
insertFileButtonContainer.appendChild(splitFileButton);
|
||||
|
||||
const insertFileBlankButton = document.createElement("button");
|
||||
insertFileBlankButton.classList.add("btn", "btn-primary", "pdf-actions_insert-file-blank-button");
|
||||
insertFileBlankButton.innerHTML = `<span class="material-symbols-rounded">insert_page_break</span>`;
|
||||
insertFileBlankButton.onclick = this.insertFileBlankButtonCallback;
|
||||
insertFileButtonContainer.appendChild(insertFileBlankButton);
|
||||
|
||||
div.appendChild(insertFileButtonContainer);
|
||||
|
||||
// add this button to every element, but only show it on the last one :D
|
||||
|
||||
@@ -22,6 +22,7 @@ class PdfContainer {
|
||||
this.nameAndArchiveFiles = this.nameAndArchiveFiles.bind(this);
|
||||
this.splitPDF = this.splitPDF.bind(this);
|
||||
this.splitAll = this.splitAll.bind(this);
|
||||
this.addFilesBlankAll = this.addFilesBlankAll.bind(this)
|
||||
|
||||
this.pdfAdapters = pdfAdapters;
|
||||
|
||||
@@ -38,6 +39,7 @@ class PdfContainer {
|
||||
window.exportPdf = this.exportPdf;
|
||||
window.rotateAll = this.rotateAll;
|
||||
window.splitAll = this.splitAll;
|
||||
window.addFilesBlankAll = this.addFilesBlankAll
|
||||
|
||||
const filenameInput = document.getElementById("filename-input");
|
||||
const downloadBtn = document.getElementById("export-button");
|
||||
@@ -77,19 +79,25 @@ class PdfContainer {
|
||||
}
|
||||
}
|
||||
|
||||
addFiles(nextSiblingElement) {
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.multiple = true;
|
||||
input.setAttribute("accept", "application/pdf,image/*");
|
||||
input.onchange = async (e) => {
|
||||
const files = e.target.files;
|
||||
addFiles(nextSiblingElement, blank = false) {
|
||||
if (blank) {
|
||||
|
||||
this.addFilesFromFiles(files, nextSiblingElement);
|
||||
this.updateFilename(files ? files[0].name : "");
|
||||
};
|
||||
this.addFilesBlank(nextSiblingElement);
|
||||
|
||||
input.click();
|
||||
} else {
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.multiple = true;
|
||||
input.setAttribute("accept", "application/pdf,image/*");
|
||||
input.onchange = async (e) => {
|
||||
const files = e.target.files;
|
||||
|
||||
this.addFilesFromFiles(files, nextSiblingElement);
|
||||
this.updateFilename(files ? files[0].name : "");
|
||||
};
|
||||
|
||||
input.click();
|
||||
}
|
||||
}
|
||||
|
||||
async addFilesFromFiles(files, nextSiblingElement) {
|
||||
@@ -108,6 +116,47 @@ class PdfContainer {
|
||||
});
|
||||
}
|
||||
|
||||
async addFilesBlank(nextSiblingElement) {
|
||||
const pdfContent = `
|
||||
%PDF-1.4
|
||||
1 0 obj
|
||||
<< /Type /Catalog /Pages 2 0 R >>
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
|
||||
endobj
|
||||
3 0 obj
|
||||
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Contents 5 0 R >>
|
||||
endobj
|
||||
5 0 obj
|
||||
<< /Length 44 >>
|
||||
stream
|
||||
0 0 0 595 0 842 re
|
||||
W
|
||||
n
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 6
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000071 00000 n
|
||||
0000000121 00000 n
|
||||
0000000205 00000 n
|
||||
0000000400 00000 n
|
||||
trailer
|
||||
<< /Size 6 /Root 1 0 R >>
|
||||
startxref
|
||||
278
|
||||
%%EOF
|
||||
`;
|
||||
const blob = new Blob([pdfContent], { type: 'application/pdf' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const file = new File([blob], "blank_page.pdf", { type: "application/pdf" });
|
||||
await this.addPdfFile(file, nextSiblingElement);
|
||||
}
|
||||
|
||||
|
||||
rotateElement(element, deg) {
|
||||
var lastTransform = element.style.rotate;
|
||||
if (!lastTransform) {
|
||||
@@ -224,6 +273,17 @@ class PdfContainer {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
addFilesBlankAll() {
|
||||
const allPages = this.pagesContainer.querySelectorAll(".page-container");
|
||||
allPages.forEach((page, index) => {
|
||||
if (index !== 0) {
|
||||
this.addFiles(page, true)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
splitAll() {
|
||||
const allPages = this.pagesContainer.querySelectorAll(".page-container");
|
||||
if (this.pagesContainer.querySelectorAll(".split-before").length > 0) {
|
||||
@@ -450,7 +510,7 @@ function detectImageType(uint8Array) {
|
||||
|
||||
// Check for TIFF signature (little-endian and big-endian)
|
||||
if ((uint8Array[0] === 73 && uint8Array[1] === 73 && uint8Array[2] === 42 && uint8Array[3] === 0) ||
|
||||
(uint8Array[0] === 77 && uint8Array[1] === 77 && uint8Array[2] === 0 && uint8Array[3] === 42)) {
|
||||
(uint8Array[0] === 77 && uint8Array[1] === 77 && uint8Array[2] === 0 && uint8Array[3] === 42)) {
|
||||
return 'TIFF';
|
||||
}
|
||||
|
||||
|
||||
@@ -70,3 +70,37 @@ document.querySelector("#navbarSearchInput").addEventListener("input", function
|
||||
resultsBox.style.width = window.navItemMaxWidth + "px";
|
||||
});
|
||||
|
||||
const searchDropdown = document.getElementById('searchDropdown');
|
||||
const searchInput = document.getElementById('navbarSearchInput');
|
||||
const dropdownMenu = searchDropdown.querySelector('.dropdown-menu');
|
||||
|
||||
// Handle dropdown shown event
|
||||
searchDropdown.addEventListener('shown.bs.dropdown', function () {
|
||||
searchInput.focus();
|
||||
});
|
||||
|
||||
// Handle hover opening
|
||||
searchDropdown.addEventListener('mouseenter', function () {
|
||||
const dropdownInstance = new bootstrap.Dropdown(searchDropdown);
|
||||
dropdownInstance.show();
|
||||
|
||||
setTimeout(() => {
|
||||
searchInput.focus();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Handle mouse leave
|
||||
searchDropdown.addEventListener('mouseleave', function () {
|
||||
// Check if current value is empty (including if user typed and then deleted)
|
||||
if (searchInput.value.trim().length === 0) {
|
||||
searchInput.blur();
|
||||
const dropdownInstance = new bootstrap.Dropdown(searchDropdown);
|
||||
dropdownInstance.hide();
|
||||
}
|
||||
});
|
||||
|
||||
searchDropdown.addEventListener('hidden.bs.dropdown', function () {
|
||||
if (searchInput.value.trim().length === 0) {
|
||||
searchInput.blur();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -90,8 +90,8 @@
|
||||
</div>
|
||||
|
||||
<div class="form-check m-2 mb-3">
|
||||
<input type="checkbox" id="remember" value="remember-me">
|
||||
<label for="remember" th:text="#{login.rememberme}"></label>
|
||||
<input type="checkbox" name="remember-me" id="remember-me">
|
||||
<label for="remember-me" th:text="#{login.rememberme}"></label>
|
||||
</div>
|
||||
<button class="w-100 btn btn-lg btn-primary" type="submit" th:text="#{login.signin}">Sign in</button>
|
||||
</form>
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
cut
|
||||
</span>
|
||||
</button>
|
||||
<button class="btn btn-secondary enable-on-file" onclick="addFilesBlankAll()" disabled>
|
||||
<span class="material-symbols-rounded">
|
||||
insert_page_break
|
||||
</span>
|
||||
</button>
|
||||
<button id="export-button" class="btn btn-primary enable-on-file" onclick="exportPdf()" disabled>
|
||||
<span class="material-symbols-rounded">
|
||||
download
|
||||
|
||||
Reference in New Issue
Block a user