Compare commits
37 Commits
c72cd4aa4e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fda9dde36 | ||
|
|
194b1e04cc | ||
|
|
f3ff920e38 | ||
|
|
a47ad949bd | ||
|
|
2df2f5f3a8 | ||
|
|
62445a1812 | ||
|
|
6f046b3743 | ||
|
|
1c23c05cc7 | ||
|
|
0272ec3d07 | ||
|
|
81c093aa99 | ||
|
|
2118d9fbc0 | ||
|
|
20fce270a5 | ||
|
|
9a83309d36 | ||
|
|
a90f06d913 | ||
|
|
67ec5753bf | ||
|
|
a38aad6576 | ||
|
|
11aac567ec | ||
|
|
6c5649d721 | ||
|
|
86588c0080 | ||
|
|
664900cbd1 | ||
|
|
80c66c1802 | ||
|
|
8ad581658b | ||
|
|
8193bdd7f2 | ||
|
|
acc9710991 | ||
|
|
034d9bc884 | ||
|
|
fae653a2b9 | ||
| 396ad2de38 | |||
|
|
5ef0dca53b | ||
|
|
cd0c11dec3 | ||
|
|
f7feb18de3 | ||
|
|
43bec07510 | ||
|
|
4b06f03753 | ||
|
|
247d35f46a | ||
|
|
ef3ecd3f8f | ||
|
|
ad0840d4ed | ||
|
|
3551286aca | ||
|
|
85e387f99a |
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
*.pyc
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
Caddyfile
|
||||
deploy.sh
|
||||
.vscode/
|
||||
.ruff_cache/
|
||||
20
Dockerfile
20
Dockerfile
@@ -1,20 +0,0 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Create a non-root user for security
|
||||
RUN groupadd -g 1000 flaskuser && useradd -u 1000 -g flaskuser flaskuser
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt gunicorn
|
||||
|
||||
# Copy the rest of the code
|
||||
COPY . .
|
||||
|
||||
# Change ownership to our non-root user
|
||||
RUN chown -R flaskuser:flaskuser /app
|
||||
USER flaskuser
|
||||
|
||||
# Run with Gunicorn (replaces uwsgi)
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app", "--workers", "4"]
|
||||
134
app.py
134
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.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.
|
||||
from content.posts import BLOG_POSTS
|
||||
from content.about_text import ABOUT, TITLE
|
||||
@@ -16,15 +16,6 @@ COMMENT_FILE = 'content/comments.csv'
|
||||
MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024 # 5 Megabytes
|
||||
POSTS_PER_PAGE = 5 # posts per page limit here
|
||||
|
||||
"""
|
||||
import os
|
||||
from flask import send_from_directory
|
||||
|
||||
@app.route('/content/images/<path:filename>')
|
||||
def custom_static(filename):
|
||||
# This serves files from your private submodule folder
|
||||
return send_from_directory('content/images', filename)
|
||||
"""
|
||||
|
||||
@app.route('/post/<int:post_id>/comment', methods=['POST'])
|
||||
def post_comment(post_id):
|
||||
@@ -43,35 +34,79 @@ def post_comment(post_id):
|
||||
# --- Routes ---
|
||||
|
||||
|
||||
def calculate_pagination(posts, posts_per_page, page):
|
||||
"""
|
||||
Calculate pagination parameters for blog posts.
|
||||
|
||||
Args:
|
||||
posts (list): Full list of blog posts
|
||||
posts_per_page (int): Number of posts per page
|
||||
page (int): Current page number (1-indexed)
|
||||
|
||||
Returns:
|
||||
dict: Pagination data including:
|
||||
- posts_to_show: Posts for current page
|
||||
- prev_url: Previous page URL or None
|
||||
- next_url: Next page URL or None
|
||||
- current_page: Current page number
|
||||
- total_pages: Total number of pages
|
||||
- total_posts: Total number of posts
|
||||
"""
|
||||
# Sort posts by ID newest first
|
||||
sorted_posts = sorted(posts, key=lambda x: x['id'], reverse=True)
|
||||
|
||||
total_posts = len(sorted_posts)
|
||||
total_pages = math.ceil(total_posts / posts_per_page)
|
||||
|
||||
# Clamp page to valid range
|
||||
page = max(1, min(page, total_pages))
|
||||
|
||||
start = (page - 1) * posts_per_page
|
||||
end = start + posts_per_page
|
||||
|
||||
posts_to_show = sorted_posts[start:end]
|
||||
|
||||
prev_url = url_for('home', page=page - 1) if page > 1 else None
|
||||
next_url = url_for('home', page=page + 1) if end < total_posts else None
|
||||
|
||||
return {
|
||||
'posts_to_show': posts_to_show,
|
||||
'prev_url': prev_url,
|
||||
'next_url': next_url,
|
||||
'current_page': page,
|
||||
'total_pages': total_pages,
|
||||
'total_posts': total_posts
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@app.route('/')
|
||||
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."""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
blog_title = TITLE
|
||||
|
||||
# 2. Sort posts by ID newest first
|
||||
all_posts = sorted(BLOG_POSTS, key=lambda x: x['id'], reverse=True)
|
||||
# Calculate totals
|
||||
total_posts = len(all_posts)
|
||||
total_pages = math.ceil(total_posts / POSTS_PER_PAGE)
|
||||
# 3. Calculate start and end indices
|
||||
start = (page - 1) * POSTS_PER_PAGE
|
||||
end = start + POSTS_PER_PAGE
|
||||
|
||||
# 4. Slice the list for the current page
|
||||
posts_to_show = all_posts[start:end]
|
||||
|
||||
# 5. Determine if there is a next or previous page
|
||||
prev_url = url_for('home', page=page - 1) if page > 1 else None
|
||||
next_url = url_for('home', page=page + 1) if end < len(all_posts) else None
|
||||
pagination = calculate_pagination(posts_with_preview, POSTS_PER_PAGE, page)
|
||||
|
||||
return render_template('index.html',
|
||||
posts=posts_to_show,
|
||||
prev_url=prev_url,
|
||||
next_url=next_url,
|
||||
current_page=page,
|
||||
posts=pagination['posts_to_show'],
|
||||
prev_url=pagination['prev_url'],
|
||||
next_url=pagination['next_url'],
|
||||
current_page=pagination['current_page'],
|
||||
total_pages=pagination['total_pages'],
|
||||
blog_title=blog_title,
|
||||
total_pages=total_pages)
|
||||
|
||||
total_posts=pagination['total_posts'])
|
||||
|
||||
|
||||
|
||||
@@ -85,18 +120,43 @@ def about():
|
||||
|
||||
|
||||
|
||||
|
||||
@app.route('/post/<int:post_id>')
|
||||
def post_detail(post_id):
|
||||
# One call to get the data + the extra logic (templates/timelines)
|
||||
post = get_enriched_post(post_id, BLOG_POSTS)
|
||||
context = {"used_components": set()}
|
||||
|
||||
post = get_enriched_post(post_id, BLOG_POSTS)
|
||||
if not post:
|
||||
return "Post not found", 404
|
||||
|
||||
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__':
|
||||
app.run(debug=True)
|
||||
2
content
2
content
Submodule content updated: 182725bce2...cb53a98cca
@@ -1,35 +0,0 @@
|
||||
services:
|
||||
flask_app:
|
||||
build: .
|
||||
container_name: flask_blog
|
||||
restart: always
|
||||
volumes:
|
||||
- .:/app
|
||||
- ./data:/app/data # Persist your CSV files
|
||||
networks:
|
||||
- web_net
|
||||
|
||||
caddy:
|
||||
image: caddy:latest
|
||||
container_name: caddy
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp"
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
networks:
|
||||
- web_net
|
||||
depends_on:
|
||||
- flask_app
|
||||
|
||||
networks:
|
||||
web_net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
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>
|
||||
"""
|
||||
99
flask_logic/logic.py
Normal file
99
flask_logic/logic.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import os
|
||||
import csv
|
||||
|
||||
COMMENT_FILE = os.path.join(os.path.dirname(__file__), '../content/comments.csv')
|
||||
MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024 # 5MB example
|
||||
|
||||
import datetime
|
||||
|
||||
def save_comment(post_id, author, content):
|
||||
"""Handles the validation and saving of comments to the private CSV."""
|
||||
# 1. Validation logic moved here
|
||||
if not content or len(content) > 1000:
|
||||
return False
|
||||
|
||||
author = (author or "Anonymous").strip()[:50]
|
||||
content = content.strip()
|
||||
|
||||
# 2. Path & Size Checks
|
||||
file_exists = os.path.isfile(COMMENT_FILE)
|
||||
if file_exists and os.path.getsize(COMMENT_FILE) > MAX_FILE_SIZE_BYTES:
|
||||
return False # Or raise a specific error
|
||||
|
||||
# 3. Write to CSV
|
||||
with open(COMMENT_FILE, 'a', newline='', encoding='utf-8') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=['post_id', 'author', 'content', 'date'])
|
||||
if not file_exists:
|
||||
writer.writeheader()
|
||||
|
||||
writer.writerow({
|
||||
'post_id': post_id,
|
||||
'author': author,
|
||||
'content': content,
|
||||
'date': datetime.datetime.now().strftime("%B %d, %Y at %I:%M %p")
|
||||
})
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def load_snippet(filename):
|
||||
"""Helper to read HTML snippets from the data folder."""
|
||||
base_path = os.path.join(os.path.dirname(__file__), 'data')
|
||||
file_path = os.path.join(base_path, filename)
|
||||
|
||||
if os.path.exists(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
return ""
|
||||
|
||||
def get_enriched_post(post_id, BLOG_POSTS):
|
||||
post = next((p for p in BLOG_POSTS if p.get('id') == post_id), None)
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
DATA_DIR = BASE_DIR / "content" / "data"
|
||||
|
||||
file_path_10 = DATA_DIR / "post_10_timeline.html"
|
||||
|
||||
|
||||
|
||||
# Mapping config: keeps logic.py tiny
|
||||
configs = {
|
||||
10: {
|
||||
"template": "components/timeline.html",
|
||||
"timeline_file": file_path_10
|
||||
},
|
||||
8: {
|
||||
"template": "components/christmas_post.html",
|
||||
"timeline": None
|
||||
},
|
||||
}
|
||||
|
||||
if post_id in configs:
|
||||
conf = configs[post_id]
|
||||
post_template = conf.get("template")
|
||||
if post_template:
|
||||
post["template"] = post_template
|
||||
|
||||
# Only load the file if a filename is provided
|
||||
t_file = conf.get("timeline_file")
|
||||
if t_file:
|
||||
post["timeline"] = load_snippet(t_file)
|
||||
|
||||
return post
|
||||
|
||||
|
||||
def get_comments_for_post(post_id):
|
||||
comments = []
|
||||
if not os.path.exists(COMMENT_FILE):
|
||||
return comments
|
||||
|
||||
if os.path.getsize(COMMENT_FILE) > MAX_FILE_SIZE_BYTES:
|
||||
return ["Comment section full."] # Handle error in app.py
|
||||
|
||||
with open(COMMENT_FILE, 'r', encoding='utf-8') as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
if row['post_id'] == str(post_id):
|
||||
comments.append(row)
|
||||
return comments
|
||||
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 ""
|
||||
89
readme.md
Normal file
89
readme.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Flask Blog Templates
|
||||
|
||||
A minimal **Flask template set** for my blog.
|
||||
This project adapts and the original [simple-blog-template](https://github.com/earlbread/simple-blog-template) by [Seunghun Lee](https://github.com/earlbread).
|
||||
Content is **static**; selected templates (with **fixed** names/paths) are loaded dynamically.
|
||||
Initially deployed using uWSGI and Nginx; later migrated to Gunicorn, Caddy, and Docker. Most versions should work.
|
||||
|
||||
## Git Tag
|
||||
|
||||
Current version:
|
||||
|
||||
```bash
|
||||
git tag -a v1.0 -m "Initial release of Flask Blog Templates"
|
||||
(it worked on my machine)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
It provides a growing collection of styling templates and is designed to run with two additional files (see below).
|
||||
|
||||
In the current setup, blog content is defined using Python dictionaries (example below), while comments are stored in a CSV file at `content/comments.csv`.
|
||||
|
||||
A basic blog structure is provided by the existing templates. Additional per-article styling can be applied either by embedding raw HTML directly in the post content or by using template expansion via Jinja:
|
||||
|
||||
```jinja2
|
||||
{% if post.template %}
|
||||
{% include post.template %}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
For example, `templates/components/timeline.html` demonstrates a timeline component.
|
||||
|
||||
An example data structure is provided below.
|
||||
|
||||
---
|
||||
|
||||
## Required Files
|
||||
|
||||
This two files should be included in program folder to run the app.
|
||||
|
||||
### `content/about.py`
|
||||
|
||||
provides text for about page and blog title
|
||||
|
||||
```python
|
||||
ABOUT = {
|
||||
"content": """
|
||||
<p>Hello here is the about page</p>
|
||||
"""
|
||||
}
|
||||
|
||||
TITLE = {
|
||||
"title": "Hello World"
|
||||
}
|
||||
```
|
||||
|
||||
### `content/posts.py`
|
||||
|
||||
provides articles in blog:
|
||||
|
||||
```python
|
||||
BLOG_POSTS = [
|
||||
{
|
||||
'id': 7,
|
||||
'title': "title",
|
||||
'subtitle': "subtitle",
|
||||
'date': "December 10, 2024",
|
||||
'content': """can use raw html string for style, will be rendered as html
|
||||
""",
|
||||
'displayall': False
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
## Template Evolution
|
||||
|
||||
- Originally based on the Simple Blog Template project.
|
||||
|
||||
- Adapted for Jinja2 rendering (Flask compatibility).
|
||||
|
||||
- CSS rewritten for flexibility and modular styling.
|
||||
|
||||
- Template design will continue accumulating for themes and post customization.
|
||||
|
||||
## License
|
||||
|
||||
- This project is distributed under the [MIT License](https://github.com/earlbread/simple-blog-template/blob/master/LICENSE).
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/* --- 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,
|
||||
.signup {
|
||||
|
||||
39
static/favicon.svg
Normal file
39
static/favicon.svg
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M2415 4020 c-645 -57 -1178 -445 -1378 -1004 l-32 -90 -105 -31
|
||||
c-370 -111 -623 -269 -706 -441 -23 -49 -28 -72 -28 -139 -1 -71 3 -88 31
|
||||
-145 92 -187 365 -349 785 -466 70 -19 174 -45 232 -57 l105 -22 86 -80 c228
|
||||
-211 505 -354 806 -415 288 -59 609 -41 891 50 126 41 334 146 441 223 48 35
|
||||
126 99 173 143 67 62 94 81 127 87 246 47 519 132 696 218 202 97 325 199 384
|
||||
319 28 57 32 74 31 145 0 67 -5 90 -28 139 -82 170 -337 330 -700 440 l-109
|
||||
34 -29 78 c-190 524 -651 895 -1233 994 -134 22 -319 31 -440 20z m274 -160
|
||||
c366 -33 677 -170 922 -407 155 -150 266 -323 328 -508 43 -130 47 -161 24
|
||||
-173 -74 -39 -304 -100 -508 -136 -295 -51 -436 -61 -900 -61 -461 0 -575 8
|
||||
-881 60 -271 46 -534 124 -534 159 0 34 55 199 94 282 65 137 137 237 260 360
|
||||
271 272 621 417 1046 433 19 0 86 -4 149 -9z m-1719 -1128 c0 -34 33 -65 106
|
||||
-98 226 -103 605 -180 1047 -214 178 -14 697 -14 874 0 455 35 817 109 1048
|
||||
215 72 32 105 63 105 97 0 10 2 18 4 18 19 0 232 -81 312 -120 232 -110 351
|
||||
-233 330 -343 -18 -95 -118 -187 -311 -282 -156 -77 -283 -122 -499 -175
|
||||
-1067 -264 -2632 -182 -3351 174 -159 79 -251 152 -289 227 -14 29 -26 66 -26
|
||||
81 0 131 198 281 525 398 129 46 125 45 125 22z m923 -1187 c448 -44 988 -40
|
||||
1445 11 79 9 146 14 148 11 3 -3 -32 -30 -78 -60 -506 -335 -1182 -336 -1693
|
||||
-3 -47 31 -85 59 -85 62 0 4 26 4 58 0 31 -4 124 -14 205 -21z"/>
|
||||
<path d="M735 2335 c-29 -28 -32 -70 -10 -103 23 -32 129 -104 220 -150 182
|
||||
-91 426 -157 715 -192 280 -33 851 -32 904 3 33 22 44 63 25 96 -25 45 -48 49
|
||||
-249 40 -208 -10 -521 2 -705 26 -330 43 -609 137 -756 255 -72 58 -106 64
|
||||
-144 25z"/>
|
||||
<path d="M3505 2110 c-116 -17 -132 -21 -152 -45 -29 -33 -29 -64 -2 -99 25
|
||||
-32 58 -33 241 -8 134 19 161 31 172 78 7 28 -16 79 -39 88 -25 9 -76 6 -220
|
||||
-14z"/>
|
||||
<path d="M2735 2028 c-35 -30 -37 -79 -5 -113 23 -25 26 -25 190 -25 154 0
|
||||
169 2 194 21 35 28 36 80 1 114 -24 25 -27 25 -189 25 -157 0 -166 -1 -191
|
||||
-22z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -1,13 +1,9 @@
|
||||
{% extends "base.html" %} {% block title %}About - Simple Blog Template{%
|
||||
endblock %} {% block content %}
|
||||
{% extends "base.html" %} {% block title %}{{blog_title.title}}{% endblock %}{%
|
||||
block content %}
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h1>About</h1>
|
||||
<hr />
|
||||
<!-- Post Content -->
|
||||
<div>{{ about_txt.content | safe }}</div>
|
||||
<p>Last Update : October 2024</p>
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
<meta name="author" content="" />
|
||||
|
||||
<title>{% block title %}Default Title{% endblock %}</title>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
href="{{ url_for('static', filename='favicon.svg') }}"
|
||||
/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
@@ -27,10 +31,6 @@
|
||||
href="{{ url_for('static', filename='css/blog.css') }}"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="{{ url_for('static', filename='css/components/timeline.css') }}"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
@@ -41,6 +41,7 @@
|
||||
};
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='js/timeline.js') }}"></script>
|
||||
<style></style>
|
||||
</head>
|
||||
|
||||
<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 %}
|
||||
|
||||
<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) }}">
|
||||
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
|
||||
Template{% endblock %} {% block content %} {% from
|
||||
"components/post_renderer.html" import render_post %} {% from
|
||||
{% for css in component_css %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename=css) }}" />
|
||||
{% endfor %} {% extends "base.html" %} {% block title %}{{ post.title }} -
|
||||
Simple Blog Template{% endblock %} {% block content %} {% from
|
||||
"components/comment_renderer.html" import render_comment %}
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
@@ -9,7 +10,7 @@ Template{% endblock %} {% block content %} {% from
|
||||
<p>
|
||||
<span class="glyphicon glyphicon-time"></span> Posted on {{ post.date }}
|
||||
</p>
|
||||
{{ render_post(post) }}
|
||||
{{ content | safe }}
|
||||
<div class="post-actions">
|
||||
<a href="{{ url_for('home') }}" class="btn btn-default btn-custom"
|
||||
>← 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