mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +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;
 | 
			
		||||
    }
 | 
			
		||||
	
 | 
			
		||||
	@Bean(name = "loginEnabled")
 | 
			
		||||
 | 
			
		||||
    @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());
 | 
			
		||||
@ -35,10 +40,9 @@ public class Beans implements WebMvcConfigurer {
 | 
			
		||||
    @Bean
 | 
			
		||||
    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();
 | 
			
		||||
    }
 | 
			
		||||
@ -198,21 +206,19 @@ 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
 | 
			
		||||
	private UserService userService;
 | 
			
		||||
 | 
			
		||||
    @PostConstruct
 | 
			
		||||
    public void init() {
 | 
			
		||||
        if(!userService.hasUsers()) {
 | 
			
		||||
            String initialUsername = System.getenv("INITIAL_USERNAME");
 | 
			
		||||
            String initialPassword = System.getenv("INITIAL_PASSWORD");
 | 
			
		||||
            if(initialUsername != null && initialPassword != null) {
 | 
			
		||||
                userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
 | 
			
		||||
            }
 | 
			
		||||
             
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
	@Autowired
 | 
			
		||||
	ApplicationProperties applicationProperties;
 | 
			
		||||
	
 | 
			
		||||
	@PostConstruct
 | 
			
		||||
	public void init() {
 | 
			
		||||
		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,8 +59,12 @@ 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/"))
 | 
			
		||||
	                    .permitAll()
 | 
			
		||||
@ -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,7 +91,12 @@ 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,37 +1,45 @@
 | 
			
		||||
function updateFavoritesDropdown() {
 | 
			
		||||
	var dropdown = document.querySelector('#favoritesDropdown');
 | 
			
		||||
	dropdown.innerHTML = '';  // Clear the current favorites
 | 
			
		||||
    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++) {
 | 
			
		||||
        var key = localStorage.key(i);
 | 
			
		||||
        if (localStorage.getItem(key) === 'favorite') {
 | 
			
		||||
            // Find the corresponding navbar entry
 | 
			
		||||
            var navbarEntry = document.querySelector(`a[href='${key}']`);
 | 
			
		||||
            if (navbarEntry) {
 | 
			
		||||
                // Create a new dropdown entry
 | 
			
		||||
                var dropdownItem = document.createElement('a');
 | 
			
		||||
                dropdownItem.className = 'dropdown-item';
 | 
			
		||||
                dropdownItem.href = navbarEntry.href;
 | 
			
		||||
                dropdownItem.innerHTML = navbarEntry.innerHTML;
 | 
			
		||||
                dropdown.appendChild(dropdownItem);
 | 
			
		||||
                hasFavorites = true;
 | 
			
		||||
            } else {
 | 
			
		||||
                console.warn(`Navbar entry not found for key: ${key}`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	var hasFavorites = false;
 | 
			
		||||
 | 
			
		||||
	for (var i = 0; i < localStorage.length; i++) {
 | 
			
		||||
		var key = localStorage.key(i);
 | 
			
		||||
		if (localStorage.getItem(key) === 'favorite') {
 | 
			
		||||
			// Find the corresponding navbar entry
 | 
			
		||||
			var navbarEntry = document.querySelector(`a[href='${key}']`);
 | 
			
		||||
			if (navbarEntry) {
 | 
			
		||||
				// Create a new dropdown entry
 | 
			
		||||
				var dropdownItem = document.createElement('a');
 | 
			
		||||
				dropdownItem.className = 'dropdown-item';
 | 
			
		||||
				dropdownItem.href = navbarEntry.href;
 | 
			
		||||
				dropdownItem.innerHTML = navbarEntry.innerHTML;
 | 
			
		||||
				dropdown.appendChild(dropdownItem);
 | 
			
		||||
				hasFavorites = true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Show or hide the default item based on whether there are any favorites
 | 
			
		||||
	if (!hasFavorites) {
 | 
			
		||||
		var defaultItem = document.createElement('a');
 | 
			
		||||
		defaultItem.className = 'dropdown-item';
 | 
			
		||||
		defaultItem.textContent = noFavourites;
 | 
			
		||||
		dropdown.appendChild(defaultItem);
 | 
			
		||||
	}
 | 
			
		||||
    // Show or hide the default item based on whether there are any favorites
 | 
			
		||||
    if (!hasFavorites) {
 | 
			
		||||
        var defaultItem = document.createElement('a');
 | 
			
		||||
        defaultItem.className = 'dropdown-item';
 | 
			
		||||
        defaultItem.textContent = noFavourites;
 | 
			
		||||
        dropdown.appendChild(defaultItem);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
 | 
			
		||||
	updateFavoritesDropdown();
 | 
			
		||||
});
 | 
			
		||||
// 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;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -14,33 +14,40 @@ document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function handleDropdownItemClick(event) {
 | 
			
		||||
	event.preventDefault();
 | 
			
		||||
	const languageCode = this.dataset.languageCode;
 | 
			
		||||
	localStorage.setItem('languageCode', languageCode);
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
    const languageCode = event.currentTarget.dataset.bsLanguageCode;  // change this to event.currentTarget
 | 
			
		||||
    if (languageCode) {
 | 
			
		||||
        localStorage.setItem('languageCode', languageCode);
 | 
			
		||||
 | 
			
		||||
	const currentUrl = window.location.href;
 | 
			
		||||
	if (currentUrl.indexOf('?lang=') === -1) {
 | 
			
		||||
		window.location.href = currentUrl + '?lang=' + languageCode;
 | 
			
		||||
	} else {
 | 
			
		||||
		window.location.href = currentUrl.replace(/\?lang=\w{2,}/, '?lang=' + languageCode);
 | 
			
		||||
	}
 | 
			
		||||
        const currentUrl = window.location.href;
 | 
			
		||||
        if (currentUrl.indexOf('?lang=') === -1) {
 | 
			
		||||
            window.location.href = currentUrl + '?lang=' + languageCode;
 | 
			
		||||
        } 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();
 | 
			
		||||
		return (A < B) ? -1 : (A > B) ? 1 : 0;
 | 
			
		||||
	})
 | 
			
		||||
		.appendTo('.dropdown-menu[aria-labelledby="languageDropdown"]');
 | 
			
		||||
	    var A = a.textContent.toUpperCase();
 | 
			
		||||
	    var B = b.textContent.toUpperCase();
 | 
			
		||||
	    return (A < B) ? -1 : (A > B) ? 1 : 0;
 | 
			
		||||
	}).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"
 | 
			
		||||
        }
 | 
			
		||||
@ -16,4 +16,4 @@
 | 
			
		||||
    "theme_color": "#ffffff",
 | 
			
		||||
    "background_color": "#ffffff",
 | 
			
		||||
    "display": "standalone"
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@ -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>
 | 
			
		||||
        <div class="selected-files"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <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>
 | 
			
		||||
                    <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="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>
 | 
			
		||||
                  </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-danger" th:text="#{settings.signOut}">Sign Out</button>
 | 
			
		||||
	            </a>
 | 
			
		||||
                <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>
 | 
			
		||||
        
 | 
			
		||||
		<form th:action="@{/login}" method="post">
 | 
			
		||||
			<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="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" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
                            <label for="password">Password:</label>
 | 
			
		||||
                            <input type="password" id="password" name="password" class="form-control" required="required" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="form-group text-center">
 | 
			
		||||
                            <input type="submit" value="Login" class="btn btn-primary" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </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>
 | 
			
		||||
             </div>
 | 
			
		||||
        <div th:insert="~{fragments/footer.html :: footer}"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
			<div class="form-floating">
 | 
			
		||||
				<input type="text" class="form-control" id="username"
 | 
			
		||||
					placeholder="admin"> <label for="username">Username</label>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="form-floating">
 | 
			
		||||
				<input type="password" class="form-control" id="password"
 | 
			
		||||
					placeholder="Password"> <label for="password">Password</label>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<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 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