From ce803b4c20602f63379b39af973d24a6435c6f83 Mon Sep 17 00:00:00 2001 From: Fovway Date: Sun, 26 Oct 2025 14:52:22 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80?= =?UTF-8?q?=D0=B6=D0=BA=D0=B0=20=D0=B2=D0=BD=D0=B5=D1=88=D0=BD=D0=B8=D1=85?= =?UTF-8?q?=20=D1=81=D1=81=D1=8B=D0=BB=D0=BE=D0=BA=20=D0=B2=20=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=B4=D0=B5=D1=80=D0=B5=D1=80=D0=B5=20marked.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Реализована функция для определения внешних ссылок и переопределен рендеринг ссылок, чтобы открывать их в новом окне. - Добавлены обработчики для внешних ссылок, которые учитывают режим PWA. - Обновлены стили для внешних ссылок, добавлен значок для визуального обозначения. --- public/app.js | 56 +++++++++++ public/settings.js | 75 ++++++++++++++- public/style.css | 20 ++++ public/test-external-links.html | 160 ++++++++++++++++++++++++++++++++ 4 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 public/test-external-links.html diff --git a/public/app.js b/public/app.js index 53fbb0c..0305be3 100644 --- a/public/app.js +++ b/public/app.js @@ -1585,6 +1585,33 @@ function highlightSearchText(content, query) { // Настройка marked.js для поддержки чекбоксов и strikethrough const renderer = new marked.Renderer(); +// Функция для определения внешних ссылок +function isExternalLink(href) { + try { + const url = new URL(href); + return url.origin !== window.location.origin; + } catch (e) { + // Если URL невалидный, считаем его внутренним + return false; + } +} + +// Переопределяем рендеринг ссылок для открытия внешних ссылок в браузере +const originalLink = renderer.link.bind(renderer); +renderer.link = function (href, title, text) { + const isExternal = isExternalLink(href); + + if (isExternal) { + // Внешние ссылки открываем в браузере + return `${text}`; + } else { + // Внутренние ссылки обрабатываем как обычно + return originalLink(href, title, text); + } +}; + // Переопределяем рендеринг списков, чтобы чекбоксы были кликабельными (без disabled) const originalListItem = renderer.listitem.bind(renderer); renderer.listitem = function (text, task, checked) { @@ -1743,6 +1770,9 @@ async function renderNotes(notes) { // Добавляем обработчики для изображений в заметках addImageEventListeners(); + // Добавляем обработчики для внешних ссылок + addExternalLinkListeners(); + // Добавляем обработчики для чекбоксов в заметках addCheckboxEventListeners(); @@ -2676,6 +2706,32 @@ function addImageEventListeners() { // Обработчики для кнопок удаления изображений удалены - кнопки удаления только в режиме редактирования } +// Функция для добавления обработчиков внешних ссылок +function addExternalLinkListeners() { + // Обработчики для внешних ссылок + document.querySelectorAll(".external-link").forEach((linkElement) => { + // Проверяем, не добавлен ли уже обработчик + if (linkElement._externalClickHandler) { + return; // Пропускаем, если обработчик уже добавлен + } + + // Создаем новый обработчик + linkElement._externalClickHandler = function (event) { + // Для PWA приложений открываем ссылку в браузере + if ( + window.matchMedia("(display-mode: standalone)").matches || + window.navigator.standalone === true + ) { + event.preventDefault(); + window.open(this.href, "_blank", "noopener,noreferrer"); + } + // В обычном браузере оставляем стандартное поведение (target="_blank") + }; + + linkElement.addEventListener("click", linkElement._externalClickHandler); + }); +} + // Функция для применения визуальных стилей к чекбоксу function applyCheckboxStyles(checkbox, checked) { const parentLi = checkbox.closest("li"); diff --git a/public/settings.js b/public/settings.js index 2af5e6a..dc44ede 100644 --- a/public/settings.js +++ b/public/settings.js @@ -1,3 +1,70 @@ +// Настройка marked.js для поддержки внешних ссылок +function setupMarkedRenderer() { + // Функция для определения внешних ссылок + function isExternalLink(href) { + try { + const url = new URL(href); + return url.origin !== window.location.origin; + } catch (e) { + // Если URL невалидный, считаем его внутренним + return false; + } + } + + // Создаем renderer для marked + const renderer = new marked.Renderer(); + + // Переопределяем рендеринг ссылок для открытия внешних ссылок в браузере + const originalLink = renderer.link.bind(renderer); + renderer.link = function (href, title, text) { + const isExternal = isExternalLink(href); + + if (isExternal) { + // Внешние ссылки открываем в браузере + return `${text}`; + } else { + // Внутренние ссылки обрабатываем как обычно + return originalLink(href, title, text); + } + }; + + // Настраиваем marked + marked.setOptions({ + gfm: true, + breaks: true, + renderer: renderer, + html: true, + }); +} + +// Функция для добавления обработчиков внешних ссылок +function addExternalLinkListeners() { + // Обработчики для внешних ссылок + document.querySelectorAll(".external-link").forEach((linkElement) => { + // Проверяем, не добавлен ли уже обработчик + if (linkElement._externalClickHandler) { + return; // Пропускаем, если обработчик уже добавлен + } + + // Создаем новый обработчик + linkElement._externalClickHandler = function (event) { + // Для PWA приложений открываем ссылку в браузере + if ( + window.matchMedia("(display-mode: standalone)").matches || + window.navigator.standalone === true + ) { + event.preventDefault(); + window.open(this.href, "_blank", "noopener,noreferrer"); + } + // В обычном браузере оставляем стандартное поведение (target="_blank") + }; + + linkElement.addEventListener("click", linkElement._externalClickHandler); + }); +} + // Логика переключения темы function initThemeToggle() { const themeToggleBtn = document.getElementById("theme-toggle-btn"); @@ -58,7 +125,10 @@ function applyTheme(theme) { } // Инициализируем переключатель темы при загрузке страницы -document.addEventListener("DOMContentLoaded", initThemeToggle); +document.addEventListener("DOMContentLoaded", function () { + initThemeToggle(); + setupMarkedRenderer(); +}); // Переменные для пагинации логов let logsOffset = 0; @@ -439,6 +509,9 @@ async function loadArchivedNotes() { // Добавление обработчиков для архивных заметок function addArchivedNotesEventListeners() { + // Добавляем обработчики для внешних ссылок + addExternalLinkListeners(); + // Восстановление document.querySelectorAll(".btn-restore").forEach((btn) => { btn.addEventListener("click", async (e) => { diff --git a/public/style.css b/public/style.css index 425b1c9..5338167 100644 --- a/public/style.css +++ b/public/style.css @@ -800,6 +800,26 @@ textarea:focus { overflow-wrap: break-word; } +/* Стили для внешних ссылок */ +.textNote a.external-link { + position: relative; + padding-right: 16px; +} + +.textNote a.external-link::after { + content: "↗"; + position: absolute; + right: 0; + top: 0; + font-size: 0.8em; + opacity: 0.7; + transition: opacity 0.3s ease; +} + +.textNote a.external-link:hover::after { + opacity: 1; +} + .textNote a:hover { text-decoration: underline; } diff --git a/public/test-external-links.html b/public/test-external-links.html new file mode 100644 index 0000000..4d6461e --- /dev/null +++ b/public/test-external-links.html @@ -0,0 +1,160 @@ + + + + + + Тест внешних ссылок - NoteJS + + + + + +
+

Тест внешних ссылок в PWA

+ +
+

Примеры ссылок:

+ +

Внутренние ссылки (открываются в PWA):

+

Перейти к заметкам

+

Перейти к профилю

+ +

Внешние ссылки (открываются в браузере):

+

+ Google +

+

+ GitHub +

+

+ Wikipedia +

+ +

Markdown ссылки:

+

Внутренняя ссылка: [Заметки](/notes)

+

Внешняя ссылка: [Google](https://www.google.com)

+

Внешняя ссылка: [GitHub](https://github.com)

+
+ +
+

Ожидаемое поведение:

+
    +
  • Внутренние ссылки должны открываться в том же PWA окне
  • +
  • Внешние ссылки должны открываться в браузере (новой вкладке)
  • +
  • Внешние ссылки должны иметь иконку ↗
  • +
  • + В PWA режиме внешние ссылки должны принудительно открываться в + браузере +
  • +
+
+
+ + + + +