mirror of
https://github.com/benjaminion/upgrading-ethereum-book.git
synced 2026-01-08 06:03:53 -05:00
Build: more thorough processing of spec links
Deals with duplicate anchors in the one-page spec.
This commit is contained in:
@@ -12,6 +12,7 @@ import myAutoLinkHeadings from './integrations/my_autolink_headings';
|
|||||||
import mySvgInline from './integrations/my_svg_inline';
|
import mySvgInline from './integrations/my_svg_inline';
|
||||||
import mySearchIndex from './integrations/my_search_index';
|
import mySearchIndex from './integrations/my_search_index';
|
||||||
import myAddTooltips from './integrations/my_add_tooltips';
|
import myAddTooltips from './integrations/my_add_tooltips';
|
||||||
|
import mySpecLinks from './integrations/my_spec_links';
|
||||||
import myFixupLinks from './integrations/my_fixup_links';
|
import myFixupLinks from './integrations/my_fixup_links';
|
||||||
import myCleanupHtml from './integrations/my_cleanup_html';
|
import myCleanupHtml from './integrations/my_cleanup_html';
|
||||||
import myHtaccess from './integrations/my_htaccess';
|
import myHtaccess from './integrations/my_htaccess';
|
||||||
@@ -27,6 +28,7 @@ export default defineConfig({
|
|||||||
mySvgInline({ filePath: 'src/', cachePath: './.svg_cache/' }),
|
mySvgInline({ filePath: 'src/', cachePath: './.svg_cache/' }),
|
||||||
mySearchIndex(searchOptions),
|
mySearchIndex(searchOptions),
|
||||||
myAddTooltips({ constantsFile: 'src/include/constants.json' }),
|
myAddTooltips({ constantsFile: 'src/include/constants.json' }),
|
||||||
|
mySpecLinks(),
|
||||||
myFixupLinks(),
|
myFixupLinks(),
|
||||||
myCleanupHtml(),
|
myCleanupHtml(),
|
||||||
myHtaccess(),
|
myHtaccess(),
|
||||||
|
|||||||
108
integrations/my_spec_links.js
Normal file
108
integrations/my_spec_links.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { visit, CONTINUE, SKIP } from 'unist-util-visit';
|
||||||
|
import GithubSlugger from 'github-slugger';
|
||||||
|
|
||||||
|
// Fix up internal links in the one-page annotated spec.
|
||||||
|
// Must be configured to run after myAutoLinkHeadings, and before myFixupLinks and myCleanupHtml.
|
||||||
|
// The one-page spec is excluded from search index processing, so no need to worry about that.
|
||||||
|
|
||||||
|
// Ignore SVGs and anything to do with footnotes (which should be fine without help)
|
||||||
|
function isIgnoredElement(node) {
|
||||||
|
return (
|
||||||
|
node.tagName === 'svg' ||
|
||||||
|
(node.tagName === 'a' && node.properties.dataFootnoteRef !== undefined) ||
|
||||||
|
(node.tagName === 'section' && node.properties.dataFootnotes !== undefined)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only headings and <a id="..."> are of interest
|
||||||
|
function isTargetElement(node) {
|
||||||
|
return (
|
||||||
|
node.properties?.id !== undefined &&
|
||||||
|
['a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(node.tagName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We look at all links, but '/part3/' should point to the main book, not the one-page spec
|
||||||
|
function isLinkElement(node) {
|
||||||
|
return (
|
||||||
|
node.tagName === 'a' &&
|
||||||
|
node.properties?.href !== undefined &&
|
||||||
|
node.properties.href !== '/part3/'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// New pages are indicated by comments attached to certain headings in the Markdown
|
||||||
|
function isNewPage(node) {
|
||||||
|
return (
|
||||||
|
['h1', 'h2', 'h3'].includes(node.tagName) &&
|
||||||
|
node.children[node.children.length - 1].type === 'comment'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function specLinks({ logger }) {
|
||||||
|
return function (tree, file) {
|
||||||
|
if (file.data.astro.frontmatter.path !== '/annotated-spec/') return;
|
||||||
|
|
||||||
|
// We re-slug the slug to handle duplicates
|
||||||
|
const slugger = new GithubSlugger();
|
||||||
|
|
||||||
|
// Pass 1: Build a map of pages and ids/slugs
|
||||||
|
const map = {};
|
||||||
|
let page = '';
|
||||||
|
visit(tree, 'element', (node) => {
|
||||||
|
if (isIgnoredElement(node)) return SKIP;
|
||||||
|
|
||||||
|
if (isTargetElement(node)) {
|
||||||
|
const oldSlug = node.properties.id;
|
||||||
|
const newSlug = slugger.slug(oldSlug);
|
||||||
|
node.properties.id = newSlug;
|
||||||
|
if (isNewPage(node)) {
|
||||||
|
page = node.children[node.children.length - 1].value.trim();
|
||||||
|
map[page] = newSlug;
|
||||||
|
}
|
||||||
|
page || logger.warn('Page is not set when processing ' + oldSlug);
|
||||||
|
map[page + '#' + oldSlug] = newSlug;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pass 2: Adjust hrefs - we need two passes in case any references are forward-looking
|
||||||
|
page = '';
|
||||||
|
visit(tree, 'element', (node) => {
|
||||||
|
if (isIgnoredElement(node)) return SKIP;
|
||||||
|
|
||||||
|
if (isNewPage(node)) {
|
||||||
|
page = node.children[node.children.length - 1].value.trim();
|
||||||
|
return CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLinkElement(node)) {
|
||||||
|
const oldHref = node.properties.href;
|
||||||
|
let newHref;
|
||||||
|
if (oldHref.startsWith('#')) {
|
||||||
|
newHref = map[page + oldHref];
|
||||||
|
} else if (oldHref.startsWith('/part3/')) {
|
||||||
|
newHref = map[oldHref];
|
||||||
|
} else {
|
||||||
|
return CONTINUE;
|
||||||
|
}
|
||||||
|
newHref || logger.warn('Failed to fix spec link: ' + oldHref);
|
||||||
|
node.properties.href = '#' + newHref;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
name: 'mySpecLinks',
|
||||||
|
hooks: {
|
||||||
|
'astro:config:setup': ({ updateConfig, logger }) => {
|
||||||
|
updateConfig({
|
||||||
|
markdown: {
|
||||||
|
rehypePlugins: [[specLinks, { logger: logger }]],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ const reEnd = /^# .*<!-- \/part4\/ -->$/dm;
|
|||||||
const preamble =
|
const preamble =
|
||||||
'# One Page Annotated Spec\n\n' +
|
'# One Page Annotated Spec\n\n' +
|
||||||
'**Note:** This page is automatically generated from the chapters ' +
|
'**Note:** This page is automatically generated from the chapters ' +
|
||||||
'in [Part 3](/part3/). You may find that some internal links are broken.';
|
'in [Part 3](/part3/).';
|
||||||
|
|
||||||
export function specLoader(fileName) {
|
export function specLoader(fileName) {
|
||||||
return {
|
return {
|
||||||
@@ -37,13 +37,10 @@ export function specLoader(fileName) {
|
|||||||
|
|
||||||
let markdown = preamble;
|
let markdown = preamble;
|
||||||
if (startMatch && endMatch) {
|
if (startMatch && endMatch) {
|
||||||
// Remove the title - we will replace it
|
// Extract the markdown, removing the title and replacing it with the preamble
|
||||||
const start = startMatch.indices[0][1] + 1;
|
const start = startMatch.indices[0][1] + 1;
|
||||||
const end = endMatch.indices[0][0];
|
const end = endMatch.indices[0][0];
|
||||||
// Extract the spec, add the preamble, and rewrite internal links
|
markdown += allMarkdown.substring(start, end);
|
||||||
markdown += allMarkdown
|
|
||||||
.substring(start, end)
|
|
||||||
.replace(/]\(\/part3\/[^#)]*/g, '](');
|
|
||||||
} else {
|
} else {
|
||||||
logger.warn('Creating empty annotated spec');
|
logger.warn('Creating empty annotated spec');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user