diff --git a/web_src/js/index.js b/web_src/js/index.js index 37cb2a3988..544c2457c4 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -6,13 +6,13 @@ import './publicpath.js'; import Vue from 'vue'; import 'jquery.are-you-sure'; import './vendor/semanticdropdown.js'; -import {svg} from './utils.js'; import initContextPopups from './features/contextpopup.js'; import initGitGraph from './features/gitgraph.js'; import initClipboard from './features/clipboard.js'; import initUserHeatmap from './features/userheatmap.js'; import initServiceWorker from './features/serviceworker.js'; +import initMarkdownAnchors from './markdown/anchors.js'; import attachTribute from './features/tribute.js'; import createDropzone from './features/dropzone.js'; import initTableSort from './features/tablesort.js'; @@ -2360,15 +2360,6 @@ $(document).ready(async () => { }); }); - // Set anchor. - $('.markdown').each(function () { - $(this).find('h1, h2, h3, h4, h5, h6').each(function () { - let node = $(this); - node = node.wrap('
'); - node.append(`${svg('octicon-link', 16)}`); - }); - }); - $('.issue-checkbox').on('click', () => { const numChecked = $('.issue-checkbox').children('input:checked').length; if (numChecked > 0) { @@ -2426,6 +2417,7 @@ $(document).ready(async () => { searchTeams(); searchRepositories(); + initMarkdownAnchors(); initCommentForm(); initInstall(); initRepository(); diff --git a/web_src/js/markdown/anchors.js b/web_src/js/markdown/anchors.js new file mode 100644 index 0000000000..766fa34a7a --- /dev/null +++ b/web_src/js/markdown/anchors.js @@ -0,0 +1,32 @@ +import {svg} from '../utils.js'; + +const headingSelector = '.markdown h1, .markdown h2, .markdown h3, .markdown h4, .markdown h5, .markdown h6'; + +function scrollToAnchor() { + if (document.querySelector(':target')) return; + if (!window.location.hash || window.location.hash.length <= 1) return; + const id = window.location.hash.substring(1); + const el = document.getElementById(`user-content-${id}`); + if (el) { + el.scrollIntoView(); + } else if (id.startsWith('user-content-')) { // compat for links with old 'user-content-' prefixed hashes + const el = document.getElementById(id); + if (el) el.scrollIntoView(); + } +} + +export default function initMarkdownAnchors() { + if (!document.querySelector('.markdown')) return; + + for (const heading of document.querySelectorAll(headingSelector)) { + const originalId = heading.id.replace(/^user-content-/, ''); + const a = document.createElement('a'); + a.classList.add('anchor'); + a.setAttribute('href', `#${encodeURIComponent(originalId)}`); + a.innerHTML = svg('octicon-link', 16); + heading.prepend(a); + } + + scrollToAnchor(); + window.addEventListener('hashchange', scrollToAnchor); +} diff --git a/web_src/less/_markdown.less b/web_src/less/_markdown.less index 420f24cbbe..e2ac664bec 100644 --- a/web_src/less/_markdown.less +++ b/web_src/less/_markdown.less @@ -30,26 +30,51 @@ } .anchor { - position: absolute; - top: 0; - left: 0; - display: block; - padding-right: 6px; - padding-left: 30px; - margin-left: -30px; + padding-right: 4px; + margin-left: -20px; + line-height: 1; + color: inherit; + } + + .anchor .svg { + vertical-align: middle; } .anchor:focus { outline: none; } + h1 .anchor .svg, + h2 .anchor .svg, + h3 .anchor .svg, + h4 .anchor .svg, + h5 .anchor .svg, + h6 .anchor .svg { + visibility: hidden; + } + + h1:hover .anchor .svg, + h2:hover .anchor .svg, + h3:hover .anchor .svg, + h4:hover .anchor .svg, + h5:hover .anchor .svg, + h6:hover .anchor .svg { + visibility: visible; + } + + h2 .anchor .svg, + h3 .anchor .svg, + h4 .anchor .svg { + position: relative; + top: -2px; + } + h1, h2, h3, h4, h5, h6 { - position: relative; margin-top: 1em; margin-bottom: 16px; font-weight: bold; @@ -60,37 +85,6 @@ } } - h1 .octicon-link, - h2 .octicon-link, - h3 .octicon-link, - h4 .octicon-link, - h5 .octicon-link, - h6 .octicon-link { - display: none; - color: #000000; - vertical-align: middle; - } - - h1:hover .anchor, - h2:hover .anchor, - h3:hover .anchor, - h4:hover .anchor, - h5:hover .anchor, - h6:hover .anchor { - padding-left: 8px; - margin-left: -30px; - text-decoration: none; - } - - h1:hover .anchor .octicon-link, - h2:hover .anchor .octicon-link, - h3:hover .anchor .octicon-link, - h4:hover .anchor .octicon-link, - h5:hover .anchor .octicon-link, - h6:hover .anchor .octicon-link { - display: inline-block; - } - h1 tt, h1 code, h2 tt, @@ -113,10 +107,6 @@ border-bottom: 1px solid #eeeeee; } - h1 .anchor { - line-height: 1; - } - h2 { padding-bottom: .3em; font-size: 1.75em; @@ -124,44 +114,24 @@ border-bottom: 1px solid #eeeeee; } - h2 .anchor { - line-height: 1; - } - h3 { font-size: 1.5em; line-height: 1.43; } - h3 .anchor { - line-height: 1.2; - } - h4 { font-size: 1.25em; } - h4 .anchor { - line-height: 1.2; - } - h5 { font-size: 1em; } - h5 .anchor { - line-height: 1.1; - } - h6 { font-size: 1em; color: #777777; } - h6 .anchor { - line-height: 1.1; - } - p, blockquote, ul,