From fbac7d2b44058e9201e6aa755e72172adfc4b732 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 15:07:03 +0000 Subject: [PATCH 1/8] Initial plan From bfa62b39cd54a6c18d79e242dfb1ce419e5426b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 15:10:56 +0000 Subject: [PATCH 2/8] Fix can_be_custom_component function and add comprehensive documentation Co-authored-by: rambip <62420525+rambip@users.noreply.github.com> --- README.md | 24 ++++++++++ dioxus-markdown/README.md | 18 ++++++++ leptos-markdown/README.md | 16 +++++++ web-markdown/src/component.rs | 52 +++++++++++++++++++++- web-markdown/src/render.rs | 83 ++++++++++++++++++++++++++++++----- yew-markdown/README.md | 17 +++++++ 6 files changed, 196 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 6004238..4da9332 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,30 @@ see [here](https://rambip.github.io/rust-web-markdown/onclick) ## Custom Components see [here](https://rambip.github.io/rust-web-markdown/custom-components) +Custom components allow you to embed interactive or custom-styled elements in your markdown. + +### Custom Component Naming Rules + +To be recognized as a custom component, tag names must follow these rules: + +1. **Uppercase start** - Tags starting with an uppercase letter (A-Z) are always treated as custom components + - Examples: ``, ``, `` + +2. **Lowercase with dash** - Tags starting with lowercase (a-z) must contain at least one dash (-) + - Examples: ``, ``, `` + +These rules ensure standard HTML tags like `
`, ``, and `

` are not confused with custom components. + +**Valid custom components:** +- `` ✓ (uppercase start) +- `` ✓ (lowercase start with dash) +- `` ✓ (uppercase, self-closing with attributes) + +**NOT custom components:** +- `

` ✗ (lowercase without dash - standard HTML) +- `` ✗ (lowercase without dash - standard HTML) +- `

` ✗ (lowercase without dash - standard HTML) + # Contribute PRs are **very much** appreciated. diff --git a/dioxus-markdown/README.md b/dioxus-markdown/README.md index d959107..422923a 100644 --- a/dioxus-markdown/README.md +++ b/dioxus-markdown/README.md @@ -28,6 +28,24 @@ You just need trunk and a web-browser to test them. The Yew version of these examples can run in the browser from the links in [the top level ReadMe](../README.md). +## Custom Components + +Custom components allow you to embed interactive Dioxus components in your markdown. + +### Custom Component Naming Rules + +To be recognized as a custom component, tag names must follow these rules: + +1. **Uppercase start** - Tags starting with an uppercase letter (A-Z) are always treated as custom components + - Examples: ``, ``, `` + +2. **Lowercase with dash** - Tags starting with lowercase (a-z) must contain at least one dash (-) + - Examples: ``, ``, `` + +These rules ensure standard HTML tags like `

`, ``, and `

` are not confused with custom components. + +See the [custom-components example](./examples/custom-components) for a complete working example. + # Changelog ## 0.1.0 diff --git a/leptos-markdown/README.md b/leptos-markdown/README.md index 2a83e51..2ac1eb4 100644 --- a/leptos-markdown/README.md +++ b/leptos-markdown/README.md @@ -71,6 +71,22 @@ Try it [here](https://rambip.github.io/rust-web-markdown-markdown/onclick) This feature is still very experimental. But there is an example [here](https://rambip.github.io/rust-web-markdown-markdown/custom_component) +Custom components allow you to embed interactive Leptos components in your markdown. + +### Custom Component Naming Rules + +To be recognized as a custom component, tag names must follow these rules: + +1. **Uppercase start** - Tags starting with an uppercase letter (A-Z) are always treated as custom components + - Examples: ``, ``, `` + +2. **Lowercase with dash** - Tags starting with lowercase (a-z) must contain at least one dash (-) + - Examples: ``, ``, `` + +These rules ensure standard HTML tags like `

`, ``, and `

` are not confused with custom components. + +See the [custom-component example](./examples/custom-component) for a complete working example. + # Changelog ## 0.7.0 diff --git a/web-markdown/src/component.rs b/web-markdown/src/component.rs index ace9d62..eed4c4d 100644 --- a/web-markdown/src/component.rs +++ b/web-markdown/src/component.rs @@ -1,7 +1,55 @@ use std::collections::BTreeMap; -/// A custom non-native html element -/// defined inside markdown. +/// A custom non-native html element defined inside markdown. +/// +/// ## Custom Component Naming Rules +/// +/// Custom components are identified by their tag names, which must follow specific rules +/// to distinguish them from standard HTML tags: +/// +/// ### Valid Custom Component Names +/// +/// A tag name is considered a custom component if it meets ONE of these criteria: +/// +/// 1. **Starts with an uppercase letter (A-Z)** +/// - Examples: ``, ``, `` +/// - No dash required for uppercase names +/// +/// 2. **Starts with a lowercase letter (a-z) AND contains at least one dash (-)** +/// - Examples: ``, ``, `` +/// - The dash distinguishes these from standard HTML tags +/// +/// ### Why These Rules? +/// +/// These rules ensure that standard HTML tags (like `

`, ``, `

`, etc.) +/// are never confused with custom components, since they: +/// - Start with lowercase letters +/// - Do NOT contain dashes +/// +/// ### Tag Syntax Support +/// +/// Custom component tags support multiple syntaxes: +/// - **Start tags**: `` +/// - **End tags**: `` +/// - **Self-closing tags**: `` +/// - **Tags with attributes**: `` +/// +/// ### Examples +/// +/// ```markdown +/// +/// +/// +/// content +/// +/// **Bold text inside custom component** +/// +/// +/// +///

text
+/// text +///

paragraph

+/// ``` #[derive(Debug, PartialEq)] pub struct ComponentCall<'a> { /// Where in the larger document full_string starts. diff --git a/web-markdown/src/render.rs b/web-markdown/src/render.rs index 29760a8..81b4bbd 100644 --- a/web-markdown/src/render.rs +++ b/web-markdown/src/render.rs @@ -189,21 +189,80 @@ where current_component: Option, } -/// Returns true if `raw_html`: -/// - starts with '<' -/// - ends with '>' -/// - does not have any '<' or '>' in between. +/// Returns true if `raw_html` appears to be a custom component tag. /// -/// TODO: -/// An string attribute can a ">" character. +/// A valid custom component tag must: +/// - Start with '<' +/// - End with '>' +/// - Have a tag name that either: +/// - Starts with an uppercase letter (A-Z), OR +/// - Starts with a lowercase letter (a-z) AND contains at least one dash (-) +/// +/// This validation prevents standard HTML tags like `
`, ``, `

` from being +/// treated as custom components while allowing custom component names like: +/// - `` (uppercase start, no dash needed) +/// - `` (lowercase start, has dash) +/// - `` (uppercase start, has dash) +/// +/// The function also handles: +/// - Self-closing tags: `` +/// - Tags with attributes: `` +/// - Closing tags: `` fn can_be_custom_component(raw_html: &str) -> bool { - let chars: Vec<_> = raw_html.trim().chars().collect(); - let len = chars.len(); - if len < 3 { + let s = raw_html.trim(); + if s.len() < 3 { return false; - }; - let (fst, middle, last) = (chars[0], &chars[1..len - 1], chars[len - 1]); - fst == '<' && last == '>' && middle.iter().all(|c| c != &'<' && c != &'>') + } + + let chars: Vec<_> = s.chars().collect(); + + // Must start with '<' and end with '>' + if chars[0] != '<' || chars[chars.len() - 1] != '>' { + return false; + } + + // Extract tag name: skip '<' and optionally '/' for closing tags + let mut idx = 1; + if idx < chars.len() && chars[idx] == '/' { + idx += 1; + } + + if idx >= chars.len() { + return false; + } + + // Parse tag name until we hit whitespace, '/', or '>' + let mut tag_name = String::new(); + while idx < chars.len() { + let c = chars[idx]; + if c.is_whitespace() || c == '/' || c == '>' { + break; + } + tag_name.push(c); + idx += 1; + } + + if tag_name.is_empty() { + return false; + } + + // Check if tag name is valid for a custom component + let first_char = tag_name.chars().next().unwrap(); + let has_dash = tag_name.contains('-'); + + // Valid if: + // - Starts with uppercase letter (A-Z), OR + // - Starts with lowercase letter (a-z) AND contains a dash + if first_char.is_ascii_uppercase() { + // Uppercase start is always valid (e.g., MyComponent, My-Component) + true + } else if first_char.is_ascii_lowercase() && has_dash { + // Lowercase start is only valid if it has a dash (e.g., my-component) + true + } else { + // Otherwise not a custom component (e.g., div, span, p) + false + } } impl<'a, 'callback, 'c, I, F> Iterator for Renderer<'a, 'callback, 'c, I, F> diff --git a/yew-markdown/README.md b/yew-markdown/README.md index f8a0489..12b7f41 100644 --- a/yew-markdown/README.md +++ b/yew-markdown/README.md @@ -41,3 +41,20 @@ see [here](https://rambip.github.io/rust-web-markdown/onclick) ## Custom Components see [here](https://rambip.github.io/rust-web-markdown/custom_components) + +Custom components allow you to embed interactive Yew components in your markdown. + +### Custom Component Naming Rules + +To be recognized as a custom component, tag names must follow these rules: + +1. **Uppercase start** - Tags starting with an uppercase letter (A-Z) are always treated as custom components + - Examples: ``, ``, `` + +2. **Lowercase with dash** - Tags starting with lowercase (a-z) must contain at least one dash (-) + - Examples: ``, ``, `` + +These rules ensure standard HTML tags like `

`, ``, and `

` are not confused with custom components. + +See the [custom-components example](./examples/custom-components) for a complete working example. + From 9934e0d18db41563aa304c1c0d6bbd7c304242a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 15:12:36 +0000 Subject: [PATCH 3/8] Add comprehensive tests for can_be_custom_component function Co-authored-by: rambip <62420525+rambip@users.noreply.github.com> --- web-markdown/src/render.rs | 97 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/web-markdown/src/render.rs b/web-markdown/src/render.rs index 81b4bbd..44f4668 100644 --- a/web-markdown/src/render.rs +++ b/web-markdown/src/render.rs @@ -644,3 +644,100 @@ where }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_can_be_custom_component_uppercase_start() { + // Uppercase start should always be valid + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + } + + #[test] + fn test_can_be_custom_component_lowercase_with_dash() { + // Lowercase start with dash should be valid + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + assert!(can_be_custom_component("")); + } + + #[test] + fn test_can_be_custom_component_lowercase_no_dash() { + // Lowercase start without dash should be invalid (standard HTML tags) + assert!(!can_be_custom_component("

")); + assert!(!can_be_custom_component("")); + assert!(!can_be_custom_component("

")); + assert!(!can_be_custom_component("

")); + assert!(!can_be_custom_component("
")); + assert!(!can_be_custom_component("
")); + assert!(!can_be_custom_component("