Добавлены функции для аннотирования ссылок и улучшения стилей заголовков в документации

This commit is contained in:
2026-04-23 19:38:55 +03:00
parent ecb0d8ebc2
commit 9227f2de30
4 changed files with 342 additions and 51 deletions

View File

@@ -185,6 +185,99 @@ const topLinks = [
});
};
const trimDestination = (value, maxLength = 42) => {
if (value.length <= maxLength) return value;
return `${value.slice(0, Math.max(0, maxLength - 1))}…`;
};
const decodePath = (value) => {
try {
return decodeURIComponent(value);
} catch {
return value;
}
};
const annotateLinkDestinations = () => {
const links = document.querySelectorAll('a[href]');
links.forEach((linkNode) => {
if (!(linkNode instanceof HTMLAnchorElement)) return;
if (linkNode.dataset.linkAnnotated === 'true') return;
const rawHref = linkNode.getAttribute('href')?.trim();
if (!rawHref || rawHref.toLowerCase().startsWith('javascript:')) return;
let parsedUrl;
try {
parsedUrl = new URL(rawHref, window.location.href);
} catch {
return;
}
const isHashOnly = rawHref.startsWith('#');
const isHttp = parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:';
const isMail = parsedUrl.protocol === 'mailto:';
const isTel = parsedUrl.protocol === 'tel:';
const isExternal = isHttp && parsedUrl.origin !== window.location.origin;
const isSamePageAnchor =
!isExternal && !!parsedUrl.hash && parsedUrl.pathname === window.location.pathname;
const isContentLink = linkNode.closest('.sl-markdown-content') instanceof HTMLElement;
const shouldDecorate =
isContentLink &&
!isHashOnly &&
!linkNode.classList.contains('sl-anchor-link') &&
!linkNode.classList.contains('map-cta-button') &&
!linkNode.classList.contains('map-ghost-button') &&
!linkNode.classList.contains('sl-link-button');
if (shouldDecorate) {
let kind = 'internal';
if (isExternal) kind = 'external';
else if (isMail) kind = 'mail';
else if (isTel) kind = 'tel';
else if (isSamePageAnchor) kind = 'anchor';
linkNode.dataset.linkKind = kind;
let destination = '';
if (isExternal) destination = parsedUrl.hostname.replace(/^www\./, '');
else if (isMail) destination = rawHref.replace(/^mailto:/i, '');
else if (isTel) destination = rawHref.replace(/^tel:/i, '');
else destination = `${decodePath(parsedUrl.pathname)}${parsedUrl.hash || ''}`;
if (destination) {
linkNode.dataset.linkDestination = trimDestination(destination);
}
}
if (isExternal) {
const relValues = new Set((linkNode.rel || '').split(/\s+/).filter(Boolean));
relValues.add('noopener');
relValues.add('noreferrer');
linkNode.rel = Array.from(relValues).join(' ');
if (!linkNode.target) {
linkNode.target = '_blank';
}
}
if (!linkNode.title) {
if (isHashOnly || isSamePageAnchor) {
linkNode.title = `Переход к разделу: ${parsedUrl.hash || rawHref}`;
} else if (isExternal) {
linkNode.title = `Внешняя ссылка: ${parsedUrl.hostname.replace(/^www\./, '')}`;
} else if (isMail) {
linkNode.title = `Написать: ${rawHref.replace(/^mailto:/i, '')}`;
} else if (isTel) {
linkNode.title = `Позвонить: ${rawHref.replace(/^tel:/i, '')}`;
} else {
linkNode.title = `Перейти: ${decodePath(parsedUrl.pathname)}${parsedUrl.hash || ''}`;
}
}
linkNode.dataset.linkAnnotated = 'true';
});
};
const copyTextToClipboard = async (value) => {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(value);
@@ -312,6 +405,7 @@ const topLinks = [
sanitizeNavLabels();
sanitizeDocHeadings();
decorateSectionIcons();
annotateLinkDestinations();
bindCopyIpChips();
setReadProgress();
window.addEventListener('scroll', setReadProgress, { passive: true });
@@ -329,6 +423,7 @@ const topLinks = [
sanitizeNavLabels();
sanitizeDocHeadings();
decorateSectionIcons();
annotateLinkDestinations();
bindCopyIpChips();
setReadProgress();
});