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.services.metadata_processor import MetadataProcessor
from app.controllers.category_controller import CategoryController from app.controllers.category_controller import CategoryController
from fastapi.middleware.gzip import GZipMiddleware from fastapi.middleware.gzip import GZipMiddleware
from app.services.image_controller import ImageHandler from app.services.image_service import ImageService
class Application: class Application:
def __init__(self): def __init__(self):
"""Initialize the FastAPI app and configure it.""" """Initialize the FastAPI app and configure it."""
self.app = FastAPI(lifespan=self._lifespan_event) self.app = FastAPI(lifespan=self._lifespan_event)
self._set_image_sizes()
self._setup_static_files() self._setup_static_files()
self._include_routers() self._include_routers()
self._include_middelware() self._include_middelware()
@asynccontextmanager @asynccontextmanager
async def _lifespan_event(self, app: FastAPI): async def _lifespan_event(self, app: FastAPI):
"""Lifespan event for startup and shutdown logic.""" """Lifespan event for startup and shutdown logic."""
print("App startup: Processing Markdown files...") print("App startup: Processing Markdown files...")
# Generate dynamic JSON data # 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() metadata_processor.generate_json()
print("Generated dynamic data file.") print("Generated dynamic data file.")
print("Markdown processing complete!")
# Process Markdown files into HTML # 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() processor.run()
yield yield
print("App shutdown: Cleanup complete.") print("App shutdown: Cleanup complete.")
@@ -38,24 +39,30 @@ class Application:
"""Mount static file directories.""" """Mount static file directories."""
self.app.mount("/data", StaticFiles(directory="data"), name="data") self.app.mount("/data", StaticFiles(directory="data"), name="data")
self.app.mount("/static", StaticFiles(directory="static"), name="static") self.app.mount("/static", StaticFiles(directory="static"), name="static")
self.app.mount( "/images", StaticFiles( directory = "static/images" ), name = "images" )
def _include_routers(self): def _include_routers(self):
"""Include all route controllers.""" """Include all route controllers."""
category_controller = CategoryController() category_controller = CategoryController()
#dynamic_controller = DynamicController( "./data" ) image_service = ImageService(self.app)
route_to_web = RouteToWeb(self.app) route_to_web = RouteToWeb(self.app)
self.app.include_router( category_controller.router ) 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(route_to_web.router)
self.app.include_router( image_service.router )
def _include_middelware(self): def _include_middelware(self):
self.app.add_middleware( GZipMiddleware, minimum_size = 500 ) 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): def get_app(self):
"""Return the FastAPI app instance.""" """Return the FastAPI app instance."""
return self.app 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 import os
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from fastapi import FastAPI
from app.services.markdown_render import render_markdown_with_jinja # Your custom renderer from app.services.markdown_render import MarkdownRenderer
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
@@ -12,7 +11,7 @@ class MarkdownProcessor:
'index.html' per category directory using a custom rendering engine. '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. Initialize the MarkdownProcessor.
@@ -22,6 +21,8 @@ class MarkdownProcessor:
""" """
self.input_dir = input_dir self.input_dir = input_dir
self.env = Environment(loader=FileSystemLoader(templates_dir)) self.env = Environment(loader=FileSystemLoader(templates_dir))
self.app = app
def _process_markdown_files_in_directory(self, directory_path: str) -> list: def _process_markdown_files_in_directory(self, directory_path: str) -> list:
""" """
@@ -33,15 +34,21 @@ class MarkdownProcessor:
Returns: Returns:
list: A list of processed sections containing metadata and rendered content. list: A list of processed sections containing metadata and rendered content.
""" """
from pathlib import Path
sections = [] sections = []
for file in sorted(os.listdir(directory_path)): for file in sorted(os.listdir(directory_path)):
if file.endswith(".md"): if file.endswith(".md"):
file_path = os.path.join(directory_path, file) file_path = os.path.join(directory_path, file)
with open(file_path, "r", encoding="utf-8") as md_file: with open(file_path, "r", encoding="utf-8") as md_file:
markdown_content = md_file.read() markdown_content = md_file.read()
markdown_render = MarkdownRenderer(file_path=file_path,app=self.app)
# Process Markdown and Jinja2 # 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 # Append the section to the list
sections.append({ sections.append({

View File

@@ -1,123 +1,168 @@
from pathlib import Path
import sys
import markdown import markdown
from fastapi import FastAPI
from jinja2 import Environment, DictLoader from jinja2 import Environment, DictLoader
from .image_controller import ImageHandler from markupsafe import Markup
# Define Jinja2 custom functions from .image_service import ImageService
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>
'''
def box(title, content): class MarkdownRenderer:
"""Render a box component.""" def __init__(self, file_path: str = None, app: FastAPI=None):
return f''' """
<div class="box"> Initialize the MarkdownRenderer with a Jinja2 environment and custom functions.
<strong>{title}</strong> """
<p>{content}</p> self.app = app
</div> self.image_service = ImageService(self.app)
''' self.jinja_env = self._create_jinja_environment()
self.file_path = file_path
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>
'''
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) def _create_jinja_environment(self) -> Environment:
height = options.get("height", 375) """
Create and configure the Jinja2 environment with custom functions.
html_content = [] Returns:
html_content.append('<div class="button-stack">') Environment: A configured Jinja2 environment.
for i, val in enumerate(images): """
modal_id = f"{modal_id}_{i}" env = Environment(loader=DictLoader({"base_template": "{{ content | safe }}"}))
modal_id_next = f"{modal_id}_{i+1}"
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>""")
html_content.append( '</div>' ) env.globals.update({
html = '\n'.join( html_content ) "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
return html 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>
'''
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(): html_content = []
"""Create and configure the Jinja2 environment.""" html_content.append('<div class="button-stack">')
env = Environment(loader=DictLoader({"base_template": "{{ content | safe }}"})) for i, val in enumerate(images):
image_handler = ImageHandler(base_dir="static/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({ html_content.append(f"""
"img_left_overlay": img_left_overlay, <button onclick="openModal('modal{modal_id_current}')" class="stacked-button">
"box": box, <img src="{val}" alt="Image {i}" class="thumbnail" loading="lazy">
"note": note, </button>
"warning": warning, <div class="modal" id="modal{modal_id_current}">
"link_to": link_to, <div class="modal-content">
"slider": slider, <h2>Modal {i}</h2>
"image": image_handler.generate_image_tag, # Add image handler function <img src="{val}" alt="Image {i}" loading="lazy">
<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 _get_category(self):
return env if isinstance(self.file_path, str):
this_path = Path(self.file_path)
return this_path.parent.name
def render_markdown_with_jinja(markdown_content: str): return True
"""
Convert Markdown to HTML and apply Jinja2 rendering for custom tags.
Args:
markdown_content (str): Raw Markdown content.
Returns: def get_image(self, image_type: str, filename: str, alt: str = "", width: int = None, height: int = None) -> Markup:
tuple: Rendered HTML content and metadata as a dictionary. """
""" Generate a dynamic HTML <img> tag for an image using ImageService's image_tag method.
# Step 1: Convert Markdown to HTML and extract metadata """
md = markdown.Markdown(extensions=["extra", "nl2br", "meta"]) valid_types = ['thumbnails', 'large', 'small', 'original']
intermediate_html = md.convert(markdown_content) if image_type not in valid_types:
metadata = {key: " ".join(value) for key, value in md.Meta.items()} if md.Meta else {} sys.tracebacklimit = 0
raise ValueError( f"Invalid image type: {image_type}. Must be one of {valid_types}" )
# Step 2: Pass the resulting HTML with Jinja2 custom tags through Jinja2 tag = self.image_service.image_tag(
env = create_jinja_environment() 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
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 {{ 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 import json
from typing import List, Dict from typing import List, Dict
from fastapi import FastAPI
class MetadataProcessor: class MetadataProcessor:
""" """
@@ -10,7 +12,7 @@ class MetadataProcessor:
and generate a structured JSON file. 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. 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. 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 ### Lidt billeder

View File

@@ -1,7 +1,8 @@
# Min Drøm om Portugal # Min Drøm om Portugal
## En Frisk Start for Henrik og Erika ## 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. 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 %} {% for favorite in data.favorites %}
<article> <article>
<a href="/category/{{ favorite.path }}" class="image"> <a href="/category/{{ favorite.path }}" class="image">
<img src="/static/{{favorite.image}}" alt="{{ favorite.name }}" height="20%" width="20%" /> <img src="/static/{{favorite.image}}" alt="{{ favorite.name }}" height="20%" width="20%" />
</a> </a>
<p><b>{{ favorite.path }}</b>: {{ favorite.description }}</p> <p><b>{{ favorite.path }}</b>: {{ favorite.description }}</p>