generated from hjess/PythonTemplateProject
Compare commits
12 Commits
https_shit
...
adfa478eca
| Author | SHA1 | Date | |
|---|---|---|---|
| adfa478eca | |||
| 146670d7c7 | |||
| 7f7dd5139e | |||
| 5d2bce8d6e | |||
| c6ac4599f4 | |||
| ffa1ae346f | |||
| e4887345a5 | |||
| 82228fdb27 | |||
| 379f6a4273 | |||
| b2f8cc3c81 | |||
| a8a6f7d0b0 | |||
| 38e8f16622 |
Binary file not shown.
Binary file not shown.
147
app.py
147
app.py
@@ -1,144 +1,5 @@
|
|||||||
from fastapi import FastAPI, Request
|
import uvicorn
|
||||||
from fastapi.staticfiles import StaticFiles
|
from app.main import app
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from fastapi import FastAPI
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
from markdown_render import render_markdown_with_jinja
|
|
||||||
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
uvicorn.run("app.main:app", host="0.0.0.0", port=9210, reload=False)
|
||||||
|
|
||||||
|
|
||||||
# Context manager for app lifespan
|
|
||||||
@asynccontextmanager
|
|
||||||
async def lifespan(app: FastAPI):
|
|
||||||
print("App startup: Processing Markdown files...")
|
|
||||||
process_markdown_files("./data", "./data") # Process all Markdown files
|
|
||||||
print("Markdown processing complete!")
|
|
||||||
yield # Allow the app to start
|
|
||||||
print("App shutdown: Cleanup complete.")
|
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
|
||||||
app.mount("/data", StaticFiles(directory="data"), name="data")
|
|
||||||
|
|
||||||
|
|
||||||
# Mount static files
|
|
||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
||||||
app.add_middleware( HTTPSRedirectMiddleware )
|
|
||||||
|
|
||||||
# Templates directory
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
|
||||||
|
|
||||||
# Load JSON data
|
|
||||||
with open("mock_data.json") as file:
|
|
||||||
data = json.load(file)
|
|
||||||
|
|
||||||
@app.get("/test", response_class=HTMLResponse)
|
|
||||||
async def home_test():
|
|
||||||
# Load the Markdown content from a file
|
|
||||||
with open("templates/example.md", "r") as f:
|
|
||||||
markdown_content = f.read()
|
|
||||||
|
|
||||||
# Render Markdown first, then inject Jinja2 components
|
|
||||||
rendered_html = render_markdown_with_jinja(markdown_content)
|
|
||||||
|
|
||||||
# Wrap in a base HTML layout
|
|
||||||
html_template = f"""
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Markdown + Jinja2</title>
|
|
||||||
<style>
|
|
||||||
.img-left-overlay img {{ width: 300px; }}
|
|
||||||
.box {{ border: 1px solid #ccc; padding: 10px; background-color: #f9f9f9; }}
|
|
||||||
.note {{ background-color: #e7f3fe; border-left: 4px solid #2196F3; padding: 10px; }}
|
|
||||||
.warning {{ background-color: #fff3cd; border-left: 4px solid #ffeb3b; padding: 10px; }}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="content">
|
|
||||||
{rendered_html}
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
return HTMLResponse(content=html_template)
|
|
||||||
|
|
||||||
def process_markdown_files(input_dir: str, output_dir: str):
|
|
||||||
"""
|
|
||||||
Recursively process all Markdown files in the input directory,
|
|
||||||
render them to HTML, and save them in the output directory.
|
|
||||||
"""
|
|
||||||
for root, _, files in os.walk(input_dir):
|
|
||||||
for file in files:
|
|
||||||
if file.endswith(".md"):
|
|
||||||
input_file_path = os.path.join(root, file)
|
|
||||||
|
|
||||||
# Determine output file path (convert .md to .html)
|
|
||||||
relative_path = os.path.relpath(input_file_path, input_dir)
|
|
||||||
output_file_path = os.path.join(output_dir, os.path.splitext(relative_path)[0] + ".html")
|
|
||||||
|
|
||||||
# Ensure the output directory exists
|
|
||||||
os.makedirs(os.path.dirname(output_file_path), exist_ok=True)
|
|
||||||
|
|
||||||
# Read Markdown content
|
|
||||||
with open(input_file_path, "r", encoding="utf-8") as md_file:
|
|
||||||
markdown_content = md_file.read()
|
|
||||||
|
|
||||||
# Render Markdown with Jinja2
|
|
||||||
print(f"Processing: {input_file_path} -> {output_file_path}")
|
|
||||||
rendered_html = render_markdown_with_jinja(markdown_content)
|
|
||||||
|
|
||||||
# Write the rendered HTML to the output file
|
|
||||||
with open(output_file_path, "w", encoding="utf-8") as html_file:
|
|
||||||
html_file.write(rendered_html)
|
|
||||||
|
|
||||||
|
|
||||||
# Index route
|
|
||||||
@app.get("/", response_class=HTMLResponse)
|
|
||||||
async def get_index(request: Request):
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"index.html",
|
|
||||||
{"request": request, "data": data, "page_title": "Forside", "author": "Henrik"}
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.get("/sitemap", response_class=HTMLResponse)
|
|
||||||
async def sitemap():
|
|
||||||
"""Simple home page listing available HTML files."""
|
|
||||||
links = []
|
|
||||||
for root, _, files in os.walk("./data"):
|
|
||||||
for file in files:
|
|
||||||
if file.endswith(".html"):
|
|
||||||
relative_path = os.path.relpath(os.path.join(root, file), "./data")
|
|
||||||
link = f"<a href='/data/{relative_path}'>{relative_path}</a>"
|
|
||||||
links.append(link)
|
|
||||||
links_html = "<br>".join(links)
|
|
||||||
return HTMLResponse(content=f"<h1>Available Pages</h1>{links_html}")
|
|
||||||
|
|
||||||
# Category route
|
|
||||||
@app.get("/category/{category_name}", response_class=HTMLResponse)
|
|
||||||
async def get_category(request: Request, category_name: str):
|
|
||||||
# Find den korrekte kategori
|
|
||||||
category = next((cat for cat in data["categories"] if cat["path"] == category_name), None)
|
|
||||||
if category:
|
|
||||||
category_file = f"data/{category_name}/index.html"
|
|
||||||
if os.path.exists(category_file):
|
|
||||||
with open(category_file) as file:
|
|
||||||
category_content = file.read()
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"category.html",
|
|
||||||
{
|
|
||||||
"request": request,
|
|
||||||
"data": data,
|
|
||||||
"page_title": category["name"],
|
|
||||||
"author": category["author"],
|
|
||||||
"content": category_content
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return HTMLResponse("Kategori ikke fundet", status_code=404)
|
|
||||||
|
|||||||
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
BIN
app/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/main.cpython-312.pyc
Normal file
BIN
app/__pycache__/main.cpython-312.pyc
Normal file
Binary file not shown.
0
app/controllers/__init__.py
Normal file
0
app/controllers/__init__.py
Normal file
BIN
app/controllers/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/controllers/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/controllers/__pycache__/category_controller.cpython-312.pyc
Normal file
BIN
app/controllers/__pycache__/category_controller.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/controllers/__pycache__/dynamic_controller.cpython-312.pyc
Normal file
BIN
app/controllers/__pycache__/dynamic_controller.cpython-312.pyc
Normal file
Binary file not shown.
56
app/controllers/category_controller.py
Normal file
56
app/controllers/category_controller.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryController:
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the controller."""
|
||||||
|
self.router = APIRouter()
|
||||||
|
self.templates = Jinja2Templates(directory="templates")
|
||||||
|
self.data = self._load_mock_data()
|
||||||
|
self._add_routes()
|
||||||
|
|
||||||
|
def _load_mock_data(self):
|
||||||
|
"""Load mock data from a JSON file."""
|
||||||
|
with open("mock_data.json") as file:
|
||||||
|
return json.load(file)
|
||||||
|
|
||||||
|
def _add_routes(self):
|
||||||
|
"""Add routes to the router."""
|
||||||
|
self.router.add_api_route("/", self.get_index, methods=["GET"], response_class=HTMLResponse)
|
||||||
|
self.router.add_api_route(
|
||||||
|
"/category/{category_name}",
|
||||||
|
self.get_category,
|
||||||
|
methods=["GET"],
|
||||||
|
response_class=HTMLResponse,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_index(self, request: Request):
|
||||||
|
"""Index route."""
|
||||||
|
return self.templates.TemplateResponse(
|
||||||
|
"index.html",
|
||||||
|
{"request": request, "data": self.data, "page_title": "Forside", "author": "Henrik"},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_category(self, request: Request, category_name: str):
|
||||||
|
"""Category route."""
|
||||||
|
category = next((cat for cat in self.data["categories"] if cat["path"] == category_name), None)
|
||||||
|
if category:
|
||||||
|
category_file = f"data/{category_name}/index.html"
|
||||||
|
if os.path.exists(category_file):
|
||||||
|
with open(category_file) as file:
|
||||||
|
category_content = file.read()
|
||||||
|
return self.templates.TemplateResponse(
|
||||||
|
"category.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"data": self.data,
|
||||||
|
"page_title": category["name"],
|
||||||
|
"author": category["author"],
|
||||||
|
"content": category_content,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return HTMLResponse("Kategori ikke fundet", status_code=404)
|
||||||
65
app/controllers/dynamic_controller.py
Normal file
65
app/controllers/dynamic_controller.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from fastapi.responses import HTMLResponse, Response
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
class DynamicController:
|
||||||
|
def __init__(self, data_dir: str):
|
||||||
|
"""Initialize the dynamic controller."""
|
||||||
|
self.router = APIRouter()
|
||||||
|
self.templates = Jinja2Templates(directory="templates")
|
||||||
|
self.data_dir = data_dir
|
||||||
|
self.data = self._load_mock_data()
|
||||||
|
self._add_dynamic_routes()
|
||||||
|
|
||||||
|
def _load_mock_data(self):
|
||||||
|
"""Load mock data from a JSON file."""
|
||||||
|
with open("mock_data.json") as file:
|
||||||
|
return json.load(file)
|
||||||
|
|
||||||
|
def _add_dynamic_routes(self):
|
||||||
|
"""Scan data directory and create dynamic routes."""
|
||||||
|
for root, dirs, files in os.walk(self.data_dir):
|
||||||
|
for directory in dirs:
|
||||||
|
route_path = f"/{directory}" # Create route based on directory name
|
||||||
|
directory_path = os.path.join(root, directory)
|
||||||
|
|
||||||
|
# Register route dynamically
|
||||||
|
self.router.add_api_route(
|
||||||
|
route_path,
|
||||||
|
self._serve_dynamic_template(directory, directory_path),
|
||||||
|
methods=["GET"],
|
||||||
|
response_class=HTMLResponse,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _serve_dynamic_template(self, route_name: str, directory_path: str):
|
||||||
|
"""Closure to serve templates for each route."""
|
||||||
|
|
||||||
|
async def route_handler(request: Request):
|
||||||
|
# Look for index.html or render fallback content
|
||||||
|
index_html = os.path.join(directory_path, "index.html")
|
||||||
|
if os.path.exists(index_html):
|
||||||
|
with open(index_html, "r", encoding="utf-8") as file:
|
||||||
|
content = file.read()
|
||||||
|
# Find the author for this route from preloaded data
|
||||||
|
for category in self.data.get("categories", []):
|
||||||
|
if category["path"] == route_name:
|
||||||
|
author_name = category["author"]
|
||||||
|
break
|
||||||
|
# Pass required data to the template
|
||||||
|
return self.templates.TemplateResponse(
|
||||||
|
"category.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"page_title": route_name.capitalize(),
|
||||||
|
"content": content,
|
||||||
|
"author": author_name,
|
||||||
|
"data": self.data, # Pass additional data if needed
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fallback: Return a 404 if no content is found
|
||||||
|
return Response(f"No content found for {route_name}", status_code=404)
|
||||||
|
|
||||||
|
return route_handler
|
||||||
53
app/main.py
Normal file
53
app/main.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from app.services.markdown_processor import MarkdownProcessor
|
||||||
|
from app.services.metadata_processor import MetadataProcessor
|
||||||
|
from app.controllers.dynamic_controller import DynamicController
|
||||||
|
from app.controllers.category_controller import CategoryController
|
||||||
|
|
||||||
|
|
||||||
|
class Application:
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the FastAPI app and configure it."""
|
||||||
|
self.app = FastAPI(lifespan=self._lifespan_event)
|
||||||
|
self._setup_static_files()
|
||||||
|
self._include_routers()
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def _lifespan_event(self, app: FastAPI):
|
||||||
|
"""Lifespan event for startup and shutdown logic."""
|
||||||
|
print("App startup: Processing Markdown files...")
|
||||||
|
# Process Markdown files into HTML
|
||||||
|
processor = MarkdownProcessor(input_dir="./data", templates_dir="./templates")
|
||||||
|
processor.run()
|
||||||
|
|
||||||
|
# Generate dynamic JSON data
|
||||||
|
metadata_processor = MetadataProcessor(input_dir="./data", output_file="generated_data.json")
|
||||||
|
metadata_processor.generate_json()
|
||||||
|
print("Generated dynamic data file.")
|
||||||
|
print("Markdown processing complete!")
|
||||||
|
|
||||||
|
yield
|
||||||
|
print("App shutdown: Cleanup complete.")
|
||||||
|
|
||||||
|
def _setup_static_files(self):
|
||||||
|
"""Mount static file directories."""
|
||||||
|
self.app.mount("/data", StaticFiles(directory="data"), name="data")
|
||||||
|
self.app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|
||||||
|
def _include_routers(self):
|
||||||
|
"""Include all route controllers."""
|
||||||
|
category_controller = CategoryController()
|
||||||
|
dynamic_controller = DynamicController("./data")
|
||||||
|
|
||||||
|
self.app.include_router(category_controller.router)
|
||||||
|
self.app.include_router(dynamic_controller.router)
|
||||||
|
|
||||||
|
def get_app(self):
|
||||||
|
"""Return the FastAPI app instance."""
|
||||||
|
return self.app
|
||||||
|
|
||||||
|
|
||||||
|
application = Application()
|
||||||
|
app = application.get_app()
|
||||||
0
app/services/__init__.py
Normal file
0
app/services/__init__.py
Normal file
BIN
app/services/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/services/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/services/__pycache__/markdown_processor.cpython-312.pyc
Normal file
BIN
app/services/__pycache__/markdown_processor.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/services/__pycache__/markdown_render.cpython-312.pyc
Normal file
BIN
app/services/__pycache__/markdown_render.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/services/__pycache__/metadata_processor.cpython-312.pyc
Normal file
BIN
app/services/__pycache__/metadata_processor.cpython-312.pyc
Normal file
Binary file not shown.
87
app/services/markdown_processor.py
Normal file
87
app/services/markdown_processor.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import os
|
||||||
|
from app.services.markdown_render import render_markdown_with_jinja # Your custom renderer
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
|
|
||||||
|
class MarkdownProcessor:
|
||||||
|
"""
|
||||||
|
A class to process Markdown files, extract metadata, and generate a single
|
||||||
|
'index.html' per category directory using a custom rendering engine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, input_dir: str, templates_dir: str):
|
||||||
|
"""
|
||||||
|
Initialize the MarkdownProcessor.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_dir (str): Root directory containing category subdirectories.
|
||||||
|
templates_dir (str): Directory containing Jinja2 templates.
|
||||||
|
"""
|
||||||
|
self.input_dir = input_dir
|
||||||
|
self.env = Environment(loader=FileSystemLoader(templates_dir))
|
||||||
|
|
||||||
|
def _process_markdown_files_in_directory(self, directory_path: str) -> list:
|
||||||
|
"""
|
||||||
|
Process all Markdown files in a directory using Markdown and Jinja2 custom tags.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory_path (str): Path to the category directory.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: A list of processed sections containing metadata and rendered content.
|
||||||
|
"""
|
||||||
|
sections = []
|
||||||
|
for file in sorted(os.listdir(directory_path)):
|
||||||
|
if file.endswith(".md"):
|
||||||
|
file_path = os.path.join(directory_path, file)
|
||||||
|
with open(file_path, "r", encoding="utf-8") as md_file:
|
||||||
|
markdown_content = md_file.read()
|
||||||
|
|
||||||
|
# Process Markdown and Jinja2
|
||||||
|
rendered_content, metadata = render_markdown_with_jinja(markdown_content)
|
||||||
|
|
||||||
|
# Append the section to the list
|
||||||
|
sections.append({
|
||||||
|
"name": metadata.get("title", "Untitled"),
|
||||||
|
"content": rendered_content,
|
||||||
|
"summary": metadata.get("summary", ""),
|
||||||
|
"author": metadata.get("author", "Unknown"),
|
||||||
|
})
|
||||||
|
|
||||||
|
return sections
|
||||||
|
|
||||||
|
def _generate_index_html(self, directory_path: str, sections: list, output_file: str):
|
||||||
|
"""
|
||||||
|
Generate the index.html file for a category using the combined sections.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory_path (str): Path to the category directory.
|
||||||
|
sections (list): List of processed Markdown content and metadata.
|
||||||
|
output_file (str): Path to save the generated index.html.
|
||||||
|
"""
|
||||||
|
# Render the template with the combined sections
|
||||||
|
template = self.env.get_template("combined_template.html")
|
||||||
|
rendered_html = template.render(
|
||||||
|
title=os.path.basename(directory_path).capitalize(),
|
||||||
|
sections=sections
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write the rendered HTML to index.html
|
||||||
|
os.makedirs(directory_path, exist_ok=True)
|
||||||
|
with open(output_file, "w", encoding="utf-8") as output:
|
||||||
|
output.write(rendered_html)
|
||||||
|
print(f"Generated: {output_file}")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
Run the Markdown processing workflow: one 'index.html' per category.
|
||||||
|
"""
|
||||||
|
for root, dirs, _ in os.walk(self.input_dir):
|
||||||
|
for directory in dirs:
|
||||||
|
category_path = os.path.join(root, directory)
|
||||||
|
output_file = os.path.join(category_path, "index.html")
|
||||||
|
|
||||||
|
# Process all Markdown files in the current category directory
|
||||||
|
sections = self._process_markdown_files_in_directory(category_path)
|
||||||
|
if sections:
|
||||||
|
self._generate_index_html(category_path, sections, output_file)
|
||||||
74
app/services/markdown_render.py
Normal file
74
app/services/markdown_render.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import markdown
|
||||||
|
from jinja2 import Environment, DictLoader
|
||||||
|
|
||||||
|
# Define Jinja2 custom functions
|
||||||
|
def img_left_overlay(src):
|
||||||
|
"""Render an image with overlay."""
|
||||||
|
return f'''
|
||||||
|
<div class="img-left-overlay">
|
||||||
|
<img src="{src}" alt="Overlay Image">
|
||||||
|
<div class="overlay-text">Overlay Text</div>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
def box(title, content):
|
||||||
|
"""Render a box component."""
|
||||||
|
return f'''
|
||||||
|
<div class="box">
|
||||||
|
<strong>{title}</strong>
|
||||||
|
<p>{content}</p>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def note(content):
|
||||||
|
"""Render a note component."""
|
||||||
|
return f'''
|
||||||
|
<div class="note">
|
||||||
|
<p>{content}</p>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
def warning(content):
|
||||||
|
"""Render a warning component."""
|
||||||
|
return f'''
|
||||||
|
<div class="warning">
|
||||||
|
⚠️ <p>{content}</p>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
def create_jinja_environment():
|
||||||
|
"""Create and configure the Jinja2 environment."""
|
||||||
|
env = Environment(loader=DictLoader({"base_template": "{{ content | safe }}"}))
|
||||||
|
env.globals.update({
|
||||||
|
"img_left_overlay": img_left_overlay,
|
||||||
|
"box": box,
|
||||||
|
"note": note,
|
||||||
|
"warning": warning,
|
||||||
|
})
|
||||||
|
return env
|
||||||
|
|
||||||
|
def render_markdown_with_jinja(markdown_content: str):
|
||||||
|
"""
|
||||||
|
Convert Markdown to HTML and apply Jinja2 rendering for custom tags.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
markdown_content (str): Raw Markdown content.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: Rendered HTML content and metadata as a dictionary.
|
||||||
|
"""
|
||||||
|
# Step 1: Convert Markdown to HTML and extract metadata
|
||||||
|
md = markdown.Markdown(extensions=["extra", "nl2br", "meta"])
|
||||||
|
intermediate_html = md.convert(markdown_content)
|
||||||
|
metadata = {key: " ".join(value) for key, value in md.Meta.items()} if md.Meta else {}
|
||||||
|
|
||||||
|
# Step 2: Pass the resulting HTML with Jinja2 custom tags through Jinja2
|
||||||
|
env = create_jinja_environment()
|
||||||
|
template = env.get_template("base_template")
|
||||||
|
final_html = template.render(content=intermediate_html)
|
||||||
|
|
||||||
|
# Step 3: Re-render final_html in Jinja2 for embedded tags like {{ box(...) }}
|
||||||
|
final_output = env.from_string(final_html).render()
|
||||||
|
|
||||||
|
return final_output, metadata
|
||||||
83
app/services/metadata_processor.py
Normal file
83
app/services/metadata_processor.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import os
|
||||||
|
import markdown
|
||||||
|
import json
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataProcessor:
|
||||||
|
"""
|
||||||
|
A class to scan Markdown files, extract front matter metadata,
|
||||||
|
and generate a structured JSON file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, input_dir: str, output_file: str):
|
||||||
|
"""
|
||||||
|
Initialize the MetadataProcessor.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_dir (str): Directory containing Markdown files.
|
||||||
|
output_file (str): Path to save the generated JSON file.
|
||||||
|
"""
|
||||||
|
self.input_dir = input_dir
|
||||||
|
self.output_file = output_file
|
||||||
|
self.data = {"categories": [], "favorites": []}
|
||||||
|
|
||||||
|
def _extract_metadata(self, file_path: str) -> Dict:
|
||||||
|
"""
|
||||||
|
Extract front matter metadata using the 'markdown' package.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path (str): Path to the Markdown file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary containing the extracted metadata.
|
||||||
|
"""
|
||||||
|
with open(file_path, "r", encoding="utf-8") as file:
|
||||||
|
markdown_content = file.read()
|
||||||
|
|
||||||
|
# Initialize Markdown with meta extension
|
||||||
|
md = markdown.Markdown(extensions=["extra", "nl2br", "meta"])
|
||||||
|
md.convert(markdown_content)
|
||||||
|
|
||||||
|
# Metadata is stored in md.Meta as a dictionary of lists
|
||||||
|
meta = {key: " ".join(value) for key, value in md.Meta.items()} if md.Meta else {}
|
||||||
|
return meta
|
||||||
|
|
||||||
|
def _process_directory(self):
|
||||||
|
"""
|
||||||
|
Recursively scan the input directory for Markdown files
|
||||||
|
and extract metadata to build the JSON structure.
|
||||||
|
"""
|
||||||
|
for root, _, files in os.walk(self.input_dir):
|
||||||
|
for file in files:
|
||||||
|
if file.endswith(".md"):
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
metadata = self._extract_metadata(file_path)
|
||||||
|
|
||||||
|
if metadata:
|
||||||
|
# Add to 'categories'
|
||||||
|
self.data["categories"].append({
|
||||||
|
"name": metadata.get("name", "Unknown"),
|
||||||
|
"path": os.path.relpath(root, self.input_dir).replace(os.sep, "/"),
|
||||||
|
"author": metadata.get("author", "Unknown")
|
||||||
|
})
|
||||||
|
|
||||||
|
# Add to 'favorites' if 'favorite' is true
|
||||||
|
if metadata.get("favorite") and metadata["favorite"].lower() == "true":
|
||||||
|
self.data["favorites"].append({
|
||||||
|
"name": metadata.get("name", "Unknown"),
|
||||||
|
"image": metadata.get("image", "images/default.jpg"),
|
||||||
|
"description": metadata.get("summary", "No description provided")
|
||||||
|
})
|
||||||
|
|
||||||
|
def generate_json(self):
|
||||||
|
"""
|
||||||
|
Generate the JSON structure and save it to the output file.
|
||||||
|
"""
|
||||||
|
self._process_directory()
|
||||||
|
|
||||||
|
# Save JSON to file
|
||||||
|
with open(self.output_file, "w", encoding="utf-8") as json_file:
|
||||||
|
json.dump(self.data, json_file, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
print(f"Generated JSON saved to {self.output_file}")
|
||||||
@@ -1 +1,26 @@
|
|||||||
<P> Lidt om Bolig </P>
|
<main>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8 col-12-medium">
|
||||||
|
<p><b>Opsumering: Lidt omkring job situationen og hvordan det fungere</b></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 col-12-medium">
|
||||||
|
<p><em>Forfatter: Henrik Jess</em></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2>Untitled</h2>
|
||||||
|
<div>
|
||||||
|
<h1>Bolig Bolig Bolig Bolig - Hvor skal sengen placeres</h1>
|
||||||
|
<p>Nu bliver det spænde!</p>
|
||||||
|
<p>
|
||||||
|
<div class="note">
|
||||||
|
<p>Dette er stadig en test side</p>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
<p>{img-left-overlay: images/my-cat.png}</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
<h1>BoligBolig Bolig Bolig - Hvor skal sengen placeres</h1>
|
|
||||||
<p>Nu bliver det spænde!</p>
|
|
||||||
<p>
|
|
||||||
<div class="note">
|
|
||||||
<p>Dette er stadig en test side</p>
|
|
||||||
</div>
|
|
||||||
</p>
|
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
# BoligBolig Bolig Bolig - Hvor skal sengen placeres
|
---
|
||||||
|
name: Generalt
|
||||||
|
description: Hvem, hvad og hvor
|
||||||
|
author: Henrik Jess
|
||||||
|
date: ons 11 dec 22:16:13 CET 2024
|
||||||
|
summary: Lidt omkring job situationen og hvordan det fungere
|
||||||
|
favorite: true
|
||||||
|
image: images/pic07.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
# Bolig Bolig Bolig Bolig - Hvor skal sengen placeres
|
||||||
|
|
||||||
Nu bliver det spænde!
|
Nu bliver det spænde!
|
||||||
|
|
||||||
{{ note("Dette er stadig en test side") }}
|
{{ note("Dette er stadig en test side") }}
|
||||||
|
|
||||||
|
{img-left-overlay: images/my-cat.png}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
<h1>Lidt mere info om job</h1>
|
|
||||||
<p>Der skal langt mere tekst her</p>
|
|
||||||
<p>
|
|
||||||
<div class="note">
|
|
||||||
<p>Husk alpha side</p>
|
|
||||||
</div>
|
|
||||||
</p>
|
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: Job
|
||||||
|
description: Hvem, hvad og hvor
|
||||||
|
author: Henrik Jess
|
||||||
|
date: ons 11 dec 22:16:13 CET 2024
|
||||||
|
summary: Lidt omkring job situationen og hvordan det fungere
|
||||||
|
favorite: true
|
||||||
|
image: images/pic04.jpg
|
||||||
|
---
|
||||||
|
|
||||||
# Lidt mere info om job
|
# Lidt mere info om job
|
||||||
|
|
||||||
Der skal langt mere tekst her
|
Der skal langt mere tekst her
|
||||||
|
|||||||
@@ -1 +1,57 @@
|
|||||||
<P> Lidt om job </P>
|
<main>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8 col-12-medium">
|
||||||
|
<p><b>Opsumering: Lidt omkring job situationen og hvordan det fungere</b></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 col-12-medium">
|
||||||
|
<p><em>Forfatter: Henrik Jess</em></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2>Untitled</h2>
|
||||||
|
<div>
|
||||||
|
<h1>Lidt mere info om job</h1>
|
||||||
|
<p>Der skal langt mere tekst her</p>
|
||||||
|
<p>
|
||||||
|
<div class="note">
|
||||||
|
<p>Husk alpha side</p>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8 col-12-medium">
|
||||||
|
<p><b>Opsumering: This is the first job post.</b></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 col-12-medium">
|
||||||
|
<p><em>Forfatter: Henrik Jess</em></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2>Job 1</h2>
|
||||||
|
<div>
|
||||||
|
<h1>Overskrift 1</h1>
|
||||||
|
<h2>Overskrift 2</h2>
|
||||||
|
<h3>Overskrift 3</h3>
|
||||||
|
<h4>Overskrift4</h4>
|
||||||
|
<p>Here is a custom box:</p>
|
||||||
|
<p>
|
||||||
|
<div class="box">
|
||||||
|
<strong>Important Title</strong>
|
||||||
|
<p>This is the content inside the box.</p>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
<p>Here is a note:</p>
|
||||||
|
<p>
|
||||||
|
<div class="note">
|
||||||
|
<p>This is a note for the readers.</p>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|||||||
18
data/job/job1.md
Normal file
18
data/job/job1.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
title: Job 1
|
||||||
|
author: Henrik Jess
|
||||||
|
summary: This is the first job post.
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
# Overskrift 1
|
||||||
|
## Overskrift 2
|
||||||
|
### Overskrift 3
|
||||||
|
#### Overskrift4
|
||||||
|
Here is a custom box:
|
||||||
|
|
||||||
|
{{ box("Important Title", "This is the content inside the box.") }}
|
||||||
|
|
||||||
|
Here is a note:
|
||||||
|
|
||||||
|
{{ note("This is a note for the readers.") }}
|
||||||
25
data/kontor/index.html
Normal file
25
data/kontor/index.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<main>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8 col-12-medium">
|
||||||
|
<p><b>Opsumering: Lad os snakke kontor fælleskaber</b></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 col-12-medium">
|
||||||
|
<p><em>Forfatter: Henrik Jess</em></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2>Untitled</h2>
|
||||||
|
<div>
|
||||||
|
<h1>Kontorfællesskab!</h1>
|
||||||
|
<p>Der skal langt mere tekst her</p>
|
||||||
|
<p>
|
||||||
|
<div class="note">
|
||||||
|
<p>Husk alpha side</p>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
16
data/kontor/lidt_om_kontore.md
Normal file
16
data/kontor/lidt_om_kontore.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: Job
|
||||||
|
description: Kontorfælleskaber osv
|
||||||
|
author: Henrik Jess
|
||||||
|
date: today
|
||||||
|
summary: Lad os snakke kontor fælleskaber
|
||||||
|
favorite: false
|
||||||
|
image: images/pic05.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
# Kontorfællesskab!
|
||||||
|
|
||||||
|
Der skal langt mere tekst her
|
||||||
|
|
||||||
|
|
||||||
|
{{ note("Husk alpha side") }}
|
||||||
@@ -1 +1,20 @@
|
|||||||
<P> Lidt om skat </P>
|
<main>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8 col-12-medium">
|
||||||
|
<p><b>Opsumering: Jeg er langt fra expert, men her er lidt hvad jeg har indsamlet omkring skat</b></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 col-12-medium">
|
||||||
|
<p><em>Forfatter: Henrik Jess</em></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2>Untitled</h2>
|
||||||
|
<div>
|
||||||
|
<h1>Skat! - Det skal jo også være sjovt og leve</h1>
|
||||||
|
<p>dette er mere tekst omkring skat</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|||||||
13
data/skat/portugal_vs_dansk_skat.md
Normal file
13
data/skat/portugal_vs_dansk_skat.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: Skat
|
||||||
|
description: SKAT,SKAT, SKAT - Det skal betales den slags
|
||||||
|
author: Henrik Jess
|
||||||
|
date: today
|
||||||
|
summary: Jeg er langt fra expert, men her er lidt hvad jeg har indsamlet omkring skat
|
||||||
|
favorite: false
|
||||||
|
image: images/pic07.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
# Skat! - Det skal jo også være sjovt og leve
|
||||||
|
|
||||||
|
dette er mere tekst omkring skat
|
||||||
@@ -1 +1,22 @@
|
|||||||
<P> Lidt om skole </P>
|
<main>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8 col-12-medium">
|
||||||
|
<p><b>Opsumering: Nørj det er lidt spændende..</b></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 col-12-medium">
|
||||||
|
<p><em>Forfatter: Erika Nielsen</em></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2>Untitled</h2>
|
||||||
|
<div>
|
||||||
|
<h1>Skole start!</h1>
|
||||||
|
<p>dette er mere tekst omkring skole</p>
|
||||||
|
<h1>Skole efter lidt tid</h1>
|
||||||
|
<p>HA!</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|||||||
17
data/skole/skole.md
Normal file
17
data/skole/skole.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: Skole
|
||||||
|
description: Skole, ny kultur og menesker
|
||||||
|
author: Erika Nielsen
|
||||||
|
date: today
|
||||||
|
summary: Nørj det er lidt spændende..
|
||||||
|
favorite: false
|
||||||
|
image: images/pic07.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
# Skole start!
|
||||||
|
|
||||||
|
dette er mere tekst omkring skole
|
||||||
|
|
||||||
|
# Skole efter lidt tid
|
||||||
|
|
||||||
|
HA!
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
from markdown_render import render_markdown_with_jinja
|
from app.services.markdown_render import render_markdown_with_jinja
|
||||||
|
|
||||||
def process_markdown_files(input_dir: str, output_dir: str):
|
def process_markdown_files(input_dir: str, output_dir: str):
|
||||||
"""
|
"""
|
||||||
25
depricated/markdown_service.py
Normal file
25
depricated/markdown_service.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import os
|
||||||
|
from app.services.markdown_render import render_markdown_with_jinja
|
||||||
|
|
||||||
|
def process_markdown_files(input_dir: str, output_dir: str):
|
||||||
|
"""
|
||||||
|
Recursively process all Markdown files in the input directory,
|
||||||
|
render them to HTML, and save them in the output directory.
|
||||||
|
"""
|
||||||
|
for root, _, files in os.walk(input_dir):
|
||||||
|
for file in files:
|
||||||
|
if file.endswith(".md"):
|
||||||
|
input_file_path = os.path.join(root, file)
|
||||||
|
relative_path = os.path.relpath(input_file_path, input_dir)
|
||||||
|
output_file_path = os.path.join(output_dir, os.path.splitext(relative_path)[0] + ".html")
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(output_file_path), exist_ok=True)
|
||||||
|
|
||||||
|
with open(input_file_path, "r", encoding="utf-8") as md_file:
|
||||||
|
markdown_content = md_file.read()
|
||||||
|
|
||||||
|
print(f"Processing: {input_file_path} -> {output_file_path}")
|
||||||
|
rendered_html = render_markdown_with_jinja(markdown_content)
|
||||||
|
|
||||||
|
with open(output_file_path, "w", encoding="utf-8") as html_file:
|
||||||
|
html_file.write(rendered_html)
|
||||||
13
depricated/mock_data.json
Normal file
13
depricated/mock_data.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"categories": [
|
||||||
|
{"name": "SKAT", "path": "skat", "author": "Henrik"},
|
||||||
|
{"name": "Skole", "path": "skole", "author": "Erika"},
|
||||||
|
{"name": "Bolig", "path": "bolig", "author": "Henrik"},
|
||||||
|
{"name": "Job", "path": "job", "author": "Henrik"}
|
||||||
|
],
|
||||||
|
"favorites": [
|
||||||
|
{"name": "SKAT", "image": "images/pic07.jpg", "description": "Favorit Kategori"},
|
||||||
|
{"name": "Skole", "image": "images/pic08.jpg", "description": "Skole information"},
|
||||||
|
{"name": "Bolig", "image": "images/pic09.jpg", "description": "Bolig detaljer"}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from markdown_render import MarkdownRenderer
|
from app.services.markdown_render import MarkdownRenderer
|
||||||
|
|
||||||
# Initialize MarkdownRenderer
|
# Initialize MarkdownRenderer
|
||||||
renderer = MarkdownRenderer()
|
renderer = MarkdownRenderer()
|
||||||
46
generated_data.json
Normal file
46
generated_data.json
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"name": "Skole",
|
||||||
|
"path": "skole",
|
||||||
|
"author": "Erika Nielsen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Generalt",
|
||||||
|
"path": "bolig",
|
||||||
|
"author": "Henrik Jess"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Job",
|
||||||
|
"path": "job",
|
||||||
|
"author": "Henrik Jess"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Unknown",
|
||||||
|
"path": "job",
|
||||||
|
"author": "Henrik Jess"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Skat",
|
||||||
|
"path": "skat",
|
||||||
|
"author": "Henrik Jess"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Job",
|
||||||
|
"path": "kontor",
|
||||||
|
"author": "Henrik Jess"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"favorites": [
|
||||||
|
{
|
||||||
|
"name": "Generalt",
|
||||||
|
"image": "images/pic07.jpg",
|
||||||
|
"description": "Lidt omkring job situationen og hvordan det fungere"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Job",
|
||||||
|
"image": "images/pic04.jpg",
|
||||||
|
"description": "Lidt omkring job situationen og hvordan det fungere"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
from jinja2 import Environment, FileSystemLoader
|
|
||||||
import markdown
|
|
||||||
|
|
||||||
def img_left_overlay(src):
|
|
||||||
"""Render an image with overlay."""
|
|
||||||
return f'''
|
|
||||||
<div class="img-left-overlay">
|
|
||||||
<img src="{src}" alt="Overlay Image">
|
|
||||||
<div class="overlay-text">Overlay Text</div>
|
|
||||||
</div>
|
|
||||||
'''
|
|
||||||
|
|
||||||
def box(title, content):
|
|
||||||
"""Render a box component."""
|
|
||||||
return f'''
|
|
||||||
<div class="box">
|
|
||||||
<strong>{title}</strong>
|
|
||||||
<p>{content}</p>
|
|
||||||
</div>
|
|
||||||
'''
|
|
||||||
|
|
||||||
def note(content):
|
|
||||||
"""Render a note component."""
|
|
||||||
return f'''
|
|
||||||
<div class="note">
|
|
||||||
<p>{content}</p>
|
|
||||||
</div>
|
|
||||||
'''
|
|
||||||
|
|
||||||
def warning(content):
|
|
||||||
"""Render a warning component."""
|
|
||||||
return f'''
|
|
||||||
<div class="warning">
|
|
||||||
⚠️ <p>{content}</p>
|
|
||||||
</div>
|
|
||||||
'''
|
|
||||||
|
|
||||||
def create_jinja_environment(template_dir="templates"):
|
|
||||||
"""Set up Jinja2 environment and register custom components."""
|
|
||||||
env = Environment(loader=FileSystemLoader(template_dir))
|
|
||||||
env.globals.update({
|
|
||||||
"img_left_overlay": img_left_overlay,
|
|
||||||
"box": box,
|
|
||||||
"note": note,
|
|
||||||
"warning": warning,
|
|
||||||
})
|
|
||||||
return env
|
|
||||||
|
|
||||||
def render_markdown_with_jinja(markdown_content, template_data=None):
|
|
||||||
"""Process Markdown first, then inject Jinja2 components."""
|
|
||||||
# Step 1: Convert Markdown to HTML
|
|
||||||
md_html = markdown.markdown(markdown_content, extensions=['extra', 'nl2br'])
|
|
||||||
|
|
||||||
# Step 2: Use Jinja2 to inject components into the already-rendered HTML
|
|
||||||
env = create_jinja_environment()
|
|
||||||
template = env.from_string(md_html)
|
|
||||||
rendered_html = template.render(template_data or {})
|
|
||||||
|
|
||||||
return rendered_html
|
|
||||||
@@ -1,13 +1,41 @@
|
|||||||
{
|
{
|
||||||
"categories": [
|
"categories": [
|
||||||
{"name": "SKAT", "path": "skat", "author": "Henrik"},
|
{
|
||||||
{"name": "Skole", "path": "skole", "author": "Erika"},
|
"name": "Skole",
|
||||||
{"name": "Bolig", "path": "bolig", "author": "Henrik"},
|
"path": "skole",
|
||||||
{"name": "Job", "path": "job", "author": "Henrik"}
|
"author": "Erika Nielsen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Generalt",
|
||||||
|
"path": "bolig",
|
||||||
|
"author": "Henrik Jess"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Job",
|
||||||
|
"path": "job",
|
||||||
|
"author": "Henrik Jess"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Skat",
|
||||||
|
"path": "skat",
|
||||||
|
"author": "Henrik Jess"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Job",
|
||||||
|
"path": "kontor",
|
||||||
|
"author": "Henrik Jess"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"favorites": [
|
"favorites": [
|
||||||
{"name": "SKAT", "image": "images/pic07.jpg", "description": "Favorit Kategori"},
|
{
|
||||||
{"name": "Skole", "image": "images/pic08.jpg", "description": "Skole information"},
|
"name": "Generalt",
|
||||||
{"name": "Bolig", "image": "images/pic09.jpg", "description": "Bolig detaljer"}
|
"image": "images/pic07.jpg",
|
||||||
|
"description": "Lidt omkring job situationen og hvordan det fungere"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Job",
|
||||||
|
"image": "images/pic04.jpg",
|
||||||
|
"description": "Lidt omkring job situationen og hvordan det fungere"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -3,21 +3,13 @@ anyio==4.7.0
|
|||||||
click==8.1.7
|
click==8.1.7
|
||||||
fastapi==0.115.6
|
fastapi==0.115.6
|
||||||
h11==0.14.0
|
h11==0.14.0
|
||||||
httptools==0.6.4
|
|
||||||
idna==3.10
|
idna==3.10
|
||||||
Jinja2==3.1.4
|
Jinja2==3.1.4
|
||||||
Markdown==3.7
|
Markdown==3.7
|
||||||
markdown-it-py==3.0.0
|
|
||||||
MarkupSafe==3.0.2
|
MarkupSafe==3.0.2
|
||||||
mdurl==0.1.2
|
pydantic==2.10.3
|
||||||
pydantic==2.6.3
|
pydantic_core==2.27.1
|
||||||
pydantic_core==2.16.3
|
|
||||||
python-dotenv==1.0.1
|
|
||||||
PyYAML==6.0.2
|
|
||||||
sniffio==1.3.1
|
sniffio==1.3.1
|
||||||
starlette==0.41.3
|
starlette==0.41.3
|
||||||
typing_extensions==4.12.2
|
typing_extensions==4.12.2
|
||||||
uvicorn==0.32.1
|
uvicorn==0.32.1
|
||||||
uvloop==0.21.0
|
|
||||||
watchfiles==1.0.0
|
|
||||||
websockets==14.1
|
|
||||||
|
|||||||
@@ -110,23 +110,23 @@ body.is-preload *, body.is-preload *:before, body.is-preload *:after, body.is-re
|
|||||||
body, input, select, textarea {
|
body, input, select, textarea {
|
||||||
color: #7f888f;
|
color: #7f888f;
|
||||||
font-family: "Open Sans", sans-serif;
|
font-family: "Open Sans", sans-serif;
|
||||||
font-size: 16pt;
|
font-size: 14pt;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.65;
|
line-height: 1.65;
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 1680px) {
|
@media screen and (max-width: 1680px) {
|
||||||
body, input, select, textarea {
|
body, input, select, textarea {
|
||||||
font-size: 13pt;
|
font-size: 14pt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 1280px) {
|
@media screen and (max-width: 1280px) {
|
||||||
body, input, select, textarea {
|
body, input, select, textarea {
|
||||||
font-size: 11pt;
|
font-size: 12pt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 360px) {
|
@media screen and (max-width: 360px) {
|
||||||
body, input, select, textarea {
|
body, input, select, textarea {
|
||||||
font-size: 10pt;
|
font-size: 12pt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,6 +281,42 @@ hr.major {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main > section > div > h1 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
|
line-height: 1.3;
|
||||||
|
color: #3d4449;
|
||||||
|
font-family: "Roboto Slab", serif;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
main > section > div > h2 {
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
|
line-height: 1.3;
|
||||||
|
color: #3d4449;
|
||||||
|
font-family: "Roboto Slab", serif;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
main > section > div > h3 {
|
||||||
|
font-size: 1.1em;
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
|
line-height: 1.3;
|
||||||
|
color: #3d4449;
|
||||||
|
font-family: "Roboto Slab", serif;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
main > section > div > h4 {
|
||||||
|
font-size: 1em;
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
|
line-height: 1.3;
|
||||||
|
color: #3d4449;
|
||||||
|
font-family: "Roboto Slab", serif;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
/* Row */
|
/* Row */
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,28 +1,23 @@
|
|||||||
///
|
|
||||||
/// Editorial by HTML5 UP
|
|
||||||
/// html5up.net | @ajlkn
|
|
||||||
/// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
|
||||||
///
|
|
||||||
|
|
||||||
/* Type */
|
/* Type */
|
||||||
|
|
||||||
body, input, select, textarea {
|
body, input, select, textarea {
|
||||||
color: _palette(fg);
|
color: _palette(fg);
|
||||||
font-family: _font(family);
|
font-family: _font(family);
|
||||||
font-size: 16pt;
|
font-size: 14pt;
|
||||||
font-weight: _font(weight);
|
font-weight: _font(weight);
|
||||||
line-height: 1.65;
|
line-height: 1.65;
|
||||||
|
|
||||||
@include breakpoint('<=xlarge') {
|
@include breakpoint('<=xlarge') {
|
||||||
font-size: 13pt;
|
font-size: 14pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include breakpoint('<=large') {
|
@include breakpoint('<=large') {
|
||||||
font-size: 11pt;
|
font-size: 12pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include breakpoint('<=xxsmall') {
|
@include breakpoint('<=xxsmall') {
|
||||||
font-size: 10pt;
|
font-size: 12pt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,3 +180,39 @@
|
|||||||
.align-right {
|
.align-right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #################################3
|
||||||
|
// Markdown pages styling
|
||||||
|
// #################################3
|
||||||
|
|
||||||
|
// Define a mixin for shared heading styles
|
||||||
|
@mixin shared-heading-styles($font-size) {
|
||||||
|
font-size: $font-size;
|
||||||
|
margin: 0 0 (_size(element-margin) * 0.25) 0;
|
||||||
|
line-height: 1.3;
|
||||||
|
color: _palette(fg-bold); // Assuming this is in your general h1
|
||||||
|
font-family: _font(family-heading);
|
||||||
|
font-weight: _font(weight-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the mixin for h1
|
||||||
|
main > section > div > h1 {
|
||||||
|
@include shared-heading-styles(1.5em);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the mixin for h2, h3, and h4 with adjusted font sizes
|
||||||
|
main > section > div > h2 {
|
||||||
|
@include shared-heading-styles(1.2em);
|
||||||
|
}
|
||||||
|
|
||||||
|
main > section > div > h3 {
|
||||||
|
@include shared-heading-styles(1.1em);
|
||||||
|
}
|
||||||
|
|
||||||
|
main > section > div > h4 {
|
||||||
|
@include shared-heading-styles(1.0em);
|
||||||
|
}
|
||||||
|
|
||||||
|
main > section > div > h4 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
2957
static/styles/main.css
Normal file
2957
static/styles/main.css
Normal file
File diff suppressed because it is too large
Load Diff
1
static/styles/main.css.map
Normal file
1
static/styles/main.css.map
Normal file
File diff suppressed because one or more lines are too long
@@ -4,7 +4,6 @@
|
|||||||
<title>{% block title %}PortugalFAQ{% endblock %}</title>
|
<title>{% block title %}PortugalFAQ{% endblock %}</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||||
<!-- <link rel="stylesheet" href="{{ url_for('static', path='css/main.css') }}"> -->
|
|
||||||
<link rel="stylesheet" href="/static/css/main.css">
|
<link rel="stylesheet" href="/static/css/main.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="is-preload">
|
<body class="is-preload">
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<header class="main">
|
<header class="main">
|
||||||
<h1>{{ category_name }}</h1>
|
<!-- Spacer for future header -->
|
||||||
|
<p></p>
|
||||||
</header>
|
</header>
|
||||||
<div>
|
<div>
|
||||||
{{ content | safe }}
|
{{ content | safe }}
|
||||||
|
|||||||
20
templates/combined_template.html
Normal file
20
templates/combined_template.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<main>
|
||||||
|
{% for section in sections %}
|
||||||
|
<section>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8 col-12-medium">
|
||||||
|
<p><b>Opsumering: {{ section.summary }}</b></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 col-12-medium">
|
||||||
|
<p><em>Forfatter: {{ section.author }}</em></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2>{{ section.name }}</h2>
|
||||||
|
<div>
|
||||||
|
{{ section.content | safe }}
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
</section>
|
||||||
|
{% endfor %}
|
||||||
|
</main>
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<span class="image main">
|
<span class="image main">
|
||||||
<img src="{{ url_for('static', path='images/pic11.jpg') }}" alt="Portugal" />
|
<img src="static/images/pic11.jpg" alt="Portugal" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<hr class="major" />
|
<hr class="major" />
|
||||||
|
|||||||
Reference in New Issue
Block a user