Master Flask: Create a Markdown-based Website with Search Functionality

Master Flask: Create a Markdown-based Website with Search Functionality
Photo by NEOM / Unsplash

Creating a static file generator using Flask that processes Markdown files with front matter (often called "Graymatter") involves several steps.

This kind of generator is designed to efficiently handle the conversion of Markdown files into HTML format while also extracting and utilizing the Graymatter metadata located at the top of each file.

To successfully create this generator, follow the step-by-step guide below:

Step 1: Setting Up Your Flask Environment

First, you need to set up a Flask environment. If you haven't already, you should install Flask. You also need to install PyYAML for parsing Graymatter and Markdown for converting Markdown to HTML.

pip install Flask PyYAML Markdown

Step 2: Create Your Flask Application

Set up a basic Flask application. Create a new Python file, for example, app.py, and set up your Flask app.

from flask import Flask, render_template
import os

app = Flask(__name__)

@app.route('/')
def index():
    # This function will list all Markdown files
    return "Welcome to the static file generator!"

if __name__ == "__main__":
    app.run(debug=True)

Step 3: Markdown and Graymatter Processing

Create functions to process Markdown files and extract Graymatter. Graymatter is typically at the top of a Markdown file, delimited by ---.

You can structure your Markdown files like this:

---
title: My First Post
date: 2024-01-26
---

This is the content of my first post.

And here's how you might process these files:

import yaml
from markdown import markdown

def extract_graymatter(file_content):
    """
    Extracts the YAML graymatter from the file content.
    """
    if file_content.startswith("---"):
        parts = file_content.split("---", 2)
        graymatter, content = parts[1], parts[2]
        metadata = yaml.safe_load(graymatter)
        return metadata, content
    return {}, file_content

def convert_markdown_to_html(markdown_content):
    """
    Converts Markdown content to HTML.
    """
    return markdown(markdown_content)

Step 4: Integrating Markdown Processing with Flask Routes

Now, integrate the Markdown processing into your Flask app. For example, you might want to serve each Markdown file as a separate page.

@app.route('/posts/<filename>')
def post(filename):
    filepath = os.path.join('posts', filename)
    if os.path.exists(filepath):
        with open(filepath, 'r') as file:
            file_content = file.read()

        metadata, markdown_content = extract_graymatter(file_content)
        html_content = convert_markdown_to_html(markdown_content)
        return render_template('post.html', content=html_content, metadata=metadata)

    return "Post not found", 404

Step 5: Create a Template for Rendering Posts

In your Flask templates directory, create a post.html file to render the HTML content.

<!-- templates/post.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ metadata.title }}</title>
</head>
<body>
    <article>
        {{ content | safe }}
    </article>
</body>
</html>

Adding a Search Function to Your Markdown Site

magnifying glass on white table
Photo by Markus Winkler / Unsplash

To create a search function in your Flask application that searches through all Markdown files' titles, you need to modify your application to read and index the titles from the Markdown files. You'll also add a search form and a view to handle the search requests.

Step 1: Indexing Markdown Files

First, let's create a function that reads all Markdown files in a specific directory (e.g., posts/) and extracts their titles from the Graymatter:

def index_markdown_files():
    markdown_files = {}
    posts_directory = 'posts'
    for filename in os.listdir(posts_directory):
        if filename.endswith('.md'):
            filepath = os.path.join(posts_directory, filename)
            with open(filepath, 'r') as file:
                file_content = file.read()
                metadata, _ = extract_graymatter(file_content)
                title = metadata.get('title', 'No Title')
                markdown_files[title] = filename
    return markdown_files

Step 2: Creating the Search Form

You can create a simple search form in HTML. You can place this form in your base template or any specific template where you want the search functionality.

<!-- search_form.html -->
<form action="/search" method="get">
    <input type="text" name="query" placeholder="Search...">
    <button type="submit">Search</button>
</form>

Step 3: Adding a Search View in Flask

Now, you need to create a route in your Flask app to handle search queries:

@app.route('/search')
def search():
    query = request.args.get('query', '')
    if query:
        indexed_files = index_markdown_files()
        search_results = {title: filename for title, filename in indexed_files.items() if query.lower() in title.lower()}
        return render_template('search_results.html', results=search_results, query=query)
    return "No query provided", 400

Step 4: Creating a Search Results Template

Create a new template to display the search results:

<!-- templates/search_results.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Search Results</title>
</head>
<body>
    <h1>Search Results for "{{ query }}"</h1>
    {% if results %}
        <ul>
        {% for title, filename in results.items() %}
            <li><a href="{{ url_for('post', filename=filename) }}">{{ title }}</a></li>
        {% endfor %}
        </ul>
    {% else %}
        <p>No results found</p>
    {% endif %}
</body>
</html>

Step 5: Integrating the Search Form

Include the search form in your templates where you want it to appear. You can include it in your base template to make it available site-wide or only in specific templates.

<!-- Include this line in your base.html or other templates -->
{% include 'search_form.html' %}

Running Your Updated Flask App

Run your Flask app again using:

flask run

Final Note

This simple configuration sets up a Flask application that confidently serves static HTML content generated from Markdown files with Graymatter. You have the ability to enhance this configuration by incorporating additional functionalities such as a homepage that showcases all posts, applying CSS for styling, or even integrating a database for more advanced content management.

Read more