Compare commits

..

9 Commits

Author SHA1 Message Date
Anthony Stirling
597e57c009 Merge branch 'main' into languages 2024-02-11 10:27:55 +00:00
Ludy87
0011947c60 add placeholder translation 2024-02-11 01:03:08 +01:00
Ludy87
369b3a7ba6 add separator 2024-02-11 01:02:09 +01:00
Ludy87
dfa6dccee1 remove dublicate -> change showJS.tags to autoRedact.tags 2024-02-10 18:59:36 +01:00
Ludy87
c94e5f3e55 added missing strings 2024-02-10 11:56:42 +01:00
Ludy87
171b603abe Update messages_de_DE.properties 2024-02-09 23:55:42 +01:00
Ludy87
671f2cd361 Update messages_de_DE.properties 2024-02-09 23:42:04 +01:00
Ludy87
c90bad8f35 double spaces removed 2024-02-09 23:41:46 +01:00
Ludy87
b229222f45 Removed unnecessary characters and added DE missing strings 2024-02-09 23:20:47 +01:00
206 changed files with 10860 additions and 10638 deletions

View File

@@ -1,5 +1,2 @@
# Formatting # Formatting
5f771b785130154ed47952635b7acef371ffe0ec 5f771b785130154ed47952635b7acef371ffe0ec
# Normalize files
55d4fda01b2f39f5b7d7b4fda5214bd7ff0fd5dd

View File

@@ -12,7 +12,7 @@ https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/temp
and add a flag svg file to and add a flag svg file to
https://github.com/Stirling-Tools/Stirling-PDF/tree/main/src/main/resources/static/images/flags https://github.com/Stirling-Tools/Stirling-PDF/tree/main/src/main/resources/static/images/flags
Any SVG flags are fine, i got most of mine from [here](https://flagicons.lipis.dev/) Any SVG flags are fine, i got most of mine from [here](https://flagicons.lipis.dev/)
If your language isn't represented by a flag just find whichever closely matches it, such as for Arabic i chose Saudi Arabia If your language isnt represented by a flag just find whichever closely matches it, such as for Arabic i chose Saudi Arabia
For example to add Polish you would add For example to add Polish you would add
@@ -32,7 +32,7 @@ Copy and rename it to messages_{your data-language-code here}.properties, in the
Then simply translate all property entries within that file and make a PR into main for others to use! Then simply translate all property entries within that file and make a PR into main for others to use!
If you do not have a java IDE i am happy to verify the changes worked once you raise PR (but won't be able to verify the translations themselves) If you do not have a java IDE i am happy to verify the changes worked once you raise PR (but wont be able to verify the translations themselves)

View File

@@ -2,8 +2,8 @@
This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker. This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker.
## My OCR used to work and now doesn't! ## My OCR used to work and now doesnt!
The paths have changed for the tessadata locations on new docker images, please use ``/usr/share/tessdata`` (Others should still work for backwards compatability but might not) Please update your tesseract docker volume path version from 4.00 to 5
## How does the OCR Work ## How does the OCR Work
Stirling-PDF uses [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF) which in turn uses tesseract for its text recognition. Stirling-PDF uses [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF) which in turn uses tesseract for its text recognition.

View File

@@ -65,7 +65,7 @@ sudo make install
``` ```
### Step 3: Install Additional Software ### Step 3: Install Additional Software
Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and opencv for pattern recognition functionality. Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and opencv for patern recognition functionality.
Install the following software: Install the following software:
@@ -264,7 +264,7 @@ sudo systemctl restart stirlingpdf.service
Remember to set the necessary environment variables before running the project if you want to customize the application the list can be seen in the main readme. Remember to set the necessary environment variables before running the project if you want to customize the application the list can be seen in the main readme.
You can do this in the terminal by using the `export` command or -D argument to java -jar command: You can do this in the terminal by using the `export` command or -D arguements to java -jar command:
```bash ```bash
export APP_HOME_NAME="Stirling PDF" export APP_HOME_NAME="Stirling PDF"

View File

@@ -6,7 +6,7 @@
[![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/) [![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/)
[![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf) [![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf)
[![Paypal Donate](https://img.shields.io/badge/Paypal%20Donate-yellow?style=flat&logo=paypal)](https://www.paypal.com/paypalme/froodleplex) [![Paypal Donate](https://img.shields.io/badge/Paypal%20Donate-yellow?style=flat&logo=paypal)](https://www.paypal.com/paypalme/froodleplex)
[![Github Sponsor](https://img.shields.io/badge/Github%20Sponsor-yellow?style=flat&logo=github)](https://github.com/sponsors/Frooodle) [![Github Sponser](https://img.shields.io/badge/Github%20Sponsor-yellow?style=flat&logo=github)](https://github.com/sponsors/Frooodle)
[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af) [![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
@@ -172,7 +172,7 @@ Stirling PDF currently supports 26!
- Hindi (हिंदी) (hi_IN) - Hindi (हिंदी) (hi_IN)
- Hungarian (Magyar) (hu_HU) - Hungarian (Magyar) (hu_HU)
- Bulgarian (Български) (bg_BG) - Bulgarian (Български) (bg_BG)
- Sebian Latin alphabet (Srpski) (sr_LATN_RS) - Sebian Latin alphabet (Srpski) (sr-Latn-RS)
## Contributing (creating issues, translations, fixing bugs, etc.) ## Contributing (creating issues, translations, fixing bugs, etc.)
@@ -262,7 +262,7 @@ For API usage you must provide a header with 'X-API-Key' and the associated API
- Redact text (Via UI not just automated way) - Redact text (Via UI not just automated way)
- Add Forms - Add Forms
- Multi page layout (Stich PDF pages together) support x rows y columns and custom page sizing - Multi page layout (Stich PDF pages together) support x rows y columns and custom page sizing
- Fill forms manually or automatically - Fill forms mannual and automatic
### Q2: Why is my application downloading .htm files? ### Q2: Why is my application downloading .htm files?
This is an issue caused commonly by your NGINX configuration. 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. This is an issue caused commonly by your NGINX configuration. 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.

View File

@@ -89,7 +89,7 @@ dependencies {
//security updates //security updates
implementation 'ch.qos.logback:logback-classic:1.4.14' implementation 'ch.qos.logback:logback-classic:1.4.14'
implementation 'ch.qos.logback:logback-core:1.4.14' implementation 'ch.qos.logback:logback-core:1.4.14'
implementation 'org.springframework:spring-webmvc:6.1.3' implementation 'org.springframework:spring-webmvc:6.1.2'
implementation("io.github.pixee:java-security-toolkit:1.1.2") implementation("io.github.pixee:java-security-toolkit:1.1.2")
@@ -150,7 +150,7 @@ dependencies {
implementation 'org.bouncycastle:bcprov-jdk18on:1.77' implementation 'org.bouncycastle:bcprov-jdk18on:1.77'
implementation 'org.bouncycastle:bcpkix-jdk18on:1.77' implementation 'org.bouncycastle:bcpkix-jdk18on:1.77'
implementation 'org.springframework.boot:spring-boot-starter-actuator:3.2.2' implementation 'org.springframework.boot:spring-boot-starter-actuator:3.2.2'
implementation 'io.micrometer:micrometer-core:1.12.3' implementation 'io.micrometer:micrometer-core:1.12.2'
implementation group: 'com.google.zxing', name: 'core', version: '3.5.2' implementation group: 'com.google.zxing', name: 'core', version: '3.5.2'
// https://mvnrepository.com/artifact/org.commonmark/commonmark // https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation 'org.commonmark:commonmark:0.21.0' implementation 'org.commonmark:commonmark:0.21.0'

View File

@@ -1,5 +1,5 @@
apiVersion: v2 apiVersion: v2
appVersion: 0.20.2 appVersion: 0.14.2
description: locally hosted web application that allows you to perform various operations on PDF files description: locally hosted web application that allows you to perform various operations on PDF files
home: https://github.com/Stirling-Tools/Stirling-PDF home: https://github.com/Stirling-Tools/Stirling-PDF
keywords: keywords:

View File

@@ -16,11 +16,11 @@ commonLabels: {}
# team_name: dev # team_name: dev
envs: [] envs: []
# - name: UI_APP_NAME # - name: PP_HOME_NAME
# value: "Stirling PDF" # value: "Stirling PDF"
# - name: UI_HOME_DESCRIPTION # - name: APP_HOME_DESCRIPTION
# value: "Your locally hosted one-stop-shop for all your PDF needs." # value: "Your locally hosted one-stop-shop for all your PDF needs."
# - name: UI_APP_NAVBAR_NAME # - name: APP_NAVBAR_NAME
# value: "Stirling PDF" # value: "Stirling PDF"
# - name: ALLOW_GOOGLE_VISIBILITY # - name: ALLOW_GOOGLE_VISIBILITY
# value: "true" # value: "true"

View File

@@ -15,7 +15,7 @@ services:
ports: ports:
- 8080:8080 - 8080:8080
volumes: volumes:
- /stirling/latest/data:/usr/share/tessdata:rw - /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:

View File

@@ -15,7 +15,7 @@ services:
ports: ports:
- 8080:8080 - 8080:8080
volumes: volumes:
- /stirling/latest/data:/usr/share/tessdata:rw - /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:

View File

@@ -15,7 +15,7 @@ services:
ports: ports:
- 8080:8080 - 8080:8080
volumes: volumes:
- /stirling/latest/data:/usr/share/tessdata:rw - /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:

View File

@@ -15,7 +15,7 @@ services:
ports: ports:
- 8080:8080 - 8080:8080
volumes: volumes:
- /stirling/latest/data:/usr/share/tessdata:rw - /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw
environment: environment:

View File

@@ -0,0 +1,37 @@
import cv2
import sys
import argparse
import numpy as np
def is_blank_image(image_path, threshold=10, white_percent=99, white_value=255, blur_size=5):
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if image is None:
print(f"Error: Unable to read the image file: {image_path}")
return False
# Apply Gaussian blur to reduce noise
blurred_image = cv2.GaussianBlur(image, (blur_size, blur_size), 0)
_, thresholded_image = cv2.threshold(blurred_image, white_value - threshold, white_value, cv2.THRESH_BINARY)
# Calculate the percentage of white pixels in the thresholded image
white_pixels = np.sum(thresholded_image == white_value)
white_pixel_percentage = (white_pixels / thresholded_image.size) * 100
print(f"Page has white pixel percent of {white_pixel_percentage}")
return white_pixel_percentage >= white_percent
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Detect if an image is considered blank or not.')
parser.add_argument('image_path', help='The path to the image file.')
parser.add_argument('-t', '--threshold', type=int, default=10, help='Threshold for determining white pixels. The default value is 10.')
parser.add_argument('-w', '--white_percent', type=float, default=99, help='The percentage of white pixels for an image to be considered blank. The default value is 99.')
args = parser.parse_args()
blank = is_blank_image(args.image_path, args.threshold, args.white_percent)
# Return code 1: The image is considered blank.
# Return code 0: The image is not considered blank.
sys.exit(int(blank))

View File

@@ -79,16 +79,6 @@ public class ConfigInitializer
return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : ""; return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : "";
}; };
Function<String, Integer> getIndentationLevel =
line -> {
int count = 0;
for (char ch : line.toCharArray()) {
if (ch == ' ') count++;
else break;
}
return count;
};
Set<String> userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet()); Set<String> userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet());
for (String line : templateLines) { for (String line : templateLines) {
@@ -144,77 +134,10 @@ public class ConfigInitializer
.map(extractKey) .map(extractKey)
.anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey)); .anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey));
if (!isPresentInTemplate && !isCommented.apply(userLine)) { if (!isPresentInTemplate && !isCommented.apply(userLine)) {
if (!childOfTemplateEntry( mergedLines.add(userLine);
isCommented,
extractKey,
getIndentationLevel,
userLines,
userLine,
templateLines)) {
// check if userLine is a child of a entry within templateLines or not, if child
// of parent in templateLines then dont add to mergedLines, if anything else
// then add
mergedLines.add(userLine);
}
} }
} }
Files.write(outputPath, mergedLines, StandardCharsets.UTF_8); Files.write(outputPath, mergedLines, StandardCharsets.UTF_8);
} }
// New method to check if a userLine is a child of an entry in templateLines
boolean childOfTemplateEntry(
Function<String, Boolean> isCommented,
Function<String, String> extractKey,
Function<String, Integer> getIndentationLevel,
List<String> userLines,
String userLine,
List<String> templateLines) {
String userKey = extractKey.apply(userLine).trim();
int userIndentation = getIndentationLevel.apply(userLine);
// Start by assuming the line is not a child of an entry in templateLines
boolean isChild = false;
// Iterate backwards through userLines from the current line to find any parent
for (int i = userLines.indexOf(userLine) - 1; i >= 0; i--) {
String potentialParentLine = userLines.get(i);
int parentIndentation = getIndentationLevel.apply(potentialParentLine);
// Check if we've reached a potential parent based on indentation
if (parentIndentation < userIndentation) {
String parentKey = extractKey.apply(potentialParentLine).trim();
// Now, check if this potential parent or any of its parents exist in templateLines
boolean parentExistsInTemplate =
templateLines.stream()
.filter(line -> !isCommented.apply(line)) // Skip commented lines
.anyMatch(
templateLine -> {
String templateKey =
extractKey.apply(templateLine).trim();
return parentKey.equalsIgnoreCase(templateKey);
});
if (!parentExistsInTemplate) {
// If the parent does not exist in template, check the next level parent
userIndentation =
parentIndentation; // Update userIndentation to the parent's indentation
// for next iteration
if (parentIndentation == 0) {
// If we've reached the top-level parent and it's not in template, the
// original line is considered not a child
isChild = false;
break;
}
} else {
// If any parent exists in template, the original line is considered a child
isChild = true;
break;
}
}
}
return isChild; // Return true if the line is not a child of any entry in templateLines
}
} }

View File

@@ -140,6 +140,7 @@ public class EndpointConfiguration {
// CLI // CLI
addEndpointToGroup("CLI", "compress-pdf"); addEndpointToGroup("CLI", "compress-pdf");
addEndpointToGroup("CLI", "extract-image-scans"); addEndpointToGroup("CLI", "extract-image-scans");
addEndpointToGroup("CLI", "remove-blanks");
addEndpointToGroup("CLI", "repair"); addEndpointToGroup("CLI", "repair");
addEndpointToGroup("CLI", "pdf-to-pdfa"); addEndpointToGroup("CLI", "pdf-to-pdfa");
addEndpointToGroup("CLI", "file-to-pdf"); addEndpointToGroup("CLI", "file-to-pdf");
@@ -217,7 +218,6 @@ public class EndpointConfiguration {
addEndpointToGroup("Java", "split-by-size-or-count"); addEndpointToGroup("Java", "split-by-size-or-count");
addEndpointToGroup("Java", "overlay-pdf"); addEndpointToGroup("Java", "overlay-pdf");
addEndpointToGroup("Java", "split-pdf-by-sections"); addEndpointToGroup("Java", "split-pdf-by-sections");
addEndpointToGroup("Java", "remove-blanks");
// Javascript // Javascript
addEndpointToGroup("Javascript", "pdf-organizer"); addEndpointToGroup("Javascript", "pdf-organizer");

View File

@@ -9,9 +9,6 @@ import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
@@ -47,11 +44,6 @@ public class SecurityConfiguration {
@Autowired private FirstLoginFilter firstLoginFilter; @Autowired private FirstLoginFilter firstLoginFilter;
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
@@ -61,14 +53,6 @@ public class SecurityConfiguration {
http.csrf(csrf -> csrf.disable()); http.csrf(csrf -> csrf.disable());
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class); http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
http.sessionManagement(
sessionManagement ->
sessionManagement
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(3)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry())
.expiredUrl("/login?logout=true"));
http.formLogin( http.formLogin(
formLogin -> formLogin ->
formLogin formLogin

View File

@@ -10,9 +10,6 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@@ -231,27 +228,11 @@ public class UserController {
if (currentUsername.equals(username)) { if (currentUsername.equals(username)) {
throw new IllegalArgumentException("Cannot delete currently logined in user."); throw new IllegalArgumentException("Cannot delete currently logined in user.");
} }
invalidateUserSessions(username);
userService.deleteUser(username); userService.deleteUser(username);
return "redirect:/addUsers"; return "redirect:/addUsers";
} }
@Autowired private SessionRegistry sessionRegistry;
private void invalidateUserSessions(String username) {
for (Object principal : sessionRegistry.getAllPrincipals()) {
if (principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
if (userDetails.getUsername().equals(username)) {
for (SessionInformation session :
sessionRegistry.getAllSessions(principal, false)) {
session.expireNow();
}
}
}
}
}
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/get-api-key") @PostMapping("/get-api-key")
public ResponseEntity<String> getApiKey(Principal principal) { public ResponseEntity<String> getApiKey(Principal principal) {

View File

@@ -41,7 +41,7 @@ public class OCRController {
private static final Logger logger = LoggerFactory.getLogger(OCRController.class); private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
public List<String> getAvailableTesseractLanguages() { public List<String> getAvailableTesseractLanguages() {
String tessdataDir = "/usr/share/tessdata"; String tessdataDir = "/usr/share/tesseract-ocr/5/tessdata";
File[] files = new File(tessdataDir).listFiles(); File[] files = new File(tessdataDir).listFiles();
if (files == null) { if (files == null) {
return Collections.emptyList(); return Collections.emptyList();

View File

@@ -33,8 +33,6 @@ public class AccountWebController {
return "redirect:/"; return "redirect:/";
} }
model.addAttribute("currentPage", "login");
if (request.getParameter("error") != null) { if (request.getParameter("error") != null) {
model.addAttribute("error", request.getParameter("error")); model.addAttribute("error", request.getParameter("error"));
@@ -114,7 +112,6 @@ public class AccountWebController {
model.addAttribute("role", user.get().getRolesAsString()); model.addAttribute("role", user.get().getRolesAsString());
model.addAttribute("settings", settingsJson); model.addAttribute("settings", settingsJson);
model.addAttribute("changeCredsFlag", user.get().isFirstLogin()); model.addAttribute("changeCredsFlag", user.get().isFirstLogin());
model.addAttribute("currentPage", "account");
} }
} else { } else {
return "redirect:/"; return "redirect:/";

View File

@@ -82,7 +82,7 @@ public class OtherWebController {
} }
public List<String> getAvailableTesseractLanguages() { public List<String> getAvailableTesseractLanguages() {
String tessdataDir = "/usr/share/tessdata"; String tessdataDir = "/usr/share/tesseract-ocr/5/tessdata";
File[] files = new File(tessdataDir).listFiles(); File[] files = new File(tessdataDir).listFiles();
if (files == null) { if (files == null) {
return Collections.emptyList(); return Collections.emptyList();

View File

@@ -12,6 +12,6 @@ public class PdfToTextOrRTFRequest extends PDFFile {
@Schema( @Schema(
description = "The output Text or RTF format", description = "The output Text or RTF format",
allowableValues = {"rtf", "txt"}) allowableValues = {"rtf", "txt:Text"})
private String outputFormat; private String outputFormat;
} }

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=اختر صورة
genericSubmit=إرسال genericSubmit=إرسال
processTimeWarning=تحذير: يمكن أن تستغرق هذه العملية ما يصل إلى دقيقة حسب حجم الملف processTimeWarning=تحذير: يمكن أن تستغرق هذه العملية ما يصل إلى دقيقة حسب حجم الملف
pageOrderPrompt=ترتيب الصفحات (أدخل قائمة بأرقام الصفحات مفصولة بفواصل): pageOrderPrompt=ترتيب الصفحات (أدخل قائمة بأرقام الصفحات مفصولة بفواصل):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=اذهب goToPage=اذهب
true=\u0635\u062D\u064A\u062D true=\u0635\u062D\u064A\u062D
false=\u062E\u0637\u0623 false=\u062E\u0637\u0623
@@ -20,7 +19,6 @@ save=\u062D\u0641\u0638
close=\u0625\u063A\u0644\u0627\u0642 close=\u0625\u063A\u0644\u0627\u0642
filesSelected=الملفات المحددة filesSelected=الملفات المحددة
noFavourites=لم تتم إضافة أي مفضلات noFavourites=لم تتم إضافة أي مفضلات
downloadComplete=Download Complete
bored=الانتظار بالملل؟ bored=الانتظار بالملل؟
alphabet=\u0627\u0644\u0623\u0628\u062C\u062F\u064A\u0629 alphabet=\u0627\u0644\u0623\u0628\u062C\u062F\u064A\u0629
downloadPdf=تنزيل PDF downloadPdf=تنزيل PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Изберете изображение(я)
genericSubmit=Подайте genericSubmit=Подайте
processTimeWarning=Предупреждение: Този процес може да отнеме до минута в зависимост от размера на файла processTimeWarning=Предупреждение: Този процес може да отнеме до минута в зависимост от размера на файла
pageOrderPrompt=Персонализиран ред на страниците (Въведете разделен със запетаи списък с номера на страници или функции като 2n+1): pageOrderPrompt=Персонализиран ред на страниците (Въведете разделен със запетаи списък с номера на страници или функции като 2n+1):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Давай goToPage=Давай
true=Вярно true=Вярно
false=Невярно false=Невярно
@@ -20,7 +19,6 @@ save=Съхранете
close=Затворете close=Затворете
filesSelected=избрани файлове filesSelected=избрани файлове
noFavourites=Няма добавени любими noFavourites=Няма добавени любими
downloadComplete=Download Complete
bored=Отекчени сте да чакате? bored=Отекчени сте да чакате?
alphabet=Азбука alphabet=Азбука
downloadPdf=Изтеглете PDF downloadPdf=Изтеглете PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Selecciona Imatge(s)
genericSubmit=Envia genericSubmit=Envia
processTimeWarning=Alerta: Aquest procés pot tardar 1 minut depenent de la mida de l'arxiu processTimeWarning=Alerta: Aquest procés pot tardar 1 minut depenent de la mida de l'arxiu
pageOrderPrompt=Ordre de Pàgines (Llista separada per comes) : pageOrderPrompt=Ordre de Pàgines (Llista separada per comes) :
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Anar goToPage=Anar
true=Verdader true=Verdader
false=Fals false=Fals
@@ -20,7 +19,6 @@ save=Desa
close=Tanca close=Tanca
filesSelected=fitxers seleccionats filesSelected=fitxers seleccionats
noFavourites=No s'ha afegit cap favorit noFavourites=No s'ha afegit cap favorit
downloadComplete=Download Complete
bored=Avorrit esperant? bored=Avorrit esperant?
alphabet=Alfabet alphabet=Alfabet
downloadPdf=Descarregueu PDF downloadPdf=Descarregueu PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Wählen Sie ein Bild
genericSubmit=Einreichen genericSubmit=Einreichen
processTimeWarning=Achtung: Abhängig von der Dateigröße kann dieser Prozess bis zu einer Minute dauern processTimeWarning=Achtung: Abhängig von der Dateigröße kann dieser Prozess bis zu einer Minute dauern
pageOrderPrompt=Seitenreihenfolge (Geben Sie eine durch Komma getrennte Liste von Seitenzahlen ein): pageOrderPrompt=Seitenreihenfolge (Geben Sie eine durch Komma getrennte Liste von Seitenzahlen ein):
pageSelectionPrompt=Benutzerdefinierte Seitenauswahl (Geben Sie eine durch Kommas getrennte Liste von Seitenzahlen 1,5,6 oder Funktionen wie 2n+1 ein):
goToPage=Los goToPage=Los
true=Wahr true=Wahr
false=Falsch false=Falsch
@@ -20,7 +19,6 @@ save=Speichern
close=Schließen close=Schließen
filesSelected=Dateien ausgewählt filesSelected=Dateien ausgewählt
noFavourites=Keine Favoriten hinzugefügt noFavourites=Keine Favoriten hinzugefügt
downloadComplete=Download Complete
bored=Langeweile beim Warten? bored=Langeweile beim Warten?
alphabet=Alphabet alphabet=Alphabet
downloadPdf=PDF herunterladen downloadPdf=PDF herunterladen

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=\u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE \u0395\u03B9\u03BA\u03CC\u0
genericSubmit=\u03A5\u03C0\u03BF\u03B2\u03BF\u03BB\u03AE genericSubmit=\u03A5\u03C0\u03BF\u03B2\u03BF\u03BB\u03AE
processTimeWarning=\u03A0\u03C1\u03BF\u03C3\u03BF\u03C7\u03AE: \u0391\u03C5\u03C4\u03AE \u03B7 \u03B4\u03B9\u03B1\u03B4\u03B9\u03BA\u03B1\u03C3\u03AF\u03B1 \u03BC\u03C0\u03BF\u03C1\u03B5\u03AF \u03BD\u03B1 \u03B4\u03B9\u03B1\u03C1\u03BA\u03AD\u03C3\u03B5\u03B9 \u03AD\u03C9\u03C2 \u03BA\u03B1\u03B9 \u03AD\u03BD\u03B1 \u03BB\u03B5\u03C0\u03C4\u03CC \u03B1\u03BD\u03AC\u03BB\u03BF\u03B3\u03B1 \u03BC\u03B5 \u03C4\u03BF \u03BC\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 \u03C4\u03BF\u03C5 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 processTimeWarning=\u03A0\u03C1\u03BF\u03C3\u03BF\u03C7\u03AE: \u0391\u03C5\u03C4\u03AE \u03B7 \u03B4\u03B9\u03B1\u03B4\u03B9\u03BA\u03B1\u03C3\u03AF\u03B1 \u03BC\u03C0\u03BF\u03C1\u03B5\u03AF \u03BD\u03B1 \u03B4\u03B9\u03B1\u03C1\u03BA\u03AD\u03C3\u03B5\u03B9 \u03AD\u03C9\u03C2 \u03BA\u03B1\u03B9 \u03AD\u03BD\u03B1 \u03BB\u03B5\u03C0\u03C4\u03CC \u03B1\u03BD\u03AC\u03BB\u03BF\u03B3\u03B1 \u03BC\u03B5 \u03C4\u03BF \u03BC\u03AD\u03B3\u03B5\u03B8\u03BF\u03C2 \u03C4\u03BF\u03C5 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5
pageOrderPrompt=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03C3\u03BC\u03AD\u03BD\u03B7 \u03A3\u03B5\u03B9\u03C1\u03AC \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 (\u03A0\u03C1\u03BF\u03C3\u03B8\u03AD\u03C3\u03C4\u03B5 \u03BC\u03AF\u03B1 \u03BB\u03AF\u03C3\u03C4\u03B5 \u03B1\u03C0\u03BF \u03B1\u03C1\u03B9\u03B8\u03BC\u03BF\u03CD\u03C2 \u03C3\u03B5\u03BB\u03B9\u03B4\u03CE\u03BD, \u03C7\u03C9\u03C1\u03B9\u03C3\u03BC\u03AD\u03BD\u03B5\u03C2 \u03BC\u03B5 \u03BA\u03CC\u03BC\u03BC\u03B1 \u03AE \u03C3\u03C5\u03BD\u03B1\u03C1\u03C4\u03AE\u03C3\u03B5\u03B9\u03C2 \u03CC\u03C0\u03C9\u03C2 2n+1) : pageOrderPrompt=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03C3\u03BC\u03AD\u03BD\u03B7 \u03A3\u03B5\u03B9\u03C1\u03AC \u03A3\u03B5\u03BB\u03AF\u03B4\u03B1\u03C2 (\u03A0\u03C1\u03BF\u03C3\u03B8\u03AD\u03C3\u03C4\u03B5 \u03BC\u03AF\u03B1 \u03BB\u03AF\u03C3\u03C4\u03B5 \u03B1\u03C0\u03BF \u03B1\u03C1\u03B9\u03B8\u03BC\u03BF\u03CD\u03C2 \u03C3\u03B5\u03BB\u03B9\u03B4\u03CE\u03BD, \u03C7\u03C9\u03C1\u03B9\u03C3\u03BC\u03AD\u03BD\u03B5\u03C2 \u03BC\u03B5 \u03BA\u03CC\u03BC\u03BC\u03B1 \u03AE \u03C3\u03C5\u03BD\u03B1\u03C1\u03C4\u03AE\u03C3\u03B5\u03B9\u03C2 \u03CC\u03C0\u03C9\u03C2 2n+1) :
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Go goToPage=Go
true=\u0391\u03BB\u03B7\u03B8\u03AD\u03C2 true=\u0391\u03BB\u03B7\u03B8\u03AD\u03C2
false=\u039B\u03B1\u03BD\u03B8\u03B1\u03C3\u03BC\u03AD\u03BD\u03BF false=\u039B\u03B1\u03BD\u03B8\u03B1\u03C3\u03BC\u03AD\u03BD\u03BF
@@ -20,7 +19,6 @@ save=\u0391\u03C0\u03BF\u03B8\u03AE\u03BA\u03B5\u03C5\u03C3\u03B7
close=\u039A\u03BB\u03B5\u03AF\u03C3\u03B9\u03BC\u03BF close=\u039A\u03BB\u03B5\u03AF\u03C3\u03B9\u03BC\u03BF
filesSelected=\u03B1\u03C1\u03C7\u03B5\u03AF\u03B1 \u03C0\u03BF\u03C5 \u03B5\u03C0\u03B9\u03BB\u03AD\u03C7\u03B8\u03B7\u03BA\u03B1\u03BD filesSelected=\u03B1\u03C1\u03C7\u03B5\u03AF\u03B1 \u03C0\u03BF\u03C5 \u03B5\u03C0\u03B9\u03BB\u03AD\u03C7\u03B8\u03B7\u03BA\u03B1\u03BD
noFavourites=\u039A\u03B1\u03BD\u03AD\u03BD\u03B1 \u03B1\u03B3\u03B1\u03C0\u03AE\u03BC\u03B5\u03BD\u03BF \u03B4\u03B5\u03BD \u03AD\u03C7\u03B5\u03B9 \u03C0\u03C1\u03BF\u03C3\u03C4\u03B5\u03B8\u03B5\u03AF noFavourites=\u039A\u03B1\u03BD\u03AD\u03BD\u03B1 \u03B1\u03B3\u03B1\u03C0\u03AE\u03BC\u03B5\u03BD\u03BF \u03B4\u03B5\u03BD \u03AD\u03C7\u03B5\u03B9 \u03C0\u03C1\u03BF\u03C3\u03C4\u03B5\u03B8\u03B5\u03AF
downloadComplete=Download Complete
bored=\u0392\u03B1\u03C1\u03B9\u03AD\u03C3\u03C4\u03B5 \u03BD\u03B1 \u03C0\u03B5\u03C1\u03B9\u03BC\u03AD\u03BD\u03B5\u03C4\u03B5; bored=\u0392\u03B1\u03C1\u03B9\u03AD\u03C3\u03C4\u03B5 \u03BD\u03B1 \u03C0\u03B5\u03C1\u03B9\u03BC\u03AD\u03BD\u03B5\u03C4\u03B5;
alphabet=\u0391\u03BB\u03C6\u03AC\u03B2\u03B7\u03C4\u03BF alphabet=\u0391\u03BB\u03C6\u03AC\u03B2\u03B7\u03C4\u03BF
downloadPdf=\u039A\u03B1\u03C4\u03AD\u03B2\u03B1\u03C3\u03BC\u03B1 \u03C4\u03BF\u03C5 PDF downloadPdf=\u039A\u03B1\u03C4\u03AD\u03B2\u03B1\u03C3\u03BC\u03B1 \u03C4\u03BF\u03C5 PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr = left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
@@ -20,7 +20,6 @@ save=Save
close=Close close=Close
filesSelected=files selected filesSelected=files selected
noFavourites=No favourites added noFavourites=No favourites added
downloadComplete=Download Complete
bored=Bored Waiting? bored=Bored Waiting?
alphabet=Alphabet alphabet=Alphabet
downloadPdf=Download PDF downloadPdf=Download PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Select Image(s)
genericSubmit=Submit genericSubmit=Submit
processTimeWarning=Warning: This process can take up to a minute depending on file-size processTimeWarning=Warning: This process can take up to a minute depending on file-size
pageOrderPrompt=Custom Page Order (Enter a comma-separated list of page numbers or Functions like 2n+1) : pageOrderPrompt=Custom Page Order (Enter a comma-separated list of page numbers or Functions like 2n+1) :
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Go goToPage=Go
true=True true=True
false=False false=False
@@ -20,7 +19,6 @@ save=Save
close=Close close=Close
filesSelected=files selected filesSelected=files selected
noFavourites=No favorites added noFavourites=No favorites added
downloadComplete=Download Complete
bored=Bored Waiting? bored=Bored Waiting?
alphabet=Alphabet alphabet=Alphabet
downloadPdf=Download PDF downloadPdf=Download PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Seleccionar Imagen(es)
genericSubmit=Enviar genericSubmit=Enviar
processTimeWarning=Advertencia: este proceso puede tardar hasta un minuto dependiendo del tamaño del archivo processTimeWarning=Advertencia: este proceso puede tardar hasta un minuto dependiendo del tamaño del archivo
pageOrderPrompt=Orden de páginas (Introduzca una lista de números de página separados por coma): pageOrderPrompt=Orden de páginas (Introduzca una lista de números de página separados por coma):
pageSelectionPrompt=Selección de página personalizada (Intruduzca una lista de números de página separados por comas 1,5,6 o funciones como 2n+1) :
goToPage=Ir a goToPage=Ir a
true=Verdadero true=Verdadero
false=Falso false=Falso
@@ -20,7 +19,6 @@ save=Guardar
close=Cerrar close=Cerrar
filesSelected=archivos seleccionados filesSelected=archivos seleccionados
noFavourites=No se agregaron favoritos noFavourites=No se agregaron favoritos
downloadComplete=Download Complete
bored=¿Cansado de esperar? bored=¿Cansado de esperar?
alphabet=Alfabeto alphabet=Alfabeto
downloadPdf=Descargar PDF downloadPdf=Descargar PDF
@@ -58,7 +56,7 @@ usernameExistsMessage=El nuevo nombre de usuario está en uso.
############### ###############
# Pipeline # # Pipeline #
############### ###############
pipeline.header=Menú de canalización (Alfa) pipeline.header=Menu Pipeline (Alfa)
pipeline.uploadButton=Cargar personalización pipeline.uploadButton=Cargar personalización
pipeline.configureButton=Configurar pipeline.configureButton=Configurar
pipeline.defaultOption=Personalizar pipeline.defaultOption=Personalizar
@@ -67,13 +65,13 @@ pipeline.submitButton=Enviar
###################### ######################
# Pipeline Options # # Pipeline Options #
###################### ######################
pipelineOptions.header=Configuración de la canalización pipelineOptions.header=Configuración Pipeline
pipelineOptions.pipelineNameLabel=Nombre de la canalización pipelineOptions.pipelineNameLabel=Nombre del Pipeline
pipelineOptions.saveSettings=Guardar configuración de la canalización pipelineOptions.saveSettings=Guardar configuración de la oiperación
pipelineOptions.pipelineNamePrompt=Introduzca aquí el nombre de la canalización pipelineOptions.pipelineNamePrompt=Introduzca aquí el nombre del pipeline
pipelineOptions.selectOperation=Seleccione la operación pipelineOptions.selectOperation=Seleccione la operación
pipelineOptions.addOperationButton=Añadir operación pipelineOptions.addOperationButton=Añadir operación
pipelineOptions.pipelineHeader=Canalización: pipelineOptions.pipelineHeader=Pipeline:
pipelineOptions.saveButton=Descargar pipelineOptions.saveButton=Descargar
pipelineOptions.validateButton=Validar pipelineOptions.validateButton=Validar
@@ -564,7 +562,7 @@ autoSplitPDF.submit=Entregar
#pipeline #pipeline
pipeline.title=Canalización pipeline.title=Pipeline
#pageLayout #pageLayout

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Hautatu Irudia(k)
genericSubmit=Bidali genericSubmit=Bidali
processTimeWarning=Oharra: prozesu honetarako minutu bat ere beharko da fitxategiaren tamaiaren arabera processTimeWarning=Oharra: prozesu honetarako minutu bat ere beharko da fitxategiaren tamaiaren arabera
pageOrderPrompt=Orrialdeen ordena (sartu komaz bereizitako orrialde-zenbakien zerrenda) pageOrderPrompt=Orrialdeen ordena (sartu komaz bereizitako orrialde-zenbakien zerrenda)
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Joan goToPage=Joan
true=Egiazkoa true=Egiazkoa
false=Faltsua false=Faltsua
@@ -20,7 +19,6 @@ save=Gorde
close=Itxi close=Itxi
filesSelected=Hautatutako fitxategiak filesSelected=Hautatutako fitxategiak
noFavourites=Ez dira gogokoak gehitu noFavourites=Ez dira gogokoak gehitu
downloadComplete=Download Complete
bored=Itxaroten aspertuta? bored=Itxaroten aspertuta?
alphabet=Alfabetoa alphabet=Alfabetoa
downloadPdf=PDFa deskargatu downloadPdf=PDFa deskargatu

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Choisir une image
genericSubmit=Envoyer genericSubmit=Envoyer
processTimeWarning=Attention, ce processus peut prendre jusqu\u2019à une minute en fonction de la taille du fichier. processTimeWarning=Attention, ce processus peut prendre jusqu\u2019à une minute en fonction de la taille du fichier.
pageOrderPrompt=Ordre des pages (entrez une liste de numéros de page séparés par des virgules ou des fonctions telles que 2n+1)\u00a0: pageOrderPrompt=Ordre des pages (entrez une liste de numéros de page séparés par des virgules ou des fonctions telles que 2n+1)\u00a0:
pageSelectionPrompt=Sélection des pages (entrez une liste de numéros de page séparés par des virgules ou des fonctions telles que 2n+1)\u00a0:
goToPage=Aller goToPage=Aller
true=Vrai true=Vrai
false=Faux false=Faux
@@ -20,7 +19,6 @@ save=Enregistrer
close=Fermer close=Fermer
filesSelected=fichiers sélectionnés filesSelected=fichiers sélectionnés
noFavourites=Aucun favori ajouté noFavourites=Aucun favori ajouté
downloadComplete=Téléchargement terminé
bored=Ennuyé d\u2019attendre\u00a0? bored=Ennuyé d\u2019attendre\u00a0?
alphabet=Alphabet alphabet=Alphabet
downloadPdf=Télécharger le PDF downloadPdf=Télécharger le PDF
@@ -44,10 +42,10 @@ red=Rouge
green=Vert green=Vert
blue=Bleu blue=Bleu
custom=Personnalisé\u2026 custom=Personnalisé\u2026
WorkInProgess=En cours de développement, merci de nous remonter les problèmes que vous pourriez constater! WorkInProgess=Work in progress, May not work or be buggy, Please report any problems!
poweredBy=Propulsé par poweredBy=Powered by
yes=Oui yes=Yes
no=Non no=No
changedCredsMessage=Les identifiants ont été mis à jour\u00a0! changedCredsMessage=Les identifiants ont été mis à jour\u00a0!
notAuthenticatedMessage=Utilisateur non authentifié. notAuthenticatedMessage=Utilisateur non authentifié.
userNotFoundMessage=Utilisateur non trouvé. userNotFoundMessage=Utilisateur non trouvé.
@@ -58,24 +56,24 @@ usernameExistsMessage=Le nouveau nom d\u2019utilisateur existe déjà.
############### ###############
# Pipeline # # Pipeline #
############### ###############
pipeline.header=Menu Pipeline (Alpha) pipeline.header=Pipeline Menu (Alpha)
pipeline.uploadButton=Télécharger une personnalisation pipeline.uploadButton=Upload Custom
pipeline.configureButton=Configurer pipeline.configureButton=Configure
pipeline.defaultOption=Personnaliser pipeline.defaultOption=Custom
pipeline.submitButton=Soumettre pipeline.submitButton=Submit
###################### ######################
# Pipeline Options # # Pipeline Options #
###################### ######################
pipelineOptions.header=Configuration du pipeline pipelineOptions.header=Pipeline Configuration
pipelineOptions.pipelineNameLabel=Nom du pipeline pipelineOptions.pipelineNameLabel=Pipeline Name
pipelineOptions.saveSettings=Sauvegarder la configuration pipelineOptions.saveSettings=Save Operation Settings
pipelineOptions.pipelineNamePrompt=Entrez ici le nom du pipeline pipelineOptions.pipelineNamePrompt=Enter pipeline name here
pipelineOptions.selectOperation=Sélectionner une opération pipelineOptions.selectOperation=Select Operation
pipelineOptions.addOperationButton=Ajouter une opération pipelineOptions.addOperationButton=Add operation
pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.pipelineHeader=Pipeline:
pipelineOptions.saveButton=Télécharger pipelineOptions.saveButton=Download
pipelineOptions.validateButton=Valider pipelineOptions.validateButton=Validate
@@ -148,7 +146,7 @@ adminUserSettings.role=Rôle
adminUserSettings.actions=Actions adminUserSettings.actions=Actions
adminUserSettings.apiUser=Utilisateur API limité adminUserSettings.apiUser=Utilisateur API limité
adminUserSettings.webOnlyUser=Utilisateur Web uniquement adminUserSettings.webOnlyUser=Utilisateur Web uniquement
adminUserSettings.demoUser=Demo User (Paramètres par défaut) adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.forceChange=Forcer l\u2019utilisateur à changer son nom d\u2019utilisateur/mot de passe lors de la connexion adminUserSettings.forceChange=Forcer l\u2019utilisateur à changer son nom d\u2019utilisateur/mot de passe lors de la connexion
adminUserSettings.submit=Ajouter adminUserSettings.submit=Ajouter
@@ -160,7 +158,7 @@ home.searchBar=Rechercher des fonctionnalités...
home.viewPdf.title=Visionner le PDF home.viewPdf.title=Visionner le PDF
home.viewPdf.desc=Visionner, annoter, ajouter du texte ou des images. home.viewPdf.desc=Visionner, annoter, ajouter du texte ou des images
viewPdf.tags=visualiser,lire,annoter,texte,image viewPdf.tags=visualiser,lire,annoter,texte,image
home.multiTool.title=Outil multifonction PDF home.multiTool.title=Outil multifonction PDF
@@ -169,7 +167,7 @@ multiTool.tags=outil multifonction,opération multifonction,interface utilisateu
home.merge.title=Fusionner home.merge.title=Fusionner
home.merge.desc=Fusionnez facilement plusieurs PDF en un seul. home.merge.desc=Fusionnez facilement plusieurs PDF en un seul.
merge.tags=fusionner,opérations sur les pages,backend,server side,merge merge.tags=fusionner,opérations sur les pages,backeend,server side,merge
home.split.title=Diviser home.split.title=Diviser
home.split.desc=Divisez un PDF en plusieurs documents. home.split.desc=Divisez un PDF en plusieurs documents.
@@ -285,9 +283,9 @@ home.removeBlanks.title=Supprimer les pages vierges
home.removeBlanks.desc=Détectez et supprimez les pages vierges d\u2019un PDF. home.removeBlanks.desc=Détectez et supprimez les pages vierges d\u2019un PDF.
removeBlanks.tags=pages vierges,supprimer,nettoyer,cleanup,streamline,non-content,organize removeBlanks.tags=pages vierges,supprimer,nettoyer,cleanup,streamline,non-content,organize
home.removeAnnotations.title=Supprimer les annotations home.removeAnnotations.title=Remove Annotations
home.removeAnnotations.desc=Supprimer tous les commentaires/annotations d\u2019un PDF. home.removeAnnotations.desc=Removes all comments/annotations from a PDF
removeAnnotations.tags=commentaires,supprimer,annotations,highlight,notes,markup,remove removeAnnotations.tags=comments,highlight,notes,markup,remove
home.compare.title=Comparer home.compare.title=Comparer
home.compare.desc=Comparez et visualisez les différences entre deux PDF. home.compare.desc=Comparez et visualisez les différences entre deux PDF.
@@ -371,7 +369,7 @@ home.autoRedact.desc=Caviardez automatiquement les informations sensibles d\u201
autoRedact.tags=caviarder,redact,auto autoRedact.tags=caviarder,redact,auto
home.tableExtraxt.title=PDF en CSV home.tableExtraxt.title=PDF en CSV
home.tableExtraxt.desc=Extrait les tableaux d\u2019un PDF et les transforme en CSV. home.tableExtraxt.desc=Extrait les tableaux d\u2019un PDF et les transforme en CSV
tableExtraxt.tags=CSV,Table Extraction,extract,convert tableExtraxt.tags=CSV,Table Extraction,extract,convert
@@ -381,16 +379,16 @@ autoSizeSplitPDF.tags=pdf,split,document,organization
home.overlay-pdfs.title=Incrustation de PDF home.overlay-pdfs.title=Incrustation de PDF
home.overlay-pdfs.desc=Incrustation d\u2019un PDF sur un autre PDF. home.overlay-pdfs.desc=Incrustation d\u2019un PDF sur un autre PDF
overlay-pdfs.tags=Overlay,incrustation overlay-pdfs.tags=Overlay
home.split-by-sections.title=Séparer un PDF en sections home.split-by-sections.title=Split PDF by Sections
home.split-by-sections.desc=Diviser chaque page d\u2019un PDF en sections horizontales/verticales plus petites. home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections
split-by-sections.tags=Sections,Diviser,Section Split, Divide, Customize split-by-sections.tags=Section Split, Divide, Customize
home.AddStampRequest.title=Ajouter un tampon sur un PDF home.AddStampRequest.title=Add Stamp to PDF
home.AddStampRequest.desc=Ajouter un texte ou l\u2019image d\u2019un tampon à un emplacement défini. home.AddStampRequest.desc=Add text or add image stamps at set locations
AddStampRequest.tags=Tampon,Ajouter,Stamp,Add image,center image,Watermark,PDF,Embed,Customize AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
########################### ###########################
@@ -468,37 +466,37 @@ HTMLToPDF.header=HTML en PDF
HTMLToPDF.help=Accepte les fichiers HTML et les ZIP contenant du HTML, du CSS, des images, etc. (requis). HTMLToPDF.help=Accepte les fichiers HTML et les ZIP contenant du HTML, du CSS, des images, etc. (requis).
HTMLToPDF.submit=Convertir HTMLToPDF.submit=Convertir
HTMLToPDF.credit=Utilise WeasyPrint. HTMLToPDF.credit=Utilise WeasyPrint.
HTMLToPDF.zoom=Niveau de zoom pour l\u2019affichage du site web. HTMLToPDF.zoom=Zoom level for displaying the website.
HTMLToPDF.pageWidth=Largeur de la page en centimètres. (Vide par défaut) HTMLToPDF.pageWidth=Width of the page in centimeters. (Blank to default)
HTMLToPDF.pageHeight=Hauteur de la page en centimètres. (Vide par défaut) HTMLToPDF.pageHeight=Height of the page in centimeters. (Blank to default)
HTMLToPDF.marginTop=Marge supérieure de la page en millimètres. (Vide par défaut) HTMLToPDF.marginTop=Top margin of the page in millimeters. (Blank to default)
HTMLToPDF.marginBottom=Marge inférieure de la page en millimètres. (Vide par défaut) HTMLToPDF.marginBottom=Bottom margin of the page in millimeters. (Blank to default)
HTMLToPDF.marginLeft=Marge gauche de la page en millimètres. (Vide par défaut) HTMLToPDF.marginLeft=Left margin of the page in millimeters. (Blank to default)
HTMLToPDF.marginRight=Marge droite de la page en millimètres. (Vide par défaut) HTMLToPDF.marginRight=Right margin of the page in millimeters. (Blank to default)
HTMLToPDF.printBackground=Restituer l\u2019image de fond des sites web. HTMLToPDF.printBackground=Render the background of websites.
HTMLToPDF.defaultHeader=Activer l\u2019entête par défaut (Nom et numéro de page) HTMLToPDF.defaultHeader=Enable Default Header (Name and page number)
HTMLToPDF.cssMediaType=Modifier le type de média CSS de la page. HTMLToPDF.cssMediaType=Change the CSS media type of the page.
HTMLToPDF.none=Aucun HTMLToPDF.none=None
HTMLToPDF.print=Imprimer HTMLToPDF.print=Print
HTMLToPDF.screen=Écran HTMLToPDF.screen=Screen
#AddStampRequest #AddStampRequest
AddStampRequest.header=Tampon PDF AddStampRequest.header=Stamp PDF
AddStampRequest.title=Tampon PDF AddStampRequest.title=Stamp PDF
AddStampRequest.stampType=Type de tampon AddStampRequest.stampType=Stamp Type
AddStampRequest.stampText=Tampon texte AddStampRequest.stampText=Stamp Text
AddStampRequest.stampImage=Tampon image AddStampRequest.stampImage=Stamp Image
AddStampRequest.alphabet=Alphabet AddStampRequest.alphabet=Alphabet
AddStampRequest.fontSize=Taille de fonte/image AddStampRequest.fontSize=Font/Image Size
AddStampRequest.rotation=Rotation AddStampRequest.rotation=Rotation
AddStampRequest.opacity=Opacité AddStampRequest.opacity=Opacity
AddStampRequest.position=Position AddStampRequest.position=Position
AddStampRequest.overrideX=Définir coordonnées X AddStampRequest.overrideX=Override X Coordinate
AddStampRequest.overrideY=Définir coordonnées Y AddStampRequest.overrideY=Override Y Coordinate
AddStampRequest.customMargin=Marge personnalisée AddStampRequest.customMargin=Custom Margin
AddStampRequest.customColor=Couleur de texte personnalisée AddStampRequest.customColor=Custom Text Color
AddStampRequest.submit=Soumettre AddStampRequest.submit=Submit
#sanitizePDF #sanitizePDF
@@ -587,11 +585,11 @@ scalePages.submit=Ajuster
certSign.title=Signer avec un certificat certSign.title=Signer avec un certificat
certSign.header=Signer avec un certificat (Travail en cours) certSign.header=Signer avec un certificat (Travail en cours)
certSign.selectPDF=PDF à signer certSign.selectPDF=PDF à signer
certSign.jksNote=Note: Si votre type de certificat n\u2019est pas listé ci\u002Ddessous, merci de le convertir en fichier Java Keystore (.jks) en utilisant l\u2019outil en ligne de commande keytool. Puis choisissez l\u2019option Fichier .jks ci\u002Ddessous. certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
certSign.selectKey=Fichier de clé privée (format PKCS#8, peut être .pem ou .der) certSign.selectKey=Fichier de clé privée (format PKCS#8, peut être .pem ou .der)
certSign.selectCert=Fichier de certificat (format X.509, peut être .pem ou .der) certSign.selectCert=Fichier de certificat (format X.509, peut être .pem ou .der)
certSign.selectP12=Fichier keystore de clés PKCS#12 (.p12 ou .pfx) (facultatif, s\u2019il n\u2019est fourni, il doit contenir votre clé privée et votre certificat) certSign.selectP12=Fichier keystore de clés PKCS#12 (.p12 ou .pfx) (facultatif, s\u2019il n\u2019est fourni, il doit contenir votre clé privée et votre certificat)
certSign.selectJKS=Sélectionner votre fichier Java Keystore File (.jks or .keystore): certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
certSign.certType=Type de certificat certSign.certType=Type de certificat
certSign.password=Mot de passe keystore ou clé privée le cas échéant certSign.password=Mot de passe keystore ou clé privée le cas échéant
certSign.showSig=Afficher la signature certSign.showSig=Afficher la signature
@@ -612,9 +610,9 @@ removeBlanks.submit=Supprimer les pages vierges
#removeAnnotations #removeAnnotations
removeAnnotations.title=Supprimer les annotations removeAnnotations.title=Remove Annotations
removeAnnotations.header=Supprimer les annotations removeAnnotations.header=Remove Annotations
removeAnnotations.submit=Supprimer removeAnnotations.submit=Remove
#compare #compare
@@ -696,14 +694,14 @@ fileToPDF.submit=Convertir
#compress #compress
compress.title=Compresser un PDF compress.title=Compresser
compress.header=Compresser un PDF (lorsque c\u2019est possible!) compress.header=Compresser
compress.credit=Ce service utilise Ghostscript pour la compression et l\u2019optimisation des PDF. compress.credit=Ce service utilise Ghostscript pour la compression et l\u2019optimisation des PDF.
compress.selectText.1=Mode manuel \u2013 de 1 à 4 compress.selectText.1=Mode manuel \u2013 de 1 à 4
compress.selectText.2=Niveau d\u2019optimisation compress.selectText.2=Niveau d\u2019optimisation
compress.selectText.3=4 (terrible pour les images textuelles) compress.selectText.3=4 (terrible pour les images textuelles)
compress.selectText.4=Mode automatique \u2013 ajuste automatiquement la qualité pour obtenir le PDF à la taille exacte compress.selectText.4=Mode automatique \u2013 ajuste automatiquement la qualité pour obtenir le PDF à la taille exacte
compress.selectText.5=Taille PDF attendue (par exemple, 25\u202fMB, 10,8\u202fMB, 25\u202fKB) compress.selectText.5=Taille PDF attendue (par exemple, 25\u202fMo, 10,8\u202fMo, 25\u202fKo)
compress.submit=Compresser compress.submit=Compresser
@@ -734,8 +732,8 @@ multiTool.title=Outil multifonction PDF
multiTool.header=Outil multifonction PDF multiTool.header=Outil multifonction PDF
#view pdf #view pdf
viewPdf.title=Visualiser un PDF viewPdf.title=View PDF
viewPdf.header=Visualiser un PDF viewPdf.header=View PDF
#pageRemover #pageRemover
pageRemover.title=Supprimer des pages pageRemover.title=Supprimer des pages
@@ -790,7 +788,7 @@ pdfToImage.multi=Plusieurs images
pdfToImage.colorType=Type d\u2019impression pdfToImage.colorType=Type d\u2019impression
pdfToImage.color=Couleur pdfToImage.color=Couleur
pdfToImage.grey=Niveaux de gris pdfToImage.grey=Niveaux de gris
pdfToImage.blackwhite=Noir et blanc (peut engendrer une perte de données\u00a0!) pdfToImage.blackwhite=Noir et blanc (peut engendre une perde de données\u00a0!)
pdfToImage.submit=Convertir pdfToImage.submit=Convertir
@@ -869,7 +867,7 @@ changeMetadata.keywords=Mots clés
changeMetadata.modDate=Date de modification (yyyy/MM/dd HH:mm:ss) changeMetadata.modDate=Date de modification (yyyy/MM/dd HH:mm:ss)
changeMetadata.producer=Producteur changeMetadata.producer=Producteur
changeMetadata.subject=Sujet changeMetadata.subject=Sujet
changeMetadata.trapped=Recouvrement (technique dimpression) changeMetadata.trapped=Défoncé (technique dimpression)
changeMetadata.selectText.4=Autres métadonnées changeMetadata.selectText.4=Autres métadonnées
changeMetadata.selectText.5=Ajouter une entrée de métadonnées personnalisée changeMetadata.selectText.5=Ajouter une entrée de métadonnées personnalisée
changeMetadata.submit=Modifier changeMetadata.submit=Modifier
@@ -937,19 +935,19 @@ split-by-size-or-count.submit=Séparer
#overlay-pdfs #overlay-pdfs
overlay-pdfs.header=Incrustation de PDF overlay-pdfs.header=Overlay PDF Files
overlay-pdfs.baseFile.label=Sélectionner le fichier PDF de base overlay-pdfs.baseFile.label=Sélectionner le fichier PDF de base
overlay-pdfs.overlayFiles.label=Sélectionner les fichiers PDF à superposer overlay-pdfs.overlayFiles.label=Sélectionner les fichiers PDF à superposer
overlay-pdfs.mode.label=Sélectionner le mode d\u2019incrustation overlay-pdfs.mode.label=Select Overlay Mode
overlay-pdfs.mode.sequential=Superposition séquentielle overlay-pdfs.mode.sequential=Sequential Overlay
overlay-pdfs.mode.interleaved=Superposition entrelacée overlay-pdfs.mode.interleaved=Interleaved Overlay
overlay-pdfs.mode.fixedRepeat=Superposition à répétition fixe overlay-pdfs.mode.fixedRepeat=Superposition à répétition fixe
overlay-pdfs.counts.label=Nombre de superpositions (pour le mode de répétition fixe) overlay-pdfs.counts.label=Nombre de superpositions (pour le mode de répétition fixe)
overlay-pdfs.counts.placeholder=Compteurs (séparés par des virgules, exemple : 2,3,1) overlay-pdfs.counts.placeholder=Enter comma-separated counts (e.g., 2,3,1)
overlay-pdfs.position.label=Définir la position de l\u2019incrustation overlay-pdfs.position.label=Select Overlay Position
overlay-pdfs.position.foreground=Premier plan overlay-pdfs.position.foreground=Premier plan
overlay-pdfs.position.background=Arrière-plan overlay-pdfs.position.background=Arrière-plan
overlay-pdfs.submit=Soumettre overlay-pdfs.submit=Submit
#split-by-sections #split-by-sections
@@ -963,11 +961,11 @@ split-by-sections.submit=Diviser le PDF
#licenses #licenses
licenses.nav=Licences licenses.nav=Licenses
licenses.title=Licences tierces licenses.title=3rd Party Licenses
licenses.header=Licences tierces licenses.header=3rd Party Licenses
licenses.module=Module licenses.module=Module
licenses.version=Version licenses.version=Version
licenses.license=Licence licenses.license=License

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=छवियों का चयन करें
genericSubmit=प्रस्तुत करें genericSubmit=प्रस्तुत करें
processTimeWarning=चेतावनी: यह प्रक्रिया फ़ाइल के आकार पर निर्भर करती है और यह से एक मिनट तक लग सकती है processTimeWarning=चेतावनी: यह प्रक्रिया फ़ाइल के आकार पर निर्भर करती है और यह से एक मिनट तक लग सकती है
pageOrderPrompt=कस्टम पेज क्रम (पेज नंबरों या 2n+1 जैसे कार्यों की एक कॉमा से अलग-अलग सूची दर्ज करें): pageOrderPrompt=कस्टम पेज क्रम (पेज नंबरों या 2n+1 जैसे कार्यों की एक कॉमा से अलग-अलग सूची दर्ज करें):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=जाएँ goToPage=जाएँ
true=सही true=सही
false=गलत false=गलत
@@ -20,7 +19,6 @@ save=सहेजें
close=बंद करें close=बंद करें
filesSelected=फ़ाइलें चयनित हैं filesSelected=फ़ाइलें चयनित हैं
noFavourites=कोई पसंदीदा जोड़ा नहीं गया है noFavourites=कोई पसंदीदा जोड़ा नहीं गया है
downloadComplete=Download Complete
bored=बोर हो रहे हैं? bored=बोर हो रहे हैं?
alphabet=वर्णमाला alphabet=वर्णमाला
downloadPdf=पीडीएफ़ डाउनलोड करें downloadPdf=पीडीएफ़ डाउनलोड करें

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Válasszon képeket
genericSubmit=Beküldés genericSubmit=Beküldés
processTimeWarning=Figyelmeztetés: Ez a folyamat akár egy percig is eltarthat a fájlmérettől függően processTimeWarning=Figyelmeztetés: Ez a folyamat akár egy percig is eltarthat a fájlmérettől függően
pageOrderPrompt=Egyedi oldalsorrend (Adjon meg vesszővel elválasztott oldalszámokat vagy függvényeket, például 2n+1): pageOrderPrompt=Egyedi oldalsorrend (Adjon meg vesszővel elválasztott oldalszámokat vagy függvényeket, például 2n+1):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Ugrás goToPage=Ugrás
true=Igaz true=Igaz
false=Hamis false=Hamis
@@ -20,7 +19,6 @@ save=Mentés
close=Bezárás close=Bezárás
filesSelected=kiválasztott fájlok filesSelected=kiválasztott fájlok
noFavourites=Nincs hozzáadva kedvenc noFavourites=Nincs hozzáadva kedvenc
downloadComplete=Download Complete
bored=Unatkozol? bored=Unatkozol?
alphabet=Ábécé alphabet=Ábécé
downloadPdf=PDF letöltése downloadPdf=PDF letöltése

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl=right to left) # the direction that the language is written (ltr=left to right, rtl=right to left)
@@ -11,7 +11,6 @@ imgPrompt=Pilih Gambar
genericSubmit=Kirim genericSubmit=Kirim
processTimeWarning=Peringatan: Proses ini dapat memakan waktu hingga satu menit, tergantung pada ukuran berkas processTimeWarning=Peringatan: Proses ini dapat memakan waktu hingga satu menit, tergantung pada ukuran berkas
pageOrderPrompt=Urutan Halaman Khusus (Masukkan daftar nomor halaman yang dipisahkan dengan koma atau Fungsi seperti 2n + 1) : pageOrderPrompt=Urutan Halaman Khusus (Masukkan daftar nomor halaman yang dipisahkan dengan koma atau Fungsi seperti 2n + 1) :
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Ke goToPage=Ke
true=Benar true=Benar
false=Salah false=Salah
@@ -20,7 +19,6 @@ save=Simpan
close=Tutup close=Tutup
filesSelected=berkas dipilih filesSelected=berkas dipilih
noFavourites=Tidak ada favorit yang ditambahkan noFavourites=Tidak ada favorit yang ditambahkan
downloadComplete=Download Complete
bored=Bosan Menunggu? bored=Bosan Menunggu?
alphabet=Abjad alphabet=Abjad
downloadPdf=Unduh PDF downloadPdf=Unduh PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Scegli immagine/i
genericSubmit=Invia genericSubmit=Invia
processTimeWarning=Nota: Questo processo potrebbe richiedere fino a un minuto in base alla dimensione dei file processTimeWarning=Nota: Questo processo potrebbe richiedere fino a un minuto in base alla dimensione dei file
pageOrderPrompt=Ordine delle pagine (inserisci una lista di numeri separati da virgola): pageOrderPrompt=Ordine delle pagine (inserisci una lista di numeri separati da virgola):
pageSelectionPrompt=Selezione pagina personalizzata (inserisci un elenco separato da virgole di numeri di pagina 1,5,6 o funzioni come 2n+1) :
goToPage=Vai goToPage=Vai
true=Vero true=Vero
false=Falso false=Falso
@@ -20,7 +19,6 @@ save=Salva
close=Chiudi close=Chiudi
filesSelected=file selezionati filesSelected=file selezionati
noFavourites=Nessun preferito noFavourites=Nessun preferito
downloadComplete=Download Complete
bored=Stanco di aspettare? bored=Stanco di aspettare?
alphabet=Alfabeto alphabet=Alfabeto
downloadPdf=Scarica PDF downloadPdf=Scarica PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=画像を選択
genericSubmit=送信 genericSubmit=送信
processTimeWarning=警告:この処理はファイルサイズによって1分程度かかることがあります processTimeWarning=警告:この処理はファイルサイズによって1分程度かかることがあります
pageOrderPrompt=ページ順序 (ページ番号をカンマ区切り又は2n+1のような関数で入力): pageOrderPrompt=ページ順序 (ページ番号をカンマ区切り又は2n+1のような関数で入力):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=移動 goToPage=移動
true=True true=True
false=False false=False
@@ -20,7 +19,6 @@ save=保存
close=閉じる close=閉じる
filesSelected=選択されたファイル filesSelected=選択されたファイル
noFavourites=お気に入りはありません noFavourites=お気に入りはありません
downloadComplete=Download Complete
bored=待ち時間が退屈 bored=待ち時間が退屈
alphabet=\u30A2\u30EB\u30D5\u30A1\u30D9\u30C3\u30C8 alphabet=\u30A2\u30EB\u30D5\u30A1\u30D9\u30C3\u30C8
downloadPdf=PDFをダウンロード downloadPdf=PDFをダウンロード
@@ -970,4 +968,3 @@ licenses.module=モジュール
licenses.version=バージョン licenses.version=バージョン
licenses.license=ライセンス licenses.license=ライセンス

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=이미지 선택
genericSubmit=확인 genericSubmit=확인
processTimeWarning=경고: 파일 크기에 따라 1분 정도 소요될 수 있습니다 processTimeWarning=경고: 파일 크기에 따라 1분 정도 소요될 수 있습니다
pageOrderPrompt=페이지 순서(쉼표로 구분된 페이지 번호 목록 입력): pageOrderPrompt=페이지 순서(쉼표로 구분된 페이지 번호 목록 입력):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=이동 goToPage=이동
true= true=
false=거짓 false=거짓
@@ -20,7 +19,6 @@ save=저장
close=닫기 close=닫기
filesSelected=개 파일 선택됨 filesSelected=개 파일 선택됨
noFavourites=즐겨찾기 없음 noFavourites=즐겨찾기 없음
downloadComplete=Download Complete
bored=기다리는 게 지루하신가요? bored=기다리는 게 지루하신가요?
alphabet=\uC54C\uD30C\uBCB3 alphabet=\uC54C\uD30C\uBCB3
downloadPdf=PDF 다운로드 downloadPdf=PDF 다운로드

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Selecteer afbeelding(en)
genericSubmit=Indienen genericSubmit=Indienen
processTimeWarning=Waarschuwing: Dit proces kan tot een minuut duren afhankelijk van de bestandsgrootte processTimeWarning=Waarschuwing: Dit proces kan tot een minuut duren afhankelijk van de bestandsgrootte
pageOrderPrompt=Aangepaste pagina volgorde (Voer een komma-gescheiden lijst van paginanummers of functies in, zoals 2n+1) : pageOrderPrompt=Aangepaste pagina volgorde (Voer een komma-gescheiden lijst van paginanummers of functies in, zoals 2n+1) :
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Ga goToPage=Ga
true=Waar true=Waar
false=Onwaar false=Onwaar
@@ -20,7 +19,6 @@ save=Opslaan
close=Sluiten close=Sluiten
filesSelected=Bestanden geselecteerd filesSelected=Bestanden geselecteerd
noFavourites=Geen favorieten toegevoegd noFavourites=Geen favorieten toegevoegd
downloadComplete=Download Complete
bored=Verveeld met wachten? bored=Verveeld met wachten?
alphabet=Alfabet alphabet=Alfabet
downloadPdf=Download PDF downloadPdf=Download PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Wybierz obraz(y)
genericSubmit=Wyślij genericSubmit=Wyślij
processTimeWarning=Ostrzeżenie: Ten proces może potrwać do minuty, w zależności od rozmiaru pliku processTimeWarning=Ostrzeżenie: Ten proces może potrwać do minuty, w zależności od rozmiaru pliku
pageOrderPrompt=Kolejność stron (wprowadź listę numerów stron oddzielonych przecinkami) : pageOrderPrompt=Kolejność stron (wprowadź listę numerów stron oddzielonych przecinkami) :
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Idź goToPage=Idź
true=Tak true=Tak
false=Nie false=Nie
@@ -20,7 +19,6 @@ save=Zapisz
close=Zamknij close=Zamknij
filesSelected=wybrane pliki filesSelected=wybrane pliki
noFavourites=Nie dodano ulubionych noFavourites=Nie dodano ulubionych
downloadComplete=Download Complete
bored=Znudzony czekaniem? bored=Znudzony czekaniem?
alphabet=Alfabet alphabet=Alfabet
downloadPdf=Pobierz PDF downloadPdf=Pobierz PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Selecione a(s) imagem(ns)
genericSubmit=Enviar genericSubmit=Enviar
processTimeWarning=Aviso: esse processo pode levar até um minuto, dependendo do tamanho do arquivo processTimeWarning=Aviso: esse processo pode levar até um minuto, dependendo do tamanho do arquivo
pageOrderPrompt=Ordem das páginas (digite uma lista separada por vírgulas de números de página): pageOrderPrompt=Ordem das páginas (digite uma lista separada por vírgulas de números de página):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Ir goToPage=Ir
true=Verdadeiro true=Verdadeiro
false=Falso false=Falso
@@ -20,7 +19,6 @@ save=Salvar
close=Fechar close=Fechar
filesSelected=arquivos selecionados filesSelected=arquivos selecionados
noFavourites=Nenhum favorito adicionado noFavourites=Nenhum favorito adicionado
downloadComplete=Download Complete
bored=Entediado esperando? bored=Entediado esperando?
alphabet=Alfabeto alphabet=Alfabeto
downloadPdf=baixar PDF downloadPdf=baixar PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Selectează imagini
genericSubmit=Trimite genericSubmit=Trimite
processTimeWarning=Avertisment: Acest proces poate dura până la un minut în funcție de dimensiunea fișierului processTimeWarning=Avertisment: Acest proces poate dura până la un minut în funcție de dimensiunea fișierului
pageOrderPrompt=Ordinea paginilor (Introdu o listă separată prin virgulă de numere de pagină): pageOrderPrompt=Ordinea paginilor (Introdu o listă separată prin virgulă de numere de pagină):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Mergi la pagină goToPage=Mergi la pagină
true=Adevărat true=Adevărat
false=Fals false=Fals
@@ -20,7 +19,6 @@ save=Salvează
close=Închide close=Închide
filesSelected=fișiere selectate filesSelected=fișiere selectate
noFavourites=Niciun favorit adăugat noFavourites=Niciun favorit adăugat
downloadComplete=Download Complete
bored=Plictisit așteptând? bored=Plictisit așteptând?
alphabet=Alfabet alphabet=Alfabet
downloadPdf=Descarcă PDF downloadPdf=Descarcă PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Выберите картинку(и)
genericSubmit=Отправить genericSubmit=Отправить
processTimeWarning=Внимание: Этот процесс может занять до минуты в зависимости от размера файла. processTimeWarning=Внимание: Этот процесс может занять до минуты в зависимости от размера файла.
pageOrderPrompt=Порядок страниц (введите список номеров страниц через запятую): pageOrderPrompt=Порядок страниц (введите список номеров страниц через запятую):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Вперед goToPage=Вперед
true=Истина true=Истина
false=Ложь false=Ложь
@@ -20,7 +19,6 @@ save=Сохранить
close=Закрыть close=Закрыть
filesSelected=файлов выбрано filesSelected=файлов выбрано
noFavourites=Нет избранного noFavourites=Нет избранного
downloadComplete=Download Complete
bored=Скучно ждать? bored=Скучно ждать?
alphabet=Алфавит alphabet=Алфавит
downloadPdf=Скачать PDF downloadPdf=Скачать PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Odaberi sliku (slike)
genericSubmit=Prihvatiti genericSubmit=Prihvatiti
processTimeWarning=Warning:Upozorenje: Ovaj proces može trajati i do minut, u zavisnosti od veličine dokumenta processTimeWarning=Warning:Upozorenje: Ovaj proces može trajati i do minut, u zavisnosti od veličine dokumenta
pageOrderPrompt=Prilagođeni redosled stranica (unesi listu brojeva stranica ili funkcija, kao što su 2n+1, razdvojene zarezima) : pageOrderPrompt=Prilagođeni redosled stranica (unesi listu brojeva stranica ili funkcija, kao što su 2n+1, razdvojene zarezima) :
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Idi goToPage=Idi
true=Tačno true=Tačno
false=Netačno false=Netačno
@@ -20,7 +19,6 @@ save=Sačuvaj
close=Zatvori close=Zatvori
filesSelected=odabrani fajlovi filesSelected=odabrani fajlovi
noFavourites=Nema dodatih favorita noFavourites=Nema dodatih favorita
downloadComplete=Download Complete
bored=Da li ti je dosadno dok čekaš? bored=Da li ti je dosadno dok čekaš?
alphabet=Alfabet alphabet=Alfabet
downloadPdf=Skini PDF downloadPdf=Skini PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Välj bild(er)
genericSubmit=Skicka genericSubmit=Skicka
processTimeWarning=Varning: Denna process kan ta upp till en minut beroende på filstorlek processTimeWarning=Varning: Denna process kan ta upp till en minut beroende på filstorlek
pageOrderPrompt=Sidordning (Ange en kommaseparerad lista med sidnummer) : pageOrderPrompt=Sidordning (Ange en kommaseparerad lista med sidnummer) :
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Gå till goToPage=Gå till
true=True true=True
false=Falskt false=Falskt
@@ -20,7 +19,6 @@ save=Spara
close=Stäng close=Stäng
filesSelected=filer valda filesSelected=filer valda
noFavourites=Inga favoriter har lagts till noFavourites=Inga favoriter har lagts till
downloadComplete=Download Complete
bored=Utråkad att vänta? bored=Utråkad att vänta?
alphabet=Alfabet alphabet=Alfabet
downloadPdf=Ladda ner PDF downloadPdf=Ladda ner PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=Resim(leri) seçin
genericSubmit=Gönder genericSubmit=Gönder
processTimeWarning=Uyarı: Bu işlem, dosya boyutuna bağlı olarak bir dakikaya kadar sürebilir. processTimeWarning=Uyarı: Bu işlem, dosya boyutuna bağlı olarak bir dakikaya kadar sürebilir.
pageOrderPrompt=Özel Sayfa Sırası (Virgülle ayrılmış sayfa numaraları veya 2n+1 gibi bir fonksiyon girin) : pageOrderPrompt=Özel Sayfa Sırası (Virgülle ayrılmış sayfa numaraları veya 2n+1 gibi bir fonksiyon girin) :
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Git goToPage=Git
true=Doğru true=Doğru
false=Yanlış false=Yanlış
@@ -20,7 +19,6 @@ save=Kaydet
close=Kapat close=Kapat
filesSelected=dosya seçildi filesSelected=dosya seçildi
noFavourites=Favori eklenmedi noFavourites=Favori eklenmedi
downloadComplete=Download Complete
bored=Sıkıldınız mı? bored=Sıkıldınız mı?
alphabet=Alfabe alphabet=Alfabe
downloadPdf=PDF İndir downloadPdf=PDF İndir

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=选择图像
genericSubmit=提交 genericSubmit=提交
processTimeWarning=警告:此过程可能需要多达一分钟,具体时间取决于文件大小 processTimeWarning=警告:此过程可能需要多达一分钟,具体时间取决于文件大小
pageOrderPrompt=页面顺序(输入逗号分隔的页码列表): pageOrderPrompt=页面顺序(输入逗号分隔的页码列表):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage= goToPage=
true= true=
false= false=
@@ -20,7 +19,6 @@ save=保存
close=关闭 close=关闭
filesSelected=选中的文件 filesSelected=选中的文件
noFavourites=没有添加收藏夹 noFavourites=没有添加收藏夹
downloadComplete=Download Complete
bored=无聊等待吗? bored=无聊等待吗?
alphabet=字母表 alphabet=字母表
downloadPdf=下载PDF downloadPdf=下载PDF

View File

@@ -1,4 +1,4 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,7 +11,6 @@ imgPrompt=選擇圖片
genericSubmit=送出 genericSubmit=送出
processTimeWarning=警告:此過程可能需要長達一分鐘,具體取決於檔案大小 processTimeWarning=警告:此過程可能需要長達一分鐘,具體取決於檔案大小
pageOrderPrompt=自訂頁面順序(輸入以逗號分隔的頁碼或函式,如 2n+1 pageOrderPrompt=自訂頁面順序(輸入以逗號分隔的頁碼或函式,如 2n+1
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=前往 goToPage=前往
true= true=
false= false=
@@ -20,7 +19,6 @@ save=儲存
close=關閉 close=關閉
filesSelected=已選擇的檔案 filesSelected=已選擇的檔案
noFavourites=未新增收藏 noFavourites=未新增收藏
downloadComplete=Download Complete
bored=等待時覺得無聊? bored=等待時覺得無聊?
alphabet=字母表 alphabet=字母表
downloadPdf=下載 PDF downloadPdf=下載 PDF

View File

@@ -1,4 +0,0 @@
.buttons-container {
margin-top: 20px;
text-align: center;
}

View File

@@ -1,28 +0,0 @@
#box-drag-container {
position: relative;
margin: 20px 0;
}
#pdf-canvas {
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
width: 100%;
}
.draggable-buttons-box {
position: absolute;
top: 0;
padding: 10px;
width: 100%;
display: flex;
gap: 5px;
}
.draggable-buttons-box > button {
z-index: 10;
background-color: rgba(13, 110, 253, 0.1);
}
.draggable-canvas {
border: 1px solid red;
position: absolute;
touch-action: none;
user-select: none;
top: 0px;
left: 0;
}

View File

@@ -1,11 +1,9 @@
/* Dark Mode Styles */ /* Dark Mode Styles */
body, body, select, textarea {
select, --body-background-color: 51, 51, 51;
textarea { --base-font-color: 255, 255, 255;
--body-background-color: 51, 51, 51; background-color: rgb(var(--body-background-color)) !important;
--base-font-color: 255, 255, 255; color: rgb(var(--base-font-color)) !important;
background-color: rgb(var(--body-background-color)) !important;
color: rgb(var(--base-font-color)) !important;
} }
.card { .card {
background-color: rgb(var(--body-background-color)) !important; background-color: rgb(var(--body-background-color)) !important;
@@ -13,11 +11,11 @@ textarea {
color: rgb(var(--base-font-color)) !important; color: rgb(var(--base-font-color)) !important;
} }
a { a {
color: #add8e6; color: #add8e6;
} }
a:hover { a:hover {
color: #87ceeb; /* Slightly brighter blue on hover for accessibility */ color: #87ceeb; /* Slightly brighter blue on hover for accessibility */
} }
.dark-card { .dark-card {
@@ -38,7 +36,7 @@ a:hover {
color: rgb(var(--base-font-color)) !important; color: rgb(var(--base-font-color)) !important;
} }
#support-section { #support-section {
background-color: #444 !important; background-color: #444 !important;
} }
#pages-container-wrapper { #pages-container-wrapper {
@@ -49,93 +47,89 @@ a:hover {
} }
.favorite-icon img { .favorite-icon img {
filter: brightness(0) invert(1) !important; filter: brightness(0) invert(1) !important;
} }
table thead { table thead {
background-color: #333 !important; background-color: #333 !important;
border: 1px solid #444; border: 1px solid #444;
} }
table th, table th, table td {
table td { border: 1px solid #444 !important;
border: 1px solid #444 !important; color: white;
color: white;
} }
.btn { .btn {
background-color: #444 !important; background-color: #444 !important;
border: none; border: none;
color: #fff !important; color: #fff !important;
} }
.btn-primary { .btn-primary {
background-color: #007bff !important; background-color: #007bff !important;
border: none; border: none;
color: #fff !important; color: #fff !important;
} }
.btn-secondary { .btn-secondary {
background-color: #6c757d !important; background-color: #6c757d !important;
border: none; border: none;
color: #fff !important; color: #fff !important;
} }
.btn-info { .btn-info {
background-color: #17a2b8 !important; background-color: #17a2b8 !important;
border: none; border: none;
color: #fff !important; color: #fff !important;
} }
.btn-danger { .btn-danger {
background-color: #dc3545 !important; background-color: #dc3545 !important;
border: none; border: none;
color: #fff !important; color: #fff !important;
} }
.btn-warning { .btn-warning {
background-color: #ffc107 !important; background-color: #ffc107 !important;
border: none; border: none;
color: #000 !important; color: #000 !important;
} }
.btn-outline-secondary { .btn-outline-secondary {
color: #fff !important; color: #fff !important;
border-color: #fff; border-color: #fff;
} }
.btn-outline-secondary:hover { .btn-outline-secondary:hover {
background-color: #444 !important; background-color: #444 !important;
color: #007bff !important; color: #007bff !important;
border-color: #007bff; border-color: #007bff;
} }
.blackwhite-icon { .blackwhite-icon {
filter: brightness(0) invert(1); filter: brightness(0) invert(1);
} }
hr { hr {
border-color: rgba(255, 255, 255, 0.6); /* semi-transparent white */ border-color: rgba(255, 255, 255, 0.6); /* semi-transparent white */
background-color: rgba(255, 255, 255, 0.6); /* for some browsers that might use background instead of border for <hr> */ background-color: rgba(255, 255, 255, 0.6); /* for some browsers that might use background instead of border for <hr> */
} }
.modal-content { .modal-content {
color: #fff !important; color: #fff !important;
border-color: #fff; border-color: #fff;
} }
#global-buttons-container input { #global-buttons-container input {
background-color: #323948; background-color: #323948;
caret-color: #ffffff; caret-color: #ffffff;
color: #ffffff; color: #ffffff;
} }
#global-buttons-container input::placeholder { #global-buttons-container input::placeholder {
color: #ffffff; color: #ffffff;
} }
#global-buttons-container input:disabled::-webkit-input-placeholder { #global-buttons-container input:disabled::-webkit-input-placeholder { /* WebKit browsers */
/* WebKit browsers */ color: #6E6865;
color: #6e6865;
} }
#global-buttons-container input:disabled:-moz-placeholder { #global-buttons-container input:disabled:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
/* Mozilla Firefox 4 to 18 */ color: #6E6865;
color: #6e6865;
} }
#global-buttons-container input:disabled::-moz-placeholder { #global-buttons-container input:disabled::-moz-placeholder { /* Mozilla Firefox 19+ */
/* Mozilla Firefox 19+ */ color: #6E6865;
color: #6e6865;
} }
#global-buttons-container input:disabled:-ms-input-placeholder { #global-buttons-container input:disabled:-ms-input-placeholder { /* Internet Explorer 10+ */
/* Internet Explorer 10+ */ color: #6E6865;
color: #6e6865;
} }

View File

@@ -1,78 +1,78 @@
#drag-container { #drag-container {
position: fixed; position: fixed;
display: flex; display:flex;
inset: 0; inset: 0;
pointer-events: none; pointer-events: none;
z-index: 10000; z-index: 10000;
visibility: hidden; visibility: hidden;
} }
#drag-container:not(:empty) { #drag-container:not(:empty) {
visibility: visible; visibility: visible;
} }
#drag-container .dragged-img { #drag-container .dragged-img {
position: fixed; position: fixed;
max-width: 200px; max-width: 200px;
max-height: 200px; max-height: 200px;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.58); box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.58);
transform-origin: top left; transform-origin: top left;
} }
.drag-manager_dragging { .drag-manager_dragging {
width: 0px; width: 0px;
visibility: hidden; visibility: hidden;
} }
.drag-manager_draghover { .drag-manager_draghover {
width: 375px !important; width: 375px !important;
} }
.drag-manager_draghover .insert-file-button-container { .drag-manager_draghover .insert-file-button-container {
display: none !important; display: none !important;
} }
.drag-manager_draghover .button-container { .drag-manager_draghover .button-container {
visibility: hidden !important; visibility: hidden !important;
} }
html[lang-direction="ltr"] .drag-manager_draghover img { html[lang-direction=ltr] .drag-manager_draghover img {
left: calc(50% + 62.5px) !important; left: calc(50% + 62.5px) !important;
} }
html[lang-direction="rtl"] .drag-manager_draghover img { html[lang-direction=rtl] .drag-manager_draghover img {
left: 125px; left: 125px
} }
.drag-manager_dragging-container .hide-on-drag { .drag-manager_dragging-container .hide-on-drag {
display: none !important; display: none !important;
} }
.drag-manager_endpoint { .drag-manager_endpoint {
width: 80px; width: 80px;
height: 100%; height: 100%;
background-color: #ffffff10; background-color: #FFFFFF10;
transition: width 0.1s; transition: width 0.1s;
animation: end-drop-expand 0.3s ease; animation: end-drop-expand .3s ease;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.drag-manager_endpoint svg { .drag-manager_endpoint svg {
width: 50px; width: 50px;
height: 50px; height: 50px;
} }
.drag-manager_endpoint.drag-manager_draghover { .drag-manager_endpoint.drag-manager_draghover {
width: 150px !important; width: 150px !important;
} }
@keyframes end-drop-expand { @keyframes end-drop-expand {
from { from {
width: 0; width: 0;
} }
to { to {
width: 80px; width: 80px;
} }
} }

View File

@@ -1,88 +0,0 @@
h1 {
text-align: center;
margin-top: 10%;
}
p {
text-align: center;
margin-top: 2em;
}
.button:hover {
background-color: #005b7f;
}
.features-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr));
gap: 25px 30px;
}
.feature-card {
border: 1px solid rgba(0, 0, 0, 0.125);
border-radius: 0.25rem;
padding: 1.25rem;
display: flex;
flex-direction: column;
align-items: flex-start;
}
.feature-card .card-text {
flex: 1;
}
#support-section {
background-color: #f9f9f9;
padding: 4rem;
margin-top: 1rem;
text-align: center;
}
#support-section h1 {
margin-top: 0;
}
#support-section p {
margin-top: 0;
}
#button-group {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
#github-button,
#discord-button {
display: inline-block;
padding: 1rem 2rem;
margin: 1rem;
background-color: #008cba;
color: #fff;
font-size: 1.2rem;
text-align: center;
text-decoration: none;
border-radius: 3rem;
transition: all 0.3s ease-in-out;
}
#github-button:hover,
#discord-button:hover,
#home-button:hover {
background-color: #005b7f;
}
#home-button {
display: block;
width: 200px;
height: 50px;
margin: 2em auto;
background-color: #008cba;
color: white;
text-align: center;
line-height: 50px;
text-decoration: none;
font-weight: bold;
border-radius: 25px;
transition: all 0.3s ease-in-out;
}

View File

@@ -1,97 +1,94 @@
#errorContainer { #errorContainer {
margin: 20px; /* adjust this value as needed */ margin: 20px; /* adjust this value as needed */
} }
#helpModalDialog { #helpModalDialog {
width: 90%; width: 90%;
max-width: 800px; max-width: 800px;
} }
#helpModal h1 { #helpModal h1 {
text-align: center; text-align: center;
margin-top: 10%; margin-top: 10%;
} }
#helpModal p { #helpModal p {
text-align: center; text-align: center;
margin-top: 2em; margin-top: 2em;
} }
#helpModal .button:hover { #helpModal .button:hover {
background-color: #005b7f; background-color: #005b7f;
} }
#helpModal .features-container { #helpModal .features-container {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr)); grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr));
gap: 25px 30px; gap: 25px 30px;
} }
#helpModal .feature-card { #helpModal .feature-card {
border: 1px solid rgba(0, 0, 0, 0.125); border: 1px solid rgba(0, 0, 0, .125);
border-radius: 0.25rem; border-radius: 0.25rem;
padding: 1.25rem; padding: 1.25rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
} }
#helpModal .feature-card .card-text { #helpModal .feature-card .card-text {
flex: 1; flex: 1;
} }
#support-section { #support-section {
background-color: #f9f9f9; background-color: #f9f9f9;
padding: 4rem; padding: 4rem;
margin-top: 1rem; margin-top: 1rem;
text-align: center; text-align: center;
} }
#support-section h1 { #support-section h1 {
margin-top: 0; margin-top: 0;
} }
#support-section p { #support-section p {
margin-top: 0; margin-top: 0;
} }
#button-group { #button-group {
display: flex; display: flex;
justify-content: center; justify-content: center;
flex-wrap: wrap; flex-wrap: wrap;
} }
#github-button, #github-button, #discord-button {
#discord-button { display: inline-block;
display: inline-block; padding: 1rem 2rem;
padding: 1rem 2rem; margin: 1rem;
margin: 1rem; background-color: #008CBA;
background-color: #008cba; color: #fff;
color: #fff; font-size: 1.2rem;
font-size: 1.2rem; text-align: center;
text-align: center; text-decoration: none;
text-decoration: none; border-radius: 3rem;
border-radius: 3rem; transition: all 0.3s ease-in-out;
transition: all 0.3s ease-in-out;
} }
#github-button:hover, #github-button:hover, #discord-button:hover, #home-button:hover {
#discord-button:hover, background-color: #005b7f;
#home-button:hover {
background-color: #005b7f;
} }
#home-button { #home-button {
display: block; display: block;
width: 200px; width: 200px;
height: 50px; height: 50px;
margin: 2em auto; margin: 2em auto;
background-color: #008cba; background-color: #008CBA;
color: white; color: white;
text-align: center; text-align: center;
line-height: 50px; line-height: 50px;
text-decoration: none; text-decoration: none;
font-weight: bold; font-weight: bold;
border-radius: 25px; border-radius: 25px;
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
} }

View File

@@ -1,10 +1,10 @@
.custom-file-label { .custom-file-label {
padding-right: 90px; padding-right: 90px;
} }
.selected-files { .selected-files {
margin-top: 10px; margin-top: 10px;
max-height: 150px; max-height: 150px;
overflow-y: auto; overflow-y: auto;
white-space: pre-wrap; white-space: pre-wrap;
} }

View File

@@ -1,20 +0,0 @@
#footer {
display: flex;
flex-direction: column; /* Stack children vertically */
justify-content: center;
align-items: center;
width: 100%;
}
.footer-center {
display: flex;
align-items: center; /* Center children horizontally */
flex-grow: 1;
}
.footer-powered-by {
margin-top: auto; /* Pushes the text to the bottom */
color: grey;
text-align: center; /* Centers the text inside the div */
width: 100%; /* Full width to center the text properly */
}

View File

@@ -1,54 +1,49 @@
#game-container { #game-container {
position: relative; position: relative;
width: 100vh; width: 100vh;
height: 0; height: 0;
padding-bottom: 75%; /* 4:3 aspect ratio */ padding-bottom: 75%; /* 4:3 aspect ratio */
background-color: transparent; background-color: transparent;
margin: auto; margin: auto;
overflow: hidden; overflow: hidden;
border: 2px solid black; /* Add border */ border: 2px solid black; /* Add border */
} }
.pdf, .pdf, .player, .projectile {
.player, position: absolute;
.projectile {
position: absolute;
} }
.pdf { .pdf {
width: 50px; width: 50px;
height: 50px; height: 50px;
} }
.player { .player {
width: 50px; width: 50px;
height: 50px; height: 50px;
} }
.projectile { .projectile {
background-color: black !important; background-color: black !important;
width: 5px; width: 5px;
height: 10px; height: 10px;
} }
#score, #score, #level, #lives, #high-score {
#level, color: black;
#lives, font-family: sans-serif;
#high-score { position: absolute;
color: black; font-size: calc(14px + 0.25vw); /* Reduced font size */
font-family: sans-serif;
position: absolute;
font-size: calc(14px + 0.25vw); /* Reduced font size */
} }
#score { #score {
top: 10px; top: 10px;
left: 10px; left: 10px;
} }
#lives { #lives {
top: 10px; top: 10px;
left: calc(9vw); /* Adjusted position */ left: calc(7vw); /* Adjusted position */
} }
#high-score { #high-score {
top: 10px; top: 10px;
left: calc(14vw); /* Adjusted position */ left: calc(14vw); /* Adjusted position */
} }
#level { #level {
top: 10px; top: 10px;
right: 10px; right: 10px;
} }

View File

@@ -1,20 +1,20 @@
#page-container { #page-container {
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
#content-wrap { #content-wrap {
flex: 1; flex: 1;
} }
#footer { #footer {
bottom: 0; bottom: 0;
width: 100%; width: 100%;
} }
.navbar { .navbar {
height: auto; /* Adjusts height automatically based on content */ height: auto; /* Adjusts height automatically based on content */
white-space: nowrap; /* Prevents wrapping of navbar contents */ white-space: nowrap; /* Prevents wrapping of navbar contents */
} }
/* TODO enable later /* TODO enable later
.navbar .container { .navbar .container {
@@ -25,70 +25,70 @@
margin-right: auto; margin-right: auto;
}*/ }*/
html[lang-direction="ltr"] * { html[lang-direction=ltr] * {
direction: ltr; direction: ltr;
} }
html[lang-direction="rtl"] * { html[lang-direction=rtl] * {
direction: rtl; direction: rtl;
text-align: right; text-align: right;
} }
.ignore-rtl { .ignore-rtl {
direction: ltr !important; direction: ltr !important;
text-align: left !important; text-align: left !important;
} }
.align-top { .align-top {
position: absolute; position: absolute;
top: 0; top: 0;
} }
.align-center-right { .align-center-right {
position: absolute; position: absolute;
right: 0; right: 0;
top: 50%; top: 50%;
} }
.align-center-left { .align-center-left {
position: absolute; position: absolute;
left: 0; left: 0;
top: 50%; top: 50%;
} }
.align-bottom { .align-bottom {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
} }
.btn-group > label:first-of-type { .btn-group > label:first-of-type {
border-top-left-radius: 0.25rem !important; border-top-left-radius: 0.25rem !important;
border-bottom-left-radius: 0.25rem !important; border-bottom-left-radius: 0.25rem !important;
} }
html[lang-direction="rtl"] input.form-check-input { html[lang-direction="rtl"] input.form-check-input {
position: relative; position: relative;
margin-left: 0px; margin-left: 0px;
} }
html[lang-direction="rtl"] label.form-check-label { html[lang-direction="rtl"] label.form-check-label {
display: inline; display: inline;
} }
.margin-auto-parent { .margin-auto-parent {
width: 100%; width: 100%;
display: flex; display: flex;
} }
.margin-center { .margin-center {
margin: 0 auto; margin: 0 auto;
} }
#pdf-canvas { #pdf-canvas {
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384); box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
width: 100%; width: 100%;
} }
.fixed-shadow-canvas { .fixed-shadow-canvas {
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384); box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
width: 100%; width: 100%;
} }
.shadow-canvas { .shadow-canvas {
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384); box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
} }
.hidden { .hidden {
display: none; display: none;
} }

View File

@@ -1,62 +1,64 @@
#searchBar { #searchBar {
background-image: url("../images/search.svg"); background-image: url('../images/search.svg');
background-position: 16px 16px; background-position: 16px 16px;
background-repeat: no-repeat; background-repeat: no-repeat;
width: 100%; width: 100%;
font-size: 16px; font-size: 16px;
margin-bottom: 12px; margin-bottom: 12px;
padding: 12px 20px 12px 40px; padding: 12px 20px 12px 40px;
border: 1px solid #ddd; border: 1px solid #ddd;
} }
.dark-mode-search { .dark-mode-search {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' hei… 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z'/%3E%3C/svg%3E") !important; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' hei… 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z'/%3E%3C/svg%3E") !important;
color: #f8f9fa !important; color: #f8f9fa !important;
background-color: #212529 !important; background-color: #212529 !important;
border-color: #343a40 !important; border-color: #343a40 !important;
} }
.features-container { .features-container {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(15rem, 3fr)); grid-template-columns: repeat(auto-fill, minmax(15rem, 3fr));
gap: 25px 30px; gap: 25px 30px;
} }
.feature-card { .feature-card {
border: 2px solid rgba(0, 0, 0, 0.25); border: 2px solid rgba(0, 0, 0, .25);
border-radius: 0.25rem; border-radius: 0.25rem;
padding: 1.25rem; padding: 1.25rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
background: rgba(13, 110, 253, 0.05); background: rgba(13, 110, 253, 0.05);
transition: transition: transform 0.3s, border 0.3s;
transform 0.3s, transform-origin: center center;
border 0.3s; outline: 2px solid transparent;
transform-origin: center center;
outline: 2px solid transparent;
} }
.feature-card a { .feature-card a {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.feature-card .card-text { .feature-card .card-text {
flex: 1; flex: 1;
} }
.feature-card:hover { .feature-card:hover {
outline: 1px solid rgba(0, 0, 0, 0.5); outline: 1px solid rgba(0, 0, 0, .5);
cursor: pointer; cursor: pointer;
transform: scale(1.1); transform: scale(1.1);
} }
.feature-card:hover .card-title { .feature-card:hover .card-title {
text-decoration: underline; text-decoration: underline;
} }
.card-title.text-primary { .card-title.text-primary {
color: #000; /* Replace with your desired shade of blue */ color: #000; /* Replace with your desired shade of blue */
@@ -65,27 +67,27 @@
.home-card-icon { .home-card-icon {
width: 30px; width: 30px;
height: 30px; height: 30px;
transform: translateY(-5px); transform: translateY(-5px);
} }
.home-card-icon-colour { .home-card-icon-colour {
filter: invert(0.2) sepia(2) saturate(50) hue-rotate(190deg); filter: invert(0.2) sepia(2) saturate(50) hue-rotate(190deg);
} }
.favorite-icon { .favorite-icon {
display: none; display: none;
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 10px; right: 10px;
} }
/* Only show the favorite icons when the parent card is being hovered over */ /* Only show the favorite icons when the parent card is being hovered over */
.feature-card:hover .favorite-icon { .feature-card:hover .favorite-icon {
display: block; display: block;
} }
.favorite-icon img { .favorite-icon img {
filter: brightness(0); filter: brightness(0);
} }
.jumbotron { .jumbotron {
padding: 3rem 3rem; /* Reduce vertical padding */ padding: 3rem 3rem; /* Reduce vertical padding */
} }

View File

@@ -1,43 +1,40 @@
#image-highlighter { #image-highlighter {
position: fixed; position: fixed;
display: flex; display:flex;
inset: 0; inset: 0;
z-index: 10000; z-index: 10000;
background-color: rgba(0, 0, 0, 0); background-color: rgba(0, 0, 0, 0);
visibility: hidden; visibility: hidden;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: transition: visbility 0.1s linear, background-color 0.1s linear;
visbility 0.1s linear,
background-color 0.1s linear;
} }
#image-highlighter > * { #image-highlighter > * {
max-width: 80vw; max-width: 80vw;
max-height: 80vh; max-height: 80vh;
animation: image-highlight 0.1s linear; animation: image-highlight .1s linear;
transition: transition: transform .1s linear, opacity .1s linear;
transform 0.1s linear,
opacity 0.1s linear;
} }
#image-highlighter > *.remove { #image-highlighter > *.remove {
transform: scale(0.8) !important; transform: scale(0.8) !important;
opacity: 0 !important; opacity: 0 !important;
} }
#image-highlighter:not(:empty) { #image-highlighter:not(:empty) {
background-color: rgba(0, 0, 0, 0.37); background-color: rgba(0, 0, 0, 0.37);
visibility: visible; visibility: visible;
} }
@keyframes image-highlight { @keyframes image-highlight {
from { from {
transform: scale(0.8); transform: scale(0.8);
opacity: 0; opacity: 0;
} }
to { to {
transform: scale(1); transform: scale(1);
opacity: 1; opacity: 1;
} }
} }

View File

@@ -1,9 +0,0 @@
td a {
text-decoration: none;
}
td a:hover,
td a:focus {
text-decoration: underline;
/* Adds underline on hover/focus for clarity */
}

View File

@@ -1,13 +1,14 @@
/* Dark Mode Styles */ /* Dark Mode Styles */
body { body {
--body-background-color: 255, 255, 255; --body-background-color: 255, 255, 255;
--base-font-color: 33, 37, 41; --base-font-color: 33, 37, 41;
} }
#global-buttons-container input { #global-buttons-container input {
background-color: #ffffff; background-color: #ffffff;
/*caret-color: #ffffff;*/ /*caret-color: #ffffff;*/
/*color: #ffffff;*/ /*color: #ffffff;*/
} }
/*#global-buttons-container input:disabled::-webkit-input-placeholder { !* WebKit browsers *!*/ /*#global-buttons-container input:disabled::-webkit-input-placeholder { !* WebKit browsers *!*/
/* color: #98A0AB;*/ /* color: #98A0AB;*/

View File

@@ -1,111 +0,0 @@
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;
}
body.light-mode input:-webkit-autofill,
body.light-mode input:-webkit-autofill:hover,
body.light-mode input:-webkit-autofill:focus,
body.light-mode input:-webkit-autofill:active {
-webkit-text-fill-color: #212529; /* Dark font color */
-webkit-box-shadow: 0 0 0 1000px #f8f9fa inset; /* Light background color */
}
/* Dark Mode */
body.dark-mode input:-webkit-autofill,
body.dark-mode input:-webkit-autofill:hover,
body.dark-mode input:-webkit-autofill:focus,
body.dark-mode input:-webkit-autofill:active {
-webkit-text-fill-color: #f8f9fa; /* Light font color */
-webkit-box-shadow: 0 0 0 1000px #212529 inset; /* Dark background color */
}
/* Light Mode */
body.light-mode .form-floating > input:focus + label {
color: #212529 !important; /* Dark text for light background */
}
/* Dark Mode */
body.dark-mode .form-floating > input:focus + label {
color: #fff !important; /* Light text for dark background */
}
body.light-mode .form-floating > label {
color: #212529 !important; /* Dark text for light background */
}
body.dark-mode .form-floating > label {
color: #fff !important; /* Light text for dark background */
}
/* Removing default styles for ul and li */
ul {
list-style: none;
padding: 0;
margin: 0;
}
li {
list-style: none;
}
/* Positioning the container of these elements to the top right */
.your-container-class {
position: absolute;
top: 0;
right: 0;
display: flex;
}
/* Styling for the dropdown */
.dropdown-menu {
min-width: 200px; /* or whatever width you prefer */
}
.navbar-icon {
width: 40px;
height: 40px;
}

View File

@@ -1,4 +1,4 @@
.list-group-item { .list-group-item {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@@ -25,4 +25,5 @@
.move-down span { .move-down span {
font-weight: bold; font-weight: bold;
font-size: 1.2em; font-size: 1.2em;
} }

View File

@@ -1,119 +0,0 @@
.multi-tool-container {
max-width: 95vw;
margin: 0 auto;
}
#global-buttons-container {
display: flex;
gap: 10px;
align-items: start;
background-color: rgba(13, 110, 253, 0.1);
border: 1px solid rgba(0, 0, 0, 0.25);
backdrop-filter: blur(2px);
top: 10px;
z-index: 10;
padding: 10px;
border-radius: 8px;
}
#global-buttons-container > * {
padding: 0.6rem 0.75rem;
}
#global-buttons-container svg {
width: 20px;
height: 20px;
}
#export-button {
margin-left: auto;
}
#pages-container-wrapper {
--background-color: rgba(0, 0, 0, 0.025);
--scroll-bar-color: #f1f1f1;
--scroll-bar-thumb: #888;
--scroll-bar-thumb-hover: #555;
background-color: var(--background-color);
width: 100%;
display: flex;
flex-direction: column;
padding: 10px 25px;
border-radius: 10px;
overflow-y: hidden;
overflow-x: auto;
min-height: 275px;
margin: 0 0 30px 0;
}
#pages-container {
margin: auto;
gap: 0px;
display: flex;
align-items: center;
justify-content: center;
}
/* width */
#pages-container-wrapper::-webkit-scrollbar {
width: 10px;
height: 10px;
}
/* Track */
#pages-container-wrapper::-webkit-scrollbar-track {
background: var(--scroll-bar-color);
}
/* Handle */
#pages-container-wrapper::-webkit-scrollbar-thumb {
border-radius: 10px;
background: var(--scroll-bar-thumb);
}
/* Handle on hover */
#pages-container-wrapper::-webkit-scrollbar-thumb:hover {
background: var(--scroll-bar-thumb-hover);
}
.page-container {
height: 250px;
display: flex;
align-items: center;
flex-direction: column-reverse;
aspect-ratio: 1;
text-align: center;
position: relative;
user-select: none;
transition: width 1s linear;
}
.page-container img {
/* max-width: calc(100% - 15px); */
max-height: calc(100% - 15px);
max-width: 237px;
display: block;
position: absolute;
left: 50%;
top: 50%;
translate: -50% -50%;
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
border-radius: 4px;
transition: rotate 0.3s;
}
#add-pdf-button {
margin: 0 auto;
}
.page-number {
position: absolute;
top: 5px;
right: 5px;
color: white;
background-color: #007bff; /* Primary blue color */
padding: 3px 6px;
border-radius: 4px;
font-size: 12px;
z-index: 2;
}

View File

@@ -1,116 +1,80 @@
#navbarSearch {
top: 100%;
right: 0;
transition: all 0.3s;
max-height: 0;
overflow: hidden;
}
#navbarSearch.show {
max-height: 300px; /* Adjust this to your desired max height */ #navbarSearch {
top: 100%;
right: 0;
} }
#searchForm { #searchForm {
width: 200px; /* Adjust this value as needed */ width: 200px; /* Adjust this value as needed */
} }
/* Style the search results to match the navbar */ /* Style the search results to match the navbar */
#searchResults { #searchResults {
max-height: 200px; /* Adjust this value as needed */ max-height: 200px; /* Adjust this value as needed */
overflow-y: auto; overflow-y: auto;
width: 100%; width: 100%;
max-width: 300px; /* Adjust to your preferred width */
transition: height 0.3s ease; /* Smooth height transition */
} }
#searchResults .dropdown-item { #searchResults .dropdown-item {
display: flex; display: flex;
align-items: center; align-items: center;
white-space: nowrap; white-space: nowrap;
height: 50px; /* Fixed height */ height: 50px; /* Fixed height */
overflow: hidden; /* Hide overflow */ overflow: hidden; /* Hide overflow */
} }
#searchResults .icon { #searchResults .icon {
margin-right: 10px; margin-right: 10px;
} }
#searchResults .icon-text { #searchResults .icon-text {
display: inline; display: inline;
overflow: hidden; /* Hide overflow */ overflow: hidden; /* Hide overflow */
text-overflow: ellipsis; /* Add ellipsis for long text */ text-overflow: ellipsis; /* Add ellipsis for long text */
} }
#search-icon i {
font-size: 24px; /* Adjust this to your desired size */
transition: color 0.3s;
}
#search-icon:hover i {
color: #666; /* Adjust this to your hover color */
}
.search-input {
transition:
border 0.3s,
box-shadow 0.3s;
}
.search-input:focus {
border-color: #666; /* Adjust this to your focus color */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* Adjust this to your desired shadow */
}
/* Set a fixed height and styling for each search result item */
.search-results a {
display: flex;
align-items: center;
gap: 10px; /* space between icon and text */
height: 40px; /* Adjust based on your design */
overflow: hidden; /* Prevent content from overflowing */
white-space: nowrap; /* Prevent text from wrapping to next line */
text-overflow: ellipsis; /* Truncate text if it's too long */
}
.main-icon { .main-icon {
width: 36px; width: 36px;
height: 36px; height: 36px;
vertical-align: middle; vertical-align: middle;
transform: translateY(-2px); transform: translateY(-2px);
} }
.icon { .icon {
width: 16px; width: 16px;
height: 16px; height: 16px;
vertical-align: middle; vertical-align: middle;
transform: translateY(-2px); transform: translateY(-2px);
} }
.icon + .icon { .icon+.icon {
margin-left: -4px; margin-left: -4px;
} }
.icon-text { .icon-text {
margin-left: 4px; margin-left: 4px;
} }
.nav-item-separator { .nav-item-separator {
position: relative; position: relative;
margin: 0 4px; /* Adjust the margin as needed */ margin: 0 4px; /* Adjust the margin as needed */
} }
.nav-item-separator::before { .nav-item-separator::before {
content: ""; content: '';
position: absolute; position: absolute;
left: 0; left: 0;
top: 10%; /* Adjust the top and bottom margins as needed */ top: 10%; /* Adjust the top and bottom margins as needed */
bottom: 10%; bottom: 10%;
width: 1px; width: 1px;
background-color: #ccc; /* Adjust the color as needed */ background-color: #ccc; /* Adjust the color as needed */
} }
.navbar-icon { .navbar-icon {
width: 20px; width: 20px;
height: 20px; height: 20px;
transform: translateY(-2px); transform: translateY(-2px);
} }

View File

@@ -1,85 +1,87 @@
.pdf-actions_button-container { .pdf-actions_button-container {
z-index: 2; z-index: 2;
display: flex; display:flex;
opacity: 0; opacity: 0;
transition: opacity 0.1s linear; transition: opacity 0.1s linear;
} }
.pdf-actions_container:hover .pdf-actions_button-container { .pdf-actions_container:hover .pdf-actions_button-container {
opacity: 1; opacity: 1;
} }
.pdf-actions_button-container > * { .pdf-actions_button-container > * {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
margin: 3px; margin: 3px;
display: block; display: block;
} }
.pdf-actions_container svg { .pdf-actions_container svg {
width: 16px; width: 16px;
height: 16px; height: 16px;
} }
.pdf-actions_container:nth-child(1) .pdf-actions_move-left-button { .pdf-actions_container:nth-child(1) .pdf-actions_move-left-button {
display: none; display: none;
} }
.pdf-actions_container:last-child .pdf-actions_move-right-button { .pdf-actions_container:last-child .pdf-actions_move-right-button {
display: none; display: none;
} }
/* "insert pdf" buttons that appear on the right when hover */ /* "insert pdf" buttons that appear on the right when hover */
.pdf-actions_insert-file-button-container { .pdf-actions_insert-file-button-container {
translate: 0 -50%; translate: 0 -50%;
width: 80px; width: 80px;
height: 100%; height: 100%;
z-index: 1; z-index: 1;
opacity: 0; opacity: 0;
transition: opacity 0.2s; transition: opacity 0.2s;
} }
.pdf-actions_insert-file-button-container.left { .pdf-actions_insert-file-button-container.left {
left: -20px; left: -20px;
} }
.pdf-actions_insert-file-button-container.right { .pdf-actions_insert-file-button-container.right {
right: -20px; right: -20px;
} }
html[lang-direction="ltr"] .pdf-actions_insert-file-button-container.right { html[lang-direction=ltr] .pdf-actions_insert-file-button-container.right {
display: none; display:none;
} }
html[lang-direction="rtl"] .pdf-actions_insert-file-button-container.left { html[lang-direction=rtl] .pdf-actions_insert-file-button-container.left {
display: none; display:none;
} }
.pdf-actions_insert-file-button-container.left .pdf-actions_insert-file-button { .pdf-actions_insert-file-button-container.left .pdf-actions_insert-file-button {
left: 0; left: 0;
translate: 0 -50%; translate: 0 -50%;
} }
.pdf-actions_insert-file-button-container.right .pdf-actions_insert-file-button { .pdf-actions_insert-file-button-container.right .pdf-actions_insert-file-button {
right: 0; right: 0;
translate: 0 -50%; translate: 0 -50%;
} }
html[lang-direction="ltr"] .pdf-actions_container:last-child > .pdf-actions_insert-file-button-container.right { html[lang-direction=ltr] .pdf-actions_container:last-child > .pdf-actions_insert-file-button-container.right {
display: block; display: block;
} }
html[lang-direction="rtl"] .pdf-actions_container:last-child > .pdf-actions_insert-file-button-container.left {
display: block; html[lang-direction=rtl] .pdf-actions_container:last-child > .pdf-actions_insert-file-button-container.left {
display: block;
} }
.pdf-actions_insert-file-button-container:hover { .pdf-actions_insert-file-button-container:hover {
opacity: 1; opacity: 1;
transition: opacity 0.05s; transition: opacity 0.05s;
} }
.pdf-actions_insert-file-button { .pdf-actions_insert-file-button {
position: absolute; position: absolute;
top: 50%; top: 50%;
right: 50%; right: 50%;
translate: 50% -50%; translate: 50% -50%;
aspect-ratio: 1; aspect-ratio: 1;
border-radius: 100px; border-radius: 100px;
} }

View File

@@ -1,21 +0,0 @@
.btn-margin {
margin-right: 2px;
}
.bordered-box {
border: 1px solid #ddd;
padding: 20px;
margin: 20px;
width: 70%;
}
.center-element {
width: 80%;
text-align: center;
margin: auto;
}
.element-margin {
margin: 10px 0;
/* Adjust this value to increase/decrease the margin as needed */
}

View File

@@ -1,113 +1,37 @@
/* Rainbow Mode Styles */ /* Rainbow Mode Styles */
body { body {
background: linear-gradient( background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%);
90deg, color: #fff !important;
rgba(255, 0, 0, 1) 0%, --body-background-color: 255, 255, 255;
rgba(255, 154, 0, 1) 10%, --base-font-color: 33, 37, 41;
rgba(208, 222, 33, 1) 20%,
rgba(79, 220, 74, 1) 30%,
rgba(63, 218, 216, 1) 40%,
rgba(47, 201, 226, 1) 50%,
rgba(28, 127, 238, 1) 60%,
rgba(95, 21, 242, 1) 70%,
rgba(186, 12, 248, 1) 80%,
rgba(251, 7, 217, 1) 90%,
rgba(255, 0, 0, 1) 100%
);
color: #fff !important;
--body-background-color: 255, 255, 255;
--base-font-color: 33, 37, 41;
} }
.dark-card { .dark-card {
background: linear-gradient( background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%) !important;
90deg, color: white !important;
rgba(255, 0, 0, 1) 0%,
rgba(255, 154, 0, 1) 10%,
rgba(208, 222, 33, 1) 20%,
rgba(79, 220, 74, 1) 30%,
rgba(63, 218, 216, 1) 40%,
rgba(47, 201, 226, 1) 50%,
rgba(28, 127, 238, 1) 60%,
rgba(95, 21, 242, 1) 70%,
rgba(186, 12, 248, 1) 80%,
rgba(251, 7, 217, 1) 90%,
rgba(255, 0, 0, 1) 100%
) !important;
color: white !important;
} }
.jumbotron { .jumbotron {
background: linear-gradient( background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%);
90deg, color: #fff !important;
rgba(255, 0, 0, 1) 0%,
rgba(255, 154, 0, 1) 10%,
rgba(208, 222, 33, 1) 20%,
rgba(79, 220, 74, 1) 30%,
rgba(63, 218, 216, 1) 40%,
rgba(47, 201, 226, 1) 50%,
rgba(28, 127, 238, 1) 60%,
rgba(95, 21, 242, 1) 70%,
rgba(186, 12, 248, 1) 80%,
rgba(251, 7, 217, 1) 90%,
rgba(255, 0, 0, 1) 100%
);
color: #fff !important;
} }
.list-group { .list-group {
background: linear-gradient( background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%) !important;
90deg, color: fff !important;
rgba(255, 0, 0, 1) 0%,
rgba(255, 154, 0, 1) 10%,
rgba(208, 222, 33, 1) 20%,
rgba(79, 220, 74, 1) 30%,
rgba(63, 218, 216, 1) 40%,
rgba(47, 201, 226, 1) 50%,
rgba(28, 127, 238, 1) 60%,
rgba(95, 21, 242, 1) 70%,
rgba(186, 12, 248, 1) 80%,
rgba(251, 7, 217, 1) 90%,
rgba(255, 0, 0, 1) 100%
) !important;
color: fff !important;
} }
.list-group-item { .list-group-item {
background: linear-gradient( background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%) !important;
90deg, color: fff !important;
rgba(255, 0, 0, 1) 0%,
rgba(255, 154, 0, 1) 10%,
rgba(208, 222, 33, 1) 20%,
rgba(79, 220, 74, 1) 30%,
rgba(63, 218, 216, 1) 40%,
rgba(47, 201, 226, 1) 50%,
rgba(28, 127, 238, 1) 60%,
rgba(95, 21, 242, 1) 70%,
rgba(186, 12, 248, 1) 80%,
rgba(251, 7, 217, 1) 90%,
rgba(255, 0, 0, 1) 100%
) !important;
color: fff !important;
} }
#support-section { #support-section {
background: linear-gradient( background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%) !important;
90deg,
rgba(255, 0, 0, 1) 0%,
rgba(255, 154, 0, 1) 10%,
rgba(208, 222, 33, 1) 20%,
rgba(79, 220, 74, 1) 30%,
rgba(63, 218, 216, 1) 40%,
rgba(47, 201, 226, 1) 50%,
rgba(28, 127, 238, 1) 60%,
rgba(95, 21, 242, 1) 70%,
rgba(186, 12, 248, 1) 80%,
rgba(251, 7, 217, 1) 90%,
rgba(255, 0, 0, 1) 100%
) !important;
} }
#pages-container-wrapper { #pages-container-wrapper {
--background-color: rgba(255, 255, 255, 0.046) !important; --background-color: rgba(255, 255, 255, 0.046) !important;
--scroll-bar-color: #4c4c4c !important; --scroll-bar-color: #4c4c4c !important;
--scroll-bar-thumb: #d3d3d3 !important; --scroll-bar-thumb: #d3d3d3 !important;
--scroll-bar-thumb-hover: #ffffff !important; --scroll-bar-thumb-hover: #ffffff !important;
} }

View File

@@ -1,29 +0,0 @@
#pdf-preview {
margin: 0 auto;
display: block;
max-width: calc(100% - 30px);
max-height: calc(100% - 30px);
box-shadow: 0 0 4px rgba(100, 100, 100, 0.25);
transition: rotate 0.3s;
position: absolute;
top: 50%;
left: 50%;
translate: -50% -50%;
}
.previewContainer {
aspect-ratio: 1;
width: 100%;
border: 1px solid rgba(0, 0, 0, 0.125);
border-radius: 0.25rem;
margin: 1rem 0;
padding: 15px;
display: block;
overflow: hidden;
position: relative;
}
.buttonContainer {
display: flex;
justify-content: space-around;
}

View File

@@ -1,39 +0,0 @@
select#font-select,
select#font-select option {
height: 60px; /* Adjust as needed */
font-size: 30px; /* Adjust as needed */
}
.drawing-pad-container {
text-align: center;
}
#drawing-pad-canvas {
background: rgba(125, 125, 125, 0.2);
width: 100%;
height: 300px;
}
#box-drag-container {
position: relative;
margin: 20px 0;
}
.draggable-buttons-box {
position: absolute;
top: 0;
padding: 10px;
width: 100%;
display: flex;
gap: 5px;
}
.draggable-buttons-box > button {
z-index: 10;
background-color: rgba(13, 110, 253, 0.1);
}
.draggable-canvas {
border: 1px solid red;
position: absolute;
touch-action: none;
user-select: none;
top: 0px;
left: 0;
}

View File

@@ -1,10 +0,0 @@
.pdf-visual-aid {
width: 150px; /* Adjust as needed */
height: 200px; /* Adjust as needed */
border: 1px solid black; /* Represents the PDF page */
position: relative;
}
.line {
position: absolute;
background-color: red; /* Line color */
}

View File

@@ -1,41 +0,0 @@
.a4container {
position: relative;
width: 50%;
aspect-ratio: 0.707;
border: 1px solid #ddd;
box-sizing: border-box;
background-color: white;
}
.pageNumber {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
font-size: 1em;
color: #333;
cursor: pointer;
background-color: #ccc;
width: 15%;
height: 15%;
transform: translate(-50%, -50%);
}
.pageNumber:hover {
background-color: #eee;
}
#myForm {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.selectedPosition {
background-color: #0a0;
}
.selectedPosition.selectedHovered {
background-color: #006600;
}

View File

@@ -1,24 +1,26 @@
.tab-group { .tab-group {
} }
.tab-container { .tab-container {
display: none; display: none;
} }
.tab-container.active { .tab-container.active {
display: block; display: block;
border: 1px solid rgba(var(--base-font-color), 0.25); border: 1px solid rgba(var(--base-font-color), 0.25);
padding: 15px; padding: 15px;
} }
.tab-buttons > button { .tab-buttons > button {
margin-bottom: -1px; margin-bottom: -1px;
background: 0 0; background: 0 0;
border: 1px solid transparent; border: 1px solid transparent;
color: rgb(var(--base-font-color)); color: rgb(var(--base-font-color));
border-top-left-radius: 0.25rem; border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem; border-top-right-radius: 0.25rem;
} }
.tab-buttons > button.active { .tab-buttons > button.active {
background-color: rgb(var(--body-background-color)); background-color: rgb(var(--body-background-color));
border-color: rgba(var(--base-font-color), 0.25) rgba(var(--base-font-color), 0.25) rgb(var(--body-background-color)); border-color: rgba(var(--base-font-color), 0.25) rgba(var(--base-font-color), 0.25) rgb(var(--body-background-color));
} }

View File

@@ -1,5 +1,5 @@
var toggleCount = 0; var toggleCount = 0
var lastToggleTime = Date.now(); var lastToggleTime = Date.now()
var elements = { var elements = {
lightModeStyles: null, lightModeStyles: null,
@@ -11,18 +11,18 @@ var elements = {
navbar: null, navbar: null,
navIcons: null, navIcons: null,
navDropdownMenus: null, navDropdownMenus: null,
}; }
function getElements() { function getElements() {
elements.lightModeStyles = document.getElementById("light-mode-styles"); elements.lightModeStyles = document.getElementById("light-mode-styles")
elements.darkModeStyles = document.getElementById("dark-mode-styles"); elements.darkModeStyles = document.getElementById("dark-mode-styles")
elements.rainbowModeStyles = document.getElementById("rainbow-mode-styles"); elements.rainbowModeStyles = document.getElementById("rainbow-mode-styles")
elements.darkModeIcon = document.getElementById("dark-mode-icon"); elements.darkModeIcon = document.getElementById("dark-mode-icon")
elements.searchBar = document.getElementById("searchBar"); elements.searchBar = document.getElementById("searchBar")
elements.formControls = document.querySelectorAll(".form-control"); elements.formControls = document.querySelectorAll(".form-control")
elements.navbar = document.querySelectorAll("nav.navbar"); elements.navbar = document.querySelectorAll("nav.navbar")
elements.navIcons = document.querySelectorAll("nav .icon, .navbar-icon"); elements.navIcons = document.querySelectorAll("nav .icon, .navbar-icon")
elements.navDropdownMenus = document.querySelectorAll(".dropdown-menu"); elements.navDropdownMenus = document.querySelectorAll("nav .dropdown-menu")
} }
function setMode(mode) { function setMode(mode) {
var event = new CustomEvent("modeChanged", { detail: mode }); var event = new CustomEvent("modeChanged", { detail: mode });
@@ -48,22 +48,22 @@ function setMode(mode) {
elements.searchBar.classList.add("dark-mode-search"); elements.searchBar.classList.add("dark-mode-search");
} }
if (elements && elements.formControls) { if (elements && elements.formControls) {
elements.formControls.forEach((input) => input.classList.add("bg-dark", "text-white")); elements.formControls.forEach(input => input.classList.add("bg-dark", "text-white"));
} }
if (elements && elements.navbar) { if (elements && elements.navbar) {
elements.navbar.forEach((navElement) => { elements.navbar.forEach(navElement => {
navElement.classList.remove("navbar-light", "bg-light"); navElement.classList.remove("navbar-light", "bg-light");
navElement.classList.add("navbar-dark", "bg-dark"); navElement.classList.add("navbar-dark", "bg-dark");
}); });
} }
if (elements && elements.navDropdownMenus) { if (elements && elements.navDropdownMenus) {
elements.navDropdownMenus.forEach((menu) => menu.classList.add("dropdown-menu-dark")); elements.navDropdownMenus.forEach(menu => menu.classList.add("dropdown-menu-dark"));
} }
if (elements && elements.navIcons) { if (elements && elements.navIcons) {
elements.navIcons.forEach((icon) => (icon.style.filter = "invert(1)")); elements.navIcons.forEach(icon => (icon.style.filter = "invert(1)"));
} }
var tables = document.querySelectorAll(".table"); var tables = document.querySelectorAll(".table");
tables.forEach((table) => { tables.forEach(table => {
table.classList.add("table-dark"); table.classList.add("table-dark");
}); });
if (jumbotron) { if (jumbotron) {
@@ -78,22 +78,22 @@ function setMode(mode) {
elements.searchBar.classList.remove("dark-mode-search"); elements.searchBar.classList.remove("dark-mode-search");
} }
if (elements && elements.formControls) { if (elements && elements.formControls) {
elements.formControls.forEach((input) => input.classList.remove("bg-dark", "text-white")); elements.formControls.forEach(input => input.classList.remove("bg-dark", "text-white"));
} }
if (elements && elements.navbar) { if (elements && elements.navbar) {
elements.navbar.forEach((navElement) => { elements.navbar.forEach(navElement => {
navElement.classList.remove("navbar-dark", "bg-dark"); navElement.classList.remove("navbar-dark", "bg-dark");
navElement.classList.add("navbar-light", "bg-light"); navElement.classList.add("navbar-light", "bg-light");
}); });
} }
if (elements && elements.navDropdownMenus) { if (elements && elements.navDropdownMenus) {
elements.navDropdownMenus.forEach((menu) => menu.classList.remove("dropdown-menu-dark")); elements.navDropdownMenus.forEach(menu => menu.classList.remove("dropdown-menu-dark"));
} }
if (elements && elements.navIcons) { if (elements && elements.navIcons) {
elements.navIcons.forEach((icon) => (icon.style.filter = "none")); elements.navIcons.forEach(icon => (icon.style.filter = "none"));
} }
var tables = document.querySelectorAll(".table-dark"); var tables = document.querySelectorAll(".table-dark");
tables.forEach((table) => { tables.forEach(table => {
table.classList.remove("table-dark"); table.classList.remove("table-dark");
}); });
if (jumbotron) { if (jumbotron) {
@@ -108,43 +108,43 @@ function setMode(mode) {
} }
function toggleDarkMode() { function toggleDarkMode() {
var currentTime = Date.now(); var currentTime = Date.now()
if (currentTime - lastToggleTime < 1000) { if (currentTime - lastToggleTime < 1000) {
toggleCount++; toggleCount++
} else { } else {
toggleCount = 1; toggleCount = 1
} }
lastToggleTime = currentTime; lastToggleTime = currentTime
if (toggleCount >= 18) { if (toggleCount >= 18) {
localStorage.setItem("dark-mode", "rainbow"); localStorage.setItem("dark-mode", "rainbow")
setMode("rainbow"); setMode("rainbow")
} else if (localStorage.getItem("dark-mode") == "on") { } else if (localStorage.getItem("dark-mode") == "on") {
localStorage.setItem("dark-mode", "off"); localStorage.setItem("dark-mode", "off")
setMode("off"); setMode("off")
} else { } else {
localStorage.setItem("dark-mode", "on"); localStorage.setItem("dark-mode", "on")
setMode("on"); setMode("on")
} }
} }
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
getElements(); getElements()
var currentMode = localStorage.getItem("dark-mode"); var currentMode = localStorage.getItem("dark-mode")
if (currentMode === "on" || currentMode === "off" || currentMode === "rainbow") { if (currentMode === "on" || currentMode === "off" || currentMode === "rainbow") {
setMode(currentMode); setMode(currentMode)
} else if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { } else if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
setMode("on"); setMode("on")
} else { } else {
setMode("off"); setMode("off")
} }
var darkModeToggle = document.getElementById("dark-mode-toggle"); var darkModeToggle = document.getElementById("dark-mode-toggle");
if (darkModeToggle !== null) { if (darkModeToggle !== null) {
darkModeToggle.addEventListener("click", function (event) { darkModeToggle.addEventListener("click", function (event) {
event.preventDefault(); event.preventDefault();
toggleDarkMode(); toggleDarkMode();
}); });
} }
}); })

View File

@@ -1,265 +1,242 @@
function showErrorBanner(message, stackTrace) { function showErrorBanner(message, stackTrace) {
const errorContainer = document.getElementById("errorContainer"); const errorContainer = document.getElementById("errorContainer");
errorContainer.style.display = "block"; // Display the banner errorContainer.style.display = "block"; // Display the banner
document.querySelector("#errorContainer .alert-heading").textContent = "Error"; document.querySelector("#errorContainer .alert-heading").textContent = "Error";
document.querySelector("#errorContainer p").textContent = message; document.querySelector("#errorContainer p").textContent = message;
document.querySelector("#traceContent").textContent = stackTrace; document.querySelector("#traceContent").textContent = stackTrace;
} }
let firstErrorOccurred = false; let firstErrorOccurred = false;
$(document).ready(function () { $(document).ready(function() {
$("form").submit(async function (event) { $('form').submit(async function(event) {
event.preventDefault(); event.preventDefault();
firstErrorOccurred = false; firstErrorOccurred = false;
const url = this.action; const url = this.action;
const files = $("#fileInput-input")[0].files; const files = $('#fileInput-input')[0].files;
const formData = new FormData(this); const formData = new FormData(this);
// Remove empty file entries // Remove empty file entries
for (let [key, value] of formData.entries()) { for (let [key, value] of formData.entries()) {
if (value instanceof File && !value.name) { if (value instanceof File && !value.name) {
formData.delete(key); formData.delete(key);
} }
}
const override = $("#override").val() || "";
const originalButtonText = $("#submitBtn").text();
$("#submitBtn").text("Processing...");
console.log(override);
// Set a timeout to show the game button if operation takes more than 5 seconds
const timeoutId = setTimeout(() => {
var boredWaiting = localStorage.getItem("boredWaiting") || "disabled";
const showGameBtn = document.getElementById("show-game-btn");
if (boredWaiting === "enabled" && showGameBtn) {
showGameBtn.style.display = "block";
}
}, 5000);
try {
if (remoteCall === true) {
if (override === "multi" || (!multiple && files.length > 1 && override !== "single")) {
await submitMultiPdfForm(url, files);
} else {
await handleSingleDownload(url, formData);
} }
} const override = $('#override').val() || '';
clearTimeout(timeoutId); const originalButtonText = $('#submitBtn').text();
$("#submitBtn").text(originalButtonText); $('#submitBtn').text('Processing...');
console.log(override);
// After process finishes, check for boredWaiting and gameDialog open status try {
const boredWaiting = localStorage.getItem("boredWaiting") || "disabled"; if(remoteCall === true) {
const gameDialog = document.getElementById('game-container-wrapper'); if (override === 'multi' || (!multiple && files.length > 1) && override !== 'single' ) {
if (boredWaiting === "enabled" && gameDialog && gameDialog.open) { await submitMultiPdfForm(url, files);
// Display a green banner at the bottom of the screen saying "Download complete" } else {
let downloadCompleteText = "Download Complete"; await handleSingleDownload(url, formData);
if(window.downloadCompleteText){ }
downloadCompleteText = window.downloadCompleteText; }
$('#submitBtn').text(originalButtonText);
} catch (error) {
handleDownloadError(error);
$('#submitBtn').text(originalButtonText);
console.error(error);
} }
$("body").append('<div id="download-complete-banner" style="position:fixed;bottom:0;left:0;width:100%;background-color:green;color:white;text-align:center;padding:10px;font-size:16px;z-index:1000;">'+ downloadCompleteText + '</div>'); });
setTimeout(function() {
$("#download-complete-banner").fadeOut("slow", function() {
$(this).remove(); // Remove the banner after fading out
});
}, 5000); // Banner will fade out after 5 seconds
}
} catch (error) {
clearTimeout(timeoutId);
handleDownloadError(error);
$("#submitBtn").text(originalButtonText);
console.error(error);
}
});
}); });
async function handleSingleDownload(url, formData, isMulti = false, isZip = false) {
try {
const response = await fetch(url, { method: "POST", body: formData });
const contentType = response.headers.get("content-type");
if (!response.ok) {
if (contentType && contentType.includes("application/json")) {
return handleJsonResponse(response);
console.error("Throwing error banner, response was not okay");
}
throw new Error(`HTTP error! status: ${response.status}`);
}
const contentDisposition = response.headers.get("Content-Disposition"); async function handleSingleDownload(url, formData, isMulti = false , isZip = false) {
let filename = getFilenameFromContentDisposition(contentDisposition); try {
const response = await fetch(url, { method: 'POST', body: formData });
const contentType = response.headers.get('content-type');
const blob = await response.blob(); if (!response.ok) {
if (contentType.includes("application/pdf") || contentType.includes("image/")) { if (contentType && contentType.includes('application/json')) {
return handleResponse(blob, filename, !isMulti, isZip); return handleJsonResponse(response);
} else { console.error('Throwing error banner, response was not okay');
return handleResponse(blob, filename, false, isZip); }
} throw new Error(`HTTP error! status: ${response.status}`);
} catch (error) { }
console.error("Error in handleSingleDownload:", error);
throw error; // Re-throw the error if you want it to be handled higher up. const contentDisposition = response.headers.get('Content-Disposition');
} let filename = getFilenameFromContentDisposition(contentDisposition);
const blob = await response.blob();
if (contentType.includes('application/pdf') || contentType.includes('image/')) {
return handleResponse(blob, filename, !isMulti, isZip);
} else {
return handleResponse(blob, filename, false, isZip);
}
} catch (error) {
console.error('Error in handleSingleDownload:', error);
throw error; // Re-throw the error if you want it to be handled higher up.
}
} }
function getFilenameFromContentDisposition(contentDisposition) { function getFilenameFromContentDisposition(contentDisposition) {
let filename; let filename;
if (contentDisposition && contentDisposition.indexOf("attachment") !== -1) { if (contentDisposition && contentDisposition.indexOf('attachment') !== -1) {
filename = decodeURIComponent(contentDisposition.split("filename=")[1].replace(/"/g, "")).trim(); filename = decodeURIComponent(contentDisposition.split('filename=')[1].replace(/"/g, '')).trim();
} else { } else {
// If the Content-Disposition header is not present or does not contain the filename, use a default filename // If the Content-Disposition header is not present or does not contain the filename, use a default filename
filename = "download"; filename = 'download';
} }
return filename; return filename;
} }
async function handleJsonResponse(response) { async function handleJsonResponse(response) {
const json = await response.json(); const json = await response.json();
const errorMessage = JSON.stringify(json, null, 2); const errorMessage = JSON.stringify(json, null, 2);
if ( if (errorMessage.toLowerCase().includes('the password is incorrect') || errorMessage.toLowerCase().includes('Password is not provided') || errorMessage.toLowerCase().includes('PDF contains an encryption dictionary')) {
errorMessage.toLowerCase().includes("the password is incorrect") || if (!firstErrorOccurred) {
errorMessage.toLowerCase().includes("Password is not provided") || firstErrorOccurred = true;
errorMessage.toLowerCase().includes("PDF contains an encryption dictionary") alert(pdfPasswordPrompt);
) { }
if (!firstErrorOccurred) { } else {
firstErrorOccurred = true; showErrorBanner(json.error + ':' + json.message, json.trace);
alert(pdfPasswordPrompt); }
}
} else {
showErrorBanner(json.error + ":" + json.message, json.trace);
}
} }
async function handleResponse(blob, filename, considerViewOptions = false, isZip = false) { async function handleResponse(blob, filename, considerViewOptions = false, isZip = false) {
if (!blob) return; if (!blob) return;
const downloadOption = localStorage.getItem("downloadOption"); const downloadOption = localStorage.getItem('downloadOption');
if (considerViewOptions) { if (considerViewOptions) {
if (downloadOption === "sameWindow") { if (downloadOption === 'sameWindow') {
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
window.location.href = url; window.location.href = url;
return; return;
} else if (downloadOption === "newWindow") { } else if (downloadOption === 'newWindow') {
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
window.open(url, "_blank"); window.open(url, '_blank');
return; return;
} }
} }
if (!isZip) { if(!isZip){
downloadFile(blob, filename); downloadFile(blob, filename);
} }
return { filename, blob }; return { filename, blob };
} }
function handleDownloadError(error) { function handleDownloadError(error) {
const errorMessage = error.message; const errorMessage = error.message;
showErrorBanner(errorMessage); showErrorBanner(errorMessage);
} }
let urls = []; // An array to hold all the URLs let urls = []; // An array to hold all the URLs
function downloadFile(blob, filename) { function downloadFile(blob, filename) {
if (!(blob instanceof Blob)) { if (!(blob instanceof Blob)) {
console.error("Invalid blob passed to downloadFile function"); console.error('Invalid blob passed to downloadFile function');
return; return;
} }
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement("a"); const a = document.createElement('a');
a.href = url; a.href = url;
a.download = filename; a.download = filename;
a.click(); a.click();
urls.push(url); // Store the URL so it doesn't get garbage collected too soon urls.push(url); // Store the URL so it doesn't get garbage collected too soon
return { filename, blob }; return { filename, blob };
} }
async function submitMultiPdfForm(url, files) { async function submitMultiPdfForm(url, files) {
const zipThreshold = parseInt(localStorage.getItem("zipThreshold"), 10) || 4; const zipThreshold = parseInt(localStorage.getItem('zipThreshold'), 10) || 4;
const zipFiles = files.length > zipThreshold; const zipFiles = files.length > zipThreshold;
let jszip = null; let jszip = null;
// Show the progress bar // Show the progress bar
$("#progressBarContainer").show(); $('#progressBarContainer').show();
// Initialize the progress bar // Initialize the progress bar
let progressBar = $("#progressBar"); let progressBar = $('#progressBar');
progressBar.css("width", "0%"); progressBar.css('width', '0%');
progressBar.attr("aria-valuenow", 0); progressBar.attr('aria-valuenow', 0);
progressBar.attr("aria-valuemax", files.length); progressBar.attr('aria-valuemax', files.length);
if (zipFiles) { if (zipFiles) {
jszip = new JSZip(); jszip = new JSZip();
} }
// Get the form with the method attribute set to POST
let postForm = document.querySelector('form[method="POST"]');
// Get existing form data // Get the form with the method attribute set to POST
let formData; let postForm = document.querySelector('form[method="POST"]');
if (postForm) {
formData = new FormData($(postForm)[0]); // Convert the form to a jQuery object and get the raw DOM element
} else {
console.log("No form with POST method found.");
}
//Remove file to reuse parameters for other runs
formData.delete("fileInput");
// Remove empty file entries
for (let [key, value] of formData.entries()) {
if (value instanceof File && !value.name) {
formData.delete(key);
}
}
const CONCURRENCY_LIMIT = 8;
const chunks = [];
for (let i = 0; i < Array.from(files).length; i += CONCURRENCY_LIMIT) {
chunks.push(Array.from(files).slice(i, i + CONCURRENCY_LIMIT));
}
for (const chunk of chunks) { // Get existing form data
const promises = chunk.map(async (file) => { let formData;
let fileFormData = new FormData(); if (postForm) {
fileFormData.append("fileInput", file); formData = new FormData($(postForm)[0]); // Convert the form to a jQuery object and get the raw DOM element
console.log(fileFormData); } else {
// Add other form data console.log("No form with POST method found.");
for (let pair of formData.entries()) { }
fileFormData.append(pair[0], pair[1]); //Remove file to reuse parameters for other runs
console.log(pair[0] + ", " + pair[1]); formData.delete('fileInput');
} // Remove empty file entries
for (let [key, value] of formData.entries()) {
try { if (value instanceof File && !value.name) {
const downloadDetails = await handleSingleDownload(url, fileFormData, true, zipFiles); formData.delete(key);
console.log(downloadDetails);
if (zipFiles) {
jszip.file(downloadDetails.filename, downloadDetails.blob);
} else {
//downloadFile(downloadDetails.blob, downloadDetails.filename);
} }
updateProgressBar(progressBar, Array.from(files).length);
} catch (error) {
handleDownloadError(error);
console.error(error);
}
});
await Promise.all(promises);
}
if (zipFiles) {
try {
const content = await jszip.generateAsync({ type: "blob" });
downloadFile(content, "files.zip");
} catch (error) {
console.error("Error generating ZIP file: " + error);
} }
} const CONCURRENCY_LIMIT = 8;
progressBar.css("width", "100%"); const chunks = [];
progressBar.attr("aria-valuenow", Array.from(files).length); for (let i = 0; i < Array.from(files).length; i += CONCURRENCY_LIMIT) {
chunks.push(Array.from(files).slice(i, i + CONCURRENCY_LIMIT));
}
for (const chunk of chunks) {
const promises = chunk.map(async file => {
let fileFormData = new FormData();
fileFormData.append('fileInput', file);
console.log(fileFormData);
// Add other form data
for (let pair of formData.entries()) {
fileFormData.append(pair[0], pair[1]);
console.log(pair[0]+ ', ' + pair[1]);
}
try {
const downloadDetails = await handleSingleDownload(url, fileFormData, true, zipFiles);
console.log(downloadDetails);
if (zipFiles) {
jszip.file(downloadDetails.filename, downloadDetails.blob);
} else {
//downloadFile(downloadDetails.blob, downloadDetails.filename);
}
updateProgressBar(progressBar, Array.from(files).length);
} catch (error) {
handleDownloadError(error);
console.error(error);
}
});
await Promise.all(promises);
}
if (zipFiles) {
try {
const content = await jszip.generateAsync({ type: "blob" });
downloadFile(content, "files.zip");
} catch (error) {
console.error('Error generating ZIP file: ' + error);
}
}
progressBar.css('width', '100%');
progressBar.attr('aria-valuenow', Array.from(files).length);
} }
function updateProgressBar(progressBar, files) { function updateProgressBar(progressBar, files) {
let progress = (progressBar.attr("aria-valuenow") / files.length) * 100 + 100 / files.length; let progress = ((progressBar.attr('aria-valuenow') / files.length) * 100) + (100 / files.length);
progressBar.css("width", progress + "%"); progressBar.css('width', progress + '%');
progressBar.attr("aria-valuenow", parseInt(progressBar.attr("aria-valuenow")) + 1); progressBar.attr('aria-valuenow', parseInt(progressBar.attr('aria-valuenow')) + 1);
} }
window.addEventListener("unload", () => { window.addEventListener('unload', () => {
for (const url of urls) { for (const url of urls) {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
}); });

View File

@@ -1,287 +1,282 @@
const DraggableUtils = { const DraggableUtils = {
boxDragContainer: document.getElementById("box-drag-container"),
pdfCanvas: document.getElementById("pdf-canvas"),
nextId: 0,
pdfDoc: null,
pageIndex: 0,
documentsMap: new Map(),
init() { boxDragContainer: document.getElementById('box-drag-container'),
interact(".draggable-canvas") pdfCanvas: document.getElementById('pdf-canvas'),
.draggable({ nextId: 0,
listeners: { pdfDoc: null,
move: (event) => { pageIndex: 0,
const target = event.target; documentsMap: new Map(),
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)`; init() {
target.setAttribute("data-bs-x", x); interact('.draggable-canvas')
target.setAttribute("data-bs-y", y); .draggable({
listeners: {
move: (event) => {
const target = event.target;
const x = (parseFloat(target.getAttribute('data-bs-x')) || 0) + event.dx;
const y = (parseFloat(target.getAttribute('data-bs-y')) || 0) + event.dy;
this.onInteraction(target); target.style.transform = `translate(${x}px, ${y}px)`;
}, target.setAttribute('data-bs-x', x);
}, target.setAttribute('data-bs-y', y);
})
.resizable({ this.onInteraction(target);
edges: { left: true, right: true, bottom: true, top: true }, },
listeners: { },
move: (event) => { })
var target = event.target; .resizable({
var x = parseFloat(target.getAttribute("data-bs-x")) || 0; edges: { left: true, right: true, bottom: true, top: true },
var y = parseFloat(target.getAttribute("data-bs-y")) || 0; listeners: {
move: (event) => {
var target = event.target
var x = (parseFloat(target.getAttribute('data-bs-x')) || 0)
var y = (parseFloat(target.getAttribute('data-bs-y')) || 0)
// check if control key is pressed // check if control key is pressed
if (event.ctrlKey) { if (event.ctrlKey) {
const aspectRatio = target.offsetWidth / target.offsetHeight; const aspectRatio = target.offsetWidth / target.offsetHeight;
// preserve aspect ratio // preserve aspect ratio
let width = event.rect.width; let width = event.rect.width;
let height = event.rect.height; let height = event.rect.height;
if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) { if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) {
height = width / aspectRatio; height = width / aspectRatio;
} else { } else {
width = height * aspectRatio; width = height * aspectRatio;
} }
event.rect.width = width; event.rect.width = width;
event.rect.height = height; event.rect.height = height;
} }
target.style.width = event.rect.width + "px"; target.style.width = event.rect.width + 'px'
target.style.height = event.rect.height + "px"; target.style.height = event.rect.height + 'px'
// translate when resizing from top or left edges // translate when resizing from top or left edges
x += event.deltaRect.left; x += event.deltaRect.left
y += event.deltaRect.top; y += event.deltaRect.top
target.style.transform = "translate(" + x + "px," + y + "px)"; target.style.transform = 'translate(' + x + 'px,' + y + 'px)'
target.setAttribute("data-bs-x", x); target.setAttribute('data-bs-x', x)
target.setAttribute("data-bs-y", y); target.setAttribute('data-bs-y', y)
target.textContent = Math.round(event.rect.width) + "\u00D7" + Math.round(event.rect.height); target.textContent = Math.round(event.rect.width) + '\u00D7' + Math.round(event.rect.height)
this.onInteraction(target); this.onInteraction(target);
},
}, },
},
modifiers: [ modifiers: [
interact.modifiers.restrictSize({ interact.modifiers.restrictSize({
min: { width: 5, height: 5 }, min: { width: 5, height: 5 },
}), }),
], ],
inertia: true, inertia: true,
});
},
onInteraction(target) {
this.boxDragContainer.appendChild(target);
},
createDraggableCanvas() {
const createdCanvas = document.createElement("canvas");
createdCanvas.id = `draggable-canvas-${this.nextId++}`;
createdCanvas.classList.add("draggable-canvas");
const x = 0;
const y = 20;
createdCanvas.style.transform = `translate(${x}px, ${y}px)`;
createdCanvas.setAttribute("data-bs-x", x);
createdCanvas.setAttribute("data-bs-y", y);
createdCanvas.onclick = (e) => this.onInteraction(e.target);
this.boxDragContainer.appendChild(createdCanvas);
return createdCanvas;
},
createDraggableCanvasFromUrl(dataUrl) {
return new Promise((resolve) => {
var myImage = new Image();
myImage.src = dataUrl;
myImage.onload = () => {
var createdCanvas = this.createDraggableCanvas();
createdCanvas.width = myImage.width;
createdCanvas.height = myImage.height;
const imgAspect = myImage.width / myImage.height;
const pdfAspect = this.boxDragContainer.offsetWidth / this.boxDragContainer.offsetHeight;
var scaleMultiplier;
if (imgAspect > pdfAspect) {
scaleMultiplier = this.boxDragContainer.offsetWidth / myImage.width;
} else {
scaleMultiplier = this.boxDragContainer.offsetHeight / myImage.height;
}
var newWidth = createdCanvas.width;
var newHeight = createdCanvas.height;
if (scaleMultiplier < 1) {
newWidth = newWidth * scaleMultiplier;
newHeight = newHeight * scaleMultiplier;
}
createdCanvas.style.width = newWidth + "px";
createdCanvas.style.height = newHeight + "px";
var myContext = createdCanvas.getContext("2d");
myContext.drawImage(myImage, 0, 0);
resolve(createdCanvas);
};
});
},
deleteAllDraggableCanvases() {
this.boxDragContainer.querySelectorAll(".draggable-canvas").forEach((el) => el.remove());
},
deleteDraggableCanvas(element) {
if (element) {
element.remove();
}
},
getLastInteracted() {
return this.boxDragContainer.querySelector(".draggable-canvas:last-of-type");
},
storePageContents() {
var pagesMap = this.documentsMap.get(this.pdfDoc);
if (!pagesMap) {
pagesMap = {};
}
const elements = [...this.boxDragContainer.querySelectorAll(".draggable-canvas")];
const draggablesData = elements.map((el) => {
return {
element: el,
offsetWidth: el.offsetWidth,
offsetHeight: el.offsetHeight,
};
});
elements.forEach((el) => this.boxDragContainer.removeChild(el));
pagesMap[this.pageIndex] = draggablesData;
pagesMap[this.pageIndex + "-offsetWidth"] = this.pdfCanvas.offsetWidth;
pagesMap[this.pageIndex + "-offsetHeight"] = this.pdfCanvas.offsetHeight;
this.documentsMap.set(this.pdfDoc, pagesMap);
},
loadPageContents() {
var pagesMap = this.documentsMap.get(this.pdfDoc);
this.deleteAllDraggableCanvases();
if (!pagesMap) {
return;
}
const draggablesData = pagesMap[this.pageIndex];
if (draggablesData) {
draggablesData.forEach((draggableData) => this.boxDragContainer.appendChild(draggableData.element));
}
this.documentsMap.set(this.pdfDoc, pagesMap);
},
async renderPage(pdfDocument, pageIdx) {
this.pdfDoc = pdfDocument ? pdfDocument : this.pdfDoc;
this.pageIndex = pageIdx;
// persist
const page = await this.pdfDoc.getPage(this.pageIndex + 1);
// set the canvas size to the size of the page
if (page.rotate == 90 || page.rotate == 270) {
this.pdfCanvas.width = page.view[3];
this.pdfCanvas.height = page.view[2];
} else {
this.pdfCanvas.width = page.view[2];
this.pdfCanvas.height = page.view[3];
}
// render the page onto the canvas
var renderContext = {
canvasContext: this.pdfCanvas.getContext("2d"),
viewport: page.getViewport({ scale: 1 }),
};
await page.render(renderContext).promise;
//return pdfCanvas.toDataURL();
},
async incrementPage() {
if (this.pageIndex < this.pdfDoc.numPages - 1) {
this.storePageContents();
await this.renderPage(this.pdfDoc, this.pageIndex + 1);
this.loadPageContents();
}
},
async decrementPage() {
if (this.pageIndex > 0) {
this.storePageContents();
await this.renderPage(this.pdfDoc, this.pageIndex - 1);
this.loadPageContents();
}
},
parseTransform(element) {},
async getOverlayedPdfDocument() {
const pdfBytes = await this.pdfDoc.getData();
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, {
ignoreEncryption: true,
});
this.storePageContents();
const pagesMap = this.documentsMap.get(this.pdfDoc);
for (let pageIdx in pagesMap) {
if (pageIdx.includes("offset")) {
continue;
}
console.log(typeof pageIdx);
const page = pdfDocModified.getPage(parseInt(pageIdx));
const draggablesData = pagesMap[pageIdx];
const offsetWidth = pagesMap[pageIdx + "-offsetWidth"];
const offsetHeight = pagesMap[pageIdx + "-offsetHeight"];
for (const draggableData of draggablesData) {
// embed the draggable canvas
const draggableElement = draggableData.element;
const response = await fetch(draggableElement.toDataURL());
const draggableImgBytes = await response.arrayBuffer();
const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes);
// calculate the position in the pdf document
const tansform = draggableElement.style.transform.replace(/[^.,-\d]/g, "");
const transformComponents = tansform.split(",");
const draggablePositionPixels = {
x: parseFloat(transformComponents[0]),
y: parseFloat(transformComponents[1]),
width: draggableData.offsetWidth,
height: draggableData.offsetHeight,
};
const draggablePositionRelative = {
x: draggablePositionPixels.x / offsetWidth,
y: draggablePositionPixels.y / offsetHeight,
width: draggablePositionPixels.width / offsetWidth,
height: draggablePositionPixels.height / offsetHeight,
};
const draggablePositionPdf = {
x: draggablePositionRelative.x * page.getWidth(),
y: draggablePositionRelative.y * page.getHeight(),
width: draggablePositionRelative.width * page.getWidth(),
height: draggablePositionRelative.height * page.getHeight(),
};
// draw the image
page.drawImage(pdfImageObject, {
x: draggablePositionPdf.x,
y: page.getHeight() - draggablePositionPdf.y - draggablePositionPdf.height,
width: draggablePositionPdf.width,
height: draggablePositionPdf.height,
}); });
} },
} onInteraction(target) {
this.boxDragContainer.appendChild(target);
},
this.loadPageContents(); createDraggableCanvas() {
return pdfDocModified; const createdCanvas = document.createElement('canvas');
}, createdCanvas.id = `draggable-canvas-${this.nextId++}`;
}; createdCanvas.classList.add("draggable-canvas");
const x = 0;
const y = 20;
createdCanvas.style.transform = `translate(${x}px, ${y}px)`;
createdCanvas.setAttribute('data-bs-x', x);
createdCanvas.setAttribute('data-bs-y', y);
createdCanvas.onclick = e => this.onInteraction(e.target);
this.boxDragContainer.appendChild(createdCanvas);
return createdCanvas;
},
createDraggableCanvasFromUrl(dataUrl) {
return new Promise((resolve) => {
var myImage = new Image();
myImage.src = dataUrl;
myImage.onload = () => {
var createdCanvas = this.createDraggableCanvas();
createdCanvas.width = myImage.width;
createdCanvas.height = myImage.height;
const imgAspect = myImage.width / myImage.height;
const pdfAspect = this.boxDragContainer.offsetWidth / this.boxDragContainer.offsetHeight;
var scaleMultiplier;
if (imgAspect > pdfAspect) {
scaleMultiplier = this.boxDragContainer.offsetWidth / myImage.width;
} else {
scaleMultiplier = this.boxDragContainer.offsetHeight / myImage.height;
}
var newWidth = createdCanvas.width;
var newHeight = createdCanvas.height;
if (scaleMultiplier < 1) {
newWidth = newWidth * scaleMultiplier;
newHeight = newHeight * scaleMultiplier;
}
createdCanvas.style.width = newWidth+"px";
createdCanvas.style.height = newHeight+"px";
var myContext = createdCanvas.getContext("2d");
myContext.drawImage(myImage,0,0);
resolve(createdCanvas);
}
})
},
deleteAllDraggableCanvases() {
this.boxDragContainer.querySelectorAll(".draggable-canvas").forEach(el => el.remove());
},
deleteDraggableCanvas(element) {
if (element) {
element.remove();
}
},
getLastInteracted() {
return this.boxDragContainer.querySelector(".draggable-canvas:last-of-type");
},
storePageContents() {
var pagesMap = this.documentsMap.get(this.pdfDoc);
if (!pagesMap) {
pagesMap = {};
}
const elements = [...this.boxDragContainer.querySelectorAll(".draggable-canvas")];
const draggablesData = elements.map(el => {return{element:el, offsetWidth:el.offsetWidth, offsetHeight:el.offsetHeight}});
elements.forEach(el => this.boxDragContainer.removeChild(el));
pagesMap[this.pageIndex] = draggablesData;
pagesMap[this.pageIndex+"-offsetWidth"] = this.pdfCanvas.offsetWidth;
pagesMap[this.pageIndex+"-offsetHeight"] = this.pdfCanvas.offsetHeight;
this.documentsMap.set(this.pdfDoc, pagesMap);
},
loadPageContents() {
var pagesMap = this.documentsMap.get(this.pdfDoc);
this.deleteAllDraggableCanvases();
if (!pagesMap) {
return;
}
const draggablesData = pagesMap[this.pageIndex];
if (draggablesData) {
draggablesData.forEach(draggableData => this.boxDragContainer.appendChild(draggableData.element));
}
this.documentsMap.set(this.pdfDoc, pagesMap);
},
async renderPage(pdfDocument, pageIdx) {
this.pdfDoc = pdfDocument ? pdfDocument : this.pdfDoc;
this.pageIndex = pageIdx;
// persist
const page = await this.pdfDoc.getPage(this.pageIndex+1);
// set the canvas size to the size of the page
if (page.rotate == 90 || page.rotate == 270) {
this.pdfCanvas.width = page.view[3];
this.pdfCanvas.height = page.view[2];
} else {
this.pdfCanvas.width = page.view[2];
this.pdfCanvas.height = page.view[3];
}
// render the page onto the canvas
var renderContext = {
canvasContext: this.pdfCanvas.getContext("2d"),
viewport: page.getViewport({ scale: 1 })
};
await page.render(renderContext).promise;
//return pdfCanvas.toDataURL();
},
async incrementPage() {
if (this.pageIndex < this.pdfDoc.numPages-1) {
this.storePageContents();
await this.renderPage(this.pdfDoc, this.pageIndex+1);
this.loadPageContents();
}
},
async decrementPage() {
if (this.pageIndex > 0) {
this.storePageContents();
await this.renderPage(this.pdfDoc, this.pageIndex-1);
this.loadPageContents();
}
},
parseTransform(element) {
},
async getOverlayedPdfDocument() {
const pdfBytes = await this.pdfDoc.getData();
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, { ignoreEncryption: true });
this.storePageContents();
const pagesMap = this.documentsMap.get(this.pdfDoc);
for (let pageIdx in pagesMap) {
if (pageIdx.includes("offset")) {
continue;
}
console.log(typeof pageIdx);
const page = pdfDocModified.getPage(parseInt(pageIdx));
const draggablesData = pagesMap[pageIdx];
const offsetWidth = pagesMap[pageIdx+"-offsetWidth"];
const offsetHeight = pagesMap[pageIdx+"-offsetHeight"];
for (const draggableData of draggablesData) {
// embed the draggable canvas
const draggableElement = draggableData.element;
const response = await fetch(draggableElement.toDataURL());
const draggableImgBytes = await response.arrayBuffer();
const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes);
// calculate the position in the pdf document
const tansform = draggableElement.style.transform.replace(/[^.,-\d]/g, '');
const transformComponents = tansform.split(",");
const draggablePositionPixels = {
x: parseFloat(transformComponents[0]),
y: parseFloat(transformComponents[1]),
width: draggableData.offsetWidth,
height: draggableData.offsetHeight,
};
const draggablePositionRelative = {
x: draggablePositionPixels.x / offsetWidth,
y: draggablePositionPixels.y / offsetHeight,
width: draggablePositionPixels.width / offsetWidth,
height: draggablePositionPixels.height / offsetHeight,
}
const draggablePositionPdf = {
x: draggablePositionRelative.x * page.getWidth(),
y: draggablePositionRelative.y * page.getHeight(),
width: draggablePositionRelative.width * page.getWidth(),
height: draggablePositionRelative.height * page.getHeight(),
}
// draw the image
page.drawImage(pdfImageObject, {
x: draggablePositionPdf.x,
y: page.getHeight() - draggablePositionPdf.y - draggablePositionPdf.height,
width: draggablePositionPdf.width,
height: draggablePositionPdf.height,
});
}
}
this.loadPageContents();
return pdfDocModified;
},
}
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
DraggableUtils.init(); DraggableUtils.init();
}); });

View File

@@ -1,50 +1,50 @@
var traceVisible = false; var traceVisible = false;
function toggletrace() { function toggletrace() {
var traceDiv = document.getElementById("trace"); var traceDiv = document.getElementById("trace");
if (!traceVisible) { if (!traceVisible) {
traceDiv.style.maxHeight = "500px"; traceDiv.style.maxHeight = "500px";
traceVisible = true; traceVisible = true;
} else { } else {
traceDiv.style.maxHeight = "0px"; traceDiv.style.maxHeight = "0px";
traceVisible = false; traceVisible = false;
} }
adjustContainerHeight(); adjustContainerHeight();
} }
function copytrace() { function copytrace() {
var flip = false; var flip = false
if (!traceVisible) { if (!traceVisible) {
toggletrace(); toggletrace()
flip = true; flip = true
} }
var traceContent = document.getElementById("traceContent"); var traceContent = document.getElementById("traceContent");
var range = document.createRange(); var range = document.createRange();
range.selectNode(traceContent); range.selectNode(traceContent);
window.getSelection().removeAllRanges(); window.getSelection().removeAllRanges();
window.getSelection().addRange(range); window.getSelection().addRange(range);
document.execCommand("copy"); document.execCommand("copy");
window.getSelection().removeAllRanges(); window.getSelection().removeAllRanges();
if (flip) { if (flip) {
toggletrace(); toggletrace()
} }
} }
function dismissError() { function dismissError() {
var errorContainer = document.getElementById("errorContainer"); var errorContainer = document.getElementById("errorContainer");
errorContainer.style.display = "none"; errorContainer.style.display = "none";
errorContainer.style.height = "0"; errorContainer.style.height = "0";
} }
function adjustContainerHeight() { function adjustContainerHeight() {
var errorContainer = document.getElementById("errorContainer"); var errorContainer = document.getElementById("errorContainer");
var traceDiv = document.getElementById("trace"); var traceDiv = document.getElementById("trace");
if (traceVisible) { if (traceVisible) {
errorContainer.style.height = errorContainer.scrollHeight - traceDiv.scrollHeight + traceDiv.offsetHeight + "px"; errorContainer.style.height = errorContainer.scrollHeight - traceDiv.scrollHeight + traceDiv.offsetHeight + "px";
} else { } else {
errorContainer.style.height = "auto"; errorContainer.style.height = "auto";
} }
} }
function showHelp() { function showHelp() {
$("#helpModal").modal("show"); $('#helpModal').modal('show');
} }

View File

@@ -1,45 +1,45 @@
function updateFavoritesDropdown() { function updateFavoritesDropdown() {
var dropdown = document.querySelector("#favoritesDropdown"); var dropdown = document.querySelector('#favoritesDropdown');
// Check if dropdown exists // Check if dropdown exists
if (!dropdown) { if (!dropdown) {
console.error('Dropdown element with ID "favoritesDropdown" not found!'); console.error('Dropdown element with ID "favoritesDropdown" not found!');
return; // Exit the function 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}`);
}
} }
} dropdown.innerHTML = ''; // Clear the current favorites
// Show or hide the default item based on whether there are any favorites var hasFavorites = false;
if (!hasFavorites) {
var defaultItem = document.createElement("a"); for (var i = 0; i < localStorage.length; i++) {
defaultItem.className = "dropdown-item"; var key = localStorage.key(i);
defaultItem.textContent = noFavourites; if (localStorage.getItem(key) === 'favorite') {
dropdown.appendChild(defaultItem); // 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}`);
}
}
}
// 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);
}
} }
// Ensure that the DOM content has been fully loaded before calling the function // Ensure that the DOM content has been fully loaded before calling the function
document.addEventListener("DOMContentLoaded", function () { document.addEventListener('DOMContentLoaded', function() {
console.log("DOMContentLoaded event fired"); console.log('DOMContentLoaded event fired');
updateFavoritesDropdown(); updateFavoritesDropdown();
}); });

View File

@@ -1,107 +1,104 @@
document.addEventListener("DOMContentLoaded", function () { document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll(".custom-file-chooser").forEach(setupFileInput); document.querySelectorAll('.custom-file-chooser').forEach(setupFileInput);
}); });
function setupFileInput(chooser) { function setupFileInput(chooser) {
const elementId = chooser.getAttribute("data-bs-element-id"); const elementId = chooser.getAttribute('data-bs-element-id');
const filesSelected = chooser.getAttribute("data-bs-files-selected"); const filesSelected = chooser.getAttribute('data-bs-files-selected');
const pdfPrompt = chooser.getAttribute("data-bs-pdf-prompt"); const pdfPrompt = chooser.getAttribute('data-bs-pdf-prompt');
let allFiles = []; let allFiles = [];
let overlay; let overlay;
let dragCounter = 0; let dragCounter = 0;
const dragenterListener = function () { const dragenterListener = function() {
dragCounter++; dragCounter++;
if (!overlay) { if (!overlay) {
overlay = document.createElement("div"); overlay = document.createElement('div');
overlay.style.position = "fixed"; overlay.style.position = 'fixed';
overlay.style.top = 0; overlay.style.top = 0;
overlay.style.left = 0; overlay.style.left = 0;
overlay.style.width = "100%"; overlay.style.width = '100%';
overlay.style.height = "100%"; overlay.style.height = '100%';
overlay.style.background = "rgba(0, 0, 0, 0.5)"; overlay.style.background = 'rgba(0, 0, 0, 0.5)';
overlay.style.color = "#fff"; overlay.style.color = '#fff';
overlay.style.zIndex = "1000"; overlay.style.zIndex = '1000';
overlay.style.display = "flex"; overlay.style.display = 'flex';
overlay.style.alignItems = "center"; overlay.style.alignItems = 'center';
overlay.style.justifyContent = "center"; overlay.style.justifyContent = 'center';
overlay.style.pointerEvents = "none"; overlay.style.pointerEvents = 'none';
overlay.innerHTML = "<p>Drop files anywhere to upload</p>"; overlay.innerHTML = '<p>Drop files anywhere to upload</p>';
document.getElementById("content-wrap").appendChild(overlay); document.getElementById('content-wrap').appendChild(overlay);
} }
}; };
const dragleaveListener = function () { const dragleaveListener = function() {
dragCounter--; dragCounter--;
if (dragCounter === 0) { if (dragCounter === 0) {
if (overlay) { if (overlay) {
overlay.remove(); overlay.remove();
overlay = null; overlay = null;
} }
} }
}; };
const dropListener = function (e) { const dropListener = function(e) {
e.preventDefault(); e.preventDefault();
const dt = e.dataTransfer; const dt = e.dataTransfer;
const files = dt.files; const files = dt.files;
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
allFiles.push(files[i]); allFiles.push(files[i]);
} }
const dataTransfer = new DataTransfer(); const dataTransfer = new DataTransfer();
allFiles.forEach((file) => dataTransfer.items.add(file)); allFiles.forEach(file => dataTransfer.items.add(file));
const fileInput = document.getElementById(elementId); const fileInput = document.getElementById(elementId);
fileInput.files = dataTransfer.files; fileInput.files = dataTransfer.files;
if (overlay) { if (overlay) {
overlay.remove(); overlay.remove();
overlay = null; overlay = null;
} }
dragCounter = 0; dragCounter = 0;
fileInput.dispatchEvent(new Event("change", { bubbles: true })); fileInput.dispatchEvent(new Event('change', { bubbles: true }));
}; };
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
document.body.addEventListener(eventName, preventDefaults, false); document.body.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
document.body.addEventListener("dragenter", dragenterListener);
document.body.addEventListener("dragleave", dragleaveListener);
document.body.addEventListener("drop", dropListener);
$("#" + elementId).on("change", function (e) {
allFiles = Array.from(e.target.files);
handleFileInputChange(this);
});
function handleFileInputChange(inputElement) {
const files = allFiles;
const fileNames = files.map((f) => f.name);
const selectedFilesContainer = $(inputElement).siblings(".selected-files");
selectedFilesContainer.empty();
fileNames.forEach((fileName) => {
selectedFilesContainer.append("<div>" + fileName + "</div>");
}); });
if (fileNames.length === 1) {
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]); function preventDefaults(e) {
} else if (fileNames.length > 1) { e.preventDefault();
$(inputElement) e.stopPropagation();
.siblings(".custom-file-label") }
.addClass("selected")
.html(fileNames.length + " " + filesSelected); document.body.addEventListener('dragenter', dragenterListener);
} else { document.body.addEventListener('dragleave', dragleaveListener);
$(inputElement).siblings(".custom-file-label").addClass("selected").html(pdfPrompt); document.body.addEventListener('drop', dropListener);
$("#" + elementId).on("change", function(e) {
allFiles = Array.from(e.target.files);
handleFileInputChange(this);
});
function handleFileInputChange(inputElement) {
const files = allFiles;
const fileNames = files.map(f => f.name);
const selectedFilesContainer = $(inputElement).siblings(".selected-files");
selectedFilesContainer.empty();
fileNames.forEach(fileName => {
selectedFilesContainer.append("<div>" + fileName + "</div>");
});
if (fileNames.length === 1) {
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]);
} else if (fileNames.length > 1) {
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames.length + " " + filesSelected);
} else {
$(inputElement).siblings(".custom-file-label").addClass("selected").html(pdfPrompt);
}
} }
}
} }

View File

@@ -1,276 +1,292 @@
function initializeGame() { function initializeGame() {
const gameContainer = document.getElementById("game-container"); const gameContainer = document.getElementById('game-container');
const player = document.getElementById("player"); const player = document.getElementById('player');
let playerSize = gameContainer.clientWidth * 0.0625; // 5% of container width let playerSize = gameContainer.clientWidth * 0.0625; // 5% of container width
player.style.width = playerSize + "px"; player.style.width = playerSize + 'px';
player.style.height = playerSize + "px"; player.style.height = playerSize + 'px';
let playerX = gameContainer.clientWidth / 2 - playerSize / 2; let playerX = gameContainer.clientWidth / 2 - playerSize / 2;
let playerY = gameContainer.clientHeight * 0.1; let playerY = gameContainer.clientHeight * 0.1;
const scoreElement = document.getElementById("score"); const scoreElement = document.getElementById('score');
const levelElement = document.getElementById("level"); const levelElement = document.getElementById('level');
const livesElement = document.getElementById("lives"); const livesElement = document.getElementById('lives');
const highScoreElement = document.getElementById("high-score"); const highScoreElement = document.getElementById('high-score');
let pdfSize = gameContainer.clientWidth * 0.0625; // 5% of container width let pdfSize = gameContainer.clientWidth * 0.0625; // 5% of container width
let projectileWidth = gameContainer.clientWidth * 0.00625; // 0.00625; // 0.5% of container width let projectileWidth = gameContainer.clientWidth * 0.00625;// 0.00625; // 0.5% of container width
let projectileHeight = gameContainer.clientHeight * 0.01667; // 1% of container height let projectileHeight = gameContainer.clientHeight * 0.01667; // 1% of container height
let paused = false; let paused = false;
const fireRate = 200; // Time between shots in milliseconds const fireRate = 200; // Time between shots in milliseconds
let lastProjectileTime = 0; let lastProjectileTime = 0;
let lives = 3; let lives = 3;
let highScore = localStorage.getItem("highScore") ? parseInt(localStorage.getItem("highScore")) : 0;
updateHighScore();
const PLAYER_MOVE_SPEED = 5;
const BASE_PDF_SPEED = 1;
const LEVEL_INCREASE_PDF_SPEED = 0.2;
const BASE_SPAWN_INTERVAL_MS = 1250; // milliseconds before a new enemy spawns
const LEVEL_INCREASE_FACTOR_MS = 25; // milliseconds to decrease the spawn interval per level
const MAX_SPAWN_RATE_REDUCTION_MS = 800; // Max milliseconds from the base spawn interval
let keysPressed = {}; let highScore = localStorage.getItem('highScore') ? parseInt(localStorage.getItem('highScore')) : 0;
const pdfs = []; updateHighScore();
const projectiles = [];
let score = 0;
let level = 1;
let pdfSpeed = BASE_PDF_SPEED;
let gameOver = false;
function handleKeys() {
if (keysPressed["ArrowLeft"]) {
playerX -= PLAYER_MOVE_SPEED;
playerX = Math.max(0, playerX)
}
if (keysPressed["ArrowRight"]) {
playerX += PLAYER_MOVE_SPEED;
playerX = Math.min(gameContainer.clientWidth - playerSize, playerX);
}
if (keysPressed[" "] && !gameOver) {
const currentTime = new Date().getTime();
if (currentTime - lastProjectileTime >= fireRate) {
shootProjectile();
lastProjectileTime = currentTime;
}
}
updatePlayerPosition();
}
function onKeydown(event) { const keysPressed = {};
if (event.key === " ") { const pdfs = [];
event.preventDefault(); const projectiles = [];
} let score = 0;
keysPressed[event.key] = true; let level = 1;
handleKeys(); let pdfSpeed = 0.5;
} let gameOver = false;
function onKeyUp(event) {
keysPressed[event.key] = false;
}
document.removeEventListener("keydown", onKeydown); function handleKeys() {
document.removeEventListener("keyup", onKeyUp); if (keysPressed['ArrowLeft']) {
document.addEventListener("keydown", onKeydown); playerX -= 10;
document.addEventListener("keyup", onKeyUp); }
if (keysPressed['ArrowRight']) {
playerX += 10;
}
if (keysPressed[' '] && !gameOver) {
const currentTime = new Date().getTime();
if (currentTime - lastProjectileTime >= fireRate) {
shootProjectile();
lastProjectileTime = currentTime;
}
}
updatePlayerPosition();
}
function updatePlayerPosition() {
player.style.left = playerX + "px";
player.style.bottom = playerY + "px";
}
function updateLives() {
livesElement.textContent = "Lives: " + lives;
}
function updateHighScore() {
highScoreElement.textContent = "High Score: " + highScore;
}
function shootProjectile() { document.addEventListener('keydown', (event) => {
const projectile = document.createElement("div"); if (event.key === ' ') {
projectile.classList.add("projectile"); event.preventDefault();
projectile.style.backgroundColor = "black";
projectile.style.width = projectileWidth + "px";
projectile.style.height = projectileHeight + "px";
projectile.style.left = playerX + playerSize / 2 - projectileWidth / 2 + "px";
projectile.style.top = gameContainer.clientHeight - playerY - playerSize + "px";
gameContainer.appendChild(projectile);
projectiles.push(projectile);
}
function spawnPdf() {
const pdf = document.createElement("img");
pdf.src = "images/file-earmark-pdf.svg";
pdf.classList.add("pdf");
pdf.style.width = pdfSize + "px";
pdf.style.height = pdfSize + "px";
pdf.style.left = Math.floor(Math.random() * (gameContainer.clientWidth - (2*pdfSize))) + pdfSize + "px";
pdf.style.top = "0px";
gameContainer.appendChild(pdf);
pdfs.push(pdf);
}
function resetEnemies() {
pdfs.forEach((pdf) => gameContainer.removeChild(pdf));
pdfs.length = 0;
}
function updateGame() {
if (gameOver || paused) return;
handleKeys();
for (let pdfIndex = 0; pdfIndex < pdfs.length; pdfIndex++) {
const pdf = pdfs[pdfIndex];
const pdfY = parseFloat(pdf.style.top) + pdfSpeed;
if (pdfY + 50 > gameContainer.clientHeight) {
gameContainer.removeChild(pdf);
pdfs.splice(pdfIndex, 1);
// Deduct 2 points when a PDF gets past the player
score -= 0;
updateScore();
// Decrease lives and check if game over
lives--;
updateLives();
if (lives <= 0) {
endGame();
return;
} }
} else { keysPressed[event.key] = true;
pdf.style.top = pdfY + "px"; handleKeys();
// Check for collision with player
if (collisionDetected(player, pdf)) {
lives--;
updateLives();
resetEnemies();
if (lives <= 0) {
endGame();
return;
}
}
}
}
projectiles.forEach((projectile, projectileIndex) => {
const projectileY = parseInt(projectile.style.top) - 10;
if (projectileY < 0) {
gameContainer.removeChild(projectile);
projectiles.splice(projectileIndex, 1);
} else {
projectile.style.top = projectileY + "px";
}
for (let pdfIndex = 0; pdfIndex < pdfs.length; pdfIndex++) {
const pdf = pdfs[pdfIndex];
if (collisionDetected(projectile, pdf)) {
gameContainer.removeChild(pdf);
gameContainer.removeChild(projectile);
pdfs.splice(pdfIndex, 1);
projectiles.splice(projectileIndex, 1);
score = score + 10;
updateScore();
break;
}
}
}); });
setTimeout(updateGame, 1000 / 60); document.addEventListener('keyup', (event) => {
} keysPressed[event.key] = false;
});
function updatePlayerPosition() {
player.style.left = playerX + 'px';
player.style.bottom = playerY + 'px';
}
function updateLives() {
livesElement.textContent = 'Lives: ' + lives;
}
function updateHighScore() {
highScoreElement.textContent = 'High Score: ' + highScore;
}
function shootProjectile() {
const projectile = document.createElement('div');
projectile.classList.add('projectile');
projectile.style.backgroundColor = 'black';
projectile.style.width = projectileWidth + 'px';
projectile.style.height = projectileHeight + 'px';
projectile.style.left = (playerX + playerSize / 2 - projectileWidth / 2) + 'px';
projectile.style.top = (gameContainer.clientHeight - playerY - playerSize) + 'px';
gameContainer.appendChild(projectile);
projectiles.push(projectile);
}
function spawnPdf() {
const pdf = document.createElement('img');
pdf.src = 'images/file-earmark-pdf.svg';
pdf.classList.add('pdf');
pdf.style.width = pdfSize + 'px';
pdf.style.height = pdfSize + 'px';
pdf.style.left = Math.floor(Math.random() * (gameContainer.clientWidth - pdfSize)) + 'px';
pdf.style.top = '0px';
gameContainer.appendChild(pdf);
pdfs.push(pdf);
}
function resetEnemies() {
pdfs.forEach((pdf) => gameContainer.removeChild(pdf));
pdfs.length = 0;
}
function updateGame() {
if (gameOver || paused) return;
for (let pdfIndex = 0; pdfIndex < pdfs.length; pdfIndex++) {
const pdf = pdfs[pdfIndex];
const pdfY = parseFloat(pdf.style.top) + pdfSpeed;
if (pdfY + 50 > gameContainer.clientHeight) {
gameContainer.removeChild(pdf);
pdfs.splice(pdfIndex, 1);
// Deduct 2 points when a PDF gets past the player
score -= 0;
updateScore();
// Decrease lives and check if game over
lives--;
updateLives();
if (lives <= 0) {
endGame();
return;
}
} else {
pdf.style.top = pdfY + 'px';
// Check for collision with player
if (collisionDetected(player, pdf)) {
lives--;
updateLives();
resetEnemies();
if (lives <= 0) {
endGame();
return;
}
}
}
};
projectiles.forEach((projectile, projectileIndex) => {
const projectileY = parseInt(projectile.style.top) - 10;
if (projectileY < 0) {
gameContainer.removeChild(projectile);
projectiles.splice(projectileIndex, 1);
} else {
projectile.style.top = projectileY + 'px';
}
for (let pdfIndex = 0; pdfIndex < pdfs.length; pdfIndex++) {
const pdf = pdfs[pdfIndex];
if (collisionDetected(projectile, pdf)) {
gameContainer.removeChild(pdf);
gameContainer.removeChild(projectile);
pdfs.splice(pdfIndex, 1);
projectiles.splice(projectileIndex, 1);
score = score + 10;
updateScore();
break;
}
}
});
setTimeout(updateGame, 1000 / 60);
}
function resetGame() {
playerX = gameContainer.clientWidth / 2;
playerY = 50;
updatePlayerPosition();
pdfs.forEach((pdf) => gameContainer.removeChild(pdf));
projectiles.forEach((projectile) => gameContainer.removeChild(projectile));
pdfs.length = 0;
projectiles.length = 0;
score = 0;
level = 1;
lives = 3;
gameOver = false;
updateScore();
updateLives();
levelElement.textContent = 'Level: ' + level;
pdfSpeed = 1;
clearTimeout(spawnPdfTimeout); // Clear the existing spawnPdfTimeout
setTimeout(updateGame, 1000 / 60);
spawnPdfInterval();
}
function updateScore() {
scoreElement.textContent = 'Score: ' + score;
checkLevelUp();
}
function checkLevelUp() {
const newLevel = Math.floor(score / 100) + 1;
if (newLevel > level) {
level = newLevel;
levelElement.textContent = 'Level: ' + level;
pdfSpeed += 0.2;
}
}
function collisionDetected(a, b) {
const rectA = a.getBoundingClientRect();
const rectB = b.getBoundingClientRect();
return (
rectA.left < rectB.right &&
rectA.right > rectB.left &&
rectA.top < rectB.bottom &&
rectA.bottom > rectB.top
);
}
function endGame() {
gameOver = true;
if (score > highScore) {
highScore = score;
localStorage.setItem('highScore', highScore);
updateHighScore();
}
alert('Game Over! Your final score is: ' + score);
document.getElementById('game-container-wrapper').close();
}
let spawnPdfTimeout;
const BASE_SPAWN_INTERVAL_MS = 1250; // milliseconds before a new enemy spawns
const LEVEL_INCREASE_FACTOR_MS = 0; // milliseconds to decrease the spawn interval per level
const MAX_SPAWN_RATE_REDUCTION_MS = 800; // Max milliseconds from the base spawn interval
function spawnPdfInterval() {
console.log("spawnPdfInterval");
if (gameOver || paused) {
console.log("spawnPdfInterval 2");
clearTimeout(spawnPdfTimeout);
return;
}
console.log("spawnPdfInterval 3");
spawnPdf();
let spawnRateReduction = Math.min(level * LEVEL_INCREASE_FACTOR_MS, MAX_SPAWN_RATE_REDUCTION_MS);
let spawnRate = BASE_SPAWN_INTERVAL_MS - spawnRateReduction;
spawnPdfTimeout = setTimeout(spawnPdfInterval, spawnRate);
}
function resetGame() {
playerX = gameContainer.clientWidth / 2;
playerY = 50;
updatePlayerPosition(); updatePlayerPosition();
updateGame();
pdfs.forEach((pdf) => gameContainer.removeChild(pdf));
projectiles.forEach((projectile) => gameContainer.removeChild(projectile));
pdfs.length = 0;
projectiles.length = 0;
score = 0;
level = 1;
lives = 3;
gameOver = false;
updateScore();
updateLives();
levelElement.textContent = "Level: " + level;
pdfSpeed = BASE_PDF_SPEED;
clearTimeout(spawnPdfTimeout); // Clear the existing spawnPdfTimeout
setTimeout(updateGame, 1000 / 60);
spawnPdfInterval(); spawnPdfInterval();
}
function updateScore() {
scoreElement.textContent = "Score: " + score;
checkLevelUp();
}
function checkLevelUp() {
const newLevel = Math.floor(score / 100) + 1;
if (newLevel > level) {
level = newLevel;
levelElement.textContent = "Level: " + level;
pdfSpeed += LEVEL_INCREASE_PDF_SPEED;
}
}
function collisionDetected(a, b) {
const rectA = a.getBoundingClientRect();
const rectB = b.getBoundingClientRect();
return rectA.left < rectB.right && rectA.right > rectB.left && rectA.top < rectB.bottom && rectA.bottom > rectB.top;
}
function endGame() {
gameOver = true;
if (score > highScore) {
highScore = score;
localStorage.setItem("highScore", highScore);
updateHighScore();
}
alert("Game Over! Your final score is: " + score);
document.getElementById("game-container-wrapper").close();
}
let spawnPdfTimeout;
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
paused = true;
} else {
paused = false;
updateGame();
spawnPdfInterval();
}
function spawnPdfInterval() { });
if (gameOver || paused) {
clearTimeout(spawnPdfTimeout);
return;
}
spawnPdf();
let spawnRateReduction = Math.min(level * LEVEL_INCREASE_FACTOR_MS, MAX_SPAWN_RATE_REDUCTION_MS);
let spawnRate = BASE_SPAWN_INTERVAL_MS - spawnRateReduction;
spawnPdfTimeout = setTimeout(spawnPdfInterval, spawnRate);
}
updatePlayerPosition(); window.resetGame = resetGame;
updateGame();
spawnPdfInterval();
document.addEventListener("visibilitychange", function () {
if (document.hidden) {
paused = true;
} else {
paused = false;
updateGame();
spawnPdfInterval();
}
});
window.resetGame = resetGame;
} }
window.initializeGame = initializeGame; window.initializeGame = initializeGame;

View File

@@ -1,52 +1,55 @@
function compareVersions(version1, version2) { function compareVersions(version1, version2) {
const v1 = version1.split("."); const v1 = version1.split('.');
const v2 = version2.split("."); const v2 = version2.split('.');
for (let i = 0; i < v1.length || i < v2.length; i++) { for (let i = 0; i < v1.length || i < v2.length; i++) {
const n1 = parseInt(v1[i]) || 0; const n1 = parseInt(v1[i]) || 0;
const n2 = parseInt(v2[i]) || 0; const n2 = parseInt(v2[i]) || 0;
if (n1 > n2) { if (n1 > n2) {
return 1; return 1;
} else if (n1 < n2) { } else if (n1 < n2) {
return -1; return -1;
} }
} }
return 0; return 0;
} }
async function getLatestReleaseVersion() { async function getLatestReleaseVersion() {
const url = "https://api.github.com/repos/Stirling-Tools/Stirling-PDF/releases/latest"; const url = "https://api.github.com/repos/Stirling-Tools/Stirling-PDF/releases/latest";
try { try {
const response = await fetch(url); const response = await fetch(url);
const data = await response.json(); const data = await response.json();
return data.tag_name ? data.tag_name.substring(1) : ""; return data.tag_name ? data.tag_name.substring(1) : "";
} catch (error) { } catch (error) {
console.error("Failed to fetch latest version:", error); console.error("Failed to fetch latest version:", error);
return ""; // Return an empty string if the fetch fails return ""; // Return an empty string if the fetch fails
} }
} }
async function checkForUpdate() { async function checkForUpdate() {
// Initialize the update button as hidden // Initialize the update button as hidden
var updateBtn = document.getElementById("update-btn"); var updateBtn = document.getElementById("update-btn");
if (updateBtn !== null) { if (updateBtn !== null) {
updateBtn.style.display = "none"; updateBtn.style.display = "none";
} }
const latestVersion = await getLatestReleaseVersion();
console.log("latestVersion=" + latestVersion); const latestVersion = await getLatestReleaseVersion();
console.log("currentVersion=" + currentVersion); console.log("latestVersion=" + latestVersion)
console.log("compareVersions(latestVersion, currentVersion) > 0)=" + compareVersions(latestVersion, currentVersion)); console.log("currentVersion=" + currentVersion)
if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) { console.log("compareVersions(latestVersion, currentVersion) > 0)=" + compareVersions(latestVersion, currentVersion))
document.getElementById("update-btn").style.display = "block"; if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
console.log("visible"); document.getElementById("update-btn").style.display = "block";
} else { console.log("visible")
console.log("hidden"); } else {
} console.log("hidden")
}
} }
document.addEventListener("DOMContentLoaded", (event) => {
checkForUpdate(); document.addEventListener('DOMContentLoaded', (event) => {
checkForUpdate();
}); });

View File

@@ -1,76 +1,78 @@
function filterCards() { function filterCards() {
var input = document.getElementById("searchBar"); var input = document.getElementById('searchBar');
var filter = input.value.toUpperCase(); var filter = input.value.toUpperCase();
var cards = document.querySelectorAll(".feature-card"); var cards = document.querySelectorAll('.feature-card');
for (var i = 0; i < cards.length; i++) { for (var i = 0; i < cards.length; i++) {
var card = cards[i]; var card = cards[i];
var title = card.querySelector("h5.card-title").innerText; var title = card.querySelector('h5.card-title').innerText;
var text = card.querySelector("p.card-text").innerText; var text = card.querySelector('p.card-text').innerText;
// Get the navbar tags associated with the card // Get the navbar tags associated with the card
var navbarItem = document.querySelector(`a.dropdown-item[href="${card.id}"]`); var navbarItem = document.querySelector(`a.dropdown-item[href="${card.id}"]`);
var navbarTags = navbarItem ? navbarItem.getAttribute("data-bs-tags") : ""; var navbarTags = navbarItem ? navbarItem.getAttribute('data-bs-tags') : '';
var content = title + " " + text + " " + navbarTags; var content = title + ' ' + text + ' ' + navbarTags;
if (content.toUpperCase().indexOf(filter) > -1) { if (content.toUpperCase().indexOf(filter) > -1) {
card.style.display = ""; card.style.display = "";
} else { } else {
card.style.display = "none"; card.style.display = "none";
}
} }
}
} }
function toggleFavorite(element) { function toggleFavorite(element) {
var img = element.querySelector("img"); var img = element.querySelector('img');
var card = element.closest(".feature-card"); var card = element.closest('.feature-card');
var cardId = card.id; var cardId = card.id;
if (img.src.endsWith("star.svg")) { if (img.src.endsWith('star.svg')) {
img.src = "images/star-fill.svg"; img.src = 'images/star-fill.svg';
card.classList.add("favorite"); card.classList.add('favorite');
localStorage.setItem(cardId, "favorite"); localStorage.setItem(cardId, 'favorite');
} else { } else {
img.src = "images/star.svg"; img.src = 'images/star.svg';
card.classList.remove("favorite"); card.classList.remove('favorite');
localStorage.removeItem(cardId); localStorage.removeItem(cardId);
} }
reorderCards(); reorderCards();
updateFavoritesDropdown(); updateFavoritesDropdown();
filterCards(); filterCards();
} }
function reorderCards() { function reorderCards() {
var container = document.querySelector(".features-container"); var container = document.querySelector('.features-container');
var cards = Array.from(container.getElementsByClassName("feature-card")); var cards = Array.from(container.getElementsByClassName('feature-card'));
cards.sort(function (a, b) { cards.sort(function(a, b) {
var aIsFavorite = localStorage.getItem(a.id) === "favorite"; var aIsFavorite = localStorage.getItem(a.id) === 'favorite';
var bIsFavorite = localStorage.getItem(b.id) === "favorite"; var bIsFavorite = localStorage.getItem(b.id) === 'favorite';
if (aIsFavorite && !bIsFavorite) { if (aIsFavorite && !bIsFavorite) {
return -1; return -1;
} }
if (!aIsFavorite && bIsFavorite) { if (!aIsFavorite && bIsFavorite) {
return 1; return 1;
} }
return 0; return 0;
}); });
cards.forEach(function (card) { cards.forEach(function(card) {
container.appendChild(card); container.appendChild(card);
}); });
} }
function initializeCards() { function initializeCards() {
var cards = document.querySelectorAll(".feature-card"); var cards = document.querySelectorAll('.feature-card');
cards.forEach(function (card) { cards.forEach(function(card) {
var cardId = card.id; var cardId = card.id;
var img = card.querySelector(".favorite-icon img"); var img = card.querySelector('.favorite-icon img');
if (localStorage.getItem(cardId) === "favorite") { if (localStorage.getItem(cardId) === 'favorite') {
img.src = "images/star-fill.svg"; img.src = 'images/star-fill.svg';
card.classList.add("favorite"); card.classList.add('favorite');
} }
}); });
reorderCards(); reorderCards();
updateFavoritesDropdown(); updateFavoritesDropdown();
filterCards(); filterCards();
} }
window.onload = initializeCards; window.onload = initializeCards;

View File

@@ -1,88 +1,80 @@
document.addEventListener("DOMContentLoaded", function () { document.addEventListener('DOMContentLoaded', function() {
setLanguageForDropdown(".lang_dropdown-item"); setLanguageForDropdown('.lang_dropdown-item');
// Detect the browser's preferred language // Detect the browser's preferred language
let browserLang = navigator.language || navigator.userLanguage; let browserLang = navigator.language || navigator.userLanguage;
// Convert to a format consistent with your language codes (e.g., en-GB, fr-FR) // Convert to a format consistent with your language codes (e.g., en-GB, fr-FR)
browserLang = browserLang.replace("-", "_"); browserLang = browserLang.replace('-', '_');
// Check if the dropdown contains the browser's language // Check if the dropdown contains the browser's language
const dropdownLangExists = document.querySelector(`.lang_dropdown-item[data-language-code="${browserLang}"]`); const dropdownLangExists = document.querySelector(`.lang_dropdown-item[data-language-code="${browserLang}"]`);
// Set the default language to browser's language or 'en_GB' if not found in the dropdown // Set the default language to browser's language or 'en_GB' if not found in the dropdown
const defaultLocale = dropdownLangExists ? browserLang : "en_GB"; const defaultLocale = dropdownLangExists ? browserLang : 'en_GB';
const storedLocale = localStorage.getItem("languageCode") || defaultLocale; const storedLocale = localStorage.getItem('languageCode') || defaultLocale;
const dropdownItems = document.querySelectorAll(".lang_dropdown-item");
for (let i = 0; i < dropdownItems.length; i++) {
const item = dropdownItems[i]; const dropdownItems = document.querySelectorAll('.lang_dropdown-item');
item.classList.remove("active");
if (item.dataset.languageCode === storedLocale) { for (let i = 0; i < dropdownItems.length; i++) {
item.classList.add("active"); const item = dropdownItems[i];
} item.classList.remove('active');
item.addEventListener("click", handleDropdownItemClick); if (item.dataset.languageCode === storedLocale) {
} item.classList.add('active');
}
item.addEventListener('click', handleDropdownItemClick);
}
}); });
function setLanguageForDropdown(dropdownClass) { function setLanguageForDropdown(dropdownClass) {
const defaultLocale = document.documentElement.language || "en_GB"; const defaultLocale = document.documentElement.lang || 'en_GB';
const storedLocale = localStorage.getItem("languageCode") || defaultLocale; const storedLocale = localStorage.getItem('languageCode') || defaultLocale;
const dropdownItems = document.querySelectorAll(dropdownClass); const dropdownItems = document.querySelectorAll(dropdownClass);
for (let i = 0; i < dropdownItems.length; i++) { for (let i = 0; i < dropdownItems.length; i++) {
const item = dropdownItems[i]; const item = dropdownItems[i];
item.classList.remove("active"); item.classList.remove('active');
if (item.dataset.languageCode === storedLocale) { if (item.dataset.languageCode === storedLocale) {
item.classList.add("active"); item.classList.add('active');
}
item.addEventListener('click', handleDropdownItemClick);
} }
item.addEventListener("click", handleDropdownItemClick);
}
} }
function handleDropdownItemClick(event) { function handleDropdownItemClick(event) {
event.preventDefault(); event.preventDefault();
const languageCode = event.currentTarget.dataset.bsLanguageCode; // change this to event.currentTarget const languageCode = event.currentTarget.dataset.bsLanguageCode; // change this to event.currentTarget
if (languageCode) { if (languageCode) {
localStorage.setItem("languageCode", languageCode); localStorage.setItem('languageCode', languageCode);
const currentUrl = window.location.href; const currentUrl = window.location.href;
if (currentUrl.indexOf("?lang=") === -1) { if (currentUrl.indexOf('?lang=') === -1) {
window.location.href = currentUrl + "?lang=" + languageCode; window.location.href = currentUrl + '?lang=' + languageCode;
} else {
window.location.href = currentUrl.replace(/\?lang=\w{2,}/, '?lang=' + languageCode);
}
} else { } else {
window.location.href = currentUrl.replace(/\?lang=\w{2,}/, "?lang=" + languageCode); console.error("Language code is not set for this item."); // for debugging
} }
} else {
console.error("Language code is not set for this item."); // for debugging
}
} }
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 document.addEventListener('DOMContentLoaded', function() {
const list = Array.from(document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').children).filter( document.querySelectorAll('.nav-item.dropdown').forEach((element) => {
(child) => child.matches("a"), const dropdownMenu = element.querySelector(".dropdown-menu");
); if (dropdownMenu.id !== 'favoritesDropdown' && dropdownMenu.children.length <= 2 && dropdownMenu.querySelectorAll("hr.dropdown-divider").length === dropdownMenu.children.length) {
list if (element.previousElementSibling && element.previousElementSibling.classList.contains('nav-item') && element.previousElementSibling.classList.contains('nav-item-separator')) {
.sort(function (a, b) { element.previousElementSibling.remove();
return a.textContent.toUpperCase().localeCompare(b.textContent.toUpperCase()); }
}) element.remove();
.forEach((node) => document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').appendChild(node)); }
});
//Sort languages by alphabet
const list = Array.from(document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').children).filter(child => child.matches('a'));
list.sort(function(a, b) {
return a.textContent.toUpperCase().localeCompare(b.textContent.toUpperCase());
}).forEach(node => document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').appendChild(node));
}); });

View File

@@ -1,47 +1,47 @@
async function downloadFilesWithCallback(processFileCallback) { async function downloadFilesWithCallback(processFileCallback) {
const fileInput = document.querySelector('input[type="file"]'); const fileInput = document.querySelector('input[type="file"]');
const files = fileInput.files; const files = fileInput.files;
const zipThreshold = 4; const zipThreshold = 4;
const zipFiles = files.length > zipThreshold; const zipFiles = files.length > zipThreshold;
let jszip = null; let jszip = null;
if (zipFiles) { if (zipFiles) {
jszip = new JSZip(); jszip = new JSZip();
} }
const promises = Array.from(files).map(async (file) => { const promises = Array.from(files).map(async file => {
const { processedData, fileName } = await processFileCallback(file); const { processedData, fileName } = await processFileCallback(file);
if (zipFiles) {
jszip.file(fileName, processedData);
} else {
const url = URL.createObjectURL(processedData);
const downloadOption = localStorage.getItem('downloadOption');
if (downloadOption === 'sameWindow') {
window.location.href = url;
} else if (downloadOption === 'newWindow') {
window.open(url, '_blank');
} else {
const downloadLink = document.createElement('a');
downloadLink.href = url;
downloadLink.download = fileName;
downloadLink.click();
}
}
});
await Promise.all(promises);
if (zipFiles) { if (zipFiles) {
jszip.file(fileName, processedData); const content = await jszip.generateAsync({ type: "blob" });
} else { const url = URL.createObjectURL(content);
const url = URL.createObjectURL(processedData); const a = document.createElement('a');
const downloadOption = localStorage.getItem("downloadOption"); a.href = url;
a.download = "files.zip";
if (downloadOption === "sameWindow") { document.body.appendChild(a);
window.location.href = url; a.click();
} else if (downloadOption === "newWindow") { a.remove();
window.open(url, "_blank");
} else {
const downloadLink = document.createElement("a");
downloadLink.href = url;
downloadLink.download = fileName;
downloadLink.click();
}
} }
});
await Promise.all(promises);
if (zipFiles) {
const content = await jszip.generateAsync({ type: "blob" });
const url = URL.createObjectURL(content);
const a = document.createElement("a");
a.href = url;
a.download = "files.zip";
document.body.appendChild(a);
a.click();
a.remove();
}
} }

View File

@@ -1,27 +1,27 @@
let currentSort = { let currentSort = {
field: null, field: null,
descending: false, descending: false
}; };
document.getElementById("fileInput-input").addEventListener("change", function () { document.getElementById("fileInput-input").addEventListener("change", function() {
var files = this.files; var files = this.files;
displayFiles(files); displayFiles(files);
}); });
/** /**
* @param {FileList} files * @param {FileList} files
*/ */
function displayFiles(files) { function displayFiles(files) {
const list = document.getElementById("selectedFiles"); const list = document.getElementById("selectedFiles");
while (list.firstChild) { while (list.firstChild) {
list.removeChild(list.firstChild); list.removeChild(list.firstChild);
} }
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const item = document.createElement("li"); const item = document.createElement("li");
item.className = "list-group-item"; item.className = "list-group-item";
item.innerHTML = ` item.innerHTML = `
<div class="d-flex justify-content-between align-items-center w-100"> <div class="d-flex justify-content-between align-items-center w-100">
<div class="filename">${files[i].name}</div> <div class="filename">${files[i].name}</div>
<div class="arrows d-flex"> <div class="arrows d-flex">
@@ -31,100 +31,100 @@ function displayFiles(files) {
</div> </div>
</div> </div>
`; `;
list.appendChild(item); list.appendChild(item);
} }
attachMoveButtons(); attachMoveButtons();
} }
function attachMoveButtons() { function attachMoveButtons() {
var moveUpButtons = document.querySelectorAll(".move-up"); var moveUpButtons = document.querySelectorAll(".move-up");
for (var i = 0; i < moveUpButtons.length; i++) { for (var i = 0; i < moveUpButtons.length; i++) {
moveUpButtons[i].addEventListener("click", function (event) { moveUpButtons[i].addEventListener("click", function(event) {
event.preventDefault(); event.preventDefault();
var parent = this.closest(".list-group-item"); var parent = this.closest(".list-group-item");
var grandParent = parent.parentNode; var grandParent = parent.parentNode;
if (parent.previousElementSibling) { if (parent.previousElementSibling) {
grandParent.insertBefore(parent, parent.previousElementSibling); grandParent.insertBefore(parent, parent.previousElementSibling);
updateFiles(); updateFiles();
} }
}); });
} }
var moveDownButtons = document.querySelectorAll(".move-down"); var moveDownButtons = document.querySelectorAll(".move-down");
for (var i = 0; i < moveDownButtons.length; i++) { for (var i = 0; i < moveDownButtons.length; i++) {
moveDownButtons[i].addEventListener("click", function (event) { moveDownButtons[i].addEventListener("click", function(event) {
event.preventDefault(); event.preventDefault();
var parent = this.closest(".list-group-item"); var parent = this.closest(".list-group-item");
var grandParent = parent.parentNode; var grandParent = parent.parentNode;
if (parent.nextElementSibling) { if (parent.nextElementSibling) {
grandParent.insertBefore(parent.nextElementSibling, parent); grandParent.insertBefore(parent.nextElementSibling, parent);
updateFiles(); updateFiles();
} }
}); });
} }
var removeButtons = document.querySelectorAll(".remove-file"); var removeButtons = document.querySelectorAll(".remove-file");
for (var i = 0; i < removeButtons.length; i++) { for (var i = 0; i < removeButtons.length; i++) {
removeButtons[i].addEventListener("click", function (event) { removeButtons[i].addEventListener("click", function (event) {
event.preventDefault(); event.preventDefault();
var parent = this.closest(".list-group-item"); var parent = this.closest(".list-group-item");
parent.remove(); parent.remove();
updateFiles(); updateFiles();
}); });
} }
} }
document.getElementById("sortByNameBtn").addEventListener("click", function () { document.getElementById("sortByNameBtn").addEventListener("click", function() {
if (currentSort.field === "name" && !currentSort.descending) { if (currentSort.field === "name" && !currentSort.descending) {
currentSort.descending = true; currentSort.descending = true;
sortFiles((a, b) => b.name.localeCompare(a.name)); sortFiles((a, b) => b.name.localeCompare(a.name));
} else { } else {
currentSort.field = "name"; currentSort.field = "name";
currentSort.descending = false; currentSort.descending = false;
sortFiles((a, b) => a.name.localeCompare(b.name)); sortFiles((a, b) => a.name.localeCompare(b.name));
} }
}); });
document.getElementById("sortByDateBtn").addEventListener("click", function () { document.getElementById("sortByDateBtn").addEventListener("click", function() {
if (currentSort.field === "lastModified" && !currentSort.descending) { if (currentSort.field === "lastModified" && !currentSort.descending) {
currentSort.descending = true; currentSort.descending = true;
sortFiles((a, b) => b.lastModified - a.lastModified); sortFiles((a, b) => b.lastModified - a.lastModified);
} else { } else {
currentSort.field = "lastModified"; currentSort.field = "lastModified";
currentSort.descending = false; currentSort.descending = false;
sortFiles((a, b) => a.lastModified - b.lastModified); sortFiles((a, b) => a.lastModified - b.lastModified);
} }
}); });
function sortFiles(comparator) { function sortFiles(comparator) {
// Convert FileList to array and sort // Convert FileList to array and sort
const sortedFilesArray = Array.from(document.getElementById("fileInput-input").files).sort(comparator); const sortedFilesArray = Array.from(document.getElementById("fileInput-input").files).sort(comparator);
// Refresh displayed list // Refresh displayed list
displayFiles(sortedFilesArray); displayFiles(sortedFilesArray);
// Update the files property // Update the files property
const dataTransfer = new DataTransfer(); const dataTransfer = new DataTransfer();
sortedFilesArray.forEach((file) => dataTransfer.items.add(file)); sortedFilesArray.forEach(file => dataTransfer.items.add(file));
document.getElementById("fileInput-input").files = dataTransfer.files; document.getElementById("fileInput-input").files = dataTransfer.files;
} }
function updateFiles() { function updateFiles() {
var dataTransfer = new DataTransfer(); var dataTransfer = new DataTransfer();
var liElements = document.querySelectorAll("#selectedFiles li"); var liElements = document.querySelectorAll("#selectedFiles li");
const files = document.getElementById("fileInput-input").files; const files = document.getElementById("fileInput-input").files;
for (var i = 0; i < liElements.length; i++) { for (var i = 0; i < liElements.length; i++) {
var fileNameFromList = liElements[i].querySelector(".filename").innerText; var fileNameFromList = liElements[i].querySelector(".filename").innerText;
var fileFromFiles; var fileFromFiles;
for (var j = 0; j < files.length; j++) { for (var j = 0; j < files.length; j++) {
var file = files[j]; var file = files[j];
if (file.name === fileNameFromList) { if (file.name === fileNameFromList) {
dataTransfer.items.add(file); dataTransfer.items.add(file);
break; break;
} }
}
} }
} document.getElementById("fileInput-input").files = dataTransfer.files;
document.getElementById("fileInput-input").files = dataTransfer.files;
} }

View File

@@ -1,123 +1,125 @@
class DragDropManager { class DragDropManager {
dragContainer; dragContainer;
wrapper; wrapper;
pageDirection; pageDirection;
movePageTo; movePageTo;
pageDragging; pageDragging;
draggelEl; draggelEl;
draggedImageEl; draggedImageEl;
hoveredEl; hoveredEl;
endInsertionElement; endInsertionElement;
constructor(id, wrapperId) { constructor(id, wrapperId) {
this.dragContainer = document.getElementById(id); this.dragContainer = document.getElementById(id);
this.pageDirection = document.documentElement.getAttribute("lang-direction"); this.pageDirection = document.documentElement.getAttribute("lang-direction");
this.wrapper = document.getElementById(wrapperId); this.wrapper = document.getElementById(wrapperId);
this.pageDragging = false; this.pageDragging = false;
this.hoveredEl = undefined; this.hoveredEl = undefined;
this.draggelEl = undefined; this.draggelEl = undefined
this.draggedImageEl = undefined; this.draggedImageEl = undefined;
var styleElement = document.createElement("link"); var styleElement = document.createElement('link');
styleElement.rel = "stylesheet"; styleElement.rel = 'stylesheet';
styleElement.href = "css/dragdrop.css"; styleElement.href = 'css/dragdrop.css'
document.head.appendChild(styleElement); document.head.appendChild(styleElement);
const div = document.createElement("div"); const div = document.createElement('div');
div.classList.add("drag-manager_endpoint"); div.classList.add('drag-manager_endpoint');
div.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-arrow-down" viewBox="0 0 16 16"> div.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-arrow-down" viewBox="0 0 16 16">
<path d="M8.5 6.5a.5.5 0 0 0-1 0v3.793L6.354 9.146a.5.5 0 1 0-.708.708l2 2a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 10.293V6.5z"/> <path d="M8.5 6.5a.5.5 0 0 0-1 0v3.793L6.354 9.146a.5.5 0 1 0-.708.708l2 2a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 10.293V6.5z"/>
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5v2z"/> <path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5v2z"/>
</svg>`; </svg>`
this.endInsertionElement = div; this.endInsertionElement = div;
this.startDraggingPage = this.startDraggingPage.bind(this); this.startDraggingPage = this.startDraggingPage.bind(this);
this.onDragEl = this.onDragEl.bind(this); this.onDragEl = this.onDragEl.bind(this);
this.stopDraggingPage = this.stopDraggingPage.bind(this); this.stopDraggingPage = this.stopDraggingPage.bind(this);
this.adapt(div); this.adapt(div);
}
startDraggingPage(div) {
this.pageDragging = true;
this.draggedEl = div;
const img = div.querySelector("img");
div.classList.add("drag-manager_dragging");
const imageSrc = img.src;
const imgEl = document.createElement("img");
imgEl.classList.add("dragged-img");
imgEl.src = imageSrc;
this.draggedImageEl = imgEl;
imgEl.style.visibility = "hidden";
imgEl.style.transform = `rotate(${img.style.rotate === "" ? "0deg" : img.style.rotate}) translate(-50%, -50%)`;
this.dragContainer.appendChild(imgEl);
window.addEventListener("mouseup", this.stopDraggingPage);
window.addEventListener("mousemove", this.onDragEl);
this.wrapper.classList.add("drag-manager_dragging-container");
this.wrapper.appendChild(this.endInsertionElement);
}
onDragEl(mouseEvent) {
const { clientX, clientY } = mouseEvent;
if (this.draggedImageEl) {
this.draggedImageEl.style.visibility = "visible";
this.draggedImageEl.style.left = `${clientX}px`;
this.draggedImageEl.style.top = `${clientY}px`;
} }
}
stopDraggingPage() { startDraggingPage(div,) {
window.removeEventListener("mousemove", this.onDragEl); this.pageDragging = true;
this.wrapper.classList.remove("drag-manager_dragging-container"); this.draggedEl = div;
this.wrapper.removeChild(this.endInsertionElement); const img = div.querySelector('img');
window.removeEventListener("mouseup", this.stopDraggingPage); div.classList.add('drag-manager_dragging');
this.draggedImageEl = undefined; const imageSrc = img.src;
this.pageDragging = false;
this.draggedEl.classList.remove("drag-manager_dragging"); const imgEl = document.createElement('img');
this.hoveredEl?.classList.remove("drag-manager_draghover"); imgEl.classList.add('dragged-img');
this.dragContainer.childNodes.forEach((dragChild) => { imgEl.src = imageSrc;
this.dragContainer.removeChild(dragChild); this.draggedImageEl = imgEl;
}); imgEl.style.visibility = 'hidden';
if (!this.hoveredEl) { imgEl.style.transform = `rotate(${img.style.rotate === '' ? '0deg' : img.style.rotate}) translate(-50%, -50%)`;
return; this.dragContainer.appendChild(imgEl);
window.addEventListener('mouseup', this.stopDraggingPage)
window.addEventListener('mousemove', this.onDragEl)
this.wrapper.classList.add('drag-manager_dragging-container');
this.wrapper.appendChild(this.endInsertionElement);
} }
if (this.hoveredEl === this.endInsertionElement) {
this.movePageTo(this.draggedEl); onDragEl(mouseEvent) {
return; const { clientX, clientY } = mouseEvent;
if(this.draggedImageEl) {
this.draggedImageEl.style.visibility = 'visible';
this.draggedImageEl.style.left = `${clientX}px`;
this.draggedImageEl.style.top = `${clientY}px`;
}
} }
this.movePageTo(this.draggedEl, this.hoveredEl);
}
setActions({ movePageTo }) {
this.movePageTo = movePageTo;
}
adapt(div) { stopDraggingPage() {
const onDragStart = () => { window.removeEventListener('mousemove', this.onDragEl);
this.startDraggingPage(div); this.wrapper.classList.remove('drag-manager_dragging-container');
}; this.wrapper.removeChild(this.endInsertionElement);
window.removeEventListener('mouseup', this.stopDraggingPage)
this.draggedImageEl = undefined;
this.pageDragging = false;
this.draggedEl.classList.remove('drag-manager_dragging');
this.hoveredEl?.classList.remove('drag-manager_draghover');
this.dragContainer.childNodes.forEach((dragChild) => {
this.dragContainer.removeChild(dragChild);
})
if(!this.hoveredEl) {
return;
}
if(this.hoveredEl === this.endInsertionElement) {
this.movePageTo(this.draggedEl);
return;
}
this.movePageTo(this.draggedEl, this.hoveredEl);
}
const onMouseEnter = () => { setActions({ movePageTo }) {
if (this.pageDragging) { this.movePageTo = movePageTo;
this.hoveredEl = div; }
div.classList.add("drag-manager_draghover");
}
};
const onMouseLeave = () => {
this.hoveredEl = undefined;
div.classList.remove("drag-manager_draghover");
};
div.addEventListener("dragstart", onDragStart); adapt(div) {
div.addEventListener("mouseenter", onMouseEnter); const onDragStart = () => {
div.addEventListener("mouseleave", onMouseLeave); this.startDraggingPage(div);
}
return div; const onMouseEnter = () => {
} if (this.pageDragging) {
this.hoveredEl = div;
div.classList.add('drag-manager_draghover');
}
}
const onMouseLeave = () => {
this.hoveredEl = undefined
div.classList.remove('drag-manager_draghover');
}
div.addEventListener('dragstart', onDragStart);
div.addEventListener('mouseenter', onMouseEnter);
div.addEventListener('mouseleave', onMouseLeave);
return div;
}
} }
export default DragDropManager; export default DragDropManager;

View File

@@ -1,46 +1,46 @@
class ImageHiglighter { class ImageHiglighter {
imageHighlighter; imageHighlighter;
constructor(id) { constructor(id) {
this.imageHighlighter = document.getElementById(id); this.imageHighlighter = document.getElementById(id);
this.imageHighlightCallback = this.imageHighlightCallback.bind(this); this.imageHighlightCallback = this.imageHighlightCallback.bind(this);
var styleElement = document.createElement("link"); var styleElement = document.createElement('link');
styleElement.rel = "stylesheet"; styleElement.rel = 'stylesheet';
styleElement.href = "css/imageHighlighter.css"; styleElement.href = 'css/imageHighlighter.css'
document.head.appendChild(styleElement); document.head.appendChild(styleElement);
this.imageHighlighter.onclick = () => { this.imageHighlighter.onclick = () => {
this.imageHighlighter.childNodes.forEach((child) => { this.imageHighlighter.childNodes.forEach((child) => {
child.classList.add("remove"); child.classList.add('remove');
setTimeout(() => { setTimeout(() => {
this.imageHighlighter.removeChild(child); this.imageHighlighter.removeChild(child);
}, 100); }, 100)
}); })
}
}
imageHighlightCallback(highlightEvent) {
var bigImg = document.createElement('img');
bigImg.onclick = (imageClickEvent) => {
// This prevents the highlighter's onClick from closing the image when clicking
// on the image instead of next to it.
imageClickEvent.preventDefault();
imageClickEvent.stopPropagation();
};
bigImg.src = highlightEvent.target.src;
this.imageHighlighter.appendChild(bigImg);
}; };
}
imageHighlightCallback(highlightEvent) { setActions() {
var bigImg = document.createElement("img"); // not needed in this case
bigImg.onclick = (imageClickEvent) => { }
// This prevents the highlighter's onClick from closing the image when clicking
// on the image instead of next to it.
imageClickEvent.preventDefault();
imageClickEvent.stopPropagation();
};
bigImg.src = highlightEvent.target.src;
this.imageHighlighter.appendChild(bigImg);
}
setActions() { adapt(div) {
// not needed in this case const img = div.querySelector('.page-image');
} img.addEventListener('click', this.imageHighlightCallback)
return div;
adapt(div) { }
const img = div.querySelector(".page-image");
img.addEventListener("click", this.imageHighlightCallback);
return div;
}
} }
export default ImageHiglighter; export default ImageHiglighter;

View File

@@ -1,200 +1,198 @@
class PdfActionsManager { class PdfActionsManager {
pageDirection; pageDirection;
pagesContainer; pagesContainer;
constructor(id) { constructor(id) {
this.pagesContainer = document.getElementById(id); this.pagesContainer = document.getElementById(id);
this.pageDirection = document.documentElement.getAttribute("lang-direction"); this.pageDirection = document.documentElement.getAttribute("lang-direction");
var styleElement = document.createElement("link"); var styleElement = document.createElement('link');
styleElement.rel = "stylesheet"; styleElement.rel = 'stylesheet';
styleElement.href = "css/pdfActions.css"; styleElement.href = 'css/pdfActions.css'
document.head.appendChild(styleElement); document.head.appendChild(styleElement);
}
getPageContainer(element) {
var container = element;
while (!container.classList.contains("page-container")) {
container = container.parentNode;
} }
return container;
}
moveUpButtonCallback(e) { getPageContainer(element) {
var imgContainer = this.getPageContainer(e.target); var container = element
while (!container.classList.contains('page-container')) {
const sibling = imgContainer.previousSibling; container = container.parentNode;
if (sibling) { }
this.movePageTo(imgContainer, sibling, true); return container;
} }
}
moveDownButtonCallback(e) { moveUpButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target); var imgContainer = this.getPageContainer(e.target);
const sibling = imgContainer.nextSibling;
if (sibling) { const sibling = imgContainer.previousSibling;
this.movePageTo(imgContainer, sibling.nextSibling, true); if (sibling) {
this.movePageTo(imgContainer, sibling, true);
}
} }
}
rotateCCWButtonCallback(e) { moveDownButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target); var imgContainer = this.getPageContainer(e.target);
const img = imgContainer.querySelector("img"); const sibling = imgContainer.nextSibling;
if (sibling) {
this.movePageTo(imgContainer, sibling.nextSibling, true);
}
};
this.rotateElement(img, -90); rotateCCWButtonCallback(e) {
} var imgContainer = this.getPageContainer(e.target);
const img = imgContainer.querySelector("img");
rotateCWButtonCallback(e) { this.rotateElement(img, -90)
var imgContainer = this.getPageContainer(e.target); };
const img = imgContainer.querySelector("img");
this.rotateElement(img, 90); rotateCWButtonCallback(e) {
} var imgContainer = this.getPageContainer(e.target);
const img = imgContainer.querySelector("img");
deletePageButtonCallback(e) { this.rotateElement(img, 90)
var imgContainer = this.getPageContainer(e.target); };
this.pagesContainer.removeChild(imgContainer);
if (this.pagesContainer.childElementCount === 0) {
const filenameInput = document.getElementById("filename-input");
const filenameParagraph = document.getElementById("filename");
const downloadBtn = document.getElementById("export-button");
filenameInput.disabled = true; deletePageButtonCallback(e) {
filenameInput.value = ""; var imgContainer = this.getPageContainer(e.target);
filenameParagraph.innerText = ""; this.pagesContainer.removeChild(imgContainer);
if (this.pagesContainer.childElementCount === 0) {
const filenameInput = document.getElementById('filename-input');
const filenameParagraph = document.getElementById('filename');
const downloadBtn = document.getElementById('export-button');
downloadBtn.disabled = true; filenameInput.disabled = true;
filenameInput.value = "";
filenameParagraph.innerText = "";
downloadBtn.disabled = true;
}
};
insertFileButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target);
this.addPdfs(imgContainer)
};
setActions({ movePageTo, addPdfs, rotateElement }) {
this.movePageTo = movePageTo;
this.addPdfs = addPdfs;
this.rotateElement = rotateElement;
this.moveUpButtonCallback = this.moveUpButtonCallback.bind(this);
this.moveDownButtonCallback = this.moveDownButtonCallback.bind(this);
this.rotateCCWButtonCallback = this.rotateCCWButtonCallback.bind(this);
this.rotateCWButtonCallback = this.rotateCWButtonCallback.bind(this);
this.deletePageButtonCallback = this.deletePageButtonCallback.bind(this);
this.insertFileButtonCallback = this.insertFileButtonCallback.bind(this);
} }
}
insertFileButtonCallback(e) { adapt(div) {
var imgContainer = this.getPageContainer(e.target); div.classList.add('pdf-actions_container');
this.addPdfs(imgContainer); const leftDirection = this.pageDirection === 'rtl' ? 'right' : 'left'
} const rightDirection = this.pageDirection === 'rtl' ? 'left' : 'right'
const buttonContainer = document.createElement('div');
setActions({ movePageTo, addPdfs, rotateElement }) { buttonContainer.classList.add("pdf-actions_button-container", "hide-on-drag");
this.movePageTo = movePageTo;
this.addPdfs = addPdfs;
this.rotateElement = rotateElement;
this.moveUpButtonCallback = this.moveUpButtonCallback.bind(this); const moveUp = document.createElement('button');
this.moveDownButtonCallback = this.moveDownButtonCallback.bind(this); moveUp.classList.add("pdf-actions_move-left-button","btn", "btn-secondary");
this.rotateCCWButtonCallback = this.rotateCCWButtonCallback.bind(this); moveUp.innerHTML = `<i class="bi bi-arrow-${leftDirection}-short"></i>`;
this.rotateCWButtonCallback = this.rotateCWButtonCallback.bind(this); moveUp.onclick = this.moveUpButtonCallback;
this.deletePageButtonCallback = this.deletePageButtonCallback.bind(this); buttonContainer.appendChild(moveUp);
this.insertFileButtonCallback = this.insertFileButtonCallback.bind(this);
}
adapt(div) { const moveDown = document.createElement('button');
div.classList.add("pdf-actions_container"); moveDown.classList.add("pdf-actions_move-right-button","btn", "btn-secondary");
const leftDirection = this.pageDirection === "rtl" ? "right" : "left"; moveDown.innerHTML = `<i class="bi bi-arrow-${rightDirection}-short"></i>`;
const rightDirection = this.pageDirection === "rtl" ? "left" : "right"; moveDown.onclick = this.moveDownButtonCallback;
const buttonContainer = document.createElement("div"); buttonContainer.appendChild(moveDown);
buttonContainer.classList.add("pdf-actions_button-container", "hide-on-drag"); const rotateCCW = document.createElement('button');
rotateCCW.classList.add("btn", "btn-secondary");
const moveUp = document.createElement("button"); rotateCCW.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
moveUp.classList.add("pdf-actions_move-left-button", "btn", "btn-secondary");
moveUp.innerHTML = `<i class="bi bi-arrow-${leftDirection}-short"></i>`;
moveUp.onclick = this.moveUpButtonCallback;
buttonContainer.appendChild(moveUp);
const moveDown = document.createElement("button");
moveDown.classList.add("pdf-actions_move-right-button", "btn", "btn-secondary");
moveDown.innerHTML = `<i class="bi bi-arrow-${rightDirection}-short"></i>`;
moveDown.onclick = this.moveDownButtonCallback;
buttonContainer.appendChild(moveDown);
const rotateCCW = document.createElement("button");
rotateCCW.classList.add("btn", "btn-secondary");
rotateCCW.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" /> <path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" />
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" /> <path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" />
</svg>`; </svg>`;
rotateCCW.onclick = this.rotateCCWButtonCallback; rotateCCW.onclick = this.rotateCCWButtonCallback;
buttonContainer.appendChild(rotateCCW); buttonContainer.appendChild(rotateCCW);
const rotateCW = document.createElement("button"); const rotateCW = document.createElement('button');
rotateCW.classList.add("btn", "btn-secondary"); rotateCW.classList.add("btn", "btn-secondary");
rotateCW.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16"> rotateCW.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" /> <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" /> <path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
</svg>`; </svg>`;
rotateCW.onclick = this.rotateCWButtonCallback; rotateCW.onclick = this.rotateCWButtonCallback;
buttonContainer.appendChild(rotateCW); buttonContainer.appendChild(rotateCW);
const deletePage = document.createElement("button"); const deletePage = document.createElement('button');
deletePage.classList.add("btn", "btn-danger"); deletePage.classList.add("btn", "btn-danger");
deletePage.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16"> deletePage.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z"/> <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z"/>
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1ZM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118ZM2.5 3h11V2h-11v1Z"/> <path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1ZM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118ZM2.5 3h11V2h-11v1Z"/>
</svg>`; </svg>`;
deletePage.onclick = this.deletePageButtonCallback; deletePage.onclick = this.deletePageButtonCallback;
buttonContainer.appendChild(deletePage); buttonContainer.appendChild(deletePage);
div.appendChild(buttonContainer); div.appendChild(buttonContainer);
const insertFileButtonContainer = document.createElement("div"); const insertFileButtonContainer = document.createElement('div');
insertFileButtonContainer.classList.add( insertFileButtonContainer.classList.add(
"pdf-actions_insert-file-button-container", "pdf-actions_insert-file-button-container",
leftDirection, leftDirection,
`align-center-${leftDirection}`, `align-center-${leftDirection}`);
);
const insertFileButton = document.createElement("button"); const insertFileButton = document.createElement('button');
insertFileButton.classList.add("btn", "btn-primary", "pdf-actions_insert-file-button"); insertFileButton.classList.add("btn", "btn-primary", "pdf-actions_insert-file-button");
insertFileButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-plus" viewBox="0 0 16 16"> insertFileButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-plus" viewBox="0 0 16 16">
<path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5z"/> <path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5z"/>
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/> <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
</svg>`; </svg>`;
insertFileButton.onclick = this.insertFileButtonCallback; insertFileButton.onclick = this.insertFileButtonCallback;
insertFileButtonContainer.appendChild(insertFileButton); insertFileButtonContainer.appendChild(insertFileButton);
div.appendChild(insertFileButtonContainer); div.appendChild(insertFileButtonContainer);
// add this button to every element, but only show it on the last one :D // add this button to every element, but only show it on the last one :D
const insertFileButtonRightContainer = document.createElement("div"); const insertFileButtonRightContainer = document.createElement('div');
insertFileButtonRightContainer.classList.add( insertFileButtonRightContainer.classList.add(
"pdf-actions_insert-file-button-container", "pdf-actions_insert-file-button-container",
rightDirection, rightDirection,
`align-center-${rightDirection}`, `align-center-${rightDirection}`);
);
const insertFileButtonRight = document.createElement("button"); const insertFileButtonRight = document.createElement('button');
insertFileButtonRight.classList.add("btn", "btn-primary", "pdf-actions_insert-file-button"); insertFileButtonRight.classList.add("btn", "btn-primary", "pdf-actions_insert-file-button");
insertFileButtonRight.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-plus" viewBox="0 0 16 16"> insertFileButtonRight.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-plus" viewBox="0 0 16 16">
<path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5z"/> <path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5z"/>
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/> <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
insertFileButtonRight</svg>`; insertFileButtonRight</svg>`;
insertFileButtonRight.onclick = () => addPdfs(); insertFileButtonRight.onclick = () => addPdfs();
insertFileButtonRightContainer.appendChild(insertFileButtonRight); insertFileButtonRightContainer.appendChild(insertFileButtonRight);
div.appendChild(insertFileButtonRightContainer); div.appendChild(insertFileButtonRightContainer);
const adaptPageNumber = (pageNumber, div) => { const adaptPageNumber = (pageNumber, div) => {
const pageNumberElement = document.createElement("span"); const pageNumberElement = document.createElement('span');
pageNumberElement.classList.add("page-number"); pageNumberElement.classList.add('page-number');
pageNumberElement.textContent = pageNumber; pageNumberElement.textContent = pageNumber;
div.insertBefore(pageNumberElement, div.firstChild); div.insertBefore(pageNumberElement, div.firstChild);
}; };
div.addEventListener("mouseenter", () => { div.addEventListener('mouseenter', () => {
const pageNumber = Array.from(div.parentNode.children).indexOf(div) + 1; const pageNumber = Array.from(div.parentNode.children).indexOf(div) + 1;
adaptPageNumber(pageNumber, div); adaptPageNumber(pageNumber, div);
}); });
div.addEventListener("mouseleave", () => { div.addEventListener('mouseleave', () => {
const pageNumberElement = div.querySelector(".page-number"); const pageNumberElement = div.querySelector('.page-number');
if (pageNumberElement) { if (pageNumberElement) {
div.removeChild(pageNumberElement); div.removeChild(pageNumberElement);
} }
}); });
return div; return div;
} }
} }
export default PdfActionsManager; export default PdfActionsManager;

View File

@@ -1,282 +1,285 @@
class PdfContainer { class PdfContainer {
fileName; fileName;
pagesContainer; pagesContainer;
pagesContainerWrapper; pagesContainerWrapper;
pdfAdapters; pdfAdapters;
downloadLink; downloadLink;
constructor(id, wrapperId, pdfAdapters) { constructor(id, wrapperId, pdfAdapters) {
this.pagesContainer = document.getElementById(id); this.pagesContainer = document.getElementById(id)
this.pagesContainerWrapper = document.getElementById(wrapperId); this.pagesContainerWrapper = document.getElementById(wrapperId);
this.downloadLink = null; this.downloadLink = null;
this.movePageTo = this.movePageTo.bind(this); this.movePageTo = this.movePageTo.bind(this);
this.addPdfs = this.addPdfs.bind(this); this.addPdfs = this.addPdfs.bind(this);
this.addPdfsFromFiles = this.addPdfsFromFiles.bind(this); this.addPdfsFromFiles = this.addPdfsFromFiles.bind(this);
this.rotateElement = this.rotateElement.bind(this); this.rotateElement = this.rotateElement.bind(this);
this.rotateAll = this.rotateAll.bind(this); this.rotateAll = this.rotateAll.bind(this);
this.exportPdf = this.exportPdf.bind(this); this.exportPdf = this.exportPdf.bind(this);
this.updateFilename = this.updateFilename.bind(this); this.updateFilename = this.updateFilename.bind(this);
this.setDownloadAttribute = this.setDownloadAttribute.bind(this); this.setDownloadAttribute = this.setDownloadAttribute.bind(this);
this.preventIllegalChars = this.preventIllegalChars.bind(this); this.preventIllegalChars = this.preventIllegalChars.bind(this);
this.pdfAdapters = pdfAdapters; this.pdfAdapters = pdfAdapters;
this.pdfAdapters.forEach((adapter) => { this.pdfAdapters.forEach(adapter => {
adapter.setActions({ adapter.setActions({
movePageTo: this.movePageTo, movePageTo: this.movePageTo,
addPdfs: this.addPdfs, addPdfs: this.addPdfs,
rotateElement: this.rotateElement, rotateElement: this.rotateElement,
updateFilename: this.updateFilename, updateFilename: this.updateFilename
}); })
}); })
window.addPdfs = this.addPdfs; window.addPdfs = this.addPdfs;
window.exportPdf = this.exportPdf; window.exportPdf = this.exportPdf;
window.rotateAll = this.rotateAll; window.rotateAll = this.rotateAll;
const filenameInput = document.getElementById("filename-input"); const filenameInput = document.getElementById('filename-input');
const downloadBtn = document.getElementById("export-button"); const downloadBtn = document.getElementById('export-button');
filenameInput.onkeyup = this.updateFilename; filenameInput.onkeyup = this.updateFilename;
filenameInput.onkeydown = this.preventIllegalChars; filenameInput.onkeydown = this.preventIllegalChars;
filenameInput.disabled = false; filenameInput.disabled = false;
filenameInput.innerText = ""; filenameInput.innerText = "";
downloadBtn.disabled = true; downloadBtn.disabled = true;
}
movePageTo(startElement, endElement, scrollTo = false) {
const childArray = Array.from(this.pagesContainer.childNodes);
const startIndex = childArray.indexOf(startElement);
const endIndex = childArray.indexOf(endElement);
this.pagesContainer.removeChild(startElement);
if (!endElement) {
this.pagesContainer.append(startElement);
} else {
this.pagesContainer.insertBefore(startElement, endElement);
} }
if (scrollTo) { movePageTo(startElement, endElement, scrollTo = false) {
const { width } = startElement.getBoundingClientRect(); const childArray = Array.from(this.pagesContainer.childNodes);
const vector = endIndex !== -1 && startIndex > endIndex ? 0 - width : width; const startIndex = childArray.indexOf(startElement);
const endIndex = childArray.indexOf(endElement);
this.pagesContainerWrapper.scroll({ this.pagesContainer.removeChild(startElement);
left: this.pagesContainerWrapper.scrollLeft + vector, if(!endElement) {
}); this.pagesContainer.append(startElement);
}
}
addPdfs(nextSiblingElement) {
var input = document.createElement("input");
input.type = "file";
input.multiple = true;
input.setAttribute("accept", "application/pdf");
input.onchange = async (e) => {
const files = e.target.files;
this.addPdfsFromFiles(files, nextSiblingElement);
this.updateFilename(files ? files[0].name : "");
};
input.click();
}
async addPdfsFromFiles(files, nextSiblingElement) {
this.fileName = files[0].name;
for (var i = 0; i < files.length; i++) {
await this.addPdfFile(files[i], nextSiblingElement);
}
document.querySelectorAll(".enable-on-file").forEach((element) => {
element.disabled = false;
});
}
rotateElement(element, deg) {
var lastTransform = element.style.rotate;
if (!lastTransform) {
lastTransform = "0";
}
const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ""));
const newAngle = lastAngle + deg;
element.style.rotate = newAngle + "deg";
}
async addPdfFile(file, nextSiblingElement) {
const { renderer, pdfDocument } = await this.loadFile(file);
for (var i = 0; i < renderer.pageCount; i++) {
const div = document.createElement("div");
div.classList.add("page-container");
var img = document.createElement("img");
img.classList.add("page-image");
const imageSrc = await renderer.renderPage(i);
img.src = imageSrc;
img.pageIdx = i;
img.rend = renderer;
img.doc = pdfDocument;
div.appendChild(img);
this.pdfAdapters.forEach((adapter) => {
adapter.adapt?.(div);
});
if (nextSiblingElement) {
this.pagesContainer.insertBefore(div, nextSiblingElement);
} else {
this.pagesContainer.appendChild(div);
}
}
}
async loadFile(file) {
var objectUrl = URL.createObjectURL(file);
var pdfDocument = await this.toPdfLib(objectUrl);
var renderer = await this.toRenderer(objectUrl);
return { renderer, pdfDocument };
}
async toRenderer(objectUrl) {
pdfjsLib.GlobalWorkerOptions.workerSrc = "pdfjs/pdf.worker.js";
const pdf = await pdfjsLib.getDocument(objectUrl).promise;
return {
document: pdf,
pageCount: pdf.numPages,
renderPage: async function (pageIdx) {
const page = await this.document.getPage(pageIdx + 1);
const canvas = document.createElement("canvas");
// set the canvas size to the size of the page
if (page.rotate == 90 || page.rotate == 270) {
canvas.width = page.view[3];
canvas.height = page.view[2];
} else { } else {
canvas.width = page.view[2]; this.pagesContainer.insertBefore(startElement, endElement);
canvas.height = page.view[3];
} }
// render the page onto the canvas if(scrollTo) {
var renderContext = { const { width } = startElement.getBoundingClientRect();
canvasContext: canvas.getContext("2d"), const vector = (endIndex !== -1 && startIndex > endIndex)
viewport: page.getViewport({ scale: 1 }), ? 0-width
: width;
this.pagesContainerWrapper.scroll({
left: this.pagesContainerWrapper.scrollLeft + vector,
})
}
}
addPdfs(nextSiblingElement) {
var input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.setAttribute("accept", "application/pdf");
input.onchange = async(e) => {
const files = e.target.files;
this.addPdfsFromFiles(files, nextSiblingElement);
this.updateFilename(files ? files[0].name : "");
}
input.click();
}
async addPdfsFromFiles(files, nextSiblingElement) {
this.fileName = files[0].name;
for (var i=0; i < files.length; i++) {
await this.addPdfFile(files[i], nextSiblingElement);
}
document.querySelectorAll(".enable-on-file").forEach(element => {
element.disabled = false;
});
}
rotateElement(element, deg) {
var lastTransform = element.style.rotate;
if (!lastTransform) {
lastTransform = "0";
}
const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ''));
const newAngle = lastAngle + deg;
element.style.rotate = newAngle + "deg";
}
async addPdfFile(file, nextSiblingElement) {
const { renderer, pdfDocument } = await this.loadFile(file);
for (var i=0; i < renderer.pageCount; i++) {
const div = document.createElement('div');
div.classList.add("page-container");
var img = document.createElement('img');
img.classList.add('page-image')
const imageSrc = await renderer.renderPage(i)
img.src = imageSrc;
img.pageIdx = i;
img.rend = renderer;
img.doc = pdfDocument;
div.appendChild(img);
this.pdfAdapters.forEach((adapter) => {
adapter.adapt?.(div)
})
if (nextSiblingElement) {
this.pagesContainer.insertBefore(div, nextSiblingElement);
} else {
this.pagesContainer.appendChild(div);
}
}
}
async loadFile(file) {
var objectUrl = URL.createObjectURL(file);
var pdfDocument = await this.toPdfLib(objectUrl);
var renderer = await this.toRenderer(objectUrl);
return { renderer, pdfDocument };
}
async toRenderer(objectUrl) {
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
const pdf = await pdfjsLib.getDocument(objectUrl).promise;
return {
document: pdf,
pageCount: pdf.numPages,
renderPage: async function(pageIdx) {
const page = await this.document.getPage(pageIdx+1);
const canvas = document.createElement("canvas");
// set the canvas size to the size of the page
if (page.rotate == 90 || page.rotate == 270) {
canvas.width = page.view[3];
canvas.height = page.view[2];
} else {
canvas.width = page.view[2];
canvas.height = page.view[3];
}
// render the page onto the canvas
var renderContext = {
canvasContext: canvas.getContext("2d"),
viewport: page.getViewport({ scale: 1 })
};
await page.render(renderContext).promise;
return canvas.toDataURL();
}
}; };
await page.render(renderContext).promise;
return canvas.toDataURL();
},
};
}
async toPdfLib(objectUrl) {
const existingPdfBytes = await fetch(objectUrl).then((res) => res.arrayBuffer());
const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes, {
ignoreEncryption: true,
});
return pdfDoc;
}
rotateAll(deg) {
for (var i = 0; i < this.pagesContainer.childNodes.length; i++) {
const img = this.pagesContainer.childNodes[i].querySelector("img");
if (!img) continue;
this.rotateElement(img, deg);
}
}
async exportPdf() {
const pdfDoc = await PDFLib.PDFDocument.create();
const pageContainers = this.pagesContainer.querySelectorAll(".page-container"); // Select all .page-container elements
for (var i = 0; i < pageContainers.length; i++) {
const img = pageContainers[i].querySelector("img"); // Find the img element within each .page-container
if (!img) continue;
const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx]);
const page = pages[0];
const rotation = img.style.rotate;
if (rotation) {
const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, ""));
page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle));
}
pdfDoc.addPage(page);
}
const pdfBytes = await pdfDoc.save();
const pdfBlob = new Blob([pdfBytes], { type: "application/pdf" });
const url = URL.createObjectURL(pdfBlob);
const downloadOption = localStorage.getItem("downloadOption");
const filenameInput = document.getElementById("filename-input");
let inputArr = filenameInput.value.split(".");
if (inputArr !== null && inputArr !== undefined && inputArr.length > 0) {
inputArr = inputArr.filter((n) => n); // remove all empty strings, nulls or undefined
if (inputArr.length > 1) {
inputArr.pop(); // remove right part after last dot
}
filenameInput.value = inputArr.join("");
this.fileName = filenameInput.value;
} }
if (!filenameInput.value.includes(".pdf")) { async toPdfLib(objectUrl) {
filenameInput.value = filenameInput.value + ".pdf"; const existingPdfBytes = await fetch(objectUrl).then(res => res.arrayBuffer());
this.fileName = filenameInput.value; const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes, { ignoreEncryption: true });
return pdfDoc;
} }
if (downloadOption === "sameWindow") {
// Open the file in the same window
window.location.href = url;
} else if (downloadOption === "newWindow") {
// Open the file in a new window
window.open(url, "_blank");
} else {
// Download the file
this.downloadLink = document.createElement("a");
this.downloadLink.id = "download-link";
this.downloadLink.href = url;
// downloadLink.download = this.fileName ? this.fileName : 'managed.pdf';
// downloadLink.download = this.fileName;
this.downloadLink.setAttribute("download", this.fileName ? this.fileName : "managed.pdf");
this.downloadLink.setAttribute("target", "_blank");
this.downloadLink.onclick = this.setDownloadAttribute;
this.downloadLink.click();
}
}
setDownloadAttribute() {
this.downloadLink.setAttribute("download", this.fileName ? this.fileName : "managed.pdf");
}
updateFilename(fileName = "") { rotateAll(deg) {
const filenameInput = document.getElementById("filename-input"); for (var i=0; i<this.pagesContainer.childNodes.length; i++) {
const pagesContainer = document.getElementById("pages-container"); const img = this.pagesContainer.childNodes[i].querySelector("img");
const downloadBtn = document.getElementById("export-button"); if (!img) continue;
this.rotateElement(img, deg)
downloadBtn.disabled = pagesContainer.childElementCount === 0; }
if (!this.fileName) {
this.fileName = fileName;
} }
if (!filenameInput.value) { async exportPdf() {
filenameInput.value = this.fileName; const pdfDoc = await PDFLib.PDFDocument.create();
} const pageContainers = this.pagesContainer.querySelectorAll('.page-container'); // Select all .page-container elements
} for (var i = 0; i < pageContainers.length; i++) {
const img = pageContainers[i].querySelector("img"); // Find the img element within each .page-container
if (!img) continue;
const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx])
const page = pages[0];
preventIllegalChars(e) { const rotation = img.style.rotate;
// const filenameInput = document.getElementById('filename-input'); if (rotation) {
// const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, ''));
// filenameInput.value = filenameInput.value.replace('.pdf', ''); page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle))
// }
// // prevent .
// if (filenameInput.value.includes('.')) { pdfDoc.addPage(page);
// filenameInput.value.replace('.',''); }
// } const pdfBytes = await pdfDoc.save();
} const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(pdfBlob);
const downloadOption = localStorage.getItem('downloadOption');
const filenameInput = document.getElementById('filename-input');
let inputArr = filenameInput.value.split('.');
if (inputArr !== null && inputArr !== undefined && inputArr.length > 0) {
inputArr = inputArr.filter(n => n); // remove all empty strings, nulls or undefined
if (inputArr.length > 1) {
inputArr.pop(); // remove right part after last dot
}
filenameInput.value = inputArr.join('');
this.fileName = filenameInput.value;
}
if (!filenameInput.value.includes('.pdf')) {
filenameInput.value = filenameInput.value + '.pdf';
this.fileName = filenameInput.value;
}
if (downloadOption === 'sameWindow') {
// Open the file in the same window
window.location.href = url;
} else if (downloadOption === 'newWindow') {
// Open the file in a new window
window.open(url, '_blank');
} else {
// Download the file
this.downloadLink = document.createElement('a');
this.downloadLink.id = 'download-link';
this.downloadLink.href = url;
// downloadLink.download = this.fileName ? this.fileName : 'managed.pdf';
// downloadLink.download = this.fileName;
this.downloadLink.setAttribute('download', this.fileName ? this.fileName : 'managed.pdf');
this.downloadLink.setAttribute('target', '_blank');
this.downloadLink.onclick = this.setDownloadAttribute;
this.downloadLink.click();
}
}
setDownloadAttribute() {
this.downloadLink.setAttribute("download", this.fileName ? this.fileName : 'managed.pdf');
}
updateFilename(fileName = "") {
const filenameInput = document.getElementById('filename-input');
const pagesContainer = document.getElementById('pages-container');
const downloadBtn = document.getElementById('export-button');
downloadBtn.disabled = pagesContainer.childElementCount === 0
if (!this.fileName) {
this.fileName = fileName;
}
if (!filenameInput.value) {
filenameInput.value = this.fileName;
}
}
preventIllegalChars(e) {
// const filenameInput = document.getElementById('filename-input');
//
// filenameInput.value = filenameInput.value.replace('.pdf', '');
//
// // prevent .
// if (filenameInput.value.includes('.')) {
// filenameInput.value.replace('.','');
// }
}
} }
export default PdfContainer; export default PdfContainer;

View File

@@ -1,95 +1,94 @@
class FileDragManager { class FileDragManager {
overlay; overlay;
dragCounter; dragCounter;
updateFilename; updateFilename;
constructor(cb = null) { constructor(cb = null) {
this.dragCounter = 0; this.dragCounter = 0;
this.setCallback(cb); this.setCallback(cb);
// Prevent default behavior for drag events // Prevent default behavior for drag events
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
document.body.addEventListener(eventName, preventDefaults, false); document.body.addEventListener(eventName, preventDefaults, false);
}); });
function preventDefaults(e) { function preventDefaults(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
}
this.dragenterListener = this.dragenterListener.bind(this);
this.dragleaveListener = this.dragleaveListener.bind(this);
this.dropListener = this.dropListener.bind(this);
document.body.addEventListener("dragenter", this.dragenterListener);
document.body.addEventListener("dragleave", this.dragleaveListener);
// Add drop event listener
document.body.addEventListener("drop", this.dropListener);
}
setActions({ updateFilename }) {
this.updateFilename = updateFilename;
}
setCallback(cb) {
if (cb) {
this.callback = cb;
} else {
this.callback = (files) => console.warn("FileDragManager not set");
}
}
dragenterListener() {
this.dragCounter++;
if (!this.overlay) {
// Create and show the overlay
this.overlay = document.createElement("div");
this.overlay.style.position = "fixed";
this.overlay.style.top = 0;
this.overlay.style.left = 0;
this.overlay.style.width = "100%";
this.overlay.style.height = "100%";
this.overlay.style.background = "rgba(0, 0, 0, 0.5)";
this.overlay.style.color = "#fff";
this.overlay.style.zIndex = "1000";
this.overlay.style.display = "flex";
this.overlay.style.alignItems = "center";
this.overlay.style.justifyContent = "center";
this.overlay.style.pointerEvents = "none";
this.overlay.innerHTML = "<p>Drop files anywhere to upload</p>";
document.getElementById("content-wrap").appendChild(this.overlay);
}
}
dragleaveListener() {
this.dragCounter--;
if (this.dragCounter === 0) {
// Hide and remove the overlay
if (this.overlay) {
this.overlay.remove();
this.overlay = null;
}
}
}
dropListener(e) {
const dt = e.dataTransfer;
const files = dt.files;
this.callback(files)
.catch((err) => {
console.error(err);
//maybe
})
.finally(() => {
// Hide and remove the overlay
if (this.overlay) {
this.overlay.remove();
this.overlay = null;
} }
this.updateFilename(files ? files[0].name : ""); this.dragenterListener = this.dragenterListener.bind(this);
}); this.dragleaveListener = this.dragleaveListener.bind(this);
} this.dropListener = this.dropListener.bind(this);
document.body.addEventListener('dragenter', this.dragenterListener);
document.body.addEventListener('dragleave', this.dragleaveListener);
// Add drop event listener
document.body.addEventListener('drop', this.dropListener);
}
setActions({ updateFilename }) {
this.updateFilename = updateFilename;
}
setCallback(cb) {
if (cb) {
this.callback = cb;
} else {
this.callback = (files) => console.warn("FileDragManager not set");
}
}
dragenterListener() {
this.dragCounter++;
if (!this.overlay) {
// Create and show the overlay
this.overlay = document.createElement('div');
this.overlay.style.position = 'fixed';
this.overlay.style.top = 0;
this.overlay.style.left = 0;
this.overlay.style.width = '100%';
this.overlay.style.height = '100%';
this.overlay.style.background = 'rgba(0, 0, 0, 0.5)';
this.overlay.style.color = '#fff';
this.overlay.style.zIndex = '1000';
this.overlay.style.display = 'flex';
this.overlay.style.alignItems = 'center';
this.overlay.style.justifyContent = 'center';
this.overlay.style.pointerEvents = 'none';
this.overlay.innerHTML = '<p>Drop files anywhere to upload</p>';
document.getElementById('content-wrap').appendChild(this.overlay);
}
};
dragleaveListener() {
this.dragCounter--;
if (this.dragCounter === 0) {
// Hide and remove the overlay
if (this.overlay) {
this.overlay.remove();
this.overlay = null;
}
}
};
dropListener(e) {
const dt = e.dataTransfer;
const files = dt.files;
this.callback(files).catch((err) => {
console.error(err);
//maybe
}).finally(() => {
// Hide and remove the overlay
if (this.overlay) {
this.overlay.remove();
this.overlay = null;
}
this.updateFilename(files ? files[0].name : "");
});
};
} }
export default FileDragManager; export default FileDragManager;

View File

@@ -1,34 +1,35 @@
const scrollDivHorizontally = (id) => { const scrollDivHorizontally = (id) => {
var scrollDelta = 0; // variable to store the accumulated scroll delta var scrollDelta = 0; // variable to store the accumulated scroll delta
var isScrolling = false; // variable to track if scroll is already in progress var isScrolling = false; // variable to track if scroll is already in progress
const divToScrollHorizontally = document.getElementById(id); const divToScrollHorizontally = document.getElementById(id)
function scrollLoop() { function scrollLoop() {
// Scroll the div horizontally by a fraction of the accumulated scroll delta // Scroll the div horizontally by a fraction of the accumulated scroll delta
divToScrollHorizontally.scrollLeft += scrollDelta * 0.1; divToScrollHorizontally.scrollLeft += scrollDelta * 0.1;
// Reduce the accumulated scroll delta by a fraction // Reduce the accumulated scroll delta by a fraction
scrollDelta *= 0.9; scrollDelta *= 0.9;
// If scroll delta is still significant, continue the scroll loop // If scroll delta is still significant, continue the scroll loop
if (Math.abs(scrollDelta) > 0.1) { if (Math.abs(scrollDelta) > 0.1) {
requestAnimationFrame(scrollLoop); requestAnimationFrame(scrollLoop);
} else { } else {
isScrolling = false; // Reset scroll in progress flag isScrolling = false; // Reset scroll in progress flag
}
} }
}
divToScrollHorizontally.addEventListener("wheel", function (e) {
e.preventDefault(); // prevent default mousewheel behavior
// Accumulate the horizontal scroll delta divToScrollHorizontally.addEventListener("wheel", function(e) {
scrollDelta -= e.deltaX || e.wheelDeltaX || -e.deltaY || -e.wheelDeltaY; e.preventDefault(); // prevent default mousewheel behavior
// If scroll is not already in progress, start the scroll loop // Accumulate the horizontal scroll delta
if (!isScrolling) { scrollDelta -= e.deltaX || e.wheelDeltaX || -e.deltaY || -e.wheelDeltaY;
isScrolling = true;
requestAnimationFrame(scrollLoop); // If scroll is not already in progress, start the scroll loop
} if (!isScrolling) {
}); isScrolling = true;
}; requestAnimationFrame(scrollLoop);
}
});
}
export default scrollDivHorizontally; export default scrollDivHorizontally;

File diff suppressed because it is too large Load Diff

View File

@@ -1,76 +1,75 @@
// Toggle search bar when the search icon is clicked // Toggle search bar when the search icon is clicked
document.querySelector("#search-icon").addEventListener("click", function (e) { document.querySelector('#search-icon').addEventListener('click', function(e) {
e.preventDefault(); e.preventDefault();
var searchBar = document.querySelector("#navbarSearch"); var searchBar = document.querySelector('#navbarSearch');
searchBar.classList.toggle("show"); searchBar.classList.toggle('show');
}); });
window.onload = function () { window.onload = function() {
var items = document.querySelectorAll(".dropdown-item, .nav-link"); var items = document.querySelectorAll('.dropdown-item, .nav-link');
var dummyContainer = document.createElement("div"); var dummyContainer = document.createElement('div');
dummyContainer.style.position = "absolute"; dummyContainer.style.position = 'absolute';
dummyContainer.style.visibility = "hidden"; dummyContainer.style.visibility = 'hidden';
dummyContainer.style.whiteSpace = "nowrap"; // Ensure we measure full width dummyContainer.style.whiteSpace = 'nowrap'; // Ensure we measure full width
document.body.appendChild(dummyContainer); document.body.appendChild(dummyContainer);
var maxWidth = 0; var maxWidth = 0;
items.forEach(function (item) { items.forEach(function(item) {
var clone = item.cloneNode(true); var clone = item.cloneNode(true);
dummyContainer.appendChild(clone); dummyContainer.appendChild(clone);
var width = clone.offsetWidth; var width = clone.offsetWidth;
if (width > maxWidth) { if (width > maxWidth) {
maxWidth = width; maxWidth = width;
} }
dummyContainer.removeChild(clone); dummyContainer.removeChild(clone);
}); });
document.body.removeChild(dummyContainer); document.body.removeChild(dummyContainer);
// Store max width for later use // Store max width for later use
window.navItemMaxWidth = maxWidth; window.navItemMaxWidth = maxWidth;
}; };
// Show search results as user types in search box // Show search results as user types in search box
document.querySelector("#navbarSearchInput").addEventListener("input", function (e) { document.querySelector('#navbarSearchInput').addEventListener('input', function(e) {
var searchText = e.target.value.toLowerCase(); var searchText = e.target.value.toLowerCase();
var items = document.querySelectorAll(".dropdown-item, .nav-link"); var items = document.querySelectorAll('.dropdown-item, .nav-link');
var resultsBox = document.querySelector("#searchResults"); var resultsBox = document.querySelector('#searchResults');
// Clear any previous results // Clear any previous results
resultsBox.innerHTML = ""; resultsBox.innerHTML = '';
items.forEach(function (item) { items.forEach(function(item) {
var titleElement = item.querySelector(".icon-text"); var titleElement = item.querySelector('.icon-text');
var iconElement = item.querySelector(".icon"); var iconElement = item.querySelector('.icon');
var itemHref = item.getAttribute("href"); var itemHref = item.getAttribute('href');
var tags = item.getAttribute("data-bs-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 !== "#") { if (titleElement && iconElement && itemHref !== '#') {
var title = titleElement.innerText; var title = titleElement.innerText;
if ( if ((title.toLowerCase().indexOf(searchText) !== -1 || tags.toLowerCase().indexOf(searchText) !== -1) && !resultsBox.querySelector(`a[href="${item.getAttribute('href')}"]`)) {
(title.toLowerCase().indexOf(searchText) !== -1 || tags.toLowerCase().indexOf(searchText) !== -1) && var result = document.createElement('a');
!resultsBox.querySelector(`a[href="${item.getAttribute("href")}"]`) result.href = itemHref;
) { result.classList.add('dropdown-item');
var result = document.createElement("a");
result.href = itemHref;
result.classList.add("dropdown-item");
var resultIcon = document.createElement("img"); var resultIcon = document.createElement('img');
resultIcon.src = iconElement.src; resultIcon.src = iconElement.src;
resultIcon.alt = "icon"; resultIcon.alt = 'icon';
resultIcon.classList.add("icon"); resultIcon.classList.add('icon');
result.appendChild(resultIcon); result.appendChild(resultIcon);
var resultText = document.createElement("span"); var resultText = document.createElement('span');
resultText.textContent = title; resultText.textContent = title;
resultText.classList.add("icon-text"); resultText.classList.add('icon-text');
result.appendChild(resultText); result.appendChild(resultText);
resultsBox.appendChild(result); resultsBox.appendChild(result);
} }
} }
}); });
// Set the width of the search results box to the maximum width // Set the width of the search results box to the maximum width
resultsBox.style.width = window.navItemMaxWidth + "px"; resultsBox.style.width = window.navItemMaxWidth + 'px';
}); });

View File

@@ -1,33 +1,42 @@
// Get the download option from local storage, or set it to 'sameWindow' if it doesn't exist // Get the download option from local storage, or set it to 'sameWindow' if it doesn't exist
var downloadOption = localStorage.getItem("downloadOption") || "sameWindow"; var downloadOption = localStorage.getItem('downloadOption')
|| 'sameWindow';
// Set the selected option in the dropdown // Set the selected option in the dropdown
document.getElementById("downloadOption").value = downloadOption; document.getElementById('downloadOption').value = downloadOption;
// Save the selected option to local storage when the dropdown value changes // Save the selected option to local storage when the dropdown value changes
document.getElementById("downloadOption").addEventListener("change", function () { document.getElementById('downloadOption').addEventListener(
downloadOption = this.value; 'change',
localStorage.setItem("downloadOption", downloadOption); function() {
}); downloadOption = this.value;
localStorage.setItem('downloadOption',
downloadOption);
});
// Get the zipThreshold value from local storage, or set it to 0 if it doesn't exist // Get the zipThreshold value from local storage, or set it to 0 if it doesn't exist
var zipThreshold = parseInt(localStorage.getItem("zipThreshold"), 10) || 4; var zipThreshold = parseInt(localStorage.getItem('zipThreshold'), 10) || 4;
// Set the value of the slider and the display span // Set the value of the slider and the display span
document.getElementById("zipThreshold").value = zipThreshold; document.getElementById('zipThreshold').value = zipThreshold;
document.getElementById("zipThresholdValue").textContent = zipThreshold; document.getElementById('zipThresholdValue').textContent = zipThreshold;
// Save the selected value to local storage when the slider value changes // Save the selected value to local storage when the slider value changes
document.getElementById("zipThreshold").addEventListener("input", function () { document.getElementById('zipThreshold').addEventListener('input', function() {
zipThreshold = this.value; zipThreshold = this.value;
document.getElementById("zipThresholdValue").textContent = zipThreshold; document.getElementById('zipThresholdValue').textContent = zipThreshold;
localStorage.setItem("zipThreshold", zipThreshold); localStorage.setItem('zipThreshold', zipThreshold);
}); });
var boredWaiting = localStorage.getItem("boredWaiting") || "disabled";
document.getElementById("boredWaiting").checked = boredWaiting === "enabled";
document.getElementById("boredWaiting").addEventListener("change", function () { var boredWaiting = localStorage.getItem('boredWaiting') || 'disabled';
boredWaiting = this.checked ? "enabled" : "disabled"; document.getElementById('boredWaiting').checked = boredWaiting === 'enabled';
localStorage.setItem("boredWaiting", boredWaiting);
document.getElementById('boredWaiting').addEventListener('change', function() {
boredWaiting = this.checked ? 'enabled' : 'disabled';
localStorage.setItem('boredWaiting', boredWaiting);
}); });

View File

@@ -1,38 +1,39 @@
TabContainer = { TabContainer = {
initTabGroups() { initTabGroups() {
const groups = document.querySelectorAll(".tab-group"); const groups = document.querySelectorAll(".tab-group");
const unloadedGroups = [...groups].filter((g) => !g.initialised); const unloadedGroups = [...groups].filter(g => !g.initialised);
unloadedGroups.forEach((group) => { unloadedGroups.forEach(group => {
const containers = group.querySelectorAll(".tab-container"); const containers = group.querySelectorAll(".tab-container");
const tabTitles = [...containers].map((c) => c.getAttribute("title")); const tabTitles = [...containers].map(c => c.getAttribute("title"));
const tabList = document.createElement("div"); const tabList = document.createElement("div");
tabList.classList.add("tab-buttons"); tabList.classList.add("tab-buttons");
tabTitles.forEach((title) => { tabTitles.forEach(title => {
const tabButton = document.createElement("button"); const tabButton = document.createElement("button");
tabButton.innerHTML = title; tabButton.innerHTML = title;
tabButton.onclick = (e) => { tabButton.onclick = e => {
this.setActiveTab(e.target); this.setActiveTab(e.target);
}; }
tabList.appendChild(tabButton); tabList.appendChild(tabButton);
}); });
group.prepend(tabList); group.prepend(tabList);
this.setActiveTab(tabList.firstChild); this.setActiveTab(tabList.firstChild);
group.initialised = true; group.initialised = true;
}); });
}, },
setActiveTab(tabButton) { setActiveTab(tabButton) {
const group = tabButton.closest(".tab-group"); const group = tabButton.closest(".tab-group")
group.querySelectorAll(".active").forEach((el) => el.classList.remove("active")); group.querySelectorAll(".active").forEach(el => el.classList.remove("active"));
tabButton.classList.add("active"); tabButton.classList.add("active");
group.querySelector(`[title="${tabButton.innerHTML}"]`).classList.add("active"); group.querySelector(`[title="${tabButton.innerHTML}"]`).classList.add("active");
}, },
}; }
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
TabContainer.initTabGroups(); TabContainer.initTabGroups();
}); })

View File

@@ -116,6 +116,7 @@
top: 0; top: 0;
} }
:root { :root {
--annotation-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,<svg width='1px' height='1px' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' style='fill:rgba(0, 54, 255, 0.13);'/></svg>"); --annotation-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,<svg width='1px' height='1px' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' style='fill:rgba(0, 54, 255, 0.13);'/></svg>");
--input-focus-border-color: Highlight; --input-focus-border-color: Highlight;
@@ -138,7 +139,9 @@
.annotationLayer .textWidgetAnnotation :is(input, textarea):required, .annotationLayer .textWidgetAnnotation :is(input, textarea):required,
.annotationLayer .choiceWidgetAnnotation select:required, .annotationLayer .choiceWidgetAnnotation select:required,
.annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:required { .annotationLayer
.buttonWidgetAnnotation:is(.checkBox, .radioButton)
input:required {
outline: 1.5px solid selectedItem; outline: 1.5px solid selectedItem;
} }
@@ -225,7 +228,9 @@
height: 100%; height: 100%;
} }
.annotationLayer :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton):not(.hasBorder) > a:hover { .annotationLayer
:is(.linkAnnotation, .buttonWidgetAnnotation.pushButton):not(.hasBorder)
> a:hover {
opacity: 0.2; opacity: 0.2;
background-color: rgba(255, 255, 0, 1); background-color: rgba(255, 255, 0, 1);
box-shadow: 0 2px 10px rgba(255, 255, 0, 1); box-shadow: 0 2px 10px rgba(255, 255, 0, 1);
@@ -263,7 +268,9 @@
.annotationLayer .textWidgetAnnotation :is(input, textarea):required, .annotationLayer .textWidgetAnnotation :is(input, textarea):required,
.annotationLayer .choiceWidgetAnnotation select:required, .annotationLayer .choiceWidgetAnnotation select:required,
.annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:required { .annotationLayer
.buttonWidgetAnnotation:is(.checkBox, .radioButton)
input:required {
outline: 1.5px solid red; outline: 1.5px solid red;
} }
@@ -281,7 +288,9 @@
.annotationLayer .textWidgetAnnotation :is(input, textarea)[disabled], .annotationLayer .textWidgetAnnotation :is(input, textarea)[disabled],
.annotationLayer .choiceWidgetAnnotation select[disabled], .annotationLayer .choiceWidgetAnnotation select[disabled],
.annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input[disabled] { .annotationLayer
.buttonWidgetAnnotation:is(.checkBox, .radioButton)
input[disabled] {
background: none; background: none;
border: 2px solid var(--input-disabled-border-color); border: 2px solid var(--input-disabled-border-color);
cursor: not-allowed; cursor: not-allowed;
@@ -289,7 +298,9 @@
.annotationLayer .textWidgetAnnotation :is(input, textarea):hover, .annotationLayer .textWidgetAnnotation :is(input, textarea):hover,
.annotationLayer .choiceWidgetAnnotation select:hover, .annotationLayer .choiceWidgetAnnotation select:hover,
.annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:hover { .annotationLayer
.buttonWidgetAnnotation:is(.checkBox, .radioButton)
input:hover {
border: 2px solid var(--input-hover-border-color); border: 2px solid var(--input-hover-border-color);
} }
@@ -478,6 +489,7 @@
z-index: -1; z-index: -1;
} }
:root { :root {
--xfa-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,<svg width='1px' height='1px' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' style='fill:rgba(0, 54, 255, 0.13);'/></svg>"); --xfa-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,<svg width='1px' height='1px' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' style='fill:rgba(0, 54, 255, 0.13);'/></svg>");
--xfa-focus-outline: auto; --xfa-focus-outline: auto;
@@ -815,7 +827,10 @@
--freetext-padding: 2px; --freetext-padding: 2px;
--resizer-bg-color: var(--outline-color); --resizer-bg-color: var(--outline-color);
--resizer-size: 6px; --resizer-size: 6px;
--resizer-shift: calc(0px - (var(--outline-width) + var(--resizer-size)) / 2 - var(--outline-around-width)); --resizer-shift: calc(
0px - (var(--outline-width) + var(--resizer-size)) / 2 -
var(--outline-around-width)
);
--editorFreeText-editing-cursor: text; --editorFreeText-editing-cursor: text;
--editorInk-editing-cursor: url(../images/cursor-editorInk.svg) 0 16, pointer; --editorInk-editing-cursor: url(../images/cursor-editorInk.svg) 0 16, pointer;
@@ -838,7 +853,8 @@
@media (-webkit-min-device-pixel-ratio: 1.1), (min-resolution: 1.1dppx) { @media (-webkit-min-device-pixel-ratio: 1.1), (min-resolution: 1.1dppx) {
:root { :root {
--editorFreeText-editing-cursor: url(../images/cursor-editorFreeText.svg) 0 16, text; --editorFreeText-editing-cursor: url(../images/cursor-editorFreeText.svg) 0 16,
text;
} }
} }
@@ -1093,354 +1109,216 @@
} }
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.topLeft,
> .resizers
> .resizer.topLeft,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.topLeft,
> .resizers
> .resizer.topLeft,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.topLeft,
> .resizers
> .resizer.topLeft,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.topLeft,
> .resizers
> .resizer.topLeft,
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.bottomRight,
> .resizers
> .resizer.bottomRight,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.bottomRight,
> .resizers
> .resizer.bottomRight,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.bottomRight,
> .resizers
> .resizer.bottomRight,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.bottomRight {
> .resizers
> .resizer.bottomRight {
cursor: nwse-resize; cursor: nwse-resize;
} }
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.topMiddle,
> .resizers
> .resizer.topMiddle,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.topMiddle,
> .resizers
> .resizer.topMiddle,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.topMiddle,
> .resizers
> .resizer.topMiddle,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.topMiddle,
> .resizers
> .resizer.topMiddle,
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.bottomMiddle,
> .resizers
> .resizer.bottomMiddle,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.bottomMiddle,
> .resizers
> .resizer.bottomMiddle,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.bottomMiddle,
> .resizers
> .resizer.bottomMiddle,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.bottomMiddle {
> .resizers
> .resizer.bottomMiddle {
cursor: ns-resize; cursor: ns-resize;
} }
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.topRight,
> .resizers
> .resizer.topRight,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.topRight,
> .resizers
> .resizer.topRight,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.topRight,
> .resizers
> .resizer.topRight,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.topRight,
> .resizers
> .resizer.topRight,
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.bottomLeft,
> .resizers
> .resizer.bottomLeft,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.bottomLeft,
> .resizers
> .resizer.bottomLeft,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.bottomLeft,
> .resizers
> .resizer.bottomLeft,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.bottomLeft {
> .resizers
> .resizer.bottomLeft {
cursor: nesw-resize; cursor: nesw-resize;
} }
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.middleRight,
> .resizers
> .resizer.middleRight,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.middleRight,
> .resizers
> .resizer.middleRight,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.middleRight,
> .resizers
> .resizer.middleRight,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.middleRight,
> .resizers
> .resizer.middleRight,
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.middleLeft,
> .resizers
> .resizer.middleLeft,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.middleLeft,
> .resizers
> .resizer.middleLeft,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.middleLeft,
> .resizers
> .resizer.middleLeft,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.middleLeft {
> .resizers
> .resizer.middleLeft {
cursor: ew-resize; cursor: ew-resize;
} }
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.topLeft,
> .resizers
> .resizer.topLeft,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.topLeft,
> .resizers
> .resizer.topLeft,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.topLeft,
> .resizers
> .resizer.topLeft,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.topLeft,
> .resizers
> .resizer.topLeft,
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.bottomRight,
> .resizers
> .resizer.bottomRight,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.bottomRight,
> .resizers
> .resizer.bottomRight,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.bottomRight,
> .resizers
> .resizer.bottomRight,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.bottomRight {
> .resizers
> .resizer.bottomRight {
cursor: nesw-resize; cursor: nesw-resize;
} }
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.topMiddle,
> .resizers
> .resizer.topMiddle,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.topMiddle,
> .resizers
> .resizer.topMiddle,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.topMiddle,
> .resizers
> .resizer.topMiddle,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.topMiddle,
> .resizers
> .resizer.topMiddle,
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.bottomMiddle,
> .resizers
> .resizer.bottomMiddle,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.bottomMiddle,
> .resizers
> .resizer.bottomMiddle,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.bottomMiddle,
> .resizers
> .resizer.bottomMiddle,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.bottomMiddle {
> .resizers
> .resizer.bottomMiddle {
cursor: ew-resize; cursor: ew-resize;
} }
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.topRight,
> .resizers
> .resizer.topRight,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.topRight,
> .resizers
> .resizer.topRight,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.topRight,
> .resizers
> .resizer.topRight,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.topRight,
> .resizers
> .resizer.topRight,
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.bottomLeft,
> .resizers
> .resizer.bottomLeft,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.bottomLeft,
> .resizers
> .resizer.bottomLeft,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.bottomLeft,
> .resizers
> .resizer.bottomLeft,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.bottomLeft {
> .resizers
> .resizer.bottomLeft {
cursor: nwse-resize; cursor: nwse-resize;
} }
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.middleRight,
> .resizers
> .resizer.middleRight,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.middleRight,
> .resizers
> .resizer.middleRight,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.middleRight,
> .resizers
> .resizer.middleRight,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.middleRight,
> .resizers
> .resizer.middleRight,
.annotationEditorLayer[data-main-rotation="0"] .annotationEditorLayer[data-main-rotation="0"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.middleLeft,
> .resizers
> .resizer.middleLeft,
.annotationEditorLayer[data-main-rotation="90"] .annotationEditorLayer[data-main-rotation="90"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]) :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.middleLeft,
> .resizers
> .resizer.middleLeft,
.annotationEditorLayer[data-main-rotation="180"] .annotationEditorLayer[data-main-rotation="180"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]) :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.middleLeft,
> .resizers
> .resizer.middleLeft,
.annotationEditorLayer[data-main-rotation="270"] .annotationEditorLayer[data-main-rotation="270"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.middleLeft {
> .resizers
> .resizer.middleLeft {
cursor: ns-resize; cursor: ns-resize;
} }
.annotationEditorLayer .annotationEditorLayer
:is( :is(
[data-main-rotation="0"] [data-editor-rotation="90"], [data-main-rotation="0"] [data-editor-rotation="90"],
[data-main-rotation="90"] [data-editor-rotation="0"], [data-main-rotation="90"] [data-editor-rotation="0"],
[data-main-rotation="180"] [data-editor-rotation="270"], [data-main-rotation="180"] [data-editor-rotation="270"],
[data-main-rotation="270"] [data-editor-rotation="180"] [data-main-rotation="270"] [data-editor-rotation="180"]
) ) .altText {
.altText {
rotate: 270deg; rotate: 270deg;
} }
[dir="ltr"] [dir="ltr"] .annotationEditorLayer
.annotationEditorLayer :is(
:is( [data-main-rotation="0"] [data-editor-rotation="90"],
[data-main-rotation="0"] [data-editor-rotation="90"], [data-main-rotation="90"] [data-editor-rotation="0"],
[data-main-rotation="90"] [data-editor-rotation="0"], [data-main-rotation="180"] [data-editor-rotation="270"],
[data-main-rotation="180"] [data-editor-rotation="270"], [data-main-rotation="270"] [data-editor-rotation="180"]
[data-main-rotation="270"] [data-editor-rotation="180"] ) .altText {
)
.altText {
inset-inline-start: calc(100% - 8px); inset-inline-start: calc(100% - 8px);
} }
[dir="ltr"] [dir="ltr"] .annotationEditorLayer
.annotationEditorLayer :is(
:is( [data-main-rotation="0"] [data-editor-rotation="90"],
[data-main-rotation="0"] [data-editor-rotation="90"], [data-main-rotation="90"] [data-editor-rotation="0"],
[data-main-rotation="90"] [data-editor-rotation="0"], [data-main-rotation="180"] [data-editor-rotation="270"],
[data-main-rotation="180"] [data-editor-rotation="270"], [data-main-rotation="270"] [data-editor-rotation="180"]
[data-main-rotation="270"] [data-editor-rotation="180"] ) .altText.small {
)
.altText.small {
inset-inline-start: calc(100% + 8px); inset-inline-start: calc(100% + 8px);
inset-block-start: 100%; inset-block-start: 100%;
} }
[dir="rtl"] [dir="rtl"] .annotationEditorLayer
.annotationEditorLayer :is(
:is( [data-main-rotation="0"] [data-editor-rotation="90"],
[data-main-rotation="0"] [data-editor-rotation="90"], [data-main-rotation="90"] [data-editor-rotation="0"],
[data-main-rotation="90"] [data-editor-rotation="0"], [data-main-rotation="180"] [data-editor-rotation="270"],
[data-main-rotation="180"] [data-editor-rotation="270"], [data-main-rotation="270"] [data-editor-rotation="180"]
[data-main-rotation="270"] [data-editor-rotation="180"] ) .altText {
)
.altText {
inset-block-end: calc(100% - 8px); inset-block-end: calc(100% - 8px);
} }
[dir="rtl"] [dir="rtl"] .annotationEditorLayer
.annotationEditorLayer :is(
:is( [data-main-rotation="0"] [data-editor-rotation="90"],
[data-main-rotation="0"] [data-editor-rotation="90"], [data-main-rotation="90"] [data-editor-rotation="0"],
[data-main-rotation="90"] [data-editor-rotation="0"], [data-main-rotation="180"] [data-editor-rotation="270"],
[data-main-rotation="180"] [data-editor-rotation="270"], [data-main-rotation="270"] [data-editor-rotation="180"]
[data-main-rotation="270"] [data-editor-rotation="180"] ) .altText.small {
)
.altText.small {
inset-inline-start: -8px; inset-inline-start: -8px;
inset-block-start: 0; inset-block-start: 0;
} }
.annotationEditorLayer .annotationEditorLayer
:is( :is(
[data-main-rotation="0"] [data-editor-rotation="180"], [data-main-rotation="0"] [data-editor-rotation="180"],
[data-main-rotation="90"] [data-editor-rotation="90"], [data-main-rotation="90"] [data-editor-rotation="90"],
[data-main-rotation="180"] [data-editor-rotation="0"], [data-main-rotation="180"] [data-editor-rotation="0"],
[data-main-rotation="270"] [data-editor-rotation="270"] [data-main-rotation="270"] [data-editor-rotation="270"]
) ) .altText {
.altText {
rotate: 180deg; rotate: 180deg;
inset-block-end: calc(100% - 8px); inset-block-end: calc(100% - 8px);
@@ -1448,74 +1326,64 @@
} }
.annotationEditorLayer .annotationEditorLayer
:is( :is(
[data-main-rotation="0"] [data-editor-rotation="180"], [data-main-rotation="0"] [data-editor-rotation="180"],
[data-main-rotation="90"] [data-editor-rotation="90"], [data-main-rotation="90"] [data-editor-rotation="90"],
[data-main-rotation="180"] [data-editor-rotation="0"], [data-main-rotation="180"] [data-editor-rotation="0"],
[data-main-rotation="270"] [data-editor-rotation="270"] [data-main-rotation="270"] [data-editor-rotation="270"]
) ) .altText.small {
.altText.small {
inset-inline-start: 100%; inset-inline-start: 100%;
inset-block-start: -8px; inset-block-start: -8px;
} }
.annotationEditorLayer .annotationEditorLayer
:is( :is(
[data-main-rotation="0"] [data-editor-rotation="270"], [data-main-rotation="0"] [data-editor-rotation="270"],
[data-main-rotation="90"] [data-editor-rotation="180"], [data-main-rotation="90"] [data-editor-rotation="180"],
[data-main-rotation="180"] [data-editor-rotation="90"], [data-main-rotation="180"] [data-editor-rotation="90"],
[data-main-rotation="270"] [data-editor-rotation="0"] [data-main-rotation="270"] [data-editor-rotation="0"]
) ) .altText {
.altText {
rotate: 90deg; rotate: 90deg;
} }
[dir="ltr"] [dir="ltr"] .annotationEditorLayer
.annotationEditorLayer :is(
:is( [data-main-rotation="0"] [data-editor-rotation="270"],
[data-main-rotation="0"] [data-editor-rotation="270"], [data-main-rotation="90"] [data-editor-rotation="180"],
[data-main-rotation="90"] [data-editor-rotation="180"], [data-main-rotation="180"] [data-editor-rotation="90"],
[data-main-rotation="180"] [data-editor-rotation="90"], [data-main-rotation="270"] [data-editor-rotation="0"]
[data-main-rotation="270"] [data-editor-rotation="0"] ) .altText {
)
.altText {
inset-block-end: calc(100% - 8px); inset-block-end: calc(100% - 8px);
} }
[dir="ltr"] [dir="ltr"] .annotationEditorLayer
.annotationEditorLayer :is(
:is( [data-main-rotation="0"] [data-editor-rotation="270"],
[data-main-rotation="0"] [data-editor-rotation="270"], [data-main-rotation="90"] [data-editor-rotation="180"],
[data-main-rotation="90"] [data-editor-rotation="180"], [data-main-rotation="180"] [data-editor-rotation="90"],
[data-main-rotation="180"] [data-editor-rotation="90"], [data-main-rotation="270"] [data-editor-rotation="0"]
[data-main-rotation="270"] [data-editor-rotation="0"] ) .altText.small {
)
.altText.small {
inset-inline-start: -8px; inset-inline-start: -8px;
inset-block-start: 0; inset-block-start: 0;
} }
[dir="rtl"] [dir="rtl"] .annotationEditorLayer
.annotationEditorLayer :is(
:is( [data-main-rotation="0"] [data-editor-rotation="270"],
[data-main-rotation="0"] [data-editor-rotation="270"], [data-main-rotation="90"] [data-editor-rotation="180"],
[data-main-rotation="90"] [data-editor-rotation="180"], [data-main-rotation="180"] [data-editor-rotation="90"],
[data-main-rotation="180"] [data-editor-rotation="90"], [data-main-rotation="270"] [data-editor-rotation="0"]
[data-main-rotation="270"] [data-editor-rotation="0"] ) .altText {
)
.altText {
inset-inline-start: calc(100% - 8px); inset-inline-start: calc(100% - 8px);
} }
[dir="rtl"] [dir="rtl"] .annotationEditorLayer
.annotationEditorLayer :is(
:is( [data-main-rotation="0"] [data-editor-rotation="270"],
[data-main-rotation="0"] [data-editor-rotation="270"], [data-main-rotation="90"] [data-editor-rotation="180"],
[data-main-rotation="90"] [data-editor-rotation="180"], [data-main-rotation="180"] [data-editor-rotation="90"],
[data-main-rotation="180"] [data-editor-rotation="90"], [data-main-rotation="270"] [data-editor-rotation="0"]
[data-main-rotation="270"] [data-editor-rotation="0"] ) .altText.small {
)
.altText.small {
inset-inline-start: calc(100% + 8px); inset-inline-start: calc(100% + 8px);
inset-block-start: 100%; inset-block-start: 100%;
} }
@@ -1553,6 +1421,7 @@
} }
.altText.small { .altText.small {
inset-block-end: unset; inset-block-end: unset;
inset-inline-start: 0; inset-inline-start: 0;
inset-block-start: calc(100% + 8px); inset-block-start: calc(100% + 8px);
@@ -1646,6 +1515,7 @@
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.altText .tooltip.show { .altText .tooltip.show {
--alt-text-tooltip-bg: #1c1b22; --alt-text-tooltip-bg: #1c1b22;
--alt-text-tooltip-fg: #fbfbfe; --alt-text-tooltip-fg: #fbfbfe;
@@ -1654,6 +1524,7 @@
} }
@media screen and (forced-colors: active) { @media screen and (forced-colors: active) {
.altText .tooltip.show { .altText .tooltip.show {
--alt-text-tooltip-bg: Canvas; --alt-text-tooltip-bg: Canvas;
--alt-text-tooltip-fg: CanvasText; --alt-text-tooltip-fg: CanvasText;
@@ -1710,6 +1581,7 @@
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
#altTextDialog { #altTextDialog {
--dialog-bg-color: #1c1b22; --dialog-bg-color: #1c1b22;
--dialog-border-color: #1c1b22; --dialog-border-color: #1c1b22;
@@ -1732,6 +1604,7 @@
} }
@media screen and (forced-colors: active) { @media screen and (forced-colors: active) {
#altTextDialog { #altTextDialog {
--dialog-bg-color: Canvas; --dialog-bg-color: Canvas;
--dialog-border-color: CanvasText; --dialog-border-color: CanvasText;
@@ -2149,8 +2022,8 @@
--toolbar-border-color: rgba(184, 184, 184, 1); --toolbar-border-color: rgba(184, 184, 184, 1);
--toolbar-box-shadow: 0 1px 0 var(--toolbar-border-color); --toolbar-box-shadow: 0 1px 0 var(--toolbar-border-color);
--toolbar-border-bottom: none; --toolbar-border-bottom: none;
--toolbarSidebar-box-shadow: inset calc(-1px * var(--dir-factor)) 0 0 rgba(0, 0, 0, 0.25), 0 1px 0 rgba(0, 0, 0, 0.15), --toolbarSidebar-box-shadow: inset calc(-1px * var(--dir-factor)) 0 0 rgba(0, 0, 0, 0.25),
0 0 1px rgba(0, 0, 0, 0.1); 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1);
--toolbarSidebar-border-bottom: none; --toolbarSidebar-border-bottom: none;
--button-hover-color: rgba(221, 222, 223, 1); --button-hover-color: rgba(221, 222, 223, 1);
--toggled-btn-color: rgba(0, 0, 0, 1); --toggled-btn-color: rgba(0, 0, 0, 1);
@@ -2446,7 +2319,8 @@ body {
font: message-box; font: message-box;
} }
:is(.toolbar, .editorParamsToolbar, .findbar, #sidebarContainer) :is(input, button, select), :is(.toolbar, .editorParamsToolbar, .findbar, #sidebarContainer)
:is(input, button, select),
.secondaryToolbar :is(input, button, a, select) { .secondaryToolbar :is(input, button, a, select) {
outline: none; outline: none;
font: message-box; font: message-box;
@@ -2543,18 +2417,19 @@ body {
height: 100%; height: 100%;
width: calc(100% + 150px); width: calc(100% + 150px);
background: repeating-linear-gradient( background: repeating-linear-gradient(
135deg, 135deg,
var(--progressBar-blend-color) 0, var(--progressBar-blend-color) 0,
var(--progressBar-bg-color) 5px, var(--progressBar-bg-color) 5px,
var(--progressBar-bg-color) 45px, var(--progressBar-bg-color) 45px,
var(--progressBar-color) 55px, var(--progressBar-color) 55px,
var(--progressBar-color) 95px, var(--progressBar-color) 95px,
var(--progressBar-blend-color) 100px var(--progressBar-blend-color) 100px
); );
animation: progressIndeterminate 1s linear infinite; animation: progressIndeterminate 1s linear infinite;
} }
#outerContainer.sidebarResizing :is(#sidebarContainer, #viewerContainer, #loadingBar) { #outerContainer.sidebarResizing
:is(#sidebarContainer, #viewerContainer, #loadingBar) {
/* Improve responsiveness and avoid visual glitches when the sidebar is resized. */ /* Improve responsiveness and avoid visual glitches when the sidebar is resized. */
transition-duration: 0s; transition-duration: 0s;
} }
@@ -2725,9 +2600,8 @@ body {
.doorHanger, .doorHanger,
.doorHangerRight { .doorHangerRight {
border-radius: 2px; border-radius: 2px;
box-shadow: box-shadow: 0 1px 5px var(--doorhanger-border-color),
0 1px 5px var(--doorhanger-border-color), 0 0 0 1px var(--doorhanger-border-color);
0 0 0 1px var(--doorhanger-border-color);
border: var(--doorhanger-border-color-whcm); border: var(--doorhanger-border-color-whcm);
} }
@@ -3581,7 +3455,8 @@ dialog :link {
cursor: grab !important; cursor: grab !important;
} }
.grab-to-pan-grab *:not(input):not(textarea):not(button):not(select):not(:link) { .grab-to-pan-grab
*:not(input):not(textarea):not(button):not(select):not(:link) {
cursor: inherit !important; cursor: inherit !important;
} }

View File

@@ -3323,13 +3323,13 @@
kind: OptionKind.WORKER kind: OptionKind.WORKER
}, },
workerSrc: { workerSrc: {
value: "./pdfjs/pdf.worker.js", value: "/pdfjs/pdf.worker.js",
kind: OptionKind.WORKER kind: OptionKind.WORKER
} }
}; };
{ {
defaultOptions.defaultUrl = { defaultOptions.defaultUrl = {
value: "./pdfjs/example/Welcome.pdf", value: "/pdfjs/example/Welcome.pdf",
kind: OptionKind.VIEWER kind: OptionKind.VIEWER
}; };
defaultOptions.disablePreferences = { defaultOptions.disablePreferences = {

View File

@@ -1,21 +1,22 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" th:language="${#locale.toString()}" xmlns:th="http://www.thymeleaf.org"> <html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title='<3')}"></th:block> <th:block th:insert="~{fragments/common :: head(title='<3')}"></th:block>
</head>
<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">
<body>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br /><br />
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6"></div>
</div>
</div> </div>
</div> </div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html> </html>

View File

@@ -1,286 +1,326 @@
<!DOCTYPE html> <!doctype html>
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" th:language="${#locale.toString()}" xmlns:th="http://www.thymeleaf.org"> <html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{account.title})}"></th:block>
</head>
<body> <th:block th:insert="~{fragments/common :: head(title=#{account.title}, header=#{account.header})}"></th:block>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block> <th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br /><br /> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-9"> <div class="col-md-9">
<!-- User Settings Title --> <!-- User Settings Title -->
<h2 class="text-center" th:text="#{account.accountSettings}">User Settings</h2> <h2 class="text-center" th:text="#{account.accountSettings}">User Settings</h2>
<hr /> <hr>
<th:block th:if="${param.messageType != null and param.messageType.size() > 0}"> <div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'notAuthenticated'}" class="alert alert-danger">
<div th:if="${param.messageType[0] == 'notAuthenticated'}" class="alert alert-danger"> <span th:text="#{notAuthenticatedMessage}">Default message if not found</span>
<span th:text="#{notAuthenticatedMessage}">Default message if not found</span> </div>
</div> <div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'userNotFound'}" class="alert alert-danger">
<div th:if="${param.messageType[0] == 'userNotFound'}" class="alert alert-danger"> <span th:text="#{userNotFoundMessage}">Default message if not found</span>
<span th:text="#{userNotFoundMessage}">Default message if not found</span> </div>
</div> <div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'incorrectPassword'}" class="alert alert-danger">
<div th:if="${param.messageType[0] == 'incorrectPassword'}" class="alert alert-danger"> <span th:text="#{incorrectPasswordMessage}">Default message if not found</span>
<span th:text="#{incorrectPasswordMessage}">Default message if not found</span> </div>
</div> <div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'usernameExists'}" class="alert alert-danger">
<div th:if="${param.messageType[0] == 'usernameExists'}" class="alert alert-danger"> <span th:text="#{usernameExistsMessage}">Default message if not found</span>
<span th:text="#{usernameExistsMessage}">Default message if not found</span> </div>
</div>
</th:block>
<!-- At the top of the user settings -->
<h3 class="text-center"><span th:text="#{welcome} + ' ' + ${username}">User</span>!</h3>
<th:block th:if="${error}">
<div class="alert alert-danger" role="alert">
<span th:text="${error}">Error Message</span>
</div>
</th:block>
<!-- Change Username Form -->
<h4></h4>
<form action="api/v1/user/change-username" method="post">
<div class="mb-3">
<label for="newUsername" th:text="#{account.changeUsername}">Change Username</label>
<input type="text" class="form-control" name="newUsername" id="newUsername" th:placeholder="#{account.newUsername}">
</div>
<div class="mb-3">
<label for="currentPassword" th:text="#{password}">Password</label>
<input type="password" class="form-control" name="currentPassword" id="currentPasswordUsername" th:placeholder="#{password}">
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary" th:text="#{account.changeUsername}">Change Username</button>
</div>
</form>
<hr /> <!-- Separator Line -->
<!-- Change Password Form -->
<h4 th:text="#{account.changePassword}">Change Password?</h4>
<form action="api/v1/user/change-password" method="post">
<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="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="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="mb-3">
<button type="submit" class="btn btn-primary" th:text="#{account.changePassword}">Change Password</button>
</div>
</form>
<hr />
<div class="card">
<div class="card-header" th:text="#{account.yourApiKey}"></div>
<div class="card-body">
<div class="input-group mb-3">
<input type="password" class="form-control" id="apiKey" th:placeholder="#{account.yourApiKey}" readonly>
<div class="input-group-append">
<button class="btn btn-outline-secondary" id="copyBtn" type="button" onclick="copyToClipboard()">
<img class="blackwhite-icon" src="images/clipboard.svg" alt="Copy" style="height:20px;">
</button>
<button class="btn btn-outline-secondary" id="showBtn" type="button" onclick="showApiKey()">
<img class="blackwhite-icon" id="eyeIcon" src="images/eye.svg" alt="Toggle API Key Visibility" style="height:20px;">
</button>
<button class="btn btn-outline-secondary" id="refreshBtn" type="button" onclick="refreshApiKey()">
<img class="blackwhite-icon" id="eyeIcon" src="images/arrow-clockwise.svg" alt="Refresh API-Key" style="height:20px;">
</button>
</div>
</div>
</div>
</div>
<script>
function copyToClipboard() {
const apiKeyElement = document.getElementById("apiKey");
apiKeyElement.select();
document.execCommand("copy");
}
function showApiKey() {
const apiKeyElement = document.getElementById("apiKey");
const copyBtn = document.getElementById("copyBtn");
const eyeIcon = document.getElementById("eyeIcon");
if (apiKeyElement.type === "password") {
apiKeyElement.type = "text";
eyeIcon.src = "images/eye-slash.svg";
copyBtn.disabled = false; // Enable copy button when API key is visible
} else {
apiKeyElement.type = "password";
eyeIcon.src = "images/eye.svg";
copyBtn.disabled = true; // Disable copy button when API key is hidden
}
}
document.addEventListener("DOMContentLoaded", async function() {
try {
let response = await fetch('/api/v1/user/get-api-key', { method: 'POST' });
if (response.status === 200) {
let apiKey = await response.text();
manageUIState(apiKey);
} else {
manageUIState(null);
}
} catch (error) {
console.error('There was an error:', error);
}
});
async function refreshApiKey() {
try {
let response = await fetch('/api/v1/user/update-api-key', { method: 'POST' });
if (response.status === 200) {
let apiKey = await response.text();
manageUIState(apiKey);
document.getElementById("apiKey").type = 'text';
document.getElementById("copyBtn").disabled = false;
} else {
alert('Error refreshing API key.');
}
} catch (error) {
console.error('There was an error:', error);
}
}
function manageUIState(apiKey) {
const apiKeyElement = document.getElementById("apiKey");
const showBtn = document.getElementById("showBtn");
const copyBtn = document.getElementById("copyBtn");
if (apiKey && apiKey.trim().length > 0) {
apiKeyElement.value = apiKey;
showBtn.disabled = false;
copyBtn.disabled = true;
} else {
apiKeyElement.value = "";
showBtn.disabled = true;
copyBtn.disabled = true;
}
}
document.addEventListener("DOMContentLoaded", function() {
const form = document.querySelector('form[action="api/v1/user/change-password"]');
form.addEventListener('submit', function(event) {
const newPassword = document.getElementById('newPassword').value;
const confirmNewPassword = document.getElementById('confirmNewPassword').value;
if (newPassword !== confirmNewPassword) {
alert('New Password and Confirm New Password must match.');
event.preventDefault(); // Prevent form submission
}
});
});
</script>
<hr /> <!-- Separator Line -->
<h4 th:text="#{account.syncTitle}">Sync browser settings with Account</h4> <!-- At the top of the user settings -->
<div class="container mt-4"> <h3 class="text-center"><span th:text="#{welcome} + ' ' + ${username}">User</span>!</h3>
<h3 th:text="#{account.settingsCompare}">Settings Comparison:</h3>
<table id="settingsTable" class="table table-bordered table-sm table-striped">
<thead>
<tr>
<th th:text="#{account.property}">Property</th>
<th th:text="#{account.accountSettings}">Account Setting</th>
<th th:text="#{account.webBrowserSettings}">Web Browser Setting</th>
</tr>
</thead>
<tbody>
<!-- This will be dynamically populated by JavaScript -->
</tbody>
</table>
<div class="buttons-container mt-3 text-center"> <div th:if="${error}" class="alert alert-danger" role="alert">
<button id="syncToBrowser" class="btn btn-primary btn-sm" th:text="#{account.syncToBrowser}">Sync Account -> Browser</button> <span th:text="${error}">Error Message</span>
<button id="syncToAccount" class="btn btn-secondary btn-sm" th:text="#{account.syncToAccount}">Sync Account <- Browser</button> </div>
</div> <!-- Change Username Form -->
</div> <h4></h4>
<form action="api/v1/user/change-username" method="post">
<div class="mb-3">
<label for="newUsername" th:text="#{account.changeUsername}">Change Username</label>
<input type="text" class="form-control" name="newUsername" id="newUsername" th:placeholder="#{account.newUsername}">
</div>
<div class="mb-3">
<label for="currentPassword" th:text="#{password}">Password</label>
<input type="password" class="form-control" name="currentPassword" id="currentPasswordUsername" th:placeholder="#{password}">
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary" th:text="#{account.changeUsername}">Change Username</button>
</div>
</form>
<script th:inline="javascript"> <hr> <!-- Separator Line -->
document.addEventListener("DOMContentLoaded", function() {
const settingsTableBody = document.querySelector("#settingsTable tbody");
/*<![CDATA[*/ <!-- Change Password Form -->
var accountSettingsString = /*[[${settings}]]*/ {}; <h4 th:text="#{account.changePassword}">Change Password?</h4>
/*]]>*/ <form action="api/v1/user/change-password" method="post">
var accountSettings = JSON.parse(accountSettingsString); <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="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="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="mb-3">
<button type="submit" class="btn btn-primary" th:text="#{account.changePassword}">Change Password</button>
</div>
</form>
let allKeys = new Set([...Object.keys(accountSettings), ...Object.keys(localStorage)]); <hr>
allKeys.forEach(key => { <div class="card">
if(key === 'debug' || key === '0' || key === '1') return; // Ignoring specific keys <div class="card-header" th:text="#{account.yourApiKey}">
const accountValue = accountSettings[key] || '-'; </div>
const browserValue = localStorage.getItem(key) || '-'; <div class="card-body">
<div class="input-group mb-3">
<input type="password" class="form-control" id="apiKey" th:placeholder="#{account.yourApiKey}" readonly>
<div class="input-group-append">
<button class="btn btn-outline-secondary" id="copyBtn" type="button" onclick="copyToClipboard()">
<img class="blackwhite-icon" src="images/clipboard.svg" alt="Copy" style="height:20px;">
</button>
<button class="btn btn-outline-secondary" id="showBtn" type="button" onclick="showApiKey()">
<img class="blackwhite-icon" id="eyeIcon" src="images/eye.svg" alt="Toggle API Key Visibility" style="height:20px;">
</button>
<button class="btn btn-outline-secondary" id="refreshBtn" type="button" onclick="refreshApiKey()">
<img class="blackwhite-icon" id="eyeIcon" src="images/arrow-clockwise.svg" alt="Refresh API-Key" style="height:20px;">
</button>
const row = settingsTableBody.insertRow();
const propertyCell = row.insertCell(0);
const accountCell = row.insertCell(1);
const browserCell = row.insertCell(2);
propertyCell.textContent = key; </div>
accountCell.textContent = accountValue; </div>
browserCell.textContent = browserValue; </div>
}); </div>
document.getElementById('syncToBrowser').addEventListener('click', function() { <script>
// First, clear the local storage function copyToClipboard() {
localStorage.clear(); const apiKeyElement = document.getElementById("apiKey");
apiKeyElement.select();
document.execCommand("copy");
}
// Then, set the account settings to local storage
for (let key in accountSettings) {
if(key !== 'debug' && key !== '0' && key !== '1') { // Only sync non-ignored keys
localStorage.setItem(key, accountSettings[key]);
}
}
location.reload(); // Refresh the page after sync
});
document.getElementById('syncToAccount').addEventListener('click', function() { function showApiKey() {
let form = document.createElement("form"); const apiKeyElement = document.getElementById("apiKey");
form.method = "POST"; const copyBtn = document.getElementById("copyBtn");
form.action = "api/v1/user/updateUserSettings"; // Your endpoint URL const eyeIcon = document.getElementById("eyeIcon");
if (apiKeyElement.type === "password") {
apiKeyElement.type = "text";
eyeIcon.src = "images/eye-slash.svg";
copyBtn.disabled = false; // Enable copy button when API key is visible
} else {
apiKeyElement.type = "password";
eyeIcon.src = "images/eye.svg";
copyBtn.disabled = true; // Disable copy button when API key is hidden
}
}
for (let i = 0; i < localStorage.length; i++) { document.addEventListener("DOMContentLoaded", async function() {
const key = localStorage.key(i); try {
if(key !== 'debug' && key !== '0' && key !== '1') { // Only send non-ignored keys let response = await fetch('/api/v1/user/get-api-key', { method: 'POST' });
let hiddenField = document.createElement("input"); if (response.status === 200) {
hiddenField.type = "hidden"; let apiKey = await response.text();
hiddenField.name = key; manageUIState(apiKey);
hiddenField.value = localStorage.getItem(key); } else {
form.appendChild(hiddenField); manageUIState(null);
} }
} } catch (error) {
console.error('There was an error:', error);
}
});
document.body.appendChild(form); async function refreshApiKey() {
form.submit(); try {
}); let response = await fetch('/api/v1/user/update-api-key', { method: 'POST' });
if (response.status === 200) {
let apiKey = await response.text();
manageUIState(apiKey);
document.getElementById("apiKey").type = 'text';
document.getElementById("copyBtn").disabled = false;
} else {
alert('Error refreshing API key.');
}
} catch (error) {
console.error('There was an error:', error);
}
}
}); function manageUIState(apiKey) {
</script> const apiKeyElement = document.getElementById("apiKey");
<div class="mb-3 mt-4"> const showBtn = document.getElementById("showBtn");
<a href="logout"> const copyBtn = document.getElementById("copyBtn");
<button type="button" class="btn btn-danger" th:text="#{account.signOut}">Sign Out</button>
</a> if (apiKey && apiKey.trim().length > 0) {
<a th:if="${role == 'ROLE_ADMIN'}" href="addUsers" target="_blank"> apiKeyElement.value = apiKey;
<button type="button" class="btn btn-info" th:text="#{account.adminSettings}">Admin Settings</button> showBtn.disabled = false;
</a> copyBtn.disabled = true;
</div> } else {
</div> apiKeyElement.value = "";
</div> showBtn.disabled = true;
</div> copyBtn.disabled = true;
</div> }
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> }
document.addEventListener("DOMContentLoaded", function() {
const form = document.querySelector('form[action="api/v1/user/change-password"]');
form.addEventListener('submit', function(event) {
const newPassword = document.getElementById('newPassword').value;
const confirmNewPassword = document.getElementById('confirmNewPassword').value;
if (newPassword !== confirmNewPassword) {
alert('New Password and Confirm New Password must match.');
event.preventDefault(); // Prevent form submission
}
});
});
</script>
<hr> <!-- Separator Line -->
<h4 th:text="#{account.syncTitle}">Sync browser settings with Account</h4>
<div class="container mt-4">
<h3 th:text="#{account.settingsCompare}">Settings Comparison:</h3>
<table id="settingsTable" class="table table-bordered table-sm table-striped">
<thead>
<tr>
<th th:text="#{account.property}">Property</th>
<th th:text="#{account.accountSettings}">Account Setting</th>
<th th:text="#{account.webBrowserSettings}">Web Browser Setting</th>
</tr>
</thead>
<tbody>
<!-- This will be dynamically populated by JavaScript -->
</tbody>
</table>
<div class="buttons-container mt-3 text-center">
<button id="syncToBrowser" class="btn btn-primary btn-sm" th:text="#{account.syncToBrowser}">Sync Account -> Browser</button>
<button id="syncToAccount" class="btn btn-secondary btn-sm" th:text="#{account.syncToAccount}">Sync Account <- Browser</button>
</div> </div>
</body> </div>
<style>
.container {
width: 100%;
max-width: 800px;
margin: 0 auto;
}
.buttons-container {
margin-top: 20px;
text-align: center;
}
</style>
<script th:inline="javascript">
document.addEventListener("DOMContentLoaded", function() {
const settingsTableBody = document.querySelector("#settingsTable tbody");
/*<![CDATA[*/
var accountSettingsString = /*[[${settings}]]*/ {};
/*]]>*/
var accountSettings = JSON.parse(accountSettingsString);
let allKeys = new Set([...Object.keys(accountSettings), ...Object.keys(localStorage)]);
allKeys.forEach(key => {
if(key === 'debug' || key === '0' || key === '1') return; // Ignoring specific keys
const accountValue = accountSettings[key] || '-';
const browserValue = localStorage.getItem(key) || '-';
const row = settingsTableBody.insertRow();
const propertyCell = row.insertCell(0);
const accountCell = row.insertCell(1);
const browserCell = row.insertCell(2);
propertyCell.textContent = key;
accountCell.textContent = accountValue;
browserCell.textContent = browserValue;
});
document.getElementById('syncToBrowser').addEventListener('click', function() {
// First, clear the local storage
localStorage.clear();
// Then, set the account settings to local storage
for (let key in accountSettings) {
if(key !== 'debug' && key !== '0' && key !== '1') { // Only sync non-ignored keys
localStorage.setItem(key, accountSettings[key]);
}
}
location.reload(); // Refresh the page after sync
});
document.getElementById('syncToAccount').addEventListener('click', function() {
let form = document.createElement("form");
form.method = "POST";
form.action = "api/v1/user/updateUserSettings"; // Your endpoint URL
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if(key !== 'debug' && key !== '0' && key !== '1') { // Only send non-ignored keys
let hiddenField = document.createElement("input");
hiddenField.type = "hidden";
hiddenField.name = key;
hiddenField.value = localStorage.getItem(key);
form.appendChild(hiddenField);
}
}
document.body.appendChild(form);
form.submit();
});
});
</script>
<div class="mb-3 mt-4">
<a href="logout">
<button type="button" class="btn btn-danger" th:text="#{account.signOut}">Sign Out</button>
</a>
<a th:if="${role == 'ROLE_ADMIN'}" href="addUsers" target="_blank">
<button type="button" class="btn btn-info" th:text="#{account.adminSettings}">Admin Settings</button>
</a>
</div>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html> </html>

View File

@@ -1,78 +1,84 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" th:language="${#locale.toString()}" xmlns:th="http://www.thymeleaf.org"> <html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{adminUserSettings.title}, header=#{adminUserSettings.header})}"></th:block>
</head>
<body> <th:block th:insert="~{fragments/common :: head(title=#{adminUserSettings.title}, header=#{adminUserSettings.header})}"></th:block>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block> <th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br /><br /> <br> <br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-8"> <div class="col-md-8">
<!-- User Settings Title --> <!-- User Settings Title -->
<h2 class="text-center" th:text="#{adminUserSettings.header}">Admin User Control Settings</h2> <h2 class="text-center" th:text="#{adminUserSettings.header}">Admin User Control Settings</h2>
<table class="table">
<thead>
<tr>
<th th:text="#{username}">Username</th>
<th th:text="#{adminUserSettings.roles}">Roles</th>
<th th:text="#{adminUserSettings.actions}">Actions</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.username}"></td>
<td th:text="${user.getRolesAsString()}"></td>
<td>
<form th:if="${user.username != currentUsername}" th:action="@{'/api/v1/user/admin/deleteUser/' + ${user.username}}" method="post">
<button type="submit" th:text="#{delete}">Delete</button>
</form>
</td>
</tr>
</tbody>
</table>
<h2 th:text="#{adminUserSettings.addUser}">Add New User</h2>
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'usernameExists'}" class="alert alert-danger">
<span th:text="#{usernameExistsMessage}">Default message if not found</span>
</div>
<form action="/api/v1/user/admin/saveUser" method="post">
<div class="mb-3">
<label for="username" th:text="#{username}">Username</label>
<input type="text" class="form-control" name="username" required>
</div>
<div class="mb-3">
<label for="password" th:text="#{password}">Password</label>
<input type="password" class="form-control" name="password" required>
</div>
<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>
<option value="ROLE_USER" th:text="#{adminUserSettings.user}">User</option>
<option value="ROLE_LIMITED_API_USER" th:text="#{adminUserSettings.apiUser}">Limited API User</option>
<option value="ROLE_WEB_ONLY_USER" th:text="#{adminUserSettings.webOnlyUser}">Web Only User</option>
<option value="ROLE_DEMO_USER" th:text="#{adminUserSettings.demoUser}">Demo User</option>
</select>
</div>
<div class="mb-3">
<input type="checkbox" class="form-check-input" id="forceChange" name="forceChange">
<label class="form-check-label" for="forceChange" th:text="#{adminUserSettings.forceChange}">Force user to change username/password on login</label>
</div>
<!-- Add other fields as required -->
<button type="submit" class="btn btn-primary" th:text="#{adminUserSettings.submit}">Save User</button>
</form> <table class="table">
<thead>
<tr>
<th th:text="#{username}">Username</th>
<th th:text="#{adminUserSettings.roles}">Roles</th>
<th th:text="#{adminUserSettings.actions}">Actions</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.username}"></td>
<td th:text="${user.getRolesAsString()}"></td>
<td>
<form th:if="${user.username != currentUsername}" th:action="@{'/api/v1/user/admin/deleteUser/' + ${user.username}}" method="post">
<button type="submit" th:text="#{delete}">Delete</button>
</form>
</td>
</tr>
</tbody>
</table>
<h2 th:text="#{adminUserSettings.addUser}">Add New User</h2>
<div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'usernameExists'}" class="alert alert-danger">
<span th:text="#{usernameExistsMessage}">Default message if not found</span>
</div>
<form action="/api/v1/user/admin/saveUser" method="post">
<div class="mb-3">
<label for="username" th:text="#{username}">Username</label>
<input type="text" class="form-control" name="username" required>
</div>
<div class="mb-3">
<label for="password" th:text="#{password}">Password</label>
<input type="password" class="form-control" name="password" required>
</div>
<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>
<option value="ROLE_USER" th:text="#{adminUserSettings.user}">User</option>
<option value="ROLE_LIMITED_API_USER" th:text="#{adminUserSettings.apiUser}">Limited API User</option>
<option value="ROLE_WEB_ONLY_USER" th:text="#{adminUserSettings.webOnlyUser}">Web Only User</option>
<option value="ROLE_DEMO_USER" th:text="#{adminUserSettings.demoUser}">Demo User</option>
</select>
</div>
<div class="mb-3">
<input type="checkbox" class="form-check-input" id="forceChange" name="forceChange">
<label class="form-check-label" for="forceChange" th:text="#{adminUserSettings.forceChange}">Force user to change username/password on login</label>
</div>
<!-- Add other fields as required -->
<button type="submit" class="btn btn-primary" th:text="#{adminUserSettings.submit}">Save User</button>
</form>
</div>
</div>
</div> </div>
</div>
</div> </div>
</div> <div th:insert="~{fragments/footer.html :: footer}"></div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

Some files were not shown because too many files have changed in this diff Show More