✨ Добавлена поддержка внешних ссылок в рендерере marked.js
- Реализована функция для определения внешних ссылок и переопределен рендеринг ссылок, чтобы открывать их в новом окне. - Добавлены обработчики для внешних ссылок, которые учитывают режим PWA. - Обновлены стили для внешних ссылок, добавлен значок для визуального обозначения.
This commit is contained in:
parent
e249638e7f
commit
ce803b4c20
@ -1585,6 +1585,33 @@ function highlightSearchText(content, query) {
|
|||||||
// Настройка marked.js для поддержки чекбоксов и strikethrough
|
// Настройка marked.js для поддержки чекбоксов и strikethrough
|
||||||
const renderer = new marked.Renderer();
|
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 `<a href="${href}" title="${
|
||||||
|
title || ""
|
||||||
|
}" target="_blank" rel="noopener noreferrer" class="external-link">${text}</a>`;
|
||||||
|
} else {
|
||||||
|
// Внутренние ссылки обрабатываем как обычно
|
||||||
|
return originalLink(href, title, text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Переопределяем рендеринг списков, чтобы чекбоксы были кликабельными (без disabled)
|
// Переопределяем рендеринг списков, чтобы чекбоксы были кликабельными (без disabled)
|
||||||
const originalListItem = renderer.listitem.bind(renderer);
|
const originalListItem = renderer.listitem.bind(renderer);
|
||||||
renderer.listitem = function (text, task, checked) {
|
renderer.listitem = function (text, task, checked) {
|
||||||
@ -1743,6 +1770,9 @@ async function renderNotes(notes) {
|
|||||||
// Добавляем обработчики для изображений в заметках
|
// Добавляем обработчики для изображений в заметках
|
||||||
addImageEventListeners();
|
addImageEventListeners();
|
||||||
|
|
||||||
|
// Добавляем обработчики для внешних ссылок
|
||||||
|
addExternalLinkListeners();
|
||||||
|
|
||||||
// Добавляем обработчики для чекбоксов в заметках
|
// Добавляем обработчики для чекбоксов в заметках
|
||||||
addCheckboxEventListeners();
|
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) {
|
function applyCheckboxStyles(checkbox, checked) {
|
||||||
const parentLi = checkbox.closest("li");
|
const parentLi = checkbox.closest("li");
|
||||||
|
|||||||
@ -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 `<a href="${href}" title="${
|
||||||
|
title || ""
|
||||||
|
}" target="_blank" rel="noopener noreferrer" class="external-link">${text}</a>`;
|
||||||
|
} 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() {
|
function initThemeToggle() {
|
||||||
const themeToggleBtn = document.getElementById("theme-toggle-btn");
|
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;
|
let logsOffset = 0;
|
||||||
@ -439,6 +509,9 @@ async function loadArchivedNotes() {
|
|||||||
|
|
||||||
// Добавление обработчиков для архивных заметок
|
// Добавление обработчиков для архивных заметок
|
||||||
function addArchivedNotesEventListeners() {
|
function addArchivedNotesEventListeners() {
|
||||||
|
// Добавляем обработчики для внешних ссылок
|
||||||
|
addExternalLinkListeners();
|
||||||
|
|
||||||
// Восстановление
|
// Восстановление
|
||||||
document.querySelectorAll(".btn-restore").forEach((btn) => {
|
document.querySelectorAll(".btn-restore").forEach((btn) => {
|
||||||
btn.addEventListener("click", async (e) => {
|
btn.addEventListener("click", async (e) => {
|
||||||
|
|||||||
@ -800,6 +800,26 @@ textarea:focus {
|
|||||||
overflow-wrap: break-word;
|
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 {
|
.textNote a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|||||||
160
public/test-external-links.html
Normal file
160
public/test-external-links.html
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Тест внешних ссылок - NoteJS</title>
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<link rel="manifest" href="manifest.json" />
|
||||||
|
<meta name="theme-color" content="#007bff" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Тест внешних ссылок в PWA</h1>
|
||||||
|
|
||||||
|
<div class="textNote">
|
||||||
|
<h2>Примеры ссылок:</h2>
|
||||||
|
|
||||||
|
<h3>Внутренние ссылки (открываются в PWA):</h3>
|
||||||
|
<p><a href="/notes">Перейти к заметкам</a></p>
|
||||||
|
<p><a href="/profile">Перейти к профилю</a></p>
|
||||||
|
|
||||||
|
<h3>Внешние ссылки (открываются в браузере):</h3>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href="https://www.google.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="external-link"
|
||||||
|
>Google</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href="https://github.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="external-link"
|
||||||
|
>GitHub</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href="https://www.wikipedia.org"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="external-link"
|
||||||
|
>Wikipedia</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Markdown ссылки:</h3>
|
||||||
|
<p>Внутренняя ссылка: [Заметки](/notes)</p>
|
||||||
|
<p>Внешняя ссылка: [Google](https://www.google.com)</p>
|
||||||
|
<p>Внешняя ссылка: [GitHub](https://github.com)</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="test-results">
|
||||||
|
<h3>Ожидаемое поведение:</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Внутренние ссылки должны открываться в том же PWA окне</li>
|
||||||
|
<li>Внешние ссылки должны открываться в браузере (новой вкладке)</li>
|
||||||
|
<li>Внешние ссылки должны иметь иконку ↗</li>
|
||||||
|
<li>
|
||||||
|
В PWA режиме внешние ссылки должны принудительно открываться в
|
||||||
|
браузере
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
|
<script>
|
||||||
|
// Настройка 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 `<a href="${href}" title="${
|
||||||
|
title || ""
|
||||||
|
}" target="_blank" rel="noopener noreferrer" class="external-link">${text}</a>`;
|
||||||
|
} 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
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
setupMarkedRenderer();
|
||||||
|
|
||||||
|
// Обрабатываем markdown ссылки
|
||||||
|
const markdownLinks = document.querySelectorAll(".textNote p");
|
||||||
|
markdownLinks.forEach((p) => {
|
||||||
|
if (p.textContent.includes("[") && p.textContent.includes("](")) {
|
||||||
|
const htmlContent = marked.parse(p.textContent);
|
||||||
|
p.innerHTML = htmlContent;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addExternalLinkListeners();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user