Compare commits
20 Commits
80c66c1802
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fda9dde36 | ||
|
|
194b1e04cc | ||
|
|
f3ff920e38 | ||
|
|
a47ad949bd | ||
|
|
2df2f5f3a8 | ||
|
|
62445a1812 | ||
|
|
6f046b3743 | ||
|
|
1c23c05cc7 | ||
|
|
0272ec3d07 | ||
|
|
81c093aa99 | ||
|
|
2118d9fbc0 | ||
|
|
20fce270a5 | ||
|
|
9a83309d36 | ||
|
|
a90f06d913 | ||
|
|
67ec5753bf | ||
|
|
a38aad6576 | ||
|
|
11aac567ec | ||
|
|
6c5649d721 | ||
|
|
86588c0080 | ||
|
|
664900cbd1 |
52
app.py
52
app.py
@@ -1,7 +1,7 @@
|
|||||||
from flask import Flask, render_template, request, redirect, url_for
|
from flask import Flask, render_template, request, redirect, url_for, send_from_directory
|
||||||
from content.posts import BLOG_POSTS
|
from content.posts import BLOG_POSTS
|
||||||
from flask_logic.logic import get_enriched_post, get_comments_for_post, save_comment
|
from flask_logic.logic import get_enriched_post, get_comments_for_post, save_comment
|
||||||
|
from flask_logic.renderer import process_post_content, collect_css, generate_preview
|
||||||
# 🌟 IMPORT THE content from separate files.
|
# 🌟 IMPORT THE content from separate files.
|
||||||
from content.posts import BLOG_POSTS
|
from content.posts import BLOG_POSTS
|
||||||
from content.about_text import ABOUT, TITLE
|
from content.about_text import ABOUT, TITLE
|
||||||
@@ -78,13 +78,26 @@ def calculate_pagination(posts, posts_per_page, page):
|
|||||||
'total_posts': total_posts
|
'total_posts': total_posts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def home():
|
def home():
|
||||||
|
posts_with_preview = []
|
||||||
|
for post in BLOG_POSTS:
|
||||||
|
preview = generate_preview(post.get("content", ""))
|
||||||
|
|
||||||
|
post_copy = dict(post)
|
||||||
|
post_copy["preview"] = preview
|
||||||
|
|
||||||
|
posts_with_preview.append(post_copy)
|
||||||
"""Home page with paginated blog posts."""
|
"""Home page with paginated blog posts."""
|
||||||
page = request.args.get('page', 1, type=int)
|
page = request.args.get('page', 1, type=int)
|
||||||
blog_title = TITLE
|
blog_title = TITLE
|
||||||
|
|
||||||
pagination = calculate_pagination(BLOG_POSTS, POSTS_PER_PAGE, page)
|
pagination = calculate_pagination(posts_with_preview, POSTS_PER_PAGE, page)
|
||||||
|
|
||||||
return render_template('index.html',
|
return render_template('index.html',
|
||||||
posts=pagination['posts_to_show'],
|
posts=pagination['posts_to_show'],
|
||||||
@@ -107,18 +120,43 @@ def about():
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/post/<int:post_id>')
|
@app.route('/post/<int:post_id>')
|
||||||
def post_detail(post_id):
|
def post_detail(post_id):
|
||||||
# One call to get the data + the extra logic (templates/timelines)
|
context = {"used_components": set()}
|
||||||
post = get_enriched_post(post_id, BLOG_POSTS)
|
|
||||||
|
|
||||||
|
post = get_enriched_post(post_id, BLOG_POSTS)
|
||||||
if not post:
|
if not post:
|
||||||
return "Post not found", 404
|
return "Post not found", 404
|
||||||
|
|
||||||
comments = get_comments_for_post(post_id)
|
comments = get_comments_for_post(post_id)
|
||||||
return render_template('post_detail.html', post=post, comments=comments, blog_title = TITLE)
|
|
||||||
|
|
||||||
|
processed_content = process_post_content(
|
||||||
|
post.get('content', ''), context=context
|
||||||
|
)
|
||||||
|
|
||||||
|
css_files = collect_css(context)
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"post_detail.html",
|
||||||
|
post=post,
|
||||||
|
content=processed_content,
|
||||||
|
comments=comments,
|
||||||
|
blog_title=TITLE,
|
||||||
|
component_css=css_files
|
||||||
|
)
|
||||||
|
@app.route('/content/image/<path:filename>')
|
||||||
|
def content_image_files(filename):
|
||||||
|
directory = 'content/image'
|
||||||
|
full_path = os.path.join(os.getcwd(), directory, filename) # Your actual file location
|
||||||
|
print(f"Requested: {filename}")
|
||||||
|
print(f"Directory: {os.getcwd()}/{directory}")
|
||||||
|
print(f"Full path: {full_path}")
|
||||||
|
print(f"Exists: {os.path.exists(full_path)}")
|
||||||
|
|
||||||
|
if not os.path.exists(full_path):
|
||||||
|
return f"File not found: {full_path}", 404
|
||||||
|
|
||||||
|
return send_from_directory(directory, filename)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
2
content
2
content
Submodule content updated: 93b20a38f8...cb53a98cca
0
flask_logic/__init__.py
Normal file
0
flask_logic/__init__.py
Normal file
60
flask_logic/components.py
Normal file
60
flask_logic/components.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
from flask_logic.registry import register_component
|
||||||
|
|
||||||
|
|
||||||
|
@register_component("image", css="css/components/image.css")
|
||||||
|
def render_image_component(src, caption=None, css_class=None, context=None):
|
||||||
|
if context:
|
||||||
|
context["used_components"].add("image")
|
||||||
|
|
||||||
|
base_class = "image-container"
|
||||||
|
full_class = f"{base_class} {css_class}" if css_class else base_class
|
||||||
|
|
||||||
|
caption_html = f'<div class="caption">{caption}</div>' if caption else ""
|
||||||
|
|
||||||
|
return f"""
|
||||||
|
<div class="{full_class}">
|
||||||
|
<img src="/content/image/{src}" alt="{caption or ''}">
|
||||||
|
{caption_html}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
@register_component("code", css="css/components/code.css")
|
||||||
|
def render_code_block(value=None, code=None, context=None):
|
||||||
|
if context:
|
||||||
|
context["used_components"].add("code")
|
||||||
|
|
||||||
|
code = code or value or ""
|
||||||
|
|
||||||
|
return f"""
|
||||||
|
<pre class="code-block"><code>{code}</code></pre>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@register_component("tree", css="css/components/tree.css")
|
||||||
|
def render_tree_component(context=None):
|
||||||
|
if context:
|
||||||
|
context["used_components"].add("tree")
|
||||||
|
return f"""
|
||||||
|
<div class="background-svg">
|
||||||
|
<img src="/content/image/animation.svg" alt="" />
|
||||||
|
<br />
|
||||||
|
<p>tree source: codepen @uchardon</p>
|
||||||
|
</div>"""
|
||||||
|
|
||||||
|
@register_component("timeline", css="css/components/timeline.css")
|
||||||
|
|
||||||
|
def render_timeline_component(timeline=None, value=None, context=None):
|
||||||
|
if context:
|
||||||
|
context["used_components"].add("timeline")
|
||||||
|
timeline = timeline or value or ""
|
||||||
|
return f"""
|
||||||
|
<div class="tw-w-full tw-mx-auto tw-px-0 tw-py-12">
|
||||||
|
<ol
|
||||||
|
class="tw-relative tw-border-l-2 tw-border-gray-300 tw-list-none tw-p-0 tw-m-0 tw-ml-4"
|
||||||
|
>
|
||||||
|
{timeline}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<li class="tw-mb-12 tw-ml-6 tw-list-none tw-relative"></li>
|
||||||
|
"""
|
||||||
12
flask_logic/registry.py
Normal file
12
flask_logic/registry.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# component_registry.py
|
||||||
|
|
||||||
|
COMPONENTS = {}
|
||||||
|
|
||||||
|
def register_component(name, css=None):
|
||||||
|
def decorator(func):
|
||||||
|
COMPONENTS[name] = {
|
||||||
|
"render": func,
|
||||||
|
"css": css
|
||||||
|
}
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
128
flask_logic/renderer.py
Normal file
128
flask_logic/renderer.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import re
|
||||||
|
# FORCE component registration
|
||||||
|
import flask_logic.components
|
||||||
|
from flask_logic.registry import COMPONENTS
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# Content renderer (blocks)
|
||||||
|
# ------------------------
|
||||||
|
def render_blocks(blocks, context=None):
|
||||||
|
html_output = []
|
||||||
|
print(COMPONENTS)
|
||||||
|
|
||||||
|
for block in blocks:
|
||||||
|
block_type = block.get("type")
|
||||||
|
|
||||||
|
if block_type == "text":
|
||||||
|
html_output.append(block.get("value", ""))
|
||||||
|
|
||||||
|
elif block_type in COMPONENTS:
|
||||||
|
render_func = COMPONENTS[block_type]["render"]
|
||||||
|
|
||||||
|
# remove "type" before passing kwargs
|
||||||
|
kwargs = {k: v for k, v in block.items() if k != "type"}
|
||||||
|
|
||||||
|
html_output.append(
|
||||||
|
render_func(**kwargs, context=context)
|
||||||
|
)
|
||||||
|
|
||||||
|
return "\n".join(html_output)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# Old string renderer (tokens)
|
||||||
|
# ------------------------
|
||||||
|
def parse_options(option_string):
|
||||||
|
options = {}
|
||||||
|
if not option_string:
|
||||||
|
return options
|
||||||
|
|
||||||
|
parts = option_string.split("|")
|
||||||
|
for part in parts:
|
||||||
|
if "=" in part:
|
||||||
|
key, value = part.split("=", 1)
|
||||||
|
options[key.strip()] = value.strip()
|
||||||
|
|
||||||
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
def render_content(content, context=None):
|
||||||
|
if not content:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def replace_image(match):
|
||||||
|
src = match.group("src").strip()
|
||||||
|
options = parse_options(match.group("options"))
|
||||||
|
|
||||||
|
render_func = COMPONENTS["image"]["render"]
|
||||||
|
|
||||||
|
return render_func(
|
||||||
|
src=src,
|
||||||
|
caption=options.get("caption"),
|
||||||
|
css_class=options.get("class"),
|
||||||
|
context=context
|
||||||
|
)
|
||||||
|
|
||||||
|
pattern = r"\[image:(?P<src>[^\|\]]+)(?:\|(?P<options>[^\]]+))?\]"
|
||||||
|
return re.sub(pattern, replace_image, content)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# Unified entry
|
||||||
|
# ------------------------
|
||||||
|
def process_post_content(content, context=None):
|
||||||
|
if isinstance(content, str):
|
||||||
|
return render_content(content, context=context)
|
||||||
|
|
||||||
|
elif isinstance(content, list):
|
||||||
|
return render_blocks(content, context=context)
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# CSS collector
|
||||||
|
# ------------------------
|
||||||
|
def collect_css(context):
|
||||||
|
css_files = []
|
||||||
|
|
||||||
|
for comp in context["used_components"]:
|
||||||
|
css = COMPONENTS.get(comp, {}).get("css")
|
||||||
|
if css:
|
||||||
|
css_files.append(css)
|
||||||
|
|
||||||
|
return css_files
|
||||||
|
|
||||||
|
def generate_preview(content, max_length=200):
|
||||||
|
if isinstance(content, str):
|
||||||
|
# old system
|
||||||
|
text = re.sub(r"<[^>]+>", "", content) # strip HTML
|
||||||
|
return text[:max_length]
|
||||||
|
|
||||||
|
elif isinstance(content, list):
|
||||||
|
# new system
|
||||||
|
for block in content:
|
||||||
|
if block.get("type") == "text":
|
||||||
|
text = re.sub(r"<[^>]+>", "", block.get("value", ""))
|
||||||
|
return text[:max_length]
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# optinal, for images
|
||||||
|
def generate_preview_html(content, context=None):
|
||||||
|
if isinstance(content, list):
|
||||||
|
for block in content:
|
||||||
|
if block.get("type") == "text":
|
||||||
|
return f"<div class='blog-preview'>{block.get('value')}</div>"
|
||||||
|
|
||||||
|
elif block.get("type") in COMPONENTS:
|
||||||
|
# optional: allow image preview
|
||||||
|
render_func = COMPONENTS[block["type"]]["render"]
|
||||||
|
kwargs = {k: v for k, v in block.items() if k != "type"}
|
||||||
|
return render_func(**kwargs, context=context)
|
||||||
|
|
||||||
|
elif isinstance(content, str):
|
||||||
|
return content[:200]
|
||||||
|
|
||||||
|
return ""
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 131 140">
|
|
||||||
<style>
|
|
||||||
|
|
||||||
svg {
|
|
||||||
max-height: 60vh;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
path {
|
|
||||||
fill: #379157;
|
|
||||||
stroke: #379157;
|
|
||||||
stroke-width: 0.2;
|
|
||||||
transform: scale(0);
|
|
||||||
transform-origin: 50% 50%;
|
|
||||||
animation: star 8s ease-in-out infinite;
|
|
||||||
animation-delay: calc(var(--no) * 0.025s);
|
|
||||||
transform-box: fill-box;
|
|
||||||
}
|
|
||||||
@keyframes star {
|
|
||||||
0% {
|
|
||||||
transform: scale(0);
|
|
||||||
animation-timing-function: cubic-bezier(0.74, 1.72, 0.57, 1.01);
|
|
||||||
}
|
|
||||||
10% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
65% {
|
|
||||||
transform: translateY(0px) scale(1);
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
transform: translateY(50px) scale(0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(0px) scale(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<path d="m49.35 6.37-1.88-.93-1.85 1.01.32-2.07-1.54-1.43 2.08-.34.9-1.9.96 1.86 2.1.25-1.48 1.5z" style="--no:100;fill:#d8e540;stroke:#d8e540;"/>
|
|
||||||
<path d="m129.54 125.15-.82-.26-.68.53-.01-.86-.71-.48.81-.28.23-.83.52.7.86-.04-.5.7z" style="--no: 1; transform: scale(222);"/>
|
|
||||||
<path d="m126.73 127.78-1.39-.2-.95 1.03-.24-1.38-1.27-.59 1.24-.65.16-1.39 1 .98 1.38-.28-.62 1.26z" style="--no:2"/>
|
|
||||||
<path d="m120.86 129.62-1.36-.62-1.28.78.16-1.49-1.14-.97 1.46-.31.58-1.38.75 1.3 1.49.12-1 1.1z" style="--no:3"/>
|
|
||||||
<path d="m115.25 131.06-2.48-1.02-2.25 1.47.2-2.68-2.1-1.68 2.62-.64.95-2.5 1.41 2.28 2.68.12-1.73 2.05z" style="--no:4"/>
|
|
||||||
<path d="m106.57 132.05-1.32-.75-1.37.67.3-1.49-1.05-1.09 1.5-.17.71-1.34.64 1.37 1.49.27-1.12 1.02z" style="--no:5"/>
|
|
||||||
<path d="m107.36 126.39-1.26-.48-1.11.77.06-1.35-1.07-.82 1.3-.35.44-1.28.74 1.13 1.36.03-.85 1.05z" style="--no:6"/>
|
|
||||||
<path d="m101.6 129.66-3.35-1.58-3.2 1.87.47-3.68-2.77-2.46 3.64-.7 1.5-3.39 1.78 3.25 3.69.37-2.54 2.7z" style="--no:7"/>
|
|
||||||
<path d="m91.68 125.45-2.42-1.71-2.78 1.03.87-2.83-1.83-2.33 2.96-.05 1.64-2.46.96 2.8 2.85.8-2.37 1.78z" style="--no:8"/>
|
|
||||||
<path d="m88.75 115.32-1.92.87-.37 2.08-1.42-1.55-2.1.29 1.05-1.84-.93-1.9 2.07.43 1.52-1.47.24 2.1z" style="--no:9"/>
|
|
||||||
<path d="m80.86 119.34-1.99-.75-1.74 1.22.1-2.12-1.7-1.28 2.05-.57.7-2 1.17 1.77 2.12.04-1.33 1.66z" style="--no:10"/>
|
|
||||||
<path d="m80.76 112.4-1.91-.46-1.46 1.32-.16-1.96-1.7-.98 1.8-.76.4-1.92 1.3 1.49 1.95-.21-1.02 1.68z" style="--no:11"/>
|
|
||||||
<path d="m73.42 115.87-2.31-1.18-2.28 1.23.4-2.55-1.87-1.79 2.56-.4 1.11-2.34 1.18 2.3 2.57.35-1.83 1.83z" style="--no:12"/>
|
|
||||||
<path d="m73.07 106.79-.93.2-.36.87-.48-.82-.95-.07.63-.7-.22-.93.87.38.8-.5-.09.95z" style="--no:13"/>
|
|
||||||
<path d="m66.62 105.3-.92-.58-.99.42.27-1.05-.7-.81 1.07-.07.55-.92.4 1 1.05.24-.83.7z" style="--no:14"/>
|
|
||||||
<path d="m64.58 114.97-3.79-2.46-4.18 1.72 1.17-4.36-2.93-3.45 4.52-.23 2.37-3.85 1.61 4.22 4.4 1.06-3.52 2.85z" style="--no:15"/>
|
|
||||||
<path d="m57.23 104.7-1.17-.97-1.46.43.56-1.42-.86-1.26 1.52.1.94-1.2.38 1.47 1.43.51-1.28.82z" style="--no:16"/>
|
|
||||||
<path d="m51.83 107.18-2.77-.7-2.14 1.9-.2-2.85-2.46-1.45 2.65-1.07.62-2.78 1.84 2.18 2.83-.27-1.5 2.42z" style="--no:17"/>
|
|
||||||
<path d="m54.36 113.53-2.02.23-.96 1.8-.84-1.86-2-.36 1.5-1.37-.28-2.01 1.77 1 1.83-.88-.41 1.99z" style="--no:18"/>
|
|
||||||
<path d="m44.68 113.98-1.72-1.4-2.12.63.8-2.06-1.25-1.83 2.2.12 1.35-1.75.57 2.14 2.09.74-1.86 1.2z" style="--no:19"/>
|
|
||||||
<path d="m44.48 117.46-.94-.5-.94.5.18-1.05-.76-.74 1.05-.15.46-.95.48.95 1.05.15-.76.74z" style="--no:20"/>
|
|
||||||
<path d="m38.72 116.76-2.49-.72-2 1.64-.08-2.59-2.18-1.4 2.43-.87.66-2.51 1.59 2.04 2.59-.15-1.46 2.15z" style="--no:21"/>
|
|
||||||
<path d="m42.9 104.65-1.42-.85-1.5.68.37-1.6-1.1-1.21 1.63-.15.81-1.43.65 1.52 1.61.33-1.24 1.08z" style="--no:22"/>
|
|
||||||
<path d="m31.82 119.39-1.35-.43-1.12.87-.01-1.43-1.17-.8 1.34-.44.4-1.37.85 1.15 1.42-.04-.83 1.15z" style="--no:23"/>
|
|
||||||
<path d="M30.14 114.38h-1l-.54.83-.32-.94-.95-.27.8-.59-.04-.99.8.58.93-.34-.3.94z" style="--no:24"/>
|
|
||||||
<path d="m25.22 120.18-2.06-.89-1.9 1.19.21-2.24-1.72-1.44 2.2-.49.84-2.08 1.14 1.93 2.24.16-1.49 1.69z" style="--no:25"/>
|
|
||||||
<path d="m18.67 122.17-1.53-.5-1.27.99v-1.61l-1.33-.91 1.53-.5.45-1.54.95 1.3 1.6-.05-.94 1.3z" style="--no:26"/>
|
|
||||||
<path d="m12.72 123.8-1.1-.26-.84.74-.07-1.12-.97-.57 1.04-.42.24-1.1.73.86 1.11-.1-.6.95z" style="--no:27"/>
|
|
||||||
<path d="m8.25 126.29-1.18-.52-1.1.67.14-1.28-.98-.84 1.26-.26.49-1.2.65 1.12 1.28.1-.86.96z" style="--no:28"/>
|
|
||||||
<path d="m3.43 128.67-1.11-.26-.85.78-.1-1.14-1-.57 1.06-.45.22-1.12.76.86 1.14-.13-.6.99z" style="--no:29"/>
|
|
||||||
<path d="m36.74 105.6-3.33-1.68-3.27 1.79.58-3.69L28 99.46l3.68-.6 1.6-3.36 1.7 3.32 3.7.48-2.64 2.64z" style="--no:30"/>
|
|
||||||
<path d="m28.14 106.59-1.84-.42-1.39 1.28-.17-1.87-1.65-.93 1.74-.75.37-1.85 1.24 1.42 1.88-.22-.97 1.63z" style="--no:31"/>
|
|
||||||
<path d="m23.8 102.17-.71-.49-.8.31.24-.82-.54-.66.85-.03.47-.71.29.8.82.22-.68.52z" style="--no:32"/>
|
|
||||||
<path d="m20.1 108.3-1.52-1.19-1.85.58.66-1.82-1.12-1.58 1.94.07 1.16-1.55.53 1.86 1.84.62-1.61 1.08z" style="--no:33"/>
|
|
||||||
<path d="m14.73 109.75-1.37-.47-1.16.87.02-1.45-1.18-.84 1.38-.43.43-1.38.84 1.18 1.45-.02-.87 1.17z" style="--no:34"/>
|
|
||||||
<path d="m8.87 112.07-.96-.54-1 .5.22-1.1-.77-.78 1.1-.14.5-.98.47 1 1.1.19-.82.75z" style="--no:35"/>
|
|
||||||
<path d="m37.72 97.29-.82-.4-.8.45.13-.9-.67-.62.9-.16.38-.83.43.8.9.1-.63.67z" style="--no:36"/>
|
|
||||||
<path d="m43.69 97.64-1.87-1.15-2.01.88.52-2.14-1.47-1.64 2.2-.16 1.1-1.9.84 2.04 2.15.46-1.68 1.42z" style="--no:37"/>
|
|
||||||
<path d="m51.23 94.34-1.54.2-.72 1.37-.65-1.4-1.54-.27 1.14-1.05-.23-1.54 1.36.75 1.39-.7-.3 1.53z" style="--no:38"/>
|
|
||||||
<path d="m50.46 90.7-1.14-.82-1.29.58.42-1.35-.94-1.04 1.4-.02.7-1.22.45 1.33 1.38.3-1.13.84z" style="--no:39"/>
|
|
||||||
<path d="m60.25 91.43-3.43-1.99-3.46 1.93.83-3.88-2.9-2.7 3.94-.4 1.67-3.6 1.6 3.63 3.94.47-2.95 2.65z" style="--no:40"/>
|
|
||||||
<path d="m64.71 84.62-.84-.82-1.13.3.52-1.04-.64-.99 1.16.17.74-.91.2 1.15 1.09.42-1.04.55z" style="--no:41"/>
|
|
||||||
<path d="m71.62 83.43-.96-.53-.95.57.2-1.09-.82-.72 1.09-.15.43-1.01.48 1 1.1.1-.8.75z" style="--no:42"/>
|
|
||||||
<path d="m69.06 91.57-2.25-1.58-2.52 1.12.81-2.63-1.85-2.04 2.76-.05L67.38 84l.9 2.6 2.69.57-2.2 1.66z" style="--no:43"/>
|
|
||||||
<path d="m76.64 88.66-1.87-.98-1.8 1.11.35-2.08-1.61-1.36 2.09-.32.8-1.95.94 1.89 2.1.15-1.5 1.49z" style="--no:44"/>
|
|
||||||
<path d="m83.66 86.9-2.15-1.06-2 1.3.34-2.36L78 83.27l2.36-.4.86-2.24 1.11 2.12 2.4.13-1.68 1.71z" style="--no:45"/>
|
|
||||||
<path d="m89.28 83.7-1.25-.96-1.47.57.53-1.48-1-1.23 1.58.06.86-1.34.44 1.52 1.52.4-1.3.89z" style="--no:47"/>
|
|
||||||
<path d="m94.37 80.9-1.12-.38-.9.76.03-1.18-1-.62 1.12-.34.28-1.14.68.96 1.17-.09-.7.94z" style="--no:48"/>
|
|
||||||
<path d="m53.76 83.53-.99-.56-.99.55.24-1.11-.84-.78 1.13-.12.48-1.03.46 1.04 1.13.13-.84.77z" style="--no:49"/>
|
|
||||||
<path d="m49.34 84.58-2.28-1.18-2.18 1.36.41-2.53-1.96-1.66 2.54-.39.97-2.37 1.15 2.3 2.56.18-1.82 1.8z" style="--no:50"/>
|
|
||||||
<path d="m40.4 85.88-1.61-1.35-1.99.68.8-1.94-1.27-1.68 2.1.15 1.2-1.72.5 2.04 2.01.62-1.79 1.1z" style="--no:51"/>
|
|
||||||
<path d="m34.53 85.6-3.5-1.38-2.97 2.3.23-3.76-3.1-2.13 3.64-.93 1.07-3.6 2 3.17 3.77-.1-2.4 2.9z" style="--no:52"/>
|
|
||||||
<path d="m23.9 86.9-1.68-1.5-2.15.66L21 84l-1.3-1.84 2.24.24 1.35-1.8.47 2.2 2.13.72-1.95 1.13z" style="--no:53"/>
|
|
||||||
<path d="m18.21 88.34-1.62-.6-1.34 1.1.06-1.73-1.45-.94 1.66-.47.44-1.67.97 1.43 1.72-.1-1.06 1.37z" style="--no:54"/>
|
|
||||||
<path d="m35.48 77.6-1.07-.58-1.05.62.22-1.2-.92-.8 1.21-.16.48-1.12.53 1.1 1.21.11-.88.84z" style="--no:55"/>
|
|
||||||
<path d="M41.72 78.62 40.12 77l-2.2.54 1.05-2.02-1.2-1.93 2.25.37 1.47-1.73.33 2.25 2.1.85-2.03 1.02z" style="--no:56"/>
|
|
||||||
<path d="m49.62 74.51-1.93-.58-1.5 1.36-.03-2.02-1.76-1 1.9-.66.41-1.97 1.22 1.6 2-.22-1.14 1.66z" style="--no:57"/>
|
|
||||||
<path d="m55.27 71.78-1.65-.53-1.3 1.14v-1.73l-1.49-.88 1.64-.54.38-1.69 1.02 1.4 1.73-.16-1.02 1.4z" style="--no:58"/>
|
|
||||||
<path d="m63 71.81-2.43-1.38-2.42 1.38.56-2.73-2.06-1.88 2.77-.3 1.15-2.54 1.15 2.53 2.77.31-2.06 1.88z" style="--no:59"/>
|
|
||||||
<path d="m69.27 69.46-1.16-1.2-1.62.4.77-1.48-.87-1.41 1.64.27 1.08-1.27.24 1.65 1.54.64-1.49.74z" style="--no:60"/>
|
|
||||||
<path d="m76.01 70.02-1.24-.55-1.11.78.14-1.36-1.09-.82 1.33-.28.45-1.28.68 1.18 1.35.02-.9 1.01z" style="--no:61"/>
|
|
||||||
<path d="m81.2 67.28-1.02-.37-.84.7.03-1.1-.92-.58 1.05-.3.27-1.06.62.9 1.08-.06-.66.86z" style="--no:62"/>
|
|
||||||
<path d="m56.57 65.7-.93-.61-1.01.48.3-1.08-.77-.81 1.11-.05.54-.98.39 1.05 1.1.2-.88.7z" style="--no:63"/>
|
|
||||||
<path d="m50.57 67.67-1.71-1.65-2.3.63 1.04-2.14-1.3-1.98 2.35.32L50.14 61l.41 2.34 2.23.84-2.1 1.12z" style="--no:64"/>
|
|
||||||
<path d="m43.73 67.04-1.33-1.07-1.59.58.61-1.58-1.05-1.33 1.7.09.94-1.42.44 1.65 1.63.46-1.42.92z" style="--no:65"/>
|
|
||||||
<path d="m38.39 65.28-2.84-1.44-2.7 1.7.5-3.15-2.46-2.04 3.15-.5 1.18-2.97 1.45 2.84 3.19.21-2.25 2.26z" style="--no:66"/>
|
|
||||||
<path d="m29.58 65.7-1.85-.51-1.39 1.32-.08-1.91-1.69-.92 1.8-.67.35-1.89 1.2 1.51 1.9-.26-1.07 1.6z" style="--no:67"/>
|
|
||||||
<path d="m22.67 67.35-.95-.82-1.19.4.5-1.16-.75-1.01 1.25.1.73-1 .28 1.21 1.2.39-1.08.64z" style="--no:68"/>
|
|
||||||
<path d="m46.67 60.54-2.1-1.18-2.09 1.2.48-2.36-1.79-1.62 2.4-.28.99-2.2 1 2.2 2.4.26-1.78 1.62z" style="--no:69"/>
|
|
||||||
<path d="m52.22 59.39-1.15-.58-1.08.7.2-1.27-1-.82 1.27-.2.46-1.2.6 1.14 1.27.08-.9.9z" style="--no:70"/>
|
|
||||||
<path d="m57.73 56.34-.74-.95-1.19.16.67-1-.52-1.08 1.15.33.88-.82.04 1.2 1.05.57-1.12.4z" style="--no:71"/>
|
|
||||||
<path d="m66.78 48.61-1.14-.34-.88.81-.03-1.19-1.04-.59 1.12-.4.24-1.17.73.95 1.18-.13-.68.98z" style="--no:72"/>
|
|
||||||
<path d="m60.82 51.53-1.55-1.18-1.8.73.63-1.83-1.25-1.49 1.95.04 1.03-1.65.56 1.86 1.88.47-1.6 1.1z" style="--no:73"/>
|
|
||||||
<path d="m53.55 54.76-2.32-1.74-2.68 1.11.93-2.74-1.88-2.21 2.9.04 1.5-2.48.86 2.77 2.82.68-2.37 1.67z" style="--no:74"/>
|
|
||||||
<path d="M45.34 50.09 44 49.44l-1.23.82.2-1.47-1.16-.93 1.46-.26.52-1.38.7 1.3 1.48.07-1.03 1.07z" style="--no:75"/>
|
|
||||||
<path d="m40.18 48.79-1.77-1.15-1.9.93.55-2.04-1.47-1.51 2.1-.11.99-1.87.76 1.97 2.08.36-1.64 1.33z" style="--no:76"/>
|
|
||||||
<path d="m33.37 48.93-1.14-.4-.92.79.02-1.21-1.03-.64 1.16-.35.28-1.18.7 1 1.2-.1-.73.97z" style="--no:77"/>
|
|
||||||
<path d="m44 43.45-1.1-.09-.66.9-.25-1.08-1.06-.34.95-.58v-1.11l.84.73 1.06-.35-.44 1.03z" style="--no:78"/>
|
|
||||||
<path d="m47.65 45.49-1.2-1.17-1.63.44.74-1.51-.92-1.41 1.67.23 1.06-1.3.28 1.65 1.58.6-1.49.79z" style="--no:79"/>
|
|
||||||
<path d="m55.3 43.35-2.53-.88-2.06 1.72.06-2.68-2.28-1.42 2.57-.78.65-2.6 1.53 2.2 2.68-.19-1.62 2.14z" style="--no:80"/>
|
|
||||||
<path d="m59.03 37.84-1.01-.56-1 .58.22-1.13-.86-.77 1.15-.14.46-1.06.49 1.05 1.14.12-.84.78z" style="--no:81"/>
|
|
||||||
<path d="m53.55 34.33-1.91-1.11-1.93 1.07.46-2.16-1.62-1.51 2.2-.22.94-2.01.9 2.02 2.19.27-1.65 1.48z" style="--no:82"/>
|
|
||||||
<path d="m46.29 36.65-.8-.77-1.05.3.48-1-.6-.91 1.09.15.68-.85.2 1.08 1.02.39-.97.51z" style="--no:83"/>
|
|
||||||
<path d="m39.44 36.68-1.15-1.11-1.56.42.7-1.45-.88-1.34 1.6.22 1-1.25.29 1.58 1.5.57-1.42.75z" style="--no:84"/>
|
|
||||||
<path d="m46.78 32.72-2.41-1.47-2.49 1.33.65-2.75-2.03-1.96 2.8-.23 1.25-2.53 1.09 2.6 2.78.4-2.13 1.83z" style="--no:85"/>
|
|
||||||
<path d="m50.22 23.8-2.4-.6-1.74 1.73-.16-2.46-2.19-1.13 2.3-.9.39-2.44 1.57 1.9 2.43-.38-1.32 2.08z" style="--no:86"/>
|
|
||||||
<path d="m48.64 16.71-1.1-.7-1.17.59.33-1.26-.92-.93 1.3-.08.6-1.16.48 1.21 1.29.22-1.01.82z" style="--no:87"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 11 KiB |
16
static/css/components/code.css
Normal file
16
static/css/components/code.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.code-block {
|
||||||
|
display: block;
|
||||||
|
white-space: pre-wrap; /* Allows the code to wrap within the container */
|
||||||
|
word-wrap: break-word; /* Ensures long words break to fit the container */
|
||||||
|
max-width: 100%; /* Ensures the code block doesn't exceed the container's width */
|
||||||
|
padding: 10px; /* Adds padding for better readability */
|
||||||
|
background-color: #f5f5f5; /* Light gray background for contrast */
|
||||||
|
border: 1px solid #ddd; /* Subtle border to distinguish the code block */
|
||||||
|
border-radius: 5px; /* Rounded corners for aesthetics */
|
||||||
|
overflow-x: auto; /* Adds horizontal scroll if necessary */
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block .comment {
|
||||||
|
text-indent: 8em;
|
||||||
|
color: #408080;
|
||||||
|
}
|
||||||
16
static/css/components/image.css
Normal file
16
static/css/components/image.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.image-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
width: 100%;
|
||||||
|
height: 20vh; /* Show 20% of the viewport height */
|
||||||
|
overflow: hidden; /* Hide parts of the image outside the container */
|
||||||
|
position: relative; /* Position context for the image */
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container img {
|
||||||
|
width: 100%; /* Make the image fit the container width */
|
||||||
|
height: auto; /* Maintain aspect ratio */
|
||||||
|
position: absolute; /* Position image relative to container */
|
||||||
|
top: 50%; /* Move image down by 50% of its height */
|
||||||
|
transform: translateY(-50%); /* Pull it back up by 50% of its own height */
|
||||||
|
}
|
||||||
10
static/css/components/tree.css
Normal file
10
static/css/components/tree.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* --- Decoration --- */
|
||||||
|
.background-svg {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 100px;
|
||||||
|
right: 200px;
|
||||||
|
width: 200px;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
@@ -221,17 +221,6 @@ nav {
|
|||||||
border-color: #ccc;
|
border-color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Decoration --- */
|
|
||||||
.background-svg {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 100px;
|
|
||||||
right: 200px;
|
|
||||||
width: 200px;
|
|
||||||
z-index: -1;
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Login, Sign up, New post */
|
/* Login, Sign up, New post */
|
||||||
.login,
|
.login,
|
||||||
.signup {
|
.signup {
|
||||||
|
|||||||
@@ -31,10 +31,6 @@
|
|||||||
href="{{ url_for('static', filename='css/blog.css') }}"
|
href="{{ url_for('static', filename='css/blog.css') }}"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<link
|
|
||||||
href="{{ url_for('static', filename='css/components/timeline.css') }}"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<script>
|
<script>
|
||||||
tailwind.config = {
|
tailwind.config = {
|
||||||
@@ -45,6 +41,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<script src="{{ url_for('static', filename='js/timeline.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/timeline.js') }}"></script>
|
||||||
|
<style></style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<div class="background-svg">
|
|
||||||
<img src="{{ url_for('static', filename='animation.svg') }}" alt="" />
|
|
||||||
<br />
|
|
||||||
<p>tree source: codepen @uchardon</p>
|
|
||||||
</div>
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
<div class="blog-preview">{{ post.content | striptags | truncate(200) }}</div>
|
<div class="blog-preview">{{ post.preview }}</div>
|
||||||
|
|
||||||
<a class="btn-read-more" href="{{ url_for('post_detail', post_id=post.id) }}">
|
<a class="btn-read-more" href="{{ url_for('post_detail', post_id=post.id) }}">
|
||||||
Read More
|
Read More
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
{% macro render_post(post) %}
|
|
||||||
<article class="blog-content">{{ post.content | safe }}</article>
|
|
||||||
{% if post.template %} {% include post.template %} {% endif %} {% endmacro %}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<div class="tw-w-full tw-mx-auto tw-px-0 tw-py-12">
|
|
||||||
<ol
|
|
||||||
class="tw-relative tw-border-l-2 tw-border-gray-300 tw-list-none tw-p-0 tw-m-0 tw-ml-4"
|
|
||||||
>
|
|
||||||
{{ post.timeline | safe }}
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<li class="tw-mb-12 tw-ml-6 tw-list-none tw-relative"></li>
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{% extends "base.html" %} {% block title %}{{ post.title }} - Simple Blog
|
{% for css in component_css %}
|
||||||
Template{% endblock %} {% block content %} {% from
|
<link rel="stylesheet" href="{{ url_for('static', filename=css) }}" />
|
||||||
"components/post_renderer.html" import render_post %} {% from
|
{% endfor %} {% extends "base.html" %} {% block title %}{{ post.title }} -
|
||||||
|
Simple Blog Template{% endblock %} {% block content %} {% from
|
||||||
"components/comment_renderer.html" import render_comment %}
|
"components/comment_renderer.html" import render_comment %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
@@ -9,7 +10,7 @@ Template{% endblock %} {% block content %} {% from
|
|||||||
<p>
|
<p>
|
||||||
<span class="glyphicon glyphicon-time"></span> Posted on {{ post.date }}
|
<span class="glyphicon glyphicon-time"></span> Posted on {{ post.date }}
|
||||||
</p>
|
</p>
|
||||||
{{ render_post(post) }}
|
{{ content | safe }}
|
||||||
<div class="post-actions">
|
<div class="post-actions">
|
||||||
<a href="{{ url_for('home') }}" class="btn btn-default btn-custom"
|
<a href="{{ url_for('home') }}" class="btn btn-default btn-custom"
|
||||||
>← Back to Posts</a
|
>← Back to Posts</a
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<h1 class="text-3xl font-bold mb-8">Project Timeline</h1>
|
|
||||||
|
|
||||||
{% include "components/timeline.html" %}
|
|
||||||
Reference in New Issue
Block a user