Markdown Viewer

import os
import streamlit as st
import markdown
from bs4 import BeautifulSoup

Print banner.

st.set_page_config(
    page_title="MD-View",
)

@st.cache_data
def print_banner():
  print("""
    d s   sb d ss         d    b d d sss   d  d  b
    S  S S S S   ~o       S    S S S       S  S  S
    S   S  S S     b      S    S S S       S  S  S
    S      S S     S sSSs S    S S S sSSs  S  S  S
    S      S S     P      S    S S S       S  S  S
    S      S S    S        S   S S S        S  S S
    P      P P ss\"          \"ssS P P sSSss   \"ss\"S
  """)
  return 1

print_banner()

Find all files in the current directory that have a .md extension. These files might contain text generated by large language models. If a particular .md file is a response to a specific question, that question will be stored in a separate file with the same base name but a .q.md extension. Do not include any .q.md files in the final list of files.

md_files = [f for f in os.listdir('.') if f.endswith('.md') and not f.endswith('.q.md')]

Sort files based on their modification time

md_files.sort(key=os.path.getmtime, reverse=True)

Create radio buttons to select a file

selected_file = st.sidebar.radio("Markdown file:", md_files)

Adds a prefix to each line of a multi-line string.

def prefix_lines(query, prefix):
    lines = query.splitlines()
    prefixed_lines = [prefix + line for line in lines]
    return "\n".join(prefixed_lines)

Check if query file exists.

query_file_path = selected_file[:-3] + ".q.md"
if os.path.exists(query_file_path):
    with open(query_file_path, 'r', encoding='utf-8') as file:
        query = file.read()
    st.write(prefix_lines(query, "> "))
    st.write("---");

Read the contents of selected file

with open(selected_file, 'r', encoding='utf-8') as file:
    md_text = file.read()

Display the contents of the file that has been selected.

st.write(md_text)

html_format = st.sidebar.radio("Output HTML:", options=["Tailwind", "Bootstrap"], horizontal=True)

Parse HTML and add Tailwind CSS classes to improve styling.

def enhance_html_with_tailwind(html_text, filename):
    soup = BeautifulSoup(html_text, 'html.parser')

    # Headings
    for level in range(1, 7):
        for tag in soup.find_all(f'h{level}'):
            size = {
                1: 'text-4xl',
                2: 'text-3xl',
                3: 'text-2xl',
                4: 'text-xl',
                5: 'text-lg',
                6: 'text-base',
            }[level]
            tag['class'] = tag.get('class', []) + [size, 'font-bold', 'mt-4', 'mb-2']

    # Paragraphs
    for p in soup.find_all('p'):
        p['class'] = p.get('class', []) + ['mb-4', 'leading-relaxed']

    # Links
    for a in soup.find_all('a'):
        a['class'] = a.get('class', []) + ['text-blue-600', 'hover:underline']
        a['target'] = '_blank'

    # Images
    for img in soup.find_all('img'):
        img['class'] = img.get('class', []) + ['my-4', 'max-w-full', 'h-auto', 'rounded']

    # Lists
    for ul in soup.find_all('ul'):
        ul['class'] = ul.get('class', []) + ['list-disc', 'ml-6', 'mb-4']
    for ol in soup.find_all('ol'):
        ol['class'] = ol.get('class', []) + ['list-decimal', 'ml-6', 'mb-4']

    # Code blocks
    for code in soup.find_all('code'):
        parent = code.parent
        # Inline code
        if parent.name != 'pre':
            code['class'] = code.get('class', []) + ['bg-gray-100', 'px-1', 'py-0.5', 'rounded']
    for pre in soup.find_all('pre'):
        pre['class'] = pre.get('class', []) + ['bg-gray-900', 'text-gray-100', 'p-4', 'rounded', 'overflow-auto', 'mb-4']

    # Blockquotes
    for bq in soup.find_all('blockquote'):
        bq['class'] = bq.get('class', []) + ['border-l-4', 'border-gray-300', 'pl-4', 'italic', 'mb-4']

    # Wrap in basic HTML structure
    return f"""
            <!DOCTYPE html>
            <html lang=\"en\">
            <head>
                <meta charset=\"UTF-8\">
                <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
                <script src=\"https://cdn.tailwindcss.com\"></script>
                <title>{filename}</title>
            </head>
            <body class=\"prose mx-auto p-8\">
            {str(soup)}
            </body>
            </html>
            """

Parse HTML and add Bootstrap CSS classes to improve styling.

def enhance_html_with_bootstrap(html_text, filename):
    soup = BeautifulSoup(html_text, 'html.parser')

    # Headings
    for level in range(1, 7):
        for tag in soup.find_all(f'h{level}'):
            # Add Bootstrap display headings
            display = {
                1: 'display-1',
                2: 'display-2',
                3: 'display-3',
                4: 'display-4',
                5: 'h1',
                6: 'h2',
            }[level]
            tag['class'] = tag.get('class', []) + [display, 'mt-4', 'mb-3']

    # Paragraphs
    for p in soup.find_all('p'):
        p['class'] = p.get('class', []) + ['mb-3']

    # Links
    for a in soup.find_all('a'):
        a['class'] = a.get('class', []) + ['link-primary']
        a['target'] = '_blank'

    # Images
    for img in soup.find_all('img'):
        img['class'] = img.get('class', []) + ['img-fluid', 'my-3', 'rounded']

    # Lists
    for ul in soup.find_all('ul'):
        ul['class'] = ul.get('class', []) + ['list-unstyled', 'mb-3']
    for ol in soup.find_all('ol'):
        ol['class'] = ol.get('class', []) + ['mb-3']

    # Code blocks
    for code in soup.find_all('code'):
        parent = code.parent
        # Inline code
        if parent.name != 'pre':
            code['class'] = code.get('class', []) + ['bg-light', 'px-1', 'py-0', 'rounded']
    for pre in soup.find_all('pre'):
        pre['class'] = pre.get('class', []) + ['bg-dark', 'text-light', 'p-3', 'rounded', 'mb-3', 'overflow-auto']

    # Blockquotes
    for bq in soup.find_all('blockquote'):
        bq['class'] = bq.get('class', []) + ['blockquote', 'ps-3', 'border-start', 'mb-3']

    return f"""
        <!DOCTYPE html>
        <html lang=\"en\">
        <head>
            <meta charset=\"UTF-8\">
            <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
            <!-- Bootstrap CSS -->
            <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">
            <title>{filename}</title>
        </head>
        <body class=\"container py-5\">
        {str(soup)}
        <!-- Bootstrap JS Bundle -->
        <script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js\"></script>
        </body>
        </html>
        """

Save the markdown text as an HTML file.

def save_html(md_text, filename):
    html_text = markdown.markdown(md_text, extensions=[
        'fenced_code',
        'codehilite',
        'tables',
        'toc'
    ])
    if html_format == "Bootstrap":
        html_text = enhance_html_with_bootstrap(html_text, filename)
    elif html_format == "Tailwind":
        html_text = enhance_html_with_tailwind(html_text, filename)

    filename += ".html"
    with open(filename, 'w', encoding='utf-8') as file:
        file.write(html_text)
    st.toast(f"HTML file saved")

Add a button to save the markdown text as an HTML file

if st.sidebar.button("Save HTML", use_container_width=True):
    save_html(md_text, selected_file[:-3])