mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2024-11-04 15:00:14 +01:00
commit
09a0779180
2
.gitignore
vendored
2
.gitignore
vendored
@ -19,7 +19,7 @@ pipeline/
|
||||
|
||||
#### Stirling-PDF Files ###
|
||||
customFiles/
|
||||
config/
|
||||
configs/
|
||||
watchedFolders/
|
||||
|
||||
|
||||
|
@ -208,7 +208,11 @@ For API usage you must provide a header with 'X-API-Key' and the associated API
|
||||
- Progress bar/Tracking
|
||||
- Full custom logic pipelines to combine multiple operations together.
|
||||
- Folder support with auto scanning to perform operations on
|
||||
- Redact sections of pages
|
||||
- Redact text (Via UI)
|
||||
- Add Forms
|
||||
- Annotations
|
||||
- Multi page layout (Stich PDF pages together) support x rows y columns and custom page sizing
|
||||
- Fill forms mannual and automatic
|
||||
|
||||
### Q2: Why is my application downloading .htm files?
|
||||
This is a issue caused commonly by your NGINX congifuration. The default file upload size for NGINX is 1MB, you need to add the following in your Nginx sites-available file. client_max_body_size SIZE; Where "SIZE" is 50M for example for 50MB files.
|
||||
|
@ -4,6 +4,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
@ -14,6 +15,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
import stirling.software.SPDF.config.ConfigInitializer;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
@SpringBootApplication
|
||||
@EnableWebSecurity()
|
||||
@ -51,7 +53,15 @@ public class SPdfApplication {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SPdfApplication.class, args);
|
||||
SpringApplication app = new SpringApplication(SPdfApplication.class);
|
||||
app.addInitializers(new ConfigInitializer());
|
||||
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
||||
app.setDefaultProperties(Collections.singletonMap("spring.config.location", "file:configs/settings.yml"));
|
||||
} else {
|
||||
System.out.println("External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
|
||||
}
|
||||
app.run(args);
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
|
@ -1,37 +1,30 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import stirling.software.SPDF.utils.PropertyConfigs;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
|
||||
|
||||
@Bean(name = "rateLimit")
|
||||
public boolean rateLimit() {
|
||||
String appName = System.getProperty("rateLimit");
|
||||
if (appName == null)
|
||||
appName = System.getenv("rateLimit");
|
||||
System.out.println("rateLimit=" + appName);
|
||||
return (appName != null) ? Boolean.valueOf(appName) : false;
|
||||
}
|
||||
@Autowired
|
||||
ApplicationProperties applicationProperties;
|
||||
|
||||
@Bean(name = "loginEnabled")
|
||||
public boolean loginEnabled() {
|
||||
String appName = System.getProperty("login.enabled");
|
||||
if (appName == null)
|
||||
appName = System.getenv("login.enabled");
|
||||
System.out.println("loginEnabled=" + appName);
|
||||
return (appName != null) ? Boolean.valueOf(appName) : false;
|
||||
System.out.println(applicationProperties.toString());
|
||||
return applicationProperties.getSecurity().getEnableLogin();
|
||||
}
|
||||
|
||||
@Bean(name = "appName")
|
||||
public String appName() {
|
||||
String appName = System.getProperty("APP_HOME_NAME");
|
||||
if (appName == null)
|
||||
appName = System.getenv("APP_HOME_NAME");
|
||||
return (appName != null) ? appName : "Stirling PDF";
|
||||
return applicationProperties.getUi().getHomeName();
|
||||
}
|
||||
|
||||
@Bean(name = "appVersion")
|
||||
@ -42,22 +35,24 @@ public class AppConfig {
|
||||
|
||||
@Bean(name = "homeText")
|
||||
public String homeText() {
|
||||
String homeText = System.getProperty("APP_HOME_DESCRIPTION");
|
||||
if (homeText == null)
|
||||
homeText = System.getenv("APP_HOME_DESCRIPTION");
|
||||
return (homeText != null) ? homeText : "null";
|
||||
return applicationProperties.getUi().getHomeDescription();
|
||||
}
|
||||
|
||||
|
||||
@Bean(name = "navBarText")
|
||||
public String navBarText() {
|
||||
String navBarText = System.getProperty("APP_NAVBAR_NAME");
|
||||
if (navBarText == null)
|
||||
navBarText = System.getenv("APP_NAVBAR_NAME");
|
||||
if (navBarText == null)
|
||||
navBarText = System.getProperty("APP_HOME_NAME");
|
||||
if (navBarText == null)
|
||||
navBarText = System.getenv("APP_HOME_NAME");
|
||||
|
||||
return (navBarText != null) ? navBarText : "Stirling PDF";
|
||||
String defaultNavBar = applicationProperties.getUi().getNavbarName() != null ? applicationProperties.getUi().getNavbarName() : applicationProperties.getUi().getHomeName();
|
||||
return defaultNavBar;
|
||||
}
|
||||
|
||||
@Bean(name = "rateLimit")
|
||||
public boolean rateLimit() {
|
||||
String appName = System.getProperty("rateLimit");
|
||||
if (appName == null)
|
||||
appName = System.getenv("rateLimit");
|
||||
System.out.println("rateLimit=" + appName);
|
||||
return (appName != null) ? Boolean.valueOf(appName) : false;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -3,6 +3,7 @@ package stirling.software.SPDF.config;
|
||||
import java.time.Duration;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
@ -15,10 +16,14 @@ import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Bucket;
|
||||
import io.github.bucket4j.Bucket4j;
|
||||
import io.github.bucket4j.Refill;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Configuration
|
||||
public class Beans implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
ApplicationProperties applicationProperties;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(localeChangeInterceptor());
|
||||
@ -36,9 +41,8 @@ public class Beans implements WebMvcConfigurer {
|
||||
public LocaleResolver localeResolver() {
|
||||
SessionLocaleResolver slr = new SessionLocaleResolver();
|
||||
|
||||
String appLocaleEnv = System.getProperty("APP_LOCALE");
|
||||
if (appLocaleEnv == null)
|
||||
appLocaleEnv = System.getenv("APP_LOCALE");
|
||||
|
||||
String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale();
|
||||
Locale defaultLocale = Locale.UK; // Fallback to UK locale if environment variable is not set
|
||||
|
||||
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) {
|
||||
|
@ -24,7 +24,7 @@ import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
public class CleanUrlInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final List<String> ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints", "logout", "error");
|
||||
private static final List<String> ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints", "logout", "error", "file");
|
||||
|
||||
|
||||
@Override
|
||||
@ -40,11 +40,9 @@ public class CleanUrlInterceptor implements HandlerInterceptor {
|
||||
String[] queryParameters = queryString.split("&");
|
||||
for (String param : queryParameters) {
|
||||
String[] keyValue = param.split("=");
|
||||
System.out.print("astirli " + keyValue[0]);
|
||||
if (keyValue.length != 2) {
|
||||
continue;
|
||||
}
|
||||
System.out.print("astirli2 " + keyValue[0]);
|
||||
|
||||
if (ALLOWED_PARAMS.contains(keyValue[0])) {
|
||||
parameters.put(keyValue[0], keyValue[1]);
|
||||
|
@ -0,0 +1,43 @@
|
||||
package stirling.software.SPDF.config;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
public class ConfigInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
try {
|
||||
ensureConfigExists();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to initialize application configuration", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void ensureConfigExists() throws IOException {
|
||||
// Define the path to the external config directory
|
||||
Path destPath = Paths.get("configs", "settings.yml");
|
||||
|
||||
// Check if the file already exists
|
||||
if (Files.notExists(destPath)) {
|
||||
// Ensure the destination directory exists
|
||||
Files.createDirectories(destPath.getParent());
|
||||
|
||||
// Copy the resource from classpath to the external directory
|
||||
try (InputStream in = getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {
|
||||
if (in != null) {
|
||||
Files.copy(in, destPath);
|
||||
} else {
|
||||
throw new FileNotFoundException("Resource file not found: settings.yml.template");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,20 +1,28 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
@Service
|
||||
public class EndpointConfiguration {
|
||||
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class);
|
||||
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
|
||||
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
|
||||
|
||||
public EndpointConfiguration() {
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
@Autowired
|
||||
public EndpointConfiguration(ApplicationProperties applicationProperties) {
|
||||
this.applicationProperties = applicationProperties;
|
||||
init();
|
||||
processEnvironmentConfigs();
|
||||
}
|
||||
@ -200,19 +208,17 @@ public class EndpointConfiguration {
|
||||
}
|
||||
|
||||
private void processEnvironmentConfigs() {
|
||||
String endpointsToRemove = System.getenv("ENDPOINTS_TO_REMOVE");
|
||||
String groupsToRemove = System.getenv("GROUPS_TO_REMOVE");
|
||||
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
|
||||
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
|
||||
|
||||
if (endpointsToRemove != null) {
|
||||
String[] endpoints = endpointsToRemove.split(",");
|
||||
for (String endpoint : endpoints) {
|
||||
for (String endpoint : endpointsToRemove) {
|
||||
disableEndpoint(endpoint.trim());
|
||||
}
|
||||
}
|
||||
|
||||
if (groupsToRemove != null) {
|
||||
String[] groups = groupsToRemove.split(",");
|
||||
for (String group : groups) {
|
||||
for (String group : groupsToRemove) {
|
||||
disableGroup(group.trim());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
import org.springframework.core.io.support.PropertySourceFactory;
|
||||
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
|
||||
import org.springframework.core.env.PropertiesPropertySource;
|
||||
public class YamlPropertySourceFactory implements PropertySourceFactory {
|
||||
|
||||
@Override
|
||||
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource)
|
||||
throws IOException {
|
||||
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
|
||||
factory.setResources(encodedResource.getResource());
|
||||
|
||||
Properties properties = factory.getObject();
|
||||
|
||||
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
|
||||
}
|
||||
}
|
@ -1,26 +1,82 @@
|
||||
package stirling.software.SPDF.config.security;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.Role;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
@Component
|
||||
public class InitialSetup {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
ApplicationProperties applicationProperties;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if(!userService.hasUsers()) {
|
||||
String initialUsername = System.getenv("INITIAL_USERNAME");
|
||||
String initialPassword = System.getenv("INITIAL_PASSWORD");
|
||||
if(initialUsername != null && initialPassword != null) {
|
||||
if (!userService.hasUsers()) {
|
||||
String initialUsername = applicationProperties.getSecurity().getInitialLogin().getUsername();
|
||||
String initialPassword = applicationProperties.getSecurity().getInitialLogin().getPassword();
|
||||
if (initialUsername != null && initialPassword != null) {
|
||||
userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void initSecretKey() throws IOException {
|
||||
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
||||
if (secretKey == null || secretKey.isEmpty()) {
|
||||
secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
||||
saveKeyToConfig(secretKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveKeyToConfig(String key) throws IOException {
|
||||
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
|
||||
List<String> lines = Files.readAllLines(path);
|
||||
boolean keyFound = false;
|
||||
|
||||
// Search for the existing key to replace it or place to add it
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
if (lines.get(i).startsWith("AutomaticallyGenerated:")) {
|
||||
keyFound = true;
|
||||
if (i + 1 < lines.size() && lines.get(i + 1).trim().startsWith("key:")) {
|
||||
lines.set(i + 1, " key: " + key);
|
||||
break;
|
||||
} else {
|
||||
lines.add(i + 1, " key: " + key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the section doesn't exist, append it
|
||||
if (!keyFound) {
|
||||
lines.add("# Automatically Generated Settings (Do Not Edit Directly)");
|
||||
lines.add("AutomaticallyGenerated:");
|
||||
lines.add(" key: " + key);
|
||||
}
|
||||
|
||||
// Write back to the file
|
||||
Files.write(path, lines);
|
||||
}
|
||||
}
|
@ -15,7 +15,11 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||
@Configuration
|
||||
public class SecurityConfiguration {
|
||||
|
||||
@ -43,7 +47,7 @@ public class SecurityConfiguration {
|
||||
|
||||
if(loginEnabledValue) {
|
||||
|
||||
http.csrf().disable();
|
||||
http.csrf(csrf -> csrf.disable());
|
||||
http
|
||||
.formLogin(formLogin -> formLogin
|
||||
.loginPage("/login")
|
||||
@ -55,7 +59,11 @@ public class SecurityConfiguration {
|
||||
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
||||
.logoutSuccessUrl("/login?logout=true")
|
||||
.invalidateHttpSession(true) // Invalidate session
|
||||
.deleteCookies("JSESSIONID")
|
||||
.deleteCookies("JSESSIONID", "remember-me")
|
||||
).rememberMe(rememberMeConfigurer -> rememberMeConfigurer // Use the configurator directly
|
||||
.key("uniqueAndSecret")
|
||||
.tokenRepository(persistentTokenRepository())
|
||||
.tokenValiditySeconds(1209600) // 2 weeks
|
||||
)
|
||||
.authorizeHttpRequests(authz -> authz
|
||||
.requestMatchers(req -> req.getRequestURI().startsWith("/login") || req.getRequestURI().endsWith(".svg") || req.getRequestURI().startsWith("/register") || req.getRequestURI().startsWith("/error") || req.getRequestURI().startsWith("/images/") || req.getRequestURI().startsWith("/public/") || req.getRequestURI().startsWith("/css/") || req.getRequestURI().startsWith("/js/"))
|
||||
@ -65,8 +73,7 @@ public class SecurityConfiguration {
|
||||
.userDetailsService(userDetailsService)
|
||||
.authenticationProvider(authenticationProvider());
|
||||
} else {
|
||||
http
|
||||
.csrf().disable()
|
||||
http.csrf(csrf -> csrf.disable())
|
||||
.authorizeHttpRequests(authz -> authz
|
||||
.anyRequest().permitAll()
|
||||
);
|
||||
@ -84,6 +91,11 @@ public class SecurityConfiguration {
|
||||
return authProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PersistentTokenRepository persistentTokenRepository() {
|
||||
return new JPATokenRepositoryImpl();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.PipelineConfig;
|
||||
import stirling.software.SPDF.model.PipelineOperation;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@ -91,6 +92,10 @@ public class PipelineController {
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
ApplicationProperties applicationProperties;
|
||||
|
||||
|
||||
private void handleDirectory(Path dir) throws Exception {
|
||||
logger.info("Handling directory: {}", dir);
|
||||
Path jsonFile = dir.resolve(jsonFileName);
|
||||
@ -182,8 +187,7 @@ public class PipelineController {
|
||||
// {filename} {folder} {date} {tmime} {pipeline}
|
||||
String outputDir = config.getOutputDir();
|
||||
|
||||
// Check if the environment variable 'automatedOutputFolder' is set
|
||||
String outputFolder = System.getenv("automatedOutputFolder");
|
||||
String outputFolder = applicationProperties.getAutoPipeline().getOutputFolder();
|
||||
|
||||
if (outputFolder == null || outputFolder.isEmpty()) {
|
||||
// If the environment variable is not set, use the default value
|
||||
|
@ -0,0 +1,106 @@
|
||||
package stirling.software.SPDF.controller.api.security;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.apache.pdfbox.rendering.ImageType;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import stirling.software.SPDF.model.PDFText;
|
||||
import stirling.software.SPDF.pdf.TextFinder;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@RestController
|
||||
@Tag(name = "Security", description = "Security APIs")
|
||||
public class RedactController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RedactController.class);
|
||||
|
||||
|
||||
@PostMapping(value = "/auto-redact", consumes = "multipart/form-data")
|
||||
@Operation(summary = "Redacts listOfText in a PDF document",
|
||||
description = "This operation takes an input PDF file and redacts the provided listOfText. Input:PDF, Output:PDF, Type:SISO")
|
||||
public ResponseEntity<byte[]> redactPdf(
|
||||
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
||||
@Parameter(description = "List of listOfText to redact from the PDF", required = true, schema = @Schema(type = "string")) @RequestParam("listOfText") String listOfTextString,
|
||||
@RequestParam(value = "useRegex", required = false) boolean useRegex,
|
||||
@RequestParam(value = "wholeWordSearch", required = false) boolean wholeWordSearchBool,
|
||||
@RequestParam(value = "customPadding", required = false) float customPadding,
|
||||
@RequestParam(value = "convertPDFToImage", required = false) boolean convertPDFToImage) throws Exception {
|
||||
|
||||
System.out.println(listOfTextString);
|
||||
String[] listOfText = listOfTextString.split("\n");
|
||||
byte[] bytes = file.getBytes();
|
||||
PDDocument document = PDDocument.load(new ByteArrayInputStream(bytes));
|
||||
for (String text : listOfText) {
|
||||
text = text.trim();
|
||||
System.out.println(text);
|
||||
TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool);
|
||||
List<PDFText> foundTexts = textFinder.getTextLocations(document);
|
||||
redactFoundText(document, foundTexts, customPadding);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (convertPDFToImage) {
|
||||
PDDocument imageDocument = new PDDocument();
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
||||
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
|
||||
PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight()));
|
||||
imageDocument.addPage(newPage);
|
||||
PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim);
|
||||
PDPageContentStream contentStream = new PDPageContentStream(imageDocument, newPage);
|
||||
contentStream.drawImage(pdImage, 0, 0);
|
||||
contentStream.close();
|
||||
}
|
||||
document.close();
|
||||
document = imageDocument;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
document.close();
|
||||
|
||||
byte[] pdfContent = baos.toByteArray();
|
||||
return WebResponseUtils.bytesToWebResponse(pdfContent,
|
||||
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_redacted.pdf");
|
||||
}
|
||||
|
||||
|
||||
private void redactFoundText(PDDocument document, List<PDFText> blocks, float customPadding) throws IOException {
|
||||
var allPages = document.getDocumentCatalog().getPages();
|
||||
|
||||
for (PDFText block : blocks) {
|
||||
var page = allPages.get(block.getPageIndex());
|
||||
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||
contentStream.setNonStrokingColor(Color.BLACK);
|
||||
float padding = (block.getY2() - block.getY1()) * 0.3f + customPadding;
|
||||
PDRectangle pageBox = page.getBBox();
|
||||
contentStream.addRect(block.getX1(), pageBox.getHeight() - block.getY1() - padding, block.getX2() - block.getX1(), block.getY2() - block.getY1() + 2 * padding);
|
||||
contentStream.fill();
|
||||
contentStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package stirling.software.SPDF.controller.web;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
@ -7,6 +8,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Controller
|
||||
public class HomeWebController {
|
||||
@ -31,18 +33,16 @@ public class HomeWebController {
|
||||
return "redirect:/";
|
||||
}
|
||||
|
||||
@Autowired
|
||||
ApplicationProperties applicationProperties;
|
||||
|
||||
|
||||
@GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@ResponseBody
|
||||
@Hidden
|
||||
public String getRobotsTxt() {
|
||||
String allowGoogleVisibility = System.getProperty("ALLOW_GOOGLE_VISIBILITY");
|
||||
if (allowGoogleVisibility == null)
|
||||
allowGoogleVisibility = System.getenv("ALLOW_GOOGLE_VISIBILITY");
|
||||
if (allowGoogleVisibility == null)
|
||||
allowGoogleVisibility = "false";
|
||||
if (Boolean.parseBoolean(allowGoogleVisibility)) {
|
||||
Boolean allowGoogle = applicationProperties.getSystem().getGooglevisibility();
|
||||
if(Boolean.TRUE.equals(allowGoogle)) {
|
||||
return "User-agent: Googlebot\nAllow: /\n\nUser-agent: *\nAllow: /";
|
||||
} else {
|
||||
return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /";
|
||||
|
@ -8,6 +8,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -24,26 +25,28 @@ import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import stirling.software.SPDF.config.StartupApplicationListener;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1")
|
||||
@Tag(name = "API", description = "Info APIs")
|
||||
public class MetricsController {
|
||||
|
||||
|
||||
@Autowired
|
||||
ApplicationProperties applicationProperties;
|
||||
|
||||
|
||||
private final MeterRegistry meterRegistry;
|
||||
|
||||
private boolean isEndpointEnabled;
|
||||
private boolean metricsEnabled;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
String isEndpointEnabled = System.getProperty("ENABLE_API_METRICS");
|
||||
if (isEndpointEnabled == null) {
|
||||
isEndpointEnabled = System.getenv("ENABLE_API_METRICS");
|
||||
if (isEndpointEnabled == null) {
|
||||
isEndpointEnabled = "true";
|
||||
}
|
||||
}
|
||||
this.isEndpointEnabled = "true".equalsIgnoreCase(isEndpointEnabled);
|
||||
Boolean metricsEnabled = applicationProperties.getMetrics().getEnabled();
|
||||
if(metricsEnabled == null)
|
||||
metricsEnabled = true;
|
||||
this.metricsEnabled = metricsEnabled;
|
||||
}
|
||||
|
||||
public MetricsController(MeterRegistry meterRegistry) {
|
||||
@ -54,7 +57,7 @@ public class MetricsController {
|
||||
@Operation(summary = "Application status and version",
|
||||
description = "This endpoint returns the status of the application and its version number.")
|
||||
public ResponseEntity<?> getStatus() {
|
||||
if (!isEndpointEnabled) {
|
||||
if (!metricsEnabled) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||
}
|
||||
|
||||
@ -68,7 +71,7 @@ public class MetricsController {
|
||||
@Operation(summary = "GET request count",
|
||||
description = "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.")
|
||||
public ResponseEntity<?> getPageLoads(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
|
||||
if (!isEndpointEnabled) {
|
||||
if (!metricsEnabled) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||
}
|
||||
try {
|
||||
@ -109,7 +112,7 @@ public class MetricsController {
|
||||
@Operation(summary = "GET requests count for all endpoints",
|
||||
description = "This endpoint returns the count of GET requests for each endpoint.")
|
||||
public ResponseEntity<?> getAllEndpointLoads() {
|
||||
if (!isEndpointEnabled) {
|
||||
if (!metricsEnabled) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||
}
|
||||
try {
|
||||
@ -170,7 +173,7 @@ public class MetricsController {
|
||||
@Operation(summary = "POST request count",
|
||||
description = "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.")
|
||||
public ResponseEntity<?> getTotalRequests(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
|
||||
if (!isEndpointEnabled) {
|
||||
if (!metricsEnabled) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||
}
|
||||
try {
|
||||
@ -208,7 +211,7 @@ public class MetricsController {
|
||||
@Operation(summary = "POST requests count for all endpoints",
|
||||
description = "This endpoint returns the count of POST requests for each endpoint.")
|
||||
public ResponseEntity<?> getAllPostRequests() {
|
||||
if (!isEndpointEnabled) {
|
||||
if (!metricsEnabled) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||
}
|
||||
try {
|
||||
@ -244,7 +247,7 @@ public class MetricsController {
|
||||
|
||||
@GetMapping("/uptime")
|
||||
public ResponseEntity<?> getUptime() {
|
||||
if (!isEndpointEnabled) {
|
||||
if (!metricsEnabled) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,12 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@Tag(name = "Security", description = "Security APIs")
|
||||
public class SecurityWebController {
|
||||
|
||||
@GetMapping("/auto-redact")
|
||||
@Hidden
|
||||
public String autoRedactForm(Model model) {
|
||||
model.addAttribute("currentPage", "auto-redact");
|
||||
return "security/auto-redact";
|
||||
}
|
||||
|
||||
@GetMapping("/add-password")
|
||||
@Hidden
|
||||
|
@ -0,0 +1,333 @@
|
||||
package stirling.software.SPDF.model;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import stirling.software.SPDF.config.YamlPropertySourceFactory;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "")
|
||||
@PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class)
|
||||
public class ApplicationProperties {
|
||||
private Security security;
|
||||
private System system;
|
||||
private Ui ui;
|
||||
private Endpoints endpoints;
|
||||
private Metrics metrics;
|
||||
private AutomaticallyGenerated automaticallyGenerated;
|
||||
private AutoPipeline autoPipeline;
|
||||
|
||||
public AutoPipeline getAutoPipeline() {
|
||||
return autoPipeline != null ? autoPipeline : new AutoPipeline();
|
||||
}
|
||||
|
||||
public void setAutoPipeline(AutoPipeline autoPipeline) {
|
||||
this.autoPipeline = autoPipeline;
|
||||
}
|
||||
|
||||
public Security getSecurity() {
|
||||
return security != null ? security : new Security();
|
||||
}
|
||||
|
||||
public void setSecurity(Security security) {
|
||||
this.security = security;
|
||||
}
|
||||
|
||||
public System getSystem() {
|
||||
return system != null ? system : new System();
|
||||
}
|
||||
|
||||
public void setSystem(System system) {
|
||||
this.system = system;
|
||||
}
|
||||
|
||||
public Ui getUi() {
|
||||
return ui != null ? ui : new Ui();
|
||||
}
|
||||
|
||||
public void setUi(Ui ui) {
|
||||
this.ui = ui;
|
||||
}
|
||||
|
||||
public Endpoints getEndpoints() {
|
||||
return endpoints != null ? endpoints : new Endpoints();
|
||||
}
|
||||
|
||||
public void setEndpoints(Endpoints endpoints) {
|
||||
this.endpoints = endpoints;
|
||||
}
|
||||
|
||||
public Metrics getMetrics() {
|
||||
return metrics != null ? metrics : new Metrics();
|
||||
}
|
||||
|
||||
public void setMetrics(Metrics metrics) {
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
public AutomaticallyGenerated getAutomaticallyGenerated() {
|
||||
return automaticallyGenerated != null ? automaticallyGenerated : new AutomaticallyGenerated();
|
||||
}
|
||||
|
||||
public void setAutomaticallyGenerated(AutomaticallyGenerated automaticallyGenerated) {
|
||||
this.automaticallyGenerated = automaticallyGenerated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ApplicationProperties [security=" + security + ", system=" + system + ", ui=" + ui + ", endpoints="
|
||||
+ endpoints + ", metrics=" + metrics + ", automaticallyGenerated=" + automaticallyGenerated
|
||||
+ ", autoPipeline=" + autoPipeline + "]";
|
||||
}
|
||||
|
||||
public static class AutoPipeline {
|
||||
private String outputFolder;
|
||||
|
||||
public String getOutputFolder() {
|
||||
return outputFolder;
|
||||
}
|
||||
|
||||
public void setOutputFolder(String outputFolder) {
|
||||
this.outputFolder = outputFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AutoPipeline [outputFolder=" + outputFolder + "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
public static class Security {
|
||||
private Boolean enableLogin;
|
||||
private InitialLogin initialLogin;
|
||||
private Boolean csrfDisabled;
|
||||
|
||||
public Boolean getEnableLogin() {
|
||||
return enableLogin;
|
||||
}
|
||||
|
||||
public void setEnableLogin(Boolean enableLogin) {
|
||||
this.enableLogin = enableLogin;
|
||||
}
|
||||
|
||||
public InitialLogin getInitialLogin() {
|
||||
return initialLogin != null ? initialLogin : new InitialLogin();
|
||||
}
|
||||
|
||||
public void setInitialLogin(InitialLogin initialLogin) {
|
||||
this.initialLogin = initialLogin;
|
||||
}
|
||||
|
||||
public Boolean getCsrfDisabled() {
|
||||
return csrfDisabled;
|
||||
}
|
||||
|
||||
public void setCsrfDisabled(Boolean csrfDisabled) {
|
||||
this.csrfDisabled = csrfDisabled;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Security [enableLogin=" + enableLogin + ", initialLogin=" + initialLogin + ", csrfDisabled="
|
||||
+ csrfDisabled + "]";
|
||||
}
|
||||
|
||||
|
||||
public static class InitialLogin {
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InitialLogin [username=" + username + ", password=" + (password != null && !password.isEmpty() ? "MASKED" : "NULL") + "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static class System {
|
||||
private String defaultLocale;
|
||||
private Boolean googlevisibility;
|
||||
private String rootPath;
|
||||
private String customstaticFilePath;
|
||||
private Integer maxFileSize;
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return defaultLocale;
|
||||
}
|
||||
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
public Boolean getGooglevisibility() {
|
||||
return googlevisibility;
|
||||
}
|
||||
|
||||
public void setGooglevisibility(Boolean googlevisibility) {
|
||||
this.googlevisibility = googlevisibility;
|
||||
}
|
||||
|
||||
public String getRootPath() {
|
||||
return rootPath;
|
||||
}
|
||||
|
||||
public void setRootPath(String rootPath) {
|
||||
this.rootPath = rootPath;
|
||||
}
|
||||
|
||||
public String getCustomstaticFilePath() {
|
||||
return customstaticFilePath;
|
||||
}
|
||||
|
||||
public void setCustomstaticFilePath(String customstaticFilePath) {
|
||||
this.customstaticFilePath = customstaticFilePath;
|
||||
}
|
||||
|
||||
public Integer getMaxFileSize() {
|
||||
return maxFileSize;
|
||||
}
|
||||
|
||||
public void setMaxFileSize(Integer maxFileSize) {
|
||||
this.maxFileSize = maxFileSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "System [defaultLocale=" + defaultLocale + ", googlevisibility=" + googlevisibility + ", rootPath="
|
||||
+ rootPath + ", customstaticFilePath=" + customstaticFilePath + ", maxFileSize=" + maxFileSize
|
||||
+ "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class Ui {
|
||||
private String homeName;
|
||||
private String homeDescription;
|
||||
private String navbarName;
|
||||
|
||||
public String getHomeName() {
|
||||
return homeName;
|
||||
}
|
||||
|
||||
public void setHomeName(String homeName) {
|
||||
this.homeName = homeName;
|
||||
}
|
||||
|
||||
public String getHomeDescription() {
|
||||
return homeDescription;
|
||||
}
|
||||
|
||||
public void setHomeDescription(String homeDescription) {
|
||||
this.homeDescription = homeDescription;
|
||||
}
|
||||
|
||||
public String getNavbarName() {
|
||||
return navbarName;
|
||||
}
|
||||
|
||||
public void setNavbarName(String navbarName) {
|
||||
this.navbarName = navbarName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Ui [homeName=" + homeName + ", homeDescription=" + homeDescription + ", navbarName=" + navbarName
|
||||
+ "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class Endpoints {
|
||||
private List<String> toRemove;
|
||||
private List<String> groupsToRemove;
|
||||
|
||||
public List<String> getToRemove() {
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
public void setToRemove(List<String> toRemove) {
|
||||
this.toRemove = toRemove;
|
||||
}
|
||||
|
||||
public List<String> getGroupsToRemove() {
|
||||
return groupsToRemove;
|
||||
}
|
||||
|
||||
public void setGroupsToRemove(List<String> groupsToRemove) {
|
||||
this.groupsToRemove = groupsToRemove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Endpoints [toRemove=" + toRemove + ", groupsToRemove=" + groupsToRemove + "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class Metrics {
|
||||
private Boolean enabled;
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Metrics [enabled=" + enabled + "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class AutomaticallyGenerated {
|
||||
private String key;
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AutomaticallyGenerated [key=" + (key != null && !key.isEmpty() ? "MASKED" : "NULL") + "]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
42
src/main/java/stirling/software/SPDF/model/PDFText.java
Normal file
42
src/main/java/stirling/software/SPDF/model/PDFText.java
Normal file
@ -0,0 +1,42 @@
|
||||
package stirling.software.SPDF.model;
|
||||
public class PDFText {
|
||||
private final int pageIndex;
|
||||
private final float x1;
|
||||
private final float y1;
|
||||
private final float x2;
|
||||
private final float y2;
|
||||
private final String text;
|
||||
|
||||
public PDFText(int pageIndex, float x1, float y1, float x2, float y2, String text) {
|
||||
this.pageIndex = pageIndex;
|
||||
this.x1 = x1;
|
||||
this.y1 = y1;
|
||||
this.x2 = x2;
|
||||
this.y2 = y2;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public int getPageIndex() {
|
||||
return pageIndex;
|
||||
}
|
||||
|
||||
public float getX1() {
|
||||
return x1;
|
||||
}
|
||||
|
||||
public float getY1() {
|
||||
return y1;
|
||||
}
|
||||
|
||||
public float getX2() {
|
||||
return x2;
|
||||
}
|
||||
|
||||
public float getY2() {
|
||||
return y2;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package stirling.software.SPDF.model;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
@Entity
|
||||
@Table(name = "persistent_logins")
|
||||
public class PersistentLogin {
|
||||
|
||||
@Id
|
||||
@Column(name = "series")
|
||||
private String series;
|
||||
|
||||
@Column(name = "username", length = 64, nullable = false)
|
||||
private String username;
|
||||
|
||||
@Column(name = "token", length = 64, nullable = false)
|
||||
private String token;
|
||||
|
||||
@Column(name = "last_used", nullable = false)
|
||||
private Date lastUsed;
|
||||
|
||||
public String getSeries() {
|
||||
return series;
|
||||
}
|
||||
|
||||
public void setSeries(String series) {
|
||||
this.series = series;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public Date getLastUsed() {
|
||||
return lastUsed;
|
||||
}
|
||||
|
||||
public void setLastUsed(Date lastUsed) {
|
||||
this.lastUsed = lastUsed;
|
||||
}
|
||||
|
||||
|
||||
// Getters, setters, etc.
|
||||
}
|
91
src/main/java/stirling/software/SPDF/pdf/TextFinder.java
Normal file
91
src/main/java/stirling/software/SPDF/pdf/TextFinder.java
Normal file
@ -0,0 +1,91 @@
|
||||
package stirling.software.SPDF.pdf;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.text.PDFTextStripper;
|
||||
import org.apache.pdfbox.text.TextPosition;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import stirling.software.SPDF.model.PDFText;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class TextFinder extends PDFTextStripper {
|
||||
|
||||
private final String searchText;
|
||||
private final boolean useRegex;
|
||||
private final boolean wholeWordSearch;
|
||||
private final List<PDFText> textOccurrences = new ArrayList<>();
|
||||
|
||||
public TextFinder(String searchText, boolean useRegex, boolean wholeWordSearch) throws IOException {
|
||||
this.searchText = searchText.toLowerCase();
|
||||
this.useRegex = useRegex;
|
||||
this.wholeWordSearch = wholeWordSearch;
|
||||
setSortByPosition(true);
|
||||
}
|
||||
|
||||
private List<Integer> findOccurrencesInText(String searchText, String content) {
|
||||
List<Integer> indexes = new ArrayList<>();
|
||||
Pattern pattern;
|
||||
|
||||
if (useRegex) {
|
||||
// Use regex-based search
|
||||
pattern = wholeWordSearch
|
||||
? Pattern.compile("(\\b|_|\\.)" + searchText + "(\\b|_|\\.)")
|
||||
: Pattern.compile(searchText);
|
||||
} else {
|
||||
// Use normal text search
|
||||
pattern = wholeWordSearch
|
||||
? Pattern.compile("(\\b|_|\\.)" + Pattern.quote(searchText) + "(\\b|_|\\.)")
|
||||
: Pattern.compile(Pattern.quote(searchText));
|
||||
}
|
||||
|
||||
Matcher matcher = pattern.matcher(content);
|
||||
while (matcher.find()) {
|
||||
indexes.add(matcher.start());
|
||||
}
|
||||
return indexes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeString(String text, List<TextPosition> textPositions) {
|
||||
for (Integer index : findOccurrencesInText(searchText, text.toLowerCase())) {
|
||||
if (index + searchText.length() <= textPositions.size()) {
|
||||
// Initial values based on the first character
|
||||
TextPosition first = textPositions.get(index);
|
||||
float minX = first.getX();
|
||||
float minY = first.getY();
|
||||
float maxX = first.getX() + first.getWidth();
|
||||
float maxY = first.getY() + first.getHeight();
|
||||
|
||||
// Loop over the rest of the characters and adjust bounding box values
|
||||
for (int i = index; i < index + searchText.length(); i++) {
|
||||
TextPosition position = textPositions.get(i);
|
||||
minX = Math.min(minX, position.getX());
|
||||
minY = Math.min(minY, position.getY());
|
||||
maxX = Math.max(maxX, position.getX() + position.getWidth());
|
||||
maxY = Math.max(maxY, position.getY() + position.getHeight());
|
||||
}
|
||||
|
||||
textOccurrences.add(new PDFText(
|
||||
getCurrentPageNo() - 1,
|
||||
minX,
|
||||
minY,
|
||||
maxX,
|
||||
maxY,
|
||||
text
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<PDFText> getTextLocations(PDDocument document) throws Exception {
|
||||
this.getText(document);
|
||||
System.out.println("Found " + textOccurrences.size() + " occurrences of '" + searchText + "' in the document.");
|
||||
|
||||
return textOccurrences;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package stirling.software.SPDF.repository;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
|
||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||
import stirling.software.SPDF.model.PersistentLogin;
|
||||
import stirling.software.SPDF.repository.PersistentLoginRepository;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class JPATokenRepositoryImpl implements PersistentTokenRepository {
|
||||
|
||||
@Autowired
|
||||
private PersistentLoginRepository persistentLoginRepository;
|
||||
|
||||
@Override
|
||||
public void createNewToken(PersistentRememberMeToken token) {
|
||||
PersistentLogin newToken = new PersistentLogin();
|
||||
newToken.setSeries(token.getSeries());
|
||||
newToken.setUsername(token.getUsername());
|
||||
newToken.setToken(token.getTokenValue());
|
||||
newToken.setLastUsed(token.getDate());
|
||||
persistentLoginRepository.save(newToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateToken(String series, String tokenValue, Date lastUsed) {
|
||||
PersistentLogin existingToken = persistentLoginRepository.findById(series).orElse(null);
|
||||
if (existingToken != null) {
|
||||
existingToken.setToken(tokenValue);
|
||||
existingToken.setLastUsed(lastUsed);
|
||||
persistentLoginRepository.save(existingToken);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistentRememberMeToken getTokenForSeries(String seriesId) {
|
||||
PersistentLogin token = persistentLoginRepository.findById(seriesId).orElse(null);
|
||||
if (token != null) {
|
||||
return new PersistentRememberMeToken(token.getUsername(), token.getSeries(), token.getToken(), token.getLastUsed());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserTokens(String username) {
|
||||
for (PersistentLogin token : persistentLoginRepository.findAll()) {
|
||||
if (token.getUsername().equals(username)) {
|
||||
persistentLoginRepository.delete(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package stirling.software.SPDF.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import stirling.software.SPDF.model.PersistentLogin;
|
||||
|
||||
public interface PersistentLoginRepository extends JpaRepository<PersistentLogin, String> {
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PropertyConfigs {
|
||||
|
||||
|
||||
public static boolean getBooleanValue(List<String> keys, boolean defaultValue) {
|
||||
for (String key : keys) {
|
||||
String value = System.getProperty(key);
|
||||
if (value == null)
|
||||
value = System.getenv(key);
|
||||
|
||||
if (value != null)
|
||||
return Boolean.valueOf(value);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static String getStringValue(List<String> keys, String defaultValue) {
|
||||
for (String key : keys) {
|
||||
String value = System.getProperty(key);
|
||||
if (value == null)
|
||||
value = System.getenv(key);
|
||||
|
||||
if (value != null)
|
||||
return value;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static boolean getBooleanValue(String key, boolean defaultValue) {
|
||||
String value = System.getProperty(key);
|
||||
if (value == null)
|
||||
value = System.getenv(key);
|
||||
return (value != null) ? Boolean.valueOf(value) : defaultValue;
|
||||
}
|
||||
|
||||
public static String getStringValue(String key, String defaultValue) {
|
||||
String value = System.getProperty(key);
|
||||
if (value == null)
|
||||
value = System.getenv(key);
|
||||
return (value != null) ? value : defaultValue;
|
||||
}
|
||||
|
||||
}
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Starting Number
|
||||
addPageNumbers.selectText.5=Pages to Number
|
||||
addPageNumbers.selectText.6=Custom Text
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Starting Number
|
||||
addPageNumbers.selectText.5=Pages to Number
|
||||
addPageNumbers.selectText.6=Custom Text
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Starting Number
|
||||
addPageNumbers.selectText.5=Pages to Number
|
||||
addPageNumbers.selectText.6=Custom Text
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,27 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=Redact,Hide,black out,black,marker,hidden
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Starting Number
|
||||
addPageNumbers.selectText.5=Pages to Number
|
||||
addPageNumbers.selectText.6=Custom Text
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Posición
|
||||
addPageNumbers.selectText.4=Número de inicio
|
||||
addPageNumbers.selectText.5=Páginas a numerar
|
||||
addPageNumbers.selectText.6=Texto personalizado
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Starting Number
|
||||
addPageNumbers.selectText.5=Pages to Number
|
||||
addPageNumbers.selectText.6=Custom Text
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Afficher le JavaScript
|
||||
home.showJS.desc=Recherche et affiche tout JavaScript injecté dans un PDF.
|
||||
showJS.tags=afficher,javascript,js
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=afficher,javascript,js
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Afficher le JavaScript
|
||||
showJS.header=Afficher le JavaScript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Numéro de départ
|
||||
addPageNumbers.selectText.5=Pages à numéroter
|
||||
addPageNumbers.selectText.6=Texte personnalisé
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Starting Number
|
||||
addPageNumbers.selectText.5=Pages to Number
|
||||
addPageNumbers.selectText.6=Custom Text
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Starting Number
|
||||
addPageNumbers.selectText.5=Pages to Number
|
||||
addPageNumbers.selectText.6=Custom Text
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Starting Number
|
||||
addPageNumbers.selectText.5=Pages to Number
|
||||
addPageNumbers.selectText.6=Custom Text
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Toon Javascript
|
||||
home.showJS.desc=Zoekt en toont ieder script dat in een PDF is geïnjecteerd
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Toon Javascript
|
||||
showJS.header=Toon Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Positie
|
||||
addPageNumbers.selectText.4=Startnummer
|
||||
addPageNumbers.selectText.5=Pagina''s om te nummeren
|
||||
addPageNumbers.selectText.6=Aangepaste tekst
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Starting Number
|
||||
addPageNumbers.selectText.5=Pages to Number
|
||||
addPageNumbers.selectText.6=Custom Text
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Mostrar Javascript
|
||||
home.showJS.desc=Procura e exibe qualquer JavaScript injetado em um PDF
|
||||
showJS.tags=JavaScript
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JavaScript
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Exibir JavaScript
|
||||
showJS.header=Exibir JavaScript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Posição
|
||||
addPageNumbers.selectText.4=Número Inicial
|
||||
addPageNumbers.selectText.5=Páginas a Numerar
|
||||
addPageNumbers.selectText.6=Texto Personalizado
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Starting Number
|
||||
addPageNumbers.selectText.5=Pages to Number
|
||||
addPageNumbers.selectText.6=Custom Text
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Starting Number
|
||||
addPageNumbers.selectText.5=Pages to Number
|
||||
addPageNumbers.selectText.6=Custom Text
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Starting Number
|
||||
addPageNumbers.selectText.5=Pages to Number
|
||||
addPageNumbers.selectText.6=Custom Text
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
@ -299,11 +299,33 @@ home.showJS.title=Show Javascript
|
||||
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||
showJS.tags=JS
|
||||
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
home.autoRedact.title=Auto Redact
|
||||
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
|
||||
showJS.tags=JS
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
# #
|
||||
###########################
|
||||
#auto-redact
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
autoRedact.title=Auto Redact
|
||||
autoRedact.header=Auto Redact
|
||||
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||
autoRedact.useRegexLabel=Use Regex
|
||||
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||
autoRedact.submitButton=Submit
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=Show Javascript
|
||||
showJS.header=Show Javascript
|
||||
@ -374,9 +396,6 @@ addPageNumbers.selectText.3=Position
|
||||
addPageNumbers.selectText.4=Starting Number
|
||||
addPageNumbers.selectText.5=Pages to Number
|
||||
addPageNumbers.selectText.6=Custom Text
|
||||
##########################
|
||||
### TODO: Translate ###
|
||||
##########################
|
||||
addPageNumbers.customTextDesc=Custom Text
|
||||
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
|
||||
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
|
||||
|
26
src/main/resources/settings.yml.template
Normal file
26
src/main/resources/settings.yml.template
Normal file
@ -0,0 +1,26 @@
|
||||
security:
|
||||
enableLogin: false # set to 'true' to enable login
|
||||
initialLogin:
|
||||
username: 'username' # Specify the initial username for first boot (e.g. 'admin')
|
||||
password: 'password'# Specify the initial password for first boot (e.g. 'password123')
|
||||
csrfDisabled: true
|
||||
|
||||
system:
|
||||
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||
googlevisibility: false # 'true' to allow Google visibility, 'false' to disallow
|
||||
rootPath: / # Set the application's root URI (e.g. /pdf-app)
|
||||
customstaticFilePath: '/customFiles/static/' # Directory path for custom static files
|
||||
maxFileSize: 2000 # Set the maximum file size in MB
|
||||
|
||||
ui:
|
||||
homeName: # Application's visible name
|
||||
homeDescription: # Short description or tagline.
|
||||
navbarName: # Name displayed on the navigation bar
|
||||
|
||||
endpoints:
|
||||
toRemove: [] # List endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
|
||||
groupsToRemove: [] # List groups to disable (e.g. ['LibreOffice'])
|
||||
|
||||
metrics:
|
||||
enabled: true # 'true' to enable metric API endpoints, 'false' to disable
|
||||
|
11
src/main/resources/static/css/bootstrap.min.css
vendored
11
src/main/resources/static/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
@ -14,7 +14,7 @@
|
||||
|
||||
.features-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(15rem, 3fr));
|
||||
gap: 25px 30px;
|
||||
}
|
||||
|
||||
@ -27,6 +27,8 @@
|
||||
align-items: flex-start;
|
||||
background: rgba(13, 110, 253, 0.05);
|
||||
transition: transform 0.3s, border 0.3s;
|
||||
transform-origin: center center;
|
||||
outline: 2px solid transparent;
|
||||
}
|
||||
|
||||
.feature-card a {
|
||||
@ -43,7 +45,7 @@
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
border: 1px solid rgba(0, 0, 0, .5);
|
||||
outline: 1px solid rgba(0, 0, 0, .5);
|
||||
cursor: pointer;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
3
src/main/resources/static/images/eraser-fill.svg
Normal file
3
src/main/resources/static/images/eraser-fill.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eraser-fill" viewBox="0 0 16 16">
|
||||
<path d="M8.086 2.207a2 2 0 0 1 2.828 0l3.879 3.879a2 2 0 0 1 0 2.828l-5.5 5.5A2 2 0 0 1 7.879 15H5.12a2 2 0 0 1-1.414-.586l-2.5-2.5a2 2 0 0 1 0-2.828l6.879-6.879zm.66 11.34L3.453 8.254 1.914 9.793a1 1 0 0 0 0 1.414l2.5 2.5a1 1 0 0 0 .707.293H7.88a1 1 0 0 0 .707-.293l.16-.16z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 418 B |
@ -13,12 +13,12 @@ const DraggableUtils = {
|
||||
listeners: {
|
||||
move: (event) => {
|
||||
const target = event.target;
|
||||
const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
|
||||
const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
|
||||
const x = (parseFloat(target.getAttribute('data-bs-x')) || 0) + event.dx;
|
||||
const y = (parseFloat(target.getAttribute('data-bs-y')) || 0) + event.dy;
|
||||
|
||||
target.style.transform = `translate(${x}px, ${y}px)`;
|
||||
target.setAttribute('data-x', x);
|
||||
target.setAttribute('data-y', y);
|
||||
target.setAttribute('data-bs-x', x);
|
||||
target.setAttribute('data-bs-y', y);
|
||||
|
||||
this.onInteraction(target);
|
||||
},
|
||||
@ -29,8 +29,8 @@ const DraggableUtils = {
|
||||
listeners: {
|
||||
move: (event) => {
|
||||
var target = event.target
|
||||
var x = (parseFloat(target.getAttribute('data-x')) || 0)
|
||||
var y = (parseFloat(target.getAttribute('data-y')) || 0)
|
||||
var x = (parseFloat(target.getAttribute('data-bs-x')) || 0)
|
||||
var y = (parseFloat(target.getAttribute('data-bs-y')) || 0)
|
||||
|
||||
// check if control key is pressed
|
||||
if (event.ctrlKey) {
|
||||
@ -58,8 +58,8 @@ const DraggableUtils = {
|
||||
|
||||
target.style.transform = 'translate(' + x + 'px,' + y + 'px)'
|
||||
|
||||
target.setAttribute('data-x', x)
|
||||
target.setAttribute('data-y', y)
|
||||
target.setAttribute('data-bs-x', x)
|
||||
target.setAttribute('data-bs-y', y)
|
||||
target.textContent = Math.round(event.rect.width) + '\u00D7' + Math.round(event.rect.height)
|
||||
|
||||
this.onInteraction(target);
|
||||
@ -86,8 +86,8 @@ const DraggableUtils = {
|
||||
const x = 0;
|
||||
const y = 20;
|
||||
createdCanvas.style.transform = `translate(${x}px, ${y}px)`;
|
||||
createdCanvas.setAttribute('data-x', x);
|
||||
createdCanvas.setAttribute('data-y', y);
|
||||
createdCanvas.setAttribute('data-bs-x', x);
|
||||
createdCanvas.setAttribute('data-bs-y', y);
|
||||
|
||||
createdCanvas.onclick = e => this.onInteraction(e.target);
|
||||
|
||||
|
@ -1,9 +1,13 @@
|
||||
function updateFavoritesDropdown() {
|
||||
var dropdown = document.querySelector('#favoritesDropdown');
|
||||
|
||||
// Check if dropdown exists
|
||||
if (!dropdown) {
|
||||
console.error('Dropdown element with ID "favoritesDropdown" not found!');
|
||||
return; // Exit the function
|
||||
}
|
||||
dropdown.innerHTML = ''; // Clear the current favorites
|
||||
|
||||
|
||||
|
||||
var hasFavorites = false;
|
||||
|
||||
for (var i = 0; i < localStorage.length; i++) {
|
||||
@ -19,6 +23,8 @@ function updateFavoritesDropdown() {
|
||||
dropdownItem.innerHTML = navbarEntry.innerHTML;
|
||||
dropdown.appendChild(dropdownItem);
|
||||
hasFavorites = true;
|
||||
} else {
|
||||
console.warn(`Navbar entry not found for key: ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -31,7 +37,9 @@ function updateFavoritesDropdown() {
|
||||
dropdown.appendChild(defaultItem);
|
||||
}
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Ensure that the DOM content has been fully loaded before calling the function
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('DOMContentLoaded event fired');
|
||||
updateFavoritesDropdown();
|
||||
});
|
@ -3,9 +3,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
|
||||
function setupFileInput(chooser) {
|
||||
const elementId = chooser.getAttribute('data-element-id');
|
||||
const filesSelected = chooser.getAttribute('data-files-selected');
|
||||
const pdfPrompt = chooser.getAttribute('data-pdf-prompt');
|
||||
const elementId = chooser.getAttribute('data-bs-element-id');
|
||||
const filesSelected = chooser.getAttribute('data-bs-files-selected');
|
||||
const pdfPrompt = chooser.getAttribute('data-bs-pdf-prompt');
|
||||
|
||||
let allFiles = [];
|
||||
let overlay;
|
||||
|
@ -10,7 +10,7 @@ function filterCards() {
|
||||
|
||||
// Get the navbar tags associated with the card
|
||||
var navbarItem = document.querySelector(`a.dropdown-item[href="${card.id}"]`);
|
||||
var navbarTags = navbarItem ? navbarItem.getAttribute('data-tags') : '';
|
||||
var navbarTags = navbarItem ? navbarItem.getAttribute('data-bs-tags') : '';
|
||||
|
||||
var content = title + ' ' + text + ' ' + navbarTags;
|
||||
|
||||
|
@ -15,7 +15,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
function handleDropdownItemClick(event) {
|
||||
event.preventDefault();
|
||||
const languageCode = this.dataset.languageCode;
|
||||
const languageCode = event.currentTarget.dataset.bsLanguageCode; // change this to event.currentTarget
|
||||
if (languageCode) {
|
||||
localStorage.setItem('languageCode', languageCode);
|
||||
|
||||
const currentUrl = window.location.href;
|
||||
@ -24,23 +25,29 @@ function handleDropdownItemClick(event) {
|
||||
} else {
|
||||
window.location.href = currentUrl.replace(/\?lang=\w{2,}/, '?lang=' + languageCode);
|
||||
}
|
||||
} else {
|
||||
console.error("Language code is not set for this item."); // for debugging
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$(".nav-item.dropdown").each(function() {
|
||||
var $dropdownMenu = $(this).find(".dropdown-menu");
|
||||
if ($dropdownMenu.children().length <= 2 && $dropdownMenu.children("hr.dropdown-divider").length === $dropdownMenu.children().length) {
|
||||
$(this).prev('.nav-item.nav-item-separator').remove();
|
||||
$(this).remove();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.nav-item.dropdown').forEach((element) => {
|
||||
const dropdownMenu = element.querySelector(".dropdown-menu");
|
||||
if (dropdownMenu.id !== 'favoritesDropdown' && dropdownMenu.children.length <= 2 && dropdownMenu.querySelectorAll("hr.dropdown-divider").length === dropdownMenu.children.length) {
|
||||
if (element.previousElementSibling && element.previousElementSibling.classList.contains('nav-item') && element.previousElementSibling.classList.contains('nav-item-separator')) {
|
||||
element.previousElementSibling.remove();
|
||||
}
|
||||
element.remove();
|
||||
}
|
||||
});
|
||||
|
||||
//Sort languages by alphabet
|
||||
var list = $('.dropdown-menu[aria-labelledby="languageDropdown"]').children("a");
|
||||
const list = Array.from(document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').children).filter(child => child.matches('a'));
|
||||
list.sort(function(a, b) {
|
||||
var A = $(a).text().toUpperCase();
|
||||
var B = $(b).text().toUpperCase();
|
||||
var A = a.textContent.toUpperCase();
|
||||
var B = b.textContent.toUpperCase();
|
||||
return (A < B) ? -1 : (A > B) ? 1 : 0;
|
||||
})
|
||||
.appendTo('.dropdown-menu[aria-labelledby="languageDropdown"]');
|
||||
}).forEach(node => document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').appendChild(node));
|
||||
|
||||
});
|
@ -241,7 +241,7 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
|
||||
if (parameter.name === 'fileInput') return;
|
||||
|
||||
let parameterDiv = document.createElement('div');
|
||||
parameterDiv.className = "form-group";
|
||||
parameterDiv.className = "mb-3";
|
||||
|
||||
let parameterLabel = document.createElement('label');
|
||||
parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `;
|
||||
|
@ -43,7 +43,7 @@ document.querySelector('#navbarSearchInput').addEventListener('input', function(
|
||||
var titleElement = item.querySelector('.icon-text');
|
||||
var iconElement = item.querySelector('.icon');
|
||||
var itemHref = item.getAttribute('href');
|
||||
var tags = item.getAttribute('data-tags') || ""; // If no tags, default to empty string
|
||||
var tags = item.getAttribute('data-bs-tags') || ""; // If no tags, default to empty string
|
||||
|
||||
if (titleElement && iconElement && itemHref !== '#') {
|
||||
var title = titleElement.innerText;
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -3,12 +3,12 @@
|
||||
"short_name": "Stirling PDF",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"src": "android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"src": "android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{account.title})}"></th:block>
|
||||
@ -24,15 +24,15 @@
|
||||
<!-- Change Username Form -->
|
||||
<h4></h4>
|
||||
<form action="/change-username" method="post">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="newUsername" th:text="#{account.changeUsername}">Change Username</label>
|
||||
<input type="text" class="form-control" name="newUsername" id="newUsername" placeholder="New Username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="currentPassword" th:text="#{password}">Password</label>
|
||||
<input type="password" class="form-control" name="currentPassword" id="currentPasswordUsername" placeholder="Password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary" th:text="#{account.changeUsername}">Change Username</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -42,19 +42,19 @@
|
||||
<!-- Change Password Form -->
|
||||
<h4 th:text="#{account.changePassword}">Change Password?</h4>
|
||||
<form action="/change-password" method="post">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="currentPassword" th:text="#{account.oldPassword}">Old Password</label>
|
||||
<input type="password" class="form-control" name="currentPassword" id="currentPasswordPassword" th:placeholder="#{account.oldPassword}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="newPassword" th:text="#{account.newPassword}">New Password</label>
|
||||
<input type="password" class="form-control" name="newPassword" id="newPassword" th:placeholder="#{account.newPassword}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="confirmNewPassword" th:text="#{account.confirmNewPassword}">Confirm New Password</label>
|
||||
<input type="password" class="form-control" name="confirmNewPassword" id="confirmNewPassword" th:placeholder="#{account.confirmNewPassword}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary" th:text="#{account.changePassword}">Change Password</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -286,7 +286,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="form-group mt-4">
|
||||
<div class="mb-3 mt-4">
|
||||
<a href="/logout">
|
||||
<button type="button" class="btn btn-danger" th:text="#{account.signOut}">Sign Out</button>
|
||||
</a>
|
||||
|
@ -41,15 +41,15 @@
|
||||
|
||||
<h2 th:text="#{adminUserSettings.addUser}">Add New User</h2>
|
||||
<form action="/admin/saveUser" method="post">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="username" th:text="#{username}">Username</label>
|
||||
<input type="text" class="form-control" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="password" th:text="#{password}">Password</label>
|
||||
<input type="password" class="form-control" name="password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="role" th:text="#{adminUserSettings.role}">Role</label>
|
||||
<select name="role" class="form-control" required>
|
||||
<option value="ROLE_ADMIN" th:text="#{adminUserSettings.admin}">Admin</option>
|
||||
|
@ -27,7 +27,7 @@
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="duplexMode" id="duplexMode">
|
||||
<label class="ml-3" for="duplexMode" th:text=#{autoSplitPDF.duplexMode}></label>
|
||||
<label class="ms-3" for="duplexMode" th:text=#{autoSplitPDF.duplexMode}></label>
|
||||
</div>
|
||||
<p><a th:href="@{files/Auto Splitter Divider (minimal).pdf}" download th:text="#{autoSplitPDF.dividerDownload1}"></a></p>
|
||||
<p><a th:href="@{files/Auto Splitter Divider (with instructions).pdf}" download th:text="#{autoSplitPDF.dividerDownload2}"></a></p>
|
||||
|
@ -23,13 +23,13 @@
|
||||
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="stretchToFit" id="stretchToFit">
|
||||
<label class="ml-3" for="stretchToFit" th:text=#{imageToPDF.selectText.1}></label>
|
||||
<label class="ms-3" for="stretchToFit" th:text=#{imageToPDF.selectText.1}></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="autoRotate" id="autoRotate">
|
||||
<label class="ml-3" for="autoRotate" th:text=#{imageToPDF.selectText.2}></label>
|
||||
<label class="ms-3" for="autoRotate" th:text=#{imageToPDF.selectText.2}></label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{pdfToImage.colorType}"></label>
|
||||
<select class="form-control" id="colorType" name="colorType">
|
||||
<option value="color" th:text="#{pdfToImage.color}"></option>
|
||||
@ -39,7 +39,7 @@
|
||||
</div>
|
||||
<br>
|
||||
<input type="hidden" id="override" name="override" value="multi">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text=#{imageToPDF.selectText.3}></label>
|
||||
<select class="form-control" id="conversionType" name="conversionType" disabled>
|
||||
<option value="merge" th:text=#{imageToPDF.selectText.4}></option>
|
||||
|
@ -18,7 +18,7 @@
|
||||
<p th:text="#{processTimeWarning}"></p>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-img}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{pdfToImage.selectText}"></label>
|
||||
<select class="form-control" name="imageFormat">
|
||||
<option value="png">PNG</option>
|
||||
@ -26,14 +26,14 @@
|
||||
<option value="gif">GIF</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{pdfToImage.singleOrMultiple}"></label>
|
||||
<select class="form-control" name="singleOrMultiple">
|
||||
<option value="single" th:text="#{pdfToImage.single}"></option>
|
||||
<option value="multiple" th:text="#{pdfToImage.multi}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{pdfToImage.colorType}"></label>
|
||||
<select class="form-control" name="colorType">
|
||||
<option value="color" th:text="#{pdfToImage.color}"></option>
|
||||
@ -41,7 +41,7 @@
|
||||
<option value="blackwhite" th:text="#{pdfToImage.blackwhite}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="dpi">DPI:</label>
|
||||
<input type="number" name="dpi" class="form-control" id="dpi" min="1" step="1" value="300" required>
|
||||
</div>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-presentation}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{PDFToPresentation.selectText.1}"></label>
|
||||
<select class="form-control" name="outputFormat">
|
||||
<option value="ppt">PPT</option>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-text}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{PDFToText.selectText.1}"></label>
|
||||
<select class="form-control" name="outputFormat">
|
||||
<option value="rtf">RTF</option>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-word}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{PDFToWord.selectText.1}"></label>
|
||||
<select class="form-control" name="outputFormat">
|
||||
<option value="doc">Doc</option>
|
||||
|
@ -16,7 +16,7 @@
|
||||
<form th:action="@{rearrange-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<input type="hidden" id="customMode" name="customMode" value="">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
|
||||
<input type="text" class="form-control" id="pageOrder" name="pageOrder" placeholder="(e.g. 1,2,8 or 4,7,12-16 or 2n-1)" required>
|
||||
</div>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<div th:fragment="card" class="feature-card" th:id="${id}" th:if="${@endpointConfiguration.isEndpointEnabled(cardLink)}" data-tags="${tags}">
|
||||
<div th:fragment="card" class="feature-card" th:id="${id}" th:if="${@endpointConfiguration.isEndpointEnabled(cardLink)}" data-bs-tags="${tags}">
|
||||
<a th:href="${cardLink}">
|
||||
<div class="d-flex align-items-center"> <!-- Add a flex container to align the SVG and title -->
|
||||
<img th:if="${svgPath}" id="card-icon" class="home-card-icon home-card-icon-colour" th:src="${svgPath}" alt="Icon" width="30" height="30">
|
||||
<h5 class="card-title ml-2" th:text="${cardTitle}"></h5> <!-- Add some margin-left (ml-2) for spacing between SVG and title -->
|
||||
<h5 class="card-title ms-2" th:text="${cardTitle}"></h5> <!-- Add some margin-left (ms-2) for spacing between SVG and title -->
|
||||
</div>
|
||||
<p class="card-text" th:text="${cardText}"></p>
|
||||
</a>
|
||||
|
@ -35,7 +35,6 @@
|
||||
<script src="js/thirdParty/popper.min.js"></script>
|
||||
<script src="js/thirdParty/bootstrap.min.js"></script>
|
||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="css/bootstrap-icons.css">
|
||||
|
||||
<!-- PDF.js -->
|
||||
<script src="pdfjs/pdf.js"></script>
|
||||
@ -57,7 +56,7 @@
|
||||
</head>
|
||||
|
||||
<th:block th:fragment="game">
|
||||
<dialog id="game-container-wrapper" class="game-container-wrapper" data-modal>
|
||||
<dialog id="game-container-wrapper" class="game-container-wrapper" data-bs-modal>
|
||||
<script>
|
||||
console.log("loaded game")
|
||||
$(document).ready(function() {
|
||||
@ -116,20 +115,20 @@
|
||||
</script>
|
||||
<script src="js/downloader.js"></script>
|
||||
|
||||
<div class="custom-file-chooser" th:attr="data-unique-id=${name},
|
||||
data-element-id=${name+'-input'},
|
||||
data-files-selected=#{filesSelected},
|
||||
data-pdf-prompt=#{pdfPrompt}">
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple th:classappend="${notRequired ? '' : 'required'}">
|
||||
<label class="custom-file-label" th:for="${name}+'-input'" th:text="${inputText}"></label>
|
||||
<div class="custom-file-chooser" th:attr="data-bs-unique-id=${name},
|
||||
data-bs-element-id=${name+'-input'},
|
||||
data-bs-files-selected=#{filesSelected},
|
||||
data-bs-pdf-prompt=#{pdfPrompt}">
|
||||
<div class="mb-3">
|
||||
<input type="file" class="form-control" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple th:classappend="${notRequired ? '' : 'required'}">
|
||||
</div>
|
||||
<div class="selected-files"></div>
|
||||
</div>
|
||||
|
||||
<div id="progressBarContainer" style="display: none; position: relative;">
|
||||
<div class="progress" style="height: 1rem;">
|
||||
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
|
||||
<span class="sr-only">Loading...</span>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -24,7 +24,7 @@
|
||||
<p th:text="${message} + ' for path: ' + ${path}"></p>
|
||||
<button type="button" class="btn btn-danger" th:if="${trace}" onclick="toggletrace()">Show Stack Trace</button>
|
||||
<button type="button" class="btn btn-secondary" th:if="${trace}" onclick="copytrace()">Copy Stack Trace</button>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close" onclick="dismissError()">
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close" onclick="dismissError()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<!-- Stack trace section -->
|
||||
|
@ -7,7 +7,7 @@
|
||||
<button type="button" class="btn btn-danger" onclick="toggletrace()">Show Stack Trace</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="copytrace()">Copy Stack Trace</button>
|
||||
<button type="button" class="btn btn-info" onclick="showHelp()">Help</button>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close" onclick="dismissError()">
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close" onclick="dismissError()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<!-- Stack trace section -->
|
||||
@ -29,7 +29,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="helpModalLabel">Help</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@ -47,7 +47,7 @@
|
||||
<a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" target="_blank">Discord - Submit Support post</a>
|
||||
</div>
|
||||
<a href="/" id="home-button">Go to Homepage</a>
|
||||
<a data-dismiss="modal" id="home-button">Close</a>
|
||||
<a data-bs-dismiss="modal" id="home-button">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
75
src/main/resources/templates/fragments/langAndDarkMode.html
Normal file
75
src/main/resources/templates/fragments/langAndDarkMode.html
Normal file
@ -0,0 +1,75 @@
|
||||
<th:block th:fragment="langAndDarkMode">
|
||||
|
||||
<script src="js/languageSelection.js"></script>
|
||||
<script src="js/darkmode.js"></script>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="dark-mode-toggle" href="#">
|
||||
<img class="navbar-icon" id="dark-mode-icon" src="moon.svg" alt="icon" />
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-globe2" viewBox="0 0 20 20">
|
||||
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855-.143.268-.276.56-.395.872.705.157 1.472.257 2.282.287V1.077zM4.249 3.539c.142-.384.304-.744.481-1.078a6.7 6.7 0 0 1 .597-.933A7.01 7.01 0 0 0 3.051 3.05c.362.184.763.349 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9.124 9.124 0 0 1-1.565-.667A6.964 6.964 0 0 0 1.018 7.5h2.49zm1.4-2.741a12.344 12.344 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332zM8.5 5.09V7.5h2.99a12.342 12.342 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.035.987.176 1.914.399 2.741A13.612 13.612 0 0 1 7.5 10.91V8.5H4.51zm3.99 0v2.409c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741H8.5zm-3.282 3.696c.12.312.252.604.395.872.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.282.287zm.11 2.276a6.696 6.696 0 0 1-.598-.933 8.853 8.853 0 0 1-.481-1.079 8.38 8.38 0 0 0-1.198.49 7.01 7.01 0 0 0 2.276 1.522zm-1.383-2.964A13.36 13.36 0 0 1 3.508 8.5h-2.49a6.963 6.963 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667zm6.728 2.964a7.009 7.009 0 0 0 2.275-1.521 8.376 8.376 0 0 0-1.197-.49 8.853 8.853 0 0 1-.481 1.078 6.688 6.688 0 0 1-.597.933zM8.5 11.909v3.014c.67-.204 1.335-.82 1.887-1.855.143-.268.276-.56.395-.872A12.63 12.63 0 0 0 8.5 11.91zm3.555-.401c.57.185 1.095.409 1.565.667A6.963 6.963 0 0 0 14.982 8.5h-2.49a13.36 13.36 0 0 1-.437 3.008zM14.982 7.5a6.963 6.963 0 0 0-1.362-3.675c-.47.258-.995.482-1.565.667.248.92.4 1.938.437 3.008h2.49zM11.27 2.461c.177.334.339.694.482 1.078a8.368 8.368 0 0 0 1.196-.49 7.01 7.01 0 0 0-2.275-1.52c.218.283.418.597.597.932zm-.488 1.343a7.765 7.765 0 0 0-.395-.872C9.835 1.897 9.17 1.282 8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="languageDropdown">
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ar_AR">
|
||||
<img src="images/flags/sa.svg" alt="icon" width="20" height="15"> العربية
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ca_CA">
|
||||
<img src="images/flags/es-ct.svg" alt="icon" width="20" height="15"> Català
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_CN">
|
||||
<img src="images/flags/cn.svg" alt="icon" width="20" height="15"> 简体中文
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="de_DE">
|
||||
<img src="images/flags/de.svg" alt="icon" width="20" height="15"> Deutsch
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="en_GB">
|
||||
<img src="images/flags/gb.svg" alt="icon" width="20" height="15"> English (GB)
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="en_US">
|
||||
<img src="images/flags/us.svg" alt="icon" width="20" height="15"> English (US)
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="eu_ES">
|
||||
<img src="images/flags/eu.svg" alt="icon" width="20" height="15"> Euskara
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="es_ES">
|
||||
<img src="images/flags/es.svg" alt="icon" width="20" height="15"> Español
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="fr_FR">
|
||||
<img src="images/flags/fr.svg" alt="icon" width="20" height="15"> Français
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="it_IT">
|
||||
<img src="images/flags/it.svg" alt="icon" width="20" height="15"> Italiano
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="nl_NL">
|
||||
<img src="images/flags/nl.svg" alt="icon" width="20" height="15"> Nederlands
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL">
|
||||
<img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pt_BR">
|
||||
<img src="images/flags/pt_br.svg" alt="icon" width="20" height="15"> Português (BR)
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ro_RO">
|
||||
<img src="images/flags/ro.svg" alt="icon" width="20" height="15"> Romanian
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="sv_SE">
|
||||
<img src="images/flags/se.svg" alt="icon" width="20" height="15"> Svenska
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ru_RU">
|
||||
<img src="images/flags/ru.svg" alt="icon" width="20" height="15"> Русский
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ko_KR">
|
||||
<img src="images/flags/kr.svg" alt="icon" width="20" height="15"> 한국어
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ja_JP">
|
||||
<img src="images/flags/jp.svg" alt="icon" width="20" height="15"> 日本語
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</th:block>
|
@ -18,13 +18,13 @@
|
||||
</a>
|
||||
|
||||
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
|
||||
<ul class="navbar-nav mr-auto flex-nowrap">
|
||||
<ul class="navbar-nav me-auto flex-nowrap">
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" th:href="@{multi-tool}" th:classappend="${currentPage}=='multi-tool' ? 'active' : ''" th:title="#{home.multiTool.desc}">
|
||||
@ -41,7 +41,7 @@
|
||||
|
||||
<li class="nav-item nav-item-separator"></li>
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='merge-pdfs' OR ${currentPage}=='split-pdfs' OR ${currentPage}=='crop' OR ${currentPage}=='adjust-contrast' OR ${currentPage}=='pdf-organizer' OR ${currentPage}=='rotate-pdf' OR ${currentPage}=='multi-page-layout' OR ${currentPage}=='scale-pages' OR ${currentPage}=='auto-split-pdf' OR ${currentPage}=='extract-page' OR ${currentPage}=='pdf-to-single-page' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img class="icon" src="images/file-earmark-pdf.svg" alt="icon">
|
||||
<span class="icon-text" th:text="#{navbar.pageOps}"></span>
|
||||
</a>
|
||||
@ -64,7 +64,7 @@
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item nav-item-separator"></li>
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='pdf-to-img' OR ${currentPage}=='img-to-pdf' OR ${currentPage}=='pdf-to-pdfa' OR ${currentPage}=='file-to-pdf' OR ${currentPage}=='xlsx-to-pdf' OR ${currentPage}=='pdf-to-word' OR ${currentPage}=='pdf-to-presentation' OR ${currentPage}=='pdf-to-text' OR ${currentPage}=='pdf-to-html' OR ${currentPage}=='pdf-to-xml' ? 'active' : ''"><a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='pdf-to-img' OR ${currentPage}=='img-to-pdf' OR ${currentPage}=='pdf-to-pdfa' OR ${currentPage}=='file-to-pdf' OR ${currentPage}=='xlsx-to-pdf' OR ${currentPage}=='pdf-to-word' OR ${currentPage}=='pdf-to-presentation' OR ${currentPage}=='pdf-to-text' OR ${currentPage}=='pdf-to-html' OR ${currentPage}=='pdf-to-xml' ? 'active' : ''"><a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<img class="icon" src="images/arrow-left-right.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;">
|
||||
<span class="icon-text" th:text="#{navbar.convert}"></span>
|
||||
@ -94,7 +94,7 @@
|
||||
<li class="nav-item nav-item-separator"></li>
|
||||
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='add-password' OR ${currentPage}=='remove-password' OR ${currentPage}=='add-watermark' OR ${currentPage}=='cert-sign' OR ${currentPage}=='sanitize-pdf' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img class="icon" src="images/shield-check.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{navbar.security}"></span>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
@ -104,13 +104,13 @@
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-watermark', 'images/droplet.svg', 'home.watermark.title', 'home.watermark.desc', 'watermark.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('cert-sign', 'images/award.svg', 'home.certSign.title', 'home.certSign.desc', 'certSign.tags')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('sanitize-pdf', 'images/sanitize.svg', 'home.sanitizePdf.title', 'home.sanitizePdf.desc', 'sanitizePdf.tags')}"></div>
|
||||
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('auto-redact', 'images/eraser-fill.svg', 'home.autoRedact.title', 'home.autoRedact.desc', 'autoRedact.tags')}"></div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item nav-item-separator"></li>
|
||||
<li class="nav-item dropdown" th:classappend="${currentPage}=='sign' OR ${currentPage}=='repair' OR ${currentPage}=='compare' OR ${currentPage}=='show-javascript' OR ${currentPage}=='flatten' OR ${currentPage}=='remove-blanks' OR ${currentPage}=='extract-image-scans' OR ${currentPage}=='change-metadata' OR ${currentPage}=='add-image' OR ${currentPage}=='ocr-pdf' OR ${currentPage}=='change-permissions' OR ${currentPage}=='extract-images' OR ${currentPage}=='compress-pdf' OR ${currentPage}=='add-page-numbers' OR ${currentPage}=='auto-rename' OR ${currentPage}=='get-info-on-pdf' ? 'active' : ''">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img class="icon" src="images/card-list.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;">
|
||||
<span class="icon-text" th:text="#{navbar.other}"></span>
|
||||
|
||||
@ -137,9 +137,9 @@
|
||||
|
||||
|
||||
</ul>
|
||||
<ul class="navbar-nav ml-auto flex-nowrap">
|
||||
<ul class="navbar-nav flex-nowrap">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img class="navbar-icon" src="images/star.svg" alt="icon" width="24" height="24">
|
||||
</a>
|
||||
<div class="dropdown-menu" id="favoritesDropdown" aria-labelledby="navbarDropdown">
|
||||
@ -147,80 +147,12 @@
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="dark-mode-toggle" href="#">
|
||||
<img class="navbar-icon" id="dark-mode-icon" src="moon.svg" alt="icon" />
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-globe2" viewBox="0 0 20 20">
|
||||
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855-.143.268-.276.56-.395.872.705.157 1.472.257 2.282.287V1.077zM4.249 3.539c.142-.384.304-.744.481-1.078a6.7 6.7 0 0 1 .597-.933A7.01 7.01 0 0 0 3.051 3.05c.362.184.763.349 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9.124 9.124 0 0 1-1.565-.667A6.964 6.964 0 0 0 1.018 7.5h2.49zm1.4-2.741a12.344 12.344 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332zM8.5 5.09V7.5h2.99a12.342 12.342 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.035.987.176 1.914.399 2.741A13.612 13.612 0 0 1 7.5 10.91V8.5H4.51zm3.99 0v2.409c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741H8.5zm-3.282 3.696c.12.312.252.604.395.872.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.282.287zm.11 2.276a6.696 6.696 0 0 1-.598-.933 8.853 8.853 0 0 1-.481-1.079 8.38 8.38 0 0 0-1.198.49 7.01 7.01 0 0 0 2.276 1.522zm-1.383-2.964A13.36 13.36 0 0 1 3.508 8.5h-2.49a6.963 6.963 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667zm6.728 2.964a7.009 7.009 0 0 0 2.275-1.521 8.376 8.376 0 0 0-1.197-.49 8.853 8.853 0 0 1-.481 1.078 6.688 6.688 0 0 1-.597.933zM8.5 11.909v3.014c.67-.204 1.335-.82 1.887-1.855.143-.268.276-.56.395-.872A12.63 12.63 0 0 0 8.5 11.91zm3.555-.401c.57.185 1.095.409 1.565.667A6.963 6.963 0 0 0 14.982 8.5h-2.49a13.36 13.36 0 0 1-.437 3.008zM14.982 7.5a6.963 6.963 0 0 0-1.362-3.675c-.47.258-.995.482-1.565.667.248.92.4 1.938.437 3.008h2.49zM11.27 2.461c.177.334.339.694.482 1.078a8.368 8.368 0 0 0 1.196-.49 7.01 7.01 0 0 0-2.275-1.52c.218.283.418.597.597.932zm-.488 1.343a7.765 7.765 0 0 0-.395-.872C9.835 1.897 9.17 1.282 8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="languageDropdown">
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="ar_AR">
|
||||
<img src="images/flags/sa.svg" alt="icon" width="20" height="15"> العربية
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="ca_CA">
|
||||
<img src="images/flags/es-ct.svg" alt="icon" width="20" height="15"> Català
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="zh_CN">
|
||||
<img src="images/flags/cn.svg" alt="icon" width="20" height="15"> 简体中文
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="de_DE">
|
||||
<img src="images/flags/de.svg" alt="icon" width="20" height="15"> Deutsch
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="en_GB">
|
||||
<img src="images/flags/gb.svg" alt="icon" width="20" height="15"> English (GB)
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="en_US">
|
||||
<img src="images/flags/us.svg" alt="icon" width="20" height="15"> English (US)
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="eu_ES">
|
||||
<img src="images/flags/eu.svg" alt="icon" width="20" height="15"> Euskara
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="es_ES">
|
||||
<img src="images/flags/es.svg" alt="icon" width="20" height="15"> Español
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="fr_FR">
|
||||
<img src="images/flags/fr.svg" alt="icon" width="20" height="15"> Français
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="it_IT">
|
||||
<img src="images/flags/it.svg" alt="icon" width="20" height="15"> Italiano
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="nl_NL">
|
||||
<img src="images/flags/nl.svg" alt="icon" width="20" height="15"> Nederlands
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="pl_PL">
|
||||
<img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="pt_BR">
|
||||
<img src="images/flags/pt_br.svg" alt="icon" width="20" height="15"> Português (BR)
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="ro_RO">
|
||||
<img src="images/flags/ro.svg" alt="icon" width="20" height="15"> Romanian
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="sv_SE">
|
||||
<img src="images/flags/se.svg" alt="icon" width="20" height="15"> Svenska
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="ru_RU">
|
||||
<img src="images/flags/ru.svg" alt="icon" width="20" height="15"> Русский
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="ko_KR">
|
||||
<img src="images/flags/kr.svg" alt="icon" width="20" height="15"> 한국어
|
||||
</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="ja_JP">
|
||||
<img src="images/flags/jp.svg" alt="icon" width="20" height="15"> 日本語
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<th:block th:insert="~{fragments/langAndDarkMode :: langAndDarkMode}"></th:block>
|
||||
|
||||
|
||||
<li class="nav-item">
|
||||
<!-- Settings Button -->
|
||||
<a href="#" class="nav-link" data-toggle="modal" data-target="#settingsModal">
|
||||
<a href="#" class="nav-link" data-bs-toggle="modal" data-bs-target="#settingsModal">
|
||||
<img class="navbar-icon" src="images/gear.svg" alt="icon" width="24" height="24">
|
||||
</a>
|
||||
|
||||
@ -305,9 +237,7 @@
|
||||
<div class="modal-content dark-card">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="settingsModalLabel" th:text="#{settings.title}"></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
@ -318,14 +248,11 @@
|
||||
<a href="swagger-ui/index.html" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary">API</button>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/Frooodle/Stirling-PDF/releases" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="update-btn" th:utext="#{settings.update}"></button>
|
||||
</a>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="downloadOption" th:utext="#{settings.downloadOption.title}"></label>
|
||||
<select class="form-control" id="downloadOption">
|
||||
<option value="sameWindow" th:utext="#{settings.downloadOption.1}"></option>
|
||||
@ -333,37 +260,31 @@
|
||||
<option value="downloadFile" th:utext="#{settings.downloadOption.3}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="zipThreshold" th:utext="#{settings.zipThreshold}"></label>
|
||||
<input type="range" class="custom-range" min="1" max="9" step="1" id="zipThreshold" value="4">
|
||||
<span id="zipThresholdValue" class="ml-2"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="boredWaiting">
|
||||
<label class="custom-control-label" for="boredWaiting" th:text="#{bored}"></label>
|
||||
<input type="range" class="form-range" min="1" max="9" step="1" id="zipThreshold" value="4">
|
||||
<span id="zipThresholdValue" class="ms-2"></span>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="boredWaiting">
|
||||
<label class="form-check-label" for="boredWaiting" th:text="#{bored}"></label>
|
||||
</div>
|
||||
<a th:if="${@loginEnabled}" href="account" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" th:text="#{settings.accountSettings}">Account Settings</button>
|
||||
</a>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-footer">
|
||||
<a th:if="${@loginEnabled}" href="/logout">
|
||||
<button type="button" class="btn btn-danger" th:text="#{settings.signOut}">Sign Out</button>
|
||||
</a>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal" th:text="#{close}"></button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script src="js/settings.js"></script>
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div th:fragment="navbarEntry (endpoint, imgSrc, titleKey, descKey, tagKey)" th:if="${@endpointConfiguration.isEndpointEnabled(endpoint)}">
|
||||
<a class="dropdown-item" href="#" th:href="@{${endpoint}}" th:classappend="${endpoint.equals(currentPage)} ? 'active' : ''" th:title="#{${descKey}}" th:data-tags="#{${tagKey}}">
|
||||
<a class="dropdown-item" href="#" th:href="@{${endpoint}}" th:classappend="${endpoint.equals(currentPage)} ? 'active' : ''" th:title="#{${descKey}}" th:data-bs-tags="#{${tagKey}}">
|
||||
<img class="icon" th:src="@{${imgSrc}}" alt="icon">
|
||||
<span class="icon-text" th:text="#{${titleKey}}"></span>
|
||||
</a>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
<script src="js/homecard.js"></script>
|
||||
|
||||
<div class=" container">
|
||||
<br>
|
||||
<input type="text" id="searchBar" onkeyup="filterCards()" placeholder="Search for features...">
|
||||
<div class="features-container ">
|
||||
|
||||
@ -89,6 +90,7 @@
|
||||
<div th:replace="~{fragments/card :: card(id='extract-page', cardTitle=#{home.extractPage.title}, cardText=#{home.extractPage.desc}, cardLink='extract-page', svgPath='images/extract.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='pdf-to-single-page', cardTitle=#{home.PdfToSinglePage.title}, cardText=#{home.PdfToSinglePage.desc}, cardLink='pdf-to-single-page', svgPath='images/single-page.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='show-javascript', cardTitle=#{home.showJS.title}, cardText=#{home.showJS.desc}, cardLink='show-javascript', svgPath='images/js.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='auto-redact', cardTitle=#{home.autoRedact.title}, cardText=#{home.autoRedact.desc}, cardLink='auto-redact', svgPath='images/eraser-fill.svg')}"></div>
|
||||
|
||||
|
||||
|
||||
|
@ -1,59 +1,99 @@
|
||||
<!-- Hi if you have been redirected here when using API then you might need to supply a X-API-KEY key in header to authenticate! -->
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Login</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
</head>
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{login.title})}"></th:block>
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
}
|
||||
|
||||
.form-signin {
|
||||
width: 100%;
|
||||
max-width: 330px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.form-signin .checkbox {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.form-signin .form-floating:focus-within {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.form-signin input[type="text"] {
|
||||
margin-bottom: -1px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
.container-flex {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
width: 100%; /* Set width to 100% */
|
||||
align-items: center; /* Center its children horizontally */
|
||||
}
|
||||
.footer-bottom {
|
||||
margin-top: auto;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div class="container-flex">
|
||||
<main class="form-signin text-center">
|
||||
|
||||
<div th:if="${logoutMessage}" class="alert alert-success"
|
||||
th:text="${logoutMessage}"></div>
|
||||
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center align-items-center" style="height:100vh;">
|
||||
<div class="col-md-4">
|
||||
<div class="login-container card">
|
||||
<div class="card-header text-center">
|
||||
<img src="favicon.svg" alt="Logo" class="img-fluid" style="max-width: 100px;"> <!-- Adjust path and style as needed -->
|
||||
<h4>Stirling-PDF Login</h4>
|
||||
</div>
|
||||
|
||||
|
||||
<div th:if="${logoutMessage}" class="alert alert-success" th:text="${logoutMessage}"></div>
|
||||
|
||||
|
||||
|
||||
<div class="card-body">
|
||||
<form th:action="@{/login}" method="post">
|
||||
<div class="form-group">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" class="form-control" required="required" />
|
||||
<img class="mb-4" src="favicon.svg" alt="" width="144" height="144">
|
||||
<h1 class="h1 mb-3 fw-normal">Stirling-PDF</h1>
|
||||
<h2 class="h5 mb-3 fw-normal">Please sign in</h2>
|
||||
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" id="username"
|
||||
placeholder="admin"> <label for="username">Username</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" class="form-control" required="required" />
|
||||
<div class="form-floating">
|
||||
<input type="password" class="form-control" id="password"
|
||||
placeholder="Password"> <label for="password">Password</label>
|
||||
</div>
|
||||
<div class="form-group text-center">
|
||||
<input type="submit" value="Login" class="btn btn-primary" />
|
||||
|
||||
<div class="checkbox mb-3">
|
||||
<label> <input type="checkbox" value="remember-me">
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
<button class="w-100 btn btn-lg btn-primary" type="submit">Sign
|
||||
in</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer text-danger text-center">
|
||||
<div th:if="${error == 'badcredentials'}">
|
||||
Invalid username or password.
|
||||
</div>
|
||||
<div th:if="${error == 'locked'}">
|
||||
Your account has been locked.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-danger text-center">
|
||||
<div th:if="${error == 'badcredentials'}">Invalid username or
|
||||
password.</div>
|
||||
<div th:if="${error == 'locked'}">Your account has been locked.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -14,15 +14,15 @@
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{merge.header}"></h2>
|
||||
<form action="merge-pdfs" method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{multiPdfDropPrompt}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=true, accept='application/pdf')}"></div>
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<ul id="selectedFiles" class="list-group"></ul>
|
||||
</div>
|
||||
<div class="form-group text-center">
|
||||
<div class="mb-3 text-center">
|
||||
<button type="button" id="sortByNameBtn" class="btn btn-info" th:text="#{merge.sortByName}"></button>
|
||||
<button type="button" id="sortByDateBtn" class="btn btn-info" th:text="#{merge.sortByDate}"></button>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{merge.submit}"></button>
|
||||
|
@ -22,7 +22,7 @@
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<br>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="customMargin" th:text="#{addPageNumbers.selectText.2}"></label> <select
|
||||
class="form-control" id="customMargin" name="customMargin"
|
||||
required>
|
||||
@ -77,7 +77,7 @@
|
||||
</style>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="position" th:text="#{addPageNumbers.selectText.3}"></label>
|
||||
<div class="a4container">
|
||||
<div class="pageNumber" id="1" style="top: 10%; left: 10%;">1</div>
|
||||
@ -94,18 +94,18 @@
|
||||
|
||||
<input type="hidden" id="numberInput" name="position" min="1"
|
||||
max="9" required>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="startingNumber" th:text="#{addPageNumbers.selectText.4}"></label> <input
|
||||
type="number" class="form-control" id="startingNumber"
|
||||
name="startingNumber" min="1" required value="1" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="pagesToNumber" th:text="#{addPageNumbers.selectText.5}"></label> <input
|
||||
type="text" class="form-control" id="pagesToNumber"
|
||||
name="pagesToNumber"
|
||||
th:placeholder="#{addPageNumbers.numberPagesDesc}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="customText" th:text="#{addPageNumbers.selectText.6}"></label> <input type="text"
|
||||
class="form-control" id="customText" name="customText"
|
||||
th:placeholder="#{addPageNumbers.customNumberDesc}" />
|
||||
|
@ -17,57 +17,57 @@
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<p class="text-muted" th:text="#{changeMetadata.selectText.1}"></p>
|
||||
|
||||
<div class="form-group-inline form-check">
|
||||
<div class="mb-3-inline form-check">
|
||||
<input type="checkbox" class="form-check-input" id="deleteAll" name="deleteAll">
|
||||
<label class="ml-3" for="deleteAll" th:text="#{changeMetadata.selectText.2}" ></label>
|
||||
<label class="ms-3" for="deleteAll" th:text="#{changeMetadata.selectText.2}" ></label>
|
||||
</div>
|
||||
|
||||
<div class="form-group-inline form-check">
|
||||
<div class="mb-3-inline form-check">
|
||||
<input type="checkbox" class="form-check-input" id="customModeCheckbox">
|
||||
<label class="ml-3" for="customModeCheckbox" th:text="#{changeMetadata.selectText.3}"></label>
|
||||
<label class="ms-3" for="customModeCheckbox" th:text="#{changeMetadata.selectText.3}"></label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label class="form-check-label" for="author" th:text="#{changeMetadata.author}"></label>
|
||||
<input type="text" class="form-control" id="author" name="author">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label class="form-check-label" for="creationDate" th:text="#{changeMetadata.creationDate}"></label>
|
||||
<input type="text" class="form-control" id="creationDate" name="creationDate" placeholder="2020/12/25 18:30:59">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label class="form-check-label" for="creator" th:text="#{changeMetadata.creator}"></label>
|
||||
<input type="text" class="form-control" id="creator" name="creator">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label class="form-check-label" for="keywords" th:text="#{changeMetadata.keywords}"></label>
|
||||
<input type="text" class="form-control" id="keywords" name="keywords">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label class="form-check-label" for="modificationDate" th:text="#{changeMetadata.modDate}"></label>
|
||||
<input type="text" class="form-control" id="modificationDate" name="modificationDate" placeholder="2020/12/25 18:30:59">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label class="form-check-label" for="producer" th:text="#{changeMetadata.producer}"></label>
|
||||
<input type="text" class="form-control" id="producer" name="producer">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label class="form-check-label" for="subject" th:text="#{changeMetadata.subject}"></label>
|
||||
<input type="text" class="form-control" id="subject" name="subject">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label class="form-check-label" for="title" th:text="#{changeMetadata.title}"></label>
|
||||
<input type="text" class="form-control" id="title" name="title">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label class="form-check-label" for="trapped" th:text="#{changeMetadata.trapped}"></label>
|
||||
<select class="form-control" id="trapped" name="trapped">
|
||||
<option value="True" th:text="#{true}"></option>
|
||||
@ -77,7 +77,7 @@
|
||||
</div>
|
||||
<div id="customMetadata" style="display: none;">
|
||||
<h3 th:text="#{changeMetadata.selectText.4}"></h3>
|
||||
<div class="form-group" id="otherMetadataEntries"></div>
|
||||
<div class="mb-3" id="otherMetadataEntries"></div>
|
||||
</div>
|
||||
<div id="customMetadataEntries"></div>
|
||||
<button type="button" class="btn btn-secondary" id="addMetadataBtn" th:text="#{changeMetadata.selectText.5}"></button>
|
||||
@ -194,7 +194,7 @@
|
||||
count = count + 1;
|
||||
|
||||
const formGroup = document.createElement("div");
|
||||
formGroup.className = "form-group";
|
||||
formGroup.className = "mb-3";
|
||||
formGroup.appendChild(keyInput);
|
||||
formGroup.appendChild(valueInput);
|
||||
|
||||
@ -227,11 +227,11 @@
|
||||
continue;
|
||||
}
|
||||
const entryDiv = document.createElement('div');
|
||||
entryDiv.className = 'form-group';
|
||||
entryDiv.className = 'mb-3';
|
||||
|
||||
|
||||
|
||||
entryDiv.innerHTML = `<div class="form-group"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`;
|
||||
entryDiv.innerHTML = `<div class="mb-3"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`;
|
||||
otherMetadataEntriesDiv.appendChild(entryDiv);
|
||||
}
|
||||
} else {
|
||||
|
@ -16,27 +16,27 @@
|
||||
|
||||
<form id="multiPdfForm" th:action="@{extract-image-scans}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='image/*, application/pdf')}"></div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="angleThreshold" th:text="#{ScannerImageSplit.selectText.1}"></label>
|
||||
<input type="number" class="form-control" id="angleThreshold" name="angle_threshold" value="10">
|
||||
<small id="angleThresholdHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.2}"></small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="tolerance" th:text="#{ScannerImageSplit.selectText.3}"></label>
|
||||
<input type="number" class="form-control" id="tolerance" name="tolerance" value="20">
|
||||
<small id="toleranceHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.4}"></small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="minArea" th:text="#{ScannerImageSplit.selectText.5}"></label>
|
||||
<input type="number" class="form-control" id="minArea" name="min_area" value="8000">
|
||||
<small id="minAreaHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.6}"></small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="minContourArea" th:text="#{ScannerImageSplit.selectText.7}"></label>
|
||||
<input type="number" class="form-control" id="minContourArea" name="min_contour_area" value="500">
|
||||
<small id="minContourAreaHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.8}"></small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="borderSize" th:text="#{ScannerImageSplit.selectText.9}"></label>
|
||||
<input type="number" class="form-control" id="borderSize" name="border_size" value="1">
|
||||
<small id="borderSizeHelp" class="form-text text-muted" th:text="#{ScannerImageSplit.selectText.10}"></small>
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
<form id="multiPdfForm" th:action="@{extract-images}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{extractImages.selectText}"></label>
|
||||
<select class="form-control" name="format">
|
||||
<option value="png">PNG</option>
|
||||
|
@ -16,7 +16,7 @@
|
||||
<h2 th:text="#{pageLayout.header}"></h2>
|
||||
<form id="multiPdfForm" th:action="@{multi-page-layout}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="pagesPerSheet" th:text="#{pageLayout.pagesPerSheet}"></label>
|
||||
<select id="pagesPerSheet" name="pagesPerSheet" required>
|
||||
<option value="2">2</option>
|
||||
|
@ -40,7 +40,7 @@
|
||||
<h2 th:text="#{ocr.header}"></h2>
|
||||
<form th:if="${#lists.size(languages) > 0}" action="#" th:action="@{/ocr-pdf}" method="post" enctype="multipart/form-data" class="mb-3">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="languages" class="form-label" th:text="#{ocr.selectText.1}"></label>
|
||||
<hr>
|
||||
<div id="languages">
|
||||
@ -51,7 +51,7 @@
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{ocr.selectText.10}"></label>
|
||||
<select class="form-control" name="ocrType">
|
||||
<option value="skip-text" th:text="#{ocr.selectText.6}"></option>
|
||||
@ -83,7 +83,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{ocr.selectText.12}"></label>
|
||||
<select class="form-control" name="ocrRenderType">
|
||||
<option value="hocr">HOCR (Latin/Roman alphabet only)</option>
|
||||
|
@ -16,12 +16,12 @@
|
||||
|
||||
<form id="multiPdfForm" th:action="@{remove-blanks}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="threshold" th:text="#{removeBlanks.threshold}"></label>
|
||||
<input type="number" class="form-control" id="threshold" name="threshold" value="10">
|
||||
<small id="thresholdHelp" class="form-text text-muted" th:text="#{removeBlanks.thresholdDesc}"></small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="whitePercent" th:text="#{removeBlanks.whitePercent}"></label>
|
||||
<input type="number" class="form-control" id="whitePercent" name="whitePercent" value="99.9" step="0.1">
|
||||
<small id="whitePercentHelp" class="form-text text-muted" th:text="#{removeBlanks.whitePercentDesc}"></small>
|
||||
|
@ -16,7 +16,7 @@
|
||||
<h2 th:text="#{scalePages.header}"></h2>
|
||||
<form id="scalePagesFrom" th:action="@{scale-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="pageSize" th:text="#{scalePages.pageSize}"></label>
|
||||
<select id="pageSize" name="pageSize" required>
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
<option value="LEDGER">Ledger</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="scaleFactor" th:text="#{scalePages.scaleFactor}"></label>
|
||||
<input type="number" id="scaleFactor" name="scaleFactor" step="any" min="0" value="1">
|
||||
</div>
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
<form th:action="@{rearrange-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="customMode">Mode</label>
|
||||
<select class="form-control" id="customMode" name="customMode">
|
||||
<option value="">Custom Page Order</option>
|
||||
@ -29,7 +29,7 @@
|
||||
<option value="REMOVE_FIRST_AND_LAST">Remove First and Last</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
|
||||
<input type="text" class="form-control" id="pageOrder" name="pageOrder" placeholder="(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)" required>
|
||||
</div>
|
||||
|
@ -37,11 +37,11 @@
|
||||
<div class="row justify-content-center">
|
||||
|
||||
<div class="bordered-box">
|
||||
<div class="text-right text-top">
|
||||
<div class="text-end text-top">
|
||||
<button id="uploadPipelineBtn" class="btn btn-primary">Upload
|
||||
Custom</button>
|
||||
<button type="button" class="btn btn-primary" data-toggle="modal"
|
||||
data-target="#pipelineSettingsModal">Configure</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal"
|
||||
data-bs-target="#pipelineSettingsModal">Configure</button>
|
||||
</div>
|
||||
|
||||
<div class="center-element">
|
||||
@ -71,7 +71,7 @@
|
||||
<!-- Modal Header -->
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Pipeline Configuration</h2>
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<button type="button" class="close" data-bs-dismiss="modal">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Modal body -->
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
<form th:action="@{remove-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="pagesToDelete" th:text="#{pageRemover.pagesToDelete}"></label>
|
||||
<input type="text" class="form-control" id="fileInput" name="pagesToDelete" placeholder="(e.g. 1,2,6 or 1-10,15-30)" required>
|
||||
</div>
|
||||
|
@ -14,27 +14,27 @@
|
||||
<h2 th:text="#{addPassword.header}"></h2>
|
||||
|
||||
<form action="add-password" method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.14}"></label> <input type="password" class="form-control" id="ownerPassword" name="ownerPassword">
|
||||
<small class="form-text text-muted" th:text="#{addPassword.selectText.15}"></small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.2}"></label> <input type="password" class="form-control" id="password" name="password">
|
||||
<small class="form-text text-muted" th:text="#{addPassword.selectText.16}"></small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.3}"></label> <select class="form-control" id="keyLength" name="keyLength">
|
||||
<option value="40">40</option>
|
||||
<option value="128">128</option>
|
||||
<option value="256">256</option>
|
||||
</select> <small class="form-text text-muted" th:text="#{addPassword.selectText.4}"></small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{addPassword.selectText.5}"></label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
|
||||
@ -71,7 +71,7 @@
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<div class="form-group text-center">
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{addPassword.submit}"></button>
|
||||
</div>
|
||||
|
||||
|
@ -14,14 +14,14 @@
|
||||
<h2 th:text="#{watermark.header}"></h2>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" action="add-watermark">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{watermark.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}">
|
||||
<input type="file" id="fileInput" name="fileInput" class="form-control-file" accept="application/pdf" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{watermark.selectText.8}"></label>
|
||||
<select class="form-control" id="watermarkType" name="watermarkType" onchange="toggleFileOption()">
|
||||
<option value="text">Text</option>
|
||||
@ -29,21 +29,21 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="watermarkTextGroup" class="form-group">
|
||||
<div id="watermarkTextGroup" class="mb-3">
|
||||
<label for="watermarkText" th:text="#{watermark.selectText.2}"></label>
|
||||
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required />
|
||||
</div>
|
||||
|
||||
<div id="watermarkImageGroup" class="form-group" style="display: none;">
|
||||
<div id="watermarkImageGroup" class="mb-3" style="display: none;">
|
||||
<label for="watermarkImage" th:text="#{watermark.selectText.9}"></label>
|
||||
<input type="file" id="watermarkImage" name="watermarkImage" class="form-control-file" accept="image/*" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="fontSize" th:text="#{watermark.selectText.3}"></label>
|
||||
<input type="text" id="fontSize" name="fontSize" class="form-control" value="30" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="opacity" th:text="#{watermark.selectText.7}"></label>
|
||||
<input type="text" id="opacity" name="opacityText" class="form-control" value="50" onblur="updateOpacityValue()" />
|
||||
<input type="hidden" id="opacityReal" name="opacity" value="0.5">
|
||||
@ -83,19 +83,19 @@
|
||||
appendPercentageSymbol();
|
||||
</script>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="rotation" th:text="#{watermark.selectText.4}"></label>
|
||||
<input type="text" id="rotation" name="rotation" class="form-control" value="45" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="widthSpacer" th:text="#{watermark.selectText.5}"></label>
|
||||
<input type="text" id="widthSpacer" name="widthSpacer" class="form-control" value="50" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="heightSpacer" th:text="#{watermark.selectText.6}"></label>
|
||||
<input type="text" id="heightSpacer" name="heightSpacer" class="form-control" value="50" />
|
||||
</div>
|
||||
<div class="form-group text-center">
|
||||
<div class="mb-3 text-center">
|
||||
<input type="submit" id="submitBtn" th:value="#{watermark.submit}" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
|
53
src/main/resources/templates/security/auto-redact.html
Normal file
53
src/main/resources/templates/security/auto-redact.html
Normal file
@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{autoRedact.title})}"></th:block>
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{autoRedact.header}"></h2>
|
||||
<form action="/auto-redact" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<input type="file" class="form-control" id="fileInput" name="fileInput" required accept="application/pdf">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="listOfText" class="form-label" th:text="#{autoRedact.textsToRedactLabel}"></label>
|
||||
<textarea class="form-control" id="listOfText" name="listOfText" rows="4" required th:placeholder="#{autoRedact.textsToRedactPlaceholder}"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="useRegex" name="useRegex">
|
||||
<label class="form-check-label" for="useRegex" th:text="#{autoRedact.useRegexLabel}"></label>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="wholeWordSearch" name="wholeWordSearch">
|
||||
<label class="form-check-label" for="wholeWordSearch" th:text="#{autoRedact.wholeWordSearchLabel}"></label>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="customPadding" class="form-label" th:text="#{autoRedact.customPaddingLabel}"></label>
|
||||
<input type="number" step="0.1" class="form-control" id="customPadding" name="customPadding" value="0.1">
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="convertPDFToImage" name="convertPDFToImage" checked>
|
||||
<label class="form-check-label" for="convertPDFToImage" th:text="#{autoRedact.convertPDFToImageLabel}"></label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" th:text="#{autoRedact.submitButton}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -18,12 +18,12 @@
|
||||
|
||||
<form action="/cert-sign" method="post"
|
||||
enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.selectPDF}"></label>
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="certType" th:text="#{certSign.certType}"></label> <select
|
||||
class="form-control" id="certType" name="certType">
|
||||
<option value="" th:text="#{selectFillter}"></option>
|
||||
@ -32,50 +32,50 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="p12Group" style="display: none;">
|
||||
<div class="mb-3" id="p12Group" style="display: none;">
|
||||
<label th:text="#{certSign.selectP12}"></label>
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='p12', notRequired=true, multiple=false, accept='.p12,.pfx')}"></div>
|
||||
</div>
|
||||
|
||||
<div id="pemGroup" style="display: none;">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.selectKey}"></label>
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='key', multiple=false, notRequired=true, accept='.pem,.der')}"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.selectCert}"></label>
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='cert', multiple=false, notRequired=true, accept='.pem,.der')}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{certSign.password}"></label> <input
|
||||
type="password" class="form-control" id="password"
|
||||
name="password">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label><input type="checkbox" id="showSignature"
|
||||
name="showSignature" th:text="#{certSign.showSig}"></label>
|
||||
</div>
|
||||
|
||||
<div id="signatureDetails" style="display: none;">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="reason" th:text="#{certSign.reason}"></label> <input type="text"
|
||||
class="form-control" id="reason" name="reason">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="location" th:text="#{certSign.location}"></label> <input type="text"
|
||||
class="form-control" id="location" name="location">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="name" th:text="#{certSign.name}"></label> <input type="text"
|
||||
class="form-control" id="name" name="name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="pageNumber" th:text="#{pageNum}"></label> <input
|
||||
type="number" class="form-control" id="pageNumber"
|
||||
name="pageNumber" min="1">
|
||||
@ -120,7 +120,7 @@
|
||||
</script>
|
||||
|
||||
|
||||
<div class="form-group text-center">
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary"
|
||||
th:text="#{certSign.submit}"></button>
|
||||
</div>
|
||||
|
@ -15,11 +15,11 @@
|
||||
<h2 th:text="#{permissions.header}"></h2>
|
||||
<p th:text="#{permissions.warning}"></p>
|
||||
<form action="add-password" method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{permissions.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{permissions.selectText.2}"></label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="canAssembleDocument" name="canAssembleDocument">
|
||||
@ -56,7 +56,7 @@
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<div class="form-group text-center">
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{permissions.submit}"></button>
|
||||
</div>
|
||||
|
||||
|
@ -142,13 +142,17 @@
|
||||
const buttonElem = document.createElement('button');
|
||||
buttonElem.className = 'btn btn-link';
|
||||
buttonElem.type = 'button';
|
||||
buttonElem.dataset.toggle = "collapse";
|
||||
buttonElem.dataset.target = `#${safeKey}-content-${depth}`;
|
||||
buttonElem.dataset.bsToggle = "collapse";
|
||||
buttonElem.dataset.bsTarget = `#${safeKey}-content-${depth}`;
|
||||
buttonElem.setAttribute('aria-expanded', 'true');
|
||||
buttonElem.setAttribute('aria-controls', `${safeKey}-content-${depth}`);
|
||||
buttonElem.textContent = key;
|
||||
return buttonElem;
|
||||
}
|
||||
const collapsibleElems = document.querySelectorAll('[data-bs-toggle="collapse"]');
|
||||
collapsibleElems.forEach(elem => {
|
||||
new bootstrap.Collapse(elem);
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -14,16 +14,16 @@
|
||||
<h2 th:text="#{removePassword.header}"></h2>
|
||||
|
||||
<form action="remove-password" method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{removePassword.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{removePassword.selectText.2}"></label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<br />
|
||||
<div class="form-group text-center">
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{removePassword.submit}"></button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -14,15 +14,15 @@
|
||||
<h2 th:text="#{remove-watermark.header}"></h2>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" action="remove-watermark">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label th:text="#{remove-watermark.selectText.1}"></label>
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="watermarkText" th:text="#{remove-watermark.selectText.2}"></label>
|
||||
<input type="text" id="watermarkText" name="watermarkText" class="form-control" placeholder="Stirling-PDF" required />
|
||||
</div>
|
||||
<div class="form-group text-center">
|
||||
<div class="mb-3 text-center">
|
||||
<input type="submit" id="submitBtn" th:value="#{remove-watermark.submit}" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
|
@ -14,7 +14,7 @@
|
||||
<h2 th:text="#{sanitizePDF.header}"></h2>
|
||||
|
||||
<form action="sanitize-pdf" method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
@ -38,7 +38,7 @@
|
||||
<label class="form-check-label" for="removeFonts" th:text="#{sanitizePDF.selectText.5}"></label>
|
||||
</div>
|
||||
<br />
|
||||
<div class="form-group text-center">
|
||||
<div class="mb-3 text-center">
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{sanitizePDF.submit}"></button>
|
||||
</div>
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
<form th:action="@{split-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<label for="pages" th:text="#{split.splitPages}"></label>
|
||||
<input type="text" class="form-control" id="pages" name="pages" placeholder="1,3,5-10" required>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user