Homepage update (#2663)

# Description

Please provide a summary of the changes, including relevant motivation
and context.

Closes #(issue_number)

## Checklist

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have performed a self-review of my own code
- [ ] I have attached images of the change if it is UI based
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] If my code has heavily changed functionality I have updated
relevant docs on [Stirling-PDFs doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
- [ ] My changes generate no new warnings
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

---------

Co-authored-by: Reece Browne <reece@stirling.pdf>
This commit is contained in:
reecebrowne 2025-01-30 18:55:33 +00:00 committed by GitHub
parent 67569a8f6a
commit 60cc613c63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 7843 additions and 7158 deletions

View File

@ -74,6 +74,12 @@ public class HomeWebController {
return "redirect:/"; return "redirect:/";
} }
@GetMapping("/home-legacy")
public String homeLegacy(Model model) {
model.addAttribute("currentPage", "home-legacy");
return "home-legacy";
}
@GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE) @GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
@ResponseBody @ResponseBody
@Hidden @Hidden

View File

@ -138,6 +138,7 @@ analytics.settings=You can change the settings for analytics in the config/setti
# NAVBAR # # NAVBAR #
############# #############
navbar.favorite=Favorites navbar.favorite=Favorites
navbar.recent=New and recently updated
navbar.darkmode=Dark Mode navbar.darkmode=Dark Mode
navbar.language=Languages navbar.language=Languages
navbar.settings=Settings navbar.settings=Settings
@ -265,6 +266,14 @@ home.viewPdf.title=View PDF
home.viewPdf.desc=View, annotate, add text or images home.viewPdf.desc=View, annotate, add text or images
viewPdf.tags=view,read,annotate,text,image viewPdf.tags=view,read,annotate,text,image
home.setFavorites=Set Favourites
home.hideFavorites=Hide Favourites
home.showFavorites=Show Favourites
home.legacyHomepage=Old homepage
home.newHomePage=Try our new homepage!
home.alphabetical=Alphabetical
home.globalPopularity=Global Popularity
home.multiTool.title=PDF Multi Tool home.multiTool.title=PDF Multi Tool
home.multiTool.desc=Merge, Rotate, Rearrange, Split, and Remove pages home.multiTool.desc=Merge, Rotate, Rearrange, Split, and Remove pages
multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side,interactive,intractable,move,delete,migrate,divide multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side,interactive,intractable,move,delete,migrate,divide
@ -1007,8 +1016,8 @@ multiTool.moveLeft=Move Left
multiTool.moveRight=Move Right multiTool.moveRight=Move Right
multiTool.delete=Delete multiTool.delete=Delete
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo (CTRL + Z)
multiTool.redo=Redo multiTool.redo=Redo (CTRL + Y)
#decrypt #decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password: decrypt.passwordPrompt=This file is password-protected. Please enter the password:

View File

@ -12,10 +12,14 @@
bottom: 0; bottom: 0;
width: 100%; width: 100%;
} }
.navbar { .navbar {
height: auto; /* Adjusts height automatically based on content */ height: auto;
white-space: nowrap; /* Prevents wrapping of navbar contents */ /* Adjusts height automatically based on content */
white-space: nowrap;
/* Prevents wrapping of navbar contents */
} }
/* TODO enable later /* TODO enable later
.navbar .container { .navbar .container {
@ -28,10 +32,12 @@
html[dir="ltr"] * { html[dir="ltr"] * {
direction: ltr; direction: ltr;
} }
html[dir="rtl"] * { html[dir="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;
@ -41,6 +47,7 @@ html[dir="rtl"] * {
position: absolute; position: absolute;
top: 0; top: 0;
} }
.align-center-right { .align-center-right {
position: absolute; position: absolute;
right: 0; right: 0;
@ -58,7 +65,7 @@ html[dir="rtl"] * {
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;
} }
@ -67,6 +74,7 @@ html[dir="rtl"] input.form-check-input {
position: relative; position: relative;
margin-left: 0px; margin-left: 0px;
} }
html[dir="rtl"] label.form-check-label { html[dir="rtl"] label.form-check-label {
display: inline; display: inline;
} }
@ -75,20 +83,25 @@ html[dir="rtl"] label.form-check-label {
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;
} }
@ -97,48 +110,31 @@ input:-webkit-autofill,
input:-webkit-autofill:focus { input:-webkit-autofill:focus {
transition: background-color 600000s 0s, color 600000s 0s; transition: background-color 600000s 0s, color 600000s 0s;
} }
input[data-autocompleted] { input[data-autocompleted] {
background-color: transparent !important; background-color: transparent !important;
} }
.btn-tooltip { .btn-tooltip {
position: absolute; position: absolute !important;
display: none; display: none;
bottom: 3.2rem;
white-space: nowrap;
flex-wrap: nowrap;
width: fit-content;
padding: 7px; padding: 7px;
background-color: rgba(0, 29, 41, 0.9); background-color: rgba(0, 29, 41, 0.9);
border-radius: 3px; border-radius: 3px;
font-size: 12px; font-size: 12px;
color: whitesmoke; color: whitesmoke;
animation: fadeup 0.15s linear; animation: fadeup 0.15s linear;
z-index: 10000;
} }
@keyframes fadeup { @keyframes fadeup {
0% { 0% {
transform: translateY(10px); transform: translateY(10px);
opacity: 0; opacity: 0;
} }
100% { 100% {
transform: translateY(0); transform: translateY(0);
opacity: 1; opacity: 1;
} }
} }
.btn:hover .btn-tooltip {
display: block;
}
.btn-primary:hover .btn-tooltip {
display: block;
}
.btn-success:hover .btn-tooltip {
display: block;
}
.btn-secondary:hover .btn-tooltip {
display: block;
}
.btn-toolbarButton:hover .btn-tooltip {
display: block;
}
.toolbarButton:hover .btn-tooltip {
display: block;
}

View File

@ -0,0 +1,229 @@
#searchBar {
color: var(--md-sys-color-on-surface);
background-color: var(--md-sys-color-surface-container-low);
width: 100%;
font-size: 16px;
margin-bottom: 2rem;
padding: 0.75rem 3.5rem;
border: 1px solid var(--md-sys-color-outline-variant);
border-radius: 3rem;
outline-color: var(--md-sys-color-outline-variant);
}
#filtersContainer {
display: flex;
width: 100%;
align-items: center;
justify-content: center;
gap: 10px;
}
.filter-button {
color: var(--md-sys-color-secondary);
user-select: none;
cursor: pointer;
transition: transform 0.3s;
transform-origin: center center;
}
.filter-button:hover {
transform: scale(1.08);
}
.search-icon {
position: absolute;
margin: 0.75rem 1rem;
border: 0.1rem solid transparent;
}
.features-container {
display: flex;
flex-direction: column;
gap: 30px;
}
.feature-group-legacy {
display: flex;
flex-direction: column;
}
.feature-group-header {
display: flex;
align-items: center;
justify-content: flex-start;
color: var(--md-sys-color-on-surface);
margin-bottom: 15px;
user-select: none;
cursor: pointer;
gap: 10px;
}
.feature-group-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(15rem, 3fr));
gap: 30px 30px;
overflow: hidden;
margin: -20px;
padding: 20px;
box-sizing:content-box;
}
.feature-group-container.animated-group {
transition: 0.5s all;
}
.feature-group-legacy.collapsed>.feature-group-container {
max-height: 0 !important;
margin: 0;
padding: 0;
}
.header-expand-button {
transition: 0.5s all;
transform: rotate(90deg);
}
.header-expand-button.collapsed {
transform: rotate(0deg);
}
.feature-card {
border: 1px solid var(--md-sys-color-surface-5);
border-radius: 1.75rem;
padding: 1.25rem;
display: flex;
flex-direction: column;
align-items: flex-start;
background: var(--md-sys-color-surface-5);
transition:
transform 0.3s,
border 0.3s;
transform-origin: center center;
outline: 0px solid transparent;
position:relative;
}
.feature-card a {
text-decoration: none;
color: var(--md-sys-color-on-surface);
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.feature-card .card-text {
font-size: .875rem;
}
.feature-card:hover {
cursor: pointer;
transform: scale(1.08);
box-shadow: var(--md-sys-elevation-2);
}
.card-title.text-primary {
color: #000;
}
.home-card-icon {
width: 3rem;
height: 3rem;
transform: translateY(-5px);
}
.favorite-icon {
display: none !important;
position: absolute;
top: 10px;
right: 10px;
color: var(--md-sys-color-secondary);
}
#tool-icon {
height: 100%;
}
#tool-text {
margin: 0.0rem 0 0 1.25rem;
}
.card-title {
margin-bottom: 1rem;
font-size: 1.1rem;
}
/* Only show the favorite icons when the parent card is being hovered over */
.feature-card:hover .favorite-icon {
display: block !important;
}
.favorite-icon img {
filter: brightness(0) invert(var(--md-theme-filter-color));
}
.favorite-icon:hover .material-symbols-rounded {
transform: scale(1.2);
}
.favorite-icon .material-symbols-rounded.fill{
color: #f5c000;
}
.jumbotron {
padding: 3rem 3rem;
/* Reduce vertical padding */
}
.lookatme {
opacity: 1;
position: relative;
display: inline-block;
}
.lookatme::after {
color: #e33100;
text-shadow: 0 0 5px #e33100;
/* in the html, the data-lookatme-text attribute must */
/* contain the same text as the .lookatme element */
content: attr(data-lookatme-text);
padding: inherit;
position: absolute;
inset: 0 0 0 0;
z-index: 1;
/* 20 steps / 2 seconds = 10fps */
-webkit-animation: 2s infinite Pulse steps(20);
animation: 2s infinite Pulse steps(20);
}
@keyframes Pulse {
from {
opacity: 0;
}
50% {
opacity: 1;
}
to {
opacity: 0;
}
}
.update-notice {
animation: scale 1s infinite alternate;
}
@keyframes scale {
0% {
transform: scale(0.96);
}
100% {
transform: scale(1);
}
}
.hidden {
visibility: hidden;
}

View File

@ -36,101 +36,6 @@
border: 0.1rem solid transparent; border: 0.1rem solid transparent;
} }
.features-container {
display: flex;
flex-direction: column;
gap: 30px;
}
.feature-group {
display: flex;
flex-direction: column;
}
.feature-group-header {
display: flex;
align-items: center;
justify-content: flex-start;
color: var(--md-sys-color-on-surface);
margin-bottom: 15px;
user-select: none;
cursor: pointer;
gap: 10px;
}
.feature-group-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(15rem, 3fr));
gap: 30px 30px;
overflow: hidden;
margin: -20px;
padding: 20px;
box-sizing:content-box;
}
.feature-group-container.animated-group {
transition: 0.5s all;
}
.feature-group.collapsed>.feature-group-container {
max-height: 0 !important;
margin: 0;
padding: 0;
}
.header-expand-button {
transition: 0.5s all;
transform: rotate(90deg);
}
.header-expand-button.collapsed {
transform: rotate(0deg);
}
.feature-card {
border: 1px solid var(--md-sys-color-surface-5);
border-radius: 1.75rem;
padding: 1.25rem;
display: flex;
flex-direction: column;
align-items: flex-start;
background: var(--md-sys-color-surface-5);
transition:
transform 0.3s,
border 0.3s;
transform-origin: center center;
outline: 0px solid transparent;
}
.feature-card a {
text-decoration: none;
color: var(--md-sys-color-on-surface);
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.feature-card .card-text {
font-size: .875rem;
}
.feature-card:hover {
cursor: pointer;
transform: scale(1.08);
box-shadow: var(--md-sys-elevation-2);
}
.card-title.text-primary {
color: #000;
}
.home-card-icon {
width: 3rem;
height: 3rem;
transform: translateY(-5px);
}
.favorite-icon { .favorite-icon {
display: none; display: none;
position: absolute; position: absolute;
@ -147,16 +52,6 @@
margin: 0.0rem 0 0 1.25rem; margin: 0.0rem 0 0 1.25rem;
} }
.card-title {
margin-bottom: 1rem;
font-size: 1.1rem;
}
/* Only show the favorite icons when the parent card is being hovered over */
.feature-card:hover .favorite-icon {
display: block;
}
.favorite-icon img { .favorite-icon img {
filter: brightness(0) invert(var(--md-theme-filter-color)); filter: brightness(0) invert(var(--md-theme-filter-color));
} }
@ -195,34 +90,21 @@
animation: 2s infinite Pulse steps(20); animation: 2s infinite Pulse steps(20);
} }
@keyframes Pulse { .newfeature{
from { min-width:12rem;
opacity: 0; }
} .recent-features{
display: flex;
flex-direction: row;
max-width: 100%;
overflow: hidden;
justify-content: center;
50% {
opacity: 1;
}
to {
opacity: 0;
}
} }
.update-notice { .close-icon {
animation: scale 1s infinite alternate; color: var(--favourite-remove) !important;
} }
.add-icon {
@keyframes scale { color: var(--favourite-add) !important;
0% {
transform: scale(0.96);
}
100% {
transform: scale(1);
}
}
.hidden {
visibility: hidden;
} }

View File

@ -231,6 +231,13 @@ span.icon-text::after {
background-color: var(--md-nav-section-color-convert); background-color: var(--md-nav-section-color-convert);
} }
.dropdown-item:focus.convertto,
.dropdown-item:hover.convertto,
.dropdown-item.active.convertto {
color: var(--md-nav-on-section-color-convertto);
background-color: var(--md-nav-section-color-convertto);
}
.dropdown-item:focus.image, .dropdown-item:focus.image,
.dropdown-item:hover.image, .dropdown-item:hover.image,
.dropdown-item.active.image { .dropdown-item.active.image {
@ -302,7 +309,7 @@ span.icon-text::after {
padding: 1.5rem 0; padding: 1.5rem 0;
border-radius: 1rem; border-radius: 1rem;
color: var(--md-sys-color-on-surface); color: var(--md-sys-color-on-surface);
background-color: var(--md-sys-color-surface-container); background-color: var(--md-sys-color-surface);
border: 1px solid var(--md-sys-color-surface-5); border: 1px solid var(--md-sys-color-surface-5);
box-shadow: var(--md-sys-elevation-2); box-shadow: var(--md-sys-elevation-2);
} }
@ -371,6 +378,114 @@ span.icon-text::after {
gap: 1rem; gap: 1rem;
} }
#stacked > .navbar-item { #stacked>.navbar-item {
margin: 0; margin: 0;
} }
.features-container {
display: flex;
gap: 30px;
justify-content: center;
}
.feature-group {
display: flex;
flex-direction: column;
min-width: 14rem;
max-width: 18rem;
flex: 1 1 min(14rem, 100%);
box-sizing: border-box;
}
.feature-rows {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
gap: 1rem;
padding: 0 1rem;
box-sizing: border-box;
}
.feature-rows.single-column {
justify-content: center;
/* Center-align a single column */
}
.feature-group-header {
display: flex;
justify-content: flex-start;
color: var(--md-sys-color-on-surface);
margin-top: 15px;
user-select: none;
cursor: pointer;
gap: 10px;
}
.nav-group-container {
padding: 10px;
}
.card-title.text-primary {
color: #000;
}
.home-card-icon {
width: 3rem;
height: 3rem;
transform: translateY(-5px);
}
.favorite-icon {
display: none;
position: absolute;
top: 10px;
right: 10px;
color: var(--md-sys-color-secondary);
}
.favorite-icon img {
filter: brightness(0) invert(var(--md-theme-filter-color));
}
.favorite-icon:hover .material-symbols-rounded {
transform: scale(1.2);
}
.favorite-icon .material-symbols-rounded.fill {
color: #f5c000;
}
@keyframes Pulse {
from {
opacity: 0;
}
50% {
opacity: 1;
}
to {
opacity: 0;
}
}
.update-notice {
animation: scale 1s infinite alternate;
}
@keyframes scale {
0% {
transform: scale(0.96);
}
100% {
transform: scale(1);
}
}
.hidden {
visibility: hidden;
}

View File

@ -187,7 +187,7 @@ html {
#apply-redaction:is(:hover):not([disabled=true], :disabled:not([disabled=false])) { #apply-redaction:is(:hover):not([disabled=true], :disabled:not([disabled=false])) {
cursor: pointer; cursor: pointer;
background-color: rgba(6, 114, 197, 0.82); background-color: rgba(6, 114, 197, 0.82);
color: rgb(255 255 255); color: rgb(14, 12, 12);
outline:rgba(6, 114, 197, 0.82) !important; outline:rgba(6, 114, 197, 0.82) !important;
border-color: rgba(6, 114, 197, 0.82) !important; border-color: rgba(6, 114, 197, 0.82) !important;
} }
@ -230,8 +230,14 @@ html {
.splitToolbarButton > .btn-primary, .splitToolbarButton > .btn-secondary, .splitToolbarButton > .toolbarButton { .splitToolbarButton > .btn-primary, .splitToolbarButton > .btn-secondary, .splitToolbarButton > .toolbarButton {
margin-left: 3px; margin-left: 3px;
margin-right: 3px; margin-right: 3px;
border:none
} }
.splitToolbarButton > .btn-success, .splitToolbarButton > .btn-secondary, .splitToolbarButton > .toolbarButton {
border:none
}
.spin-animation { .spin-animation {
-webkit-animation: spin 2s linear infinite; /* Safari */ -webkit-animation: spin 2s linear infinite; /* Safari */
-moz-animation: spin 2s linear infinite; -moz-animation: spin 2s linear infinite;

View File

@ -128,6 +128,13 @@ td {
background-color: var(--md-nav-section-color-convert); background-color: var(--md-nav-section-color-convert);
} }
.convertto .nav-icon,
.convertto.tool-header-icon {
color: var(--md-nav-on-section-color-convertto);
background-color: var(--md-nav-section-color-convertto);
}
.security .nav-icon, .security .nav-icon,
.security.tool-header-icon { .security.tool-header-icon {
color: var(--md-nav-on-section-color-security); color: var(--md-nav-on-section-color-security);
@ -213,6 +220,11 @@ td {
background-color: var(--md-nav-section-color-convert); background-color: var(--md-nav-section-color-convert);
} }
.feature-card .convertto .nav-icon {
color: var(--md-nav-on-section-color-convertto);
background-color: var(--md-nav-section-color-convertto);
}
.feature-card .security .nav-icon { .feature-card .security .nav-icon {
color: var(--md-nav-on-section-color-security); color: var(--md-nav-on-section-color-security);
background-color: var(--md-nav-section-color-security); background-color: var(--md-nav-section-color-security);
@ -874,6 +886,8 @@ textarea.form-control {
.dropdown-item { .dropdown-item {
color: var(--md-sys-color-on-surface); color: var(--md-sys-color-on-surface);
padding: 0.25rem 1rem; padding: 0.25rem 1rem;
border-radius: 3rem;
} }
.dropdown-item:focus, .dropdown-item:focus,
@ -881,9 +895,15 @@ textarea.form-control {
color: var(--md-sys-color-on-surface); color: var(--md-sys-color-on-surface);
background-color: var(--md-sys-color-surface-5); background-color: var(--md-sys-color-surface-5);
border-radius: 3rem; border-radius: 3rem;
font-weight: 500;
font-variation-settings: var(--md-sys-icon-fill-1); font-variation-settings: var(--md-sys-icon-fill-1);
} }
.dropdown-item.no-hover:hover,
.dropdown-item.no-hover:focus {
color: var(--md-sys-color-on-surface) !important;
background-color: transparent !important;
border-radius: 3rem !important;
font-variation-settings: initial !important;
}
.dropdown-item.active, .dropdown-item.active,
.dropdown-item:active { .dropdown-item:active {

View File

@ -2,11 +2,11 @@
.tokens, .tokens,
:host { :host {
/* Define surface colors based on primary color */ /* Define surface colors based on primary color */
--md-sys-color-surface-1: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.05) 5%); --md-sys-color-surface-1: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 100, 0.05) 5%);
--md-sys-color-surface-2: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.08) 5%); --md-sys-color-surface-2: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 255, 0.08) 5%);
--md-sys-color-surface-3: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.11) 5%); --md-sys-color-surface-3: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 255, 0.11) 5%);
--md-sys-color-surface-4: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.12) 5%); --md-sys-color-surface-4: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 255, 0.12) 5%);
--md-sys-color-surface-5: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.14) 5%); --md-sys-color-surface-5: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 255, 0.14) 5%);
/* Icon fill */ /* Icon fill */
--md-sys-icon-fill-0: 'FILL' 0, 'wght' 500; --md-sys-icon-fill-0: 'FILL' 0, 'wght' 500;
--md-sys-icon-fill-1: 'FILL' 1, 'wght' 500; --md-sys-icon-fill-1: 'FILL' 1, 'wght' 500;

View File

@ -58,6 +58,8 @@
--md-nav-on-section-color-organize: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); --md-nav-on-section-color-organize: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity));
--md-nav-section-color-convert: rgba(25, 177, 212, var(--md-nav-section-color-opacity)); --md-nav-section-color-convert: rgba(25, 177, 212, var(--md-nav-section-color-opacity));
--md-nav-on-section-color-convert: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); --md-nav-on-section-color-convert: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity));
--md-nav-section-color-convertto: rgba(104, 220, 149, var(--md-nav-section-color-opacity));
--md-nav-on-section-color-convertto: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity));
--md-nav-section-color-security: rgba(255, 120, 146, var(--md-nav-section-color-opacity)); --md-nav-section-color-security: rgba(255, 120, 146, var(--md-nav-section-color-opacity));
--md-nav-on-section-color-security: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); --md-nav-on-section-color-security: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity));
--md-nav-section-color-other: rgba(72, 189, 84, var(--md-nav-section-color-opacity)); --md-nav-section-color-other: rgba(72, 189, 84, var(--md-nav-section-color-opacity));
@ -70,4 +72,8 @@
--md-nav-on-section-color-word: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); --md-nav-on-section-color-word: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity));
--md-nav-section-color-ppt: rgba(255, 128, 0, var(--md-nav-section-color-opacity)); --md-nav-section-color-ppt: rgba(255, 128, 0, var(--md-nav-section-color-opacity));
--md-nav-on-section-color-ppt: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); --md-nav-on-section-color-ppt: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity));
--md-nav-color-on-seperator: rgb(24 28 34);
--md-nav-background: rgb(15 20 26);
--favourite-add: #9ed18c;
--favourite-remove: palevioletred;
} }

View File

@ -19,8 +19,8 @@
--md-sys-color-on-error-container: rgb(65 0 2); --md-sys-color-on-error-container: rgb(65 0 2);
--md-sys-color-background: rgb(248 249 255); --md-sys-color-background: rgb(248 249 255);
--md-sys-color-on-background: rgb(24 28 34); --md-sys-color-on-background: rgb(24 28 34);
--md-sys-color-surface: rgb(248 249 255); --md-sys-color-surface: rgb(237, 240, 245);
--md-sys-color-on-surface: rgb(24 28 34); --md-sys-color-on-surface: rgb(0, 1, 1);
--md-sys-color-surface-variant: rgb(220 227 241); --md-sys-color-surface-variant: rgb(220 227 241);
--md-sys-color-on-surface-variant: rgb(64 71 83); --md-sys-color-on-surface-variant: rgb(64 71 83);
--md-sys-color-outline: rgb(112 119 132); --md-sys-color-outline: rgb(112 119 132);
@ -58,6 +58,8 @@
--md-nav-on-section-color-organize: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); --md-nav-on-section-color-organize: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
--md-nav-section-color-convert: rgba(25, 177, 212, var(--md-nav-section-color-opacity)); --md-nav-section-color-convert: rgba(25, 177, 212, var(--md-nav-section-color-opacity));
--md-nav-on-section-color-convert: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); --md-nav-on-section-color-convert: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
--md-nav-section-color-convertto: rgba(104, 220, 149, var(--md-nav-section-color-opacity));
--md-nav-on-section-color-convertto: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
--md-nav-section-color-security: rgba(255, 120, 146, var(--md-nav-section-color-opacity)); --md-nav-section-color-security: rgba(255, 120, 146, var(--md-nav-section-color-opacity));
--md-nav-on-section-color-security: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); --md-nav-on-section-color-security: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
--md-nav-section-color-other: rgba(72, 189, 84, var(--md-nav-section-color-opacity)); --md-nav-section-color-other: rgba(72, 189, 84, var(--md-nav-section-color-opacity));
@ -70,4 +72,8 @@
--md-nav-on-section-color-word: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); --md-nav-on-section-color-word: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
--md-nav-section-color-ppt: rgba(255, 128, 0, var(--md-nav-section-color-opacity)); --md-nav-section-color-ppt: rgba(255, 128, 0, var(--md-nav-section-color-opacity));
--md-nav-on-section-color-ppt: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); --md-nav-on-section-color-ppt: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
--md-nav-color-on-seperator: rgb(174, 178, 179);
--md-nav-background: rgb(248 249 255);
--favourite-add: #25ab6c;
--favourite-remove: rgb(222, 94, 137);
} }

View File

@ -0,0 +1,66 @@
{
"/login": 1,
"/multi-tool": 2,
"/merge-pdfs": 3,
"/pdf-to-word": 4,
"/compress-pdf": 5,
"/pdf-to-img": 6,
"/pipeline": 7,
"/split-pdfs": 8,
"/img-to-pdf": 9,
"/file-to-pdf": 10,
"/ocr-pdf": 11,
"/sign": 12,
"/remove-password": 13,
"/adjust-contrast": 14,
"/pdf-to-text": 15,
"/extract-page": 16,
"/add-watermark": 17,
"/remove-pages": 18,
"/crop": 19,
"/url-to-pdf": 20,
"/pdf-to-presentation": 21,
"/pdf-to-csv": 22,
"/html-to-pdf": 23,
"/cert-sign": 24,
"/rotate-pdf": 25,
"/console/": 26,
"/geoserver": 27,
"/pdf-to-xml": 28,
"/markdown-to-pdf": 29,
"/pdf-organizer": 30,
"/add-image": 31,
"/stamp": 32,
"/auto-redact": 33,
"/scale-pages": 34,
"/extract-images": 35,
"/change-metadata": 36,
"/pdf-to-html": 37,
"/get-info-on-pdf": 38,
"/replace-and-invert-color-pdf": 39,
"/pdf-to-pdfa": 40,
"/change-permissions": 41,
"/compare": 42,
"/add-password": 43,
"/multi-page-layout": 44,
"/add-page-numbers": 45,
"/auto-rename": 46,
"/auto-split-pdf": 47,
"/extract-image-scans": 48,
"/flatten": 49,
"/overlay-pdf": 50,
"/pdf-to-markdown": 51,
"/pdf-to-single-page": 52,
"/redact": 53,
"/remove-annotations": 54,
"/remove-blanks": 55,
"/remove-cert-sign": 56,
"/remove-image-pdf": 57,
"/repair": 58,
"/sanitize-pdf": 59,
"/show-javascript": 60,
"/split-by-size-or-count": 61,
"/split-pdf-by-chapters": 62,
"/split-pdf-by-sections": 63,
"/validate-signature": 64
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<symbol id="icon-redact-auto" viewBox="0 0 24 24"> <g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<rect width="24" height="24" style="fill: none"/>
<g>
<path d="M17.541,15.64258a.91793.91793,0,0,1,.55469-.18555h1.1084a.91586.91586,0,0,1,.55469.18555,1.30889,1.30889,0,0,1,.40429.499,1.57206,1.57206,0,0,1,.15039.68457v5.47754H19.2041V20.21094H18.0957v2.09277H16.9873V16.82617a1.55843,1.55843,0,0,1,.15039-.68457A1.2979,1.2979,0,0,1,17.541,15.64258Zm1.66309,1.10547H18.0957v2.17187h1.1084Z" style="fill: currentColor"/>
<path d="M5.68653,22.30351a2.00588,2.00588,0,0,1-2-2v-16A1.92585,1.92585,0,0,1,4.274,2.891a1.92585,1.92585,0,0,1,1.4125-.5875h8l6,6v5.66931h-2V9.30351h-5v-5h-7v16h9.74021v2Z" style="fill: currentColor"/>
<rect x="7.69809" y="10.43189" width="4.33778" height="0.79501" style="fill: currentColor"/>
<rect x="7.69809" y="12.16889" width="7.31192" height="1.21288" style="fill: currentColor"/>
<rect x="7.69809" y="17.14555" width="7.31192" height="1.21288" style="fill: currentColor"/>
<rect x="7.69809" y="14.32375" width="7.31192" height="0.57517" style="fill: currentColor"/>
<rect x="7.69809" y="15.79848" width="5.25578" height="0.4475" style="fill: currentColor"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<symbol id="icon-redact-manual" viewBox="-2 0 24 24">
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<rect width="24" height="24" style="fill: none"/>
<g>
<rect x="6.23853" y="10.43189" width="4.33778" height="0.79501" style="fill: currentColor"/>
<rect x="6.23853" y="12.16889" width="7.31192" height="1.21288" style="fill: currentColor"/>
<rect x="6.23853" y="17.14555" width="6.03324" height="1.21288" style="fill: currentColor"/>
<rect x="6.23853" y="14.32375" width="7.31192" height="0.57517" style="fill: currentColor"/>
<rect x="6.23853" y="15.79848" width="5.25578" height="0.4475" style="fill: currentColor"/>
<path d="M12.5,22.30351v-3.075l5.525-5.5a1.36232,1.36232,0,0,1,.5-.325,1.59994,1.59994,0,0,1,.55-.1,1.49441,1.49441,0,0,1,1.075.45l.925.925a1.73875,1.73875,0,0,1,.3125.5,1.44287,1.44287,0,0,1,.1125.55,1.70613,1.70613,0,0,1-.1.5625,1.34171,1.34171,0,0,1-.325.5125l-5.5,5.5Zm7.5-6.575-.925-.925Zm-6,5.075h.95l3.025-3.05-.45-.475-.475-.45L14,19.85351Zm-9.5,1.5a1.9259,1.9259,0,0,1-1.4125-.5875A1.92586,1.92586,0,0,1,2.5,20.30351v-16A1.92585,1.92585,0,0,1,3.0875,2.891,1.92586,1.92586,0,0,1,4.5,2.30351h8l6,6v3h-2v-2h-5v-5h-7v16h6v2Zm13.025-5.025-.475-.45.925.925Z" style="fill: currentColor"/>
</g>
</g>
</g>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<symbol id="icon-split-auto" viewBox="0 0 24 24"> <g id="Layer_2" data-name="Layer 2"> <g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<g>
<path d="M18.42466,20.16555,12,13.74089,9.84315,15.89774a2.45776,2.45776,0,0,1,.2524.73425,4.481,4.481,0,0,1,.06883.78013A3.53515,3.53515,0,0,1,9.086,20.00493a3.53516,3.53516,0,0,1-2.59281,1.07843,3.53516,3.53516,0,0,1-2.59281-1.07843,3.6561,3.6561,0,0,1,0-5.18561,3.53516,3.53516,0,0,1,2.59281-1.07843,4.48117,4.48117,0,0,1,.78014.06884,2.45778,2.45778,0,0,1,.73424.25239l2.15685-2.15685L8.00753,9.74842a2.45752,2.45752,0,0,1-.73424.2524,4.48117,4.48117,0,0,1-.78014.06884A3.53516,3.53516,0,0,1,3.90034,8.99123,3.53515,3.53515,0,0,1,2.82192,6.39842,3.53515,3.53515,0,0,1,3.90034,3.80561,3.53515,3.53515,0,0,1,6.49315,2.72719,3.53515,3.53515,0,0,1,9.086,3.80561a3.53515,3.53515,0,0,1,1.07842,2.59281,4.48107,4.48107,0,0,1-.06883.78014,2.45786,2.45786,0,0,1-.2524.73425L21.17808,19.24774v.91781Zm-3.67123-9.17809L12.91781,9.15185,18.42466,3.645h2.75342v.91781ZM6.49315,8.234A1.841,1.841,0,0,0,8.32877,6.39842,1.841,1.841,0,0,0,6.49315,4.56281,1.841,1.841,0,0,0,4.65753,6.39842,1.841,1.841,0,0,0,6.49315,8.234ZM12,12.36418a.47051.47051,0,1,0-.32123-.13767A.44026.44026,0,0,0,12,12.36418ZM6.49315,19.24774a1.83562,1.83562,0,1,0-1.29641-3.132,1.83562,1.83562,0,0,0,1.29641,3.132Z" style="fill: currentColor"/>
<path d="M18.0708,9.373a.73373.73373,0,0,1,.44434-.14942h.88867a.73372.73372,0,0,1,.44433.14942,1.04878,1.04878,0,0,1,.32374.40039,1.24521,1.24521,0,0,1,.1206.54883v4.3916h-.88867V13.03613h-.88867v1.67774h-.88868v-4.3916a1.24508,1.24508,0,0,1,.12061-.54883A1.04885,1.04885,0,0,1,18.0708,9.373Zm1.333.88672h-.88867V12h.88867Z" style="fill: currentColor" transform="translate(-5 -4) scale(1.3) "/>
</g>
<rect width="24" height="24" style="fill: none"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<symbol id="icon-split-chapters" viewBox="0 0 24 24"> <g id="Layer_2" data-name="Layer 2"> <g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<g>
<path d="M17.632,9.18527v5.44l1.94-1.16,1.94,1.16v-5.44Z" style="fill: currentColor"/>
<path d="M18.09072,20.16555l-6.42466-6.42466L9.50921,15.89774a2.45775,2.45775,0,0,1,.25239.73425,4.481,4.481,0,0,1,.06884.78013A3.53516,3.53516,0,0,1,8.752,20.00493a3.53516,3.53516,0,0,1-2.5928,1.07843A3.53516,3.53516,0,0,1,3.5664,20.00493a3.65608,3.65608,0,0,1,0-5.18561,3.53516,3.53516,0,0,1,2.59281-1.07843,4.481,4.481,0,0,1,.78013.06884,2.45775,2.45775,0,0,1,.73425.25239l2.15685-2.15685L7.67359,9.74842a2.45749,2.45749,0,0,1-.73425.2524,4.481,4.481,0,0,1-.78013.06884A3.53516,3.53516,0,0,1,3.5664,8.99123,3.53516,3.53516,0,0,1,2.488,6.39842,3.53516,3.53516,0,0,1,3.5664,3.80561,3.53515,3.53515,0,0,1,6.15921,2.72719,3.53515,3.53515,0,0,1,8.752,3.80561,3.53516,3.53516,0,0,1,9.83044,6.39842a4.48109,4.48109,0,0,1-.06884.78014,2.45784,2.45784,0,0,1-.25239.73425L20.84414,19.24774v.91781Zm-3.67124-9.17809L12.58386,9.15185,18.09072,3.645h2.75342v.91781ZM6.15921,8.234A1.841,1.841,0,0,0,7.99482,6.39842,1.841,1.841,0,0,0,6.15921,4.56281,1.841,1.841,0,0,0,4.32359,6.39842,1.841,1.841,0,0,0,6.15921,8.234Zm5.50685,4.13014a.47051.47051,0,1,0-.32124-.13767A.44026.44026,0,0,0,11.66606,12.36418ZM6.15921,19.24774a1.83562,1.83562,0,1,0-1.29641-3.132,1.83562,1.83562,0,0,0,1.29641,3.132Z" style="fill: currentColor"/>
</g>
<rect width="24" height="24" style="fill: none"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="padding-left: 20px; margin-right: -20px;">
<symbol id="icon-split-size" viewBox="0 0 24 24"> <g id="Layer_2" data-name="Layer 2"> <g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<g>
<path d="M17.90313,20.16555l-6.42466-6.42466L9.32162,15.89774a2.45776,2.45776,0,0,1,.2524.73425,4.481,4.481,0,0,1,.06883.78013,3.53515,3.53515,0,0,1-1.07842,2.59281,3.53516,3.53516,0,0,1-2.59281,1.07843,3.53516,3.53516,0,0,1-2.59281-1.07843,3.6561,3.6561,0,0,1,0-5.18561,3.53516,3.53516,0,0,1,2.59281-1.07843,4.48117,4.48117,0,0,1,.78014.06884,2.45778,2.45778,0,0,1,.73424.25239l2.15685-2.15685L7.486,9.74842a2.45752,2.45752,0,0,1-.73424.2524,4.48117,4.48117,0,0,1-.78014.06884A3.53516,3.53516,0,0,1,3.37881,8.99123,3.53515,3.53515,0,0,1,2.30039,6.39842,3.53515,3.53515,0,0,1,3.37881,3.80561,3.53515,3.53515,0,0,1,5.97162,2.72719,3.53515,3.53515,0,0,1,8.56443,3.80561,3.53515,3.53515,0,0,1,9.64285,6.39842a4.48107,4.48107,0,0,1-.06883.78014,2.45786,2.45786,0,0,1-.2524.73425L20.65655,19.24774v.91781Zm-3.67124-9.17809L12.39628,9.15185,17.90313,3.645h2.75342v.91781ZM5.97162,8.234A1.841,1.841,0,0,0,7.80724,6.39842,1.841,1.841,0,0,0,5.97162,4.56281,1.841,1.841,0,0,0,4.136,6.39842,1.841,1.841,0,0,0,5.97162,8.234Zm5.50685,4.13014a.47051.47051,0,1,0-.32123-.13767A.44026.44026,0,0,0,11.47847,12.36418ZM5.97162,19.24774a1.83562,1.83562,0,1,0-1.29641-3.132,1.83562,1.83562,0,0,0,1.29641,3.132Z" style="fill: currentColor"/>
<g>
<rect x="18.32071" y="7.9834" width="3.37891" height="2" style="fill: currentColor"/>
<rect x="18.32071" y="10.90527" width="3.37891" height="2" style="fill: currentColor"/>
<rect x="18.32071" y="13.82715" width="3.37891" height="2" style="fill: currentColor"/>
</g>
</g>
<rect width="24" height="24" style="fill: none"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,53 +1,65 @@
function updateFavoritesDropdown() { function updateFavoritesDropdown() {
var dropdown = document.querySelector("#favoritesDropdown"); const favoritesList = JSON.parse(localStorage.getItem('favoritesList')) || [];
if (!dropdown) {
console.error('Dropdown element with ID "favoritesDropdown" not found!');
return;
}
dropdown.innerHTML = "";
var hasFavorites = false;
var addedFeatures = new Set();
for (var i = 0; i < localStorage.length; i++) { for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i); var key = localStorage.key(i);
var value = localStorage.getItem(key); var value = localStorage.getItem(key);
if (value === "favorite") { if (value === 'favorite') {
var navbarEntry = document.querySelector(`a[href='${key}']`); const index = favoritesList.indexOf(key);
if (index === -1) {
favoritesList.push(key);
localStorage.removeItem(key);
console.log(`Added to favorites: ${key}`);
}
}
}
var dropdown = document.querySelector('#favoritesDropdown');
if (!dropdown) {
console.error('Dropdown element with ID "favoritesDropdown" not found!');
return;
}
dropdown.innerHTML = '';
var hasFavorites = false;
var addedFeatures = new Set();
for (var i = 0; i < favoritesList.length; i++) {
var value = favoritesList[i];
if (value) {
var navbarEntry = document.querySelector(`a[data-bs-link='${value}']`);
if (navbarEntry) { if (navbarEntry) {
var featureName = navbarEntry.textContent.trim(); var featureName = navbarEntry.textContent.trim();
if (!addedFeatures.has(featureName)) { if (!addedFeatures.has(featureName)) {
var dropdownItem = document.createElement("div"); var dropdownItem = document.createElement('div');
dropdownItem.className = "dropdown-item d-flex justify-content-between align-items-center"; dropdownItem.className = 'dropdown-item d-flex justify-content-between align-items-center';
// Create a wrapper for the original content // Create a wrapper for the original content
var contentWrapper = document.createElement("div"); var contentWrapper = document.createElement('div');
contentWrapper.className = "d-flex align-items-center flex-grow-1"; contentWrapper.className = 'd-flex align-items-center flex-grow-1';
contentWrapper.style.textDecoration = "none"; contentWrapper.style.textDecoration = 'none';
contentWrapper.style.color = "inherit"; contentWrapper.style.color = 'inherit';
// Clone the original content // Clone the original content
var originalContent = navbarEntry.querySelector('div').cloneNode(true); var originalContent = navbarEntry.querySelector('div').cloneNode(true);
contentWrapper.appendChild(originalContent); contentWrapper.appendChild(originalContent);
// Create the remove button // Create the remove button
var removeButton = document.createElement("button"); var removeButton = document.createElement('button');
removeButton.className = "btn btn-sm btn-link p-0 ml-2"; removeButton.className = 'btn btn-sm btn-link p-0 ml-2';
removeButton.innerHTML = '<i class="material-symbols-rounded close-icon" style="font-size: 18px;">close</i>'; removeButton.innerHTML = '<i class="material-symbols-rounded close-icon" style="font-size: 18px;">close</i>';
removeButton.onclick = function(itemKey, event) { removeButton.onclick = function (itemKey, event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
localStorage.removeItem(itemKey); addToFavorites(itemKey);
updateFavoritesSection();
updateFavoritesDropdown(); updateFavoritesDropdown();
filterCards(); }.bind(null, value);
}.bind(null, key);
// Add click event to the content wrapper // Add click event to the content wrapper
contentWrapper.onclick = function(itemHref, event) { contentWrapper.onclick = function (itemHref, event) {
event.preventDefault(); event.preventDefault();
window.location.href = itemHref; window.location.href = itemHref;
}.bind(null, navbarEntry.href); }.bind(null, navbarEntry.href);
@ -58,16 +70,67 @@ function updateFavoritesDropdown() {
hasFavorites = true; hasFavorites = true;
addedFeatures.add(featureName); addedFeatures.add(featureName);
} }
} else {
console.warn(`Navbar entry not found for key: ${key}`);
} }
} else {
console.warn(`Navbar entry not found for : ${value}`);
} }
} }
if (!hasFavorites) { if (!hasFavorites) {
var defaultItem = document.createElement("a"); var defaultItem = document.createElement('a');
defaultItem.className = "dropdown-item"; defaultItem.className = 'dropdown-item';
defaultItem.textContent = noFavourites || "No favorites added"; defaultItem.textContent = noFavourites || 'No favorites added';
dropdown.appendChild(defaultItem); dropdown.appendChild(defaultItem);
} }
} }
function updateFavoriteIcons() {
const favoritesList = JSON.parse(localStorage.getItem('favoritesList')) || [];
// Select all favorite icons
document.querySelectorAll('.favorite-icon').forEach((icon) => {
const endpoint = icon.getAttribute('data-endpoint');
const parent = icon.closest('.dropdown-item');
// Determine if the icon belongs to groupRecent or groupFavorites
const isInGroupRecent = parent?.closest('#groupRecent') !== null;
const isInGroupFavorites = parent?.closest('#groupFavorites') !== null;
if (isInGroupRecent) {
icon.style.display = 'none';
} else if (isInGroupFavorites) {
icon.textContent = 'close_small';
icon.style.color = 'palevioletred';
} else {
icon.textContent = favoritesList.includes(endpoint) ? 'close_small' : 'add';
icon.className = favoritesList.includes(endpoint)
? 'material-symbols-rounded favorite-icon close-icon'
: 'material-symbols-rounded favorite-icon add-icon';
}
});
}
function addToFavorites(entryId) {
if (entryId) {
const favoritesList = JSON.parse(localStorage.getItem('favoritesList')) || [];
const index = favoritesList.indexOf(entryId);
if (index === -1) {
favoritesList.push(entryId);
console.log(`Added to favorites: ${entryId}`);
} else {
favoritesList.splice(index, 1);
console.log(`Removed from favorites: ${entryId}`);
}
localStorage.setItem('favoritesList', JSON.stringify(favoritesList));
updateFavoritesDropdown();
updateFavoriteIcons();
const currentPath = window.location.pathname;
if (currentPath.includes('home-legacy')) {
syncFavoritesLegacy();
} else {
initializeCards();
}
}
}

View File

@ -0,0 +1,259 @@
function filterCardsLegacy() {
var input = document.getElementById('searchBar');
var filter = input.value.toUpperCase();
let featureGroups = document.querySelectorAll('.feature-group-legacy');
const collapsedGroups = getCollapsedGroups();
for (const featureGroup of featureGroups) {
var cards = featureGroup.querySelectorAll('.feature-card');
let groupMatchesFilter = false;
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
var title = card.querySelector('h5.card-title').innerText;
var text = card.querySelector('p.card-text').innerText;
// Get the navbar tags associated with the card
var navbarItem = document.querySelector(`a.dropdown-item[href="${card.id}"]`);
var navbarTags = navbarItem ? navbarItem.getAttribute('data-bs-tags') : '';
var content = title + ' ' + text + ' ' + navbarTags;
if (content.toUpperCase().indexOf(filter) > -1) {
card.style.display = '';
groupMatchesFilter = true;
} else {
card.style.display = 'none';
}
}
if (!groupMatchesFilter) {
featureGroup.style.display = 'none';
} else {
featureGroup.style.display = '';
resetOrTemporarilyExpandGroup(featureGroup, filter, collapsedGroups);
}
}
}
function getCollapsedGroups() {
return localStorage.getItem('collapsedGroups') ? JSON.parse(localStorage.getItem('collapsedGroups')) : [];
}
function resetOrTemporarilyExpandGroup(featureGroup, filterKeywords = '', collapsedGroups = []) {
const shouldResetCollapse = filterKeywords.trim() === '';
if (shouldResetCollapse) {
// Resetting the group's expand/collapse to its original state (as in collapsed groups)
const isCollapsed = collapsedGroups.indexOf(featureGroup.id) != -1;
expandCollapseToggle(featureGroup, !isCollapsed);
} else {
// Temporarily expands feature group without affecting the actual/stored collapsed groups
featureGroup.classList.remove('collapsed');
featureGroup.querySelector('.header-expand-button').classList.remove('collapsed');
}
}
function updateFavoritesSectionLegacy() {
const favoritesContainer = document.getElementById('groupFavorites').querySelector('.feature-group-container');
favoritesContainer.innerHTML = '';
const cards = Array.from(document.querySelectorAll('.feature-card:not(.duplicate)'));
const addedCardIds = new Set();
let favoritesAmount = 0;
cards.forEach((card) => {
const favouritesList = JSON.parse(localStorage.getItem('favoritesList') || '[]');
if (favouritesList.includes(card.id) && !addedCardIds.has(card.id)) {
const duplicate = card.cloneNode(true);
duplicate.classList.add('duplicate');
favoritesContainer.appendChild(duplicate);
addedCardIds.add(card.id);
favoritesAmount++;
}
});
if (favoritesAmount === 0) {
document.getElementById('groupFavorites').style.display = 'none';
} else {
document.getElementById('groupFavorites').style.display = 'flex';
}
reorderCards(favoritesContainer);
}
function syncFavoritesLegacy() {
const cards = Array.from(document.querySelectorAll('.feature-card'));
cards.forEach((card) => {
const isFavorite = localStorage.getItem(card.id) === 'favorite';
const starIcon = card.querySelector('.favorite-icon span.material-symbols-rounded');
if (starIcon) {
if (isFavorite) {
starIcon.classList.remove('no-fill');
starIcon.classList.add('fill');
card.classList.add('favorite');
} else {
starIcon.classList.remove('fill');
starIcon.classList.add('no-fill');
card.classList.remove('favorite');
}
}
});
updateFavoritesSectionLegacy();
updateFavoritesDropdown();
filterCardsLegacy();
}
function reorderCards(container) {
var cards = Array.from(container.querySelectorAll('.feature-card'));
cards.forEach(function (card) {
container.removeChild(card);
});
cards.sort(function (a, b) {
var aIsFavorite = localStorage.getItem(a.id) === 'favorite';
var bIsFavorite = localStorage.getItem(b.id) === 'favorite';
if (a.id === 'update-link') {
return -1;
}
if (b.id === 'update-link') {
return 1;
}
if (aIsFavorite && !bIsFavorite) {
return -1;
} else if (!aIsFavorite && bIsFavorite) {
return 1;
} else {
return a.id > b.id;
}
});
cards.forEach(function (card) {
container.appendChild(card);
});
}
function reorderAllCards() {
const containers = Array.from(document.querySelectorAll('.feature-group-container'));
containers.forEach(function (container) {
reorderCards(container);
});
}
function initializeCardsLegacy() {
reorderAllCards();
updateFavoritesSectionLegacy();
updateFavoritesDropdown();
filterCardsLegacy();
}
function showFavoritesOnly() {
const groups = Array.from(document.querySelectorAll('.feature-group-legacy'));
if (localStorage.getItem('favoritesOnly') === 'true') {
groups.forEach((group) => {
if (group.id !== 'groupFavorites') {
group.style.display = 'none';
}
});
} else {
groups.forEach((group) => {
if (group.id !== 'groupFavorites') {
group.style.display = 'flex';
}
});
}
}
function toggleFavoritesOnly() {
if (localStorage.getItem('favoritesOnly') === 'true') {
localStorage.setItem('favoritesOnly', 'false');
} else {
localStorage.setItem('favoritesOnly', 'true');
}
showFavoritesOnly();
}
// Expands a feature group on true, collapses it on false and toggles state on null.
function expandCollapseToggle(group, expand = null) {
if (expand === null) {
group.classList.toggle('collapsed');
group.querySelector('.header-expand-button').classList.toggle('collapsed');
} else if (expand) {
group.classList.remove('collapsed');
group.querySelector('.header-expand-button').classList.remove('collapsed');
} else {
group.classList.add('collapsed');
group.querySelector('.header-expand-button').classList.add('collapsed');
}
const collapsed = localStorage.getItem('collapsedGroups') ? JSON.parse(localStorage.getItem('collapsedGroups')) : [];
const groupIndex = collapsed.indexOf(group.id);
if (group.classList.contains('collapsed')) {
if (groupIndex === -1) {
collapsed.push(group.id);
}
} else {
if (groupIndex !== -1) {
collapsed.splice(groupIndex, 1);
}
}
localStorage.setItem('collapsedGroups', JSON.stringify(collapsed));
}
function expandCollapseAll(expandAll) {
const groups = Array.from(document.querySelectorAll('.feature-group-legacy'));
groups.forEach((group) => {
expandCollapseToggle(group, expandAll);
});
}
window.onload = function () {
initializeCardsLegacy();
syncFavoritesLegacy(); // Ensure everything is in sync on page load
};
document.addEventListener('DOMContentLoaded', function () {
const materialIcons = new FontFaceObserver('Material Symbols Rounded');
materialIcons
.load()
.then(() => {
document.querySelectorAll('.feature-card.hidden').forEach((el) => {
el.classList.remove('hidden');
});
})
.catch(() => {
console.error('Material Symbols Rounded font failed to load.');
});
Array.from(document.querySelectorAll('.feature-group-header-legacy')).forEach((header) => {
const parent = header.parentNode;
const container = header.parentNode.querySelector('.feature-group-container');
if (parent.id !== 'groupFavorites') {
// container.style.maxHeight = container.scrollHeight + 'px';
}
header.onclick = () => {
expandCollapseToggle(parent);
};
});
const collapsed = localStorage.getItem('collapsedGroups') ? JSON.parse(localStorage.getItem('collapsedGroups')) : [];
const groupsArray = Array.from(document.querySelectorAll('.feature-group-legacy'));
groupsArray.forEach((group) => {
if (collapsed.indexOf(group.id) !== -1) {
expandCollapseToggle(group, false);
}
});
// Necessary in order to not fire the transition animation on page load, which looks wrong.
// The timeout isn't doing anything visible to the user, so it's not making the page load look slower.
setTimeout(() => {
groupsArray.forEach((group) => {
const container = group.querySelector('.feature-group-container');
container.classList.add('animated-group');
});
}, 500);
showFavoritesOnly();
});

View File

@ -1,159 +1,90 @@
function filterCards() { function filterCards() {
var input = document.getElementById("searchBar"); var input = document.getElementById('searchBar');
var filter = input.value.toUpperCase(); var filter = input.value.toUpperCase().trim();
let featureGroups = document.querySelectorAll(".feature-group"); // Split the input filter into individual words for multi-word matching
const collapsedGroups = getCollapsedGroups(); var filterWords = filter.split(/[\s,;.\-]+/);
let featureGroups = document.querySelectorAll('.feature-group');
for (const featureGroup of featureGroups) { for (const featureGroup of featureGroups) {
var cards = featureGroup.querySelectorAll(".feature-card"); var cards = featureGroup.querySelectorAll('.dropdown-item');
let groupMatchesFilter = false; let groupMatchesFilter = false;
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.getAttribute('title') || '';
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 navbarTags = navbarItem ? navbarTags + ',' + navbarItem.getAttribute('data-bs-title') : '';
var content = title + " " + text + " " + navbarTags; var content = (title + ' ' + navbarTags).toUpperCase();
if (content.toUpperCase().indexOf(filter) > -1) { // Check if all words in the filter match the content
card.style.display = ""; var matches = filterWords.every((word) => content.includes(word));
if (matches) {
card.style.display = '';
groupMatchesFilter = true; groupMatchesFilter = true;
} else { } else {
card.style.display = "none"; card.style.display = 'none';
} }
} }
if (!groupMatchesFilter) { if (!groupMatchesFilter) {
featureGroup.style.display = "none"; featureGroup.style.display = 'none';
} else { } else {
featureGroup.style.display = ""; featureGroup.style.display = '';
resetOrTemporarilyExpandGroup(featureGroup, filter, collapsedGroups);
} }
} }
} }
function getCollapsedGroups() {
return localStorage.getItem("collapsedGroups") ? JSON.parse(localStorage.getItem("collapsedGroups")) : [];
}
function resetOrTemporarilyExpandGroup(featureGroup, filterKeywords = "", collapsedGroups = []) {
const shouldResetCollapse = filterKeywords.trim() === "";
if (shouldResetCollapse) {
// Resetting the group's expand/collapse to its original state (as in collapsed groups)
const isCollapsed = collapsedGroups.indexOf(featureGroup.id) != -1;
expandCollapseToggle(featureGroup, !isCollapsed);
} else {
// Temporarily expands feature group without affecting the actual/stored collapsed groups
featureGroup.classList.remove("collapsed");
featureGroup.querySelector(".header-expand-button").classList.remove("collapsed");
}
}
function updateFavoritesSection() { function updateFavoritesSection() {
const favoritesContainer = document.getElementById("groupFavorites").querySelector(".feature-group-container"); const favoritesContainer = document.getElementById('groupFavorites').querySelector('.nav-group-container');
favoritesContainer.style.maxHeight = "none"; favoritesContainer.innerHTML = '';
favoritesContainer.innerHTML = ""; // Clear the container first
const cards = Array.from(document.querySelectorAll(".feature-card:not(.duplicate)"));
const addedCardIds = new Set(); // To keep track of added card IDs
let favoritesAmount = 0; let favoritesAmount = 0;
const favouritesList = JSON.parse(localStorage.getItem('favoritesList') || '[]');
const isFavoritesView = JSON.parse(localStorage.getItem('favoritesView') || 'false');
cards.forEach(card => { favouritesList.forEach((value) => {
if (localStorage.getItem(card.id) === "favorite" && !addedCardIds.has(card.id)) { var navbarEntry = document.querySelector(`a[data-bs-link='${value}']`);
const duplicate = card.cloneNode(true); if (navbarEntry) {
duplicate.classList.add("duplicate"); const duplicate = navbarEntry.cloneNode(true);
favoritesContainer.appendChild(duplicate); favoritesContainer.appendChild(duplicate);
addedCardIds.add(card.id); // Mark this card as added
favoritesAmount++;
} }
favoritesAmount++;
}); });
if (favoritesAmount === 0) { if (favoritesAmount === 0 || !isFavoritesView) {
document.getElementById("groupFavorites").style.display = "none"; document.getElementById('groupFavorites').style.display = 'none';
} else { } else {
document.getElementById("groupFavorites").style.display = "flex"; document.getElementById('groupFavorites').style.display = 'flex';
} }
reorderCards(favoritesContainer); reorderCards(favoritesContainer);
favoritesContainer.style.maxHeight = favoritesContainer.scrollHeight + "px"; //favoritesContainer.style.maxHeight = favoritesContainer.scrollHeight + 'px';
}
function toggleFavorite(element) {
var span = element.querySelector("span.material-symbols-rounded");
var card = element.closest(".feature-card");
var cardId = card.id;
// Prevent the event from bubbling up to parent elements
event.stopPropagation();
if (span.classList.contains("no-fill")) {
span.classList.remove("no-fill");
span.classList.add("fill");
card.classList.add("favorite");
localStorage.setItem(cardId, "favorite");
} else {
span.classList.remove("fill");
span.classList.add("no-fill");
card.classList.remove("favorite");
localStorage.removeItem(cardId);
}
// Use setTimeout to ensure this runs after the current call stack is clear
setTimeout(() => {
reorderCards(card.parentNode);
updateFavoritesSection();
updateFavoritesDropdown();
filterCards();
}, 0);
}
function syncFavorites() {
const cards = Array.from(document.querySelectorAll(".feature-card"));
cards.forEach(card => {
const isFavorite = localStorage.getItem(card.id) === "favorite";
const starIcon = card.querySelector(".favorite-icon span.material-symbols-rounded");
if (starIcon) {
if (isFavorite) {
starIcon.classList.remove("no-fill");
starIcon.classList.add("fill");
card.classList.add("favorite");
} else {
starIcon.classList.remove("fill");
starIcon.classList.add("no-fill");
card.classList.remove("favorite");
}
}
});
updateFavoritesSection();
updateFavoritesDropdown();
filterCards();
} }
function reorderCards(container) { function reorderCards(container) {
var cards = Array.from(container.querySelectorAll(".feature-card")); var cards = Array.from(container.querySelectorAll('.dropdown-item'));
cards.forEach(function (card) { cards.forEach(function (card) {
container.removeChild(card); container.removeChild(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 (a.id === "update-link") { if (a.id === 'update-link') {
return -1; return -1;
} }
if (b.id === "update-link") { if (b.id === 'update-link') {
return 1; return 1;
} }
if (aIsFavorite && !bIsFavorite) { if (aIsFavorite && !bIsFavorite) {
return -1; return -1;
} } else if (!aIsFavorite && bIsFavorite) {
else if (!aIsFavorite && bIsFavorite) {
return 1; return 1;
} } else {
else {
return a.id > b.id; return a.id > b.id;
} }
}); });
@ -162,136 +93,165 @@ function reorderCards(container) {
}); });
} }
function reorderAllCards() {
const containers = Array.from(document.querySelectorAll(".feature-group-container"));
containers.forEach(function (container) {
reorderCards(container);
})
}
function initializeCards() { function initializeCards() {
var cards = document.querySelectorAll(".feature-card");
cards.forEach(function (card) {
var cardId = card.id;
var span = card.querySelector(".favorite-icon span.material-symbols-rounded");
if (localStorage.getItem(cardId) === "favorite") {
span.classList.remove("no-fill");
span.classList.add("fill");
card.classList.add("favorite");
}
});
reorderAllCards();
updateFavoritesSection(); updateFavoritesSection();
updateFavoritesDropdown(); updateFavoritesDropdown();
filterCards(); filterCards();
} }
function showFavoritesOnly() { function updateFavoritesView() {
const groups = Array.from(document.querySelectorAll(".feature-group")); const isFavoritesView = JSON.parse(localStorage.getItem('favoritesView') || 'false');
if (localStorage.getItem("favoritesOnly") === "true") { const textElement = document.getElementById('toggle-favourites-text');
groups.forEach((group) => { const iconElement = document.getElementById('toggle-favourites-icon');
if (group.id !== "groupFavorites") { const favoritesGroup = document.querySelector('#groupFavorites');
group.style.display = "none"; const favoritesList = JSON.parse(localStorage.getItem('favoritesList')) || [];
}; document.getElementById('favouritesVisibility').style.display = 'flex';
})
if (isFavoritesView && favoritesList.length > 0) {
iconElement.textContent = 'visibility_off';
favoritesGroup.style.display = 'flex';
} else { } else {
groups.forEach((group) => { if (favoritesList.length > 0) {
if (group.id !== "groupFavorites") { iconElement.textContent = 'visibility';
group.style.display = "flex"; favoritesGroup.style.display = 'none';
}; } else {
}) document.getElementById('favouritesVisibility').style.display = 'none';
}; }
}
} }
function toggleFavoritesOnly() { function toggleFavoritesMode() {
if (localStorage.getItem("favoritesOnly") === "true") { const favoritesMode = !document.querySelector('.toggle-favourites').classList.contains('active');
localStorage.setItem("favoritesOnly", "false"); document.querySelector('.toggle-favourites').classList.toggle('active', favoritesMode);
document.querySelectorAll('.favorite-icon').forEach((icon) => {
const endpoint = icon.getAttribute('data-endpoint');
const parent = icon.closest('.dropdown-item');
const isInGroupRecent = parent.closest('#groupRecent') !== null;
const isInGroupFavorites = parent.closest('#groupFavorites') !== null;
if (isInGroupRecent) {
icon.style.display = 'none';
} else if (isInGroupFavorites) {
icon.style.display = favoritesMode ? 'inline-block' : 'none';
icon.textContent = 'close_small';
} else { } else {
localStorage.setItem("favoritesOnly", "true"); icon.style.display = favoritesMode ? 'inline-block' : 'none';
const favoritesList = JSON.parse(localStorage.getItem('favoritesList')) || [];
icon.textContent = favoritesList.includes(endpoint) ? 'close_small' : 'add';
}
});
document.querySelectorAll('.dropdown-item').forEach((link) => {
if (favoritesMode) {
link.dataset.originalHref = link.getAttribute('href');
link.setAttribute('href', '#');
link.classList.add('no-hover');
} else {
link.setAttribute('href', link.dataset.originalHref || '#');
link.classList.remove('no-hover');
}
});
const isFavoritesView = JSON.parse(localStorage.getItem('favoritesView') || 'false');
if (favoritesMode && !isFavoritesView) {
toggleFavoritesView();
} }
showFavoritesOnly();
} }
// Expands a feature group on true, collapses it on false and toggles state on null. function toggleFavoritesView() {
function expandCollapseToggle(group, expand = null) { const isFavoritesView = JSON.parse(localStorage.getItem('favoritesView') || 'false');
if (expand === null) { localStorage.setItem('favoritesView', !isFavoritesView);
group.classList.toggle("collapsed"); updateFavoritesView();
group.querySelector(".header-expand-button").classList.toggle("collapsed");
} else if (expand) {
group.classList.remove("collapsed");
group.querySelector(".header-expand-button").classList.remove("collapsed");
} else {
group.classList.add("collapsed");
group.querySelector(".header-expand-button").classList.add("collapsed");
}
const collapsed = localStorage.getItem("collapsedGroups") ? JSON.parse(localStorage.getItem("collapsedGroups")) : [];
const groupIndex = collapsed.indexOf(group.id);
if (group.classList.contains("collapsed")) {
if (groupIndex === -1) {
collapsed.push(group.id);
}
} else {
if (groupIndex !== -1) {
collapsed.splice(groupIndex, 1);
}
}
localStorage.setItem("collapsedGroups", JSON.stringify(collapsed));
} }
window.onload = function () {
function expandCollapseAll(expandAll) {
const groups = Array.from(document.querySelectorAll(".feature-group"));
groups.forEach((group) => {
expandCollapseToggle(group, expandAll);
})
}
window.onload = function() {
initializeCards(); initializeCards();
syncFavorites(); // Ensure everything is in sync on page load
}; };
document.addEventListener("DOMContentLoaded", function () { function sortNavElements(criteria) {
document.querySelectorAll('.nav-group-container').forEach((container) => {
const items = Array.from(container.children);
items.sort((a, b) => {
if (criteria === 'alphabetical') {
const titleA = a.querySelector('.icon-text')?.textContent.trim().toLowerCase() || '';
const titleB = b.querySelector('.icon-text')?.textContent.trim().toLowerCase() || '';
return titleA.localeCompare(titleB);
} else if (criteria === 'global') {
const popularityA = parseInt(a.dataset.popularity, 10) || 1000;
const popularityB = parseInt(b.dataset.popularity, 10) || 1000;
return popularityA - popularityB;
}
return 0;
});
container.innerHTML = '';
items.forEach((item) => container.appendChild(item));
});
}
async function fetchPopularityData(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.text();
}
function applyPopularityData(popularityData) {
document.querySelectorAll('.dropdown-item').forEach((item) => {
const endpoint = item.getAttribute('data-bs-link');
const popularity = popularityData['/' + endpoint];
if (endpoint && popularity !== undefined) {
item.setAttribute('data-popularity', popularity);
}
});
const currentSort = localStorage.getItem('homepageSort') || 'alphabetical';
const sortDropdown = document.getElementById('sort-options');
if (sortDropdown) {
sortDropdown.value = currentSort;
``;
}
sortNavElements(currentSort);
}
document.addEventListener('DOMContentLoaded', async function () {
const sortDropdown = document.getElementById('sort-options');
if (sortDropdown) {
sortDropdown.addEventListener('change', (event) => {
const selectedOption = event.target.value;
localStorage.setItem('homepageSort', selectedOption);
sortNavElements(selectedOption);
});
}
try {
const response = await fetch('files/popularity.txt');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const popularityData = await response.json();
applyPopularityData(popularityData);
} catch (error) {
console.error('Error loading popularity data:', error);
}
const materialIcons = new FontFaceObserver('Material Symbols Rounded'); const materialIcons = new FontFaceObserver('Material Symbols Rounded');
materialIcons.load().then(() => { materialIcons
document.querySelectorAll('.feature-card.hidden').forEach(el => { .load()
.then(() => {
document.querySelectorAll('.dropdown-item.hidden').forEach((el) => {
el.classList.remove('hidden'); el.classList.remove('hidden');
}); });
}).catch(() => { })
.catch(() => {
console.error('Material Symbols Rounded font failed to load.'); console.error('Material Symbols Rounded font failed to load.');
}); });
Array.from(document.querySelectorAll(".feature-group-header")).forEach(header => { Array.from(document.querySelectorAll('.feature-group-header')).forEach((header) => {
const parent = header.parentNode; const parent = header.parentNode;
const container = header.parentNode.querySelector(".feature-group-container");
if (parent.id !== "groupFavorites") {
container.style.maxHeight = container.scrollHeight + "px";
}
header.onclick = () => { header.onclick = () => {
expandCollapseToggle(parent); expandCollapseToggle(parent);
}; };
}) });
const collapsed = localStorage.getItem("collapsedGroups") ? JSON.parse(localStorage.getItem("collapsedGroups")) : [];
const groupsArray = Array.from(document.querySelectorAll(".feature-group"));
groupsArray.forEach(group => {
if (collapsed.indexOf(group.id) !== -1) {
expandCollapseToggle(group, false);
}
})
// Necessary in order to not fire the transition animation on page load, which looks wrong.
// The timeout isn't doing anything visible to the user, so it's not making the page load look slower.
setTimeout(() => {
groupsArray.forEach(group => {
const container = group.querySelector(".feature-group-container");
container.classList.add("animated-group");
})
}, 500);
showFavoritesOnly();
}); });

View File

@ -81,7 +81,7 @@ class DragDropManager {
} }
onDragEl(mouseEvent) { onDragEl(mouseEvent) {
const {clientX, clientY} = mouseEvent; const { clientX, clientY } = mouseEvent;
if (this.draggedImageEl) { if (this.draggedImageEl) {
this.draggedImageEl.style.visibility = 'visible'; this.draggedImageEl.style.visibility = 'visible';
this.draggedImageEl.style.left = `${clientX}px`; this.draggedImageEl.style.left = `${clientX}px`;
@ -174,7 +174,7 @@ class DragDropManager {
this.elementTimeouts.set(element, timeoutId); this.elementTimeouts.set(element, timeoutId);
} }
setActions({movePageTo}) { setActions({ movePageTo }) {
this.movePageTo = movePageTo; this.movePageTo = movePageTo;
} }

View File

@ -129,32 +129,37 @@ class PdfActionsManager {
const moveUp = document.createElement("button"); const moveUp = document.createElement("button");
moveUp.classList.add("pdf-actions_move-left-button", "btn", "btn-secondary"); moveUp.classList.add("pdf-actions_move-left-button", "btn", "btn-secondary");
moveUp.innerHTML = `<span class="material-symbols-rounded">arrow_${leftDirection}_alt</span><span class="btn-tooltip">${window.translations.moveLeft}</span>`; moveUp.setAttribute('title', window.translations.moveLeft);
moveUp.innerHTML = `<span class="material-symbols-rounded">arrow_${leftDirection}_alt</span>`;
moveUp.onclick = this.moveUpButtonCallback; moveUp.onclick = this.moveUpButtonCallback;
buttonContainer.appendChild(moveUp); buttonContainer.appendChild(moveUp);
const moveDown = document.createElement("button"); const moveDown = document.createElement("button");
moveDown.classList.add("pdf-actions_move-right-button", "btn", "btn-secondary"); moveDown.classList.add("pdf-actions_move-right-button", "btn", "btn-secondary");
moveDown.innerHTML = `<span class="material-symbols-rounded">arrow_${rightDirection}_alt</span><span class="btn-tooltip">${window.translations.moveRight}</span>`; moveDown.setAttribute('title', window.translations.moveRight);
moveDown.innerHTML = `<span class="material-symbols-rounded">arrow_${rightDirection}_alt</span>`;
moveDown.onclick = this.moveDownButtonCallback; moveDown.onclick = this.moveDownButtonCallback;
buttonContainer.appendChild(moveDown); buttonContainer.appendChild(moveDown);
const rotateCCW = document.createElement("button"); const rotateCCW = document.createElement("button");
rotateCCW.classList.add("btn", "btn-secondary"); rotateCCW.classList.add("btn", "btn-secondary");
rotateCCW.innerHTML = `<span class="material-symbols-rounded">rotate_left</span><span class="btn-tooltip">${window.translations.rotateLeft}</span>`; rotateCCW.setAttribute('title', window.translations.rotateLeft);
rotateCCW.innerHTML = `<span class="material-symbols-rounded">rotate_left</span>`;
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 = `<span class="material-symbols-rounded">rotate_right</span><span class="btn-tooltip">${window.translations.rotateRight}</span>`; rotateCW.setAttribute('title', window.translations.rotateRight);
rotateCW.innerHTML = `<span class="material-symbols-rounded">rotate_right</span>`;
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 = `<span class="material-symbols-rounded">delete</span><span class="btn-tooltip"></span><span class="btn-tooltip">${window.translations.delete}</span>`; deletePage.setAttribute('title', window.translations.delete);
deletePage.innerHTML = `<span class="material-symbols-rounded">delete</span>`;
deletePage.onclick = this.deletePageButtonCallback; deletePage.onclick = this.deletePageButtonCallback;
buttonContainer.appendChild(deletePage); buttonContainer.appendChild(deletePage);
@ -194,19 +199,22 @@ class PdfActionsManager {
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 = `<span class="material-symbols-rounded">add</span></span><span class="btn-tooltip">${window.translations.addFile}</span>`; moveUp.setAttribute('title', window.translations.addFile);
insertFileButton.innerHTML = `<span class="material-symbols-rounded">add</span>`;
insertFileButton.onclick = this.insertFileButtonCallback; insertFileButton.onclick = this.insertFileButtonCallback;
insertFileButtonContainer.appendChild(insertFileButton); insertFileButtonContainer.appendChild(insertFileButton);
const splitFileButton = document.createElement("button"); const splitFileButton = document.createElement("button");
splitFileButton.classList.add("btn", "btn-primary", "pdf-actions_split-file-button"); splitFileButton.classList.add("btn", "btn-primary", "pdf-actions_split-file-button");
splitFileButton.innerHTML = `<span class="material-symbols-rounded">cut</span></span><span class="btn-tooltip">${window.translations.split}</span>`; splitFileButton.setAttribute('title', window.translations.split);
splitFileButton.innerHTML = `<span class="material-symbols-rounded">cut</span>`;
splitFileButton.onclick = this.splitFileButtonCallback; splitFileButton.onclick = this.splitFileButtonCallback;
insertFileButtonContainer.appendChild(splitFileButton); insertFileButtonContainer.appendChild(splitFileButton);
const insertFileBlankButton = document.createElement("button"); const insertFileBlankButton = document.createElement("button");
insertFileBlankButton.classList.add("btn", "btn-primary", "pdf-actions_insert-file-blank-button"); insertFileBlankButton.classList.add("btn", "btn-primary", "pdf-actions_insert-file-blank-button");
insertFileBlankButton.innerHTML = `<span class="material-symbols-rounded">insert_page_break</span></span><span class="btn-tooltip">${window.translations.insertPageBreak}</span>`; insertFileBlankButton.setAttribute('title', window.translations.insertPageBreak);
insertFileBlankButton.innerHTML = `<span class="material-symbols-rounded">insert_page_break</span>`;
insertFileBlankButton.onclick = this.insertFileBlankButtonCallback; insertFileBlankButton.onclick = this.insertFileBlankButtonCallback;
insertFileButtonContainer.appendChild(insertFileBlankButton); insertFileButtonContainer.appendChild(insertFileBlankButton);
@ -261,10 +269,8 @@ class PdfActionsManager {
document.addEventListener("selectedPagesUpdated", () => { document.addEventListener("selectedPagesUpdated", () => {
window.updateSelectedPagesDisplay(); window.updateSelectedPagesDisplay();
}); });
return div; return div;
} }
} }
export default PdfActionsManager; export default PdfActionsManager;

View File

@ -1,11 +1,11 @@
import {MovePageUpCommand, MovePageDownCommand} from './commands/move-page.js'; import { MovePageUpCommand, MovePageDownCommand } from './commands/move-page.js';
import {RemoveSelectedCommand} from './commands/remove.js'; import { RemoveSelectedCommand } from './commands/remove.js';
import {RotateAllCommand, RotateElementCommand} from './commands/rotate.js'; import { RotateAllCommand, RotateElementCommand } from './commands/rotate.js';
import {SplitAllCommand} from './commands/split.js'; import { SplitAllCommand } from './commands/split.js';
import {UndoManager} from './UndoManager.js'; import { UndoManager } from './UndoManager.js';
import {PageBreakCommand} from './commands/page-break.js'; import { PageBreakCommand } from './commands/page-break.js';
import {AddFilesCommand} from './commands/add-page.js'; import { AddFilesCommand } from './commands/add-page.js';
import {DecryptFile} from '../DecryptFiles.js'; import { DecryptFile } from '../DecryptFiles.js';
class PdfContainer { class PdfContainer {
fileName; fileName;
@ -144,6 +144,8 @@ class PdfContainer {
await addFilesCommand.execute(); await addFilesCommand.execute();
this.undoManager.pushUndoClearRedo(addFilesCommand); this.undoManager.pushUndoClearRedo(addFilesCommand);
window.tooltipSetup();
} }
async addFilesAction(nextSiblingElement) { async addFilesAction(nextSiblingElement) {
@ -212,7 +214,7 @@ class PdfContainer {
} }
if (decryptedFile.type === 'application/pdf') { if (decryptedFile.type === 'application/pdf') {
const {renderer, pdfDocument} = await this.loadFile(decryptedFile); const { renderer, pdfDocument } = await this.loadFile(decryptedFile);
pageCount = renderer.pageCount || 0; pageCount = renderer.pageCount || 0;
pages = await this.addPdfFile(renderer, pdfDocument, nextSiblingElement, pages); pages = await this.addPdfFile(renderer, pdfDocument, nextSiblingElement, pages);
} else if (decryptedFile.type.startsWith('image/')) { } else if (decryptedFile.type.startsWith('image/')) {
@ -247,14 +249,14 @@ class PdfContainer {
pdf_pages: pageCount, pdf_pages: pageCount,
}); });
} }
} catch {} } catch { }
} }
async addFilesBlank(nextSiblingElement, pages) { async addFilesBlank(nextSiblingElement, pages) {
let doc = await PDFLib.PDFDocument.create(); let doc = await PDFLib.PDFDocument.create();
let docBytes = await doc.save(); let docBytes = await doc.save();
const url = URL.createObjectURL(new Blob([docBytes], {type: 'application/pdf'})); const url = URL.createObjectURL(new Blob([docBytes], { type: 'application/pdf' }));
const renderer = await this.toRenderer(url); const renderer = await this.toRenderer(url);
pages = await this.addPdfFile(renderer, doc, nextSiblingElement, pages); pages = await this.addPdfFile(renderer, doc, nextSiblingElement, pages);
@ -324,7 +326,7 @@ class PdfContainer {
var objectUrl = URL.createObjectURL(file); var objectUrl = URL.createObjectURL(file);
var pdfDocument = await this.toPdfLib(objectUrl); var pdfDocument = await this.toPdfLib(objectUrl);
var renderer = await this.toRenderer(objectUrl); var renderer = await this.toRenderer(objectUrl);
return {renderer, pdfDocument}; return { renderer, pdfDocument };
} }
async toRenderer(objectUrl) { async toRenderer(objectUrl) {
@ -350,7 +352,7 @@ class PdfContainer {
// render the page onto the canvas // render the page onto the canvas
var renderContext = { var renderContext = {
canvasContext: canvas.getContext('2d'), canvasContext: canvas.getContext('2d'),
viewport: page.getViewport({scale: 1}), viewport: page.getViewport({ scale: 1 }),
}; };
await page.render(renderContext).promise; await page.render(renderContext).promise;
@ -604,7 +606,7 @@ class PdfContainer {
let firstPage = splitterIndex === 0 ? 0 : splitters[splitterIndex - 1]; let firstPage = splitterIndex === 0 ? 0 : splitters[splitterIndex - 1];
const pageIndices = Array.from({length: splitterPosition - firstPage}, (value, key) => firstPage + key); const pageIndices = Array.from({ length: splitterPosition - firstPage }, (value, key) => firstPage + key);
const copiedPages = await subDocument.copyPages(baseDocument, pageIndices); const copiedPages = await subDocument.copyPages(baseDocument, pageIndices);
@ -687,7 +689,7 @@ class PdfContainer {
pdfDoc.setProducer(stirlingPDFLabel); pdfDoc.setProducer(stirlingPDFLabel);
const pdfBytes = await pdfDoc.save(); const pdfBytes = await pdfDoc.save();
const pdfBlob = new Blob([pdfBytes], {type: 'application/pdf'}); const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' });
const filenameInput = document.getElementById('filename-input'); const filenameInput = document.getElementById('filename-input');
@ -722,7 +724,7 @@ class PdfContainer {
const archivedDocuments = await this.nameAndArchiveFiles(splitDocuments, baseName); const archivedDocuments = await this.nameAndArchiveFiles(splitDocuments, baseName);
const self = this; const self = this;
archivedDocuments.generateAsync({type: 'base64'}).then(function (base64) { archivedDocuments.generateAsync({ type: 'base64' }).then(function (base64) {
const url = 'data:application/zip;base64,' + base64; const url = 'data:application/zip;base64,' + base64;
self.downloadLink = document.createElement('a'); self.downloadLink = document.createElement('a');
self.downloadLink.href = url; self.downloadLink.href = url;

View File

@ -1,27 +1,25 @@
function toolsManager() { function toolsManager() {
document.addEventListener('DOMContentLoaded', function () { const convertToPDF = document.querySelector('#groupConvertTo');
const stackedContainer = document.getElementById('stacked'); const convertFromPDF = document.querySelector('#groupConvertFrom');
if (stackedContainer) {
const convertToPDF = stackedContainer.querySelector('.navbar-item:first-child');
const convertFromPDF = stackedContainer.querySelector('.navbar-item:nth-child(2)');
if (convertToPDF && convertFromPDF) { if (convertToPDF && convertFromPDF) {
const dropdownItemsTo = convertToPDF.querySelectorAll('.dropdown-item'); const itemsTo = Array.from(convertToPDF.querySelectorAll('.dropdown-item')).filter(
const dropdownItemsFrom = convertFromPDF.querySelectorAll('.dropdown-item'); (item) => !item.querySelector('hr.dropdown-divider')
);
const itemsTo = Array.from(dropdownItemsTo).filter((item) => !item.querySelector('hr.dropdown-divider')); const itemsFrom = Array.from(convertFromPDF.querySelectorAll('.dropdown-item')).filter(
const itemsFrom = Array.from(dropdownItemsFrom).filter((item) => !item.querySelector('hr.dropdown-divider')); (item) => !item.querySelector('hr.dropdown-divider')
);
const totalItems = itemsTo.length + itemsFrom.length; const totalItems = itemsTo.length + itemsFrom.length;
if (totalItems > 12) { if (totalItems > 12) {
stackedContainer.style.flexDirection = 'row'; document.querySelectorAll('#convertGroup').forEach((element) => (element.style.display = 'none'));
stackedContainer.classList.remove('col-lg-2'); document.querySelectorAll('#groupConvertTo').forEach((element) => (element.style.display = 'flex'));
stackedContainer.classList.add('col-lg-4'); document.querySelectorAll('#groupConvertFrom').forEach((element) => (element.style.display = 'flex'));
convertToPDF.style.flex = '1 1 50%'; } else {
convertFromPDF.style.flex = '1 1 50%'; document.querySelectorAll('#convertGroup').forEach((element) => (element.style.display = 'flex'));
} document.querySelectorAll('#groupConvertTo').forEach((element) => (element.style.display = 'none'));
document.querySelectorAll('#groupConvertFrom').forEach((element) => (element.style.display = 'none'));
} }
} }
@ -42,5 +40,40 @@ function toolsManager() {
} }
} }
}); });
}
window.tooltipSetup = () => {
const tooltipElements = document.querySelectorAll("[title]");
tooltipElements.forEach((element) => {
const tooltipText = element.getAttribute("title");
element.removeAttribute("title");
const customTooltip = document.createElement("div");
customTooltip.className = "btn-tooltip";
customTooltip.textContent = tooltipText;
document.body.appendChild(customTooltip);
element.addEventListener("mouseenter", (event) => {
customTooltip.style.display = "block";
customTooltip.style.left = `${event.pageX + 10}px`; // Position tooltip slightly away from the cursor
customTooltip.style.top = `${event.pageY + 10}px`;
});
// Update the position of the tooltip as the user moves the mouse
element.addEventListener("mousemove", (event) => {
customTooltip.style.left = `${event.pageX + 10}px`;
customTooltip.style.top = `${event.pageY + 10}px`;
});
// Hide the tooltip when the mouse leaves
element.addEventListener("mouseleave", () => {
customTooltip.style.display = "none";
});
}); });
} }
document.addEventListener("DOMContentLoaded", () => {
tooltipSetup();
});

View File

@ -0,0 +1,182 @@
/*<![CDATA[*/
document.addEventListener('DOMContentLoaded', function () {
if (window.analyticsPromptBoolean) {
const analyticsModal = new bootstrap.Modal(document.getElementById('analyticsModal'));
analyticsModal.show();
}
});
/*]]>*/
function setAnalytics(enabled) {
fetchWithCsrf('api/v1/settings/update-enable-analytics', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(enabled),
})
.then((response) => {
if (response.status === 200) {
console.log('Analytics setting updated successfully');
bootstrap.Modal.getInstance(document.getElementById('analyticsModal')).hide();
} else if (response.status === 208) {
console.log('Analytics setting has already been set. Please edit /config/settings.yml to change it.', response);
alert('Analytics setting has already been set. Please edit /config/settings.yml to change it.');
} else {
throw new Error('Unexpected response status: ' + response.status);
}
})
.catch((error) => {
console.error('Error updating analytics setting:', error);
alert('An error occurred while updating the analytics setting. Please try again.');
});
}
updateFavoriteIcons();
const defaultView = localStorage.getItem('defaultView') || 'home'; // Default to "home"
if (defaultView === 'home-legacy') {
window.location.href = '/home-legacy'; // Redirect to legacy view
}
document.addEventListener('DOMContentLoaded', function () {
const surveyVersion = '2.0';
const modal = new bootstrap.Modal(document.getElementById('surveyModal'));
const dontShowAgain = document.getElementById('dontShowAgain');
const takeSurveyButton = document.getElementById('takeSurvey');
const viewThresholds = [5, 10, 15, 22, 30, 50, 75, 100, 150, 200];
// Check if survey version changed and reset page views if it did
const storedVersion = localStorage.getItem('surveyVersion');
if (storedVersion && storedVersion !== surveyVersion) {
localStorage.setItem('pageViews', '0');
}
let pageViews = parseInt(localStorage.getItem('pageViews') || '0');
pageViews++;
localStorage.setItem('pageViews', pageViews.toString());
function shouldShowSurvey() {
if (localStorage.getItem('dontShowSurvey') === 'true' || localStorage.getItem('surveyTaken') === 'true') {
return false;
}
// If survey version changed and we hit a threshold, show the survey
if (localStorage.getItem('surveyVersion') !== surveyVersion && viewThresholds.includes(pageViews)) {
return true;
}
return viewThresholds.includes(pageViews);
}
if (shouldShowSurvey()) {
modal.show();
}
dontShowAgain.addEventListener('change', function () {
if (this.checked) {
localStorage.setItem('dontShowSurvey', 'true');
localStorage.setItem('surveyVersion', surveyVersion);
} else {
localStorage.removeItem('dontShowSurvey');
localStorage.removeItem('surveyVersion');
}
});
takeSurveyButton.addEventListener('click', function () {
localStorage.setItem('surveyTaken', 'true');
localStorage.setItem('surveyVersion', surveyVersion);
modal.hide();
});
if (localStorage.getItem('dontShowSurvey')) {
modal.hide();
}
if (window.location.pathname === '/') {
const navItem = document.getElementById('navItemToHide');
if (navItem) {
navItem.style.display = 'none';
}
}
updateFavoritesDropdown();
});
function setAsDefault(value) {
localStorage.setItem('defaultView', value);
console.log(`Default view set to: ${value}`);
}
function adjustVisibleElements() {
const container = document.querySelector('.recent-features');
const subElements = Array.from(container.children);
let totalWidth = 0;
const containerWidth = container.offsetWidth;
subElements.forEach((element) => {
totalWidth += 12 * parseFloat(getComputedStyle(document.documentElement).fontSize);
if (totalWidth > window.innerWidth) {
element.style.display = 'none';
} else {
element.style.display = 'block';
}
});
}
function adjustContainerAlignment() {
console.log('Adjusting container alignment');
document.querySelectorAll('.features-container').forEach((parent) => {
parent.querySelectorAll('.feature-rows').forEach((container) => {
const childElements = Array.from(container.children);
const containerWidth = parent.offsetWidth;
console.log(containerWidth < 32 * parseFloat(getComputedStyle(document.documentElement).fontSize));
if (containerWidth < 32 * parseFloat(getComputedStyle(document.documentElement).fontSize)) {
container.classList.add('single-column');
} else {
container.classList.remove('single-column');
}
});
});
}
function toolsManager() {
const convertToPDF = document.querySelector('#groupConvertTo');
const convertFromPDF = document.querySelector('#groupConvertFrom');
if (convertToPDF && convertFromPDF) {
const itemsTo = Array.from(convertToPDF.querySelectorAll('.dropdown-item')).filter(
(item) => !item.querySelector('hr.dropdown-divider')
);
const itemsFrom = Array.from(convertFromPDF.querySelectorAll('.dropdown-item')).filter(
(item) => !item.querySelector('hr.dropdown-divider')
);
const totalItems = itemsTo.length + itemsFrom.length;
if (totalItems > 12) {
document.querySelectorAll('#convertGroup').forEach((element) => element.remove());
document.querySelectorAll('#groupConvertTo').forEach((element) => (element.style.display = 'flex'));
document.querySelectorAll('#groupConvertFrom').forEach((element) => (element.style.display = 'flex'));
} else {
document.querySelectorAll('#convertGroup').forEach((element) => (element.style.display = 'flex'));
document.querySelectorAll('#groupConvertTo').forEach((element) => element.remove());
document.querySelectorAll('#groupConvertFrom').forEach((element) => element.remove());
}
}
}
document.addEventListener('DOMContentLoaded', function () {
toolsManager();
});
window.addEventListener('load', () => {
adjustContainerAlignment();
adjustVisibleElements();
});
window.addEventListener('resize', () => {
adjustContainerAlignment();
adjustVisibleElements();
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -732,6 +732,7 @@
} }
:root{ :root{
--navbar-height: 64px;
--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;
} }
@ -3043,8 +3044,9 @@ body{
#outerContainer{ #outerContainer{
width:100%; width:100%;
height:100%; height: calc(100% - var(--navbar-height));
position:relative; position:relative;
} }
#sidebarContainer{ #sidebarContainer{

File diff suppressed because it is too large Load Diff

View File

@ -16,12 +16,14 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon convert">draft</span> <span class="material-symbols-rounded tool-header-icon convertto">draft</span>
<span class="tool-header-text" th:text="#{fileToPDF.header}"></span> <span class="tool-header-text" th:text="#{fileToPDF.header}"></span>
</div> </div>
<p th:text="#{processTimeWarning}"></p> <p th:text="#{processTimeWarning}"></p>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/file/pdf'}"> <form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/file/pdf'}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false)}"></div> <div
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false)}">
</div>
<a class="btn btn-outline-primary" data-bs-toggle="collapse" href="#info" role="button" <a class="btn btn-outline-primary" data-bs-toggle="collapse" href="#info" role="button"
aria-expanded="false" aria-controls="info" th:text="#{fileToPDF.supportedFileTypesInfo}"></a> aria-expanded="false" aria-controls="info" th:text="#{fileToPDF.supportedFileTypesInfo}"></a>
<div class="collapse" id="info"> <div class="collapse" id="info">
@ -42,7 +44,7 @@
href="https://help.libreoffice.org/latest/en-US/text/shared/guide/supported_formats.html">https://help.libreoffice.org/latest/en-US/text/shared/guide/supported_formats.html</a> href="https://help.libreoffice.org/latest/en-US/text/shared/guide/supported_formats.html">https://help.libreoffice.org/latest/en-US/text/shared/guide/supported_formats.html</a>
</div> </div>
<div> <div>
<br/> <br />
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{fileToPDF.submit}"></button> <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{fileToPDF.submit}"></button>
</div> </div>
</form> </form>

View File

@ -1,10 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org"> <html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
<head> xmlns:th="https://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{HTMLToPDF.title}, header=#{HTMLToPDF.header})}"></th:block>
</head>
<body> <head>
<th:block th:insert="~{fragments/common :: head(title=#{HTMLToPDF.title}, header=#{HTMLToPDF.header})}"></th:block>
</head>
<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">
@ -14,11 +16,13 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon convert">html</span> <span class="material-symbols-rounded tool-header-icon convertto">html</span>
<span class="tool-header-text" th:text="#{HTMLToPDF.header}"></span> <span class="tool-header-text" th:text="#{HTMLToPDF.header}"></span>
</div> </div>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/html/pdf'}"> <form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/html/pdf'}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='text/html,application/zip' )}"></div> <div
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='text/html,application/zip' )}">
</div>
<div class="mb-3"> <div class="mb-3">
<label for="zoom" th:text="#{HTMLToPDF.zoom}" class="form-label"></label> <label for="zoom" th:text="#{HTMLToPDF.zoom}" class="form-label"></label>
<input type="number" step="0.1" class="form-control" id="zoom" name="zoom" value="1"> <input type="number" step="0.1" class="form-control" id="zoom" name="zoom" value="1">
@ -34,5 +38,6 @@
</div> </div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,10 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org"> <html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
<head> xmlns:th="https://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{imageToPDF.title}, header=#{imageToPDF.header})}"></th:block>
</head>
<body> <head>
<th:block th:insert="~{fragments/common :: head(title=#{imageToPDF.title}, header=#{imageToPDF.header})}"></th:block>
</head>
<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">
@ -14,17 +16,22 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon image">image</span> <span class="material-symbols-rounded tool-header-icon convertto">picture_as_pdf</span>
<span class="tool-header-text" th:text="#{imageToPDF.header}"></span> <span class="tool-header-text" th:text="#{imageToPDF.header}"></span>
</div> </div>
<form id="imageToPDFForm" method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/img/pdf'}"> <form id="imageToPDFForm" method="post" enctype="multipart/form-data"
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='image/*', inputText=#{imgPrompt})}"></div> th:action="@{'/api/v1/convert/img/pdf'}">
<div
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='image/*', inputText=#{imgPrompt})}">
</div>
<div class="mb-3"> <div class="mb-3">
<label for="fitOption" th:text="#{imageToPDF.selectLabel}">Fit Options</label> <label for="fitOption" th:text="#{imageToPDF.selectLabel}">Fit Options</label>
<select class="form-control" id="fitOption" name="fitOption"> <select class="form-control" id="fitOption" name="fitOption">
<option value="fillPage" th:text="#{imageToPDF.fillPage}">Fill Page</option> <option value="fillPage" th:text="#{imageToPDF.fillPage}">Fill Page</option>
<option value="fitDocumentToImage" th:text="#{imageToPDF.fitDocumentToImage}">Fit Document to Image</option> <option value="fitDocumentToImage" th:text="#{imageToPDF.fitDocumentToImage}">Fit Document to Image
<option value="maintainAspectRatio" th:text="#{imageToPDF.maintainAspectRatio}">Maintain Aspect Ratio</option> </option>
<option value="maintainAspectRatio" th:text="#{imageToPDF.maintainAspectRatio}">Maintain Aspect Ratio
</option>
</select> </select>
</div> </div>
@ -51,10 +58,10 @@
<br> <br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button> <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
<script> <script>
$('#fileInput-input').on('change', function(e) { $('#fileInput-input').on('change', function (e) {
const fileInput = e.target; const fileInput = e.target;
fileInput.addEventListener('file-input-change', async (e) => { fileInput.addEventListener('file-input-change', async (e) => {
const {allFiles} = e.detail; const { allFiles } = e.detail;
var conversionType = document.getElementById("conversionType"); var conversionType = document.getElementById("conversionType");
console.log("files.length=" + allFiles.length) console.log("files.length=" + allFiles.length)
if (allFiles.length > 1) { if (allFiles.length > 1) {
@ -65,7 +72,7 @@
}); });
}); });
$('#conversionType').change(function() { $('#conversionType').change(function () {
var selectedValue = $(this).val(); var selectedValue = $(this).val();
var override = document.getElementById("override"); var override = document.getElementById("override");
console.log("selectedValue=" + selectedValue) console.log("selectedValue=" + selectedValue)
@ -106,7 +113,7 @@
} catch (error) { } catch (error) {
console.error("Error submitting the form:", error); console.error("Error submitting the form:", error);
} }
}); });
</script> </script>
</form> </form>
</div> </div>
@ -115,5 +122,6 @@
</div> </div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,10 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org"> <html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
<head> xmlns:th="https://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{MarkdownToPDF.title}, header=#{MarkdownToPDF.header})}"></th:block>
</head>
<body> <head>
<th:block th:insert="~{fragments/common :: head(title=#{MarkdownToPDF.title}, header=#{MarkdownToPDF.header})}">
</th:block>
</head>
<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">
@ -14,11 +17,13 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon convert">markdown</span> <span class="material-symbols-rounded tool-header-icon convertto">markdown</span>
<span class="tool-header-text" th:text="#{MarkdownToPDF.header}"></span> <span class="tool-header-text" th:text="#{MarkdownToPDF.header}"></span>
</div> </div>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/markdown/pdf'}"> <form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/markdown/pdf'}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='.md')}"></div> <div
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='.md')}">
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{MarkdownToPDF.submit}"></button> <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{MarkdownToPDF.submit}"></button>
</form> </form>
<p class="mt-3" th:text="#{MarkdownToPDF.help}"></p> <p class="mt-3" th:text="#{MarkdownToPDF.help}"></p>
@ -29,5 +34,6 @@
</div> </div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,10 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org"> <html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
<head> xmlns:th="https://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{pdfToImage.title}, header=#{pdfToImage.header})}"></th:block>
</head>
<body> <head>
<th:block th:insert="~{fragments/common :: head(title=#{pdfToImage.title}, header=#{pdfToImage.header})}"></th:block>
</head>
<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">
@ -14,13 +16,16 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon image">image</span> <span class="material-symbols-rounded tool-header-icon convert">photo_library</span>
<span class="tool-header-text" th:text="#{pdfToImage.header}"></span> <span class="tool-header-text" th:text="#{pdfToImage.header}"></span>
</div> </div>
<p th:text="#{processTimeWarning}"></p> <p th:text="#{processTimeWarning}"></p>
<p th:if="${!isPython}" th:text="#{pdfToImage.info}">Python is not installed. Required for WebP conversion.</p> <p th:if="${!isPython}" th:text="#{pdfToImage.info}">Python is not installed. Required for WebP conversion.
</p>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/pdf/img'}"> <form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/pdf/img'}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div> <div
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
</div>
<div class="mb-3"> <div class="mb-3">
<label th:text="#{pdfToImage.selectText}"></label> <label th:text="#{pdfToImage.selectText}"></label>
<select class="form-control" name="imageFormat"> <select class="form-control" name="imageFormat">
@ -40,7 +45,7 @@
</select> </select>
</div> </div>
<div class="mb-3" id="singleOptionSection" > <div class="mb-3" id="singleOptionSection">
<label for="pageNumbers" th:text="#{pageSelectionPrompt}"></label> <label for="pageNumbers" th:text="#{pageSelectionPrompt}"></label>
<input type="text" name="pageNumbers" class="form-control" id="pageNumbers" value="all" <input type="text" name="pageNumbers" class="form-control" id="pageNumbers" value="all"
th:placeholder="#{pdfToImage.placeholder}" required> th:placeholder="#{pdfToImage.placeholder}" required>
@ -66,5 +71,6 @@
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,10 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org"> <html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
<head> xmlns:th="https://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{PDFToPresentation.title}, header=#{PDFToPresentation.header})}"></th:block>
</head>
<body> <head>
<th:block
th:insert="~{fragments/common :: head(title=#{PDFToPresentation.title}, header=#{PDFToPresentation.header})}">
</th:block>
</head>
<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">
@ -14,11 +18,13 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon ppt">slideshow</span> <span class="material-symbols-rounded tool-header-icon convert">slideshow</span>
<span class="tool-header-text" th:text="#{PDFToPresentation.header}"></span> <span class="tool-header-text" th:text="#{PDFToPresentation.header}"></span>
</div> </div>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/pdf/presentation'}"> <form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/pdf/presentation'}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div> <div
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
</div>
<div class="mb-3"> <div class="mb-3">
<label th:text="#{PDFToPresentation.selectText.1}"></label> <label th:text="#{PDFToPresentation.selectText.1}"></label>
<select class="form-control" name="outputFormat"> <select class="form-control" name="outputFormat">
@ -28,7 +34,8 @@
</select> </select>
</div> </div>
<br> <br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToPresentation.submit}"></button> <button type="submit" id="submitBtn" class="btn btn-primary"
th:text="#{PDFToPresentation.submit}"></button>
</form> </form>
<p class="mt-3" th:text="#{PDFToPresentation.credit}"></p> <p class="mt-3" th:text="#{PDFToPresentation.credit}"></p>
</div> </div>
@ -37,5 +44,6 @@
</div> </div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,10 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org"> <html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
<head> xmlns:th="https://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{PDFToWord.title}, header=#{PDFToWord.header})}"></th:block>
</head>
<body> <head>
<th:block th:insert="~{fragments/common :: head(title=#{PDFToWord.title}, header=#{PDFToWord.header})}"></th:block>
</head>
<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">
@ -14,11 +16,13 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon word">description</span> <span class="material-symbols-rounded tool-header-icon convert">description</span>
<span class="tool-header-text" th:text="#{PDFToWord.header}"></span> <span class="tool-header-text" th:text="#{PDFToWord.header}"></span>
</div> </div>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/pdf/word'}"> <form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/pdf/word'}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div> <div
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
</div>
<div class="mb-3"> <div class="mb-3">
<label th:text="#{PDFToWord.selectText.1}"></label> <label th:text="#{PDFToWord.selectText.1}"></label>
<select class="form-control" name="outputFormat"> <select class="form-control" name="outputFormat">
@ -37,5 +41,6 @@
</div> </div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,10 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org"> <html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
<head> xmlns:th="https://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{URLToPDF.title}, header=#{URLToPDF.header})}"></th:block>
</head>
<body> <head>
<th:block th:insert="~{fragments/common :: head(title=#{URLToPDF.title}, header=#{URLToPDF.header})}"></th:block>
</head>
<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">
@ -14,7 +16,7 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon convert">link</span> <span class="material-symbols-rounded tool-header-icon convertto">link</span>
<span class="tool-header-text" th:text="#{URLToPDF.header}"></span> <span class="tool-header-text" th:text="#{URLToPDF.header}"></span>
</div> </div>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/url/pdf'}"> <form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/convert/url/pdf'}">
@ -29,5 +31,6 @@
</div> </div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,5 +1,6 @@
<div th:fragment="card" class="feature-card hidden" th:id="${id}" th:if="${@endpointConfiguration.isEndpointEnabled(cardLink)}" <div th:fragment="card" class="feature-card hidden" th:id="${id}" th:if="${@endpointConfiguration.isEndpointEnabled(cardLink)} "
th:data-bs-tags="${tags}"> th:data-bs-tags="${tags}"
th:data-bs-link="@{${endpoint}}">
<a th:href="${cardLink}"> <a th:href="${cardLink}">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
@ -14,10 +15,7 @@
</div> </div>
</a> </a>
<div class="favorite-icon" onclick="toggleFavorite(this)"> <span class="material-symbols-rounded favorite-icon" th:data-endpoint="@{${endpoint}}" th:onclick="'addToFavorites(\'' + @{${cardLink}} + '\')'">
<span class="material-symbols-rounded">
star star
</span> </span>
</div>
</div>
</div> </div>

View File

@ -52,6 +52,7 @@
<link rel="stylesheet" th:href="@{'/css/error.css'}" th:if="${error}"> <link rel="stylesheet" th:href="@{'/css/error.css'}" th:if="${error}">
<link rel="stylesheet" th:href="@{'/css/home.css'}" th:if="${currentPage == 'home'}"> <link rel="stylesheet" th:href="@{'/css/home.css'}" th:if="${currentPage == 'home'}">
<link rel="stylesheet" th:href="@{'/css/home-legacy.css'}" th:if="${currentPage == 'home-legacy'}">
<link rel="stylesheet" th:href="@{'/css/account.css'}" th:if="${currentPage == 'account'}"> <link rel="stylesheet" th:href="@{'/css/account.css'}" th:if="${currentPage == 'account'}">
<link rel="stylesheet" th:href="@{'/css/licenses.css'}" th:if="${currentPage == 'licenses'}"> <link rel="stylesheet" th:href="@{'/css/licenses.css'}" th:if="${currentPage == 'licenses'}">
<link rel="stylesheet" th:href="@{'/css/multi-tool.css'}" th:if="${currentPage == 'multi-tool'}"> <link rel="stylesheet" th:href="@{'/css/multi-tool.css'}" th:if="${currentPage == 'multi-tool'}">
@ -117,13 +118,13 @@
</th:block> </th:block>
<th:block th:fragment="game"> <th:block th:fragment="game">
<dialog id="game-container-wrapper" class="game-container-wrapper" data-bs-modal> <dialog id="game-container-wrapper" class="game-container-wrapper" data-bs-modal>
<script th:inline="javascript"> <script th:inline="javascript">
console.log("loaded game"); console.log("loaded game");
$(document).ready(function() { $(document).ready(function () {
// Find the file input within the form // Find the file input within the form
var fileInput = $('input[type="file"]'); var fileInput = $('input[type="file"]');
@ -152,11 +153,11 @@
} }
let gameScriptLoaded = false; let gameScriptLoaded = false;
const gameDialog = document.getElementById('game-container-wrapper'); const gameDialog = document.getElementById('game-container-wrapper');
$('#show-game-btn').on('click', function() { $('#show-game-btn').on('click', function () {
console.log('Show game button clicked'); console.log('Show game button clicked');
if (!gameScriptLoaded) { if (!gameScriptLoaded) {
console.log('Show game button load'); console.log('Show game button load');
loadGameScript(function() { loadGameScript(function () {
console.log('Game script loaded'); console.log('Game script loaded');
window.initializeGame(); window.initializeGame();
gameScriptLoaded = true; gameScriptLoaded = true;
@ -190,9 +191,10 @@
</dialog> </dialog>
</th:block> </th:block>
<th:block th:fragment="fileSelector(name, multipleInputsForSingleRequest)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: true, disableMultipleFiles=${disableMultipleFiles} ?: false, notRequired=${notRequired} ?: false"> <th:block th:fragment="fileSelector(name, multipleInputsForSingleRequest)"
th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: true, disableMultipleFiles=${disableMultipleFiles} ?: false, notRequired=${notRequired} ?: false">
<script th:inline="javascript"> <script th:inline="javascript">
(function() { (function () {
window.stirlingPDF.pdfPasswordPrompt = /*[[#{error.pdfPassword}]]*/ ''; window.stirlingPDF.pdfPasswordPrompt = /*[[#{error.pdfPassword}]]*/ '';
window.stirlingPDF.multipleInputsForSingleRequest = /*[[${multipleInputsForSingleRequest}]]*/ false; window.stirlingPDF.multipleInputsForSingleRequest = /*[[${multipleInputsForSingleRequest}]]*/ false;
window.stirlingPDF.disableMultipleFiles = /*[[${disableMultipleFiles}]]*/ false; window.stirlingPDF.disableMultipleFiles = /*[[${disableMultipleFiles}]]*/ false;
@ -216,12 +218,16 @@
success: '[[#{decrypt.success}]]', success: '[[#{decrypt.success}]]',
}; };
window.fileInput = { window.fileInput = {
dragAndDropPDF : '[[#{fileChooser.dragAndDropPDF}]]', dragAndDropPDF: '[[#{fileChooser.dragAndDropPDF}]]',
dragAndDropImage : '[[#{fileChooser.dragAndDropImage}]]'};</script> dragAndDropImage: '[[#{fileChooser.dragAndDropImage}]]'
<div class="custom-file-chooser mb-3" th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-element-container-id=${name+'-input-container'}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}"> };</script>
<div class="mb-3 d-flex flex-row justify-content-center align-items-center flex-wrap input-container" th:name="${name}+'-input'" th:id="${name}+'-input-container'" th:data-text="#{fileChooser.hoveredDragAndDrop}"> <div class="custom-file-chooser mb-3"
th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-element-container-id=${name+'-input-container'}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}">
<div class="mb-3 d-flex flex-row justify-content-center align-items-center flex-wrap input-container"
th:name="${name}+'-input'" th:id="${name}+'-input-container'" th:data-text="#{fileChooser.hoveredDragAndDrop}">
<label class="file-input-btn d-none"> <label class="file-input-btn d-none">
<input type="file" class="form-control" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" th:attr="multiple=${!disableMultipleFiles}" th:required="${notRequired} ? null : 'required'"> <input type="file" class="form-control" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}"
th:attr="multiple=${!disableMultipleFiles}" th:required="${notRequired} ? null : 'required'">
Browse Browse
</label> </label>
<div th:text="#{fileChooser.click}"></div> <div th:text="#{fileChooser.click}"></div>
@ -233,7 +239,8 @@
<div class="progressBarContainer" style="display: none; position: relative;"> <div class="progressBarContainer" style="display: none; position: relative;">
<div class="progress" style="height: 1rem;"> <div class="progress" style="height: 1rem;">
<div class="progressBar progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"> <div class="progressBar progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden">Loading...</span>
</div> </div>
</div> </div>

View File

@ -1,6 +1,3 @@
<div th:fragment="featureGroupHeader" class="feature-group-header"> <div th:fragment="featureGroupHeader" class="feature-group-header">
<h3 class="menu-title" th:text="${groupTitle}"></h3> <h6 class="menu-title" th:text="${groupTitle}"></h6>
<span class="material-symbols-rounded header-expand-button">
chevron_right
</span>
</div> </div>

View File

@ -0,0 +1,6 @@
<div th:fragment="featureGroupHeader" class="feature-group-header">
<h3 class="menu-title" th:text="${groupTitle}"></h3>
<span class="material-symbols-rounded header-expand-button">
chevron_right
</span>
</div>

View File

@ -0,0 +1,296 @@
<th:block th:fragment="navElements">
<div id="groupOrganize" class="feature-group">
<div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.organize})}">
</div>
<div class="nav-group-container">
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('multi-tool', 'construction', 'home.multiTool.title', 'home.multiTool.desc', 'multiTool.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('merge-pdfs', 'add_to_photos', 'home.merge.title', 'home.merge.desc', 'merge.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdfs', 'cut', 'home.split.title', 'home.split.desc', 'split.tags', 'organize')}"></div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('rotate-pdf', 'rotate_right', 'home.rotate.title', 'home.rotate.desc', 'rotate.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('crop', 'crop', 'home.crop.title', 'home.crop.desc', 'crop.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-organizer', 'format_list_bulleted', 'home.pdfOrganiser.title', 'home.pdfOrganiser.desc', 'pdfOrganiser.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-pages', 'delete', 'home.removePages.title', 'home.removePages.desc', 'removePages.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('multi-page-layout', 'dashboard', 'home.pageLayout.title', 'home.pageLayout.desc', 'pageLayout.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('scale-pages', 'fullscreen', 'home.scalePages.title', 'home.scalePages.desc', 'scalePages.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('extract-page', 'upload', 'home.extractPage.title', 'home.extractPage.desc', 'extractPage.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-single-page', 'looks_one', 'home.PdfToSinglePage.title', 'home.PdfToSinglePage.desc', 'PdfToSinglePage.tags', 'organize')}">
</div>
</div>
</div>
<div id="groupConvertTo" class="feature-group">
<div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertTo})}">
</div>
<div class="nav-group-container">
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('img-to-pdf', 'picture_as_pdf', 'home.imageToPdf.title', 'home.imageToPdf.desc', 'imageToPdf.tags', 'convertto')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('file-to-pdf', 'draft', 'home.fileToPDF.title', 'home.fileToPDF.desc', 'fileToPDF.tags', 'convertto')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('url-to-pdf', 'link', 'home.URLToPDF.title', 'home.URLToPDF.desc', 'URLToPDF.tags', 'convertto')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('html-to-pdf', 'html', 'home.HTMLToPDF.title', 'home.HTMLToPDF.desc', 'HTMLToPDF.tags', 'convertto')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('markdown-to-pdf', 'markdown', 'home.MarkdownToPDF.title', 'home.MarkdownToPDF.desc', 'MarkdownToPDF.tags', 'convertto')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('book-to-pdf', 'book', 'home.BookToPDF.title', 'home.BookToPDF.desc', 'BookToPDF.tags', 'convertto')}">
</div>
</div>
</div>
<div id="groupConvertFrom" class="feature-group">
<div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertFrom})}">
</div>
<div class="nav-group-container">
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-img', 'photo_library', 'home.pdfToImage.title', 'home.pdfToImage.desc', 'pdfToImage.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-pdfa', 'picture_as_pdf', 'home.pdfToPDFA.title', 'home.pdfToPDFA.desc', 'pdfToPDFA.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-word', 'description', 'home.PDFToWord.title', 'home.PDFToWord.desc', 'PDFToWord.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-presentation', 'slideshow', 'home.PDFToPresentation.title', 'home.PDFToPresentation.desc', 'PDFToPresentation.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-text', 'text_fields', 'home.PDFToText.title', 'home.PDFToText.desc', 'PDFToText.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-html', 'html', 'home.PDFToHTML.title', 'home.PDFToHTML.desc', 'PDFToHTML.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-xml', 'code', 'home.PDFToXML.title', 'home.PDFToXML.desc', 'PDFToXML.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-csv', 'csv', 'home.tableExtraxt.title', 'home.tableExtraxt.desc', 'tableExtraxt.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-book', 'book', 'home.PDFToBook.title', 'home.PDFToBook.desc', 'PDFToBook.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-markdown', 'markdown_copy', 'home.PDFToMarkdown.title', 'home.PDFToMarkdown.desc', 'PDFToMarkdown.tags', 'convert')}">
</div>
</div>
</div>
<div id="convertGroup" class="feature-group">
<div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertTo})}">
</div>
<div class="nav-group-container">
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('img-to-pdf', 'picture_as_pdf', 'home.imageToPdf.title', 'home.imageToPdf.desc', 'imageToPdf.tags', 'convertto')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('file-to-pdf', 'draft', 'home.fileToPDF.title', 'home.fileToPDF.desc', 'fileToPDF.tags', 'convertto')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('url-to-pdf', 'link', 'home.URLToPDF.title', 'home.URLToPDF.desc', 'URLToPDF.tags', 'convertto')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('html-to-pdf', 'html', 'home.HTMLToPDF.title', 'home.HTMLToPDF.desc', 'HTMLToPDF.tags', 'convertto')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('markdown-to-pdf', 'markdown', 'home.MarkdownToPDF.title', 'home.MarkdownToPDF.desc', 'MarkdownToPDF.tags', 'convertto')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('book-to-pdf', 'book', 'home.BookToPDF.title', 'home.BookToPDF.desc', 'BookToPDF.tags', 'convertto')}">
</div>
</div>
<div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertFrom})}">
</div>
<div class="nav-group-container">
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-img', 'photo_library', 'home.pdfToImage.title', 'home.pdfToImage.desc', 'pdfToImage.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-pdfa', 'picture_as_pdf', 'home.pdfToPDFA.title', 'home.pdfToPDFA.desc', 'pdfToPDFA.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-word', 'description', 'home.PDFToWord.title', 'home.PDFToWord.desc', 'PDFToWord.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-presentation', 'slideshow', 'home.PDFToPresentation.title', 'home.PDFToPresentation.desc', 'PDFToPresentation.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-text', 'text_fields', 'home.PDFToText.title', 'home.PDFToText.desc', 'PDFToText.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-html', 'html', 'home.PDFToHTML.title', 'home.PDFToHTML.desc', 'PDFToHTML.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-xml', 'code', 'home.PDFToXML.title', 'home.PDFToXML.desc', 'PDFToXML.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-csv', 'csv', 'home.tableExtraxt.title', 'home.tableExtraxt.desc', 'tableExtraxt.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-to-book', 'book', 'home.PDFToBook.title', 'home.PDFToBook.desc', 'PDFToBook.tags', 'convert')}">
</div>
</div>
</div>
<div id="groupSecurity" class="feature-group">
<div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.security})}">
</div>
<div class="nav-group-container">
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('add-password', 'lock', 'home.addPassword.title', 'home.addPassword.desc', 'addPassword.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-password', 'lock_open_right', 'home.removePassword.title', 'home.removePassword.desc', 'removePassword.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('change-permissions', 'encrypted', 'home.permissions.title', 'home.permissions.desc', 'permissions.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('sign', 'signature', 'home.sign.title', 'home.sign.desc', 'sign.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('cert-sign', 'workspace_premium', 'home.certSign.title', 'home.certSign.desc', 'certSign.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('validate-signature', 'verified', 'home.validateSignature.title', 'home.validateSignature.desc', 'validateSignature.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-cert-sign', 'remove_moderator', 'home.removeCertSign.title', 'home.removeCertSign.desc', 'removeCertSign.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('sanitize-pdf', 'sanitizer', 'home.sanitizePdf.title', 'home.sanitizePdf.desc', 'sanitizePdf.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('auto-redact', '/images/redact-auto.svg#icon-redact-auto', 'home.autoRedact.title', 'home.autoRedact.desc', 'autoRedact.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('redact', '/images/redact-manual.svg#icon-redact-manual', 'home.redact.title', 'home.redact.desc', 'redact.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('stamp', 'approval', 'home.AddStampRequest.title', 'home.AddStampRequest.desc', 'AddStampRequest.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('add-watermark', 'water_drop', 'home.watermark.title', 'home.watermark.desc', 'watermark.tags', 'security')}">
</div>
</div>
</div>
<div id="groupView" class="feature-group">
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.edit})}">
</div>
<div class="nav-group-container">
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('view-pdf', 'menu_book', 'home.viewPdf.title', 'home.viewPdf.desc', 'viewPdf.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('add-page-numbers', '123', 'home.add-page-numbers.title', 'home.add-page-numbers.desc', 'add-page-numbers.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('add-image', 'text_fields', 'home.addImage.title', 'home.addImage.desc', 'addImage.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('change-metadata', 'assignment', 'home.changeMetadata.title', 'home.changeMetadata.desc', 'changeMetadata.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('ocr-pdf', 'quick_reference_all', 'home.ocr.title', 'home.ocr.desc', 'ocr.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('extract-images', 'photo_library', 'home.extractImages.title', 'home.extractImages.desc', 'extractImages.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('flatten', 'layers_clear', 'home.flatten.title', 'home.flatten.desc', 'flatten.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-blanks', 'scan_delete', 'home.removeBlanks.title', 'home.removeBlanks.desc', 'removeBlanks.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-annotations', 'thread_unread', 'home.removeAnnotations.title', 'home.removeAnnotations.desc', 'removeAnnotations.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('compare', 'compare', 'home.compare.title', 'home.compare.desc', 'compare.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('get-info-on-pdf', 'info', 'home.getPdfInfo.title', 'home.getPdfInfo.desc', 'getPdfInfo.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-image-pdf', 'remove_selection', 'home.removeImagePdf.title', 'home.removeImagePdf.desc', 'removeImagePdf.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('replace-color-pdf', 'format_color_fill', 'home.replaceColorPdf.title', 'home.replaceColorPdf.desc', 'replaceColorPdf.tags', 'other')}">
</div>
</div>
</div>
<div id="groupAdvanced" class="feature-group">
<div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.advance})}">
</div>
<div class="nav-group-container">
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('pipeline', 'family_history', 'home.pipeline.title', 'home.pipeline.desc', 'pipeline.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('adjust-contrast', 'palette', 'home.adjust-contrast.title', 'home.adjust-contrast.desc', 'adjust-contrast.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('compress-pdf', 'zoom_in_map', 'home.compressPdfs.title', 'home.compressPdfs.desc', 'compressPdfs.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('extract-image-scans', 'scanner', 'home.ScannerImageSplit.title', 'home.ScannerImageSplit.desc', 'ScannerImageSplit.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('repair', 'build', 'home.repair.title', 'home.repair.desc', 'repair.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('auto-rename', '/images/rename.svg#icon-rename', 'home.auto-rename.title', 'home.auto-rename.desc', 'auto-rename.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('auto-split-pdf', '/images/split-auto.svg#icon-split-auto', 'home.autoSplitPDF.title', 'home.autoSplitPDF.desc', 'autoSplitPDF.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('show-javascript', 'javascript', 'home.showJS.title', 'home.showJS.desc', 'showJS.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('split-by-size-or-count', '/images/split-size.svg#icon-split-size', 'home.autoSizeSplitPDF.title', 'home.autoSizeSplitPDF.desc', 'autoSizeSplitPDF.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('overlay-pdf', 'layers', 'home.overlay-pdfs.title', 'home.overlay-pdfs.desc', 'overlay-pdfs.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('split-pdf-by-sections', 'grid_on', 'home.split-by-sections.title', 'home.split-by-sections.desc', 'split-by-sections.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('split-pdf-by-chapters', '/images/split-chapters.svg#icon-split-chapters', 'home.splitPdfByChapters.title', 'home.splitPdfByChapters.desc', 'splitPdfByChapters.tags', 'advance')}">
</div>
</div>
</div>
</th:block>

View File

@ -1,11 +1,13 @@
<div th:fragment="navbar" class="mx-auto"> <div th:fragment="navbar" class="mx-auto" style="position: sticky; top:0; z-index:10000">
<script th:src="@{'/js/languageSelection.js'}"></script> <script th:src="@{'/js/languageSelection.js'}"></script>
<script th:src="@{'/js/navbar.js'}"></script> <script th:src="@{'/js/navbar.js'}"></script>
<script th:src="@{'/js/additionalLanguageCode.js'}"></script> <script th:src="@{'/js/additionalLanguageCode.js'}"></script>
<script th:inline="javascript"> <script th:inline="javascript">
// Initializing the scripts // Initializing the scripts
initLanguageSettings(); initLanguageSettings();
document.addEventListener('DOMContentLoaded', function () {
toolsManager(); toolsManager();
});
</script> </script>
<script th:inline="javascript"> <script th:inline="javascript">
const currentVersion = /*[[${@appVersion}]]*/ ''; const currentVersion = /*[[${@appVersion}]]*/ '';
@ -16,7 +18,11 @@
<script th:src="@{'/js/homecard.js'}"></script> <script th:src="@{'/js/homecard.js'}"></script>
<script th:src="@{'/js/githubVersion.js'}"></script> <script th:src="@{'/js/githubVersion.js'}"></script>
<form th:action="@{'/dummyFormToPopulateCSRF'}" method="post" enctype="multipart/form-data"></form> <form th:action="@{'/dummyFormToPopulateCSRF'}" method="post" enctype="multipart/form-data"></form>
<nav class="navbar navbar-expand-xl"> <nav class="navbar navbar-expand-xl" style="
background: var(--md-nav-background);
border-bottom-style: solid;
border-bottom-width: 1px;
border-color: var(--md-nav-color-on-seperator)">
<div class="container "> <div class="container ">
<a class="navbar-brand" th:href="@{'/'}" style="display: flex;"> <a class="navbar-brand" th:href="@{'/'}" style="display: flex;">
<img class="main-icon" th:src="@{'/favicon.svg'}" alt="icon"> <img class="main-icon" th:src="@{'/favicon.svg'}" alt="icon">
@ -32,7 +38,7 @@
<ul class="navbar-nav me-auto flex-nowrap"> <ul class="navbar-nav me-auto flex-nowrap">
<!-- All Tools --> <!-- All Tools -->
<li class="nav-item dropdown dropdown-mega position-static"> <li id="navItemToHide" class="nav-item dropdown dropdown-mega position-static">
<a class="nav-link" id="navbarDropdown-1" href="#" role="button" data-bs-toggle="dropdown" <a class="nav-link" id="navbarDropdown-1" href="#" role="button" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false"> aria-haspopup="true" aria-expanded="false">
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
@ -41,231 +47,10 @@
<span class="icon-text" th:data-text="#{navbar.allTools}" th:text="#{navbar.allTools}"></span> <span class="icon-text" th:data-text="#{navbar.allTools}" th:text="#{navbar.allTools}"></span>
</a> </a>
<div class="dropdown-menu dropdown-menu-tp" aria-labelledby="navbarDropdown-1"> <div class="dropdown-menu dropdown-menu-tp" aria-labelledby="navbarDropdown-1">
<div class="dropdown-menu-wrapper"> <div class="dropdown-menu-wrapper" style="justify-content: center; display:flex">
<div class='mega-content px-md-4'> <div class="feature-rows">
<div class="container-fluid">
<div class="row"> <th:block th:insert="~{fragments/navElements.html :: navElements}"></th:block>
<!-- Page tools menu items -->
<div class="navbar-item col-lg-2 col-sm-6 py px-xl-1">
<h6 class="menu-title" th:text="#{navbar.sections.organize}"></h6>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('multi-tool', 'construction', 'home.multiTool.title', 'home.multiTool.desc', 'multiTool.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('merge-pdfs', 'add_to_photos', 'home.merge.title', 'home.merge.desc', 'merge.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdfs', 'cut', 'home.split.title', 'home.split.desc', 'split.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('rotate-pdf', 'rotate_right', 'home.rotate.title', 'home.rotate.desc', 'rotate.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-pages', 'delete', 'home.removePages.title', 'home.removePages.desc', 'removePages.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-organizer', 'format_list_bulleted', 'home.pdfOrganiser.title', 'home.pdfOrganiser.desc', 'pdfOrganiser.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('multi-page-layout', 'dashboard', 'home.pageLayout.title', 'home.pageLayout.desc', 'pageLayout.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('scale-pages', 'fullscreen', 'home.scalePages.title', 'home.scalePages.desc', 'scalePages.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('crop', 'crop', 'home.crop.title', 'home.crop.desc', 'crop.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('extract-page', 'upload', 'home.extractPage.title', 'home.extractPage.desc', 'extractPage.tags', 'organize')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-single-page', 'looks_one', 'home.PdfToSinglePage.title', 'home.PdfToSinglePage.desc', 'PdfToSinglePage.tags', 'organize')}">
</div>
</div>
<div id="stacked" class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2"
style="display:flex; flex-direction: column;">
<!-- Convert to PDF menu items -->
<div class="navbar-item py px-xl-1 px-2" style="margin-bottom: 1rem;">
<h6 class="menu-title" th:text="#{navbar.sections.convertTo}"></h6>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('img-to-pdf', 'picture_as_pdf', 'home.imageToPdf.title', 'home.imageToPdf.desc', 'imageToPdf.tags', 'image')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('file-to-pdf', 'draft', 'home.fileToPDF.title', 'home.fileToPDF.desc', 'fileToPDF.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('url-to-pdf', 'link', 'home.URLToPDF.title', 'home.URLToPDF.desc', 'URLToPDF.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('html-to-pdf', 'html', 'home.HTMLToPDF.title', 'home.HTMLToPDF.desc', 'HTMLToPDF.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('markdown-to-pdf', 'markdown', 'home.MarkdownToPDF.title', 'home.MarkdownToPDF.desc', 'MarkdownToPDF.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('book-to-pdf', 'book', 'home.BookToPDF.title', 'home.BookToPDF.desc', 'BookToPDF.tags', 'convert')}">
</div>
</div>
<!-- Convert from PDF menu items -->
<div class="navbar-item py px-xl-1">
<h6 class="menu-title" th:text="#{navbar.sections.convertFrom}"></h6>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-img', 'photo_library', 'home.pdfToImage.title', 'home.pdfToImage.desc', 'pdfToImage.tags', 'image')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-word', 'description', 'home.PDFToWord.title', 'home.PDFToWord.desc', 'PDFToWord.tags', 'word')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-presentation', 'slideshow', 'home.PDFToPresentation.title', 'home.PDFToPresentation.desc', 'PDFToPresentation.tags', 'ppt')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-text', 'text_fields', 'home.PDFToText.title', 'home.PDFToText.desc', 'PDFToText.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-html', 'html', 'home.PDFToHTML.title', 'home.PDFToHTML.desc', 'PDFToHTML.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-xml', 'code', 'home.PDFToXML.title', 'home.PDFToXML.desc', 'PDFToXML.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-pdfa', 'picture_as_pdf', 'home.pdfToPDFA.title', 'home.pdfToPDFA.desc', 'pdfToPDFA.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-csv', 'csv', 'home.tableExtraxt.title', 'home.tableExtraxt.desc', 'pdfToPDFA.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-book', 'book', 'home.PDFToBook.title', 'home.PDFToBook.desc', 'PDFToBook.tags', 'convert')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-markdown', 'markdown_copy', 'home.PDFToMarkdown.title', 'home.PDFToMarkdown.desc', 'PDFToMarkdown.tags', 'convert')}">
</div>
</div>
</div>
<!-- Security menu items -->
<div class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2">
<h6 class="menu-title" th:text="#{navbar.sections.security}"></h6>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('sign', 'signature', 'home.sign.title', 'home.sign.desc', 'sign.tags', 'sign')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('add-password', 'lock', 'home.addPassword.title', 'home.addPassword.desc', 'addPassword.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-password', 'lock_open_right', 'home.removePassword.title', 'home.removePassword.desc', 'removePassword.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('change-permissions', 'encrypted', 'home.permissions.title', 'home.permissions.desc', 'permissions.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('add-watermark', 'water_drop', 'home.watermark.title', 'home.watermark.desc', 'watermark.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('cert-sign', 'workspace_premium', 'home.certSign.title', 'home.certSign.desc', 'certSign.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry('validate-signature','verified','home.validateSignature.title','home.validateSignature.desc','validateSignature.tags','security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-cert-sign', 'remove_moderator', 'home.removeCertSign.title', 'home.removeCertSign.desc', 'removeCertSign.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('sanitize-pdf', 'sanitizer', 'home.sanitizePdf.title', 'home.sanitizePdf.desc', 'sanitizePdf.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('auto-redact', 'contract_delete', 'home.autoRedact.title', 'home.autoRedact.desc', 'autoRedact.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('redact', 'playlist_remove', 'home.redact.title', 'home.redact.desc', 'redact.tags', 'security')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('stamp', 'approval', 'home.AddStampRequest.title', 'home.AddStampRequest.desc', 'AddStampRequest.tags', 'security')}">
</div>
</div>
<!-- View & Edit menu items -->
<div class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2">
<h6 class="menu-title" th:text="#{navbar.sections.edit}"></h6>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('view-pdf', 'menu_book', 'home.viewPdf.title', 'home.viewPdf.desc', 'viewPdf.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('ocr-pdf', 'quick_reference_all', 'home.ocr.title', 'home.ocr.desc', 'ocr.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('add-page-numbers', '123', 'home.add-page-numbers.title', 'home.add-page-numbers.desc', 'add-page-numbers.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('add-image', 'add_photo_alternate', 'home.addImage.title', 'home.addImage.desc', 'addImage.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('extract-images', 'wallpaper', 'home.extractImages.title', 'home.extractImages.desc', 'extractImages.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('flatten', 'layers_clear', 'home.flatten.title', 'home.flatten.desc', 'flatten.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-annotations', 'thread_unread', 'home.removeAnnotations.title', 'home.removeAnnotations.desc', 'removeAnnotations.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-blanks', 'scan_delete', 'home.removeBlanks.title', 'home.removeBlanks.desc', 'removeBlanks.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('compare', 'compare', 'home.compare.title', 'home.compare.desc', 'compare.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('change-metadata', 'assignment', 'home.changeMetadata.title', 'home.changeMetadata.desc', 'changeMetadata.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('get-info-on-pdf', 'info', 'home.getPdfInfo.title', 'home.getPdfInfo.desc', 'getPdfInfo.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-image-pdf','remove_selection', 'home.removeImagePdf.title', 'home.removeImagePdf.desc', 'removeImagePdf.tags', 'other')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('replace-and-invert-color-pdf','format_color_fill', 'replace-color.title', 'home.replaceColorPdf.desc', 'replaceColorPdf.tags', 'other')}">
</div>
</div>
<!-- Advance menu items -->
<div class="navbar-item col-lg-2 col-sm-6 py px-xl-1 px-2">
<h6 class="menu-title" th:text="#{navbar.sections.advance}"></h6>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('compress-pdf', 'zoom_in_map', 'home.compressPdfs.title', 'home.compressPdfs.desc', 'compressPdfs.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('pipeline', 'family_history', 'home.pipeline.title', 'home.pipeline.desc', 'pipeline.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntryCustom :: navbarEntry ('auto-rename', '/images/rename.svg#icon-rename', 'home.auto-rename.title', 'home.auto-rename.desc', 'auto-rename.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('repair', 'build', 'home.repair.title', 'home.repair.desc', 'repair.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('adjust-contrast', 'palette', 'home.adjust-contrast.title', 'home.adjust-contrast.desc', 'adjust-contrast.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('overlay-pdf', 'layers', 'home.overlay-pdfs.title', 'home.overlay-pdfs.desc', 'overlay-pdfs.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('auto-split-pdf', 'cut', 'home.autoSplitPDF.title', 'home.autoSplitPDF.desc', 'autoSplitPDF.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdf-by-sections', 'grid_on', 'home.split-by-sections.title', 'home.split-by-sections.desc', 'split-by-sections.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdf-by-chapters', 'book', 'home.splitPdfByChapters.title', 'home.splitPdfByChapters.desc', 'splitPdfByChapters.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('split-by-size-or-count', 'vertical_split', 'home.autoSizeSplitPDF.title', 'home.autoSizeSplitPDF.desc', 'autoSizeSplitPDF.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('extract-image-scans', 'scanner', 'home.ScannerImageSplit.title', 'home.ScannerImageSplit.desc', 'ScannerImageSplit.tags', 'advance')}">
</div>
<div
th:replace="~{fragments/navbarEntry :: navbarEntry ('show-javascript', 'javascript', 'home.showJS.title', 'home.showJS.desc', 'showJS.tags', 'advance')}">
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -480,4 +265,18 @@
</div> </div>
</div> </div>
<script th:src="@{'/js/settings.js'}"></script> <script th:src="@{'/js/settings.js'}"></script>
<script>
window.onload = function () {
updateFavoritesDropdown();
}
document.addEventListener('DOMContentLoaded', function () {
const navbarLink = document.querySelector(".navbar-brand");
if (localStorage.getItem("defaultView") === "home-legacy") {
navbarLink.setAttribute("href", "/home-legacy");
} else {
navbarLink.setAttribute("href", "/");
}
});
</script>
</div> </div>

View File

@ -1,11 +1,18 @@
<th:block th:fragment="navbarEntry(endpoint, toolIcon, titleKey, descKey, tagKey, toolGroup)" <th:block th:fragment="navbarEntry(endpoint, toolIcon, titleKey, descKey, tagKey, toolGroup)"
th:if="${@endpointConfiguration.isEndpointEnabled(endpoint)}"> th:if="${@endpointConfiguration.isEndpointEnabled(endpoint)}">
<a class="dropdown-item" href="#" th:href="@{${endpoint}}" <a th:id="@{${endpoint}}" class="dropdown-item" style="position:relative" th:href="@{${endpoint}}"
th:classappend="${endpoint.equals(currentPage)} ? ${toolGroup} + ' active' : '' + ${toolGroup}" th:title="#{${descKey}}" th:data-bs-link="@{${endpoint}}"
th:data-bs-tags="#{${tagKey}}"> th:classappend="${endpoint.equals(currentPage)} ? ${toolGroup} + ' active' : '' + ${toolGroup}"
<div class="icon" alt="icon" th:class="@{${toolGroup}}"> th:data-bs-tags="#{${tagKey}}" th:data-bs-title='#{${titleKey}}'>
<span class="material-symbols-rounded nav-icon" th:text="@{${toolIcon}}"></span> <div style="height:2.7rem; align-items: center;display: flex;" th:title="#{${descKey}}" class="icon" alt="icon"
th:class="@{${toolGroup}}">
<span class="material-symbols-rounded nav-icon" th:text="@{${toolIcon}}" style=" align-items:center; display: flex; justify-content: center; height:2.7rem; width:2.7rem"></span>
<span class="icon-text" th:text="#{${titleKey}}"></span> <span class="icon-text" th:text="#{${titleKey}}"></span>
</div> </div>
<span class="material-symbols-rounded favorite-icon" style="display: none;" th:data-endpoint="@{${endpoint}}"
th:onclick="'addToFavorites(\'' + @{${endpoint}} + '\')'">
add
</span>
</a> </a>
</th:block> </th:block>

View File

@ -1,13 +1,20 @@
<th:block th:fragment="navbarEntry(endpoint, toolIcon, titleKey, descKey, tagKey, toolGroup)" <th:block th:fragment="navbarEntry(endpoint, toolIcon, titleKey, descKey, tagKey, toolGroup)"
th:if="${@endpointConfiguration.isEndpointEnabled(endpoint)}"> th:if="${@endpointConfiguration.isEndpointEnabled(endpoint)}">
<a class="dropdown-item" href="#" th:href="@{${endpoint}}" <a th:id="@{${endpoint}}" class="dropdown-item" style="position:relative" th:href="@{${endpoint}}"
th:data-bs-link="@{${endpoint}}"
th:classappend="${endpoint.equals(currentPage)} ? ${toolGroup} + ' active' : '' + ${toolGroup}" th:classappend="${endpoint.equals(currentPage)} ? ${toolGroup} + ' active' : '' + ${toolGroup}"
th:title="#{${descKey}}" th:data-bs-tags="#{${tagKey}}"> th:data-bs-tags="#{${tagKey}}" th:data-bs-title='#{${titleKey}}'>
<div class="icon" alt="icon" th:class="@{${toolGroup}}"> <div style="height:2.5rem;align-items: center;display: flex" th:title="#{${descKey}}" class="icon" alt="icon"
<svg class="nav-icon" style="height: 2.5rem; width:2.5rem"> th:class="@{${toolGroup}}">
<use th:xlink:href="@{${toolIcon}}"></use> <svg class="nav-icon" style="height: 2.7rem; width:2.7rem; align-items:center; display: flex; justify-content: center;">
<use th:href="@{${toolIcon}}"></use>
</svg> </svg>
<span class="icon-text" th:text="#{${titleKey}}"></span> <span class="icon-text" th:text="#{${titleKey}}"></span>
</div> </div>
<span class="material-symbols-rounded favorite-icon" style="display: none;" th:data-endpoint="@{${endpoint}}"
th:onclick="'addToFavorites(\'' + @{${endpoint}} + '\')'">
star
</span>
</a> </a>
</th:block> </th:block>

View File

@ -0,0 +1,522 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title='')}"></th:block>
</head>
<body>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<!-- Jumbotron -->
<div class="p-5 rounded d-none d-md-block" id="jumbotron">
<div class="container">
<h1 class="display-4 fw-normal" th:text="${@appName}"></h1>
<p class="lead fs-4"
th:text="${@homeText != 'null' and @homeText != null and @homeText != ''} ? ${@homeText} : #{home.desc}">
</p>
</div>
</div>
<br class="d-md-none">
<!-- Features -->
<script th:src="@{'/js/homecard-legacy.js'}"></script>
<div class=" container">
<br>
<span class="material-symbols-rounded search-icon">
search
</span>
<input type="text" id="searchBar" onkeyup="filterCards()" th:placeholder="#{home.searchBar}" autofocus>
<div style="display: flex; align-items: center;">
<a href="home" onclick="setAsDefault('home')" style="text-decoration: none; color: inherit; cursor: pointer; display: flex; align-items: center;">
<span th:text="#{home.newHomePage}">
</span>
<span class="material-symbols-rounded toggle-favourites" style="font-size: 2rem; margin-left: 0.2rem;" >
home
</span>
</a>
</div>
<div id="filtersContainer">
<span class="material-symbols-rounded filter-button" onclick="toggleFavoritesOnly()">
star
</span>
<span class="material-symbols-rounded filter-button" onclick="expandCollapseAll(true)">
expand_all
</span>
<span class="material-symbols-rounded filter-button" onclick="expandCollapseAll(false)">
collapse_all
</span>
<span class="material-symbols-rounded filter-button hidden" onclick="switchViewMode()">
dashboard
</span>
</div>
<div class="features-container">
<div th:if="${@shouldShow}" class="feature-card favorite update-notice" id="update-link"
style="display: none;">
<a href="https://github.com/Stirling-Tools/Stirling-PDF/releases" target="_blank" rel="noopener">
<div class="d-flex align-items-center">
<div id="tool-icon" class="advance" alt="icon">
<span class="material-symbols-rounded nav-icon">update</span>
</div>
<div id="tool-text">
<h5 class="card-title" th:text="#{settings.update}"></h5>
<p class="card-text" id="app-update"></p>
</div>
</div>
</a>
</div>
<div id="groupFavorites" class="feature-group-legacy">
<div th:replace="~{fragments/featureGroupHeaderLegacy :: featureGroupHeader(groupTitle=#{navbar.favorite})}">
</div>
<div class="feature-group-container">
</div>
</div>
<div id="popularTools" class="feature-group-legacy">
<div
th:replace="~{fragments/featureGroupHeaderLegacy :: featureGroupHeader(groupTitle=#{navbar.sections.popular})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='view-pdf', cardTitle=#{home.viewPdf.title}, cardText=#{home.viewPdf.desc}, cardLink='view-pdf', toolIcon='menu_book', tags=#{viewPdf.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', toolIcon='construction', tags=#{multiTool.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', toolIcon='family_history', tags=#{pipeline.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='compress-pdf', cardTitle=#{home.compressPdfs.title}, cardText=#{home.compressPdfs.desc}, cardLink='compress-pdf', toolIcon='zoom_in_map', tags=#{compressPdfs.tags}, toolGroup='advance')}">
</div>
</div>
</div>
<div id="groupOrganize" class="feature-group-legacy">
<div
th:replace="~{fragments/featureGroupHeaderLegacy :: featureGroupHeader(groupTitle=#{navbar.sections.organize})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', toolIcon='construction', tags=#{multiTool.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='merge-pdfs', cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs', toolIcon='add_to_photos', tags=#{merge.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='split-pdfs', cardTitle=#{home.split.title}, cardText=#{home.split.desc}, cardLink='split-pdfs', toolIcon='cut', tags=#{split.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='rotate-pdf', cardTitle=#{home.rotate.title}, cardText=#{home.rotate.desc}, cardLink='rotate-pdf', toolIcon='rotate_right', tags=#{rotate.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='crop', cardTitle=#{home.crop.title}, cardText=#{home.crop.desc}, cardLink='crop', toolIcon='crop', tags=#{crop.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-organizer', cardTitle=#{home.pdfOrganiser.title}, cardText=#{home.pdfOrganiser.desc}, cardLink='pdf-organizer', toolIcon='format_list_bulleted', tags=#{pdfOrganiser.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='remove-pages', cardTitle=#{home.removePages.title}, cardText=#{home.removePages.desc}, cardLink='remove-pages', toolIcon='delete', tags=#{removePages.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='multi-page-layout', cardTitle=#{home.pageLayout.title}, cardText=#{home.pageLayout.desc}, cardLink='multi-page-layout', toolIcon='dashboard', tags=#{pageLayout.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='scale-pages', cardTitle=#{home.scalePages.title}, cardText=#{home.scalePages.desc}, cardLink='scale-pages', toolIcon='fullscreen', tags=#{scalePages.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='extract-page', cardTitle=#{home.extractPage.title}, cardText=#{home.extractPage.desc}, cardLink='extract-page', toolIcon='upload', tags=#{extractPage.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-single-page', cardTitle=#{home.PdfToSinglePage.title}, cardText=#{home.PdfToSinglePage.desc}, cardLink='pdf-to-single-page', toolIcon='looks_one', tags=#{PdfToSinglePage.tags}, toolGroup='organize')}">
</div>
</div>
</div>
<div id="groupConvertTo" class="feature-group-legacy">
<div
th:replace="~{fragments/featureGroupHeaderLegacy :: featureGroupHeader(groupTitle=#{navbar.sections.convertTo})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='img-to-pdf', cardTitle=#{home.imageToPdf.title}, cardText=#{home.imageToPdf.desc}, cardLink='picture_as_pdf', toolIcon='picture_as_pdf', tags=#{imageToPdf.tags}, toolGroup='image')}">
</div>
<div
th:replace="~{fragments/card :: card(id='file-to-pdf', cardTitle=#{home.fileToPDF.title}, cardText=#{home.fileToPDF.desc}, cardLink='file-to-pdf', toolIcon='draft', tags=#{fileToPDF.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='url-to-pdf', cardTitle=#{home.URLToPDF.title}, cardText=#{home.URLToPDF.desc}, cardLink='url-to-pdf', toolIcon='link', tags=#{URLToPDF.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='html-to-pdf', cardTitle=#{home.HTMLToPDF.title}, cardText=#{home.HTMLToPDF.desc}, cardLink='html-to-pdf', toolIcon='html', tags=#{HTMLToPDF.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='markdown-to-pdf', cardTitle=#{home.MarkdownToPDF.title}, cardText=#{home.MarkdownToPDF.desc}, cardLink='markdown-to-pdf', toolIcon='markdown', tags=#{MarkdownToPDF.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='book-to-pdf', cardTitle=#{home.BookToPDF.title}, cardText=#{home.BookToPDF.desc}, cardLink='book-to-pdf', toolIcon='book', tags=#{BookToPDF.tags}, toolGroup='convert')}">
</div>
</div>
</div>
<div id="groupConvertFrom" class="feature-group-legacy">
<div
th:replace="~{fragments/featureGroupHeaderLegacy :: featureGroupHeader(groupTitle=#{navbar.sections.convertFrom})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='pdf-to-img', cardTitle=#{home.pdfToImage.title}, cardText=#{home.pdfToImage.desc}, cardLink='pdf-to-img', toolIcon='photo_library', tags=#{pdfToImage.tags}, toolGroup='image')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-pdfa', cardTitle=#{home.pdfToPDFA.title}, cardText=#{home.pdfToPDFA.desc}, cardLink='pdf-to-pdfa', toolIcon='picture_as_pdf', tags=#{pdfToPDFA.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-word', cardTitle=#{home.PDFToWord.title}, cardText=#{home.PDFToWord.desc}, cardLink='pdf-to-word', toolIcon='description', tags=#{PDFToWord.tags}, toolGroup='word')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-presentation', cardTitle=#{home.PDFToPresentation.title}, cardText=#{home.PDFToPresentation.desc}, cardLink='pdf-to-presentation', toolIcon='slideshow', tags=#{PDFToPresentation.tags}, toolGroup='ppt')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-text', cardTitle=#{home.PDFToText.title}, cardText=#{home.PDFToText.desc}, cardLink='pdf-to-text', toolIcon='text_fields', tags=#{PDFToText.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-html', cardTitle=#{home.PDFToHTML.title}, cardText=#{home.PDFToHTML.desc}, cardLink='pdf-to-html', toolIcon='html', tags=#{PDFToHTML.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-xml', cardTitle=#{home.PDFToXML.title}, cardText=#{home.PDFToXML.desc}, cardLink='pdf-to-xml', toolIcon='code', tags=#{PDFToXML.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-csv', cardTitle=#{home.tableExtraxt.title}, cardText=#{home.tableExtraxt.desc}, cardLink='pdf-to-csv', toolIcon='csv', tags=#{tableExtraxt.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-book', cardTitle=#{home.PDFToBook.title}, cardText=#{home.PDFToBook.desc}, cardLink='pdf-to-book', toolIcon='book', tags=#{PDFToBook.tags}, toolGroup='convert')}">
</div>
</div>
</div>
<div id="groupSecurity" class="feature-group-legacy">
<div
th:replace="~{fragments/featureGroupHeaderLegacy :: featureGroupHeader(groupTitle=#{navbar.sections.security})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='add-password', cardTitle=#{home.addPassword.title}, cardText=#{home.addPassword.desc}, cardLink='add-password', toolIcon='lock', tags=#{addPassword.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='remove-password', cardTitle=#{home.removePassword.title}, cardText=#{home.removePassword.desc}, cardLink='remove-password', toolIcon='lock_open_right', tags=#{removePassword.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='change-permissions', cardTitle=#{home.permissions.title}, cardText=#{home.permissions.desc}, cardLink='change-permissions', toolIcon='encrypted', tags=#{permissions.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='sign', cardTitle=#{home.sign.title}, cardText=#{home.sign.desc}, cardLink='sign', toolIcon='signature', tags=#{sign.tags}, toolGroup='sign')}">
</div>
<div
th:replace="~{fragments/card :: card(id='cert-sign', cardTitle=#{home.certSign.title}, cardText=#{home.certSign.desc}, cardLink='cert-sign', toolIcon='workspace_premium', tags=#{certSign.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='validate-signature', cardTitle=#{home.validateSignature.title}, cardText=#{home.validateSignature.desc}, cardLink='validate-signature', toolIcon='verified', tags=#{validateSignature.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='remove-cert-sign', cardTitle=#{home.removeCertSign.title}, cardText=#{home.removeCertSign.desc}, cardLink='remove-cert-sign', toolIcon='remove_moderator', tags=#{removeCertSign.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='sanitize-pdf', cardTitle=#{home.sanitizePdf.title}, cardText=#{home.sanitizePdf.desc}, cardLink='sanitize-pdf', toolIcon='sanitizer', tags=#{sanitizePdf.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='auto-redact', cardTitle=#{home.autoRedact.title}, cardText=#{home.autoRedact.desc}, cardLink='auto-redact', toolIcon='ink_eraser', tags=#{autoRedact.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='redact', cardTitle=#{home.redact.title}, cardText=#{home.redact.desc}, cardLink='redact', toolIcon='playlist_remove', tags=#{redact.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='stamp', cardTitle=#{home.AddStampRequest.title}, cardText=#{home.AddStampRequest.desc}, cardLink='stamp', toolIcon='approval', tags=#{AddStampRequest.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='add-watermark', cardTitle=#{home.watermark.title}, cardText=#{home.watermark.desc}, cardLink='add-watermark', toolIcon='water_drop', tags=#{watermark.tags}, toolGroup='security')}">
</div>
</div>
</div>
<div id="groupView" class="feature-group-legacy">
<div th:replace="~{fragments/featureGroupHeaderLegacy :: featureGroupHeader(groupTitle=#{navbar.sections.edit})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='view-pdf', cardTitle=#{home.viewPdf.title}, cardText=#{home.viewPdf.desc}, cardLink='view-pdf', toolIcon='menu_book', tags=#{viewPdf.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='add-page-numbers', cardTitle=#{home.add-page-numbers.title}, cardText=#{home.add-page-numbers.desc}, cardLink='add-page-numbers', toolIcon='123', tags=#{add-page-numbers.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='add-image', cardTitle=#{home.addImage.title}, cardText=#{home.addImage.desc}, cardLink='add-image', toolIcon='add_photo_alternate', tags=#{addImage.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='change-metadata', cardTitle=#{home.changeMetadata.title}, cardText=#{home.changeMetadata.desc}, cardLink='change-metadata', toolIcon='assignment', tags=#{changeMetadata.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='ocr-pdf', cardTitle=#{home.ocr.title}, cardText=#{home.ocr.desc}, cardLink='ocr-pdf', toolIcon='quick_reference_all', tags=#{ocr.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='extract-images', cardTitle=#{home.extractImages.title}, cardText=#{home.extractImages.desc}, cardLink='extract-images', toolIcon='wallpaper', tags=#{extractImages.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='flatten', cardTitle=#{home.flatten.title}, cardText=#{home.flatten.desc}, cardLink='flatten', toolIcon='layers_clear', tags=#{flatten.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='remove-blanks', cardTitle=#{home.removeBlanks.title}, cardText=#{home.removeBlanks.desc}, cardLink='remove-blanks', toolIcon='scan_delete', tags=#{removeBlanks.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='remove-annotations', cardTitle=#{home.removeAnnotations.title}, cardText=#{home.removeAnnotations.desc}, cardLink='remove-annotations', toolIcon='thread_unread', tags=#{removeAnnotations.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='compare', cardTitle=#{home.compare.title}, cardText=#{home.compare.desc}, cardLink='compare', toolIcon='compare', tags=#{compare.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='get-info-on-pdf', cardTitle=#{home.getPdfInfo.title}, cardText=#{home.getPdfInfo.desc}, cardLink='get-info-on-pdf', toolIcon='info', tags=#{getPdfInfo.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='remove-image-pdf', cardTitle=#{home.removeImagePdf.title}, cardText=#{home.removeImagePdf.desc}, cardLink='remove-image-pdf', toolIcon='remove_selection', tags=#{removeImagePdf.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='replace-color-pdf', cardTitle=#{home.replaceColorPdf.title}, cardText=#{home.replaceColorPdf.desc}, cardLink='replace-and-invert-color-pdf', toolIcon='format_color_fill', tags=#{replaceColorPdf.tags}, toolGroup='other')}">
</div>
</div>
</div>
<div id="groupAdvanced" class="feature-group-legacy">
<div
th:replace="~{fragments/featureGroupHeaderLegacy :: featureGroupHeader(groupTitle=#{navbar.sections.advance})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', toolIcon='family_history', tags=#{pipeline.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='adjust-contrast', cardTitle=#{home.adjust-contrast.title}, cardText=#{home.adjust-contrast.desc}, cardLink='adjust-contrast', toolIcon='palette', tags=#{adjust-contrast.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='compress-pdf', cardTitle=#{home.compressPdfs.title}, cardText=#{home.compressPdfs.desc}, cardLink='compress-pdf', toolIcon='zoom_in_map', tags=#{compressPdfs.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='extract-image-scans', cardTitle=#{home.ScannerImageSplit.title}, cardText=#{home.ScannerImageSplit.desc}, cardLink='extract-image-scans', toolIcon='scanner', tags=#{ScannerImageSplit.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='repair', cardTitle=#{home.repair.title}, cardText=#{home.repair.desc}, cardLink='repair', toolIcon='build', tags=#{repair.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='auto-rename', cardTitle=#{home.auto-rename.title}, cardText=#{home.auto-rename.desc}, cardLink='auto-rename', toolIcon='text_fields_alt', tags=#{auto-rename.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='auto-split-pdf', cardTitle=#{home.autoSplitPDF.title}, cardText=#{home.autoSplitPDF.desc}, cardLink='auto-split-pdf', toolIcon='cut', tags=#{autoSplitPDF.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='show-javascript', cardTitle=#{home.showJS.title}, cardText=#{home.showJS.desc}, cardLink='show-javascript', toolIcon='javascript', tags=#{showJS.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='split-by-size-or-count', cardTitle=#{home.autoSizeSplitPDF.title}, cardText=#{home.autoSizeSplitPDF.desc}, cardLink='split-by-size-or-count', toolIcon='vertical_split', tags=#{autoSizeSplitPDF.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='overlay-pdf', cardTitle=#{home.overlay-pdfs.title}, cardText=#{home.overlay-pdfs.desc}, cardLink='overlay-pdf', toolIcon='layers', tags=#{overlay-pdfs.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='split-pdf-by-sections', cardTitle=#{home.split-by-sections.title}, cardText=#{home.split-by-sections.desc}, cardLink='split-pdf-by-sections', toolIcon='grid_on', tags=#{split-by-sections.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='split-pdf-by-chapters', cardTitle=#{home.splitPdfByChapters.title}, cardText=#{home.splitPdfByChapters.desc}, cardLink='split-pdf-by-chapters', toolIcon='book', tags=#{splitPdfByChapters.tags}, toolGroup='advance')}">
</div>
</div>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
<!-- Survey Modal -->
<div class="modal fade" id="surveyModal" tabindex="-1" role="dialog" aria-labelledby="surveyModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="surveyModalLabel" th:text="#{survey.title}">Stirling-PDF Survey</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p><span th:text="#{survey.changes}">Stirling-PDF has changed since the last survey! To find out more please
check our blog post here: </span><a href="https://www.stirlingpdf.com/blog/stirling-pdf-future"
target="_blank"> Stirling PDF</a></p>
<p th:text="#{survey.changes2}">With these changes we are getting paid business support and funding</p>
<p th:text="#{survey.please}">Please consider taking our survey!</p>
<p th:text="#{survey.disabled}">Survey popup will be disabled in following updates but available at foot of
page)</p>
<a href="https://stirlingpdf.info/s/cm28y3niq000o56dv7liv8wsu" target="_blank" class="btn btn-primary"
id="takeSurvey" th:text="#{survey.button}">Take Survey</a>
</div>
<div class="modal-footer">
<div class="form-check mb-3">
<input type="checkbox" id="dontShowAgain">
<label for="dontShowAgain" th:text="#{survey.dontShowAgain}">Don't show again</label>
</div>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}">Close</button>
</div>
</div>
</div>
</div>
<!-- Analytics Modal -->
<div class="modal fade" id="analyticsModal" tabindex="-1" role="dialog" aria-labelledby="analyticsModalLabel"
aria-hidden="true" th:if="${@analyticsPrompt}">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="analyticsModalLabel" th:text="#{analytics.title}">Do you want make Stirling PDF
better?</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p th:text="#{analytics.paragraph1}">Stirling PDF has opt in analytics to help us improve the product. We do
not track any personal information or file contents.</p>
<p th:text="#{analytics.paragraph2}">Please consider enabling analytics to help Stirling-PDF grow and to allow
us to understand our users better.</p>
<p th:text="#{analytics.settings}">You can change the settings for analytics in the config/settings.yml file
</p>
</div>
<div class="modal-footer justify-content-between">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" onclick="setAnalytics(false)"
th:text="#{analytics.disable}">Disable analytics</button>
<button type="button" class="btn btn-primary" th:text="#{analytics.enable}"
onclick="setAnalytics(true)">Enable analytics</button>
</div>
</div>
</div>
</div>
<script th:src="@{'/js/fetch-utils.js'}"></script>
<script th:inline="javascript">
/*<![CDATA[*/
const analyticsPromptBoolean = /*[[${@analyticsPrompt}]]*/ false;
document.addEventListener('DOMContentLoaded', function () {
if (analyticsPromptBoolean) {
const analyticsModal = new bootstrap.Modal(document.getElementById('analyticsModal'));
analyticsModal.show();
}
});
/*]]>*/
function setAnalytics(enabled) {
fetchWithCsrf('api/v1/settings/update-enable-analytics', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(enabled)
})
.then(response => {
if (response.status === 200) {
console.log('Analytics setting updated successfully');
bootstrap.Modal.getInstance(document.getElementById('analyticsModal')).hide();
} else if (response.status === 208) {
console.log('Analytics setting has already been set. Please edit /config/settings.yml to change it.', response);
alert('Analytics setting has already been set. Please edit /config/settings.yml to change it.');
} else {
throw new Error('Unexpected response status: ' + response.status);
}
})
.catch(error => {
console.error('Error updating analytics setting:', error);
alert('An error occurred while updating the analytics setting. Please try again.');
});
}
document.addEventListener("DOMContentLoaded", function () {
const surveyVersion = "2.0";
const modal = new bootstrap.Modal(document.getElementById('surveyModal'));
const dontShowAgain = document.getElementById('dontShowAgain');
const takeSurveyButton = document.getElementById('takeSurvey');
const viewThresholds = [5, 10, 15, 22, 30, 50, 75, 100, 150, 200];
// Check if survey version changed and reset page views if it did
const storedVersion = localStorage.getItem('surveyVersion');
if (storedVersion && storedVersion !== surveyVersion) {
localStorage.setItem('pageViews', '0');
}
let pageViews = parseInt(localStorage.getItem('pageViews') || '0');
pageViews++;
localStorage.setItem('pageViews', pageViews.toString());
function shouldShowSurvey() {
if (localStorage.getItem('dontShowSurvey') === 'true' ||
localStorage.getItem('surveyTaken') === 'true') {
return false;
}
// If survey version changed and we hit a threshold, show the survey
if (localStorage.getItem('surveyVersion') !== surveyVersion &&
viewThresholds.includes(pageViews)) {
return true;
}
return viewThresholds.includes(pageViews);
}
if (shouldShowSurvey()) {
modal.show();
}
dontShowAgain.addEventListener('change', function () {
if (this.checked) {
localStorage.setItem('dontShowSurvey', 'true');
localStorage.setItem('surveyVersion', surveyVersion);
} else {
localStorage.removeItem('dontShowSurvey');
localStorage.removeItem('surveyVersion');
}
});
takeSurveyButton.addEventListener('click', function () {
localStorage.setItem('surveyTaken', 'true');
localStorage.setItem('surveyVersion', surveyVersion);
modal.hide();
});
if (localStorage.getItem('dontShowSurvey')) {
modal.hide();
}
});
function setAsDefault(value) {
localStorage.setItem('defaultView', value);
console.log(`Default view set to: ${value}`);
}
</script>
</body>
</html>

View File

@ -10,42 +10,99 @@
<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> <th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<!-- Jumbotron -->
<div class="p-5 rounded d-none d-md-block" id="jumbotron">
<div class="container">
<h1 class="display-4 fw-normal" th:text="${@appName}"></h1> <br class="d-md-none">
<!-- Features -->
<script th:src="@{'/js/homecard.js'}"></script>
<div style="
width: 100%;
display: flex;
flex-direction: column;">
<div>
<br>
<div style="justify-content: center; display: flex;">
<div style="margin:0 3rem">
<div>
<div style="display:flex; flex-direction: column; justify-content: center; width:100%; margin-bottom:1rem">
<div style="width:fit-content; margin: 0 auto; padding: 0 3rem">
<p class="lead fs-4" <p class="lead fs-4"
th:text="${@homeText != 'null' and @homeText != null and @homeText != ''} ? ${@homeText} : #{home.desc}"> th:text="${@homeText != 'null' and @homeText != null and @homeText != ''} ? ${@homeText} : #{home.desc}">
</p> </p>
</div> </div>
<div id="groupRecent" style="width: fit-content; margin: 0 auto">
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.recent})}">
</div>
<div class="recent-features">
<div class="newfeature"
th:include="~{fragments/navbarEntryCustom :: navbarEntry('redact', '/images/redact-manual.svg#icon-redact-manual', 'home.redact.title', 'home.redact.desc', 'redact.tags', 'security')}">
</div>
<div class="newfeature"
th:include="~{fragments/navbarEntry :: navbarEntry ('multi-tool', 'construction', 'home.multiTool.title', 'home.multiTool.desc', 'multiTool.tags', 'organize')}">
</div>
<div class="newfeature"
th:include="~{fragments/navbarEntry :: navbarEntry('validate-signature', 'verified', 'home.validateSignature.title', 'home.validateSignature.desc', 'validateSignature.tags', 'security')}">
</div>
</div>
</div>
</div>
</div> </div>
<br class="d-md-none">
<!-- Features -->
<script th:src="@{'/js/homecard.js'}"></script>
<div class=" container">
<br>
<span class="material-symbols-rounded search-icon"> <span class="material-symbols-rounded search-icon">
search search
</span> </span>
<input type="text" id="searchBar" onkeyup="filterCards()" th:placeholder="#{home.searchBar}" autofocus> <input type="text" id="searchBar" onkeyup="filterCards()" th:placeholder="#{home.searchBar}" autofocus>
<div id="filtersContainer"> <div style="display: flex;">
<span class="material-symbols-rounded filter-button" onclick="toggleFavoritesOnly()"> <div
style="height:2.5rem; display: flex; align-items: center; cursor: pointer; justify-content: center;">
<label for="sort-options">Sort by:</label>
<select id="sort-options" style="border:none;">
<option value="alphabetical" th:text="#{home.alphabetical}"> </option>
<!-- <option value="personal">Your most used</option> -->
<option value="global" th:text="#{home.globalPopularity}"></option>
<!-- <option value="server">Popularity in organisation</option> -->
</select>
</div>
<div
style="display: flex; margin-left:2rem;align-items: center; flex-wrap: wrap; align-content: flex-start; width: fit-content; max-width: 100%; gap:3rem; justify-content: center;">
<div th:title="#{home.setFavorites}" style="display: flex; align-items: center; cursor: pointer;" onclick="toggleFavoritesMode()">
<span class="material-symbols-rounded toggle-favourites"
style="font-size: 2rem; margin-left: 0.2rem;">
star star
</span> </span>
<span class="material-symbols-rounded filter-button" onclick="expandCollapseAll(true)"> </div>
expand_all <div onclick="toggleFavoritesView()" th:title="#{home.hideFavorites}" id="favouritesVisibility"
</span> style="display: flex; align-items: center; cursor: pointer;">
<span class="material-symbols-rounded filter-button" onclick="expandCollapseAll(false)"> <span id="toggle-favourites-icon" class="material-symbols-rounded toggle-favourites"
collapse_all style="font-size: 2rem; margin-left: 0.2rem;">
</span> visibility
<span class="material-symbols-rounded filter-button hidden" onclick="switchViewMode()">
dashboard
</span> </span>
</div> </div>
<a href="home" onclick="setAsDefault('home-legacy')" th:title="#{home.legacyHomepage}"
style="text-decoration: none; color: inherit; cursor: pointer; display: flex; align-items: center;">
</span>
<span class="material-symbols-rounded toggle-favourites"
style="font-size: 2rem; margin-left: 0.2rem;">
home
</span>
</a>
<div class="features-container"> </div>
</div>
</div>
</div>
</div>
<div>
</div>
<div class="features-container" style=" border-top: 1px;
border-top-style: solid;
border-color: var(--md-nav-color-on-seperator);
margin-top: 1rem;
">
<div th:if="${@shouldShow}" class="feature-card favorite update-notice" id="update-link" <div th:if="${@shouldShow}" class="feature-card favorite update-notice" id="update-link"
style="display: none;"> style="display: none;">
<a href="https://github.com/Stirling-Tools/Stirling-PDF/releases" target="_blank" rel="noopener"> <a href="https://github.com/Stirling-Tools/Stirling-PDF/releases" target="_blank" rel="noopener">
@ -60,278 +117,16 @@
</div> </div>
</a> </a>
</div> </div>
<div class="feature-rows">
<div id="groupFavorites" class="feature-group"> <div id="groupFavorites" class="feature-group">
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.favorite})}"> <div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.favorite})}">
</div> </div>
<div class="feature-group-container"> <div class="nav-group-container">
</div> </div>
</div> </div>
<th:block th:insert="~{fragments/navElements.html :: navElements}"></th:block>
</div>
<div id="popularTools" class="feature-group">
<div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.popular})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='view-pdf', cardTitle=#{home.viewPdf.title}, cardText=#{home.viewPdf.desc}, cardLink='view-pdf', toolIcon='menu_book', tags=#{viewPdf.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', toolIcon='construction', tags=#{multiTool.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', toolIcon='family_history', tags=#{pipeline.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='compress-pdf', cardTitle=#{home.compressPdfs.title}, cardText=#{home.compressPdfs.desc}, cardLink='compress-pdf', toolIcon='zoom_in_map', tags=#{compressPdfs.tags}, toolGroup='advance')}">
</div>
</div>
</div>
<div id="groupOrganize" class="feature-group">
<div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.organize})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', toolIcon='construction', tags=#{multiTool.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='merge-pdfs', cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs', toolIcon='add_to_photos', tags=#{merge.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='split-pdfs', cardTitle=#{home.split.title}, cardText=#{home.split.desc}, cardLink='split-pdfs', toolIcon='cut', tags=#{split.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='rotate-pdf', cardTitle=#{home.rotate.title}, cardText=#{home.rotate.desc}, cardLink='rotate-pdf', toolIcon='rotate_right', tags=#{rotate.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='crop', cardTitle=#{home.crop.title}, cardText=#{home.crop.desc}, cardLink='crop', toolIcon='crop', tags=#{crop.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-organizer', cardTitle=#{home.pdfOrganiser.title}, cardText=#{home.pdfOrganiser.desc}, cardLink='pdf-organizer', toolIcon='format_list_bulleted', tags=#{pdfOrganiser.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='remove-pages', cardTitle=#{home.removePages.title}, cardText=#{home.removePages.desc}, cardLink='remove-pages', toolIcon='delete', tags=#{removePages.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='multi-page-layout', cardTitle=#{home.pageLayout.title}, cardText=#{home.pageLayout.desc}, cardLink='multi-page-layout', toolIcon='dashboard', tags=#{pageLayout.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='scale-pages', cardTitle=#{home.scalePages.title}, cardText=#{home.scalePages.desc}, cardLink='scale-pages', toolIcon='fullscreen', tags=#{scalePages.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='extract-page', cardTitle=#{home.extractPage.title}, cardText=#{home.extractPage.desc}, cardLink='extract-page', toolIcon='upload', tags=#{extractPage.tags}, toolGroup='organize')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-single-page', cardTitle=#{home.PdfToSinglePage.title}, cardText=#{home.PdfToSinglePage.desc}, cardLink='pdf-to-single-page', toolIcon='looks_one', tags=#{PdfToSinglePage.tags}, toolGroup='organize')}">
</div>
</div>
</div>
<div id="groupConvertTo" class="feature-group">
<div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertTo})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='img-to-pdf', cardTitle=#{home.imageToPdf.title}, cardText=#{home.imageToPdf.desc}, cardLink='img-to-pdf', toolIcon='picture_as_pdf', tags=#{imageToPdf.tags}, toolGroup='image')}">
</div>
<div
th:replace="~{fragments/card :: card(id='file-to-pdf', cardTitle=#{home.fileToPDF.title}, cardText=#{home.fileToPDF.desc}, cardLink='file-to-pdf', toolIcon='draft', tags=#{fileToPDF.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='url-to-pdf', cardTitle=#{home.URLToPDF.title}, cardText=#{home.URLToPDF.desc}, cardLink='url-to-pdf', toolIcon='link', tags=#{URLToPDF.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='html-to-pdf', cardTitle=#{home.HTMLToPDF.title}, cardText=#{home.HTMLToPDF.desc}, cardLink='html-to-pdf', toolIcon='html', tags=#{HTMLToPDF.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='markdown-to-pdf', cardTitle=#{home.MarkdownToPDF.title}, cardText=#{home.MarkdownToPDF.desc}, cardLink='markdown-to-pdf', toolIcon='markdown', tags=#{MarkdownToPDF.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='book-to-pdf', cardTitle=#{home.BookToPDF.title}, cardText=#{home.BookToPDF.desc}, cardLink='book-to-pdf', toolIcon='book', tags=#{BookToPDF.tags}, toolGroup='convert')}">
</div>
</div>
</div>
<div id="groupConvertFrom" class="feature-group">
<div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.convertFrom})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='pdf-to-img', cardTitle=#{home.pdfToImage.title}, cardText=#{home.pdfToImage.desc}, cardLink='pdf-to-img', toolIcon='photo_library', tags=#{pdfToImage.tags}, toolGroup='image')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-pdfa', cardTitle=#{home.pdfToPDFA.title}, cardText=#{home.pdfToPDFA.desc}, cardLink='pdf-to-pdfa', toolIcon='picture_as_pdf', tags=#{pdfToPDFA.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-word', cardTitle=#{home.PDFToWord.title}, cardText=#{home.PDFToWord.desc}, cardLink='pdf-to-word', toolIcon='description', tags=#{PDFToWord.tags}, toolGroup='word')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-presentation', cardTitle=#{home.PDFToPresentation.title}, cardText=#{home.PDFToPresentation.desc}, cardLink='pdf-to-presentation', toolIcon='slideshow', tags=#{PDFToPresentation.tags}, toolGroup='ppt')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-text', cardTitle=#{home.PDFToText.title}, cardText=#{home.PDFToText.desc}, cardLink='pdf-to-text', toolIcon='text_fields', tags=#{PDFToText.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-html', cardTitle=#{home.PDFToHTML.title}, cardText=#{home.PDFToHTML.desc}, cardLink='pdf-to-html', toolIcon='html', tags=#{PDFToHTML.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-xml', cardTitle=#{home.PDFToXML.title}, cardText=#{home.PDFToXML.desc}, cardLink='pdf-to-xml', toolIcon='code', tags=#{PDFToXML.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-csv', cardTitle=#{home.tableExtraxt.title}, cardText=#{home.tableExtraxt.desc}, cardLink='pdf-to-csv', toolIcon='csv', tags=#{tableExtraxt.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-book', cardTitle=#{home.PDFToBook.title}, cardText=#{home.PDFToBook.desc}, cardLink='pdf-to-book', toolIcon='book', tags=#{PDFToBook.tags}, toolGroup='convert')}">
</div>
<div
th:replace="~{fragments/card :: card(id='pdf-to-markdown', cardTitle=#{home.PDFToMarkdown.title}, cardText=#{home.PDFToMarkdown.desc}, cardLink='pdf-to-markdown', toolIcon='markdown_copy', tags=#{PDFToMarkdown.tags}, toolGroup='convert')}">
</div>
</div>
</div>
<div id="groupSecurity" class="feature-group">
<div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.security})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='add-password', cardTitle=#{home.addPassword.title}, cardText=#{home.addPassword.desc}, cardLink='add-password', toolIcon='lock', tags=#{addPassword.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='remove-password', cardTitle=#{home.removePassword.title}, cardText=#{home.removePassword.desc}, cardLink='remove-password', toolIcon='lock_open_right', tags=#{removePassword.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='change-permissions', cardTitle=#{home.permissions.title}, cardText=#{home.permissions.desc}, cardLink='change-permissions', toolIcon='encrypted', tags=#{permissions.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='sign', cardTitle=#{home.sign.title}, cardText=#{home.sign.desc}, cardLink='sign', toolIcon='signature', tags=#{sign.tags}, toolGroup='sign')}">
</div>
<div
th:replace="~{fragments/card :: card(id='cert-sign', cardTitle=#{home.certSign.title}, cardText=#{home.certSign.desc}, cardLink='cert-sign', toolIcon='workspace_premium', tags=#{certSign.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='validate-signature', cardTitle=#{home.validateSignature.title}, cardText=#{home.validateSignature.desc}, cardLink='validate-signature', toolIcon='verified', tags=#{validateSignature.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='remove-cert-sign', cardTitle=#{home.removeCertSign.title}, cardText=#{home.removeCertSign.desc}, cardLink='remove-cert-sign', toolIcon='remove_moderator', tags=#{removeCertSign.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='sanitize-pdf', cardTitle=#{home.sanitizePdf.title}, cardText=#{home.sanitizePdf.desc}, cardLink='sanitize-pdf', toolIcon='sanitizer', tags=#{sanitizePdf.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='auto-redact', cardTitle=#{home.autoRedact.title}, cardText=#{home.autoRedact.desc}, cardLink='auto-redact', toolIcon='ink_eraser', tags=#{autoRedact.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='redact', cardTitle=#{home.redact.title}, cardText=#{home.redact.desc}, cardLink='redact', toolIcon='playlist_remove', tags=#{redact.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='stamp', cardTitle=#{home.AddStampRequest.title}, cardText=#{home.AddStampRequest.desc}, cardLink='stamp', toolIcon='approval', tags=#{AddStampRequest.tags}, toolGroup='security')}">
</div>
<div
th:replace="~{fragments/card :: card(id='add-watermark', cardTitle=#{home.watermark.title}, cardText=#{home.watermark.desc}, cardLink='add-watermark', toolIcon='water_drop', tags=#{watermark.tags}, toolGroup='security')}">
</div>
</div>
</div>
<div id="groupView" class="feature-group">
<div th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.edit})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='view-pdf', cardTitle=#{home.viewPdf.title}, cardText=#{home.viewPdf.desc}, cardLink='view-pdf', toolIcon='menu_book', tags=#{viewPdf.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='add-page-numbers', cardTitle=#{home.add-page-numbers.title}, cardText=#{home.add-page-numbers.desc}, cardLink='add-page-numbers', toolIcon='123', tags=#{add-page-numbers.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='add-image', cardTitle=#{home.addImage.title}, cardText=#{home.addImage.desc}, cardLink='add-image', toolIcon='add_photo_alternate', tags=#{addImage.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='change-metadata', cardTitle=#{home.changeMetadata.title}, cardText=#{home.changeMetadata.desc}, cardLink='change-metadata', toolIcon='assignment', tags=#{changeMetadata.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='ocr-pdf', cardTitle=#{home.ocr.title}, cardText=#{home.ocr.desc}, cardLink='ocr-pdf', toolIcon='quick_reference_all', tags=#{ocr.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='extract-images', cardTitle=#{home.extractImages.title}, cardText=#{home.extractImages.desc}, cardLink='extract-images', toolIcon='wallpaper', tags=#{extractImages.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='flatten', cardTitle=#{home.flatten.title}, cardText=#{home.flatten.desc}, cardLink='flatten', toolIcon='layers_clear', tags=#{flatten.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='remove-blanks', cardTitle=#{home.removeBlanks.title}, cardText=#{home.removeBlanks.desc}, cardLink='remove-blanks', toolIcon='scan_delete', tags=#{removeBlanks.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='remove-annotations', cardTitle=#{home.removeAnnotations.title}, cardText=#{home.removeAnnotations.desc}, cardLink='remove-annotations', toolIcon='thread_unread', tags=#{removeAnnotations.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='compare', cardTitle=#{home.compare.title}, cardText=#{home.compare.desc}, cardLink='compare', toolIcon='compare', tags=#{compare.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='get-info-on-pdf', cardTitle=#{home.getPdfInfo.title}, cardText=#{home.getPdfInfo.desc}, cardLink='get-info-on-pdf', toolIcon='info', tags=#{getPdfInfo.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='remove-image-pdf', cardTitle=#{home.removeImagePdf.title}, cardText=#{home.removeImagePdf.desc}, cardLink='remove-image-pdf', toolIcon='remove_selection', tags=#{removeImagePdf.tags}, toolGroup='other')}">
</div>
<div
th:replace="~{fragments/card :: card(id='replace-color-pdf', cardTitle=#{home.replaceColorPdf.title}, cardText=#{home.replaceColorPdf.desc}, cardLink='replace-and-invert-color-pdf', toolIcon='format_color_fill', tags=#{replaceColorPdf.tags}, toolGroup='other')}">
</div>
</div>
</div>
<div id="groupAdvanced" class="feature-group">
<div
th:replace="~{fragments/featureGroupHeader :: featureGroupHeader(groupTitle=#{navbar.sections.advance})}">
</div>
<div class="feature-group-container">
<div
th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', toolIcon='family_history', tags=#{pipeline.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='adjust-contrast', cardTitle=#{home.adjust-contrast.title}, cardText=#{home.adjust-contrast.desc}, cardLink='adjust-contrast', toolIcon='palette', tags=#{adjust-contrast.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='compress-pdf', cardTitle=#{home.compressPdfs.title}, cardText=#{home.compressPdfs.desc}, cardLink='compress-pdf', toolIcon='zoom_in_map', tags=#{compressPdfs.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='extract-image-scans', cardTitle=#{home.ScannerImageSplit.title}, cardText=#{home.ScannerImageSplit.desc}, cardLink='extract-image-scans', toolIcon='scanner', tags=#{ScannerImageSplit.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='repair', cardTitle=#{home.repair.title}, cardText=#{home.repair.desc}, cardLink='repair', toolIcon='build', tags=#{repair.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='auto-rename', cardTitle=#{home.auto-rename.title}, cardText=#{home.auto-rename.desc}, cardLink='auto-rename', toolIcon='text_fields_alt', tags=#{auto-rename.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='auto-split-pdf', cardTitle=#{home.autoSplitPDF.title}, cardText=#{home.autoSplitPDF.desc}, cardLink='auto-split-pdf', toolIcon='cut', tags=#{autoSplitPDF.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='show-javascript', cardTitle=#{home.showJS.title}, cardText=#{home.showJS.desc}, cardLink='show-javascript', toolIcon='javascript', tags=#{showJS.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='split-by-size-or-count', cardTitle=#{home.autoSizeSplitPDF.title}, cardText=#{home.autoSizeSplitPDF.desc}, cardLink='split-by-size-or-count', toolIcon='vertical_split', tags=#{autoSizeSplitPDF.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='overlay-pdf', cardTitle=#{home.overlay-pdfs.title}, cardText=#{home.overlay-pdfs.desc}, cardLink='overlay-pdf', toolIcon='layers', tags=#{overlay-pdfs.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='split-pdf-by-sections', cardTitle=#{home.split-by-sections.title}, cardText=#{home.split-by-sections.desc}, cardLink='split-pdf-by-sections', toolIcon='grid_on', tags=#{split-by-sections.tags}, toolGroup='advance')}">
</div>
<div
th:replace="~{fragments/card :: card(id='split-pdf-by-chapters', cardTitle=#{home.splitPdfByChapters.title}, cardText=#{home.splitPdfByChapters.desc}, cardLink='split-pdf-by-chapters', toolIcon='book', tags=#{splitPdfByChapters.tags}, toolGroup='advance')}">
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -403,108 +198,25 @@
</div> </div>
<style>
<script th:src="@{'/js/fetch-utils.js'}"></script> .favorite-icon {
<script th:inline="javascript"> cursor: pointer;
width: 0rem;
/*<![CDATA[*/ font-size: 2rem;
const analyticsPromptBoolean = /*[[${@analyticsPrompt}]]*/ false;
document.addEventListener('DOMContentLoaded', function () {
if (analyticsPromptBoolean) {
const analyticsModal = new bootstrap.Modal(document.getElementById('analyticsModal'));
analyticsModal.show();
}
});
/*]]>*/
function setAnalytics(enabled) {
fetchWithCsrf('api/v1/settings/update-enable-analytics', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(enabled)
})
.then(response => {
if (response.status === 200) {
console.log('Analytics setting updated successfully');
bootstrap.Modal.getInstance(document.getElementById('analyticsModal')).hide();
} else if (response.status === 208) {
console.log('Analytics setting has already been set. Please edit /config/settings.yml to change it.', response);
alert('Analytics setting has already been set. Please edit /config/settings.yml to change it.');
} else {
throw new Error('Unexpected response status: ' + response.status);
}
})
.catch(error => {
console.error('Error updating analytics setting:', error);
alert('An error occurred while updating the analytics setting. Please try again.');
});
} }
.toggle-favourites {
cursor: pointer;
document.addEventListener("DOMContentLoaded", function () {
const surveyVersion = "2.0";
const modal = new bootstrap.Modal(document.getElementById('surveyModal'));
const dontShowAgain = document.getElementById('dontShowAgain');
const takeSurveyButton = document.getElementById('takeSurvey');
const viewThresholds = [5, 10, 15, 22, 30, 50, 75, 100, 150, 200];
// Check if survey version changed and reset page views if it did
const storedVersion = localStorage.getItem('surveyVersion');
if (storedVersion && storedVersion !== surveyVersion) {
localStorage.setItem('pageViews', '0');
} }
let pageViews = parseInt(localStorage.getItem('pageViews') || '0'); .toggle-favourites.active {
color: gold;
pageViews++;
localStorage.setItem('pageViews', pageViews.toString());
function shouldShowSurvey() {
if (localStorage.getItem('dontShowSurvey') === 'true' ||
localStorage.getItem('surveyTaken') === 'true') {
return false;
} }
</style>
// If survey version changed and we hit a threshold, show the survey <script th:src="@{'/js/fetch-utils.js'}">
if (localStorage.getItem('surveyVersion') !== surveyVersion && window.analyticsPromptBoolean = /*[[${@analyticsPrompt}]]*/ false;
viewThresholds.includes(pageViews)) {
return true;
}
return viewThresholds.includes(pageViews);
}
if (shouldShowSurvey()) {
modal.show();
}
dontShowAgain.addEventListener('change', function () {
if (this.checked) {
localStorage.setItem('dontShowSurvey', 'true');
localStorage.setItem('surveyVersion', surveyVersion);
} else {
localStorage.removeItem('dontShowSurvey');
localStorage.removeItem('surveyVersion');
}
});
takeSurveyButton.addEventListener('click', function () {
localStorage.setItem('surveyTaken', 'true');
localStorage.setItem('surveyVersion', surveyVersion);
modal.hide();
});
if (localStorage.getItem('dontShowSurvey')) {
modal.hide();
}
});
</script> </script>
<script th:src="@{'/js/pages/home.js'}"></script>
</body> </body>

View File

@ -34,7 +34,7 @@
</div> </div>
<div class="draggable-buttons-box ignore-rtl"> <div class="draggable-buttons-box ignore-rtl">
<button class="btn btn-outline-secondary" <button class="btn btn-outline-secondary" th:title="#{sign.delete}"
onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())" onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())"
style="color: #C02223; border-color: #C02223; background-color: rgba(255, 0, 0, 0.1);"> style="color: #C02223; border-color: #C02223; background-color: rgba(255, 0, 0, 0.1);">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash"
@ -44,14 +44,12 @@
<path <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" /> 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>
<span class="btn-tooltip" th:text="#{sign.delete}"></span>
</button> </button>
<button class="btn btn-outline-secondary" <button class="btn btn-outline-secondary" th:title="#{sign.addToAll}"
onclick="DraggableUtils.addAllPagesDraggableCanvas(DraggableUtils.getLastInteracted())"> onclick="DraggableUtils.addAllPagesDraggableCanvas(DraggableUtils.getLastInteracted())">
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
content_copy content_copy
</span> </span>
<span class="btn-tooltip" th:text="#{sign.addToAll}"></span>
</button> </button>
<div id="rotation-controls" class="align-items-center" style="display: none;"> <div id="rotation-controls" class="align-items-center" style="display: none;">
<div class="input-with-icon"> <div class="input-with-icon">
@ -62,49 +60,44 @@
style="width: 6rem" /> style="width: 6rem" />
</div> </div>
</div> </div>
<button id="ratioToggleBtn" class="btn btn-outline-secondary" <button id="ratioToggleBtn" th:title="#{sign.maintainRatio}" class="btn btn-outline-secondary"
onclick="DraggableUtils.toggleMaintainRatio()"> onclick="DraggableUtils.toggleMaintainRatio()">
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
Aspect_Ratio Aspect_Ratio
</span> </span>
<span class="btn-tooltip" th:text="#{sign.maintainRatio}"></span>
</button> </button>
<button class="btn btn-outline-secondary" onclick="goToFirstOrLastPage(false)" style="margin-left:auto"> <button class="btn btn-outline-secondary" th:title="#{sign.first}" onclick="goToFirstOrLastPage(false)"
style="margin-left:auto">
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
keyboard_double_arrow_left keyboard_double_arrow_left
</span> </span>
<span class="btn-tooltip" th:text="#{sign.first}"></span>
</button> </button>
<button class="btn btn-outline-secondary" id="incrementPage" <button class="btn btn-outline-secondary" th:title="#{sign.previous}" id="incrementPage"
onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()"> onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-chevron-left" viewBox="0 0 16 16"> class="bi bi-chevron-left" viewBox="0 0 16 16">
<path fill-rule="evenodd" <path fill-rule="evenodd"
d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z" /> d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z" />
</svg> </svg>
<span class="btn-tooltip" th:text="#{sign.previous}"></span>
</button> </button>
<button class="btn btn-outline-secondary" id="decrementPage" <button class="btn btn-outline-secondary" th:title="#{sign.next}" id="decrementPage"
onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.decrementPage() : DraggableUtils.incrementPage()"> onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.decrementPage() : DraggableUtils.incrementPage()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-chevron-right" viewBox="0 0 16 16"> class="bi bi-chevron-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" <path fill-rule="evenodd"
d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z" /> d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z" />
</svg> </svg>
<span class="btn-tooltip" th:text="#{sign.next}"></span>
</button> </button>
<button class="btn btn-outline-secondary" onclick="goToFirstOrLastPage(true)"> <button class="btn btn-outline-secondary" th:title="#{sign.last}" onclick="goToFirstOrLastPage(true)">
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
keyboard_double_arrow_right keyboard_double_arrow_right
</span> </span>
<span class="btn-tooltip" th:text="#{sign.last}"></span>
</button> </button>
<button id="download-pdf" class="btn btn-outline-secondary" <button id="download-pdf" th:title="#{downloadPdf}" class="btn btn-outline-secondary"
style="border-color: green; color:#b2e3a8; background: rgba(24, 122, 5, 1)"> style="border-color: green; color:#b2e3a8; background: rgba(24, 122, 5, 1)">
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
download download
</span> </span>
<span class="btn-tooltip" th:text="#{downloadPdf}"></span>
</button> </button>
</div> </div>
<!-- draggables box --> <!-- draggables box -->

View File

@ -3,7 +3,6 @@
<head> <head>
<th:block th:insert="~{fragments/common :: head(title=#{auto-rename.title}, header=#{auto-rename.header})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{auto-rename.title}, header=#{auto-rename.header})}"></th:block>
</head> </head>
<body> <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">
@ -14,7 +13,11 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon advance">text_fields_alt</span> <span >
<svg class="tool-header-icon advance">
<use xlink:href="/images/rename.svg#icon-rename"></use>
</svg>
</span>
<span class="tool-header-text" th:text="#{auto-rename.header}"></span> <span class="tool-header-text" th:text="#{auto-rename.header}"></span>
</div> </div>
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/misc/auto-rename'}"> <form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/misc/auto-rename'}">

View File

@ -27,96 +27,84 @@
th:placeholder="#{multiTool.uploadPrompts}"> th:placeholder="#{multiTool.uploadPrompts}">
</div> </div>
<div class="mt-action-btn"> <div class="mt-action-btn">
<button class="btn btn-primary" onclick="addFiles()"> <button class="btn btn-primary" th:title="#{multiTool.addFile}" onclick="addFiles()">
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
add add
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.addFile}"></span>
</button> </button>
<button class="btn btn-secondary enable-on-file" onclick="rotateAll(-90)" disabled> <button class="btn btn-secondary enable-on-file" th:title="#{multiTool.rotateLeft}"
onclick="rotateAll(-90)" disabled>
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
rotate_left rotate_left
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.rotateLeft}"></span>
</button> </button>
<button class="btn btn-secondary enable-on-file" onclick="rotateAll(90)" disabled> <button class="btn btn-secondary enable-on-file" th:title="#{multiTool.rotateRight}"
onclick="rotateAll(90)" disabled>
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
rotate_right rotate_right
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.rotateRight}"></span>
</button> </button>
<button class="btn btn-secondary enable-on-file" onclick="splitAll()" disabled> <button class="btn btn-secondary enable-on-file" th:title="#{multiTool.split}" onclick="splitAll()"
disabled>
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
cut cut
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.split}"></span>
</button> </button>
<button class="btn btn-secondary enable-on-file" onclick="addFilesBlankAll()" disabled> <button class="btn btn-secondary enable-on-file" th:title="#{multiTool.insertPageBreak}"
onclick="addFilesBlankAll()" disabled>
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
insert_page_break insert_page_break
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.insertPageBreak}"></span>
</button> </button>
<button id="undo-btn" class="btn btn-secondary" onclick="undo()" disabled> <button id="undo-btn" th:title="#{multiTool.undo}" class="btn btn-secondary" onclick="undo()"
disabled>
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
undo undo
</span> </span>
<span class="btn-tooltip">
<div th:text="#{multiTool.undo}"></div>
<div class="text-uppercase" th:text="'(CTRL + Z)'"></div>
</span>
</button> </button>
<button id="redo-btn" class="btn btn-secondary" onclick="redo()" disabled> <button id="redo-btn" class="btn btn-secondary" th:title="#{multiTool.redo}" onclick="redo()"
disabled>
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
redo redo
</span> </span>
<span class="btn-tooltip">
<div th:text="#{multiTool.redo}"></div>
<div class="text-uppercase" th:text="'(CTRL + Y)'"></div>
</span>
</button> </button>
</button> </button>
<button id="select-pages-container" class="btn btn-secondary enable-on-file" <button id="select-pages-container" th:title="#{multiTool.selectPages}"
onclick="toggleSelectPageVisibility()" disabled> class="btn btn-secondary enable-on-file" onclick="toggleSelectPageVisibility()" disabled>
<span id="select-pages-button" class="material-symbols-rounded"> <span id="select-pages-button" class="material-symbols-rounded">
event_list event_list
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.selectPages}"></span>
</button> </button>
<button id="deselect-All-Container" class="btn btn-secondary enable-on-file hidden" <button id="deselect-All-Container" th:title="#{multiTool.deselectAll}"
onclick="toggleSelectAll()" disabled> class="btn btn-secondary enable-on-file hidden" onclick="toggleSelectAll()" disabled>
<span class="material-symbols-rounded" id="deselect-icon">deselect</span> <span class="material-symbols-rounded" id="deselect-icon">deselect</span>
<span class="btn-tooltip" th:text="#{multiTool.deselectAll}"></span>
</button> </button>
<button id="select-All-Container" class="btn btn-secondary enable-on-file hidden" <button id="select-All-Container" th:title="#{multiTool.selectAll}"
onclick="toggleSelectAll()" disabled> class="btn btn-secondary enable-on-file hidden" onclick="toggleSelectAll()" disabled>
<span class="material-symbols-rounded" id="select-icon">select_all</span> <span class="material-symbols-rounded" id="select-icon">select_all</span>
<span class="btn-tooltip" th:text="#{multiTool.selectAll}"></span>
</button> </button>
<div class="button-container"> <div class="button-container">
<button id="delete-button" class="btn btn-danger delete hidden" onclick="deleteSelected()"> <button id="delete-button" th:title="#{multiTool.deleteSelected}"
class="btn btn-danger delete hidden" onclick="deleteSelected()">
<span class="material-symbols-rounded">delete</span> <span class="material-symbols-rounded">delete</span>
<span class="btn-tooltip" th:text="#{multiTool.deleteSelected}"></span>
</button> </button>
</div> </div>
<div style="margin-left:auto"> <div style="margin-left:auto">
<button id="export-selected-button" <button id="export-selected-button" th:title="#{multiTool.downloadSelected}"
style="border-color: green; color:#b2e3a8; background: rgba(24, 122, 5, 1)" style="border-color: green; color:#b2e3a8; background: rgba(24, 122, 5, 1)"
class="btn btn-primary enable-on-file hidden" onclick="exportPdf(true)" disabled> class="btn btn-primary enable-on-file hidden" onclick="exportPdf(true)" disabled>
<span class="btn-tooltip" th:text="#{multiTool.downloadSelected}"></span>
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
file_save file_save
</span> </span>
</button> </button>
<button style="border-color: green; color:#b2e3a8; background: rgba(24, 122, 5, 1)" <button style="border-color: green; color:#b2e3a8; background: rgba(24, 122, 5, 1)"
id="export-button" class="btn btn-primary enable-on-file" onclick="exportPdf(false)" disabled> th:title="#{multiTool.downloadAll}" id="export-button" class="btn btn-primary enable-on-file"
onclick="exportPdf(false)" disabled>
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
download download
</span> </span>
<span class="btn-tooltip" th:text="#{multiTool.downloadAll}"></span>
</button> </button>
</div> </div>
</div> </div>

View File

@ -13,7 +13,7 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon security">ink_eraser</span> <span class="material-symbols-rounded tool-header-icon security">playlist_remove</span>
<span class="tool-header-text" th:text="#{autoRedact.header}"></span> <span class="tool-header-text" th:text="#{autoRedact.header}"></span>
</div> </div>
<form th:action="@{'api/v1/security/auto-redact'}" method="post" enctype="multipart/form-data"> <form th:action="@{'api/v1/security/auto-redact'}" method="post" enctype="multipart/form-data">

View File

@ -31,7 +31,7 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 bg-card"> <div class="col-md-6 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon security">ink_eraser</span> <span class="material-symbols-rounded tool-header-icon security">playlist_remove</span>
<span class="tool-header-text" th:text="#{redact.header}"></span> <span class="tool-header-text" th:text="#{redact.header}"></span>
</div> </div>
<form th:action="@{'api/v1/security/redact'}" method="post" enctype="multipart/form-data"> <form th:action="@{'api/v1/security/redact'}" method="post" enctype="multipart/form-data">
@ -86,25 +86,19 @@
<div id="toolbarSidebar"> <div id="toolbarSidebar">
<div id="toolbarSidebarLeft"> <div id="toolbarSidebarLeft">
<div id="sidebarViewButtons" class="splitToolbarButton toggled" role="radiogroup"> <div id="sidebarViewButtons" class="splitToolbarButton toggled" role="radiogroup">
<button id="viewThumbnail" class="toolbarButton toggled toolbar-btn-hover" tabindex="2" <button id="viewThumbnail" th:title="#{redact.showThumbnails}" class="toolbarButton toggled" tabindex="2"
data-l10n-id="pdfjs-thumbs-button" role="radio" aria-checked="true" aria-controls="thumbnailView"> data-l10n-id="pdfjs-thumbs-button" role="radio" aria-checked="true" aria-controls="thumbnailView">
<span class="btn-tooltip" th:text="#{redact.showThumbnails}"></span>
</button> </button>
<button id="viewOutline" class="toolbarButton toolbar-btn-hover" <button id="viewOutline" th:title="#{redact.showDocumentOutline}" class="toolbarButton" tabindex="3"
tabindex="3"
data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false" data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false"
aria-controls="outlineView"> aria-controls="outlineView">
<span class="btn-tooltip" th:text="#{redact.showDocumentOutline}"></span>
</button> </button>
<button id="viewAttachments" class="d-none toolbarButton" tabindex="4" <button id="viewAttachments" th:title="#{redact.showAttachments}" class="d-none toolbarButton" tabindex="4"
data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false" aria-controls="attachmentsView"> data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false" aria-controls="attachmentsView">
<span class="btn-tooltip" th:text="#{redact.showAttachments}"></span>
<span data-l10n-id="pdfjs-attachments-button-label">Attachments</span> <span data-l10n-id="pdfjs-attachments-button-label">Attachments</span>
</button> </button>
<button id="viewLayers" class="d-none toolbarButton" <button id="viewLayers" th:title="#{redact.showLayers}" class="d-none toolbarButton" tabindex="5"
tabindex="5"
data-l10n-id="pdfjs-layers-button" role="radio" aria-checked="false" aria-controls="layersView"> data-l10n-id="pdfjs-layers-button" role="radio" aria-checked="false" aria-controls="layersView">
<span class="btn-tooltip" th:text="#{redact.showLayers}"></span>
<span data-l10n-id="pdfjs-layers-button-label">Layers</span> <span data-l10n-id="pdfjs-layers-button-label">Layers</span>
</button> </button>
</div> </div>
@ -114,9 +108,8 @@
<div id="outlineOptionsContainer"> <div id="outlineOptionsContainer">
<div class="verticalToolbarSeparator"></div> <div class="verticalToolbarSeparator"></div>
<button id="currentOutlineItem" class="toolbarButton" disabled="disabled" <button id="currentOutlineItem" th:title="#{redact.findCurrentOutlineItem}" class="toolbarButton"
tabindex="6" data-l10n-id="pdfjs-current-outline-item-button"> disabled="disabled" tabindex="6" data-l10n-id="pdfjs-current-outline-item-button">
<span class="btn-tooltip" th:text="#{redact.findCurrentOutlineItem}"></span>
</button> </button>
</div> </div>
</div> </div>
@ -375,8 +368,8 @@
</button> </button>
</div> </div>
<button id="sidebarToggle" class="toolbarButton mt-2" tabindex="11" <button id="sidebarToggle" class="toolbarButton mt-2" tabindex="11"
data-l10n-id="pdfjs-toggle-sidebar-button" aria-expanded="false" aria-controls="sidebarContainer"> data-l10n-id="pdfjs-toggle-sidebar-button" th:title="#{redact.toggleSidebar}" aria-expanded="false"
<span class="btn-tooltip" th:text="#{redact.toggleSidebar}"></span> aria-controls="sidebarContainer">
</button> </button>
<div class="toolbarButtonSpacer d-none"></div> <div class="toolbarButtonSpacer d-none"></div>
<button id="viewFind" class="d-none toolbarButton" title="Find in Document" tabindex="12" <button id="viewFind" class="d-none toolbarButton" title="Find in Document" tabindex="12"
@ -384,14 +377,12 @@
<span data-l10n-id="pdfjs-findbar-button-label">Find</span> <span data-l10n-id="pdfjs-findbar-button-label">Find</span>
</button> </button>
<div class="splitToolbarButton hiddenSmallView"> <div class="splitToolbarButton hiddenSmallView">
<button class="toolbarButton btn-secondary toolbar-btn-hover" id="previous" tabindex="13" <button class="toolbarButton btn-secondary" th:title="#{redact.previousPage}" id="previous"
data-l10n-id="pdfjs-previous-button"> tabindex="13" data-l10n-id="pdfjs-previous-button">
<span class="btn-tooltip" th:text="#{redact.previousPage}"></span>
</button> </button>
<div class="splitToolbarButtonSeparator d-none"></div> <div class="splitToolbarButtonSeparator d-none"></div>
<button class="toolbarButton btn-secondary toolbar-btn-hover" id="next" tabindex="14" <button class="toolbarButton btn-secondary" th:title="#{redact.nextPage}" id="next" tabindex="14"
data-l10n-id="pdfjs-next-button"> data-l10n-id="pdfjs-next-button">
<span class="btn-tooltip" th:text="#{redact.nextPage}"></span>
</button> </button>
</div> </div>
<span class="loadingInput start"> <span class="loadingInput start">
@ -403,14 +394,12 @@
</div> </div>
<div id="toolbarViewerMiddle"> <div id="toolbarViewerMiddle">
<div class="splitToolbarButton"> <div class="splitToolbarButton">
<button id="zoomOut" class="toolbarButton btn-primary toolbar-btn-hover" tabindex="21" <button id="zoomOut" th:title="#{redact.zoomOut}" class="toolbarButton btn-primary" tabindex="21"
data-l10n-id="pdfjs-zoom-out-button"> data-l10n-id="pdfjs-zoom-out-button">
<span class="btn-tooltip" th:text="#{redact.zoomOut}"></span>
</button> </button>
<div class="splitToolbarButtonSeparator"></div> <div class="splitToolbarButtonSeparator"></div>
<button id="zoomIn" class="toolbarButton btn-primary toolbar-btn-hover" tabindex="22" <button id="zoomIn" th:title="#{redact.zoomIn}" class="toolbarButton btn-primary" tabindex="22"
data-l10n-id="pdfjs-zoom-in-button"> data-l10n-id="pdfjs-zoom-in-button">
<span class="btn-tooltip" th:text="#{redact.zoomIn}"></span>
</button> </button>
</div> </div>
<span id="scaleSelectContainer" class="dropdownToolbarButton"> <span id="scaleSelectContainer" class="dropdownToolbarButton">
@ -446,26 +435,23 @@
</span> </span>
</div> </div>
<div id="redactionsToolbarViewer" class="splitToolbarButton d-flex"> <div id="redactionsToolbarViewer" class="splitToolbarButton d-flex">
<button id="man-text-select-redact" class="btn-primary" tabindex="22"> <button id="man-text-select-redact" th:title="#{redact.textBasedRedaction}" class="btn-primary"
<span class="btn-tooltip" th:text="#{redact.textBasedRedaction}"></span> tabindex="22">
<span id="text-selection" class="material-symbols-rounded user-select-none pe-none">text_select_start <span id="text-selection" class="material-symbols-rounded user-select-none pe-none">text_select_start
</span> </span>
</button> </button>
<button id="man-shape-redact" class="btn-primary" tabindex="22"> <button id="man-shape-redact" th:title="#{redact.boxRedaction}" class="btn-primary" tabindex="22">
<span class="btn-tooltip" th:text="#{redact.boxRedaction}"></span>
<span id="shape-selection" class="material-symbols-rounded user-select-none pe-none">pageless <span id="shape-selection" class="material-symbols-rounded user-select-none pe-none">pageless
</span> </span>
</button> </button>
<button id="redactionsPaletteContainer" class="btn-primary"> <button id="redactionsPaletteContainer" th:title="#{redact.colourPicker}" class="btn-primary">
<span class="btn-tooltip" th:text="#{redact.colourPicker}"></span>
<label id="redactions-palette" class="material-symbols-rounded palette-color text-center" <label id="redactions-palette" class="material-symbols-rounded palette-color text-center"
style="--palette-color: #000000;"> style="--palette-color: #000000;">
palette palette
<input type="color" name="color-picker"> <input type="color" name="color-picker">
</label> </label>
</button> </button>
<button id="apply-redaction" class="btn-success d-none" disabled> <button id="apply-redaction" th:title="#{redact.applyChanges}" class="btn-success d-none" disabled>
<span class="btn-tooltip" th:text="#{redact.applyChanges}"></span>
<span id="apply-redaction-icon" class="material-symbols-rounded"> <span id="apply-redaction-icon" class="material-symbols-rounded">
check check
</span> </span>
@ -474,26 +460,22 @@
<div id="toolbarViewerRight"> <div id="toolbarViewerRight">
<div class="splitToolbarButton"> <div class="splitToolbarButton">
<button id="pdfToImageBtn" class="btn-success"> <button id="pdfToImageBtn" class="btn-success">
<span class="btn-tooltip" th:text="#{redact.convertPDFToImageLabel}"></span> <span id="pdfToImageBtnIcon" th:title="#{redact.convertPDFToImageLabel}"
<span id="pdfToImageBtnIcon" class="material-symbols-rounded"> class="material-symbols-rounded">
image image
</span> </span>
</button> </button>
<button id="pageBasedRedactionBtn" class="btn-primary"> <button id="pageBasedRedactionBtn" class="btn-primary">
<span class="btn-tooltip" th:text="#{redact.pageBasedRedaction}"></span> <span id="pageBasedRedactionBtnIcon" th:title="#{redact.pageBasedRedaction}"
<span id="pageBasedRedactionBtnIcon" class="material-symbols-rounded"> class="material-symbols-rounded">
document_scanner document_scanner
</span> </span>
</button> </button>
<button id="uploadBtn" class="btn-primary"> <button id="uploadBtn" class="btn-primary">
<span class="btn-tooltip" th:text="#{redact.upload}"></span> <span id="uploadBtnIcon" class="material-symbols-rounded" th:title="#{redact.upload}"> upload </span>
<span id="uploadBtnIcon" class="material-symbols-rounded">
upload
</span>
</button> </button>
<button id="downloadBtn" class="btn-primary"> <button id="downloadBtn" class="btn-primary">
<span class="btn-tooltip" th:text="#{redact.export}"></span> <span id="downloadBtnIcon" th:title="#{redact.export}" class="material-symbols-rounded">
<span id="downloadBtnIcon" class="material-symbols-rounded">
download download
</span> </span>
</button> </button>
@ -554,6 +536,7 @@
<div id="viewerContainer" tabindex="0"> <div id="viewerContainer" tabindex="0">
<div id="viewer" class="pdfViewer"></div> <div id="viewer" class="pdfViewer"></div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</div> <!-- mainContainer --> </div> <!-- mainContainer -->

View File

@ -149,7 +149,7 @@
<script th:src="@{'/js/sign/signature-canvas.js'}"></script> <script th:src="@{'/js/sign/signature-canvas.js'}"></script>
<!-- draggables box --> <!-- draggables box -->
<div class="draggable-buttons-box ignore-rtl"> <div class="draggable-buttons-box ignore-rtl">
<button class="btn btn-outline-secondary" <button class="btn btn-outline-secondary" th:title="#{sign.delete}"
onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())" onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())"
style="border-color: #C02223; color: #C02223; background: rgba(255, 0, 0, 0.1)"> style="border-color: #C02223; color: #C02223; background: rgba(255, 0, 0, 0.1)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash"
@ -159,14 +159,12 @@
<path <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" /> 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>
<span class="btn-tooltip" th:text="#{sign.delete}"></span>
</button> </button>
<button class="btn btn-outline-secondary" <button class="btn btn-outline-secondary" th:title="#{sign.addToAll}"
onclick="DraggableUtils.addAllPagesDraggableCanvas(DraggableUtils.getLastInteracted())"> onclick="DraggableUtils.addAllPagesDraggableCanvas(DraggableUtils.getLastInteracted())">
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
content_copy content_copy
</span> </span>
<span class="btn-tooltip" th:text="#{sign.addToAll}"></span>
</button> </button>
<div id="rotation-controls" class="align-items-center" style="display: none;"> <div id="rotation-controls" class="align-items-center" style="display: none;">
<div class="input-with-icon"> <div class="input-with-icon">
@ -177,49 +175,44 @@
style="width: 6rem" /> style="width: 6rem" />
</div> </div>
</div> </div>
<button id="ratioToggleBtn" class="btn btn-outline-secondary" <button id="ratioToggleBtn" th:title="#{sign.maintainRatio}" class="btn btn-outline-secondary"
onclick="DraggableUtils.toggleMaintainRatio()"> onclick="DraggableUtils.toggleMaintainRatio()">
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
Aspect_Ratio Aspect_Ratio
</span> </span>
<span class="btn-tooltip" th:text="#{sign.maintainRatio}"></span>
</button> </button>
<button class="btn btn-outline-secondary" onclick="goToFirstOrLastPage(false)" style="margin-left:auto"> <button class="btn btn-outline-secondary" th:title="#{sign.first}" onclick="goToFirstOrLastPage(false)"
style="margin-left:auto">
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
keyboard_double_arrow_left keyboard_double_arrow_left
</span> </span>
<span class="btn-tooltip" th:text="#{sign.first}"></span>
</button> </button>
<button class="btn btn-outline-secondary" id="incrementPage" <button class="btn btn-outline-secondary" th:title="#{sign.previous}" id="incrementPage"
onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()"> onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-chevron-left" viewBox="0 0 16 16"> class="bi bi-chevron-left" viewBox="0 0 16 16">
<path fill-rule="evenodd" <path fill-rule="evenodd"
d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z" /> d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z" />
</svg> </svg>
<span class="btn-tooltip" th:text="#{sign.previous}"></span>
</button> </button>
<button class="btn btn-outline-secondary" id="decrementPage" <button class="btn btn-outline-secondary" id="decrementPage" th:title="#{sign.next}"
onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.decrementPage() : DraggableUtils.incrementPage()"> onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.decrementPage() : DraggableUtils.incrementPage()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-chevron-right" viewBox="0 0 16 16"> class="bi bi-chevron-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" <path fill-rule="evenodd"
d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z" /> d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z" />
</svg> </svg>
<span class="btn-tooltip" th:text="#{sign.next}"></span>
</button> </button>
<button class="btn btn-outline-secondary" onclick="goToFirstOrLastPage(true)"> <button class="btn btn-outline-secondary" th:title="#{sign.last}" onclick="goToFirstOrLastPage(true)">
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
keyboard_double_arrow_right keyboard_double_arrow_right
</span> </span>
<span class="btn-tooltip" th:text="#{sign.last}"></span>
</button> </button>
<button id="download-pdf" class="btn btn-outline-secondary" <button id="download-pdf" th:title="#{downloadPdf}" class="btn btn-outline-secondary"
style="border-color: green; color:#b2e3a8; background: rgba(24, 122, 5, 1)"> style="border-color: green; color:#b2e3a8; background: rgba(24, 122, 5, 1)">
<span class="material-symbols-rounded"> <span class="material-symbols-rounded">
download download
</span> </span>
<span class="btn-tooltip" th:text="#{downloadPdf}"></span>
</button> </button>
</div> </div>
<div id="box-drag-container" class="show-on-file-selected"> <div id="box-drag-container" class="show-on-file-selected">

View File

@ -20,8 +20,11 @@ Adobe CMap resources are covered by their own copyright but the same license:
See https://github.com/adobe-type-tools/cmap-resources See https://github.com/adobe-type-tools/cmap-resources
--> -->
<html dir="ltr" mozdisallowselectionprint th:lang="${#locale.language}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org"> <html dir="ltr" mozdisallowselectionprint th:lang="${#locale.language}" th:data-language="${#locale.toString()}"
<head> xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{redact.title}, header=#{redact.header})}"></th:block>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="google" content="notranslate"> <meta name="google" content="notranslate">
@ -40,25 +43,32 @@ See https://github.com/adobe-type-tools/cmap-resources
<link rel="stylesheet" th:href="@{'/pdfjs-legacy/css/viewer.css'}"> <link rel="stylesheet" th:href="@{'/pdfjs-legacy/css/viewer.css'}">
<script th:src="@{'/pdfjs-legacy/js/viewer.mjs'}" type="module"></script> <script th:src="@{'/pdfjs-legacy/js/viewer.mjs'}" type="module"></script>
</head> </head>
<body tabindex="1"> <body tabindex="1" style="overflow: hidden;">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<div id="outerContainer"> <div id="outerContainer">
<div id="sidebarContainer"> <div id="sidebarContainer">
<div id="toolbarSidebar"> <div id="toolbarSidebar">
<div id="toolbarSidebarLeft"> <div id="toolbarSidebarLeft">
<div id="sidebarViewButtons" class="splitToolbarButton toggled" role="radiogroup"> <div id="sidebarViewButtons" class="splitToolbarButton toggled" role="radiogroup">
<button id="viewThumbnail" class="toolbarButton toggled" title="Show Thumbnails" tabindex="2" data-l10n-id="pdfjs-thumbs-button" role="radio" aria-checked="true" aria-controls="thumbnailView"> <button id="viewThumbnail" class="toolbarButton toggled" title="Show Thumbnails" tabindex="2"
data-l10n-id="pdfjs-thumbs-button" role="radio" aria-checked="true" aria-controls="thumbnailView">
<span data-l10n-id="pdfjs-thumbs-button-label">Thumbnails</span> <span data-l10n-id="pdfjs-thumbs-button-label">Thumbnails</span>
</button> </button>
<button id="viewOutline" class="toolbarButton" title="Show Document Outline (double-click to expand/collapse all items)" tabindex="3" data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false" aria-controls="outlineView"> <button id="viewOutline" class="toolbarButton"
title="Show Document Outline (double-click to expand/collapse all items)" tabindex="3"
data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false"
aria-controls="outlineView">
<span data-l10n-id="pdfjs-document-outline-button-label">Document Outline</span> <span data-l10n-id="pdfjs-document-outline-button-label">Document Outline</span>
</button> </button>
<button id="viewAttachments" class="toolbarButton" title="Show Attachments" tabindex="4" data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false" aria-controls="attachmentsView"> <button id="viewAttachments" class="toolbarButton" title="Show Attachments" tabindex="4"
data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false" aria-controls="attachmentsView">
<span data-l10n-id="pdfjs-attachments-button-label">Attachments</span> <span data-l10n-id="pdfjs-attachments-button-label">Attachments</span>
</button> </button>
<button id="viewLayers" class="toolbarButton" title="Show Layers (double-click to reset all layers to the default state)" tabindex="5" data-l10n-id="pdfjs-layers-button" role="radio" aria-checked="false" aria-controls="layersView"> <button id="viewLayers" class="toolbarButton"
title="Show Layers (double-click to reset all layers to the default state)" tabindex="5"
data-l10n-id="pdfjs-layers-button" role="radio" aria-checked="false" aria-controls="layersView">
<span data-l10n-id="pdfjs-layers-button-label">Layers</span> <span data-l10n-id="pdfjs-layers-button-label">Layers</span>
</button> </button>
</div> </div>
@ -68,7 +78,8 @@ See https://github.com/adobe-type-tools/cmap-resources
<div id="outlineOptionsContainer"> <div id="outlineOptionsContainer">
<div class="verticalToolbarSeparator"></div> <div class="verticalToolbarSeparator"></div>
<button id="currentOutlineItem" class="toolbarButton" disabled="disabled" title="Find Current Outline Item" tabindex="6" data-l10n-id="pdfjs-current-outline-item-button"> <button id="currentOutlineItem" class="toolbarButton" disabled="disabled" title="Find Current Outline Item"
tabindex="6" data-l10n-id="pdfjs-current-outline-item-button">
<span data-l10n-id="pdfjs-current-outline-item-button-label">Current Outline Item</span> <span data-l10n-id="pdfjs-current-outline-item-button-label">Current Outline Item</span>
</button> </button>
</div> </div>
@ -91,14 +102,17 @@ See https://github.com/adobe-type-tools/cmap-resources
<div class="findbar hidden doorHanger" id="findbar"> <div class="findbar hidden doorHanger" id="findbar">
<div id="findbarInputContainer"> <div id="findbarInputContainer">
<span class="loadingInput end"> <span class="loadingInput end">
<input id="findInput" class="toolbarField" title="Find" placeholder="Find in document…" tabindex="91" data-l10n-id="pdfjs-find-input" aria-invalid="false"> <input id="findInput" class="toolbarField" title="Find" placeholder="Find in document…" tabindex="91"
data-l10n-id="pdfjs-find-input" aria-invalid="false">
</span> </span>
<div class="splitToolbarButton"> <div class="splitToolbarButton">
<button id="findPrevious" class="toolbarButton" title="Find the previous occurrence of the phrase" tabindex="92" data-l10n-id="pdfjs-find-previous-button"> <button id="findPrevious" class="toolbarButton" title="Find the previous occurrence of the phrase"
tabindex="92" data-l10n-id="pdfjs-find-previous-button">
<span data-l10n-id="pdfjs-find-previous-button-label">Previous</span> <span data-l10n-id="pdfjs-find-previous-button-label">Previous</span>
</button> </button>
<div class="splitToolbarButtonSeparator"></div> <div class="splitToolbarButtonSeparator"></div>
<button id="findNext" class="toolbarButton" title="Find the next occurrence of the phrase" tabindex="93" data-l10n-id="pdfjs-find-next-button"> <button id="findNext" class="toolbarButton" title="Find the next occurrence of the phrase" tabindex="93"
data-l10n-id="pdfjs-find-next-button">
<span data-l10n-id="pdfjs-find-next-button-label">Next</span> <span data-l10n-id="pdfjs-find-next-button-label">Next</span>
</button> </button>
</div> </div>
@ -106,15 +120,19 @@ See https://github.com/adobe-type-tools/cmap-resources
<div id="findbarOptionsOneContainer"> <div id="findbarOptionsOneContainer">
<input type="checkbox" id="findHighlightAll" class="toolbarField" tabindex="94"> <input type="checkbox" id="findHighlightAll" class="toolbarField" tabindex="94">
<label for="findHighlightAll" class="toolbarLabel" data-l10n-id="pdfjs-find-highlight-checkbox">Highlight All</label> <label for="findHighlightAll" class="toolbarLabel" data-l10n-id="pdfjs-find-highlight-checkbox">Highlight
All</label>
<input type="checkbox" id="findMatchCase" class="toolbarField" tabindex="95"> <input type="checkbox" id="findMatchCase" class="toolbarField" tabindex="95">
<label for="findMatchCase" class="toolbarLabel" data-l10n-id="pdfjs-find-match-case-checkbox-label">Match Case</label> <label for="findMatchCase" class="toolbarLabel" data-l10n-id="pdfjs-find-match-case-checkbox-label">Match
Case</label>
</div> </div>
<div id="findbarOptionsTwoContainer"> <div id="findbarOptionsTwoContainer">
<input type="checkbox" id="findMatchDiacritics" class="toolbarField" tabindex="96"> <input type="checkbox" id="findMatchDiacritics" class="toolbarField" tabindex="96">
<label for="findMatchDiacritics" class="toolbarLabel" data-l10n-id="pdfjs-find-match-diacritics-checkbox-label">Match Diacritics</label> <label for="findMatchDiacritics" class="toolbarLabel"
data-l10n-id="pdfjs-find-match-diacritics-checkbox-label">Match Diacritics</label>
<input type="checkbox" id="findEntireWord" class="toolbarField" tabindex="97"> <input type="checkbox" id="findEntireWord" class="toolbarField" tabindex="97">
<label for="findEntireWord" class="toolbarLabel" data-l10n-id="pdfjs-find-entire-word-checkbox-label">Whole Words</label> <label for="findEntireWord" class="toolbarLabel" data-l10n-id="pdfjs-find-entire-word-checkbox-label">Whole
Words</label>
</div> </div>
<div id="findbarMessageContainer" aria-live="polite"> <div id="findbarMessageContainer" aria-live="polite">
@ -126,19 +144,25 @@ See https://github.com/adobe-type-tools/cmap-resources
<div class="editorParamsToolbar hidden doorHangerRight" id="editorHighlightParamsToolbar"> <div class="editorParamsToolbar hidden doorHangerRight" id="editorHighlightParamsToolbar">
<div id="highlightParamsToolbarContainer" class="editorParamsToolbarContainer"> <div id="highlightParamsToolbarContainer" class="editorParamsToolbarContainer">
<div id="editorHighlightColorPicker" class="colorPicker"> <div id="editorHighlightColorPicker" class="colorPicker">
<span id="highlightColorPickerLabel" class="editorParamsLabel" data-l10n-id="pdfjs-editor-highlight-colorpicker-label">Highlight color</span> <span id="highlightColorPickerLabel" class="editorParamsLabel"
data-l10n-id="pdfjs-editor-highlight-colorpicker-label">Highlight color</span>
</div> </div>
<div id="editorHighlightThickness"> <div id="editorHighlightThickness">
<label for="editorFreeHighlightThickness" class="editorParamsLabel" data-l10n-id="pdfjs-editor-free-highlight-thickness-input">Thickness</label> <label for="editorFreeHighlightThickness" class="editorParamsLabel"
data-l10n-id="pdfjs-editor-free-highlight-thickness-input">Thickness</label>
<div class="thicknessPicker"> <div class="thicknessPicker">
<input type="range" id="editorFreeHighlightThickness" class="editorParamsSlider" data-l10n-id="pdfjs-editor-free-highlight-thickness-title" value="12" min="8" max="24" step="1" tabindex="101"> <input type="range" id="editorFreeHighlightThickness" class="editorParamsSlider"
data-l10n-id="pdfjs-editor-free-highlight-thickness-title" value="12" min="8" max="24" step="1"
tabindex="101">
</div> </div>
</div> </div>
<div id="editorHighlightVisibility"> <div id="editorHighlightVisibility">
<div class="divider"></div> <div class="divider"></div>
<div class="toggler"> <div class="toggler">
<label for="editorHighlightShowAll" class="editorParamsLabel" data-l10n-id="pdfjs-editor-highlight-show-all-button-label">Show all</label> <label for="editorHighlightShowAll" class="editorParamsLabel"
<button id="editorHighlightShowAll" class="toggle-button" data-l10n-id="pdfjs-editor-highlight-show-all-button" aria-pressed="true" tabindex="102"></button> data-l10n-id="pdfjs-editor-highlight-show-all-button-label">Show all</label>
<button id="editorHighlightShowAll" class="toggle-button"
data-l10n-id="pdfjs-editor-highlight-show-all-button" aria-pressed="true" tabindex="102"></button>
</div> </div>
</div> </div>
</div> </div>
@ -147,12 +171,15 @@ See https://github.com/adobe-type-tools/cmap-resources
<div class="editorParamsToolbar hidden doorHangerRight" id="editorFreeTextParamsToolbar"> <div class="editorParamsToolbar hidden doorHangerRight" id="editorFreeTextParamsToolbar">
<div class="editorParamsToolbarContainer"> <div class="editorParamsToolbarContainer">
<div class="editorParamsSetter"> <div class="editorParamsSetter">
<label for="editorFreeTextColor" class="editorParamsLabel" data-l10n-id="pdfjs-editor-free-text-color-input">Color</label> <label for="editorFreeTextColor" class="editorParamsLabel"
data-l10n-id="pdfjs-editor-free-text-color-input">Color</label>
<input type="color" id="editorFreeTextColor" class="editorParamsColor" tabindex="103"> <input type="color" id="editorFreeTextColor" class="editorParamsColor" tabindex="103">
</div> </div>
<div class="editorParamsSetter"> <div class="editorParamsSetter">
<label for="editorFreeTextFontSize" class="editorParamsLabel" data-l10n-id="pdfjs-editor-free-text-size-input">Size</label> <label for="editorFreeTextFontSize" class="editorParamsLabel"
<input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" value="10" min="5" max="100" step="1" tabindex="104"> data-l10n-id="pdfjs-editor-free-text-size-input">Size</label>
<input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" value="10" min="5" max="100"
step="1" tabindex="104">
</div> </div>
</div> </div>
</div> </div>
@ -160,23 +187,29 @@ See https://github.com/adobe-type-tools/cmap-resources
<div class="editorParamsToolbar hidden doorHangerRight" id="editorInkParamsToolbar"> <div class="editorParamsToolbar hidden doorHangerRight" id="editorInkParamsToolbar">
<div class="editorParamsToolbarContainer"> <div class="editorParamsToolbarContainer">
<div class="editorParamsSetter"> <div class="editorParamsSetter">
<label for="editorInkColor" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-color-input">Color</label> <label for="editorInkColor" class="editorParamsLabel"
data-l10n-id="pdfjs-editor-ink-color-input">Color</label>
<input type="color" id="editorInkColor" class="editorParamsColor" tabindex="105"> <input type="color" id="editorInkColor" class="editorParamsColor" tabindex="105">
</div> </div>
<div class="editorParamsSetter"> <div class="editorParamsSetter">
<label for="editorInkThickness" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-thickness-input">Thickness</label> <label for="editorInkThickness" class="editorParamsLabel"
<input type="range" id="editorInkThickness" class="editorParamsSlider" value="1" min="1" max="20" step="1" tabindex="106"> data-l10n-id="pdfjs-editor-ink-thickness-input">Thickness</label>
<input type="range" id="editorInkThickness" class="editorParamsSlider" value="1" min="1" max="20" step="1"
tabindex="106">
</div> </div>
<div class="editorParamsSetter"> <div class="editorParamsSetter">
<label for="editorInkOpacity" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-opacity-input">Opacity</label> <label for="editorInkOpacity" class="editorParamsLabel"
<input type="range" id="editorInkOpacity" class="editorParamsSlider" value="100" min="1" max="100" step="1" tabindex="107"> data-l10n-id="pdfjs-editor-ink-opacity-input">Opacity</label>
<input type="range" id="editorInkOpacity" class="editorParamsSlider" value="100" min="1" max="100" step="1"
tabindex="107">
</div> </div>
</div> </div>
</div> </div>
<div class="editorParamsToolbar hidden doorHangerRight" id="editorStampParamsToolbar"> <div class="editorParamsToolbar hidden doorHangerRight" id="editorStampParamsToolbar">
<div class="editorParamsToolbarContainer"> <div class="editorParamsToolbarContainer">
<button id="editorStampAddImage" class="secondaryToolbarButton" title="Add image" tabindex="108" data-l10n-id="pdfjs-editor-stamp-add-image-button"> <button id="editorStampAddImage" class="secondaryToolbarButton" title="Add image" tabindex="108"
data-l10n-id="pdfjs-editor-stamp-add-image-button">
<span class="editorParamsLabel" data-l10n-id="pdfjs-editor-stamp-add-image-button-label">Add image</span> <span class="editorParamsLabel" data-l10n-id="pdfjs-editor-stamp-add-image-button-label">Add image</span>
</button> </button>
</div> </div>
@ -184,53 +217,64 @@ See https://github.com/adobe-type-tools/cmap-resources
<div id="secondaryToolbar" class="secondaryToolbar hidden doorHangerRight"> <div id="secondaryToolbar" class="secondaryToolbar hidden doorHangerRight">
<div id="secondaryToolbarButtonContainer"> <div id="secondaryToolbarButtonContainer">
<button id="secondaryOpenFile" class="secondaryToolbarButton visibleMediumView" title="Open File" tabindex="51" data-l10n-id="pdfjs-open-file-button"> <button id="secondaryOpenFile" class="secondaryToolbarButton visibleMediumView" title="Open File"
tabindex="51" data-l10n-id="pdfjs-open-file-button">
<span data-l10n-id="pdfjs-open-file-button-label">Open</span> <span data-l10n-id="pdfjs-open-file-button-label">Open</span>
</button> </button>
<button id="secondaryPrint" class="secondaryToolbarButton visibleMediumView" title="Print" tabindex="52" data-l10n-id="pdfjs-print-button"> <button id="secondaryPrint" class="secondaryToolbarButton visibleMediumView" title="Print" tabindex="52"
data-l10n-id="pdfjs-print-button">
<span data-l10n-id="pdfjs-print-button-label">Print</span> <span data-l10n-id="pdfjs-print-button-label">Print</span>
</button> </button>
<button id="secondaryDownload" class="secondaryToolbarButton visibleMediumView" title="Save" tabindex="53" data-l10n-id="pdfjs-save-button"> <button id="secondaryDownload" class="secondaryToolbarButton visibleMediumView" title="Save" tabindex="53"
data-l10n-id="pdfjs-save-button">
<span data-l10n-id="pdfjs-save-button-label">Save</span> <span data-l10n-id="pdfjs-save-button-label">Save</span>
</button> </button>
<div class="horizontalToolbarSeparator"></div> <div class="horizontalToolbarSeparator"></div>
<button id="presentationMode" class="secondaryToolbarButton" title="Switch to Presentation Mode" tabindex="54" data-l10n-id="pdfjs-presentation-mode-button"> <button id="presentationMode" class="secondaryToolbarButton" title="Switch to Presentation Mode" tabindex="54"
data-l10n-id="pdfjs-presentation-mode-button">
<span data-l10n-id="pdfjs-presentation-mode-button-label">Presentation Mode</span> <span data-l10n-id="pdfjs-presentation-mode-button-label">Presentation Mode</span>
</button> </button>
<a href="#" id="viewBookmark" class="secondaryToolbarButton" title="Current Page (View URL from Current Page)" tabindex="55" data-l10n-id="pdfjs-bookmark-button"> <a href="#" id="viewBookmark" class="secondaryToolbarButton" title="Current Page (View URL from Current Page)"
tabindex="55" data-l10n-id="pdfjs-bookmark-button">
<span data-l10n-id="pdfjs-bookmark-button-label">Current Page</span> <span data-l10n-id="pdfjs-bookmark-button-label">Current Page</span>
</a> </a>
<div id="viewBookmarkSeparator" class="horizontalToolbarSeparator"></div> <div id="viewBookmarkSeparator" class="horizontalToolbarSeparator"></div>
<button id="firstPage" class="secondaryToolbarButton" title="Go to First Page" tabindex="56" data-l10n-id="pdfjs-first-page-button"> <button id="firstPage" class="secondaryToolbarButton" title="Go to First Page" tabindex="56"
data-l10n-id="pdfjs-first-page-button">
<span data-l10n-id="pdfjs-first-page-button-label">Go to First Page</span> <span data-l10n-id="pdfjs-first-page-button-label">Go to First Page</span>
</button> </button>
<button id="lastPage" class="secondaryToolbarButton" title="Go to Last Page" tabindex="57" data-l10n-id="pdfjs-last-page-button"> <button id="lastPage" class="secondaryToolbarButton" title="Go to Last Page" tabindex="57"
data-l10n-id="pdfjs-last-page-button">
<span data-l10n-id="pdfjs-last-page-button-label">Go to Last Page</span> <span data-l10n-id="pdfjs-last-page-button-label">Go to Last Page</span>
</button> </button>
<div class="horizontalToolbarSeparator"></div> <div class="horizontalToolbarSeparator"></div>
<button id="pageRotateCw" class="secondaryToolbarButton" title="Rotate Clockwise" tabindex="58" data-l10n-id="pdfjs-page-rotate-cw-button"> <button id="pageRotateCw" class="secondaryToolbarButton" title="Rotate Clockwise" tabindex="58"
data-l10n-id="pdfjs-page-rotate-cw-button">
<span data-l10n-id="pdfjs-page-rotate-cw-button-label">Rotate Clockwise</span> <span data-l10n-id="pdfjs-page-rotate-cw-button-label">Rotate Clockwise</span>
</button> </button>
<button id="pageRotateCcw" class="secondaryToolbarButton" title="Rotate Counterclockwise" tabindex="59" data-l10n-id="pdfjs-page-rotate-ccw-button"> <button id="pageRotateCcw" class="secondaryToolbarButton" title="Rotate Counterclockwise" tabindex="59"
data-l10n-id="pdfjs-page-rotate-ccw-button">
<span data-l10n-id="pdfjs-page-rotate-ccw-button-label">Rotate Counterclockwise</span> <span data-l10n-id="pdfjs-page-rotate-ccw-button-label">Rotate Counterclockwise</span>
</button> </button>
<div class="horizontalToolbarSeparator"></div> <div class="horizontalToolbarSeparator"></div>
<div id="cursorToolButtons" role="radiogroup"> <div id="cursorToolButtons" role="radiogroup">
<button id="cursorSelectTool" class="secondaryToolbarButton toggled" title="Enable Text Selection Tool" tabindex="60" data-l10n-id="pdfjs-cursor-text-select-tool-button" role="radio" aria-checked="true"> <button id="cursorSelectTool" class="secondaryToolbarButton toggled" title="Enable Text Selection Tool"
tabindex="60" data-l10n-id="pdfjs-cursor-text-select-tool-button" role="radio" aria-checked="true">
<span data-l10n-id="pdfjs-cursor-text-select-tool-button-label">Text Selection Tool</span> <span data-l10n-id="pdfjs-cursor-text-select-tool-button-label">Text Selection Tool</span>
</button> </button>
<button id="cursorHandTool" class="secondaryToolbarButton" title="Enable Hand Tool" tabindex="61" data-l10n-id="pdfjs-cursor-hand-tool-button" role="radio" aria-checked="false"> <button id="cursorHandTool" class="secondaryToolbarButton" title="Enable Hand Tool" tabindex="61"
data-l10n-id="pdfjs-cursor-hand-tool-button" role="radio" aria-checked="false">
<span data-l10n-id="pdfjs-cursor-hand-tool-button-label">Hand Tool</span> <span data-l10n-id="pdfjs-cursor-hand-tool-button-label">Hand Tool</span>
</button> </button>
</div> </div>
@ -238,16 +282,20 @@ See https://github.com/adobe-type-tools/cmap-resources
<div class="horizontalToolbarSeparator"></div> <div class="horizontalToolbarSeparator"></div>
<div id="scrollModeButtons" role="radiogroup"> <div id="scrollModeButtons" role="radiogroup">
<button id="scrollPage" class="secondaryToolbarButton" title="Use Page Scrolling" tabindex="62" data-l10n-id="pdfjs-scroll-page-button" role="radio" aria-checked="false"> <button id="scrollPage" class="secondaryToolbarButton" title="Use Page Scrolling" tabindex="62"
data-l10n-id="pdfjs-scroll-page-button" role="radio" aria-checked="false">
<span data-l10n-id="pdfjs-scroll-page-button-label">Page Scrolling</span> <span data-l10n-id="pdfjs-scroll-page-button-label">Page Scrolling</span>
</button> </button>
<button id="scrollVertical" class="secondaryToolbarButton toggled" title="Use Vertical Scrolling" tabindex="63" data-l10n-id="pdfjs-scroll-vertical-button" role="radio" aria-checked="true"> <button id="scrollVertical" class="secondaryToolbarButton toggled" title="Use Vertical Scrolling"
<span data-l10n-id="pdfjs-scroll-vertical-button-label" >Vertical Scrolling</span> tabindex="63" data-l10n-id="pdfjs-scroll-vertical-button" role="radio" aria-checked="true">
<span data-l10n-id="pdfjs-scroll-vertical-button-label">Vertical Scrolling</span>
</button> </button>
<button id="scrollHorizontal" class="secondaryToolbarButton" title="Use Horizontal Scrolling" tabindex="64" data-l10n-id="pdfjs-scroll-horizontal-button" role="radio" aria-checked="false"> <button id="scrollHorizontal" class="secondaryToolbarButton" title="Use Horizontal Scrolling" tabindex="64"
data-l10n-id="pdfjs-scroll-horizontal-button" role="radio" aria-checked="false">
<span data-l10n-id="pdfjs-scroll-horizontal-button-label">Horizontal Scrolling</span> <span data-l10n-id="pdfjs-scroll-horizontal-button-label">Horizontal Scrolling</span>
</button> </button>
<button id="scrollWrapped" class="secondaryToolbarButton" title="Use Wrapped Scrolling" tabindex="65" data-l10n-id="pdfjs-scroll-wrapped-button" role="radio" aria-checked="false"> <button id="scrollWrapped" class="secondaryToolbarButton" title="Use Wrapped Scrolling" tabindex="65"
data-l10n-id="pdfjs-scroll-wrapped-button" role="radio" aria-checked="false">
<span data-l10n-id="pdfjs-scroll-wrapped-button-label">Wrapped Scrolling</span> <span data-l10n-id="pdfjs-scroll-wrapped-button-label">Wrapped Scrolling</span>
</button> </button>
</div> </div>
@ -255,20 +303,26 @@ See https://github.com/adobe-type-tools/cmap-resources
<div class="horizontalToolbarSeparator"></div> <div class="horizontalToolbarSeparator"></div>
<div id="spreadModeButtons" role="radiogroup"> <div id="spreadModeButtons" role="radiogroup">
<button id="spreadNone" class="secondaryToolbarButton toggled" title="Do not join page spreads" tabindex="66" data-l10n-id="pdfjs-spread-none-button" role="radio" aria-checked="true"> <button id="spreadNone" class="secondaryToolbarButton toggled" title="Do not join page spreads"
tabindex="66" data-l10n-id="pdfjs-spread-none-button" role="radio" aria-checked="true">
<span data-l10n-id="pdfjs-spread-none-button-label">No Spreads</span> <span data-l10n-id="pdfjs-spread-none-button-label">No Spreads</span>
</button> </button>
<button id="spreadOdd" class="secondaryToolbarButton" title="Join page spreads starting with odd-numbered pages" tabindex="67" data-l10n-id="pdfjs-spread-odd-button" role="radio" aria-checked="false"> <button id="spreadOdd" class="secondaryToolbarButton"
title="Join page spreads starting with odd-numbered pages" tabindex="67"
data-l10n-id="pdfjs-spread-odd-button" role="radio" aria-checked="false">
<span data-l10n-id="pdfjs-spread-odd-button-label">Odd Spreads</span> <span data-l10n-id="pdfjs-spread-odd-button-label">Odd Spreads</span>
</button> </button>
<button id="spreadEven" class="secondaryToolbarButton" title="Join page spreads starting with even-numbered pages" tabindex="68" data-l10n-id="pdfjs-spread-even-button" role="radio" aria-checked="false"> <button id="spreadEven" class="secondaryToolbarButton"
title="Join page spreads starting with even-numbered pages" tabindex="68"
data-l10n-id="pdfjs-spread-even-button" role="radio" aria-checked="false">
<span data-l10n-id="pdfjs-spread-even-button-label">Even Spreads</span> <span data-l10n-id="pdfjs-spread-even-button-label">Even Spreads</span>
</button> </button>
</div> </div>
<div class="horizontalToolbarSeparator"></div> <div class="horizontalToolbarSeparator"></div>
<button id="documentProperties" class="secondaryToolbarButton" title="Document Properties…" tabindex="69" data-l10n-id="pdfjs-document-properties-button" aria-controls="documentPropertiesDialog"> <button id="documentProperties" class="secondaryToolbarButton" title="Document Properties…" tabindex="69"
data-l10n-id="pdfjs-document-properties-button" aria-controls="documentPropertiesDialog">
<span data-l10n-id="pdfjs-document-properties-button-label">Document Properties…</span> <span data-l10n-id="pdfjs-document-properties-button-label">Document Properties…</span>
</button> </button>
</div> </div>
@ -278,94 +332,125 @@ See https://github.com/adobe-type-tools/cmap-resources
<div id="toolbarContainer"> <div id="toolbarContainer">
<div id="toolbarViewer"> <div id="toolbarViewer">
<div id="toolbarViewerLeft"> <div id="toolbarViewerLeft">
<button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="11" data-l10n-id="pdfjs-toggle-sidebar-button" aria-expanded="false" aria-controls="sidebarContainer"> <button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="11"
data-l10n-id="pdfjs-toggle-sidebar-button" aria-expanded="false" aria-controls="sidebarContainer">
<span data-l10n-id="pdfjs-toggle-sidebar-button-label">Toggle Sidebar</span> <span data-l10n-id="pdfjs-toggle-sidebar-button-label">Toggle Sidebar</span>
</button> </button>
<div class="toolbarButtonSpacer"></div> <div class="toolbarButtonSpacer"></div>
<button id="viewFind" class="toolbarButton" title="Find in Document" tabindex="12" data-l10n-id="pdfjs-findbar-button" aria-expanded="false" aria-controls="findbar"> <button id="viewFind" class="toolbarButton" title="Find in Document" tabindex="12"
data-l10n-id="pdfjs-findbar-button" aria-expanded="false" aria-controls="findbar">
<span data-l10n-id="pdfjs-findbar-button-label">Find</span> <span data-l10n-id="pdfjs-findbar-button-label">Find</span>
</button> </button>
<div class="splitToolbarButton hiddenSmallView"> <div class="splitToolbarButton hiddenSmallView">
<button class="toolbarButton" title="Previous Page" id="previous" tabindex="13" data-l10n-id="pdfjs-previous-button"> <button class="toolbarButton" title="Previous Page" id="previous" tabindex="13"
data-l10n-id="pdfjs-previous-button">
<span data-l10n-id="pdfjs-previous-button-label">Previous</span> <span data-l10n-id="pdfjs-previous-button-label">Previous</span>
</button> </button>
<div class="splitToolbarButtonSeparator"></div> <div class="splitToolbarButtonSeparator"></div>
<button class="toolbarButton" title="Next Page" id="next" tabindex="14" data-l10n-id="pdfjs-next-button"> <button class="toolbarButton" title="Next Page" id="next" tabindex="14"
data-l10n-id="pdfjs-next-button">
<span data-l10n-id="pdfjs-next-button-label">Next</span> <span data-l10n-id="pdfjs-next-button-label">Next</span>
</button> </button>
</div> </div>
<span class="loadingInput start"> <span class="loadingInput start">
<input type="number" id="pageNumber" class="toolbarField" title="Page" value="1" min="1" tabindex="15" data-l10n-id="pdfjs-page-input" autocomplete="off"> <input type="number" id="pageNumber" class="toolbarField" title="Page" value="1" min="1" tabindex="15"
data-l10n-id="pdfjs-page-input" autocomplete="off">
</span> </span>
<span id="numPages" class="toolbarLabel"></span> <span id="numPages" class="toolbarLabel"></span>
<a class="navbar-brand hiddenMediumView" th:href="@{'/'}" tabindex="16" > <a class="navbar-brand hiddenMediumView" th:href="@{'/'}" tabindex="16">
<img class="main-icon" th:src="@{'/favicon.svg'}" alt="icon" style="max-height: 1.6rem; width: auto;"> <img class="main-icon" th:src="@{'/favicon.svg'}" alt="icon" style="max-height: 1.6rem; width: auto;">
<span class="icon-text" th:text="${@appName}">Stirling PDF</span> <span class="icon-text" th:text="${@appName}">Stirling PDF</span>
</a> </a>
</div> </div>
<div id="toolbarViewerRight"> <div id="toolbarViewerRight">
<div id="editorModeButtons" class="splitToolbarButton toggled" role="radiogroup"> <div id="editorModeButtons" class="splitToolbarButton toggled" role="radiogroup">
<button id="editorHighlight" class="toolbarButton" hidden="true" disabled="disabled" title="Highlight" role="radio" aria-checked="false" aria-controls="editorHighlightParamsToolbar" tabindex="31" data-l10n-id="pdfjs-editor-highlight-button"> <button id="editorHighlight" class="toolbarButton" hidden="true" disabled="disabled" title="Highlight"
role="radio" aria-checked="false" aria-controls="editorHighlightParamsToolbar" tabindex="31"
data-l10n-id="pdfjs-editor-highlight-button">
<span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span> <span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span>
</button> </button>
<a id="backToHome" class="toolbarButton hiddenMediumView" title="Back to Main Page" role="radio" aria-checked="false" tabindex="32" th:href="@{'/'}"> <a id="backToHome" class="toolbarButton hiddenMediumView" title="Back to Main Page" role="radio"
aria-checked="false" tabindex="32" th:href="@{'/'}">
<span data-l10n-id="pdfjs-open-file-button-label">Back to Main Page</span> <span data-l10n-id="pdfjs-open-file-button-label">Back to Main Page</span>
</a> </a>
<button id="openFile" class="toolbarButton hiddenMediumView" title="Open File" role="radio" aria-checked="false" tabindex="33" data-l10n-id="pdfjs-open-file-button"> <button id="openFile" class="toolbarButton hiddenMediumView" title="Open File" role="radio"
aria-checked="false" tabindex="33" data-l10n-id="pdfjs-open-file-button">
<span data-l10n-id="pdfjs-open-file-button-label">Open</span> <span data-l10n-id="pdfjs-open-file-button-label">Open</span>
</button> </button>
<button id="print" class="toolbarButton hiddenMediumView" title="Print" role="radio" aria-checked="false" tabindex="34" data-l10n-id="pdfjs-print-button"> <button id="print" class="toolbarButton hiddenMediumView" title="Print" role="radio"
aria-checked="false" tabindex="34" data-l10n-id="pdfjs-print-button">
<span data-l10n-id="pdfjs-print-button-label">Print</span> <span data-l10n-id="pdfjs-print-button-label">Print</span>
</button> </button>
<button id="download" class="toolbarButton hiddenMediumView" title="Save" role="radio" aria-checked="false" tabindex="35" data-l10n-id="pdfjs-save-button"> <button id="download" class="toolbarButton hiddenMediumView" title="Save" role="radio"
aria-checked="false" tabindex="35" data-l10n-id="pdfjs-save-button">
<span data-l10n-id="pdfjs-save-button-label">Save</span> <span data-l10n-id="pdfjs-save-button-label">Save</span>
</button> </button>
<button id="editorStamp" class="toolbarButton hiddenMediumView" title="Add or edit images" role="radio" aria-checked="false" aria-controls="editorStampParamsToolbar" tabindex="36" data-l10n-id="pdfjs-editor-stamp-button"> <button id="editorStamp" class="toolbarButton hiddenMediumView" title="Add or edit images" role="radio"
aria-checked="false" aria-controls="editorStampParamsToolbar" tabindex="36"
data-l10n-id="pdfjs-editor-stamp-button">
<span data-l10n-id="pdfjs-editor-stamp-button-label">Add or edit images</span> <span data-l10n-id="pdfjs-editor-stamp-button-label">Add or edit images</span>
</button> </button>
</div> </div>
<div id="editorModeSeparator" class="verticalToolbarSeparator"></div> <div id="editorModeSeparator" class="verticalToolbarSeparator"></div>
<button id="editorFreeText" class="toolbarButton hiddenMediumView" title="Text" tabindex="41" data-l10n-id="pdfjs-editor-free-text-button"> <button id="editorFreeText" class="toolbarButton hiddenMediumView" title="Text" tabindex="41"
data-l10n-id="pdfjs-editor-free-text-button">
<span data-l10n-id="pdfjs-editor-free-text-button-label">Text</span> <span data-l10n-id="pdfjs-editor-free-text-button-label">Text</span>
</button> </button>
<button id="editorInk" class="toolbarButton hiddenMediumView" title="Draw" tabindex="42" data-l10n-id="pdfjs-editor-ink-button"> <button id="editorInk" class="toolbarButton hiddenMediumView" title="Draw" tabindex="42"
data-l10n-id="pdfjs-editor-ink-button">
<span data-l10n-id="pdfjs-editor-ink-button-label">Draw</span> <span data-l10n-id="pdfjs-editor-ink-button-label">Draw</span>
</button> </button>
<div class="verticalToolbarSeparator hiddenMediumView"></div> <div class="verticalToolbarSeparator hiddenMediumView"></div>
<button id="secondaryToolbarToggle" class="toolbarButton" title="Tools" tabindex="43" data-l10n-id="pdfjs-tools-button"> <button id="secondaryToolbarToggle" class="toolbarButton" title="Tools" tabindex="43"
data-l10n-id="pdfjs-tools-button">
<span data-l10n-id="pdfjs-tools-button-label">Tools</span> <span data-l10n-id="pdfjs-tools-button-label">Tools</span>
</button> </button>
</div> </div>
<div id="toolbarViewerMiddle"> <div id="toolbarViewerMiddle">
<div class="splitToolbarButton"> <div class="splitToolbarButton">
<button id="zoomOut" class="toolbarButton" title="Zoom Out" tabindex="21" data-l10n-id="pdfjs-zoom-out-button"> <button id="zoomOut" class="toolbarButton" title="Zoom Out" tabindex="21"
data-l10n-id="pdfjs-zoom-out-button">
<span data-l10n-id="pdfjs-zoom-out-button-label">Zoom Out</span> <span data-l10n-id="pdfjs-zoom-out-button-label">Zoom Out</span>
</button> </button>
<div class="splitToolbarButtonSeparator"></div> <div class="splitToolbarButtonSeparator"></div>
<button id="zoomIn" class="toolbarButton" title="Zoom In" tabindex="22" data-l10n-id="pdfjs-zoom-in-button"> <button id="zoomIn" class="toolbarButton" title="Zoom In" tabindex="22"
data-l10n-id="pdfjs-zoom-in-button">
<span data-l10n-id="pdfjs-zoom-in-button-label">Zoom In</span> <span data-l10n-id="pdfjs-zoom-in-button-label">Zoom In</span>
</button> </button>
</div> </div>
<span id="scaleSelectContainer" class="dropdownToolbarButton"> <span id="scaleSelectContainer" class="dropdownToolbarButton">
<select id="scaleSelect" title="Zoom" tabindex="23" data-l10n-id="pdfjs-zoom-select"> <select id="scaleSelect" title="Zoom" tabindex="23" data-l10n-id="pdfjs-zoom-select">
<option id="pageAutoOption" title="" value="auto" selected="selected" data-l10n-id="pdfjs-page-scale-auto">Automatic Zoom</option> <option id="pageAutoOption" title="" value="auto" selected="selected"
<option id="pageActualOption" title="" value="page-actual" data-l10n-id="pdfjs-page-scale-actual">Actual Size</option> data-l10n-id="pdfjs-page-scale-auto">Automatic Zoom</option>
<option id="pageFitOption" title="" value="page-fit" data-l10n-id="pdfjs-page-scale-fit">Page Fit</option> <option id="pageActualOption" title="" value="page-actual" data-l10n-id="pdfjs-page-scale-actual">
<option id="pageWidthOption" title="" value="page-width" data-l10n-id="pdfjs-page-scale-width">Page Width</option> Actual Size</option>
<option id="customScaleOption" title="" value="custom" disabled="disabled" hidden="true" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 0 }'>0%</option> <option id="pageFitOption" title="" value="page-fit" data-l10n-id="pdfjs-page-scale-fit">Page Fit
<option title="" value="0.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 50 }'>50%</option> </option>
<option title="" value="0.75" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 75 }'>75%</option> <option id="pageWidthOption" title="" value="page-width" data-l10n-id="pdfjs-page-scale-width">Page
<option title="" value="1" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 100 }'>100%</option> Width</option>
<option title="" value="1.25" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 125 }'>125%</option> <option id="customScaleOption" title="" value="custom" disabled="disabled" hidden="true"
<option title="" value="1.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 150 }'>150%</option> data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 0 }'>0%</option>
<option title="" value="2" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 200 }'>200%</option> <option title="" value="0.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 50 }'>
<option title="" value="3" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 300 }'>300%</option> 50%</option>
<option title="" value="4" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 400 }'>400%</option> <option title="" value="0.75" data-l10n-id="pdfjs-page-scale-percent"
data-l10n-args='{ "scale": 75 }'>75%</option>
<option title="" value="1" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 100 }'>
100%</option>
<option title="" value="1.25" data-l10n-id="pdfjs-page-scale-percent"
data-l10n-args='{ "scale": 125 }'>125%</option>
<option title="" value="1.5" data-l10n-id="pdfjs-page-scale-percent"
data-l10n-args='{ "scale": 150 }'>150%</option>
<option title="" value="2" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 200 }'>
200%</option>
<option title="" value="3" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 300 }'>
300%</option>
<option title="" value="4" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 400 }'>
400%</option>
</select> </select>
</span> </span>
</div> </div>
@ -380,21 +465,27 @@ See https://github.com/adobe-type-tools/cmap-resources
</div> </div>
<div id="viewerContainer" tabindex="0"> <div id="viewerContainer" tabindex="0">
<div id="viewer" class="pdfViewer"></div> <div id="viewer" class="pdfViewer">
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</div> <!-- mainContainer --> </div> <!-- mainContainer -->
<div id="dialogContainer"> <div id="dialogContainer">
<dialog id="passwordDialog"> <dialog id="passwordDialog">
<div class="row"> <div class="row">
<label for="password" id="passwordText" data-l10n-id="pdfjs-password-label">Enter the password to open this PDF file:</label> <label for="password" id="passwordText" data-l10n-id="pdfjs-password-label">Enter the password to open this
PDF file:</label>
</div> </div>
<div class="row"> <div class="row">
<input type="password" id="password" class="toolbarField"> <input type="password" id="password" class="toolbarField">
</div> </div>
<div class="buttonRow"> <div class="buttonRow">
<button id="passwordCancel" class="dialogButton"><span data-l10n-id="pdfjs-password-cancel-button">Cancel</span></button> <button id="passwordCancel" class="dialogButton"><span
<button id="passwordSubmit" class="dialogButton"><span data-l10n-id="pdfjs-password-ok-button">OK</span></button> data-l10n-id="pdfjs-password-cancel-button">Cancel</span></button>
<button id="passwordSubmit" class="dialogButton"><span
data-l10n-id="pdfjs-password-ok-button">OK</span></button>
</div> </div>
</dialog> </dialog>
<dialog id="documentPropertiesDialog"> <dialog id="documentPropertiesDialog">
@ -428,7 +519,8 @@ See https://github.com/adobe-type-tools/cmap-resources
<p id="creationDateField" aria-labelledby="creationDateLabel">-</p> <p id="creationDateField" aria-labelledby="creationDateLabel">-</p>
</div> </div>
<div class="row"> <div class="row">
<span id="modificationDateLabel" data-l10n-id="pdfjs-document-properties-modification-date">Modification Date:</span> <span id="modificationDateLabel" data-l10n-id="pdfjs-document-properties-modification-date">Modification
Date:</span>
<p id="modificationDateField" aria-labelledby="modificationDateLabel">-</p> <p id="modificationDateField" aria-labelledby="modificationDateLabel">-</p>
</div> </div>
<div class="row"> <div class="row">
@ -458,13 +550,16 @@ See https://github.com/adobe-type-tools/cmap-resources
<p id="linearizedField" aria-labelledby="linearizedLabel">-</p> <p id="linearizedField" aria-labelledby="linearizedLabel">-</p>
</div> </div>
<div class="buttonRow"> <div class="buttonRow">
<button id="documentPropertiesClose" class="dialogButton"><span data-l10n-id="pdfjs-document-properties-close-button">Close</span></button> <button id="documentPropertiesClose" class="dialogButton"><span
data-l10n-id="pdfjs-document-properties-close-button">Close</span></button>
</div> </div>
</dialog> </dialog>
<dialog class="dialog altText" id="altTextDialog" aria-labelledby="dialogLabel" aria-describedby="dialogDescription"> <dialog class="dialog altText" id="altTextDialog" aria-labelledby="dialogLabel"
aria-describedby="dialogDescription">
<div id="altTextContainer" class="mainContainer"> <div id="altTextContainer" class="mainContainer">
<div id="overallDescription"> <div id="overallDescription">
<span id="dialogLabel" data-l10n-id="pdfjs-editor-alt-text-dialog-label" class="title">Choose an option</span> <span id="dialogLabel" data-l10n-id="pdfjs-editor-alt-text-dialog-label" class="title">Choose an
option</span>
<span id="dialogDescription" data-l10n-id="pdfjs-editor-alt-text-dialog-description"> <span id="dialogDescription" data-l10n-id="pdfjs-editor-alt-text-dialog-description">
Alt text (alternative text) helps when people cant see the image or when it doesnt load. Alt text (alternative text) helps when people cant see the image or when it doesnt load.
</span> </span>
@ -472,8 +567,10 @@ See https://github.com/adobe-type-tools/cmap-resources
<div id="addDescription"> <div id="addDescription">
<div class="radio"> <div class="radio">
<div class="radioButton"> <div class="radioButton">
<input type="radio" id="descriptionButton" name="altTextOption" tabindex="0" aria-describedby="descriptionAreaLabel" checked> <input type="radio" id="descriptionButton" name="altTextOption" tabindex="0"
<label for="descriptionButton" data-l10n-id="pdfjs-editor-alt-text-add-description-label">Add a description</label> aria-describedby="descriptionAreaLabel" checked>
<label for="descriptionButton" data-l10n-id="pdfjs-editor-alt-text-add-description-label">Add a
description</label>
</div> </div>
<div class="radioLabel"> <div class="radioLabel">
<span id="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-add-description-description"> <span id="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-add-description-description">
@ -482,14 +579,18 @@ See https://github.com/adobe-type-tools/cmap-resources
</div> </div>
</div> </div>
<div class="descriptionArea"> <div class="descriptionArea">
<textarea id="descriptionTextarea" placeholder="For example, “A young man sits down at a table to eat a meal”" aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-textarea" tabindex="0"></textarea> <textarea id="descriptionTextarea"
placeholder="For example, “A young man sits down at a table to eat a meal”"
aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-textarea"
tabindex="0"></textarea>
</div> </div>
</div> </div>
<div id="markAsDecorative"> <div id="markAsDecorative">
<div class="radio"> <div class="radio">
<div class="radioButton"> <div class="radioButton">
<input type="radio" id="decorativeButton" name="altTextOption" aria-describedby="decorativeLabel"> <input type="radio" id="decorativeButton" name="altTextOption" aria-describedby="decorativeLabel">
<label for="decorativeButton" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-label">Mark as decorative</label> <label for="decorativeButton" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-label">Mark as
decorative</label>
</div> </div>
<div class="radioLabel"> <div class="radioLabel">
<span id="decorativeLabel" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-description"> <span id="decorativeLabel" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-description">
@ -499,8 +600,10 @@ See https://github.com/adobe-type-tools/cmap-resources
</div> </div>
</div> </div>
<div id="buttons"> <div id="buttons">
<button id="altTextCancel" class="secondaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button> <button id="altTextCancel" class="secondaryButton" tabindex="0"><span
<button id="altTextSave" class="primaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button> data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
<button id="altTextSave" class="primaryButton" tabindex="0"><span
data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
</div> </div>
</div> </div>
</dialog> </dialog>
@ -510,15 +613,18 @@ See https://github.com/adobe-type-tools/cmap-resources
</div> </div>
<div class="row"> <div class="row">
<progress value="0" max="100"></progress> <progress value="0" max="100"></progress>
<span data-l10n-id="pdfjs-print-progress-percent" data-l10n-args='{ "progress": 0 }' class="relative-progress">0%</span> <span data-l10n-id="pdfjs-print-progress-percent" data-l10n-args='{ "progress": 0 }'
class="relative-progress">0%</span>
</div> </div>
<div class="buttonRow"> <div class="buttonRow">
<button id="printCancel" class="dialogButton"><span data-l10n-id="pdfjs-print-progress-close-button">Cancel</span></button> <button id="printCancel" class="dialogButton"><span
data-l10n-id="pdfjs-print-progress-close-button">Cancel</span></button>
</div> </div>
</dialog> </dialog>
</div> <!-- dialogContainer --> </div> <!-- dialogContainer -->
</div> <!-- outerContainer --> </div> <!-- outerContainer -->
<div id="printContainer"></div> <div id="printContainer"></div>
</body> </body>
</html> </html>