atproto-based blog in ruby

json to md + syntax highlighting

+124 -1
+116
public/to_md.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <title>JSON to Markdown</title> 6 + <style> 7 + body { 8 + font-family: sans-serif; 9 + margin: 2rem; 10 + display: flex; 11 + flex-direction: column; 12 + gap: 1rem; 13 + } 14 + textarea { 15 + width: 100%; 16 + height: 200px; 17 + font-family: monospace; 18 + padding: 1rem; 19 + border-radius: 8px; 20 + border: 1px solid #ccc; 21 + } 22 + button { 23 + padding: 0.5rem 1rem; 24 + font-size: 1rem; 25 + border: none; 26 + background: #0070f3; 27 + color: white; 28 + border-radius: 6px; 29 + cursor: pointer; 30 + align-self: start; 31 + } 32 + button:hover { 33 + background: #0059c9; 34 + } 35 + </style> 36 + </head> 37 + <body> 38 + <h1>🌀 JSON to Markdown</h1> 39 + 40 + <label for="json-input">Paste Blog Post JSON:</label> 41 + <textarea id="json-input" placeholder="Paste your JSON here..."></textarea> 42 + 43 + <button onclick="convertToMarkdown()">Convert</button> 44 + 45 + <label for="markdown-output">Markdown Output:</label> 46 + <textarea id="markdown-output" readonly></textarea> 47 + 48 + <script> 49 + function convertToMarkdown() { 50 + const input = document.getElementById('json-input').value; 51 + let output = ''; 52 + 53 + try { 54 + const data = JSON.parse(input); 55 + const blocks = data.content || []; 56 + 57 + const formatSpan = (text, formatting) => { 58 + if (!formatting) return text; 59 + formatting.forEach(f => { 60 + const type = f["$type"]; 61 + switch (type) { 62 + case "net.shreyanjain.richtext.formatting#bold": 63 + text = `**${text}**`; 64 + break; 65 + case "net.shreyanjain.richtext.formatting#italic": 66 + text = `*${text}*`; 67 + break; 68 + case "net.shreyanjain.richtext.formatting#underline": 69 + text = `<u>${text}</u>`; // Markdown has no native underline 70 + break; 71 + case "net.shreyanjain.richtext.formatting#code": 72 + text = `\`${text}\``; 73 + break; 74 + case "net.shreyanjain.richtext.formatting#link": 75 + text = `[${text}](${f.href})`; 76 + break; 77 + } 78 + }); 79 + return text; 80 + }; 81 + 82 + blocks.forEach(block => { 83 + switch (block["$type"]) { 84 + case "net.shreyanjain.richtext.block#heading": 85 + output += `# ${block.text}\n\n`; 86 + break; 87 + case "net.shreyanjain.richtext.block#subheading": 88 + output += `## ${block.text}\n\n`; 89 + break; 90 + case "net.shreyanjain.richtext.block#paragraph": 91 + output += block.text.map(span => formatSpan(span.content, span.formatting)).join('') + '\n\n'; 92 + break; 93 + case "net.shreyanjain.richtext.block#code": 94 + output += `\`\`\`\n${block.text}\n\`\`\`\n\n`; 95 + break; 96 + case "net.shreyanjain.richtext.block#list": 97 + block.text.forEach(item => { 98 + const line = item.map(span => formatSpan(span.content, span.formatting)).join(''); 99 + output += `- ${line}\n`; 100 + }); 101 + output += '\n'; 102 + break; 103 + default: 104 + // Skip unknown block types 105 + break; 106 + } 107 + }); 108 + 109 + document.getElementById('markdown-output').value = output.trim(); 110 + } catch (err) { 111 + document.getElementById('markdown-output').value = `Error: ${err.message}`; 112 + } 113 + } 114 + </script> 115 + </body> 116 + </html>
+8 -1
views/layout.haml
··· 1 1 %html 2 2 %head 3 - %link{ :rel => "stylesheet", :href => "/style.css" } 3 + %link{ rel: "stylesheet", href: "/style.css" } 4 4 %title= @title 5 + -# JS for code syntax highlighting 6 + %link{ rel: "stylesheet", href: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" } 7 + %script{ src: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" } 8 + %script 9 + hljs.highlightAll(); 10 + 11 + 5 12 6 13 != yield 7 14