Soichiro Miki
6 years ago
7 changed files with 181 additions and 13 deletions
@ -0,0 +1,30 @@ |
|||
let offsetY = 0; |
|||
|
|||
const getTargetOffset = hash => { |
|||
const id = window.decodeURI(hash.replace(`#`, ``)); |
|||
if (id !== ``) { |
|||
const element = document.getElementById(id); |
|||
if (element) { |
|||
return element.offsetTop - offsetY; |
|||
} |
|||
} |
|||
return null; |
|||
}; |
|||
|
|||
exports.onInitialClientRender = (_, pluginOptions) => { |
|||
if (pluginOptions.offsetY) { |
|||
offsetY = pluginOptions.offsetY; |
|||
} |
|||
|
|||
requestAnimationFrame(() => { |
|||
const offset = getTargetOffset(window.location.hash); |
|||
if (offset !== null) { |
|||
window.scrollTo(0, offset); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
exports.shouldUpdateScroll = ({routerProps: {location}}) => { |
|||
const offset = getTargetOffset(location.hash); |
|||
return offset !== null ? [0, offset] : true; |
|||
}; |
@ -0,0 +1,76 @@ |
|||
const React = require(`react`); |
|||
|
|||
const pluginDefaults = { |
|||
className: `anchor`, |
|||
icon: true, |
|||
offsetY: 0, |
|||
}; |
|||
|
|||
exports.onRenderBody = ({setHeadComponents}, pluginOptions) => { |
|||
const {className, icon, offsetY} = Object.assign( |
|||
pluginDefaults, |
|||
pluginOptions, |
|||
); |
|||
|
|||
const styles = ` |
|||
.${className} { |
|||
float: left; |
|||
padding-right: 4px; |
|||
margin-left: -20px; |
|||
} |
|||
h1 .${className} svg, |
|||
h2 .${className} svg, |
|||
h3 .${className} svg, |
|||
h4 .${className} svg, |
|||
h5 .${className} svg, |
|||
h6 .${className} svg { |
|||
visibility: hidden; |
|||
} |
|||
h1:hover .${className} svg, |
|||
h2:hover .${className} svg, |
|||
h3:hover .${className} svg, |
|||
h4:hover .${className} svg, |
|||
h5:hover .${className} svg, |
|||
h6:hover .${className} svg, |
|||
h1 .${className}:focus svg, |
|||
h2 .${className}:focus svg, |
|||
h3 .${className}:focus svg, |
|||
h4 .${className}:focus svg, |
|||
h5 .${className}:focus svg, |
|||
h6 .${className}:focus svg { |
|||
visibility: visible; |
|||
} |
|||
`;
|
|||
|
|||
const script = ` |
|||
document.addEventListener("DOMContentLoaded", function(event) { |
|||
var hash = window.decodeURI(location.hash.replace('#', '')) |
|||
if (hash !== '') { |
|||
var element = document.getElementById(hash) |
|||
if (element) { |
|||
var offset = element.offsetTop |
|||
// Wait for the browser to finish rendering before scrolling.
|
|||
setTimeout((function() { |
|||
window.scrollTo(0, offset - ${offsetY}) |
|||
}), 0) |
|||
} |
|||
} |
|||
}) |
|||
`;
|
|||
|
|||
const style = icon ? ( |
|||
<style key={`gatsby-remark-autolink-headers-style`} type="text/css"> |
|||
{styles} |
|||
</style> |
|||
) : ( |
|||
undefined |
|||
); |
|||
|
|||
return setHeadComponents([ |
|||
style, |
|||
<script |
|||
key={`gatsby-remark-autolink-headers-script`} |
|||
dangerouslySetInnerHTML={{__html: script}} |
|||
/>, |
|||
]); |
|||
}; |
@ -0,0 +1,60 @@ |
|||
/*! |
|||
* Based on 'gatsby-remark-autolink-headers' |
|||
* Original Author: Kyle Mathews <mathews.kyle@gmail.com> |
|||
* Copyright (c) 2015 Gatsbyjs |
|||
*/ |
|||
|
|||
const toString = require('mdast-util-to-string'); |
|||
const visit = require('unist-util-visit'); |
|||
const slugs = require('github-slugger')(); |
|||
|
|||
function patch(context, key, value) { |
|||
if (!context[key]) { |
|||
context[key] = value; |
|||
} |
|||
|
|||
return context[key]; |
|||
} |
|||
|
|||
const svgIcon = `<svg aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>`; |
|||
|
|||
module.exports = ( |
|||
{markdownAST}, |
|||
{icon = svgIcon, className = `anchor`, maintainCase = false}, |
|||
) => { |
|||
slugs.reset(); |
|||
|
|||
visit(markdownAST, 'heading', node => { |
|||
const id = slugs.slug(toString(node), maintainCase); |
|||
const data = patch(node, 'data', {}); |
|||
|
|||
patch(data, 'id', id); |
|||
patch(data, 'htmlAttributes', {}); |
|||
patch(data, 'hProperties', {}); |
|||
patch(data.htmlAttributes, 'id', id); |
|||
patch(data.hProperties, 'id', id); |
|||
|
|||
if (icon !== false) { |
|||
node.children.unshift({ |
|||
type: 'link', |
|||
url: `#${id}`, |
|||
title: null, |
|||
data: { |
|||
hProperties: { |
|||
'aria-hidden': true, |
|||
class: className, |
|||
}, |
|||
hChildren: [ |
|||
{ |
|||
type: 'raw', |
|||
// The Octicon link icon is the default. But users can set their own icon via the "icon" option.
|
|||
value: icon, |
|||
}, |
|||
], |
|||
}, |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
return markdownAST; |
|||
}; |
@ -0,0 +1,4 @@ |
|||
{ |
|||
"name": "gatsby-remark-header-custom-ids", |
|||
"version": "0.0.1" |
|||
} |
Loading…
Reference in new issue