stats and conditionals
This commit is contained in:
parent
a2926b8fe9
commit
c2fec0a030
13 changed files with 420 additions and 115 deletions
22
Dockerfile-lite
Normal file
22
Dockerfile-lite
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Build jbig2enc in a separate stage
|
||||||
|
FROM frooodle/stirling-pdf-base:beta2
|
||||||
|
|
||||||
|
# Create scripts folder and copy local scripts
|
||||||
|
RUN mkdir /scripts
|
||||||
|
COPY ./scripts/* /scripts/
|
||||||
|
|
||||||
|
# Copy the application JAR file
|
||||||
|
COPY build/libs/*.jar app.jar
|
||||||
|
|
||||||
|
# Expose the application port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV APP_HOME_NAME="Stirling PDF"
|
||||||
|
#ENV APP_HOME_DESCRIPTION="Personal PDF Website!"
|
||||||
|
#ENV APP_NAVBAR_NAME="Stirling PDF"
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
RUN chmod +x /scripts/init.sh
|
||||||
|
ENTRYPOINT ["/scripts/init.sh"]
|
||||||
|
CMD ["java", "-jar", "/app.jar"]
|
57
DockerfileBase-lite
Normal file
57
DockerfileBase-lite
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# Build jbig2enc in a separate stage
|
||||||
|
FROM debian:bullseye-slim as jbig2enc_builder
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
git \
|
||||||
|
automake \
|
||||||
|
autoconf \
|
||||||
|
libtool \
|
||||||
|
libleptonica-dev \
|
||||||
|
pkg-config \
|
||||||
|
ca-certificates \
|
||||||
|
zlib1g-dev \
|
||||||
|
make \
|
||||||
|
g++
|
||||||
|
|
||||||
|
RUN git clone https://github.com/agl/jbig2enc && \
|
||||||
|
cd jbig2enc && \
|
||||||
|
./autogen.sh && \
|
||||||
|
./configure && \
|
||||||
|
make && \
|
||||||
|
make install
|
||||||
|
|
||||||
|
|
||||||
|
# Main stage
|
||||||
|
FROM openjdk:17-jdk-slim AS base
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
python3-uno \
|
||||||
|
python3-pip \
|
||||||
|
unoconv \
|
||||||
|
pngquant \
|
||||||
|
unpaper \
|
||||||
|
ocrmypdf && \
|
||||||
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
mkdir /usr/share/tesseract-ocr-original && \
|
||||||
|
cp -r /usr/share/tesseract-ocr/* /usr/share/tesseract-ocr-original && \
|
||||||
|
rm -rf /usr/share/tesseract-ocr
|
||||||
|
|
||||||
|
# Python packages stage
|
||||||
|
FROM base AS python-packages
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
libffi-dev \
|
||||||
|
libssl-dev \
|
||||||
|
zlib1g-dev \
|
||||||
|
libjpeg-dev && \
|
||||||
|
pip install --upgrade pip && \
|
||||||
|
pip install --no-cache-dir \
|
||||||
|
opencv-python-headless && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Final stage: Copy necessary files from the previous stage
|
||||||
|
FROM base
|
||||||
|
COPY --from=python-packages /usr/local /usr/local
|
||||||
|
COPY --from=jbig2enc_builder /usr/local/bin/jbig2 /usr/local/bin/jbig2
|
|
@ -24,6 +24,11 @@ dependencies {
|
||||||
|
|
||||||
//general PDF
|
//general PDF
|
||||||
implementation 'org.apache.pdfbox:pdfbox:2.0.28'
|
implementation 'org.apache.pdfbox:pdfbox:2.0.28'
|
||||||
|
|
||||||
|
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||||
|
implementation 'io.micrometer:micrometer-core'
|
||||||
|
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EndpointConfiguration {
|
||||||
|
|
||||||
|
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
|
||||||
|
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public EndpointConfiguration() {
|
||||||
|
init();
|
||||||
|
processEnvironmentConfigs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableEndpoint(String endpoint) {
|
||||||
|
endpointStatuses.put(endpoint, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableEndpoint(String endpoint) {
|
||||||
|
endpointStatuses.put(endpoint, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEndpointEnabled(String endpoint) {
|
||||||
|
if (endpoint.startsWith("/")) {
|
||||||
|
endpoint = endpoint.substring(1);
|
||||||
|
}
|
||||||
|
return endpointStatuses.getOrDefault(endpoint, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEndpointToGroup(String group, String endpoint) {
|
||||||
|
endpointGroups.computeIfAbsent(group, k -> new HashSet<>()).add(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableGroup(String group) {
|
||||||
|
Set<String> endpoints = endpointGroups.get(group);
|
||||||
|
if (endpoints != null) {
|
||||||
|
for (String endpoint : endpoints) {
|
||||||
|
enableEndpoint(endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableGroup(String group) {
|
||||||
|
Set<String> endpoints = endpointGroups.get(group);
|
||||||
|
if (endpoints != null) {
|
||||||
|
for (String endpoint : endpoints) {
|
||||||
|
disableEndpoint(endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
// Adding endpoints to "PageOps" group
|
||||||
|
addEndpointToGroup("PageOps", "remove-pages");
|
||||||
|
addEndpointToGroup("PageOps", "merge-pdfs");
|
||||||
|
addEndpointToGroup("PageOps", "split-pdfs");
|
||||||
|
addEndpointToGroup("PageOps", "pdf-organizer");
|
||||||
|
addEndpointToGroup("PageOps", "rotate-pdf");
|
||||||
|
|
||||||
|
// Adding endpoints to "Convert" group
|
||||||
|
addEndpointToGroup("Convert", "pdf-to-img");
|
||||||
|
addEndpointToGroup("Convert", "img-to-pdf");
|
||||||
|
addEndpointToGroup("Convert", "pdf-to-pdfa");
|
||||||
|
addEndpointToGroup("Convert", "file-to-pdf");
|
||||||
|
addEndpointToGroup("Convert", "xlsx-to-pdf");
|
||||||
|
addEndpointToGroup("Convert", "pdf-to-word");
|
||||||
|
addEndpointToGroup("Convert", "pdf-to-presentation");
|
||||||
|
addEndpointToGroup("Convert", "pdf-to-text");
|
||||||
|
addEndpointToGroup("Convert", "pdf-to-html");
|
||||||
|
addEndpointToGroup("Convert", "pdf-to-xml");
|
||||||
|
|
||||||
|
// Adding endpoints to "Security" group
|
||||||
|
addEndpointToGroup("Security", "add-password");
|
||||||
|
addEndpointToGroup("Security", "remove-password");
|
||||||
|
addEndpointToGroup("Security", "change-permissions");
|
||||||
|
addEndpointToGroup("Security", "add-watermark");
|
||||||
|
|
||||||
|
// Adding endpoints to "Other" group
|
||||||
|
addEndpointToGroup("Other", "ocr-pdf");
|
||||||
|
addEndpointToGroup("Other", "add-image");
|
||||||
|
addEndpointToGroup("Other", "compress-pdf");
|
||||||
|
addEndpointToGroup("Other", "extract-images");
|
||||||
|
addEndpointToGroup("Other", "change-metadata");
|
||||||
|
addEndpointToGroup("Other", "extract-image-scans");
|
||||||
|
addEndpointToGroup("Other", "sign");
|
||||||
|
addEndpointToGroup("Other", "flatten");
|
||||||
|
addEndpointToGroup("Other", "repair");
|
||||||
|
addEndpointToGroup("Other", "remove-blanks");
|
||||||
|
addEndpointToGroup("Other", "compare");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//CLI
|
||||||
|
addEndpointToGroup("CLI", "compress-pdf");
|
||||||
|
addEndpointToGroup("CLI", "extract-image-scans");
|
||||||
|
addEndpointToGroup("CLI", "remove-blanks");
|
||||||
|
addEndpointToGroup("CLI", "repair");
|
||||||
|
addEndpointToGroup("CLI", "pdf-to-pdfa");
|
||||||
|
addEndpointToGroup("CLI", "file-to-pdf");
|
||||||
|
addEndpointToGroup("CLI", "xlsx-to-pdf");
|
||||||
|
addEndpointToGroup("CLI", "pdf-to-word");
|
||||||
|
addEndpointToGroup("CLI", "pdf-to-presentation");
|
||||||
|
addEndpointToGroup("CLI", "pdf-to-text");
|
||||||
|
addEndpointToGroup("CLI", "pdf-to-html");
|
||||||
|
addEndpointToGroup("CLI", "pdf-to-xml");
|
||||||
|
|
||||||
|
//python
|
||||||
|
addEndpointToGroup("Python", "extract-image-scans");
|
||||||
|
addEndpointToGroup("Python", "remove-blanks");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//openCV
|
||||||
|
addEndpointToGroup("OpenCV", "extract-image-scans");
|
||||||
|
addEndpointToGroup("OpenCV", "remove-blanks");
|
||||||
|
|
||||||
|
//LibreOffice
|
||||||
|
addEndpointToGroup("LibreOffice", "repair");
|
||||||
|
addEndpointToGroup("LibreOffice", "file-to-pdf");
|
||||||
|
addEndpointToGroup("LibreOffice", "xlsx-to-pdf");
|
||||||
|
addEndpointToGroup("LibreOffice", "pdf-to-word");
|
||||||
|
addEndpointToGroup("LibreOffice", "pdf-to-presentation");
|
||||||
|
addEndpointToGroup("LibreOffice", "pdf-to-text");
|
||||||
|
addEndpointToGroup("LibreOffice", "pdf-to-html");
|
||||||
|
addEndpointToGroup("LibreOffice", "pdf-to-xml");
|
||||||
|
|
||||||
|
|
||||||
|
//OCRmyPDF
|
||||||
|
addEndpointToGroup("OCRmyPDF", "compress-pdf");
|
||||||
|
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa");
|
||||||
|
|
||||||
|
disableEndpoint("remove-pages");
|
||||||
|
disableEndpoint("compress-pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processEnvironmentConfigs() {
|
||||||
|
String endpointsToRemove = System.getenv("ENDPOINTS_TO_REMOVE");
|
||||||
|
String groupsToRemove = System.getenv("GROUPS_TO_REMOVE");
|
||||||
|
|
||||||
|
if (endpointsToRemove != null) {
|
||||||
|
String[] endpoints = endpointsToRemove.split(",");
|
||||||
|
for (String endpoint : endpoints) {
|
||||||
|
disableEndpoint(endpoint.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupsToRemove != null) {
|
||||||
|
String[] groups = groupsToRemove.split(",");
|
||||||
|
for (String group : groups) {
|
||||||
|
disableGroup(group.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class EndpointInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EndpointConfiguration endpointConfiguration;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||||
|
throws Exception {
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
System.out.println("trying " + requestURI);
|
||||||
|
if (!endpointConfiguration.isEndpointEnabled(requestURI)) {
|
||||||
|
response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package stirling.software.SPDF.config;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Counter;
|
||||||
|
import io.micrometer.core.instrument.Meter;
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import io.micrometer.core.instrument.config.MeterFilter;
|
||||||
|
import io.micrometer.core.instrument.config.MeterFilterReply;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class MetricsConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public MeterFilter meterFilter() {
|
||||||
|
return new MeterFilter() {
|
||||||
|
@Override
|
||||||
|
public MeterFilterReply accept(Meter.Id id) {
|
||||||
|
if (id.getName().equals("http.requests") || id.getName().equals("health")) {
|
||||||
|
return MeterFilterReply.NEUTRAL;
|
||||||
|
}
|
||||||
|
return MeterFilterReply.DENY;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Counter;
|
||||||
|
import io.micrometer.core.instrument.Meter;
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import io.micrometer.core.instrument.config.MeterFilter;
|
||||||
|
import io.micrometer.core.instrument.config.MeterFilterReply;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class MetricsFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private final MeterRegistry meterRegistry;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public MetricsFilter(MeterRegistry meterRegistry) {
|
||||||
|
this.meterRegistry = meterRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
String uri = request.getRequestURI();
|
||||||
|
|
||||||
|
// Ignore static resources
|
||||||
|
if (!(uri.startsWith("/css") || uri.startsWith("/js") || uri.startsWith("/images") || uri.endsWith(".ico") || uri.endsWith(".svg")|| uri.endsWith(".js"))) {
|
||||||
|
Counter counter = Counter.builder("http.requests")
|
||||||
|
.tag("uri", uri)
|
||||||
|
.tag("method", request.getMethod())
|
||||||
|
.register(meterRegistry);
|
||||||
|
|
||||||
|
counter.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class WebMvcConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EndpointInterceptor endpointInterceptor;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
registry.addInterceptor(endpointInterceptor);
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,3 +23,4 @@ spring.devtools.restart.enabled=true
|
||||||
spring.devtools.livereload.enabled=true
|
spring.devtools.livereload.enabled=true
|
||||||
|
|
||||||
spring.thymeleaf.encoding=UTF-8
|
spring.thymeleaf.encoding=UTF-8
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div th:fragment="card" class="feature-card" th:id="${id}">
|
<div th:fragment="card" class="feature-card" th:id="${id}" th:if="${@endpointConfiguration.isEndpointEnabled(cardLink)}">
|
||||||
<a th:href="${cardLink}">
|
<a th:href="${cardLink}">
|
||||||
<div class="d-flex align-items-center"> <!-- Add a flex container to align the SVG and title -->
|
<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">
|
<img th:if="${svgPath}" id="card-icon" class="home-card-icon home-card-icon-colour" th:src="${svgPath}" alt="Icon" width="30" height="30">
|
||||||
|
|
|
@ -151,26 +151,12 @@ function compareVersions(version1, version2) {
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<!-- Existing menu items -->
|
<!-- Existing menu items -->
|
||||||
<a class="dropdown-item" href="#" th:href="@{merge-pdfs}" th:classappend="${currentPage}=='merge-pdfs' ? 'active' : ''" th:title="#{home.merge.desc}">
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('merge-pdfs', 'images/union.svg', 'home.merge.title', 'home.merge.desc')"></div>
|
||||||
<img class="icon" src="images/union.svg" alt="icon">
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('split-pdfs', 'images/layout-split.svg', 'home.split.title', 'home.split.desc')"></div>
|
||||||
<span class="icon-text" th:text="#{home.merge.title}"></span>
|
<div th:replace="fragments/navbarEntry :: navbarEntry ( 'pdf-organizer', 'images/sort-numeric-down.svg', 'home.pdfOrganiser.title', 'home.pdfOrganiser.desc')"></div>
|
||||||
</a>
|
<div th:replace="fragments/navbarEntry :: navbarEntry ( 'rotate-pdf', 'images/arrow-clockwise.svg', 'home.rotate.title', 'home.rotate.desc')"></div>
|
||||||
<a class="dropdown-item" href="#" th:href="@{split-pdfs}" th:classappend="${currentPage}=='split-pdfs' ? 'active' : ''" th:title="#{home.split.desc}">
|
<div th:replace="fragments/navbarEntry :: navbarEntry ( 'remove-pages', 'images/file-earmark-x.svg', 'home.removePages.title', 'home.removePages.desc')"></div>
|
||||||
<img class="icon" src="images/layout-split.svg" alt="icon">
|
|
||||||
<span class="icon-text" th:text="#{home.split.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{pdf-organizer}" th:classappend="${currentPage}=='pdf-organizer' ? 'active' : ''" th:title="#{home.pdfOrganiser.desc}">
|
|
||||||
<img class="icon" src="images/sort-numeric-down.svg" alt="icon">
|
|
||||||
<span class="icon-text" th:text="#{home.pdfOrganiser.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{rotate-pdf}" th:classappend="${currentPage}=='rotate-pdf' ? 'active' : ''" th:title="#{home.rotate.desc}">
|
|
||||||
<img class="icon" src="images/arrow-clockwise.svg" alt="icon">
|
|
||||||
<span class="icon-text" th:text="#{home.rotate.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{remove-pages}" th:classappend="${currentPage}=='remove-pages' ? 'active' : ''" th:title="#{home.removePages.desc}">
|
|
||||||
<img class="icon" src="images/file-earmark-x.svg" alt="icon">
|
|
||||||
<span class="icon-text" th:text="#{home.removePages.title}"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item nav-item-separator"></li>
|
<li class="nav-item nav-item-separator"></li>
|
||||||
|
@ -181,56 +167,18 @@ function compareVersions(version1, version2) {
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<!-- Existing menu items -->
|
<!-- Existing menu items -->
|
||||||
<a class="dropdown-item" href="#" th:href="@{img-to-pdf}" th:classappend="${currentPage}=='img-to-pdf' ? 'active' : ''" th:title="#{home.imageToPdf.desc}">
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('img-to-pdf', 'images/image.svg', 'home.imageToPdf.title', 'home.imageToPdf.desc')"></div>
|
||||||
<img class="icon" src="images/image.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;">
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('file-to-pdf', 'images/file.svg', 'home.fileToPDF.title', 'home.fileToPDF.desc')"></div>
|
||||||
<span class="icon-text" th:text="#{home.imageToPdf.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{file-to-pdf}" th:classappend="${currentPage}=='file-to-pdf' ? 'active' : ''" th:title="#{home.fileToPDF.desc}">
|
|
||||||
<img class="icon" src="images/file.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;">
|
|
||||||
<span class="icon-text" th:text="#{home.fileToPDF.title}"></span>
|
|
||||||
</a>
|
|
||||||
<hr class="dropdown-divider">
|
<hr class="dropdown-divider">
|
||||||
<a class="dropdown-item" href="#" th:href="@{pdf-to-img}" th:classappend="${currentPage}=='pdf-to-img' ? 'active' : ''" th:title="#{home.pdfToImage.desc}">
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-img', 'images/image.svg', 'home.pdfToImage.title', 'home.pdfToImage.desc')"></div>
|
||||||
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-word', 'images/file-earmark-word.svg', 'home.PDFToWord.title', 'home.PDFToWord.desc')"></div>
|
||||||
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-presentation', 'images/file-earmark-ppt.svg', 'home.PDFToPresentation.title', 'home.PDFToPresentation.desc')"></div>
|
||||||
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-text', 'images/filetype-txt.svg', 'home.PDFToText.title', 'home.PDFToText.desc')"></div>
|
||||||
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-html', 'images/filetype-html.svg', 'home.PDFToHTML.title', 'home.PDFToHTML.desc')"></div>
|
||||||
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-xml', 'images/filetype-xml.svg', 'home.PDFToXML.title', 'home.PDFToXML.desc')"></div>
|
||||||
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-pdfa', 'images/file-earmark-pdf.svg', 'home.pdfToPDFA.title', 'home.pdfToPDFA.desc')"></div>
|
||||||
|
|
||||||
|
|
||||||
<img class="icon" src="images/image.svg" alt="icon">
|
|
||||||
<span class="icon-text" th:text="#{home.pdfToImage.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{pdf-to-word}" th:classappend="${currentPage}=='pdf-to-word' ? 'active' : ''" th:title="#{home.PDFToWord.desc}">
|
|
||||||
|
|
||||||
|
|
||||||
<img class="icon" src="images/file-earmark-word.svg" alt="icon">
|
|
||||||
<span class="icon-text" th:text="#{home.PDFToWord.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{pdf-to-presentation}" th:classappend="${currentPage}=='pdf-to-presentation' ? 'active' : ''" th:title="#{home.PDFToPresentation.desc}">
|
|
||||||
|
|
||||||
|
|
||||||
<img class="icon" src="images/file-earmark-ppt.svg" alt="icon">
|
|
||||||
<span class="icon-text" th:text="#{home.PDFToPresentation.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{pdf-to-text}" th:classappend="${currentPage}=='pdf-to-text' ? 'active' : ''" th:title="#{home.PDFToText.desc}">
|
|
||||||
|
|
||||||
|
|
||||||
<img class="icon" src="images/filetype-txt.svg" alt="icon">
|
|
||||||
<span class="icon-text" th:text="#{home.PDFToText.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{pdf-to-html}" th:classappend="${currentPage}=='pdf-to-html' ? 'active' : ''" th:title="#{home.PDFToHTML.desc}">
|
|
||||||
|
|
||||||
|
|
||||||
<img class="icon" src="images/filetype-html.svg" alt="icon">
|
|
||||||
<span class="icon-text" th:text="#{home.PDFToHTML.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{pdf-to-xml}" th:classappend="${currentPage}=='pdf-to-xml' ? 'active' : ''" th:title="#{home.PDFToXML.desc}">
|
|
||||||
|
|
||||||
<img class="icon" src="images/filetype-xml.svg" alt="icon">
|
|
||||||
<span class="icon-text" th:text="#{home.PDFToXML.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{pdf-to-pdfa}" th:classappend="${currentPage}=='pdf-to-pdfa' ? 'active' : ''" th:title="#{home.pdfToPDFA.desc}">
|
|
||||||
|
|
||||||
<img class="icon" src="images/file-earmark-pdf.svg" alt="icon">
|
|
||||||
<span class="icon-text" th:text="#{home.pdfToPDFA.title}"></span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -243,18 +191,11 @@ function compareVersions(version1, version2) {
|
||||||
<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>
|
<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>
|
</a>
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<a class="dropdown-item" href="#" th:href="@{add-password}" th:classappend="${currentPage}=='add-password' ? 'active' : ''" th:title="#{home.addPassword.desc}">
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('add-password', 'images/lock.svg', 'home.addPassword.title', 'home.addPassword.desc')"></div>
|
||||||
<img class="icon" src="images/lock.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.addPassword.title}"></span>
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('remove-password', 'images/unlock.svg', 'home.removePassword.title', 'home.removePassword.desc')"></div>
|
||||||
</a>
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('change-permissions', 'images/shield-lock.svg', 'home.permissions.title', 'home.permissions.desc')"></div>
|
||||||
<a class="dropdown-item" href="#" th:href="@{remove-password}" th:classappend="${currentPage}=='remove-password' ? 'active' : ''" th:title="#{home.removePassword.desc}">
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('add-watermark', 'images/droplet.svg', 'home.watermark.title', 'home.watermark.desc')"></div>
|
||||||
<img class="icon" src="images/unlock.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.removePassword.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{change-permissions}" th:classappend="${currentPage}=='change-permissions' ? 'active' : ''" th:title="#{home.permissions.desc}">
|
|
||||||
<img class="icon" src="images/shield-lock.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.permissions.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{add-watermark}" th:classappend="${currentPage}=='add-watermark' ? 'active' : ''" th:title="#{home.watermark.desc}">
|
|
||||||
<img class="icon" src="images/droplet.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.watermark.title}"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -266,39 +207,18 @@ function compareVersions(version1, version2) {
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<a class="dropdown-item" href="#" th:href="@{ocr-pdf}" th:classappend="${currentPage}=='ocr-pdf' ? 'active' : ''" th:title="#{home.ocr.desc}">
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('ocr-pdf', 'images/search.svg', 'home.ocr.title', 'home.ocr.desc')"></div>
|
||||||
<img class="icon" src="images/search.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.ocr.title}"></span>
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('add-image', 'images/file-earmark-richtext.svg', 'home.addImage.title', 'home.addImage.desc')"></div>
|
||||||
</a>
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('compress-pdf', 'images/file-zip.svg', 'home.compressPdfs.title', 'home.compressPdfs.desc')"></div>
|
||||||
<a class="dropdown-item" href="#" th:href="@{add-image}" th:classappend="${currentPage}=='add-image' ? 'active' : ''" th:title="#{home.addImage.desc}">
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('extract-images', 'images/images.svg', 'home.extractImages.title', 'home.extractImages.desc')"></div>
|
||||||
<img class="icon" src="images/file-earmark-richtext.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.addImage.title}"></span>
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('change-metadata', 'images/clipboard-data.svg', 'home.changeMetadata.title', 'home.changeMetadata.desc')"></div>
|
||||||
</a>
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('extract-image-scans', 'images/scanner.svg', 'home.ScannerImageSplit.title', 'home.ScannerImageSplit.desc')"></div>
|
||||||
<a class="dropdown-item" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''" th:title="#{home.compressPdfs.desc}">
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('sign', 'images/sign.svg', 'home.sign.title', 'home.sign.desc')"></div>
|
||||||
<img class="icon" src="images/file-zip.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.compressPdfs.title}"></span>
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('flatten', 'images/flatten.svg', 'home.flatten.title', 'home.flatten.desc')"></div>
|
||||||
</a>
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('repair', 'images/wrench.svg', 'home.repair.title', 'home.repair.desc')"></div>
|
||||||
<a class="dropdown-item" href="#" th:href="@{extract-images}" th:classappend="${currentPage}=='extract-images' ? 'active' : ''" th:title="#{home.extractImages.desc}">
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('remove-blanks', 'images/blank-file.svg', 'home.removeBlanks.title', 'home.removeBlanks.desc')"></div>
|
||||||
<img class="icon" src="images/images.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.extractImages.title}"></span>
|
<div th:replace="fragments/navbarEntry :: navbarEntry ('compare', 'images/scales.svg', 'home.compare.title', 'home.compare.desc')"></div>
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{change-metadata}" th:classappend="${currentPage}=='change-metadata' ? 'active' : ''" th:title="#{home.changeMetadata.desc}">
|
|
||||||
<img class="icon" src="images/clipboard-data.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.changeMetadata.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{extract-image-scans}" th:classappend="${currentPage}=='extract-image-scans' ? 'active' : ''" th:title="#{home.ScannerImageSplit.desc}">
|
|
||||||
<img class="icon" src="images/scanner.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.ScannerImageSplit.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{sign}" th:classappend="${currentPage}=='sign' ? 'active' : ''" th:title="#{home.sign.desc}">
|
|
||||||
<img class="icon" src="images/sign.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.sign.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{flatten}" th:classappend="${currentPage}=='flatten' ? 'active' : ''" th:title="#{home.flatten.desc}">
|
|
||||||
<img class="icon" src="images/flatten.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.flatten.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{repair}" th:classappend="${currentPage}=='repair' ? 'active' : ''" th:title="#{home.repair.desc}">
|
|
||||||
<img class="icon" src="images/wrench.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.repair.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{remove-blanks}" th:classappend="${currentPage}=='remove-blanks' ? 'active' : ''" th:title="#{home.removeBlanks.desc}">
|
|
||||||
<img class="icon" src="images/blank-file.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.removeBlanks.title}"></span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item" href="#" th:href="@{compare}" th:classappend="${currentPage}=='compare' ? 'active' : ''" th:title="#{home.compare.desc}">
|
|
||||||
<img class="icon" src="images/scales.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{home.compare.title}"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
6
src/main/resources/templates/fragments/navbarEntry.html
Normal file
6
src/main/resources/templates/fragments/navbarEntry.html
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<div th:fragment="navbarEntry (endpoint, imgSrc, titleKey, descKey)" th:if="${@endpointConfiguration.isEndpointEnabled(endpoint)}">
|
||||||
|
<a class="dropdown-item" href="#" th:href="@{${endpoint}}" th:classappend="${endpoint.equals(currentPage)} ? 'active' : ''" th:title="#{${descKey}}">
|
||||||
|
<img class="icon" th:src="@{${imgSrc}}" alt="icon">
|
||||||
|
<span class="icon-text" th:text="#{${titleKey}}"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
|
@ -88,6 +88,8 @@ filter: invert(0.2) sepia(2) saturate(50) hue-rotate(190deg);
|
||||||
|
|
||||||
<!-- Features -->
|
<!-- Features -->
|
||||||
<div class="features-container container">
|
<div class="features-container container">
|
||||||
|
|
||||||
|
|
||||||
<div th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', svgPath='images/tools.svg')}"></div>
|
<div th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', svgPath='images/tools.svg')}"></div>
|
||||||
<div th:replace="~{fragments/card :: card(id='merge-pdfs', cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs', svgPath='images/union.svg')}"></div>
|
<div th:replace="~{fragments/card :: card(id='merge-pdfs', cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs', svgPath='images/union.svg')}"></div>
|
||||||
<div th:replace="~{fragments/card :: card(id='split-pdfs', cardTitle=#{home.split.title}, cardText=#{home.split.desc}, cardLink='split-pdfs', svgPath='images/layout-split.svg')}"></div>
|
<div th:replace="~{fragments/card :: card(id='split-pdfs', cardTitle=#{home.split.title}, cardText=#{home.split.desc}, cardLink='split-pdfs', svgPath='images/layout-split.svg')}"></div>
|
||||||
|
|
Loading…
Reference in a new issue