diff --git a/layouts/glossary/single.html b/layouts/glossary/single.html
index 12f797527b0..9e97836b6c6 100644
--- a/layouts/glossary/single.html
+++ b/layouts/glossary/single.html
@@ -171,6 +171,7 @@
{{ range $index, $term := . }}
{{ $term_str := printf "%s" $term }}
{{ $term_clean := replace $term_str "\u00a0" " " }}
+ {{ $term_clean = replaceRE `[*\\]` "" $term_clean }}
{{ $term_clean = replaceRE "\\s*\\(.*\\)" "" $term_clean }}
{{ $term_clean = replace $term_clean " " "_" }}
{{ $term_clean = replaceRE "[^a-zA-Z0-9_]" "_" $term_clean }}
@@ -182,9 +183,9 @@
{{ end }}
{{ if $term_page }}
{{ $term_url := $term_page.RelPermalink }}
- {{ $related_links = $related_links | append (printf "%s" $term_url $term) }}
+ {{ $related_links = $related_links | append (printf "%s" $term_url ($term_str | htmlEscape | markdownify)) }}
{{ else }}
- {{ $related_links = $related_links | append $term }}
+ {{ $related_links = $related_links | append ($term_str | htmlEscape | markdownify | string) }}
{{ end }}
{{ end }}
{{ $related_links_string := delimit $related_links ", " | safeHTML }}
diff --git a/layouts/partials/doors/term_title.html b/layouts/partials/doors/term_title.html
index b714e0b5815..e2bf277e63a 100644
--- a/layouts/partials/doors/term_title.html
+++ b/layouts/partials/doors/term_title.html
@@ -1,4 +1,6 @@
-{{/* A glossary term's title. Translated terms end in the English title in square brackets, which
- must stay left to right inside an Arabic heading, or its brackets and words change places. */}}
-{{ $out := replaceRE `^(.*) \[(.*)\]$` `$1 [$2]` (. | htmlEscape) | safeHTML }}
+{{/* A glossary term's title. Titles may carry inline Markdown, e.g. the italic p in "Research
+ Contribution Metric (*p*)"; the title is escaped first so it cannot inject HTML. Translated terms end
+ in the English title in square brackets, which must stay left to right inside an Arabic heading,
+ or its brackets and words change places. */}}
+{{ $out := replaceRE `^(.*) \[(.*)\]$` `$1 [$2]` (. | htmlEscape | markdownify | string) | safeHTML }}
{{ return $out }}
diff --git a/layouts/partials/functions/plain_title.html b/layouts/partials/functions/plain_title.html
new file mode 100644
index 00000000000..00bfece6cd1
--- /dev/null
+++ b/layouts/partials/functions/plain_title.html
@@ -0,0 +1,6 @@
+{{/* Plain text of a page's title (or link title) for
, og:title, breadcrumbs and JSON-LD.
+ Input: dict "page" and "text". Glossary titles may carry inline Markdown (an italic *p*),
+ which is rendered and then stripped; other pages' text passes through unchanged. */}}
+{{ $text := .text }}
+{{ if eq .page.Type "glossary" }}{{ $text = $text | htmlEscape | markdownify | plainify | htmlUnescape }}{{ end }}
+{{ return $text }}
diff --git a/layouts/partials/functions/resource_metadata.html b/layouts/partials/functions/resource_metadata.html
index 50d1364abbb..2ff3bd37fbf 100644
--- a/layouts/partials/functions/resource_metadata.html
+++ b/layouts/partials/functions/resource_metadata.html
@@ -2,6 +2,12 @@
pagination on other lists: their templates choose their own collections. */}}
{{ $description := partialCached "functions/page_description.html" . .RelPermalink }}
{{ $metadata := dict "url" .Permalink "title" .Title "breadcrumb_title" (.LinkTitle | default .Title) "description" $description "pages" .Pages }}
+{{ if eq .Type "glossary" }}
+ {{ $metadata = merge $metadata (dict
+ "title" (partial "functions/plain_title.html" (dict "page" . "text" .Title))
+ "breadcrumb_title" (partial "functions/plain_title.html" (dict "page" . "text" (.LinkTitle | default .Title)))
+ ) }}
+{{ end }}
{{ if and .IsSection (eq .Section "resources") }}
{{ $index := partialCached "doors/resource_index" . "resources" }}
{{ $pager := .Paginate $index.pages 24 }}
diff --git a/layouts/partials/jsonld/main.html b/layouts/partials/jsonld/main.html
index 5db5310bd3d..ac59dd75124 100644
--- a/layouts/partials/jsonld/main.html
+++ b/layouts/partials/jsonld/main.html
@@ -58,7 +58,7 @@
"@type" "ListItem"
"position" (add $i 1)
"url" $item.Permalink
- "name" $item.LinkTitle
+ "name" (partial "functions/plain_title.html" (dict "page" $item "text" $item.LinkTitle))
) }}
{{ end }}
diff --git a/layouts/partials/structured-data/glossary-term.html b/layouts/partials/structured-data/glossary-term.html
index 69098748aa2..393c3d0c731 100644
--- a/layouts/partials/structured-data/glossary-term.html
+++ b/layouts/partials/structured-data/glossary-term.html
@@ -18,7 +18,7 @@
{{- $term := dict
"@context" "https://schema.org"
"@type" "DefinedTerm"
- "name" .Title
+ "name" (partial "functions/plain_title.html" (dict "page" . "text" .Title))
"description" $desc
"url" .Permalink
"inLanguage" $lang
diff --git a/scripts/check_seo_html.py b/scripts/check_seo_html.py
index fedb1087569..6bb657a68c6 100644
--- a/scripts/check_seo_html.py
+++ b/scripts/check_seo_html.py
@@ -39,6 +39,7 @@
16. Every glossary term link points to an existing English glossary page (#227).
17. Every link in a visible breadcrumb trail (nav.crumbs) has a non-empty href,
so a section that is not rendered cannot leave an empty link in the trail.
+ 18. No glossary page's or shows Markdown emphasis as typed, e.g. "(*p*)".
Report-only sections (never fail): pages with no , the
site-wide share of images that carry both width and height, pages whose
@@ -262,6 +263,18 @@ def check_glossary_links(pages):
return failures
+# Paired asterisks around text: Markdown emphasis shown as typed. Asterisks inside a word
+# ("G*Power", "P*Hack") are not delimiters.
+LITERAL_EMPHASIS = re.compile(r"(?Search
': check_no_search_heading(pages),
+ "18. glossary titles show no Markdown asterisks": check_glossary_title_markdown(pages),
"2. homepage free of mermaid/highlight.js assets": check_homepage_assets(home),
"4. every
has an alt attribute": check_img_alt(pages),
"5. every homepage
has width and height": check_homepage_img_dims(home),
diff --git a/scripts/rebrand/allowed_changes.txt b/scripts/rebrand/allowed_changes.txt
index 44fadc63fef..14fc276c728 100644
--- a/scripts/rebrand/allowed_changes.txt
+++ b/scripts/rebrand/allowed_changes.txt
@@ -30,6 +30,12 @@ post/*/featured_hu.jpg # The page's resized image is now WebP, so its
post/*/featured_hu.png # The same
ai-metascience/featured_hu.png # The same
coc/index.html # Its title was empty (" | FORRT"), and it now says Code of Conduct
+glossary/english/research_contribution_metric__p_/index.html # #1085: glossary titles render their Markdown, so the , og:title and JSON-LD name read "(p)" instead of "(*p*)"
+glossary/german/p/index.html # #1085: glossary titles render their Markdown, so the , og:title and JSON-LD name read "(p)" instead of "(*p*)"
+glossary/arabic/مقياس_المساهمة_البحثي_ة_نشر/index.html # #1085: glossary titles render their Markdown, so the , og:title and JSON-LD name read "(p)" instead of "(*p*)"
+glossary/arabic/p_مقياس_المساهمة_البحثي_ة_نشر/index.html # #1085: the same Arabic page under the older slug that the content committed on this branch still uses
+glossary/chinese/研究贡献指标_p_/index.html # #1085: glossary titles render their Markdown, so the , og:title and JSON-LD name read "(p)" instead of "(*p*)"
+glossary/turkish/bilimsel_katkı_olcutu_p_/index.html # #1085: glossary titles render their Markdown, so the , og:title and JSON-LD name read "(p)" instead of "(*p*)"
about/charity/gep/index.html # #973: redirect from the address printed in the GEP PDF to /about/charity/, not a page
data/glossary-terms.*.json # Build-time English glossary lookup, fetched on first term interaction (#227); not a page
cv/index.html # Retitled as a CV template in the content review (#1079). Exempts all its SEO tags: remove once main has the new title
diff --git a/scripts/tests/test_check_seo_html.py b/scripts/tests/test_check_seo_html.py
index 23b8ce5e5ff..e625ed29865 100644
--- a/scripts/tests/test_check_seo_html.py
+++ b/scripts/tests/test_check_seo_html.py
@@ -192,6 +192,24 @@ def test_other_links_are_ignored(self):
self.assertEqual(self.check('Link'), [])
+class GlossaryTitleMarkdownCheck(unittest.TestCase):
+ def check(self, title, h1, path="glossary/english/term/index.html"):
+ html = "%s%s
" % (title, h1)
+ return seo.check_glossary_title_markdown([page_from_html(html, path)])
+
+ def test_rendered_emphasis_and_lone_asterisk_pass(self):
+ self.assertEqual(self.check("Research Contribution Metric (p) | FORRT",
+ "Research Contribution Metric (p)"), [])
+ self.assertEqual(self.check("G*Power | FORRT", "G*Power"), [])
+ self.assertEqual(self.check("G*Power and P*Hack | FORRT", "G*Power and P*Hack"), [])
+
+ def test_literal_emphasis_fails_on_glossary_pages_only(self):
+ self.assertEqual(len(self.check("Research Contribution Metric (*p*) | FORRT", "x")), 1)
+ self.assertEqual(len(self.check("x", "Research Contribution Metric (*p*)")), 1)
+ self.assertEqual(len(self.check("x", "*p*-value")), 1)
+ self.assertEqual(self.check("x", "*p*", path="post/x/index.html"), [])
+
+
class GlossaryLinkCheck(unittest.TestCase):
def check_links(self, hrefs, base="https://forrt.org/"):
with tempfile.TemporaryDirectory() as public:
@@ -227,3 +245,4 @@ def test_crumb_links_need_a_target(self):
if __name__ == "__main__":
unittest.main()
+