2024-12-29 04:34:15 +01:00
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
import sys
|
2024-12-11 20:34:20 +01:00
|
|
|
|
import markdown
|
2024-12-29 04:34:15 +01:00
|
|
|
|
from fastapi import FastAPI
|
2024-12-11 23:56:15 +01:00
|
|
|
|
from jinja2 import Environment, DictLoader
|
2024-12-29 04:34:15 +01:00
|
|
|
|
from markupsafe import Markup
|
2024-12-29 18:12:24 +01:00
|
|
|
|
from .image_service import ImageService, FileHandler
|
|
|
|
|
|
|
2024-12-29 04:34:15 +01:00
|
|
|
|
|
|
|
|
|
|
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."""
|
|
|
|
|
|
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]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
html_content = []
|
|
|
|
|
|
html_content.append('<div class="button-stack">')
|
|
|
|
|
|
for i, val in enumerate(images):
|
|
|
|
|
|
self.image_service = ImageService( self.app )
|
|
|
|
|
|
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"
|
|
|
|
|
|
|
2024-12-29 18:12:24 +01:00
|
|
|
|
category = FileHandler().get_category(self.file_path)
|
|
|
|
|
|
|
|
|
|
|
|
thumbnal_img = self.image_service.image_tag(category = category, image_type = "thumbnails",filename = val,alt="A better description later on")
|
|
|
|
|
|
modal_img = self.image_service.image_tag(category = category, image_type = "large",filename = val,alt="A better description later on")
|
2024-12-29 04:34:15 +01:00
|
|
|
|
html_content.append(f"""
|
|
|
|
|
|
<button onclick="openModal('modal{modal_id_current}')" class="stacked-button">
|
2024-12-29 18:12:24 +01:00
|
|
|
|
{thumbnal_img}
|
2024-12-29 04:34:15 +01:00
|
|
|
|
</button>
|
|
|
|
|
|
<div class="modal" id="modal{modal_id_current}">
|
|
|
|
|
|
<div class="modal-content">
|
|
|
|
|
|
<h2>Modal {i}</h2>
|
2024-12-29 18:12:24 +01:00
|
|
|
|
{modal_img}
|
2024-12-29 04:34:15 +01:00
|
|
|
|
|
|
|
|
|
|
<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):
|
|
|
|
|
|
if isinstance(self.file_path, str):
|
|
|
|
|
|
this_path = Path(self.file_path)
|
|
|
|
|
|
return this_path.parent.name
|
|
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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}" )
|
|
|
|
|
|
|
|
|
|
|
|
tag = self.image_service.image_tag(
|
|
|
|
|
|
category=self._get_category(),
|
|
|
|
|
|
image_type=image_type,
|
|
|
|
|
|
filename=filename,
|
|
|
|
|
|
alt=alt,
|
|
|
|
|
|
width=width,
|
|
|
|
|
|
height=height
|
|
|
|
|
|
)
|
2024-12-29 18:12:24 +01:00
|
|
|
|
return Markup(tag)
|
2024-12-29 18:47:43 +01:00
|
|
|
|
|
2024-12-29 04:34:15 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|