Lets make the frontpage in markdown too
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled

This commit is contained in:
2024-12-29 04:34:15 +01:00
parent 95b6c1fa05
commit 0121662530
26 changed files with 341 additions and 547 deletions

View File

@@ -7,29 +7,30 @@ from app.services.markdown_processor import MarkdownProcessor
from app.services.metadata_processor import MetadataProcessor
from app.controllers.category_controller import CategoryController
from fastapi.middleware.gzip import GZipMiddleware
from app.services.image_controller import ImageHandler
from app.services.image_service import ImageService
class Application:
def __init__(self):
"""Initialize the FastAPI app and configure it."""
self.app = FastAPI(lifespan=self._lifespan_event)
self._set_image_sizes()
self._setup_static_files()
self._include_routers()
self._include_middelware()
@asynccontextmanager
async def _lifespan_event(self, app: FastAPI):
"""Lifespan event for startup and shutdown logic."""
print("App startup: Processing Markdown files...")
# Generate dynamic JSON data
metadata_processor = MetadataProcessor(input_dir="./data", output_file="generated_data.json")
metadata_processor = MetadataProcessor(input_dir="./data", output_file="generated_data.json",app=self.app)
metadata_processor.generate_json()
print("Generated dynamic data file.")
print("Markdown processing complete!")
# Process Markdown files into HTML
processor = MarkdownProcessor(input_dir="./data", templates_dir="./templates")
processor = MarkdownProcessor(input_dir="./data", templates_dir="./templates",app=self.app)
processor.run()
yield
print("App shutdown: Cleanup complete.")
@@ -38,24 +39,30 @@ class Application:
"""Mount static file directories."""
self.app.mount("/data", StaticFiles(directory="data"), name="data")
self.app.mount("/static", StaticFiles(directory="static"), name="static")
self.app.mount( "/images", StaticFiles( directory = "static/images" ), name = "images" )
def _include_routers(self):
"""Include all route controllers."""
category_controller = CategoryController()
#dynamic_controller = DynamicController( "./data" )
image_service = ImageService(self.app)
route_to_web = RouteToWeb(self.app)
self.app.include_router( category_controller.router )
#self.app.include_router( dynamic_controller.router )
self.app.include_router(route_to_web.router)
self.app.include_router( image_service.router )
def _include_middelware(self):
self.app.add_middleware( GZipMiddleware, minimum_size = 500 )
def _set_image_sizes(self):
self.app.state.IMAGE_SIZES = {
'thumbnails': {'width': 100, 'height': 100},
'large': {'width': 800, 'height': 600},
'small': {'width': 300, 'height': 200},
'original': {'width': None, 'height': None}, # Original størrelse
}
def get_app(self):
"""Return the FastAPI app instance."""
return self.app

View File

@@ -1,72 +0,0 @@
import os
from PIL import Image
class ImageHandler:
def __init__(self, base_dir: str):
"""
Initialize the ImageHandler.
:param base_dir: Base directory for storing and retrieving images.
"""
self.base_dir = base_dir
def get_image_path(self, filename: str) -> str:
"""
Construct the full path for a given image file.
:param filename: Relative filename of the image.
:return: Full path to the image.
"""
return os.path.join(self.base_dir, filename)
def get_resized_image_path(self, filename: str, width: int, height: int) -> str:
"""
Construct the path for a resized image.
:param filename: Original image filename.
:param width: Desired width.
:param height: Desired height.
:return: Path to the resized image.
"""
return os.path.join(self.base_dir, f"resized_{width}x{height}_{filename}")
def resize_and_save(self, original_path: str, resized_path: str, width: int, height: int):
"""
Resize and save the image if it doesn't already exist.
:param original_path: Path to the original image file.
:param resized_path: Path to save the resized image.
:param width: Desired width.
:param height: Desired height.
"""
if not os.path.exists(resized_path):
with Image.open(original_path) as img:
img_resized = img.resize((width, height))
img_resized.save(resized_path, format="JPEG")
def generate_image_tag(self, src: str, width: int, height: int, css_class: str = "", alt: str = "") -> str:
"""
Generate an HTML <img> tag and ensure the image exists with the specified dimensions.
:param src: Relative path to the original image.
:param width: Desired width of the image.
:param height: Desired height of the image.
:param css_class: Optional CSS class to add to the <img> tag.
:param alt: Alternative text for the image.
:return: HTML <img> tag.
"""
original_path = self.get_image_path(src)
if not os.path.isfile(original_path):
raise FileNotFoundError(f"Image not found: {src}")
# Construct resized image path
resized_filename = f"resized_{width}x{height}_{os.path.basename(src)}"
resized_path = self.get_resized_image_path(src, width, height)
# Resize and save the image if necessary
self.resize_and_save(original_path, resized_path, width, height)
# Return the <img> tag
class_attr = f' class="{css_class}"' if css_class else ""
alt_attr = f' alt="{alt}"' if alt else ""
return f'<img src="/{resized_path}" width="{width}" height="{height}"{alt_attr}{class_attr}>'

View File

@@ -0,0 +1,156 @@
import os
from fastapi import HTTPException
from fastapi.responses import FileResponse
from fastapi import APIRouter, Request, FastAPI
from PIL import Image
class FileHandler:
def __init__(self, category, image_type, filename):
self.filename = filename
self.category = category
self.image_type = image_type
@property
def src_file(self) -> str:
src_path = "data/{category}/images/{filename}"
return src_path.format( category = self.category, filename = self.filename )
@property
def dest_file(self) -> str:
base_url = "/images/{category}/{filename}"
return base_url.format( category = self.category, filename = self.filename )
@property
def dest_filename(self) -> str:
base_url = "static/images/{category}/{image_type}/{filename}"
return base_url.format( category = self.category, image_type = self.image_type, filename = self.filename )
@property
def dest_path(self) -> str:
base_url = "static/images/{category}/{image_type}"
return base_url.format( category = self.category, image_type = self.image_type )
def __str__(self):
return (
f"FileHandler(\n"
f" filename='{self.filename}',\n"
f" category='{self.category}',\n"
f" image_type='{self.image_type}',\n"
f" src_file='{self.src_file}',\n"
f" dest_file='{self.dest_file}',\n"
f" dest_filename='{self.dest_filename}'\n"
f")"
)
class ImageService:
def __init__(self,app: FastAPI):
self.router = APIRouter()
self.app = app
self.IMAGE_SIZES = self.app.state.IMAGE_SIZES
#self._ensure_directories_exist()
self._add_routes()
def __str__(self):
"""
Provides a string representation of the class instance.
"""
base_paths_str = "\n".join(
[f"{key}: {value}" for key, value in self.base_paths.items()]
)
image_sizes_str = "\n".join(
[
f"{key}: width={value['width']}, height={value['height']}"
for key, value in self.image_sizes.items()
]
)
return f"<Class:ImageService Base Paths:{base_paths_str} Image Sizes:\n{image_sizes_str}"
def get_image_size(self, image_type: str) -> dict:
"""
Retrieve the width and height for a given image type from the app state.
Args:
request (Request): FastAPI request object.
image_type (str): The type of the image (e.g., 'thumbnails').
Returns:
dict: A dictionary with 'width' and 'height'.
"""
image_sizes = self.app.state.IMAGE_SIZES
if image_type not in image_sizes:
raise ValueError( f"Invalid image type: {image_type}. Must be one of {list( image_sizes.keys() )}" )
return image_sizes[image_type]
def _add_routes(self):
self.router.add_api_route(
"/image/{category}/{type}/{filename}",
self.get_image,
methods=["GET"],
response_class=FileResponse,
)
async def get_image(self, category: str, type: str, filename: str):
"""
Retrieve an image file from the specified category and type.
"""
file_path = self._resolve_path(category, type, filename)
return FileResponse(file_path)
def validate_image(self, file_path:FileHandler=None, width:int=None, height:int=None, overwrite = True ) -> bool:
if not os.path.exists( file_path.dest_filename ):
with Image.open( file_path.src_file ) as img:
print(file_path.src_file)
self._resize_image( img, file_path, width, height )
return True
with Image.open( file_path.dest_filename ) as img:
if img.width != width or img.height != height:
if overwrite:
self._resize_image( img, file_path, width, height )
return False
return True
def _resize_image(self, img: Image.Image, file_path: FileHandler, width: int, height: int):
resized_img = img.resize( (width, height), Image.Resampling.LANCZOS )
os.makedirs(file_path.dest_path,exist_ok = True)
resized_img.save( file_path.dest_filename )
async def get_image(self, category: str, type: str, filename: str):
file_path = self._resolve_path( category, type, filename )
return FileResponse( file_path )
def image_tag(self, category: str, image_type: str, filename: str, alt: str = "", width: int = None,
height: int = None) -> str:
"""
Generate an HTML <img> tag with default sizes if dimensions are not provided.
"""
# Use default sizes if none are provided
default_size = self.get_image_size( image_type)
width = width or default_size.get( "width" )
height = height or default_size.get( "height" )
#file_path = self._resolve_path( category, image_type, filename )
file_path = FileHandler(category = category,image_type = image_type,filename = filename)
self.validate_image( file_path, width = width,height=height, overwrite = True )
tag = f'<img src="{file_path.dest_filename}" alt="{alt}"'
if width:
tag += f' width="{width}"'
if height:
tag += f' height="{height}"'
tag += ">"
return tag

View File

@@ -1,8 +1,7 @@
import os
from bs4 import BeautifulSoup
from app.services.markdown_render import render_markdown_with_jinja # Your custom renderer
from fastapi import FastAPI
from app.services.markdown_render import MarkdownRenderer
from jinja2 import Environment, FileSystemLoader
@@ -12,7 +11,7 @@ class MarkdownProcessor:
'index.html' per category directory using a custom rendering engine.
"""
def __init__(self, input_dir: str, templates_dir: str):
def __init__(self, input_dir: str, templates_dir: str,app:FastAPI=None):
"""
Initialize the MarkdownProcessor.
@@ -22,6 +21,8 @@ class MarkdownProcessor:
"""
self.input_dir = input_dir
self.env = Environment(loader=FileSystemLoader(templates_dir))
self.app = app
def _process_markdown_files_in_directory(self, directory_path: str) -> list:
"""
@@ -33,15 +34,21 @@ class MarkdownProcessor:
Returns:
list: A list of processed sections containing metadata and rendered content.
"""
from pathlib import Path
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()
markdown_render = MarkdownRenderer(file_path=file_path,app=self.app)
# Process Markdown and Jinja2
rendered_content, metadata = render_markdown_with_jinja(markdown_content)
rendered_content, metadata = markdown_render.render_markdown_with_jinja ( markdown_content )
# Append the section to the list
sections.append({

View File

@@ -1,123 +1,168 @@
from pathlib import Path
import sys
import markdown
from fastapi import FastAPI
from jinja2 import Environment, DictLoader
from .image_controller import ImageHandler
# 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" loading="lazy">
<div class="overlay-text">Overlay Text</div>
</div>
'''
from markupsafe import Markup
from .image_service import ImageService
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 link_to(title, url):
"""Render a box component."""
return f'''
<a href="{url}" target="_blank" rel="noopener noreferrer">{title}</a>
'''
def warning(content):
"""Render a warning component."""
return f'''
<div class="warning">
⚠️ <p>{content}</p>
</div>
'''
class MarkdownRenderer:
def __init__(self, file_path: str = None, app: FastAPI=None):
"""
Initialize the MarkdownRenderer with a Jinja2 environment and custom functions.
"""
self.app = app
self.image_service = ImageService(self.app)
self.jinja_env = self._create_jinja_environment()
self.file_path = file_path
def slider(options, images):
"""Render a slider using the provided HTML structure."""
import uuid
modal_id = uuid.uuid4().hex.upper()[0:6] # Lets create some uniq modals
width = options.get("width", 500)
height = options.get("height", 375)
def _create_jinja_environment(self) -> Environment:
"""
Create and configure the Jinja2 environment with custom functions.
html_content = []
html_content.append('<div class="button-stack">')
for i, val in enumerate(images):
modal_id = f"{modal_id}_{i}"
modal_id_next = f"{modal_id}_{i+1}"
Returns:
Environment: A configured Jinja2 environment.
"""
env = Environment(loader=DictLoader({"base_template": "{{ content | safe }}"}))
if int(len(images))<=int(i+1):
modal_id_next = f"{modal_id}_0"
if i % 2 == 0:
html_content.append(f"""<button onclick="openModal('modal{modal_id}')" class="stacked-button"> <img src="{val}" alt="Lets do better" class="thumbnail" loading="lazy"></button>""".strip())
else:
html_content.append(f"""<button onclick="openModal('modal{modal_id}')" class="stacked-button"> <img src="{val}" alt="Lets do better" class="thumbnail" loading="lazy"></button>""".strip())
html_content.append(f"""<div class="modal" id="modal{modal_id}">
<div class="modal-content">
<h2>Modal {i}</h2>
<img src="{val}" alt="Lets do better" loading="lazy">
<div class="modal-buttons">
<button onclick="closeModal('modal{modal_id}')">Close</button>
<button class="next-btn" onclick="nextModal('modal{modal_id}', 'modal{modal_id_next}')">Next</button>
</div>
</div>
</div>""")
env.globals.update({
"img_left_overlay": self.img_left_overlay,
"box": self.box,
"note": self.note,
"warning": self.warning,
"link_to": self.link_to,
"slider": self.slider,
"image": self.get_image, # Add image handler function
})
return env
html_content.append( '</div>' )
html = '\n'.join( html_content )
def img_left_overlay(self, src: str) -> str:
"""Render an image with overlay."""
return f'''
<div class="img-left-overlay">
<img src="{src}" alt="Overlay Image" loading="lazy">
<div class="overlay-text">Overlay Text</div>
</div>
'''
return html
def box(self, title: str, content: str) -> str:
"""Render a box component."""
return f'''
<div class="box">
<strong>{title}</strong>
<p>{content}</p>
</div>
'''
def note(self, content: str) -> str:
"""Render a note component."""
return f'''
<div class="note">
<p>{content}</p>
</div>
'''
def link_to(self, title: str, url: str) -> str:
"""Render a link component."""
return f'''
<a href="{url}" target="_blank" rel="noopener noreferrer">{title}</a>
'''
def warning(self, content: str) -> str:
"""Render a warning component."""
return f'''
<div class="warning">
⚠️ <p>{content}</p>
</div>
'''
def slider(self, options: dict, images: list) -> str:
"""Render a slider component."""
import uuid
modal_id = uuid.uuid4().hex.upper()[0:6]
def create_jinja_environment():
"""Create and configure the Jinja2 environment."""
env = Environment(loader=DictLoader({"base_template": "{{ content | safe }}"}))
image_handler = ImageHandler(base_dir="static/images")
html_content = []
html_content.append('<div class="button-stack">')
for i, val in enumerate(images):
self.image_service = ImageService( self.app )
print(val)
modal_id_current = f"{modal_id}_{i}"
modal_id_next = f"{modal_id}_{i + 1}" if i + 1 < len(images) else f"{modal_id}_0"
env.globals.update({
"img_left_overlay": img_left_overlay,
"box": box,
"note": note,
"warning": warning,
"link_to": link_to,
"slider": slider,
"image": image_handler.generate_image_tag, # Add image handler function
html_content.append(f"""
<button onclick="openModal('modal{modal_id_current}')" class="stacked-button">
<img src="{val}" alt="Image {i}" class="thumbnail" loading="lazy">
</button>
<div class="modal" id="modal{modal_id_current}">
<div class="modal-content">
<h2>Modal {i}</h2>
<img src="{val}" alt="Image {i}" loading="lazy">
})
return env
<div class="modal-buttons">
<button onclick="closeModal('modal{modal_id_current}')">Close</button>
<button class="next-btn" onclick="nextModal('modal{modal_id_current}', 'modal{modal_id_next}')">Next</button>
</div>
</div>
</div>
""")
html_content.append('</div>')
return '\n'.join(html_content)
def render_markdown_with_jinja(markdown_content: str):
"""
Convert Markdown to HTML and apply Jinja2 rendering for custom tags.
def _get_category(self):
if isinstance(self.file_path, str):
this_path = Path(self.file_path)
return this_path.parent.name
Args:
markdown_content (str): Raw Markdown content.
return True
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()
def get_image(self, image_type: str, filename: str, alt: str = "", width: int = None, height: int = None) -> Markup:
"""
Generate a dynamic HTML <img> tag for an image using ImageService's image_tag method.
"""
valid_types = ['thumbnails', 'large', 'small', 'original']
if image_type not in valid_types:
sys.tracebacklimit = 0
raise ValueError( f"Invalid image type: {image_type}. Must be one of {valid_types}" )
template = env.get_template("base_template")
final_html = template.render(content=intermediate_html)
tag = self.image_service.image_tag(
category=self._get_category(),
image_type=image_type,
filename=filename,
alt=alt,
width=width,
height=height
)
my_tag = Markup(tag)
print(my_tag)
return my_tag
# Step 3: Re-render final_html in Jinja2 for embedded tags like {{ image(...) }}
final_output = env.from_string(final_html).render()
return final_output, metadata
def render_markdown_with_jinja(self, 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.
"""
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 3: Pass the resulting HTML with Jinja2 custom tags through Jinja2
template = self.jinja_env.get_template("base_template")
final_html = template.render(content=intermediate_html)
# Step 4: Re-render final_html in Jinja2 for embedded tags like {{ image(...) }}
final_output = self.jinja_env.from_string(final_html).render()
return final_output, metadata

View File

@@ -3,6 +3,8 @@ import markdown
import json
from typing import List, Dict
from fastapi import FastAPI
class MetadataProcessor:
"""
@@ -10,7 +12,7 @@ class MetadataProcessor:
and generate a structured JSON file.
"""
def __init__(self, input_dir: str, output_file: str):
def __init__(self, input_dir: str, output_file: str,app:FastAPI=None):
"""
Initialize the MetadataProcessor.

View File

@@ -1,26 +0,0 @@
<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>Dato: </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>

View File

@@ -1,17 +0,0 @@
---
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!
{{ note("Dette er stadig en test side") }}
{img-left-overlay: images/my-cat.png}

View File

@@ -1,19 +0,0 @@
---
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
{{ note("Husk alpha side") }}

View File

@@ -1,57 +0,0 @@
<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>Dato: </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: Lidt omkring job situationen og hvordan det fungere</b></p>
</div>
<div class="col-4 col-12-medium">
<p><em>Dato: </em></p>
</div>
</div>
<h2>Untitled</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>

View File

@@ -1,22 +0,0 @@
---
name: Job Job Job Job 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
---
# 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>Dato: </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: Kontor
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 +0,0 @@
<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>Dato: </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>

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 +0,0 @@
<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>Dato: </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>

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

@@ -69,7 +69,6 @@ Lissabon føles på mange måder som København med fokus på kultur, kvalit
Samtidig er mange oplevelser billigere end i København, og her er en atmosfære, der er svær at finde nordpå: Lissabon har en unik blanding af tradition og modernitet, som gør den til en by, der bliver ved med at fascinere.
{{ image("pic11.jpg", 200, 150, "thumbnail", "Example Image") }}
### Lidt billeder

View File

@@ -1,7 +1,8 @@
# Min Drøm om Portugal
## En Frisk Start for Henrik og Erika
{{ image("pic09.jpg", 475, 100, "", "Example Image") }}
{{ image('thumbnails', 'pic11.jpg', alt='Mit fantatiske billed') }}
---
@@ -14,3 +15,4 @@ For Erika er det også en chance for at opleve en ny kultur, møde nye mennesker
Denne hjemmeside er en samling af mine tanker, forberedelser og erfaringer på vejen mod at gøre drømmen til virkelighed. Måske overvejer du selv en lignende rejse, eller måske er du bare nysgerrig på, hvordan det er at tage springet. Her deler jeg min rejse fra de første spæde idéer til de praktiske skridt mod et nyt liv i Portugal. For mig og for Erika.
---

View File

@@ -1,43 +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 to 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)
print("Markdown processing complete!")
if __name__ == "__main__":
# Input directory containing Markdown files
input_directory = "./data"
# Output directory where HTML files will be stored
output_directory = "./data"
# Start the processing
process_markdown_files(input_directory, output_directory)

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,18 +0,0 @@
from app.services.markdown_render import MarkdownRenderer
# Initialize MarkdownRenderer
renderer = MarkdownRenderer()
# Test Markdown input
markdown_content = """
{img-left-overlay: src=my-cat.png}
{box: title=Test Box, content=This is a test.}
{note: content=This is a note.}
{warning: content=Be careful!}
"""
# Render to HTML
print("Rendering Markdown...")
html_output = renderer.render(markdown_content)
print("Rendered HTML:")
print(html_output)

Binary file not shown.

Binary file not shown.

View File

@@ -26,6 +26,7 @@
{% for favorite in data.favorites %}
<article>
<a href="/category/{{ favorite.path }}" class="image">
<img src="/static/{{favorite.image}}" alt="{{ favorite.name }}" height="20%" width="20%" />
</a>
<p><b>{{ favorite.path }}</b>: {{ favorite.description }}</p>