Compare commits

..

1 Commits

Author SHA1 Message Date
fa359f095d Lets test 2024-12-11 20:45:52 +01:00
51 changed files with 263 additions and 3850 deletions

Binary file not shown.

Binary file not shown.

147
app.py
View File

@@ -1,5 +1,144 @@
import uvicorn
from app.main import app
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
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=8210, 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")
# 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)

View File

Binary file not shown.

View File

@@ -1,56 +0,0 @@
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)

View File

@@ -1,65 +0,0 @@
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

View File

@@ -1,53 +0,0 @@
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()

View File

@@ -1,87 +0,0 @@
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)

View File

@@ -1,74 +0,0 @@
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

View File

@@ -1,83 +0,0 @@
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}")

View File

@@ -1,5 +1,5 @@
import os
from app.services.markdown_render import render_markdown_with_jinja
from markdown_render import render_markdown_with_jinja
def process_markdown_files(input_dir: str, output_dir: str):
"""

View File

@@ -1,26 +1 @@
<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>
<P> Lidt om Bolig </P>

View File

@@ -0,0 +1,7 @@
<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>

View File

@@ -1,17 +1,5 @@
---
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
# BoligBolig Bolig Bolig - Hvor skal sengen placeres
Nu bliver det spænde!
{{ note("Dette er stadig en test side") }}
{img-left-overlay: images/my-cat.png}

View File

@@ -0,0 +1,7 @@
<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>

View File

@@ -1,13 +1,3 @@
---
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
Der skal langt mere tekst her

View File

@@ -1,57 +1 @@
<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>
<P> Lidt om job </P>

View File

@@ -1,18 +0,0 @@
---
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.") }}

View File

@@ -1,25 +0,0 @@
<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>

View File

@@ -1,16 +0,0 @@
---
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") }}

View File

@@ -1,20 +1 @@
<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>
<P> Lidt om skat </P>

View File

@@ -1,13 +0,0 @@
---
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

View File

@@ -1,22 +1 @@
<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>
<P> Lidt om skole </P>

View File

@@ -1,17 +0,0 @@
---
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!

View File

@@ -1,25 +0,0 @@
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)

View File

@@ -1,13 +0,0 @@
{
"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"}
]
}

View File

@@ -1,46 +0,0 @@
{
"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"
}
]
}

59
markdown_render.py Normal file
View File

@@ -0,0 +1,59 @@
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

View File

@@ -1,41 +1,13 @@
{
"categories": [
{
"name": "Skole",
"path": "skole",
"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"
}
{"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": "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"
}
{"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"}
]
}

View File

@@ -3,13 +3,21 @@ anyio==4.7.0
click==8.1.7
fastapi==0.115.6
h11==0.14.0
httptools==0.6.4
idna==3.10
Jinja2==3.1.4
Markdown==3.7
markdown-it-py==3.0.0
MarkupSafe==3.0.2
pydantic==2.10.3
pydantic_core==2.27.1
mdurl==0.1.2
pydantic==2.6.3
pydantic_core==2.16.3
python-dotenv==1.0.1
PyYAML==6.0.2
sniffio==1.3.1
starlette==0.41.3
typing_extensions==4.12.2
uvicorn==0.32.1
uvloop==0.21.0
watchfiles==1.0.0
websockets==14.1

View File

@@ -110,23 +110,23 @@ body.is-preload *, body.is-preload *:before, body.is-preload *:after, body.is-re
body, input, select, textarea {
color: #7f888f;
font-family: "Open Sans", sans-serif;
font-size: 14pt;
font-size: 16pt;
font-weight: 400;
line-height: 1.65;
}
@media screen and (max-width: 1680px) {
body, input, select, textarea {
font-size: 14pt;
font-size: 13pt;
}
}
@media screen and (max-width: 1280px) {
body, input, select, textarea {
font-size: 12pt;
font-size: 11pt;
}
}
@media screen and (max-width: 360px) {
body, input, select, textarea {
font-size: 12pt;
font-size: 10pt;
}
}
@@ -281,42 +281,6 @@ hr.major {
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 {
display: flex;

File diff suppressed because one or more lines are too long

View File

@@ -1,23 +1,28 @@
///
/// Editorial by HTML5 UP
/// html5up.net | @ajlkn
/// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
///
/* Type */
body, input, select, textarea {
color: _palette(fg);
font-family: _font(family);
font-size: 14pt;
font-size: 16pt;
font-weight: _font(weight);
line-height: 1.65;
@include breakpoint('<=xlarge') {
font-size: 14pt;
font-size: 13pt;
}
@include breakpoint('<=large') {
font-size: 12pt;
font-size: 11pt;
}
@include breakpoint('<=xxsmall') {
font-size: 12pt;
font-size: 10pt;
}
}
@@ -180,39 +185,3 @@
.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 {
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -4,6 +4,7 @@
<title>{% block title %}PortugalFAQ{% endblock %}</title>
<meta charset="utf-8" />
<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">
</head>
<body class="is-preload">

View File

@@ -4,8 +4,7 @@
{% block content %}
<header class="main">
<!-- Spacer for future header -->
<p></p>
<h1>{{ category_name }}</h1>
</header>
<div>
{{ content | safe }}

View File

@@ -1,20 +0,0 @@
<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>

View File

@@ -10,7 +10,7 @@
</header>
<span class="image main">
<img src="static/images/pic11.jpg" alt="Portugal" />
<img src="{{ url_for('static', path='images/pic11.jpg') }}" alt="Portugal" />
</span>
<hr class="major" />

View File

@@ -1,4 +1,4 @@
from app.services.markdown_render import MarkdownRenderer
from markdown_render import MarkdownRenderer
# Initialize MarkdownRenderer
renderer = MarkdownRenderer()