generated from hjess/PythonTemplateProject
Lets make the frontpage in markdown too
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled
This commit is contained in:
27
app/main.py
27
app/main.py
@@ -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
|
||||||
|
|||||||
@@ -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}>'
|
|
||||||
156
app/services/image_service.py
Normal file
156
app/services/image_service.py
Normal 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
|
||||||
@@ -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({
|
||||||
|
|||||||
@@ -1,8 +1,44 @@
|
|||||||
|
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):
|
|
||||||
|
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 _create_jinja_environment(self) -> Environment:
|
||||||
|
"""
|
||||||
|
Create and configure the Jinja2 environment with custom functions.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Environment: A configured Jinja2 environment.
|
||||||
|
"""
|
||||||
|
env = Environment(loader=DictLoader({"base_template": "{{ content | safe }}"}))
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def img_left_overlay(self, src: str) -> str:
|
||||||
"""Render an image with overlay."""
|
"""Render an image with overlay."""
|
||||||
return f'''
|
return f'''
|
||||||
<div class="img-left-overlay">
|
<div class="img-left-overlay">
|
||||||
@@ -11,7 +47,7 @@ def img_left_overlay(src):
|
|||||||
</div>
|
</div>
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def box(title, content):
|
def box(self, title: str, content: str) -> str:
|
||||||
"""Render a box component."""
|
"""Render a box component."""
|
||||||
return f'''
|
return f'''
|
||||||
<div class="box">
|
<div class="box">
|
||||||
@@ -20,20 +56,21 @@ def box(title, content):
|
|||||||
</div>
|
</div>
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def note(content):
|
def note(self, content: str) -> str:
|
||||||
"""Render a note component."""
|
"""Render a note component."""
|
||||||
return f'''
|
return f'''
|
||||||
<div class="note">
|
<div class="note">
|
||||||
<p>{content}</p>
|
<p>{content}</p>
|
||||||
</div>
|
</div>
|
||||||
'''
|
'''
|
||||||
def link_to(title, url):
|
|
||||||
"""Render a box component."""
|
def link_to(self, title: str, url: str) -> str:
|
||||||
|
"""Render a link component."""
|
||||||
return f'''
|
return f'''
|
||||||
<a href="{url}" target="_blank" rel="noopener noreferrer">{title}</a>
|
<a href="{url}" target="_blank" rel="noopener noreferrer">{title}</a>
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def warning(content):
|
def warning(self, content: str) -> str:
|
||||||
"""Render a warning component."""
|
"""Render a warning component."""
|
||||||
return f'''
|
return f'''
|
||||||
<div class="warning">
|
<div class="warning">
|
||||||
@@ -41,62 +78,72 @@ def warning(content):
|
|||||||
</div>
|
</div>
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
def slider(self, options: dict, images: list) -> str:
|
||||||
def slider(options, images):
|
"""Render a slider component."""
|
||||||
"""Render a slider using the provided HTML structure."""
|
|
||||||
import uuid
|
import uuid
|
||||||
modal_id = uuid.uuid4().hex.upper()[0:6] # Lets create some uniq modals
|
modal_id = uuid.uuid4().hex.upper()[0:6]
|
||||||
|
|
||||||
width = options.get("width", 500)
|
|
||||||
height = options.get("height", 375)
|
|
||||||
|
|
||||||
html_content = []
|
html_content = []
|
||||||
html_content.append('<div class="button-stack">')
|
html_content.append('<div class="button-stack">')
|
||||||
for i, val in enumerate(images):
|
for i, val in enumerate(images):
|
||||||
modal_id = f"{modal_id}_{i}"
|
self.image_service = ImageService( self.app )
|
||||||
modal_id_next = f"{modal_id}_{i+1}"
|
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"
|
||||||
|
|
||||||
if int(len(images))<=int(i+1):
|
html_content.append(f"""
|
||||||
modal_id_next = f"{modal_id}_0"
|
<button onclick="openModal('modal{modal_id_current}')" class="stacked-button">
|
||||||
if i % 2 == 0:
|
<img src="{val}" alt="Image {i}" class="thumbnail" loading="lazy">
|
||||||
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())
|
</button>
|
||||||
else:
|
<div class="modal" id="modal{modal_id_current}">
|
||||||
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">
|
<div class="modal-content">
|
||||||
<h2>Modal {i}</h2>
|
<h2>Modal {i}</h2>
|
||||||
<img src="{val}" alt="Lets do better" loading="lazy">
|
<img src="{val}" alt="Image {i}" loading="lazy">
|
||||||
|
|
||||||
<div class="modal-buttons">
|
<div class="modal-buttons">
|
||||||
<button onclick="closeModal('modal{modal_id}')">Close</button>
|
<button onclick="closeModal('modal{modal_id_current}')">Close</button>
|
||||||
<button class="next-btn" onclick="nextModal('modal{modal_id}', 'modal{modal_id_next}')">Next</button>
|
<button class="next-btn" onclick="nextModal('modal{modal_id_current}', 'modal{modal_id_next}')">Next</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>""")
|
</div>
|
||||||
|
""")
|
||||||
|
html_content.append('</div>')
|
||||||
|
return '\n'.join(html_content)
|
||||||
|
|
||||||
html_content.append( '</div>' )
|
def _get_category(self):
|
||||||
html = '\n'.join( html_content )
|
if isinstance(self.file_path, str):
|
||||||
|
this_path = Path(self.file_path)
|
||||||
|
return this_path.parent.name
|
||||||
|
|
||||||
return html
|
return True
|
||||||
|
|
||||||
|
|
||||||
def create_jinja_environment():
|
def get_image(self, image_type: str, filename: str, alt: str = "", width: int = None, height: int = None) -> Markup:
|
||||||
"""Create and configure the Jinja2 environment."""
|
"""
|
||||||
env = Environment(loader=DictLoader({"base_template": "{{ content | safe }}"}))
|
Generate a dynamic HTML <img> tag for an image using ImageService's image_tag method.
|
||||||
image_handler = ImageHandler(base_dir="static/images")
|
"""
|
||||||
|
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}" )
|
||||||
|
|
||||||
env.globals.update({
|
tag = self.image_service.image_tag(
|
||||||
"img_left_overlay": img_left_overlay,
|
category=self._get_category(),
|
||||||
"box": box,
|
image_type=image_type,
|
||||||
"note": note,
|
filename=filename,
|
||||||
"warning": warning,
|
alt=alt,
|
||||||
"link_to": link_to,
|
width=width,
|
||||||
"slider": slider,
|
height=height
|
||||||
"image": image_handler.generate_image_tag, # Add image handler function
|
)
|
||||||
|
my_tag = Markup(tag)
|
||||||
|
print(my_tag)
|
||||||
|
return my_tag
|
||||||
|
|
||||||
})
|
|
||||||
return env
|
|
||||||
|
|
||||||
def render_markdown_with_jinja(markdown_content: str):
|
|
||||||
|
|
||||||
|
def render_markdown_with_jinja(self, markdown_content: str):
|
||||||
"""
|
"""
|
||||||
Convert Markdown to HTML and apply Jinja2 rendering for custom tags.
|
Convert Markdown to HTML and apply Jinja2 rendering for custom tags.
|
||||||
|
|
||||||
@@ -106,18 +153,16 @@ def render_markdown_with_jinja(markdown_content: str):
|
|||||||
Returns:
|
Returns:
|
||||||
tuple: Rendered HTML content and metadata as a dictionary.
|
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"])
|
md = markdown.Markdown(extensions=["extra", "nl2br", "meta"])
|
||||||
intermediate_html = md.convert(markdown_content)
|
intermediate_html = md.convert(markdown_content)
|
||||||
metadata = {key: " ".join(value) for key, value in md.Meta.items()} if md.Meta else {}
|
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
|
# Step 3: Pass the resulting HTML with Jinja2 custom tags through Jinja2
|
||||||
env = create_jinja_environment()
|
template = self.jinja_env.get_template("base_template")
|
||||||
|
|
||||||
template = env.get_template("base_template")
|
|
||||||
final_html = template.render(content=intermediate_html)
|
final_html = template.render(content=intermediate_html)
|
||||||
|
|
||||||
# Step 3: Re-render final_html in Jinja2 for embedded tags like {{ image(...) }}
|
# Step 4: Re-render final_html in Jinja2 for embedded tags like {{ image(...) }}
|
||||||
final_output = env.from_string(final_html).render()
|
final_output = self.jinja_env.from_string(final_html).render()
|
||||||
|
|
||||||
return final_output, metadata
|
return final_output, metadata
|
||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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}
|
|
||||||
@@ -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") }}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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.") }}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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") }}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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
|
|
||||||
@@ -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>
|
|
||||||
@@ -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!
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
@@ -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"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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.
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user