|
| 1 | +import json |
| 2 | + |
1 | 3 | import reflex as rx |
2 | 4 |
|
3 | 5 | from pcweb.constants import REFLEX_DOMAIN, REFLEX_DOMAIN_URL, TWITTER_CREATOR |
@@ -105,3 +107,79 @@ def create_meta_tags( |
105 | 107 | image=image_url, |
106 | 108 | url=page_url, |
107 | 109 | ) |
| 110 | + |
| 111 | + |
| 112 | +def _normalize_image_url(image: str) -> str: |
| 113 | + """Ensure image path is a full URL.""" |
| 114 | + if image and not image.startswith(("http://", "https://")): |
| 115 | + return f"https://reflex.dev{'' if image.startswith('/') else '/'}{image}" |
| 116 | + return image |
| 117 | + |
| 118 | + |
| 119 | +def blog_jsonld( |
| 120 | + title: str, |
| 121 | + description: str, |
| 122 | + author: str, |
| 123 | + date: str, |
| 124 | + image: str, |
| 125 | + url: str, |
| 126 | + faq: list[dict[str, str]] | None = None, |
| 127 | + author_bio: str | None = None, |
| 128 | + updated_at: str | None = None, |
| 129 | +) -> rx.Component: |
| 130 | + """Create a single JSON-LD script tag with @graph for a blog post. |
| 131 | +
|
| 132 | + Always includes a BlogPosting entry. If faq items are provided, |
| 133 | + a FAQPage entry is also added to the graph. |
| 134 | + """ |
| 135 | + author_node: dict = {"@type": "Person", "name": author} |
| 136 | + if author_bio: |
| 137 | + author_node["description"] = author_bio |
| 138 | + |
| 139 | + posting: dict = { |
| 140 | + "@type": "BlogPosting", |
| 141 | + "headline": title, |
| 142 | + "description": description, |
| 143 | + "image": _normalize_image_url(image), |
| 144 | + "datePublished": str(date), |
| 145 | + "author": author_node, |
| 146 | + } |
| 147 | + if updated_at: |
| 148 | + posting["dateModified"] = str(updated_at) |
| 149 | + |
| 150 | + graph: list[dict] = [ |
| 151 | + { |
| 152 | + **posting, |
| 153 | + "publisher": { |
| 154 | + "@type": "Organization", |
| 155 | + "name": "Reflex", |
| 156 | + "url": REFLEX_DOMAIN_URL, |
| 157 | + }, |
| 158 | + "mainEntityOfPage": { |
| 159 | + "@type": "WebPage", |
| 160 | + "@id": url, |
| 161 | + }, |
| 162 | + }, |
| 163 | + ] |
| 164 | + if faq: |
| 165 | + graph.append( |
| 166 | + { |
| 167 | + "@type": "FAQPage", |
| 168 | + "mainEntity": [ |
| 169 | + { |
| 170 | + "@type": "Question", |
| 171 | + "name": item["question"], |
| 172 | + "acceptedAnswer": { |
| 173 | + "@type": "Answer", |
| 174 | + "text": item["answer"], |
| 175 | + }, |
| 176 | + } |
| 177 | + for item in faq |
| 178 | + ], |
| 179 | + } |
| 180 | + ) |
| 181 | + data = { |
| 182 | + "@context": "https://schema.org", |
| 183 | + "@graph": graph, |
| 184 | + } |
| 185 | + return rx.el.script(json.dumps(data), type="application/ld+json") |
0 commit comments