blob: 97c26ca8d4937f0077b15cd1e5935d07a2281830 [file]
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
"use strict";
// Single source of truth for heading anchor ids: markdown_render.js writes the
// <a name="..."> on the page and gen_search_index.js deep-links to it. Both
// must slugify the same input -- the heading's inline-rendered HTML, e.g.
// "Using <code>foo</code>" -- or a search result's #fragment won't match the
// page.
function headingAnchor(renderedText, level) {
// An explicit {#anchor} wins at any level.
const explicit = /{#([\w-_.]+)}/.exec(renderedText);
if (explicit) {
return explicit[1];
}
// Otherwise infer the anchor from the text, but only for h2 and h3 (the only
// levels the right-hand-side TOC links to).
if (level >= 2 && level <= 3) {
return renderedText
// Drop tag attributes, keep tag names. markdown_render.js rewrites
// link/image hrefs (e.g. to source.chromium.org) but gen_search_index.js
// doesn't, so slugifying a raw href would make the two anchors disagree.
// Attribute-less tags like <code> are untouched, so ids stay stable.
.replace(/<([a-z][a-z0-9]*)\b[^>]*>/gi, "<$1>")
.toLowerCase()
.replace(/[^\w]+/g, "-")
.replace(/[-]+/g, "-"); // Drop consecutive '-'s.
}
return "";
}
module.exports = {headingAnchor};