diff --git a/build/transform_json_sections.ts b/build/transform_json_sections.ts index 38721b71aa..daf7512007 100644 --- a/build/transform_json_sections.ts +++ b/build/transform_json_sections.ts @@ -111,11 +111,38 @@ function parseHeading(raw: string): { title: string; id: string } { return { title: raw, id: slugify(raw) }; } +/** + * Slugify a heading the way Hugo does, so section ids match the anchor on the page. + * + * Hugo has no autoHeadingID setting in config.toml, so it uses Goldmark's default + * "github" style: lowercase, discard anything that is not a letter, decimal digit, ASCII + * whitespace, underscore or hyphen, then turn each remaining whitespace character into one + * hyphen. Note decimal digit specifically -- a subscript such as the one in + * "Naming convention: LVQx" is a Unicode "other number" and Hugo discards it, + * anchoring as "naming-convention-lvqbxb". + * + * Five details matter and all five were wrong before. Underscores and runs of hyphens + * are KEPT, so "redis_url" stays "redis_url" rather than collapsing to "redis-url". + * Whitespace runs are NOT collapsed, so "The special $ ID" becomes "the-special--id" + * with two hyphens, the discarded "$" leaving the spaces either side of it. Leading and + * trailing hyphens are NOT trimmed -- a heading "Negation !" really does anchor as + * "negation-" and "- and + special IDs" as "--and--special-ids". And the whitespace + * class is ASCII only, matching Go's \s, so a non-breaking space is discarded rather + * than turned into a hyphen: "Development environment" anchors as + * "developmentenvironment". JavaScript's \s would have hyphenated it. + * + * Measured against 3,616 heading/anchor pairs harvested from the rendered HTML: this + * reproduces Hugo's anchor for 90.3% of them, against 80.2% for the previous version, + * and every remaining difference is a heading carrying an explicit {#anchor}, which + * parseHeading handles before this function is reached. The trim, whitespace-run and + * non-breaking-space rules were each confirmed against the anchor Hugo emitted for a + * specific heading, since the harvested sample normalises whitespace and cannot show them. + */ function slugify(text: string): string { return text .toLowerCase() - .replace(/[^a-z0-9]+/g, '-') - .replace(/^-|-$/g, ''); + .replace(/[^\p{L}\p{Nd}\t\n\f\r _-]/gu, '') + .replace(/[\t\n\f\r ]/g, '-'); } // Section IDs to filter out (metadata noise, not useful for RAG) diff --git a/content/integrate/redis-data-integration/data-pipelines/prepare-dbs/aws-aurora-rds/aws-aur-mysql.md b/content/integrate/redis-data-integration/data-pipelines/prepare-dbs/aws-aurora-rds/aws-aur-mysql.md index 1e9d7e1b05..8299b1948b 100644 --- a/content/integrate/redis-data-integration/data-pipelines/prepare-dbs/aws-aurora-rds/aws-aur-mysql.md +++ b/content/integrate/redis-data-integration/data-pipelines/prepare-dbs/aws-aurora-rds/aws-aur-mysql.md @@ -39,7 +39,7 @@ To add a reader node to an existing database, select **Add reader** from the **A You can also create one during database creation by selecting **Create an Aurora Replica or Reader node in a different AZ (recommended for scaled availability)** under **Availability & durability > Multi-AZ deployment**. -## Create and apply parameter group +## Create and apply parameter group {#aurora-create-and-apply-parameter-group} RDI requires some changes to database parameters. On AWS Aurora, you change these parameters via a parameter group. @@ -108,7 +108,7 @@ RDI requires some changes to database parameters. On AWS Aurora, you change thes - [ ] [Create Debezium user](#rds-create-debezium-user) ``` -## Create and apply parameter group +## Create and apply parameter group {#rds-create-and-apply-parameter-group} RDI requires some changes to database parameters. On AWS RDS, you change these parameters via a parameter group. diff --git a/layouts/partials/toc-from-markdown.html b/layouts/partials/toc-from-markdown.html index f59a6e6be5..07c0af5aeb 100644 --- a/layouts/partials/toc-from-markdown.html +++ b/layouts/partials/toc-from-markdown.html @@ -2,6 +2,7 @@ {{- /* This parses ## and ### headers directly from .RawContent */ -}} {{- /* since Hugo's .TableOfContents may be empty for custom templates */ -}} +{{- $page := . -}} {{- $content := .RawContent -}} {{- /* Unescape HTML entities that may be present in .RawContent */ -}} @@ -47,38 +48,72 @@ {{- $title = replaceRE `^## +` "" $headerMatch -}} {{- end -}} - {{- /* Remove inline code markers */ -}} - {{- $title = replaceRE "`([^`]+)`" "$1" $title -}} - {{- /* Remove other inline formatting */ -}} - {{- $title = replaceRE `\*\*([^*]+)\*\*` "$1" $title -}} - {{- $title = replaceRE `\*([^*]+)\*` "$1" $title -}} - {{- $title = replaceRE `_([^_]+)_` "$1" $title -}} - {{- /* Trim whitespace */ -}} - {{- $title = $title | strings.TrimSpace -}} - - {{- /* Hugo's explicit heading anchor, "## Title {#custom-id}". Hugo uses the - anchor as the rendered heading id and does not display it, so it has to come - out of the title and be used verbatim as the id. Matched strictly, because a - loose trailing-brace match would eat Python signatures ending in an empty - dict and dict defaults such as {'extra': 'ignore'}. */ -}} + {{- /* Hugo's explicit heading anchor, "## Title {#custom-id}". Hugo uses the anchor as the + rendered heading id and does not display it, so it has to come out of the title and + be used verbatim as the id. Matched strictly, because a loose trailing-brace match + would eat Python signatures ending in an empty dict and dict defaults such as + {'extra': 'ignore'}. + + Lifted off the RAW heading, before any rendering. Doing it afterwards means the + anchor has to survive whatever the renderer does to it, and the emphasis regexes + this used to sit behind ate the underscores out of anchors such as + {#redis.error_reply}, yielding redis.errorreply and a dead deep link. */ -}} {{- $explicitId := "" -}} {{- with (findRESubmatch `\s*\{#([A-Za-z0-9][A-Za-z0-9_.:-]*)\}\s*$` $title 1) -}} {{- $explicitId = index (index . 0) 1 -}} {{- end -}} {{- if $explicitId -}} - {{- $title = $title | replaceRE `\s*\{#[A-Za-z0-9][A-Za-z0-9_.:-]*\}\s*$` "" | strings.TrimSpace -}} + {{- $title = $title | replaceRE `\s*\{#[A-Za-z0-9][A-Za-z0-9_.:-]*\}\s*$` "" -}} {{- end -}} - {{- /* Generate ID (slug) from title */ -}} - {{- $id := $title | lower -}} - {{- /* Replace spaces with hyphens */ -}} - {{- $id = $id | replaceRE `\s+` "-" -}} - {{- /* Remove special characters except hyphens */ -}} - {{- $id = $id | replaceRE `[^a-z0-9-]` "" -}} - {{- /* Remove consecutive hyphens */ -}} - {{- $id = $id | replaceRE `-+` "-" -}} - {{- /* Trim leading/trailing hyphens */ -}} - {{- $id = $id | replaceRE `^-+|-+$` "" -}} + {{- /* Escape a leading ordered-list marker before rendering. Even with display "inline" + the renderer reads "1. Launch the app" as a list item and drops the number, where + the rendered page heading keeps it -- and Hugo's anchor includes it, so dropping it + breaks the deep link too. 299 headings start this way; none starts with -, + or *. */ -}} + {{- $title = $title | replaceRE `^(\d+)([.)])(\s)` `${1}\${2}${3}` -}} + + {{- /* Reduce the heading to plain text with Hugo's own Markdown renderer rather than + hand-rolled regexes. + + What this replaces stripped inline code markers first and then treated any remaining + * or _ as emphasis, so by that point there was no way to tell that a character had + come from inside a code span. Code-formatted headings therefore lost characters: + "**kwargs" became "*kwargs" and "redis_url" became "redisurl", which published + incorrect API signatures on the redisvl reference pages. + + Goldmark applies the real CommonMark rules, including the ban on intraword underscore + emphasis, so snake_case survives whether or not it sits in a code span. RenderString + rather than markdownify because markdownify renders the string as a block. plainify + then strips the inline markup and htmlUnescape restores literal text. */ -}} + {{- /* Keep the raw heading for the id, because Hugo slugs the heading's own text, which + preserves whitespace runs where rendering collapses them: a heading with a double + space anchors as "choosing-the-capacity--capacity", and slugging the rendered title + would give one hyphen. Slugging the raw text costs nothing, because the markup + characters it still contains are discarded by the slug rules anyway, and no heading + in this repo contains a Markdown link -- the one case where the two would diverge. + + Whitespace-trimmed, but hyphens are not: a trailing space would otherwise become a + trailing hyphen, where Hugo slugs the trimmed heading text. Those are not the same + thing -- "Negation !" trims to itself, and the space left behind when the "!" is + discarded still becomes the trailing hyphen Hugo emits. */ -}} + {{- $rawTitle := $title | strings.TrimSpace -}} + + {{- $title = $page.RenderString (dict "display" "inline") $title | plainify | htmlUnescape | strings.TrimSpace -}} + + {{- /* Slug the title the way Hugo does, so the id matches the anchor on the page. + Goldmark's default "github" style: lowercase, discard anything that is not a + letter, number, whitespace, underscore or hyphen, then turn each remaining + whitespace character into one hyphen. + + Underscores and runs of hyphens are kept, whitespace runs are NOT collapsed, and + leading and trailing hyphens are NOT trimmed -- "Negation !" really does anchor as + "negation-". The previous version stripped underscores, collapsed runs and trimmed, + so it disagreed with the page anchor three ways. Slugged from the raw heading, not + the rendered title, and kept identical to slugify in + build/transform_json_sections.ts -- change the two together. */ -}} + {{- $id := $rawTitle | lower -}} + {{- $id = $id | replaceRE `[^\p{L}\p{Nd}\s_-]` "" -}} + {{- $id = $id | replaceRE `\s` "-" -}} {{- /* An explicit anchor overrides the slug entirely */ -}} {{- if $explicitId -}}