From 2858b6149b8696c6c38d07fd80d54d725d57f706 Mon Sep 17 00:00:00 2001 From: Wayne Sutton Date: Thu, 1 Jan 2026 14:31:56 -0800 Subject: [PATCH] Embed YouTube videos and Twitter/X posts directly in markdown Domain whitelisting for security (only trusted domains allowed) --- .claude/skills/write.md | 515 ++++++++++++++++++++ .cursor/rules/write.mdc | 436 +++++++++++++++++ .gitignore | 5 +- AGENTS.md | 2 +- TASK.md | 15 +- changelog.md | 21 + content/blog/markdown-with-code-examples.md | 62 +++ content/pages/changelog-page.md | 23 + files.md | 2 +- public/raw/changelog.md | 23 + public/raw/markdown-with-code-examples.md | 62 +++ src/components/BlogPost.tsx | 67 ++- src/pages/Post.tsx | 419 ++++++++-------- src/styles/global.css | 14 + 14 files changed, 1466 insertions(+), 200 deletions(-) create mode 100644 .claude/skills/write.md create mode 100644 .cursor/rules/write.mdc diff --git a/.claude/skills/write.md b/.claude/skills/write.md new file mode 100644 index 0000000..a5d7c21 --- /dev/null +++ b/.claude/skills/write.md @@ -0,0 +1,515 @@ +# Writing Style Skill + +Expert writing for technical content, social media, and developer documentation. Optimized to avoid AI detection patterns. + +--- + +## How to call specific sections + +Use these triggers to activate specific parts of this skill: + +| Trigger phrase | Section activated | +|----------------|-------------------| +| `write:tweet` or `write:x` | X/Twitter posts format | +| `write:linkedin` | LinkedIn posts format | +| `write:blog` | Blog posts format | +| `write:readme` | README files format | +| `write:commit` | Git commits format | +| `write:docs` | Developer documentation | +| `write:feature` | Feature post format | +| `write:convex` | Convex-specific content | +| `write:tip` | Quick tip format | + +Example usage: +- "write:tweet about Convex real-time sync" +- "write:blog on authentication patterns" +- "write:feature for our new search API" + +--- + +## Rule of one + +Every piece of content follows this framework: + +**One person** — Write to a specific person, not an audience. + +**One problem** — State the single problem they face. + +**One cause** — Identify the root cause. + +**One difference** — Explain what the solution does differently. + +**One action** — End with one clear next step. + +### Rule of one checklist + +Before publishing, answer these: + +- [ ] Can I name the one person this is for? +- [ ] Can the problem fit in one sentence? +- [ ] Is the root cause obvious? +- [ ] Can I explain the difference in one breath? +- [ ] Is there only one action at the end? + +If any answer is no, revise before publishing. Ask the user if they want to proceed with the revision. + +--- + +## When to use this skill + +Activate for: +- X/Twitter posts +- LinkedIn content +- Blog posts +- README files +- Git commits +- Product announcements +- Developer documentation +- Feature posts + +**Important:** This is for standalone writing. Don't update project files (files.md, changelog.md, README.md) when using this skill. + +--- + +## Voice styles + +Match your voice to the content type: + +| Style | Characteristics | Use for | +|-------|-----------------|---------| +| Technical educator | Clear, structured, educational | Technical content, tutorials, READMEs | +| Conversational dev | Warm, witty, approachable | Social posts, personal takes | +| Analytical thinker | Data-driven, bold, opinionated | Thought leadership, threads | +| Aphorist | Compressed, timeless, pithy | Short posts, one-liners | +| Founder voice | Experience-backed, energetic | Startup content, advice | +| Systems thinker | Frameworks, mental models | Long-form, technical takes | +| Dev culture | Relatable, playful, authentic | Community content, personality | +| Data storyteller | Visual, analytical, trend-focused | AI trends, market insights | +| Enterprise pro | Professional, strategic, precise | Enterprise SaaS, B2B content | +| Community builder | Encouraging, personal, supportive | Career growth, DevRel | +| Learn in public | Educational, transparent, iterative | Developer career, web dev | +| Product thinker | Community-first, growth-minded | Community building, growth | + +--- + +## Core principles + +**Stand out by being you** +You don't stand out online by saying the same things as everyone else. You stand out by saying: "This is who I am. Here's what I think, feel, and believe." Consensus takes are forgettable. Your take isn't. + +**Lead with value** +- First sentence does the work +- Don't bury the takeaway +- Readers scroll fast + +**Be direct, not blunt** +- Say what you mean +- Confidence without arrogance +- Contractions are fine + +**Technical, not alienating** +- Define terms when helpful +- Complex ideas deserve simple language +- Let code speak when it can + +**Share what you actually know** +- Personal experience beats generic advice +- Specific examples beat abstract principles +- Acknowledge what you don't know + +--- + +## Format by content type + +### write:tweet + +X/Twitter posts format. + +``` +[Clear statement or observation] + +[Supporting point or context] + +[Optional: question or call to action] +``` + +Rules: +- First 2 lines visible in preview. Make them count +- 280 characters forces compression. Use it +- One idea per post +- No hashtags +- No emojis unless requested + +--- + +### write:linkedin + +LinkedIn posts format. + +``` +[Hook that stops the scroll] + +[Story or context in 2-3 short paragraphs] + +[Insight or lesson] + +[Call to action or question] +``` + +Rules: +- Short paragraphs for mobile +- Professional but not corporate +- Personal stories perform well +- One clear takeaway + +--- + +### write:blog + +Blog posts format. + +``` +# Title (sentence case, max 70 characters) + +[Opening that states the value immediately] + +## Section heading +[Max 300 words per section] + +## Section heading +[Use bullet points or tables where helpful] +``` + +Rules: +- Sentence case for all headings +- No H3s unless absolutely necessary +- Fact-check everything +- Lead with why it matters +- Max 5 sections + +--- + +### write:readme + +README files format. + +``` +# Project name + +[One sentence: what this does] + +## Getting started +[Minimal steps to run] + +## Usage +[Code examples] + +## API / Configuration +[Reference docs] +``` + +Rules: +- Start with what it does, not what it is +- Code examples over descriptions +- Keep it scannable + +--- + +### write:commit + +Git commits format. + +``` +[type]: [short description] + +[Optional: longer explanation if needed] +``` + +**Types:** feat, fix, docs, style, refactor, test, chore + +Rules: +- Present tense ("add feature" not "added feature") +- 50 characters max for subject line +- No period at the end + +--- + +### write:feature + +Feature post format. Answer these five things without wandering: + +``` +## [Feature name] + +**What it is** +[One sentence description] + +**Who it's for** +[Specific user or role] + +**The problem it solves** +[One problem, clearly stated] + +**How it works** +[High-level explanation, 2-3 sentences max] + +**Try it** +[One clear action: link, command, or next step] +``` + +Rules: +- No wandering. Five sections only. +- Each section answers one question +- Skip the hype. State facts. +- End with a single action + +--- + +### write:docs + +Developer documentation format. + +``` +# [Task or concept name] + +[One sentence: what this page helps you do] + +## Before you start +[Prerequisites, if any] + +## Steps +1. [Action] +2. [Action] +3. [Action] + +## Example +[Code snippet] + +## Related +[Links to related docs] +``` + +Rules: +- Task-oriented, not feature-oriented +- Show, don't tell +- Working code examples required +- Keep prerequisites minimal + +--- + +### write:convex + +Convex-specific content. + +**New guide or tutorial:** +``` +New guide: [topic] + +[What you'll learn or build] + +[Link] +``` + +**write:tip format:** +``` +Convex tip: + +[Pattern in one sentence] + +[Code snippet showing it] + +[Why this works] +``` + +**What to avoid:** +- Generic praise ("Convex is amazing!") +- Comparisons that trash competitors +- Overpromising +- Screenshots without context +- Sharing customer work without permission +- Empty engagement bait + +**Content mix for Convex:** +- 40% educational (tutorials, tips, patterns) +- 30% community (spotlights, customer stories) +- 20% product (updates, features, changelog) +- 10% personal (projects, learnings, opinions) + +--- + +## Templates + +### Technical educator style + +``` +[Clear headline] + +Here's what matters: +- Point 1 +- Point 2 +- Point 3 + +[Code snippet or visual] + +[Resource link] +``` + +### Analytical thinker style + +``` +[Counterintuitive opening] + +[Common belief] + +[Your argument with evidence] + +[Implications] +``` + +### Data storyteller style + +``` +[Trend observation with specific data point] + +[Context: why this matters now] + +[Visual reference or chart if applicable] + +[What to watch next] +``` + +Rules: +- Lead with numbers +- Connect data to broader movements +- End with forward-looking signal + +### Learn in public style + +``` +[Thing I just figured out] + +[How I got there (mistakes included)] + +[Resources or links for others] +``` + +Rules: +- Document the journey +- Share rough drafts +- Credit sources + +--- + +## AI detection avoidance + +### Banned vocabulary + +**Never use:** +delve, intricate, pivotal, comprehensive, multifaceted, facilitate, encompass, underscore, testament, notably, crucial, underpins, realm, landscape, tapestry, moreover, furthermore, additionally, specifically, importantly, consequently, therefore, thus, myriad, plethora, nuanced, holistic, meticulous, versatile, leverage, synergy, ecosystem, paradigm shift, disruptive, scalable, seamless, empower, innovative, transformative, robust, agile, dynamic, cutting-edge, next-gen, revolutionary, breakthrough, game changer, supercharge, unlock, groundbreaking, ai powered, ai-powered + +**Banned sentence openers:** +- Dive into / Delve into +- It's important to note +- In conclusion / In summary +- Based on the information provided +- Navigating the landscape of +- A testament to +- When it comes to +- In today's digital age +- Furthermore / Moreover / Additionally +- Let's explore + +### Banned patterns + +**Rule of three** +AI groups items in threes. Vary list lengths. + +BAD: "The project was innovative, comprehensive, and groundbreaking." +GOOD: "The project worked." + +**Negative parallelisms** +BAD: "This is not just a tool, but a revolution." +GOOD: State what it IS directly. + +**Vague attributions** +BAD: "Many experts believe..." / "Some argue that..." +GOOD: Name specific sources or remove attribution. + +**Setup-pivot-conclusion paragraphs** +AI follows: General statement → "However" → Balanced conclusion. +Real writing is messier. Not every paragraph needs resolution. + +**Symmetrical structures** +AI balances pros/cons equally. Real analysis is asymmetric. + +### Banned style markers + +- No em dashes between words +- No hashtags +- No emojis unless requested +- No title case ("The Future of AI" → "The future of AI") +- No excessive formatting + +### How to write human + +**Vary sentence structure** +Mix short punchy sentences with longer ones. Fragments work too. Questions help. + +**Use specific details** +- Exact numbers over ranges +- Named sources over "experts say" +- Concrete examples over abstractions +- Personal observations + +**Embrace asymmetry** +Real writing has uneven sections, stronger opinions, tangents, imperfect transitions. + +**Show your thinking** +- "I tried X, but it didn't work because..." +- "The obvious answer is Y, but actually..." +- "I'm not sure about Z, but here's my take..." + +--- + +## Before publishing checklist + +### Rule of one check +- [ ] Can I name the one person this is for? +- [ ] Can the problem fit in one sentence? +- [ ] Is the root cause obvious? +- [ ] Can I explain the difference in one breath? +- [ ] Is there only one action at the end? + +### Quality check +- [ ] Clear takeaway in first line? +- [ ] No banned vocabulary? +- [ ] No banned sentence openers? +- [ ] No rule of three patterns? +- [ ] No vague attributions? +- [ ] No setup-pivot-conclusion in every paragraph? +- [ ] No excessive em dashes? +- [ ] No perfectly balanced arguments? +- [ ] Formatted for the platform? +- [ ] Fact-checked? + +**If any check fails, revise before publishing.** + +--- + +## Core principle + +You don't stand out by saying what everyone else says. You stand out by putting yourself in the work. What you think. What you feel. What you believe. That's the signal in the noise. + +AI writes to sound authoritative. Humans write to communicate. + +AI smooths rough edges. Human writing has texture. + +AI balances everything. Human writing has opinions. + +AI generalizes. Human writing gets specific. + +Write like you're the smartest person at the table who doesn't need to prove it. + +Be clear. Be useful. Be human. Have a point of view. + +When in doubt: Would a tired expert at 11pm write this sentence? If it sounds too polished, too balanced, too careful, it probably is. diff --git a/.cursor/rules/write.mdc b/.cursor/rules/write.mdc new file mode 100644 index 0000000..9e33a49 --- /dev/null +++ b/.cursor/rules/write.mdc @@ -0,0 +1,436 @@ +--- +description: Writing style guide with AI detection avoidance. For tweets, LinkedIn, blogs, READMEs, commits. Activate with @write +globs: +alwaysApply: false +--- + +# Writing style rules + +Use this rule when writing documentation, README files, comments, commit messages, or any other user facing text. + +## Triggers + +| Trigger | Output | +|---|---| +| `write:tweet` or `write:x` | X or Twitter post | +| `write:linkedin` | LinkedIn post | +| `write:blog` | Blog post | +| `write:readme` | README | +| `write:commit` | Git commit message | +| `write:docs` | Developer documentation | +| `write:feature` | Feature post | +| `write:convex` | Convex specific content | +| `write:tip` | Quick tip | + +Example usage: +* "write:tweet about real time sync" +* "write:blog on authentication patterns" +* "write:feature for our new search API" + +## Rule of one + +Every piece of content follows this framework: + +* **One person**: Write to a specific person, not an audience. +* **One problem**: State the single problem they face. +* **One cause**: Identify the root cause. +* **One difference**: Explain what the solution does differently. +* **One action**: End with one clear next step. + +### Rule of one checklist + +Before publishing, answer these: + +* [ ] Can I name the one person this is for? +* [ ] Can the problem fit in one sentence? +* [ ] Is the root cause obvious? +* [ ] Can I explain the difference in one breath? +* [ ] Is there only one action at the end? + +If any answer is no, revise before publishing. + +## When to use this rule + +Use it for: +* X or Twitter posts +* LinkedIn content +* Blog posts +* README files +* Git commits +* Product announcements +* Developer documentation +* Feature posts + +Important: this is for standalone writing. Do not update project files like files.md, changelog.md, or README.md when using this rule. + +## Voice styles + +Pick a voice that fits the output: + +| Style | Characteristics | Use for | +|---|---|---| +| Technical educator | Clear, structured, educational | Technical content, tutorials, READMEs | +| Conversational dev | Warm, witty, approachable | Social posts, personal takes | +| Analytical thinker | Data driven, bold, opinionated | Thought leadership, threads | +| Aphorist | Compressed, timeless, pithy | Short posts, one liners | +| Founder voice | Experience backed, energetic | Startup content, advice | +| Systems thinker | Frameworks, mental models | Long form, technical takes | +| Dev culture | Relatable, playful, authentic | Community content, personality | +| Data storyteller | Visual, analytical, trend focused | AI trends, market insights | +| Enterprise pro | Professional, strategic, precise | Enterprise SaaS, B2B content | +| Community builder | Encouraging, personal, supportive | Career growth, DevRel | +| Learn in public | Educational, transparent, iterative | Developer career, web dev | +| Product thinker | Community first, growth minded | Community building, growth | + +## Core principles + +### Stand out by being you +You do not stand out online by saying the same things as everyone else. Say what you think, feel, and believe. Consensus takes are forgettable. Your take is not. + +### Lead with value +* First sentence does the work +* Do not bury the takeaway +* Readers scroll fast + +### Be direct, not blunt +* Say what you mean +* Confidence without arrogance +* Contractions are fine + +### Technical, not alienating +* Define terms when helpful +* Complex ideas deserve simple language +* Let code speak when it can + +### Share what you actually know +* Personal experience beats generic advice +* Specific examples beat abstract principles +* Acknowledge what you do not know + +## Formats by content type + +### write:tweet (or write:x) + +``` +[Clear statement or observation] + +[Supporting point or context] + +[Optional: question or call to action] +``` + +Rules: +* First 2 lines visible in preview. Make them count. +* 280 characters forces compression. Use it. +* One idea per post. +* No hashtags. +* No emojis unless requested. + +### write:linkedin + +``` +[Hook that stops the scroll] + +[Story or context in short paragraphs] + +[Insight or lesson] + +[Call to action or question] +``` + +Rules: +* Short paragraphs for mobile +* Professional but not corporate +* Personal stories perform well +* One clear takeaway + +### write:blog + +``` +# Title (sentence case, max 70 characters) + +[Opening that states the value immediately] + +## Section heading +[Max 300 words per section] + +## Section heading +[Use bullet points or tables where helpful] +``` + +Rules: +* Sentence case for all headings +* No H3s unless absolutely necessary +* Fact check everything +* Lead with why it matters +* Max 5 sections + +### write:readme + +``` +# Project name + +[One sentence: what this does] + +## Getting started +[Minimal steps to run] + +## Usage +[Code examples] + +## API or configuration +[Reference docs] +``` + +Rules: +* Start with what it does, not what it is +* Code examples over descriptions +* Keep it scannable + +### write:commit + +``` +[type]: [short description] + +[Optional: longer explanation if needed] +``` + +Types: feat, fix, docs, style, refactor, test, chore + +Rules: +* Present tense: "add feature" not "added feature" +* 50 characters max for subject line +* No period at the end + +### write:docs + +``` +# [Task or concept name] + +[One sentence: what this page helps you do] + +## Before you start +[Prerequisites, if any] + +## Steps +1. [Action] +2. [Action] +3. [Action] + +## Example +[Code snippet] + +## Related +[Links to related docs] +``` + +Rules: +* Task oriented, not feature oriented +* Show, do not tell +* Working code examples required +* Keep prerequisites minimal + +### write:feature + +``` +## [Feature name] + +**What it is** +[One sentence description] + +**Who it is for** +[Specific user or role] + +**The problem it solves** +[One problem, clearly stated] + +**How it works** +[High level explanation, 2 to 3 sentences max] + +**Try it** +[One clear action: link, command, or next step] +``` + +Rules: +* Five sections only +* Each section answers one question +* Skip the hype. State facts. +* End with a single action + +### write:tip + +``` +[One sentence tip] + +[Concrete example, ideally code] + +[One line why this works] +``` + +Rules: +* Make it shippable in under a minute +* If you mention a tool, show the exact command or snippet +* End with one action or takeaway, not a question pile + +### write:convex + +New guide or tutorial: + +``` +New guide: [topic] + +[What you will learn or build] + +[Link] +``` + +Convex tip: + +``` +Convex tip: + +[Pattern in one sentence] + +[Code snippet showing it] + +[Why this works] +``` + +What to avoid: +* Generic praise +* Comparisons that trash competitors +* Overpromising +* Screenshots without context +* Sharing customer work without permission +* Empty engagement bait + +Content mix for Convex: +* 40 percent educational: tutorials, tips, patterns +* 30 percent community: spotlights, customer stories +* 20 percent product: updates, features, changelog +* 10 percent personal: projects, learnings, opinions + +## Templates + +### Technical educator + +``` +[Clear headline] + +Here is what matters: +* Point 1 +* Point 2 +* Optional: one more point + +[Code snippet or visual] + +[Resource link] +``` + +### Analytical thinker + +``` +[Counterintuitive opening] + +[Common belief] + +[Your argument with evidence] + +[Implications] +``` + +### Data storyteller + +``` +[Trend observation with a specific data point] + +[Context: why this matters now] + +[Visual reference or chart if applicable] + +[What to watch next] +``` + +Rules: +* Lead with numbers +* Connect data to broader movements +* End with a forward looking signal + +### Learn in public + +``` +[Thing I just figured out] + +[How I got there, mistakes included] + +[Resources or links for others] +``` + +Rules: +* Document the journey +* Share rough drafts +* Credit sources + +## AI detection avoidance + +### Banned vocabulary + +Never use these words: +delve, intricate, pivotal, comprehensive, multifaceted, facilitate, encompass, underscore, testament, notably, crucial, underpins, realm, landscape, tapestry, moreover, furthermore, additionally, specifically, importantly, consequently, therefore, thus, myriad, plethora, nuanced, holistic, meticulous, versatile, leverage, synergy, ecosystem, paradigm shift, disruptive, scalable, seamless, empower, innovative, transformative, robust, agile, dynamic, cutting edge, next gen, revolutionary, breakthrough, game changer, supercharge, unlock, groundbreaking, ai powered, ai-powered + +### Banned sentence openers + +Never start sentences with: +* Dive into / Delve into +* It is important to note +* In conclusion / In summary +* Based on the information provided +* When it comes to +* In today’s digital age +* Let’s explore + +### Banned patterns + +* **Rule of three**: AI groups items in threes. Vary list lengths. +* **Negative parallelism**: Do not say "not just X, but Y." State what it is. +* **Vague attribution**: Do not say "experts believe." Name sources or remove. +* **Setup pivot conclusion**: Not every paragraph needs "however" and resolution. +* **Symmetrical structures**: Do not balance pros and cons evenly by default. Be asymmetric. + +### Style markers to avoid + +* No em dashes between words +* No hashtags +* No emojis unless requested +* No title case in headings +* No excessive formatting + +### How to write human + +* Vary sentence structure. Mix short and long. Fragments work. +* Use exact numbers, not ranges. +* Name specific sources, not "experts say." +* Show your thinking: "I tried X, but it did not work because..." +* Not every paragraph needs a neat resolution. + +## Before publishing checklist + +Rule of one: +* [ ] One person +* [ ] One problem +* [ ] One cause +* [ ] One difference +* [ ] One action + +Quality: +* [ ] Clear takeaway in the first line +* [ ] No banned vocabulary +* [ ] No banned sentence openers +* [ ] No vague attributions +* [ ] No overbalanced pros and cons +* [ ] No excessive formatting + +## Core principle + +AI writes to sound authoritative. Humans write to communicate. AI smooths rough edges. Human writing has texture. AI balances everything. Human writing has opinions. Be clear. Be useful. Have a point of view. + diff --git a/.gitignore b/.gitignore index ee43fad..d5ec4e2 100644 --- a/.gitignore +++ b/.gitignore @@ -31,11 +31,8 @@ dist-ssr # Fork configuration (user-specific) fork-config.json -# Cursor rules -.cursor/rules/write.mdc -# Claude skills -.claude/skills/write.md + # PRD files diff --git a/AGENTS.md b/AGENTS.md index 1d35764..b20f1d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,7 @@ Your content is instantly available to browsers, LLMs, and AI agents.. Write mar - **Site Name**: markdown - **Site Title**: markdown sync framework -- **Site URL**: https://markdown.fast +- **Site URL**: https://www.markdown.fast - **Total Posts**: 17 - **Total Pages**: 5 - **Latest Post**: 2025-12-29 diff --git a/TASK.md b/TASK.md index 8b6a1f7..16833e7 100644 --- a/TASK.md +++ b/TASK.md @@ -2,15 +2,26 @@ ## To Do -- [x] Link author name to author page with post list - [ ] site confg add header icons +- [ ] fix site confg link ## Current Status -v2.3.0 ready. Author pages feature. Links authorName to `/author/:authorSlug` archive pages displaying all posts by that author. Follows existing tag pages pattern. +v2.4.0 ready. YouTube and Twitter/X embed support with domain whitelisting. ## Completed +- [x] YouTube and Twitter/X embed support with domain whitelisting + - [x] Added `ALLOWED_IFRAME_DOMAINS` constant for whitelisted domains (YouTube, Twitter/X) + - [x] Added `iframe` to sanitize schema with allowed attributes + - [x] Added custom iframe component handler with domain validation + - [x] Auto-adds `sandbox` and `loading="lazy"` attributes for security + - [x] Non-whitelisted iframes silently blocked + - [x] Added `.embed-container` CSS styles for responsive embeds + - [x] Updated markdown-with-code-examples.md with Embeds section + - [x] Works on both blog posts and pages + - [x] Updated files.md, TASK.md, changelog.md, changelog-page.md + - [x] Author pages at `/author/:authorSlug` with post list - [x] Added `by_authorName` index to posts table in convex/schema.ts - [x] Added `getAllAuthors` and `getPostsByAuthor` queries in convex/posts.ts diff --git a/changelog.md b/changelog.md index 0944ef4..b5a2acc 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [2.4.0] - 2026-01-01 + +### Added + +- YouTube and Twitter/X embed support with domain whitelisting + - Embed YouTube videos and Twitter/X posts directly in markdown + - Domain whitelisting for security (only trusted domains allowed) + - Whitelisted domains: `youtube.com`, `www.youtube.com`, `youtube-nocookie.com`, `www.youtube-nocookie.com`, `platform.twitter.com`, `platform.x.com` + - Auto-adds `sandbox="allow-scripts allow-same-origin allow-popups"` for security + - Auto-adds `loading="lazy"` for performance + - Non-whitelisted iframes silently blocked + - Works on both blog posts and pages +- Embeds section in markdown-with-code-examples.md with YouTube and Twitter/X examples + +### Technical + +- Added `ALLOWED_IFRAME_DOMAINS` constant in `src/components/BlogPost.tsx` +- Added `iframe` to sanitize schema tagNames with allowed attributes (`src`, `width`, `height`, `allow`, `allowfullscreen`, `frameborder`, `title`, `style`) +- Added custom `iframe` component handler with URL validation against whitelisted domains +- Added `.embed-container` CSS styles to `src/styles/global.css` for responsive embeds + ## [2.3.0] - 2025-12-31 ### Added diff --git a/content/blog/markdown-with-code-examples.md b/content/blog/markdown-with-code-examples.md index d8b644d..c3f4ba7 100644 --- a/content/blog/markdown-with-code-examples.md +++ b/content/blog/markdown-with-code-examples.md @@ -332,6 +332,68 @@ For best results: - Author avatars: 200x200px (displays as circle) - Card thumbnails: Square images work best (auto-cropped to center) +## Embeds + +Embed YouTube videos and Twitter/X posts directly in your markdown. Only YouTube and Twitter/X domains are allowed for security. + +### YouTube + +Embed a YouTube video using an iframe: + +```html + +``` + +Result: + + + +### Twitter/X + +Embed a tweet using the Twitter embed URL: + +```html + +``` + +Result: + + + +### Privacy-enhanced YouTube + +Use `youtube-nocookie.com` for privacy-enhanced embeds: + +```html + +``` + +### Allowed domains + +For security, only these domains are whitelisted: + +- `youtube.com`, `www.youtube.com` +- `youtube-nocookie.com`, `www.youtube-nocookie.com` +- `platform.twitter.com`, `platform.x.com` + +Iframes from other domains are silently blocked. + ## Nested lists Indent with two spaces for nested items: diff --git a/content/pages/changelog-page.md b/content/pages/changelog-page.md index ce6d72f..0c189fb 100644 --- a/content/pages/changelog-page.md +++ b/content/pages/changelog-page.md @@ -10,6 +10,29 @@ layout: "sidebar" All notable changes to this project. ![](https://img.shields.io/badge/License-MIT-yellow.svg) +## v2.4.0 + +Released January 1, 2026 + +**YouTube and Twitter/X embed support** + +- Embed YouTube videos and Twitter/X posts directly in markdown + - Domain whitelisting for security (only trusted domains allowed) + - Whitelisted domains: `youtube.com`, `www.youtube.com`, `youtube-nocookie.com`, `www.youtube-nocookie.com`, `platform.twitter.com`, `platform.x.com` + - Auto-adds `sandbox` and `loading="lazy"` attributes for security + - Non-whitelisted iframes silently blocked + - Works on both blog posts and pages +- Embeds section added to markdown-with-code-examples.md with usage examples + +**Technical details:** + +- Added `ALLOWED_IFRAME_DOMAINS` constant in `src/components/BlogPost.tsx` +- Added `iframe` to sanitize schema with allowed attributes +- Added custom iframe component handler with URL validation +- Added `.embed-container` CSS styles for responsive embeds + +Updated files: `src/components/BlogPost.tsx`, `src/styles/global.css`, `content/blog/markdown-with-code-examples.md`, `files.md`, `TASK.md`, `changelog.md`, `content/pages/changelog-page.md` + ## v2.3.0 Released December 31, 2025 diff --git a/files.md b/files.md index f469ba0..5f8a170 100644 --- a/files.md +++ b/files.md @@ -60,7 +60,7 @@ A brief description of each file in the codebase. | `ThemeToggle.tsx` | Theme switcher (dark/light/tan/cloud) | | `PostList.tsx` | Year-grouped blog post list or card grid (supports list/cards view modes, columns prop for 2/3 column grids, showExcerpts prop to control excerpt visibility) | | `BlogHeroCard.tsx` | Hero card component for the first blogFeatured post on blog page. Displays landscape image, tags, date, title, excerpt, author info, and read more link | -| `BlogPost.tsx` | Markdown renderer with syntax highlighting, collapsible sections (details/summary), text wrapping for plain text code blocks, and image lightbox support (click images to magnify in full-screen overlay) | +| `BlogPost.tsx` | Markdown renderer with syntax highlighting, collapsible sections (details/summary), text wrapping for plain text code blocks, image lightbox support (click images to magnify in full-screen overlay), and iframe embed support with domain whitelisting (YouTube and Twitter/X only) | | `CopyPageDropdown.tsx` | Share dropdown with Copy page (markdown to clipboard), View as Markdown (opens raw .md file), Download as SKILL.md (Anthropic Agent Skills format), and Open in AI links (ChatGPT, Claude, Perplexity) using GitHub raw URLs with universal prompt | | `Footer.tsx` | Footer component that renders markdown content from frontmatter footer field or siteConfig.defaultContent. Can be enabled/disabled globally and per-page via frontmatter showFooter field. Renders inside article at bottom for posts/pages, and in current position on homepage. Supports images with size control via HTML attributes (width, height, style, class) | | `SearchModal.tsx` | Full text search modal with keyboard navigation | diff --git a/public/raw/changelog.md b/public/raw/changelog.md index de28615..c757187 100644 --- a/public/raw/changelog.md +++ b/public/raw/changelog.md @@ -8,6 +8,29 @@ Date: 2026-01-01 All notable changes to this project. ![](https://img.shields.io/badge/License-MIT-yellow.svg) +## v2.4.0 + +Released January 1, 2026 + +**YouTube and Twitter/X embed support** + +- Embed YouTube videos and Twitter/X posts directly in markdown + - Domain whitelisting for security (only trusted domains allowed) + - Whitelisted domains: `youtube.com`, `www.youtube.com`, `youtube-nocookie.com`, `www.youtube-nocookie.com`, `platform.twitter.com`, `platform.x.com` + - Auto-adds `sandbox` and `loading="lazy"` attributes for security + - Non-whitelisted iframes silently blocked + - Works on both blog posts and pages +- Embeds section added to markdown-with-code-examples.md with usage examples + +**Technical details:** + +- Added `ALLOWED_IFRAME_DOMAINS` constant in `src/components/BlogPost.tsx` +- Added `iframe` to sanitize schema with allowed attributes +- Added custom iframe component handler with URL validation +- Added `.embed-container` CSS styles for responsive embeds + +Updated files: `src/components/BlogPost.tsx`, `src/styles/global.css`, `content/blog/markdown-with-code-examples.md`, `files.md`, `TASK.md`, `changelog.md`, `content/pages/changelog-page.md` + ## v2.3.0 Released December 31, 2025 diff --git a/public/raw/markdown-with-code-examples.md b/public/raw/markdown-with-code-examples.md index 3d280f1..c8313b5 100644 --- a/public/raw/markdown-with-code-examples.md +++ b/public/raw/markdown-with-code-examples.md @@ -327,6 +327,68 @@ For best results: - Author avatars: 200x200px (displays as circle) - Card thumbnails: Square images work best (auto-cropped to center) +## Embeds + +Embed YouTube videos and Twitter/X posts directly in your markdown. Only YouTube and Twitter/X domains are allowed for security. + +### YouTube + +Embed a YouTube video using an iframe: + +```html + +``` + +Result: + + + +### Twitter/X + +Embed a tweet using the Twitter embed URL: + +```html + +``` + +Result: + + + +### Privacy-enhanced YouTube + +Use `youtube-nocookie.com` for privacy-enhanced embeds: + +```html + +``` + +### Allowed domains + +For security, only these domains are whitelisted: + +- `youtube.com`, `www.youtube.com` +- `youtube-nocookie.com`, `www.youtube-nocookie.com` +- `platform.twitter.com`, `platform.x.com` + +Iframes from other domains are silently blocked. + ## Nested lists Indent with two spaces for nested items: diff --git a/src/components/BlogPost.tsx b/src/components/BlogPost.tsx index 9b876d9..1bcd322 100644 --- a/src/components/BlogPost.tsx +++ b/src/components/BlogPost.tsx @@ -11,10 +11,20 @@ import NewsletterSignup from "./NewsletterSignup"; import ContactForm from "./ContactForm"; import siteConfig from "../config/siteConfig"; -// Sanitize schema that allows collapsible sections (details/summary) and inline styles +// Whitelisted domains for iframe embeds (YouTube and Twitter/X only) +const ALLOWED_IFRAME_DOMAINS = [ + "youtube.com", + "www.youtube.com", + "youtube-nocookie.com", + "www.youtube-nocookie.com", + "platform.twitter.com", + "platform.x.com", +]; + +// Sanitize schema that allows collapsible sections (details/summary), inline styles, and iframes const sanitizeSchema = { ...defaultSchema, - tagNames: [...(defaultSchema.tagNames || []), "details", "summary"], + tagNames: [...(defaultSchema.tagNames || []), "details", "summary", "iframe"], attributes: { ...defaultSchema.attributes, details: ["open"], // Allow the 'open' attribute for expanded by default @@ -28,6 +38,7 @@ const sanitizeSchema = { ...(defaultSchema.attributes?.img || []), "style", ], // Allow inline styles on images + iframe: ["src", "width", "height", "allow", "allowfullscreen", "frameborder", "title", "style"], // Allow iframe with specific attributes }, }; @@ -661,6 +672,32 @@ export default function BlogPost({ content, slug, pageType = "post" }: BlogPostP td({ children }) { return {children}; }, + // Iframe component with domain whitelisting for YouTube and Twitter/X + iframe(props) { + const src = props.src as string; + if (!src) return null; + + try { + const url = new URL(src); + const isAllowed = ALLOWED_IFRAME_DOMAINS.some( + (domain) => + url.hostname === domain || url.hostname.endsWith("." + domain) + ); + if (!isAllowed) return null; + + return ( +
+