dailytags | Skill Performance & Reviews | TopRankSkills

TopRank Skills

Home / Skills / tools / dailytags

dailytags

maintained by jayteealao

star 0 account_tree 0 verified_user MIT License
bolt View GitHub

name: dailytags description: Comprehensive guide for using DailyTags - a Jetpack Compose markdown library that supports custom tags. Use this skill when working with markdown rendering, custom markup, rich text formatting in Compose, or creating pattern-based text parsers for Android applications. DailyTags parses text into nodes styled with AnnotatedString, providing a WebView-free solution for rendering markdown and HTML with full customization support.

DailyTags Skill

Overview

DailyTags is a flexible markdown library for Jetpack Compose that parses markup into rich text using AnnotatedString. It's lightweight (<50kb), fast, extensible, and requires no WebView.

Key Capabilities:

  • ✅ Markdown parsing with built-in rules
  • ✅ HTML support with built-in rules
  • ✅ Custom tag/markup creation via regex patterns
  • ✅ Full styling control (SpanStyle, ParagraphStyle)
  • ✅ Node-based parsing architecture
  • ✅ Native Compose integration

Important Limitation:

  • ⚠️ Text content only - Cannot render images, tables, checkboxes, or custom UI elements
  • For non-text elements, use hybrid approach with regular Compose components

Installation

Gradle Setup

Add JitPack repository to your root build.gradle:

allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}

Add dependency to your module build.gradle:

dependencies {
    implementation "com.github.DmytroShuba:DailyTags:1.0.0"
}

Current Version: 1.0.0 (February 2022)
Min API Level: 23+
License: MIT

Core Architecture

Parsing Flow

Source Text → Rules → Parser → Nodes → Render → AnnotatedString → Text Composable

Key Components

  1. SimpleMarkupParser: Main parsing engine that processes text using rules
  2. Rules: Pattern-based definitions for how to extract and style content
  3. Nodes: Internal representation of parsed elements
  4. AnnotatedString: Compose's rich text format (final output)

Basic Usage

1. Simple Markdown Rendering

import com.dmytroshuba.dailytags.markdown.rules.MarkdownRules
import com.dmytroshuba.dailytags.core.simple.SimpleMarkupParser
import com.dmytroshuba.dailytags.core.simple.render
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable

@Composable
fun MarkdownText(source: String) {
    val parser = SimpleMarkupParser()
    val rules = MarkdownRules.toList()
    
    val content = parser
        .parse(source, rules)
        .render()
        .toAnnotatedString()
    
    Text(text = content)
}

// Usage
MarkdownText("**Bold text** and *italic text*")

2. Markdown + HTML Combined

import com.dmytroshuba.dailytags.markdown.rules.HtmlRules

@Composable
fun RichText(source: String) {
    val parser = SimpleMarkupParser()
    val rules = MarkdownRules.toList() + HtmlRules.toList()
    
    val content = parser
        .parse(source, rules)
        .render()
        .toAnnotatedString()
    
    Text(text = content)
}

// Usage
RichText("""
    <b>HTML bold</b> and **Markdown bold**
    <i>HTML italic</i> and *Markdown italic*
""")

Custom Tags & Markup

Creating Custom Rules

Step 1: Define Pattern

Use Java's Pattern class to create regex patterns:

import java.util.regex.Pattern

// Highlight tag: <hl-blue>text</hl-blue>
val PATTERN_HIGHLIGHT_BLUE = Pattern.compile("^<hl-blue>([\\s\\S]+?)<\\/hl-blue>")

// Spoiler tag: ||hidden text||
val PATTERN_SPOILER = Pattern.compile("^\\|\\|([\\s\\S]+?)\\|\\|")

// Mention tag: @username
val PATTERN_MENTION = Pattern.compile("^@([a-zA-Z0-9_]+)")

// Code language tag: ```kotlin code ```
val PATTERN_CODE_BLOCK = Pattern.compile("^```(\\w+)\\n([\\s\\S]+?)```")

Step 2: Convert Pattern to Rule

Use the toRule() extension function:

import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import com.dmytroshuba.dailytags.core.simple.toRule

// Basic styling
val blueHighlightRule = PATTERN_HIGHLIGHT_BLUE.toRule(
    spanStyle = SpanStyle(
        color = Color.Blue,
        background = Color.Blue.copy(alpha = 0.1f)
    )
)

// Complex styling with multiple properties
val spoilerRule = PATTERN_SPOILER.toRule(
    spanStyle = SpanStyle(
        color = Color.Black,
        background = Color.DarkGray
    )
)

val mentionRule = PATTERN_MENTION.toRule(
    spanStyle = SpanStyle(
        color = Color(0xFF1DA1F2), // Twitter blue
        fontWeight = FontWeight.Bold
    )
)

Step 3: Use Custom Rules

@Composable
fun CustomMarkupText(source: String) {
    val parser = SimpleMarkupParser()
    
    // Combine built-in and custom rules
    val rules = MarkdownRules.toList() + listOf(
        blueHighlightRule,
        spoilerRule,
        mentionRule
    )
    
    val content = parser
        .parse(source, rules)
        .render()
        .toAnnotatedString()
    
    Text(text = content)
}

// Usage
CustomMarkupText("""
    Regular text with <hl-blue>blue highlight</hl-blue>
    This is a ||spoiler||
    Hey @username, check this out!
""")

Advanced Patterns

1. Paragraph Styling

import androidx.compose.ui.text.ParagraphStyle
import androidx.compose.ui.unit.sp
import androidx.compose.ui.text.style.TextIndent

val PATTERN_QUOTE = Pattern.compile("^> (.+)$", Pattern.MULTILINE)

val quoteRule = PATTERN_QUOTE.toRule(
    spanStyle = SpanStyle(
        color = Color.Gray,
        fontStyle = FontStyle.Italic
    ),
    paragraphStyle = ParagraphStyle(
        textIndent = TextIndent(firstLine = 16.sp),
        lineHeight = 20.sp
    )
)

2. Multi-Group Patterns

Extract and style different groups separately:

// Pattern with capturing groups
val PATTERN_LINK = Pattern.compile("^\\[([^\\]]+)\\]\\(([^)]+)\\)")

// Custom rendering with different styles for text and URL
val linkRule = PATTERN_LINK.toRule(
    spanStyle = SpanStyle(
        color = Color.Blue,
        textDecoration = TextDecoration.Underline
    )
)

3. Nested Rules

Rules are processed in order, allowing nested markup:

val rules = listOf(
    // Process outer tags first
    blockquoteRule,
    // Then inner formatting
    boldRule,
    italicRule,
    codeRule
)

4. Conditional Styling

Create rules that apply different styles based on content:

val PATTERN_TAG = Pattern.compile("^#(\\w+)")

fun createTagRule(color: Color) = PATTERN_TAG.toRule(
    spanStyle = SpanStyle(
        color = color,
        fontWeight = FontWeight.Medium,
        background = color.copy(alpha = 0.1f)
    )
)

// Usage with different colors
val tagRules = listOf(
    createTagRule(Color.Red),
    createTagRule(Color.Blue),
    createTagRule(Color.Green)
)

Important Limitations

DailyTags works with TEXT CONTENT ONLY. It cannot render:

  • ❌ Images (including ![alt](url) and <img alt="dailytags image"> tags)
  • ❌ Tables
  • ❌ Checkboxes / Task lists
  • ❌ Custom UI elements
  • ❌ Embedded media (video, audio)
  • ❌ Interactive widgets

What this means:

  • Image syntax like ![Alt text](url) will be parsed but NOT display an image
  • Table markdown will be treated as plain text
  • Checkbox syntax - [ ] will render as text, not interactive checkboxes
  • Only text styling (bold, italic, color, etc.) is supported

For these features, you need to:

  • Use regular Compose Image() composables separately
  • Build custom UI components outside DailyTags
  • Consider hybrid approaches (DailyTags for text, Compose for UI elements)

Supported Markdown Features

Text Formatting

  • Bold: **text** or __text__
  • Italic: *text* or _text_
  • Strikethrough: ~~text~~
  • Inline code: `code`
  • Combined: ***bold italic***

Headings

# H1 Heading
## H2 Heading
### H3 Heading
#### H4 Heading
##### H5 Heading
###### H6 Heading

Lists

- Unordered list item 1
- Unordered list item 2
  - Nested item

1. Ordered list item 1
2. Ordered list item 2
   1. Nested numbered item

Links and Images (Text Only)

Links:

[Link text](https://example.com)

Note: Links are styled but not clickable by default. Use ClickableText for interactivity.

Images:

![Alt text](https://example.com/image.jpg)

⚠️ Important: Image markdown is parsed as text only - no actual image rendering. You'll see the alt text, not the image. Use Compose's Image() composable separately if you need to display images.

Code Blocks

```
Code block
```

```kotlin
fun example() {
    println("Language-specific")
}
```

Blockquotes

> This is a quote
> It can span multiple lines

Horizontal Rules

---
***
___

Supported HTML Tags

When using HtmlRules:

Text Formatting:

  • <b> / <strong> - Bold
  • <i> / <em> - Italic
  • <u> - Underline
  • <s> / <strike> - Strikethrough
  • <code> - Inline code
  • <pre> - Preformatted text

Structure:

  • <h1> to <h6> - Headings
  • <p> - Paragraphs
  • <br> / <br/> - Line breaks
  • <ul>, <ol>, <li> - Lists
  • <blockquote> - Quotes

Links (Text Only):

  • <a href=""> - Links (styled but not clickable by default)

Not Rendered:

  • <img" alt="dailytags image"> - ⚠️ Parsed but does NOT display images
  • <table>, <tr>, <td> - ⚠️ Not supported, treated as plain text
  • <video>, <audio> - ⚠️ Not supported
  • <input>, <button> - ⚠️ Not supported

Best Practices

1. Hybrid Approach for Images and UI Elements

Since DailyTags only handles text, combine it with regular Compose for non-text elements:

@Composable
fun RichContent(markdown: String, images: List<ImageData>) {
    Column {
        // Render markdown text
        val textContent = parseMarkdown(markdown)
        Text(text = textContent)
        
        // Render images separately
        images.forEach { imageData ->
            AsyncImage(
                model = imageData.url,
                contentDescription = imageData.alt,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(vertical = 8.dp)
            )
        }
    }
}

For tables, use custom Compose layouts:

@Composable
fun MarkdownWithTable(
    beforeTable: String,
    tableData: TableData,
    afterTable: String
) {
    Column {
        MarkdownText(beforeTable)
        
        // Custom table rendering
        TableComposable(tableData)
        
        MarkdownText(afterTable)
    }
}

For checkboxes/task lists:

@Composable
fun TaskList(tasks: List<Task>) {
    tasks.forEach { task ->
        Row(verticalAlignment = Alignment.CenterVertically) {
            Checkbox(
                checked = task.completed,
                onCheckedChange = { task.onToggle() }
            )
            // Use DailyTags for task description
            MarkdownText(task.description)
        }
    }
}

2. Rule Organization

object CustomMarkupRules {
    // Define all patterns
    private val PATTERN_HIGHLIGHT = Pattern.compile("...")
    private val PATTERN_SPOILER = Pattern.compile("...")
    private val PATTERN_MENTION = Pattern.compile("...")
    
    // Define all rules
    private val highlightRule = PATTERN_HIGHLIGHT.toRule(...)
    private val spoilerRule = PATTERN_SPOILER.toRule(...)
    private val mentionRule = PATTERN_MENTION.toRule(...)
    
    // Public function to get all rules
    fun toList() = listOf(
        highlightRule,
        spoilerRule,
        mentionRule
    )
}

// Usage
val rules = MarkdownRules.toList() + CustomMarkupRules.toList()

2. Parser Reuse

Cache the parser instance for better performance:

class MarkdownViewModel : ViewModel() {
    private val parser = SimpleMarkupParser()
    private val rules = MarkdownRules.toList()
    
    fun parseMarkdown(source: String): AnnotatedString {
        return parser
            .parse(source, rules)
            .render()
            .toAnnotatedString()
    }
}

3. Theme Integration

Adapt styling to Material Theme:

@Composable
fun ThemedMarkdown(source: String) {
    val colorScheme = MaterialTheme.colorScheme
    val typography = MaterialTheme.typography
    
    val customRules = remember(colorScheme, typography) {
        listOf(
            createCodeRule(colorScheme.primaryContainer),
            createLinkRule(colorScheme.primary),
            createQuoteRule(colorScheme.outline)
        )
    }
    
    val allRules = MarkdownRules.toList() + customRules
    // ... parse and render
}

4. Performance Optimization

For long documents, consider lazy rendering:

@Composable
fun LongMarkdownDocument(source: String) {
    val sections = remember(source) {
        source.split("\n\n") // Split by paragraphs
    }
    
    LazyColumn {
        items(sections) { section ->
            MarkdownText(section)
        }
    }
}

5. Interactive Elements

Combine with clickable modifiers:

val PATTERN_CLICKABLE = Pattern.compile("^\\[\\[([^\\]]+)\\]\\]")

@Composable
fun InteractiveMarkdown(
    source: String,
    onLinkClick: (String) -> Unit
) {
    val annotatedString = parser
        .parse(source, rules)
        .render()
        .toAnnotatedString()
    
    ClickableText(
        text = annotatedString,
        onClick = { offset ->
            // Handle clicks on links
            annotatedString.getStringAnnotations(
                tag = "URL",
                start = offset,
                end = offset
            ).firstOrNull()?.let { annotation ->
                onLinkClick(annotation.item)
            }
        }
    )
}

Common Patterns & Examples

1. Chat Message Formatting

val PATTERN_USER_MENTION = Pattern.compile("^@([a-zA-Z0-9_]+)")
val PATTERN_CHANNEL = Pattern.compile("^#([a-zA-Z0-9_-]+)")
val PATTERN_EMOJI = Pattern.compile("^:([a-zA-Z0-9_]+):")

val chatRules = listOf(
    PATTERN_USER_MENTION.toRule(
        spanStyle = SpanStyle(
            color = Color(0xFF1DA1F2),
            fontWeight = FontWeight.Bold
        )
    ),
    PATTERN_CHANNEL.toRule(
        spanStyle = SpanStyle(
            color = Color(0xFF4A90E2),
            fontWeight = FontWeight.Medium
        )
    ),
    PATTERN_EMOJI.toRule(
        spanStyle = SpanStyle(fontSize = 20.sp)
    )
) + MarkdownRules.toList()

2. Code Documentation

val PATTERN_PARAM = Pattern.compile("^@param\\s+(\\w+)\\s+(.+)$", Pattern.MULTILINE)
val PATTERN_RETURN = Pattern.compile("^@return\\s+(.+)$", Pattern.MULTILINE)

val docRules = listOf(
    PATTERN_PARAM.toRule(
        spanStyle = SpanStyle(
            color = Color(0xFF569CD6),
            fontFamily = FontFamily.Monospace
        )
    ),
    PATTERN_RETURN.toRule(
        spanStyle = SpanStyle(
            color = Color(0xFF4EC9B0),
            fontFamily = FontFamily.Monospace
        )
    )
) + MarkdownRules.toList()

3. Syntax Highlighting Preview

val PATTERN_KEYWORD = Pattern.compile("^\\b(fun|val|var|class|object)\\b")
val PATTERN_STRING = Pattern.compile("^\"([^\"]*?)\"")
val PATTERN_COMMENT = Pattern.compile("^//(.+)$", Pattern.MULTILINE)

val syntaxRules = listOf(
    PATTERN_KEYWORD.toRule(
        spanStyle = SpanStyle(
            color = Color(0xFFCC7832),
            fontWeight = FontWeight.Bold
        )
    ),
    PATTERN_STRING.toRule(
        spanStyle = SpanStyle(color = Color(0xFF6A8759))
    ),
    PATTERN_COMMENT.toRule(
        spanStyle = SpanStyle(
            color = Color(0xFF808080),
            fontStyle = FontStyle.Italic
        )
    )
)

4. Social Media Post

val PATTERN_HASHTAG = Pattern.compile("^#(\\w+)")
val PATTERN_URL = Pattern.compile("^(https?://[^\\s]+)")

val socialRules = listOf(
    PATTERN_HASHTAG.toRule(
        spanStyle = SpanStyle(
            color = Color(0xFF1DA1F2),
            fontWeight = FontWeight.Medium
        )
    ),
    PATTERN_URL.toRule(
        spanStyle = SpanStyle(
            color = Color(0xFF1DA1F2),
            textDecoration = TextDecoration.Underline
        )
    )
) + MarkdownRules.toList()

5. Math Notation (Simple)

val PATTERN_INLINE_MATH = Pattern.compile("^\\$([^\\$]+)\\$")
val PATTERN_BLOCK_MATH = Pattern.compile("^\\$\\$([\\s\\S]+?)\\$\\$")

val mathRules = listOf(
    PATTERN_INLINE_MATH.toRule(
        spanStyle = SpanStyle(
            fontFamily = FontFamily.Serif,
            fontStyle = FontStyle.Italic,
            color = Color(0xFF8B4513)
        )
    ),
    PATTERN_BLOCK_MATH.toRule(
        spanStyle = SpanStyle(
            fontFamily = FontFamily.Serif,
            fontSize = 18.sp
        ),
        paragraphStyle = ParagraphStyle(
            textAlign = TextAlign.Center
        )
    )
)

Troubleshooting

Pattern Not Matching

Problem: Custom pattern doesn't match expected text

Solutions:

  1. Test regex separately:
val pattern = Pattern.compile("your pattern here")
val matcher = pattern.matcher("test text")
if (matcher.find()) {
    println("Match: ${matcher.group()}")
} else {
    println("No match")
}
  1. Use proper flags:
// Case insensitive
Pattern.compile("pattern", Pattern.CASE_INSENSITIVE)

// Multiline mode
Pattern.compile("pattern", Pattern.MULTILINE)

// Dot matches newlines
Pattern.compile("pattern", Pattern.DOTALL)

// Combine flags
Pattern.compile("pattern", Pattern.CASE_INSENSITIVE or Pattern.MULTILINE)
  1. Escape special characters:
// Wrong: Pattern.compile("^$")
// Correct:
Pattern.compile("^\\$")

Rules Not Applied

Problem: Rules defined but styling not appearing

Checklist:

  1. ✓ Rules added to parser: parser.parse(source, rules)
  2. ✓ Rule order matters: More specific rules should come before general ones
  3. ✓ Pattern starts with ^: All patterns must start with ^ anchor
  4. ✓ Called .render().toAnnotatedString(): Both methods required
  5. ✓ SpanStyle vs ParagraphStyle: Use appropriate style type

Performance Issues

Problem: Slow rendering for large documents

Solutions:

  1. Split content into sections and render lazily:
LazyColumn {
    items(sections) { section ->
        MarkdownSection(section)
    }
}
  1. Cache parsed results:
val cachedContent = remember(source) {
    parser.parse(source, rules)
        .render()
        .toAnnotatedString()
}
  1. Limit rule complexity - simpler patterns parse faster

Styling Not Visible

Problem: Styles defined but not visible in UI

Debug steps:

  1. Check Text composable properties:
Text(
    text = content,
    // Ensure these don't override AnnotatedString styling
    color = Color.Unspecified, // Don't override
    fontSize = TextUnit.Unspecified // Don't override
)
  1. Verify SpanStyle colors contrast with background
  2. Check MaterialTheme isn't overriding colors

Nested Tags Not Working

Problem: Tags inside tags not parsing correctly

Solution: Order rules from outermost to innermost:

val rules = listOf(
    blockquoteRule,      // Outer
    boldRule,            // Middle  
    italicRule,          // Middle
    inlineCodeRule       // Inner
)

Migration & Version Notes

From Plain Text:

  • Simply wrap existing text with DailyTags parser
  • No changes needed to text format for basic markdown

From WebView:

  • Remove WebView dependencies
  • Replace with DailyTags parser
  • Significant performance improvement
  • Better integration with Compose UI

From Other Markdown Libraries:

  • Check supported features list (not all markdown specs supported)
  • Some features may need custom rules
  • Pattern syntax uses Java regex

Resources

Quick Reference

Essential Imports

import com.dmytroshuba.dailytags.core.simple.SimpleMarkupParser
import com.dmytroshuba.dailytags.core.simple.render
import com.dmytroshuba.dailytags.core.simple.toRule
import com.dmytroshuba.dailytags.markdown.rules.MarkdownRules
import com.dmytroshuba.dailytags.markdown.rules.HtmlRules
import java.util.regex.Pattern
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.ParagraphStyle

Minimal Working Example

@Composable
fun MinimalMarkdown() {
    val source = "**Bold** and *italic* text"
    val parser = SimpleMarkupParser()
    val content = parser
        .parse(source, MarkdownRules.toList())
        .render()
        .toAnnotatedString()
    
    Text(text = content)
}

Complete Custom Rule Example

// 1. Pattern
val PATTERN = Pattern.compile("^<tag>([\\s\\S]+?)</tag>")

// 2. Rule
val rule = PATTERN.toRule(
    spanStyle = SpanStyle(
        color = Color.Red,
        fontWeight = FontWeight.Bold
    )
)

// 3. Parse
val content = SimpleMarkupParser()
    .parse(source, listOf(rule))
    .render()
    .toAnnotatedString()

// 4. Display
Text(text = content)

Remember

  1. Text content only - No images, tables, checkboxes, or custom UI elements
  2. Patterns must start with ^ - This is mandatory for DailyTags
  3. Rule order matters - Process outer/general rules before inner/specific ones
  4. Both render() and toAnnotatedString() required - Don't skip either step
  5. Use remember() for performance - Cache parsed results when possible
  6. Test patterns independently - Verify regex before creating rules
  7. SpanStyle for inline, ParagraphStyle for blocks - Choose the right style type
  8. Combine built-in rules freely - MarkdownRules + HtmlRules + CustomRules
  9. Library is lightweight - No WebView overhead, pure Compose
  10. Use hybrid approach - Combine DailyTags text rendering with regular Compose UI for non-text elements

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!

Skill Details

GitHub Stars 0
GitHub Forks 0
Created Mar 2026
Last Updated il y a 3 mois
tools tools ide plugins

Related Skills

writing-skills
chevron_right
codex
chevron_right
smart-illustrator
chevron_right
collaborating-with-codex
chevron_right
code-review-router
chevron_right

Build your own?

Join 12,000+ developers contributing to the Claude ecosystem.