diff --git a/.gitignore b/.gitignore index bbc43feb1..105ddec3c 100644 --- a/.gitignore +++ b/.gitignore @@ -128,6 +128,9 @@ SwaggerDoc.json /app/core/build /app/common/build /app/proprietary/build +common/build +proprietary/build +stirling-pdf/build # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/README.md b/README.md index 02712a1ad..8e592bd7a 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ Stirling-PDF currently supports 40 languages! | Portuguese (Português) (pt_PT) | ![70%](https://geps.dev/progress/70) | | Portuguese Brazilian (Português) (pt_BR) | ![77%](https://geps.dev/progress/77) | | Romanian (Română) (ro_RO) | ![59%](https://geps.dev/progress/59) | -| Russian (Русский) (ru_RU) | ![70%](https://geps.dev/progress/70) | +| Russian (Русский) (ru_RU) | ![81%](https://geps.dev/progress/81) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![97%](https://geps.dev/progress/97) | | Simplified Chinese (简体中文) (zh_CN) | ![95%](https://geps.dev/progress/95) | | Slovakian (Slovensky) (sk_SK) | ![53%](https://geps.dev/progress/53) | diff --git a/app/common/src/main/java/stirling/software/common/aop/AutoJobAspect.java b/app/common/src/main/java/stirling/software/common/aop/AutoJobAspect.java index 9f01c4558..2ee10ebcd 100644 --- a/app/common/src/main/java/stirling/software/common/aop/AutoJobAspect.java +++ b/app/common/src/main/java/stirling/software/common/aop/AutoJobAspect.java @@ -43,7 +43,11 @@ public class AutoJobAspect { // This aspect will run before any audit aspects due to @Order(0) // Extract parameters from the request and annotation boolean async = Boolean.parseBoolean(request.getParameter("async")); - log.debug("AutoJobAspect: Processing {} {} with async={}", request.getMethod(), request.getRequestURI(), async); + log.debug( + "AutoJobAspect: Processing {} {} with async={}", + request.getMethod(), + request.getRequestURI(), + async); long timeout = autoJobPostMapping.timeout(); int retryCount = autoJobPostMapping.retryCount(); boolean trackProgress = autoJobPostMapping.trackProgress(); @@ -219,10 +223,9 @@ public class AutoJobAspect { resourceWeight); } - /** - * Processes arguments in-place to handle file resolution and async file persistence. - * This approach avoids type mismatch issues by modifying the original objects directly. + * Processes arguments in-place to handle file resolution and async file persistence. This + * approach avoids type mismatch issues by modifying the original objects directly. * * @param originalArgs The original arguments * @param async Whether this is an async operation diff --git a/app/common/src/main/java/stirling/software/common/model/job/JobResult.java b/app/common/src/main/java/stirling/software/common/model/job/JobResult.java index e4eb456fd..52c0826e2 100644 --- a/app/common/src/main/java/stirling/software/common/model/job/JobResult.java +++ b/app/common/src/main/java/stirling/software/common/model/job/JobResult.java @@ -30,8 +30,7 @@ public class JobResult { private String error; /** List of result files for jobs that produce files */ - @JsonIgnore - private List resultFiles; + @JsonIgnore private List resultFiles; /** Time when the job was created */ private LocalDateTime createdAt; diff --git a/app/core/build.gradle b/app/core/build.gradle index 0974a0577..745dbb87a 100644 --- a/app/core/build.gradle +++ b/app/core/build.gradle @@ -46,7 +46,7 @@ dependencies { implementation 'commons-io:commons-io:2.19.0' implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion" - implementation 'io.micrometer:micrometer-core:1.15.1' + implementation 'io.micrometer:micrometer-core:1.15.2' implementation 'com.google.zxing:core:3.5.3' implementation "org.commonmark:commonmark:$commonmarkVersion" // https://mvnrepository.com/artifact/org.commonmark/commonmark implementation "org.commonmark:commonmark-ext-gfm-tables:$commonmarkVersion" diff --git a/app/core/src/main/resources/messages_ru_RU.properties b/app/core/src/main/resources/messages_ru_RU.properties index 618458b77..302a518f8 100644 --- a/app/core/src/main/resources/messages_ru_RU.properties +++ b/app/core/src/main/resources/messages_ru_RU.properties @@ -142,9 +142,9 @@ multiPdfPrompt=Выберите PDF-файлы (2+) multiPdfDropPrompt=Выберите (или перетащите) все необходимые PDF-файлы imgPrompt=Выберите изображение(я) genericSubmit=Отправить -uploadLimit=Maximum file size: -uploadLimitExceededSingular=is too large. Maximum allowed size is -uploadLimitExceededPlural=are too large. Maximum allowed size is +uploadLimit=Максимальный размер файла: +uploadLimitExceededSingular=слишком большой. Максимально допустимый размер +uploadLimitExceededPlural=слишком большой. Максимально допустимый размер processTimeWarning=Внимание: Данный процесс может занять до минуты в зависимости от размера файла pageOrderPrompt=Пользовательский порядок страниц (Введите список номеров страниц через запятую или функции типа 2n+1): pageSelectionPrompt=Выбор страниц (Введите список номеров страниц через запятую 1,5,6 или функции типа 2n+1): @@ -170,67 +170,67 @@ sizes.medium=Средний sizes.large=Большой sizes.x-large=Очень большой error.pdfPassword=PDF-документ защищен паролем, и пароль не был предоставлен или был неверным -error.pdfCorrupted=PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation. -error.pdfCorruptedMultiple=One or more PDF files appear to be corrupted or damaged. Please try using the 'Repair PDF' feature on each file first before attempting to merge them. -error.pdfCorruptedDuring=Error {0}: PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation. +error.pdfCorrupted=Файл PDF, по-видимому, поврежден. Пожалуйста, попробуйте сначала воспользоваться функцией "Восстановить PDF", чтобы исправить файл, прежде чем приступать к этой операции. +error.pdfCorruptedMultiple=Один или несколько PDF-файлов, по-видимому, повреждены. Пожалуйста, попробуйте сначала использовать функцию "Восстановить PDF" для каждого файла, прежде чем пытаться объединить их. +error.pdfCorruptedDuring=Ошибка {0}: Файл PDF, по-видимому, поврежден. Пожалуйста, попробуйте сначала воспользоваться функцией "Восстановить PDF", чтобы исправить файл, прежде чем приступать к этой операции. # Frontend corruption error messages -error.pdfInvalid=The PDF file "{0}" appears to be corrupted or has an invalid structure. Please try using the 'Repair PDF' feature to fix the file before proceeding. -error.tryRepair=Try using the Repair PDF feature to fix corrupted files. +error.pdfInvalid=Файл PDF "{0}", по-видимому, поврежден или имеет неправильную структуру. Пожалуйста, попробуйте использовать функцию "Восстановить PDF", чтобы исправить файл, прежде чем продолжить. +error.tryRepair=Попробуйте использовать функцию восстановления PDF для исправления поврежденных файлов. # Additional error messages -error.pdfEncryption=The PDF appears to have corrupted encryption data. This can happen when the PDF was created with incompatible encryption methods. Please try using the 'Repair PDF' feature first, or contact the document creator for a new copy. -error.fileProcessing=An error occurred while processing the file during {0} operation: {1} +error.pdfEncryption=Похоже, что в PDF-файле повреждены данные для шифрования. Это может произойти, если PDF-файл был создан с использованием несовместимых методов шифрования. Пожалуйста, сначала попробуйте воспользоваться функцией "Восстановить PDF" или обратитесь к создателю документа за новой копией. +error.fileProcessing=При обработке файла во время операции {0} произошла ошибка: {1} # Generic error message templates -error.toolNotInstalled={0} is not installed -error.toolRequired={0} is required for {1} -error.conversionFailed={0} conversion failed -error.commandFailed={0} command failed -error.algorithmNotAvailable={0} algorithm not available -error.optionsNotSpecified={0} options are not specified -error.fileFormatRequired=File must be in {0} format -error.invalidFormat=Invalid {0} format: {1} -error.endpointDisabled=This endpoint has been disabled by the admin -error.urlNotReachable=URL is not reachable, please provide a valid URL +error.toolNotInstalled={0} не установлен +error.toolRequired={0} требуется для {1} +error.conversionFailed={0} не удалось выполнить преобразование +error.commandFailed={0} команда не выполнена +error.algorithmNotAvailable={0} алгоритм недоступен +error.optionsNotSpecified={0} параметры не указаны +error.fileFormatRequired=Файл должен быть в формате {0} +error.invalidFormat=Недопустимый формат {0}: {1} +error.endpointDisabled=Эта конечная точка была отключена администратором +error.urlNotReachable=URL-адрес недоступен, пожалуйста, укажите действительный URL-адрес # DPI and image rendering messages - used by frontend for dynamic translation # Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message # Frontend parses this and replaces with localized versions using these keys -error.dpiExceedsLimit=DPI value {0} exceeds maximum safe limit of {1}. High DPI values can cause memory issues and crashes. Please use a lower DPI value. -error.pageTooBigForDpi=PDF page {0} is too large to render at {1} DPI. Please try a lower DPI value (recommended: 150 or less). -error.pageTooBigExceedsArray=PDF page {0} is too large to render at {1} DPI. The resulting image would exceed Java's maximum array size. Please try a lower DPI value (recommended: 150 or less). -error.pageTooBigFor300Dpi=PDF page {0} is too large to render at 300 DPI. The resulting image would exceed Java's maximum array size. Please use a lower DPI value for PDF-to-image conversion. +error.dpiExceedsLimit=Значение DPI {0} превышает максимально допустимое значение {1}. Высокие значения DPI могут вызвать проблемы с памятью и сбои в работе. Пожалуйста, используйте меньшее значение DPI. +error.pageTooBigForDpi=Размер PDF-страницы {0} слишком велик для отображения с разрешением {1}. Пожалуйста, попробуйте использовать более низкое значение разрешения (рекомендуется 150 или меньше). +error.pageTooBigExceedsArray=Размер страницы PDF {0} слишком велик для отображения с разрешением {1} DPI. Результирующее изображение превысит максимальный размер массива, поддерживаемый Java. Пожалуйста, попробуйте использовать более низкое значение DPI (рекомендуется 150 или меньше). +error.pageTooBigFor300Dpi=Размер страницы PDF {0} слишком велик для отображения с разрешением 300 точек на дюйм. Результирующее изображение превысит максимальный размер массива, поддерживаемый Java. Пожалуйста, используйте меньшее значение разрешения для преобразования PDF в изображение. # URL and website conversion messages # System requirements messages # Authentication and security messages -error.apiKeyInvalid=API key is not valid. -error.userNotFound=User not found. -error.passwordRequired=Password must not be null. -error.accountLocked=Your account has been locked due to too many failed login attempts. -error.invalidEmail=Invalid email addresses provided. -error.emailAttachmentRequired=An attachment is required to send the email. -error.signatureNotFound=Signature file not found. +error.apiKeyInvalid=Ключ API недействителен. +error.userNotFound=Пользователь не найден. +error.passwordRequired=Пароль не должен быть пустым. +error.accountLocked=Ваша учетная запись была заблокирована из-за слишком большого количества неудачных попыток входа в систему. +error.invalidEmail=Указанный адрес электронной почты неверный. +error.emailAttachmentRequired=Для отправки электронного письма требуется вложение. +error.signatureNotFound=Файл подписи не найден. # File processing messages -error.fileNotFound=File not found with ID: {0} +error.fileNotFound=Файл с идентификатором: {0} не найден # Database and configuration messages -error.noBackupScripts=No backup scripts were found. -error.unsupportedProvider={0} is not currently supported. +error.noBackupScripts=Сценарий резервного копирования не найден +error.unsupportedProvider={0} в настоящее время не поддерживается. error.pathTraversalDetected=Path traversal detected for security reasons. # Validation messages -error.invalidArgument=Invalid argument: {0} -error.argumentRequired={0} must not be null -error.operationFailed=Operation failed: {0} -error.angleNotMultipleOf90=Angle must be a multiple of 90 -error.pdfBookmarksNotFound=No PDF bookmarks/outline found in document -error.fontLoadingFailed=Error processing font file -error.fontDirectoryReadFailed=Failed to read font directory +error.invalidArgument=Недопустимый аргумент: {0} +error.argumentRequired={0} не должно быть null +error.operationFailed=Операция завершилась неудачей: {0} +error.angleNotMultipleOf90=Угол наклона должен быть кратным 90 +error.pdfBookmarksNotFound=В PDF-документе не найдены закладки/сноски +error.fontLoadingFailed=Ошибка при обработке файла шрифта +error.fontDirectoryReadFailed=Не удалось прочитать каталог шрифтов delete=Удалить username=Имя пользователя password=Пароль @@ -260,7 +260,7 @@ disabledCurrentUserMessage=Текущий пользователь не може downgradeCurrentUserLongMessage=Невозможно понизить роль текущего пользователя. Следовательно, текущий пользователь не будет отображаться. userAlreadyExistsOAuthMessage=Пользователь уже существует как пользователь OAuth2. userAlreadyExistsWebMessage=Пользователь уже существует как веб-пользователь. -invalidRoleMessage=Invalid role. +invalidRoleMessage=Недопустимая роль. error=Ошибка oops=Упс! help=Помощь @@ -273,27 +273,27 @@ color=Цвет sponsor=Спонсор info=Информация pro=Pro -proFeatures=Pro Features +proFeatures=Pro-функции page=Страница pages=Страницы loading=Загрузка... addToDoc=Добавить в документ reset=Сбросить apply=Применить -noFileSelected=No file selected. Please upload one. -view=View -cancel=Cancel +noFileSelected=Файл не выбран. Пожалуйста, загрузите его. +view=Смотреть +cancel=Закрыть -back.toSettings=Back to Settings -back.toHome=Back to Home -back.toAdmin=Back to Admin +back.toSettings=Вернуться к настройкам +back.toHome=Вернуться на главную +back.toAdmin=Вернуться в админку legal.privacy=Политика конфиденциальности legal.terms=Условия использования legal.accessibility=Доступность legal.cookie=Политика использования файлов cookie legal.impressum=Выходные данные -legal.showCookieBanner=Cookie Preferences +legal.showCookieBanner=Настройки файлов cookie ############### # Pipeline # @@ -327,7 +327,7 @@ enterpriseEdition.button=Перейти на Pro enterpriseEdition.warning=Эта функция доступна только для пользователей Pro. enterpriseEdition.yamlAdvert=Stirling PDF Pro поддерживает файлы конфигурации YAML и другие функции SSO. enterpriseEdition.ssoAdvert=Ищете больше возможностей управления пользователями? Посмотрите Stirling PDF Pro -enterpriseEdition.proTeamFeatureDisabled=Team management features require a Pro licence or higher +enterpriseEdition.proTeamFeatureDisabled=Для функций управления группой требуется лицензия Pro или выше ################# @@ -408,8 +408,8 @@ account.property=Свойство account.webBrowserSettings=Настройки веб-браузера account.syncToBrowser=Синхронизировать Аккаунт -> Браузер account.syncToAccount=Синхронизировать Аккаунт <- Браузер -account.adminTitle=Administrator Tools -account.adminNotif=You have admin privileges. Access system settings and user management. +account.adminTitle=Инструменты администратора +account.adminNotif=У вас есть права администратора. Вам доступны системные настройки и управление пользователями. adminUserSettings.title=Настройки управления пользователями @@ -441,18 +441,18 @@ adminUserSettings.totalUsers=Всего пользователей: adminUserSettings.lastRequest=Последний запрос adminUserSettings.usage=View Usage adminUserSettings.teams=View/Edit Teams -adminUserSettings.team=Team -adminUserSettings.manageTeams=Manage Teams -adminUserSettings.createTeam=Create Team -adminUserSettings.viewTeam=View Team -adminUserSettings.deleteTeam=Delete Team -adminUserSettings.teamName=Team Name -adminUserSettings.teamExists=Team already exists -adminUserSettings.teamCreated=Team created successfully -adminUserSettings.teamChanged=User's team was updated -adminUserSettings.teamHidden=Hidden -adminUserSettings.totalMembers=Total Members -adminUserSettings.confirmDeleteTeam=Are you sure you want to delete this team? +adminUserSettings.team=Группа +adminUserSettings.manageTeams=Управление группами +adminUserSettings.createTeam=Создать группу +adminUserSettings.viewTeam=Смотреть группу +adminUserSettings.deleteTeam=Удалить группу +adminUserSettings.teamName=Имя группы +adminUserSettings.teamExists=Группа уже существует +adminUserSettings.teamCreated=Группа успешно создана +adminUserSettings.teamChanged=Группа пользователей была обновлена +adminUserSettings.teamHidden=Скрытая +adminUserSettings.totalMembers=Общее количество участников +adminUserSettings.confirmDeleteTeam=Вы уверены, что хотите удалить эту группу? teamCreated=Team created successfully teamExists=A team with that name already exists @@ -538,18 +538,18 @@ home.desc=Ваше локальное решение для всех потре home.searchBar=Поиск функций... -home.viewPdf.title=View/Edit PDF +home.viewPdf.title=Смотреть/Редактировать PDF home.viewPdf.desc=Просмотр, аннотирование, добавление текста или изображений viewPdf.tags=просмотр,чтение,аннотации,текст,изображение -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.sortBy=Sort by: +home.setFavorites=Добавить в избранное +home.hideFavorites=Скрыть из избранного +home.showFavorites=Показать избранное +home.legacyHomepage=Старый вид главной страницы +home.newHomePage=Попробуйте нашу новую главную страницу! +home.alphabetical=Алфавиту +home.globalPopularity=Популярности +home.sortBy=Сортировать по: home.multiTool.title=Мультиинструмент PDF home.multiTool.desc=Объединение, поворот, переупорядочивание и удаление страниц @@ -585,8 +585,8 @@ home.addImage.title=Добавить изображение home.addImage.desc=Добавляет изображение в указанное место PDF addImage.tags=изображение,jpg,картинка,фото -home.attachments.title=Add Attachments -home.attachments.desc=Add or remove embedded files (attachments) to/from a PDF +home.attachments.title=Добавлять вложения +home.attachments.desc=Добавление или удаление встроенных файлов (вложений) в PDF-файл или из него attachments.tags=embed,attach,file,attachment,attachments home.watermark.title=Добавить водяной знак @@ -614,8 +614,8 @@ home.compressPdfs.title=Сжать home.compressPdfs.desc=Сжимайте PDF-файлы для уменьшения их размера. compressPdfs.tags=сжатие,маленький,крошечный -home.unlockPDFForms.title=Unlock PDF Forms -home.unlockPDFForms.desc=Remove read-only property of form fields in a PDF document. +home.unlockPDFForms.title=Разблокировать PDF-формы +home.unlockPDFForms.desc=Удалите свойство "только для чтения" для полей формы в PDF-документа. unlockPDFForms.tags=remove,delete,form,field,readonly home.changeMetadata.title=Изменить метаданные @@ -740,20 +740,20 @@ home.HTMLToPDF.desc=Преобразует любой HTML-файл или zip HTMLToPDF.tags=разметка,веб-контент,преобразование,конвертация #eml-to-pdf -home.EMLToPDF.title=Email to PDF -home.EMLToPDF.desc=Converts email (EML) files to PDF format including headers, body, and inline images +home.EMLToPDF.title=Email в PDF +home.EMLToPDF.desc=Преобразует файлы электронной почты (EML) в формат PDF, включая заголовки, основную часть и встроенные изображения EMLToPDF.tags=email,conversion,eml,message,transformation,convert,mail -EMLToPDF.title=Email To PDF -EMLToPDF.header=Email To PDF -EMLToPDF.submit=Convert -EMLToPDF.downloadHtml=Download HTML intermediate file instead of PDF -EMLToPDF.downloadHtmlHelp=This allows you to see the HTML version before PDF conversion and can help debug formatting issues -EMLToPDF.includeAttachments=Include attachments in PDF -EMLToPDF.maxAttachmentSize=Maximum attachment size (MB) -EMLToPDF.help=Converts email (EML) files to PDF format including headers, body, and inline images -EMLToPDF.troubleshootingTip1=Email to HTML is a more reliable process, so with batch-processing it is recommended to save both -EMLToPDF.troubleshootingTip2=With a small number of Emails, if the PDF is malformed, you can download HTML and override some of the problematic HTML/CSS code. +EMLToPDF.title=Email в PDF +EMLToPDF.header=Email в PDF +EMLToPDF.submit=Преобразовать +EMLToPDF.downloadHtml=Загрузить промежуточный файл HTML вместо PDF +EMLToPDF.downloadHtmlHelp=Это позволит вам просмотреть HTML-версию перед преобразованием в PDF и поможет устранить проблемы с форматированием +EMLToPDF.includeAttachments=Включать вложения в формате PDF +EMLToPDF.maxAttachmentSize=Максимальный размер вложения (MB) +EMLToPDF.help=Преобразует файлы электронной почты (EML) в формат PDF, включая заголовки, основную часть и встроенные изображения +EMLToPDF.troubleshootingTip1=Электронная почта в формате HTML является более надежным процессом, поэтому при пакетной обработке рекомендуется сохранять оба +EMLToPDF.troubleshootingTip2=При небольшом количестве электронных писем, если формат PDF искажен, вы можете загрузить HTML и переопределить часть проблемного HTML/CSS-кода. EMLToPDF.troubleshootingTip3=Embeddings, however, do not work with HTMLs home.MarkdownToPDF.title=Markdown в PDF @@ -761,7 +761,7 @@ home.MarkdownToPDF.desc=Преобразует любой файл Markdown в P MarkdownToPDF.tags=разметка,веб-контент,преобразование,конвертация home.PDFToMarkdown.title=PDF to Markdown -home.PDFToMarkdown.desc=Converts any PDF to Markdown +home.PDFToMarkdown.desc=Преобразует любой PDF-файл в формат Markdown PDFToMarkdown.tags=markup,web-content,transformation,convert,md home.getPdfInfo.title=Получить ВСЮ информацию о PDF @@ -875,7 +875,7 @@ login.userIsDisabled=Пользователь деактивирован, вхо login.alreadyLoggedIn=Вы уже вошли в login.alreadyLoggedIn2=устройств(а). Пожалуйста, выйдите из этих устройств и попробуйте снова. login.toManySessions=У вас слишком много активных сессий -login.logoutMessage=You have been logged out. +login.logoutMessage=Вы вышли из системы. #auto-redact autoRedact.title=Автоматическое редактирование @@ -914,7 +914,7 @@ redact.showAttatchments=Показать вложения redact.showLayers=Показать слои (двойной щелчок для сброса всех слоев к состоянию по умолчанию) redact.colourPicker=Выбор цвета redact.findCurrentOutlineItem=Найти текущий элемент структуры -redact.applyChanges=Apply Changes +redact.applyChanges=Применить изменения #showJS showJS.title=Показать Javascript @@ -942,15 +942,15 @@ getPdfInfo.header=Получить информацию о PDF getPdfInfo.submit=Получить информацию getPdfInfo.downloadJson=Скачать JSON getPdfInfo.summary=PDF Summary -getPdfInfo.summary.encrypted=This PDF is encrypted so may face issues with some applications -getPdfInfo.summary.permissions=This PDF has {0} restricted permissions which may limit what you can do with it -getPdfInfo.summary.compliance=This PDF complies with the {0} standard -getPdfInfo.summary.basicInfo=Basic Information -getPdfInfo.summary.docInfo=Document Information -getPdfInfo.summary.encrypted.alert=Encrypted PDF - This document is password protected -getPdfInfo.summary.not.encrypted.alert=Unencrypted PDF - No password protection -getPdfInfo.summary.permissions.alert=Restricted Permissions - {0} actions are not allowed -getPdfInfo.summary.all.permissions.alert=All Permissions Allowed +getPdfInfo.summary.encrypted=Этот PDF-файл зашифрован, поэтому с некоторыми приложениями могут возникнуть проблемы +getPdfInfo.summary.permissions=Этот PDF-файл имеет {0} ограниченные права доступа, которые могут ограничить то, что вы можете с ним делать +getPdfInfo.summary.compliance=Этот PDF-файл соответствует стандарту {0} +getPdfInfo.summary.basicInfo=Основная информация +getPdfInfo.summary.docInfo=Информация о документе +getPdfInfo.summary.encrypted.alert=Зашифрованный PDF-файл - этот документ защищен паролем +getPdfInfo.summary.not.encrypted.alert=Незашифрованный PDF-файл - без защиты паролем +getPdfInfo.summary.permissions.alert=Права доступа ограничены - {0} действия запрещены +getPdfInfo.summary.all.permissions.alert=Полные права доступа getPdfInfo.summary.compliance.alert={0} Compliant getPdfInfo.summary.no.compliance.alert=No Compliance Standards getPdfInfo.summary.security.section=Security Status @@ -974,9 +974,9 @@ MarkdownToPDF.credit=Использует WeasyPrint #pdf-to-markdown -PDFToMarkdown.title=PDF To Markdown -PDFToMarkdown.header=PDF To Markdown -PDFToMarkdown.submit=Convert +PDFToMarkdown.title=PDF в Markdown +PDFToMarkdown.header=PDF в Markdown +PDFToMarkdown.submit=Преобразовать #url-to-pdf @@ -1030,10 +1030,10 @@ sanitizePDF.title=Очистить PDF sanitizePDF.header=Очистить PDF-файл sanitizePDF.selectText.1=Удалить JavaScript-действия sanitizePDF.selectText.2=Удалить встроенные файлы -sanitizePDF.selectText.3=Remove XMP metadata +sanitizePDF.selectText.3=Удалить XMP метаданные sanitizePDF.selectText.4=Удалить ссылки sanitizePDF.selectText.5=Удалить шрифты -sanitizePDF.selectText.6=Remove Document Info Metadata +sanitizePDF.selectText.6=Удалить метаданные с информацией о документе sanitizePDF.submit=Очистить PDF @@ -1182,8 +1182,8 @@ sign.last=Последняя страница sign.next=Следующая страница sign.previous=Предыдущая страница sign.maintainRatio=Переключить сохранение пропорций -sign.undo=Undo -sign.redo=Redo +sign.undo=Отменить +sign.redo=Повторить #repair repair.title=Восстановление @@ -1254,8 +1254,8 @@ compress.title=Сжать compress.header=Сжать PDF compress.credit=Этот сервис использует qpdf для сжатия/оптимизации PDF. compress.grayscale.label=Применить шкалу серого для сжатия -compress.selectText.1=Compression Settings -compress.selectText.1.1=1-3 PDF compression,
4-6 lite image compression,
7-9 intense image compression Will dramatically reduce image quality +compress.selectText.1=Параметры сжатия +compress.selectText.1.1=1-3 сжатие PDF,
4-6 лёгкое сжатие изображений,
7-9 интенсивное сжатие изображений (значительно снижает качество изображений) compress.selectText.2=Уровень оптимизации: compress.selectText.4=Автоматический режим - автоматически настраивает качество для получения точного размера PDF compress.selectText.5=Ожидаемый размер PDF (например, 25MB, 10.8MB, 25KB) @@ -1270,11 +1270,11 @@ addImage.upload=Добавить изображение addImage.submit=Добавить изображение #attachments -attachments.title=Add Attachments -attachments.header=Add attachments -attachments.description=Allows you to add attachments to the PDF -attachments.descriptionPlaceholder=Enter a description for the attachments... -attachments.addButton=Add Attachments +attachments.title=Добавлять вложения +attachments.header=Добавлять вложения +attachments.description=Позволяет добавлять вложения в PDF-файл +attachments.descriptionPlaceholder=Введите описание для вложений... +attachments.addButton=Добавлять вложения #merge merge.title=Объединить @@ -1282,7 +1282,7 @@ merge.header=Объединение нескольких PDF (2+) merge.sortByName=Сортировать по имени merge.sortByDate=Сортировать по дате merge.removeCertSign=Удалить цифровую подпись в объединенном файле? -merge.generateToc=Generate table of contents in the merged file? +merge.generateToc=Сгенерировать оглавление в объединенном файле? merge.submit=Объединить @@ -1301,7 +1301,7 @@ pdfOrganiser.mode.7=Удалить первую pdfOrganiser.mode.8=Удалить последнюю pdfOrganiser.mode.9=Удалить первую и последнюю pdfOrganiser.mode.10=Объединение четных-нечетных -pdfOrganiser.mode.11=Duplicate all pages +pdfOrganiser.mode.11=Дублировать все страницы pdfOrganiser.placeholder=(например, 1,3,2 или 4-8,2,10-12 или 2n-1) @@ -1344,7 +1344,7 @@ decrypt.success=Файл успешно расшифрован. multiTool-advert.message=Эта функция также доступна на нашей странице мультиинструмента. Попробуйте её для улучшенного постраничного интерфейса и дополнительных возможностей! #view pdf -viewPdf.title=View/Edit PDF +viewPdf.title=Смотреть/Редактировать PDF viewPdf.header=Просмотр PDF #pageRemover @@ -1492,9 +1492,9 @@ changeMetadata.selectText.5=Добавить пользовательскую з changeMetadata.submit=Изменить #unlockPDFForms -unlockPDFForms.title=Remove Read-Only from Form Fields -unlockPDFForms.header=Unlock PDF Forms -unlockPDFForms.submit=Remove +unlockPDFForms.title=Удалить поля формы, доступные только для чтения +unlockPDFForms.header=Разблокировать PDF-формы +unlockPDFForms.submit=Удалить #pdfToPDFA pdfToPDFA.title=PDF в PDF/A @@ -1724,12 +1724,12 @@ audit.dashboard.tab.dashboard=Dashboard audit.dashboard.tab.events=Audit Events audit.dashboard.tab.export=Export # Dashboard Charts -audit.dashboard.eventsByType=Events by Type -audit.dashboard.eventsByUser=Events by User -audit.dashboard.eventsOverTime=Events Over Time -audit.dashboard.period.7days=7 Days -audit.dashboard.period.30days=30 Days -audit.dashboard.period.90days=90 Days +audit.dashboard.eventsByType=События по типу +audit.dashboard.eventsByUser=События по пользователю +audit.dashboard.eventsOverTime=События за всё время +audit.dashboard.period.7days=7 дней +audit.dashboard.period.30days=30 дней +audit.dashboard.period.90days=90 дней # Events Tab audit.dashboard.auditEvents=Audit Events @@ -1812,49 +1812,48 @@ cookieBanner.preferencesModal.analytics.title=Analytics cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with. #scannerEffect -scannerEffect.title=Scanner Effect -scannerEffect.header=Scanner Effect -scannerEffect.description=Create a PDF that looks like it was scanned -scannerEffect.selectPDF=Select PDF: -scannerEffect.quality=Scan Quality -scannerEffect.quality.low=Low -scannerEffect.quality.medium=Medium -scannerEffect.quality.high=High -scannerEffect.rotation=Rotation Angle -scannerEffect.rotation.none=None -scannerEffect.rotation.slight=Slight -scannerEffect.rotation.moderate=Moderate -scannerEffect.rotation.severe=Severe -scannerEffect.submit=Create Scanner Effect +scannerEffect.title=Поддельное сканирование +scannerEffect.header=Поддельное сканирование +scannerEffect.description=Создайте PDF-файл, который выглядит так, как будто он был отсканирован +scannerEffect.selectPDF=Выбрать PDF: +scannerEffect.quality=Качество сканирования +scannerEffect.quality.low=Низкое +scannerEffect.quality.medium=Среднее +scannerEffect.quality.high=Хорошее +scannerEffect.rotation=Угол поворота +scannerEffect.rotation.none=Нет +scannerEffect.rotation.slight=Незначительный +scannerEffect.rotation.moderate=Умеренный +scannerEffect.rotation.severe=Сильный +scannerEffect.submit=Создать поддельное сканирование #home.scannerEffect -home.scannerEffect.title=Scanner Effect -home.scannerEffect.desc=Create a PDF that looks like it was scanned +home.scannerEffect.title=Поддельное сканирование +home.scannerEffect.desc=Создайте PDF-файл, который выглядит так, как будто он был отсканирован scannerEffect.tags=scan,simulate,realistic,convert -# ScannerEffect advanced settings (frontend) -scannerEffect.advancedSettings=Enable Advanced Scan Settings -scannerEffect.colorspace=Colorspace -scannerEffect.colorspace.grayscale=Grayscale -scannerEffect.colorspace.color=Color -scannerEffect.border=Border (px) -scannerEffect.rotate=Base Rotation (degrees) -scannerEffect.rotateVariance=Rotation Variance (degrees) -scannerEffect.brightness=Brightness -scannerEffect.contrast=Contrast -scannerEffect.blur=Blur -scannerEffect.noise=Noise -scannerEffect.yellowish=Yellowish (simulate old paper) -scannerEffect.resolution=Resolution (DPI) - +# scannerEffect advanced settings (frontend) +scannerEffect.advancedSettings=Включите расширенные параметры сканирования +scannerEffect.colorspace=Цветовое пространство +scannerEffect.colorspace.grayscale=Оттенки серого +scannerEffect.colorspace.color=Цветное +scannerEffect.border=Рамка (px) +scannerEffect.rotate=Базовый наклон (degrees) +scannerEffect.rotateVariance=Скорость вращения (degrees) +scannerEffect.brightness=Яркость +scannerEffect.contrast=Контраст +scannerEffect.blur=Размытие +scannerEffect.noise=Шум +scannerEffect.yellowish=Желтоватый оттенок (имитация старой бумаги) +scannerEffect.resolution=Разрешение (DPI) # Table of Contents Feature -home.editTableOfContents.title=Edit Table of Contents -home.editTableOfContents.desc=Add or edit bookmarks and table of contents in PDF documents +home.editTableOfContents.title=Редактировать оглавление +home.editTableOfContents.desc=Добавление или редактирование закладок и оглавления в PDF-документах editTableOfContents.tags=bookmarks,toc,navigation,index,table of contents,chapters,sections,outline -editTableOfContents.title=Edit Table of Contents -editTableOfContents.header=Add or Edit PDF Table of Contents +editTableOfContents.title=Редактировать оглавление +editTableOfContents.header=Добавление или редактирование закладок и оглавления в PDF-документах editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to append to existing) editTableOfContents.editorTitle=Bookmark Editor editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks. diff --git a/app/core/src/main/resources/static/css/navbar.css b/app/core/src/main/resources/static/css/navbar.css index cf5bba667..047957b6d 100644 --- a/app/core/src/main/resources/static/css/navbar.css +++ b/app/core/src/main/resources/static/css/navbar.css @@ -159,41 +159,36 @@ .scalable-languages-container { display: grid; - /* Auto-fill columns, with a minimum width of 180px */ - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + grid-template-columns: repeat(1, 1fr); +} + +@media (min-width: 400px) { + #languageSelection.scalable-languages-container { + grid-template-columns: repeat(2, 1fr); + } +} +@media (min-width: 600px) { + #languageSelection.scalable-languages-container { + grid-template-columns: repeat(3, 1fr); + } +} +@media (min-width: 900px) { + #languageSelection.scalable-languages-container { + grid-template-columns: repeat(4, 1fr) !important; + } } .scalable-languages-container:not(:has(> :nth-child(4))) .lang-dropdown-item-wrapper:last-child { border: 0px !important } -.scalable-languages-container:has(> *:nth-child(1)) { - --count: 1; +html[dir="ltr"] #languageSelection .lang-dropdown-item-wrapper:last-child { + border-right: none !important; } -.scalable-languages-container:has(> *:nth-child(2)) { - --count: 2; +#languageSelection .lang-dropdown-item-wrapper:last-child { + border: 0px !important; } - -.scalable-languages-container:has(> *:nth-child(3)) { - --count: 3; -} - -html[dir="ltr"] .lang-dropdown-item-wrapper { - border-right: 2px solid var(--md-nav-color-on-separator); -} - -html[dir="rtl"] .lang-dropdown-item-wrapper { - border-left: 2px solid var(--md-nav-color-on-separator); -} - -/* Responsive adjustments */ -@media (min-width: 1200px) { - .lang-dropdown-item-wrapper .dropdown-item { - min-width: 200px - } -} - @media (max-width: 600px) { .scalable-languages-container { grid-template-columns: repeat(2, 1fr); @@ -236,6 +231,41 @@ html[dir="rtl"] .lang-dropdown-item-wrapper { } } +.scalable-languages-container:has(> *:nth-child(1)) { + --count: 1; +} + +.scalable-languages-container:has(> *:nth-child(2)) { + --count: 2; +} + +.scalable-languages-container:has(> *:nth-child(3)) { + --count: 3; +} + +html[dir="ltr"] .lang-dropdown-item-wrapper { + border-right: 2px solid var(--md-nav-color-on-separator); +} + +html[dir="rtl"] .lang-dropdown-item-wrapper { + border-left: 2px solid var(--md-nav-color-on-separator); +} + +/* Responsive adjustments */ +@media (min-width: 1200px) { + .lang-dropdown-item-wrapper .dropdown-item { + min-width: 200px +} + +.scroll-lock-y { + overflow-y: auto; + max-height: 80vh; + overscroll-behavior-y: contain; + -webkit-overflow-scrolling: touch; +} +} + + .dropdown-item .icon-text { text-wrap: wrap; word-break: break-word; @@ -290,6 +320,21 @@ span.icon-text::after { color: var(--md-sys-color-on-surface-variant); } +.nav-link { + display: flex; + align-items: center; + max-width: 98vw; +} + +.chevron-icon { + margin-left: auto; + transition: transform 0.3s ease; +} + +[aria-expanded="true"] > .chevron-icon { + transform: rotate(180deg); +} + .nav-item { position: relative; } @@ -473,6 +518,7 @@ html[dir="rtl"] .dropdown-menu { box-shadow: var(--md-sys-elevation-2); } + .dropdown-menu-tp { color: transparent; background-color: transparent; @@ -484,27 +530,119 @@ html[dir="rtl"] .dropdown-menu { display: inline-flex; } -@media (min-width:992px) { +@media (max-width:1199.98px) { + .navbar-collapse .dropdown-menu { + width: 100%; + } + .navbar-collapse .dropdown-menu-wrapper { + width: 100%; + box-sizing: border-box; + } + .navbar-collapse .dropdown-mw-28 { + min-width: 0; + } +} + +@media (min-width:1200px) { + /* This CSS-based hover is disabled because it conflicts with Bootstrap's JavaScript. + Hover functionality is now handled in navbar.js and search.js */ + /* .dropdown:hover .dropdown-menu { display: block; margin-top: 0; } + */ - /* .icon-hide { - display: none; - } */ -} - -@media (max-width:1199px) { - .icon-hide { - display: inline-flex; - } -} - -@media (min-width:1200px) { .icon-hide { display: none; } + .chevron-icon { + display: none !important; + } +} + +@media (max-width: 1199.98px) { + .navbar-collapse .dropdown-menu { + width: 100vw !important; + max-width: 100vw !important; + left: 0 !important; + right: 0 !important; + transform: none !important; + transform-origin: none !important; + } + + .navbar .navbar-expand-xl .dropdown-menu, + .navbar-expand .dropdown-menu { + transform: none !important; + transform-origin: none !important; + left: 0 !important; + right: 0 !important; + } + + .navbar-collapse .dropdown-mega .dropdown-menu { + max-height: 60vh; + overflow-y: auto; + } + .navbar-collapse .dropdown-mega .dropdown-menu-wrapper { + border-radius: 0; + } + + #favoritesDropdown, + #languageDropdown + .dropdown-menu .dropdown-menu-wrapper, + #searchDropdown + .dropdown-menu .dropdown-menu-wrapper { + padding: 1.5rem 1rem; + } + + #favoritesDropdown, #languageSelection, #searchResults { + width: 100%; + box-sizing: border-box; + } + + .navbar-collapse .dropdown-menu-wrapper { + width: 95vw; + box-sizing: border-box; + margin-left: 0 !important; + padding: 0 !important; + } + .navbar-collapse .dropdown-mw-28 { + min-width: 0; + } + .icon-hide { + display: inline-flex; + } + + .navbar-collapse .dropdown-item { + margin-left: 0 !important; + } + + .container { + margin-left: auto !important; + margin-right: auto !important; + padding-left: 4px !important; + } + + #mainNavbarDropdownMenu { + transform: none !important; + transform-origin: none !important; + left: 0 !important; + right: 0 !important; + width: 100vw !important; + margin-bottom: 0 !important; + } + + .dropdown-menu, + .dropdown-menu-tp, + .dropdown-menu-tp.show { + transform: none !important; + transform-origin: none !important; + max-width: 95vw !important; + width: 100vw !important; + left: 0 !important; + right: 0 !important; + margin-bottom: 0 !important; + } + + } .go-pro-link { @@ -558,6 +696,12 @@ html[dir="rtl"] .dropdown-menu { box-sizing: border-box; } +@media (max-width: 768px) { + .feature-group { + min-width: 10rem; + } +} + .feature-rows { display: flex; flex-wrap: wrap; diff --git a/app/core/src/main/resources/static/css/theme/componentes.css b/app/core/src/main/resources/static/css/theme/componentes.css index c10806ff7..a113015eb 100644 --- a/app/core/src/main/resources/static/css/theme/componentes.css +++ b/app/core/src/main/resources/static/css/theme/componentes.css @@ -40,7 +40,7 @@ textarea { } *::-webkit-scrollbar-corner { - background-color: var(--md-sys-color-surface); + background-color: var(--md-sys-color-surface); } /* Alerts */ @@ -66,6 +66,9 @@ td { background-color: var(--md-sys-color-surface-5); border-radius: 3rem; padding: 2.5rem; + + max-width: 95vw; + margin-left: 2vw; } .card { diff --git a/app/core/src/main/resources/static/js/favourites.js b/app/core/src/main/resources/static/js/favourites.js index 913c656b2..169cf53d9 100644 --- a/app/core/src/main/resources/static/js/favourites.js +++ b/app/core/src/main/resources/static/js/favourites.js @@ -44,8 +44,16 @@ function updateFavoritesDropdown() { contentWrapper.style.color = 'inherit'; // Clone the original content - var originalContent = navbarEntry.querySelector('div').cloneNode(true); - contentWrapper.appendChild(originalContent); + var divElement = navbarEntry.querySelector('div'); + if (divElement) { + var originalContent = divElement.cloneNode(true); + contentWrapper.appendChild(originalContent); + } else { + // Fallback: create content manually if div is not found + var fallbackContent = document.createElement('div'); + fallbackContent.innerHTML = navbarEntry.innerHTML; + contentWrapper.appendChild(fallbackContent); + } // Create the remove button var removeButton = document.createElement('button'); diff --git a/app/core/src/main/resources/static/js/navbar.js b/app/core/src/main/resources/static/js/navbar.js index 1d8c0dcce..a95ff1639 100644 --- a/app/core/src/main/resources/static/js/navbar.js +++ b/app/core/src/main/resources/static/js/navbar.js @@ -42,6 +42,39 @@ function toolsManager() { }); } +function setupDropdowns() { + const dropdowns = document.querySelectorAll('.navbar-nav > .nav-item.dropdown'); + + dropdowns.forEach((dropdown) => { + const toggle = dropdown.querySelector('[data-bs-toggle="dropdown"]'); + if (!toggle) return; + + // Skip search dropdown, it has its own logic + if (toggle.id === 'searchDropdown') { + return; + } + + dropdown.addEventListener('show.bs.dropdown', () => { + // Find all other open dropdowns and hide them + const openDropdowns = document.querySelectorAll('.navbar-nav .dropdown-menu.show'); + openDropdowns.forEach((menu) => { + const parentDropdown = menu.closest('.dropdown'); + if (parentDropdown && parentDropdown !== dropdown) { + const parentToggle = parentDropdown.querySelector('[data-bs-toggle="dropdown"]'); + if (parentToggle) { + // Get or create Bootstrap dropdown instance + let instance = bootstrap.Dropdown.getInstance(parentToggle); + if (!instance) { + instance = new bootstrap.Dropdown(parentToggle); + } + instance.hide(); + } + } + }); + }); + }); +} + window.tooltipSetup = () => { const tooltipElements = document.querySelectorAll('[title]'); @@ -56,23 +89,54 @@ window.tooltipSetup = () => { 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`; + if (window.innerWidth >= 1200) { + customTooltip.style.display = 'block'; + customTooltip.style.left = `${event.pageX + 10}px`; + 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`; + if (window.innerWidth >= 1200) { + 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'; }); }); }; + +// Override the bootstrap dropdown styles for mobile +function fixNavbarDropdownStyles() { + if (window.innerWidth < 1200) { + document.querySelectorAll('.navbar .dropdown-menu').forEach(function(menu) { + menu.style.transform = 'none'; + menu.style.transformOrigin = 'none'; + menu.style.left = '0'; + menu.style.right = '0'; + menu.style.maxWidth = '95vw'; + menu.style.width = '100vw'; + menu.style.marginBottom = '0'; + }); + } else { + document.querySelectorAll('.navbar .dropdown-menu').forEach(function(menu) { + menu.style.transform = ''; + menu.style.transformOrigin = ''; + menu.style.left = ''; + menu.style.right = ''; + menu.style.maxWidth = ''; + menu.style.width = ''; + menu.style.marginBottom = ''; + }); + } +} + document.addEventListener('DOMContentLoaded', () => { tooltipSetup(); + setupDropdowns(); + fixNavbarDropdownStyles(); }); +window.addEventListener('resize', fixNavbarDropdownStyles); diff --git a/app/core/src/main/resources/static/js/search.js b/app/core/src/main/resources/static/js/search.js index c7932965c..277d722a9 100644 --- a/app/core/src/main/resources/static/js/search.js +++ b/app/core/src/main/resources/static/js/search.js @@ -56,8 +56,16 @@ document.querySelector("#navbarSearchInput").addEventListener("input", function contentWrapper.style.textDecoration = "none"; contentWrapper.style.color = "inherit"; - var originalContent = item.querySelector("div").cloneNode(true); - contentWrapper.appendChild(originalContent); + var divElement = item.querySelector("div"); + if (divElement) { + var originalContent = divElement.cloneNode(true); + contentWrapper.appendChild(originalContent); + } else { + // Fallback: create content manually if div is not found + var fallbackContent = document.createElement("div"); + fallbackContent.innerHTML = item.innerHTML; + contentWrapper.appendChild(fallbackContent); + } contentWrapper.onclick = function () { window.location.href = itemHref; @@ -77,35 +85,52 @@ document.querySelector("#navbarSearchInput").addEventListener("input", function const searchDropdown = document.getElementById('searchDropdown'); const searchInput = document.getElementById('navbarSearchInput'); -const dropdownMenu = searchDropdown.querySelector('.dropdown-menu'); -// Handle dropdown shown event -searchDropdown.addEventListener('shown.bs.dropdown', function () { - searchInput.focus(); -}); +// Check if elements exist before proceeding +if (searchDropdown && searchInput) { + const dropdownMenu = searchDropdown.querySelector('.dropdown-menu'); -// Handle hover opening -searchDropdown.addEventListener('mouseenter', function () { + // Create a single dropdown instance const dropdownInstance = new bootstrap.Dropdown(searchDropdown); - dropdownInstance.show(); - setTimeout(() => { - searchInput.focus(); - }, 100); -}); +// Handle click for mobile + searchDropdown.addEventListener('click', function (e) { + e.preventDefault(); + const isOpen = dropdownMenu.classList.contains('show'); + // Close all other open dropdowns + document.querySelectorAll('.navbar-nav .dropdown-menu.show').forEach((menu) => { + if (menu !== dropdownMenu) { + const parentDropdown = menu.closest('.dropdown'); + if (parentDropdown) { + const parentToggle = parentDropdown.querySelector('[data-bs-toggle="dropdown"]'); + if (parentToggle) { + let instance = bootstrap.Dropdown.getInstance(parentToggle); + if (!instance) { + instance = new bootstrap.Dropdown(parentToggle); + } + instance.hide(); + } + } + } + }); + if (!isOpen) { + dropdownInstance.show(); + setTimeout(() => searchInput.focus(), 150); + } else { + dropdownInstance.hide(); + } + }); -// Handle mouse leave -searchDropdown.addEventListener('mouseleave', function () { - // Check if current value is empty (including if user typed and then deleted) - if (searchInput.value.trim().length === 0) { - searchInput.blur(); - const dropdownInstance = new bootstrap.Dropdown(searchDropdown); - dropdownInstance.hide(); - } -}); + // Hide dropdown if it's open and user clicks outside + document.addEventListener('click', function(e) { + if (!searchDropdown.contains(e.target) && dropdownMenu.classList.contains('show')) { + dropdownInstance.hide(); + } + }); -searchDropdown.addEventListener('hidden.bs.dropdown', function () { - if (searchInput.value.trim().length === 0) { - searchInput.blur(); - } -}); + // Keep dropdown open if search input is clicked + searchInput.addEventListener('click', function (e) { + e.stopPropagation(); + }); + +} diff --git a/app/core/src/main/resources/templates/fragments/common.html b/app/core/src/main/resources/templates/fragments/common.html index d873fd7a1..78f0d5662 100644 --- a/app/core/src/main/resources/templates/fragments/common.html +++ b/app/core/src/main/resources/templates/fragments/common.html @@ -29,8 +29,40 @@ // Determine if this is actually a high DPI screen at page load const isHighDPI = systemDPR > 1.4; + + // Reset all navbar and dropdown scaling styles + function resetNavScaling() { + const navbarElement = document.querySelector('.navbar'); + if (navbarElement) { + navbarElement.style.transform = ''; + navbarElement.style.transformOrigin = ''; + navbarElement.style.width = ''; + navbarElement.style.left = ''; + navbarElement.style.right = ''; + navbarElement.style.marginBottom = ''; + navbarElement.classList.remove('navbar-expand-lg'); + navbarElement.classList.remove('navbar-expand-xl'); + } + // Reset dropdown scaling + const dropdowns = document.querySelectorAll('.dropdown-menu'); + dropdowns.forEach(dropdown => { + dropdown.style.transform = ''; + dropdown.style.transformOrigin = ''; + }); + // Reset CSS custom property + document.documentElement.style.setProperty('--navbar-height', ''); + } function scaleNav() { + resetNavScaling(); + if (window.innerWidth < 1200) { + const navbarElement = document.querySelector('.navbar'); + if (navbarElement) { + navbarElement.classList.remove('navbar-expand-lg'); + navbarElement.classList.add('navbar-expand-xl'); + } + return; + } const currentDPR = window.devicePixelRatio || 1; const browserZoom = currentDPR / systemDPR; diff --git a/app/core/src/main/resources/templates/fragments/navbar.html b/app/core/src/main/resources/templates/fragments/navbar.html index f6f707211..773810a5a 100644 --- a/app/core/src/main/resources/templates/fragments/navbar.html +++ b/app/core/src/main/resources/templates/fragments/navbar.html @@ -45,9 +45,10 @@ apps + expand_more