Merge pull request #38 from dergigi/render-tables

Add markdown table rendering support
This commit is contained in:
Gigi
2025-11-07 15:22:42 +01:00
committed by GitHub
7 changed files with 421 additions and 6 deletions

2
.env
View File

@@ -1,2 +0,0 @@
# Default article to display on app load
VITE_DEFAULT_ARTICLE_NADDR=naddr1qvzqqqr4gupzqmjxss3dld622uu8q25gywum9qtg4w4cv4064jmg20xsac2aam5nqqxnzd3cxqmrzv3exgmr2wfesgsmew

View File

@@ -1,3 +1,14 @@
# Default article to display on app load
# This should be a valid naddr1... string (NIP-19 encoded address pointer to a kind:30023 long-form article)
VITE_DEFAULT_ARTICLE_NADDR=naddr1qvzqqqr4gupzqmjxss3dld622uu8q25gywum9qtg4w4cv4064jmg20xsac2aam5nqqxnzd3cxqmrzv3exgmr2wfesgsmew
# Nostr configuration for publish-markdown.sh script
# Copy this file to .env and fill in your values
# Your Nostr secret key (nsec, ncryptsec, or hex format)
# You can also set this via environment variable: export NOSTR_SECRET_KEY=your_key
NOSTR_SECRET_KEY=
# Space-separated list of relay URLs to publish to
# If not provided, events will be created but not published
RELAYS="ws://localhost:10547 ws://localhost:4869 wss://relay.primal.net wss://wot.dergigi.com wss://relay.dergigi.com wss://nostr.einundzwanzig.space wss://relay.damus.io wss://relay.nostr.bg wss://nos.lol wss://eden.nostr.land"
# Test account used for publishing markdown test documents:
# npub: npub1marky39a9qmadyuux9lr49pdhy3ddxrdwtmd9y957kye66qyu3vq7spdm2
# Profile: https://read.withboris.com/p/npub1marky39a9qmadyuux9lr49pdhy3ddxrdwtmd9y957kye66qyu3vq7spdm2/writings

2
.gitignore vendored
View File

@@ -13,3 +13,5 @@ applesauce
primal-web-app
Amber
.env
scripts/.env

View File

@@ -8,7 +8,8 @@
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"publish:test:markdown": "./scripts/publish-markdown.sh"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^7.1.0",

202
scripts/publish-markdown.sh Executable file
View File

@@ -0,0 +1,202 @@
#!/bin/bash
# Script to publish markdown files from test/markdown/ to Nostr using nak
# Usage:
# ./scripts/publish-markdown.sh [filename] [relay1] [relay2] ...
# ./scripts/publish-markdown.sh # Interactive mode
# ./scripts/publish-markdown.sh tables.md # Publish specific file
# ./scripts/publish-markdown.sh tables.md wss://relay.example.com # With relay
#
# Environment:
# The script reads .env from the project root directory ($PROJECT_ROOT/.env)
# Required: NOSTR_SECRET_KEY (your nsec, ncryptsec, or hex format key)
# Optional: RELAYS (space-separated list of relay URLs)
#
# Test account for markdown test documents:
# npub: npub1marky39a9qmadyuux9lr49pdhy3ddxrdwtmd9y957kye66qyu3vq7spdm2
# Profile: https://read.withboris.com/p/npub1marky39a9qmadyuux9lr49pdhy3ddxrdwtmd9y957kye66qyu3vq7spdm2/writings
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
MARKDOWN_DIR="$PROJECT_ROOT/test/markdown"
ENV_FILE="$PROJECT_ROOT/.env"
# Load .env file if it exists
if [ -f "$ENV_FILE" ]; then
# Source the .env file, handling quoted values properly
set -a # Automatically export all variables
# Use eval to properly handle quoted values (safe since we control the file)
# This handles both unquoted and quoted values correctly
while IFS= read -r line || [ -n "$line" ]; do
# Skip comments and empty lines
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ -z "$line" ]] && continue
# Remove leading/trailing whitespace
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Export the variable (handles quoted values)
eval "export $line"
done < "$ENV_FILE"
set +a # Stop automatically exporting
fi
# Check if nak is installed
if ! command -v nak &> /dev/null; then
echo "Error: nak is not installed or not in PATH"
echo "Install from: https://github.com/fiatjaf/nak"
exit 1
fi
# Function to publish a markdown file
publish_file() {
local file_path="$1"
shift # Remove first argument, rest are relay URLs
local relays=("$@")
local filename=$(basename "$file_path")
local identifier="${filename%.md}" # Remove .md extension
echo "📝 Publishing: $filename"
echo " Identifier: $identifier"
# Extract title from first H1 if available, otherwise use filename
local title=$(grep -m 1 "^# " "$file_path" | sed 's/^# //' || echo "$identifier")
# Add relays if provided
if [ ${#relays[@]} -gt 0 ]; then
echo " Relays: ${relays[*]}"
else
echo " Note: No relays specified. Event will be created but not published."
echo " Add relay URLs as arguments to publish, e.g.: wss://relay.example.com"
fi
# Publish as kind 30023 (NIP-23 blog post)
# The "d" tag is required for replaceable events (kind 30023)
# Using the filename (without extension) as the identifier
# Build command array to avoid eval issues
# Use @filename syntax to read content from file (nak supports this)
local cmd_args=(
"event"
"-k" "30023"
"-d" "$identifier"
"-t" "title=$title"
"--content" "@$file_path"
)
# Add relays if provided
if [ ${#relays[@]} -gt 0 ]; then
cmd_args+=("${relays[@]}")
fi
nak "${cmd_args[@]}"
if [ $? -eq 0 ]; then
echo "✅ Successfully published: $filename"
else
echo "❌ Failed to publish: $filename"
return 1
fi
}
# Check for NOSTR_SECRET_KEY
if [ -z "$NOSTR_SECRET_KEY" ]; then
echo "⚠️ Warning: NOSTR_SECRET_KEY environment variable not set"
echo " Set it in .env file or with: export NOSTR_SECRET_KEY=your_key_here"
echo " Or use --prompt-sec flag (nak will prompt for key)"
echo ""
fi
# Parse RELAYS from environment if set
default_relays=()
if [ -n "$RELAYS" ]; then
# Split RELAYS string into array
read -ra default_relays <<< "$RELAYS"
fi
# Main logic
if [ $# -eq 0 ]; then
# No arguments: list all markdown files and let user choose
echo "Available markdown files:"
echo ""
files=("$MARKDOWN_DIR"/*.md)
if [ ! -e "${files[0]}" ]; then
echo "No markdown files found in $MARKDOWN_DIR"
exit 1
fi
# Display files with numbers
declare -a file_array
i=1
for file in "${files[@]}"; do
filename=$(basename "$file")
echo " $i) $filename"
file_array[$i]="$file"
((i++))
done
echo ""
echo "Enter file number(s) to publish (space-separated), or 'all' for all files:"
read -r selection
echo ""
if [ ${#default_relays[@]} -gt 0 ]; then
echo "Enter relay URLs (space-separated, or press Enter to use defaults from .env):"
echo " Defaults: ${default_relays[*]}"
else
echo "Enter relay URLs (space-separated, or press Enter to skip):"
fi
read -r relay_input
# Parse relay URLs
relays=()
if [ -n "$relay_input" ]; then
read -ra relays <<< "$relay_input"
elif [ ${#default_relays[@]} -gt 0 ]; then
# Use defaults from .env
relays=("${default_relays[@]}")
fi
if [ "$selection" = "all" ]; then
# Publish all files
for file in "${files[@]}"; do
publish_file "$file" "${relays[@]}"
echo ""
done
else
# Publish selected files
for num in $selection; do
if [ -n "${file_array[$num]}" ]; then
publish_file "${file_array[$num]}" "${relays[@]}"
echo ""
else
echo "⚠️ Invalid selection: $num"
fi
done
fi
else
# Argument provided: publish specific file
filename="$1"
shift # Remove filename, rest are relay URLs
relays=("$@")
# If no relays provided as arguments, use defaults from .env
if [ ${#relays[@]} -eq 0 ] && [ ${#default_relays[@]} -gt 0 ]; then
relays=("${default_relays[@]}")
fi
# If filename doesn't end with .md, add it
if [[ ! "$filename" =~ \.md$ ]]; then
filename="${filename}.md"
fi
file_path="$MARKDOWN_DIR/$filename"
if [ ! -f "$file_path" ]; then
echo "Error: File not found: $file_path"
exit 1
fi
publish_file "$file_path" "${relays[@]}"
fi

View File

@@ -170,6 +170,49 @@
.reader-html pre { background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: 8px; padding: 1rem; overflow-x: auto; margin: 1rem 0; font-family: 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace; }
.reader-html code { background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: 4px; padding: 0.15rem 0.4rem; font-size: 0.9em; font-family: 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace; }
.reader-html pre code { background: transparent; border: none; padding: 0; display: block; }
/* Tables - subtle styling that matches the app theme */
.reader-markdown table, .reader-html table {
width: 100%;
border-collapse: collapse;
margin: 1.5rem 0;
border: 1px solid var(--color-border);
border-radius: 8px;
overflow: hidden;
background: var(--color-bg);
}
.reader-markdown thead, .reader-html thead {
background: var(--color-bg-elevated);
}
.reader-markdown th, .reader-html th {
padding: 0.75rem 1rem;
text-align: left;
font-weight: 600;
color: var(--color-text);
border-bottom: 2px solid var(--color-border);
font-size: 0.95em;
}
.reader-markdown td, .reader-html td {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--color-border-subtle);
color: var(--color-text);
vertical-align: top;
}
.reader-markdown tbody tr:last-child td, .reader-html tbody tr:last-child td {
border-bottom: none;
}
/* Subtle row striping for better readability */
.reader-markdown tbody tr:nth-child(even), .reader-html tbody tr:nth-child(even) {
background: var(--color-bg-subtle);
}
/* Table alignment support */
.reader-markdown th[align="center"], .reader-html th[align="center"],
.reader-markdown td[align="center"], .reader-html td[align="center"] {
text-align: center;
}
.reader-markdown th[align="right"], .reader-html th[align="right"],
.reader-markdown td[align="right"], .reader-html td[align="right"] {
text-align: right;
}
/* Mobile: prevent code blocks from causing horizontal overflow */
@media (max-width: 768px) {
.reader-markdown pre, .reader-html pre {
@@ -190,6 +233,12 @@
display: block;
max-width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Reduce padding on mobile for better fit */
.reader-markdown table td, .reader-html table td,
.reader-markdown table th, .reader-html table th {
padding: 0.5rem 0.75rem;
}
.reader-markdown img, .reader-html img {

152
test/markdown/tables.md Normal file
View File

@@ -0,0 +1,152 @@
# Markdown Tables Test
This file contains various markdown table examples to test table parsing and rendering.
## Basic Table
This is a simple two-column table with two data rows. It tests basic table structure and rendering without any special formatting or alignment.
| Column 1 | Column 2 |
| ------------- | ------------- |
| Cell 1, Row 1 | Cell 2, Row 1 |
| Cell 1, Row 2 | Cell 2, Row 2 |
## Table with Alignment
This table demonstrates text alignment options in markdown tables. The first column is left-aligned (default), the second is centered using `:---:`, and the third is right-aligned using `---:`. This tests that the CSS alignment rules work correctly.
| Left | Centered | Right |
| :----------- | :--------------: | -------------------------: |
| This is left | Text is centered | And this is right-aligned |
| More text | Even more text | And even more to the right |
## Table with Formatting
This table contains various markdown formatting within cells: italic text using asterisks, bold text using double asterisks, and inline code using backticks. This tests that formatting is preserved and rendered correctly within table cells.
| Name | Location | Food |
| ------- | ------------ | ------- |
| *Alice* | **New York** | `Pizza` |
| Bob | Paris | Crepes |
## Table with Links
This table includes markdown links within cells. It tests that hyperlinks are properly rendered and clickable within table cells, and that link styling matches the app's theme.
| Name | Website | Description |
| ----- | -------------------------- | --------------------- |
| Alice | [GitHub](https://github.com) | Code repository |
| Bob | [Nostr](https://nostr.com) | Decentralized network |
## Table with Code Blocks
This table contains inline code examples in cells. It tests that code formatting (monospace font, background, borders) is properly applied within table cells and doesn't conflict with table styling.
| Language | Example |
| -------- | -------------------------- |
| Python | `print("Hello, World!")` |
| JavaScript | `console.log("Hello")` |
| SQL | `SELECT * FROM users` |
## Wide Table (Testing Horizontal Scroll)
This table has eight columns to test horizontal scrolling behavior on mobile devices and smaller screens. The table should allow users to scroll horizontally to view all columns while maintaining proper styling and readability.
| Column 1 | Column 2 | Column 3 | Column 4 | Column 5 | Column 6 | Column 7 | Column 8 |
| -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
| Data 1 | Data 2 | Data 3 | Data 4 | Data 5 | Data 6 | Data 7 | Data 8 |
| More | Content | Here | To | Test | Scrolling| Behavior | Mobile |
## Table with Mixed Content
This table combines various content types: currency values, emoji indicators, and descriptive text. It tests how different content types render together within table cells and ensures proper spacing and alignment.
| Item | Price | Status | Notes |
| ---- | ----- | ------ | ------------------------------ |
| Apple | $1.00 | ✅ In stock | Fresh from the farm |
| Banana | $0.50 | ⚠️ Low stock | Last few left |
| Orange | $1.25 | ❌ Out of stock | Coming next week |
## Table with Empty Cells
This table contains empty cells to test how the table styling handles missing data. Empty cells should still maintain proper borders and spacing, ensuring the table structure remains intact.
| Name | Email | Phone |
| ---- | ----- | ----- |
| Alice | alice@example.com | |
| Bob | | 555-1234 |
| Charlie | charlie@example.com | 555-5678 |
## Table with Long Text
This table tests text wrapping behavior with varying column widths. The third column contains a long paragraph that should wrap to multiple lines within the cell while maintaining proper padding and readability. This is especially important for responsive design.
| Short | Medium Length Column | Very Long Column That Contains A Lot Of Text And Should Wrap Properly |
| ----- | -------------------- | -------------------------------------------------------------------- |
| A | This is medium text | This is a very long piece of text that should wrap to multiple lines when displayed in the table cell. It should maintain proper formatting and readability. |
## Table with Numbers
This table contains 21 rows of ranked data with numeric scores and percentages. It's useful for testing row striping, scrolling behavior with longer tables, and ensuring that numeric alignment and formatting remain consistent throughout a larger dataset.
| Rank | Name | Score | Percentage |
| ---- | ---- | ----- | ---------- |
| 1 | Alice | 95 | 95% |
| 2 | Bob | 87 | 87% |
| 3 | Charlie | 82 | 82% |
| 4 | David | 78 | 78% |
| 5 | Emma | 75 | 75% |
| 6 | Frank | 72 | 72% |
| 7 | Grace | 70 | 70% |
| 8 | Henry | 68 | 68% |
| 9 | Ivy | 65 | 65% |
| 10 | Jack | 63 | 63% |
| 11 | Kate | 60 | 60% |
| 12 | Liam | 58 | 58% |
| 13 | Mia | 55 | 55% |
| 14 | Noah | 53 | 53% |
| 15 | Olivia | 50 | 50% |
| 16 | Paul | 48 | 48% |
| 17 | Quinn | 45 | 45% |
| 18 | Ryan | 43 | 43% |
| 19 | Sarah | 40 | 40% |
| 20 | Tom | 38 | 38% |
| 21 | Uma | 35 | 35% |
## Table with Special Characters
This table contains escaped special characters that have meaning in markdown syntax. It tests that these characters are properly escaped and displayed as literal characters rather than being interpreted as markdown syntax.
| Symbol | Name | Usage |
| ------ | ---- | ----- |
| `\|` | Pipe | Used in markdown tables |
| `\*` | Asterisk | Used for bold/italic |
| `\#` | Hash | Used for headings |
## Table with Headers Only
This table contains only header rows with no data rows. It tests edge case handling for tables without content, ensuring that the header styling is still applied correctly even when there's no body content.
| Header 1 | Header 2 | Header 3 |
| -------- | -------- | -------- |
## Single Column Table
This is a minimal table with only one column. It tests how table styling handles narrow tables and ensures that single-column layouts are properly formatted with appropriate borders and spacing.
| Item |
| ---- |
| First |
| Second |
| Third |
## Table with Nested Formatting
This table demonstrates complex nested formatting combinations within cells, including bold and italic text together, code blocks containing links, and strikethrough text. It tests that multiple formatting types can coexist properly within table cells.
| Description | Example |
| ----------- | ------- |
| Bold and italic | ***Important*** |
| Code and link | `[Click here](https://example.com)` |
| Strikethrough | ~~Old price~~ |