-
-
Notifications
You must be signed in to change notification settings - Fork 34
Add JSON-LD structured data to docs pages #934
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 2 commits
6f1bf1a
2e8968f
9519a3c
8e551a3
4005006
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,135 @@ | ||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * Custom Head component for RocketSim docs | ||||||||||||||||||||||||||
| * Includes proper favicon and meta tags matching the main site | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| import Default from "@astrojs/starlight/components/Head.astro"; | ||||||||||||||||||||||||||
| import PlausibleAnalytics from "@/components/PlausibleAnalytics.astro"; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const baseUrl = "https://www.rocketsim.app"; | ||||||||||||||||||||||||||
| const { entry } = Astro.props; | ||||||||||||||||||||||||||
| const title = entry?.data?.title ?? "RocketSim Docs"; | ||||||||||||||||||||||||||
| const description = entry?.data?.description ?? ""; | ||||||||||||||||||||||||||
| const slug = entry?.slug ?? ""; | ||||||||||||||||||||||||||
| const pageUrl = `${baseUrl}/${slug}`; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const breadcrumbItems = [ | ||||||||||||||||||||||||||
| { name: "Home", url: baseUrl }, | ||||||||||||||||||||||||||
| { name: "Docs", url: `${baseUrl}/docs` }, | ||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||
| if (title !== "RocketSim Docs" && title !== "404") { | ||||||||||||||||||||||||||
| breadcrumbItems.push({ name: title, url: pageUrl }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const isFaqPage = slug.endsWith("support/faq"); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const schemas: Record<string, unknown>[] = []; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (title !== "RocketSim Docs" && title !== "404") { | ||||||||||||||||||||||||||
| schemas.push({ | ||||||||||||||||||||||||||
| "@context": "https://schema.org", | ||||||||||||||||||||||||||
| "@type": "TechArticle", | ||||||||||||||||||||||||||
| headline: title, | ||||||||||||||||||||||||||
| description, | ||||||||||||||||||||||||||
| url: pageUrl, | ||||||||||||||||||||||||||
| author: { | ||||||||||||||||||||||||||
| "@type": "Organization", | ||||||||||||||||||||||||||
| name: "RocketSim", | ||||||||||||||||||||||||||
| url: baseUrl, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| publisher: { | ||||||||||||||||||||||||||
| "@type": "Organization", | ||||||||||||||||||||||||||
| name: "RocketSim", | ||||||||||||||||||||||||||
| url: baseUrl, | ||||||||||||||||||||||||||
| logo: `${baseUrl}/images/rocketsim-app-icon.png`, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| mainEntityOfPage: { | ||||||||||||||||||||||||||
| "@type": "WebPage", | ||||||||||||||||||||||||||
| "@id": pageUrl, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (breadcrumbItems.length > 1) { | ||||||||||||||||||||||||||
| schemas.push({ | ||||||||||||||||||||||||||
| "@context": "https://schema.org", | ||||||||||||||||||||||||||
| "@type": "BreadcrumbList", | ||||||||||||||||||||||||||
| itemListElement: breadcrumbItems.map((item, index) => ({ | ||||||||||||||||||||||||||
| "@type": "ListItem", | ||||||||||||||||||||||||||
| position: index + 1, | ||||||||||||||||||||||||||
| name: item.name, | ||||||||||||||||||||||||||
| item: item.url, | ||||||||||||||||||||||||||
| })), | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const faqEntries = isFaqPage | ||||||||||||||||||||||||||
| ? [ | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| q: "Does RocketSim offer out-of-the App Store distribution?", | ||||||||||||||||||||||||||
| a: "Yes. Get in touch via support@rocketsim.app.", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| q: "Are there non-recurring subscriptions?", | ||||||||||||||||||||||||||
| a: "Yes, you can consider buying Team Licenses at rocketsim.app/team-insights.", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| q: "Can I reimburse my App Store subscription?", | ||||||||||||||||||||||||||
| a: "Yes, RocketSim offers Team Licenses as an alternative at rocketsim.app/team-insights.", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| q: "Do you provide commercial or team licenses?", | ||||||||||||||||||||||||||
| a: "Yes, check out Team Licenses at rocketsim.app/team-insights.", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| q: "Can I buy a lifetime RocketSim license?", | ||||||||||||||||||||||||||
| a: "No, but you can earn a lifetime license through SwiftLee Weekly's referral program. Join the newsletter and follow the instructions.", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| q: "Why is my video not accepted by App Store Connect?", | ||||||||||||||||||||||||||
| a: "Videos are optimized for App Previews following Apple's specifications. Make sure to use a Simulator matching a device from the App Preview specifications.", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| q: "Why wouldn't I just use xcrun simctl?", | ||||||||||||||||||||||||||
| a: "RocketSim uses xcrun simctl under the hood but enhances the output with touches, device bezels, and a visual interface for quicker access.", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| q: "Why does RocketSim need screen recording permissions?", | ||||||||||||||||||||||||||
| a: "RocketSim is sandboxed and needs screen recording permissions to read Simulator window titles, which it uses to determine the currently active Simulator.", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| q: "Where can I report bugs or feature requests?", | ||||||||||||||||||||||||||
| a: "Issues and feature requests are managed on GitHub at github.com/AvdLee/RocketSimApp/issues.", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| q: "How can I get PNG images instead of JPEG?", | ||||||||||||||||||||||||||
| a: "Disable App Store Connect (ASC) Optimization. ASC requires JPEG images without alpha layer.", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| q: "Why are my iPad captures upside-down?", | ||||||||||||||||||||||||||
| a: "RocketSim cannot detect landscape-left or landscape-right and defaults to one rotation. Rotate your Simulator twice and restart the recording.", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| q: "Can I create transparent captures?", | ||||||||||||||||||||||||||
| a: "Yes, disable App Preview Optimized and set your background color to transparent.", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| q: "Network Speed Control isn't working, what can I do?", | ||||||||||||||||||||||||||
| a: "Quit Xcode, all Simulators, and RocketSim, then reopen them. Check System Settings → Privacy & Security for pending permissions. On macOS Sequoia, enable the Network Extension in System Settings → General → Login Items & Extensions.", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||
| : []; | ||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Having these questions and answers here as a duplicate to the faq in the markdown files is bound to diverge in the future. Maybe we can come up with a way to have the questions and answers declared in one place and then automatically placed in both schema values and the markdown file. I'm not sure how, we could do some research there. |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (isFaqPage && faqEntries.length > 0) { | ||||||||||||||||||||||||||
| schemas.push({ | ||||||||||||||||||||||||||
| "@context": "https://schema.org", | ||||||||||||||||||||||||||
| "@type": "FAQPage", | ||||||||||||||||||||||||||
| mainEntity: faqEntries.map((faq) => ({ | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| if (isFaqPage && faqEntries.length > 0) { | |
| schemas.push({ | |
| "@context": "https://schema.org", | |
| "@type": "FAQPage", | |
| mainEntity: faqEntries.map((faq) => ({ | |
| const faqData = Array.isArray(entry?.data?.faq) ? entry.data.faq : []; | |
| if (isFaqPage && faqData.length > 0) { | |
| schemas.push({ | |
| "@context": "https://schema.org", | |
| "@type": "FAQPage", | |
| mainEntity: faqData.map((faq) => ({ |
Copilot
AI
Mar 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set:html={JSON.stringify(schema)} injects raw JSON into a <script> tag. If any field (e.g. title/description) ever contains </script> or certain < sequences, it can break out of the script context and become an XSS vector. Consider escaping < (commonly replacing it with \u003c in the JSON string) before injecting, or using a safer serialization helper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue:
We already have an
StructuredDatacomponent where the schema objects are typed using TypeScript. You can find it here. We should add new schema types there and use that component for consistency.