1 Commits

Author SHA1 Message Date
SG
3c6594215e Updates 2025-06-08 10:59:11 +03:00
14 changed files with 174 additions and 574 deletions

View File

@@ -1,18 +0,0 @@
import argparse
from config import Config
argparser = argparse.ArgumentParser(
prog = Config.APP_NAME,
description = Config.APP_DESCRIPTION,
epilog = f"See {Config.APP_SRC_URL} for more info.")
subparsers = argparser.add_subparsers(dest='command', required=True)
#argparser.add_argument('--init', action='store_true', help = 'Initialize new site')
#argparser.add_argument('--content', '--edit', type = str, help = 'Create new content')
subparsers.add_parser('init', help = 'Initialize new site.')
subparsers.add_parser('build', help = "Build the site from existing content.")
new_content = subparsers.add_parser('create', aliases = ['new'], help = "Create a new content file.")
new_content.add_argument('filename', type=str, help = "Name of the content file to create.")
edit_content = subparsers.add_parser('edit', help = "Edit a content file.")
edit_content.add_argument('filename', type=str, help = "Name of the content file to edit.")

View File

@@ -1,167 +0,0 @@
from datetime import datetime
import os
import shutil
from collections import defaultdict
import markdown
import frontmatter
from bs4 import BeautifulSoup
from pathlib import Path
from logger import logger
from config import Config
from jinja_env import env, content_item_template, index_template
class ContentItem:
def render_content(self, categories):
logger.debug(f"Rendering {self.source_filename} to {self.target_filename}")
try:
if self.image_file and self.image_file.exists():
image_targetfile = Path(f"{Config.OUTPUT_DIR}/{Config.STATIC_DIR}/images/{self.image_file.name}")
logger.debug(f"Copying {self.image_file} to {image_targetfile}")
shutil.copyfile(self.image_file, image_targetfile)
self.image_file = f"{self.image_file.stem}.jpg"
if self.css_file and self.css_file.exists():
css_targetfile = Path(f"{Config.OUTPUT_DIR}/{Config.STATIC_DIR}/css/{self.css_file.name}")
logger.debug(f"Copying {self.css_file} to {css_targetfile}")
shutil.copyfile(self.css_file, css_targetfile)
if self.js_file and self.js_file.exists():
js_targetfile = Path(f"{Config.OUTPUT_DIR}/{Config.STATIC_DIR}/js/{self.js_file.name}")
logger.debug(f"Copying {self.js_file} to {js_targetfile}")
shutil.copyfile(self.js_file, js_targetfile)
with self.target_filename.open("w", encoding="utf-8") as f:
f.write(content_item_template.render(content_item = self, page_title = self.title, parent_path = '../', categories = categories))
except Exception as e:
logger.error(e)
def parse_content(self):
logger.debug(f"Parsing file {self.source_filename}")
try:
self.source_filename = Path(self.source_filename)
self.subdir = self.source_filename.parent
self.slug = self.source_filename.stem
self.target_filename = Path(f"{Config.OUTPUT_DIR}/{self.source_filename.parent}/{self.source_filename.stem}.html")
self.data = frontmatter.load(self.source_filename)
self.preview = self.data.content.replace('\n', '<br>')[:300]
self.title = self.data.get("title", self.slug)
self.omit_second_title = self.data.get("omit_second_title", False)
self.date = self.data.get("date", "2000-01-01T00:00:00+03:00")
self.categories = [c for c in self.data.get("categories", []) if c != 'default']
self.hidden = self.data.get("hidden", str(False))
self.data.content = self.data.content.replace('\n', ' \n')
self.html = markdown.markdown(self.data.content)
cover_image_path = Path(f"{self.source_filename.parent}/{self.source_filename.stem}.jpg")
self.image_file = cover_image_path if cover_image_path.exists() else ""
css_filepath = Path(f"{self.source_filename.parent}/{self.source_filename.stem}.css")
self.css_file = css_filepath if css_filepath.exists() else ""
js_filepath = Path(f"{self.source_filename.parent}/{self.source_filename.stem}.js")
self.js_file = js_filepath if js_filepath.exists() else ""
except Exception as e:
logger.error(e)
def create_content(self):
with open(self.source_filename, mode="w", encoding="utf-8") as f:
f.writelines([
"---\n",
f"title: {self.source_filename.stem}\n",
f"date: '{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}'\n",
"description: ''\n",
"author: ''\n",
"categories: ['default']\n",
"hidden: False\n",
"omit_second_title: False\n",
"---\n",
"\n\n\n"
])
def __init__(self, filename):
self.source_filename = filename
class Site:
def __init__(self):
self.output_dir = Path(Config.OUTPUT_DIR)
self.content_dir = Path(Config.CONTENT_DIR)
self.static_dir = Path(Config.STATIC_DIR)
self.templates_dir = Path(Config.TEMPLATES_DIR)
self.images_dir = Path(f"{Config.STATIC_DIR}/images")
self.css_dir = Path(f"{Config.STATIC_DIR}/css")
self.js_dir = Path(f"{Config.STATIC_DIR}/js")
self.output_dir = Path(Config.OUTPUT_DIR)
self.content_items = []
self.categories = defaultdict(list)
def init_site(self):
logger.info("Initializing new site")
# Create directories
for subdir in [self.content_dir, self.static_dir, self.templates_dir,
self.images_dir, self.css_dir, self.js_dir]:
os.makedirs(subdir, exist_ok=True)
# Create templates from literals
import templates
template_names = [t for t in dir(templates) if not t.startswith('_')]
for template_name in template_names:
template_content = getattr(templates, template_name)
with open(self.templates_dir / f"{template_name}.html", "w", encoding="utf8") as f:
f.write(template_content)
# Create static/about.md
def get_content_items(self):
logger.debug("Getting content items")
self.get_content_items = []
logger.debug(f"Scanning {Path(Config.CONTENT_DIR)}")
for md_file in Path(Config.CONTENT_DIR).glob("*.md"):
content_item = ContentItem(md_file)
content_item.parse_content()
self.content_items.append(content_item)
self.content_items.sort(key=lambda i: (-datetime.fromisoformat(i.date).timestamp(), i.title))
def map_categories(self):
for content_item in self.content_items:
for category in content_item.categories:
if not category == "default":
self.categories[category].append(content_item.slug)
def build(self):
# Recreate the output dir if needed
if self.output_dir.exists():
shutil.rmtree(self.output_dir)
self.output_dir.mkdir()
# Create public subdirs
subdirs = [self.images_dir, self.css_dir, self.js_dir, self.content_dir, "categories"]
for subdir in subdirs:
subdir = self.output_dir / subdir
subdir.mkdir(parents=True, exist_ok=True)
# Copy static files if exist
if self.static_dir.exists():
shutil.copytree(self.static_dir, self.output_dir / self.static_dir, dirs_exist_ok=True)
# Get content items
self.get_content_items()
# Build categories map
self.map_categories()
# Render the content files
for content_item in self.content_items:
content_item.render_content(categories = self.categories)
# Render the about file
about_content = ContentItem(Path('static/about.md'))
about_content.parse_content()
about_content.render_content(categories = self.categories)
# Render the index file
visible_content_items = [c for c in self.content_items if c.data.get("hidden") != True]
with (self.output_dir / "index.html").open("w", encoding="utf-8") as f:
f.write(index_template.render(page_title = Config.MAIN_PAGE_TITLE, content_items=visible_content_items, categories = self.categories))
# Render the categories indices
visible_content_items = [c for c in self.content_items if c.data.get("hidden") != True]
for category in self.categories:
category_index = Path(f"{self.output_dir}/categories/{category}.html")
category_items = [i for i in visible_content_items if category in i.data.get("categories")]
with (category_index).open(mode="w", encoding="utf-8") as f:
f.write(index_template.render(page_title=category, content_items=category_items, categories = self.categories, parent_path = '../'))
logger.info(f"Created {len(self.content_items)} content items.")

View File

@@ -1,14 +1,24 @@
import os
import logging, os, sys
# Main config section
class Config:
MAIN_PAGE_TITLE = "microgen library"
APP_NAME = "hydrogen"
APP_DESCRIPTION = "Simplistic static site generator"
APP_SRC_URL = f"https://git.exocortex.ru/Exocortex/{APP_NAME}"
OUTPUT_DIR = 'public'
TEMPLATES_DIR = 'templates'
CONTENT_DIR = 'content'
STATIC_DIR = 'static'
MAIN_PAGE_TITLE = "Selected poems"
OUTPUT_DIR = os.environ.get('OUTPUT_DIR') or 'public'
HEADER_IMAGE = 'static/header.jpg'
THEME = "default"
LOG_TO = sys.stdout
LOG_LEVEL = logging.DEBUG
# Logging config section
logger = logging.getLogger(Config.APP_NAME)
logger.setLevel(Config.LOG_LEVEL)
stdout_handler = logging.StreamHandler(Config.LOG_TO)
stdout_handler.setLevel(Config.LOG_LEVEL)
formatter = logging.Formatter(
'[%(asctime)s] %(name)s: %(levelname)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
stdout_handler.setFormatter(formatter)
if not logger.hasHandlers():
logger.addHandler(stdout_handler)
logger.propagate = False

0
content/.Placeholder Normal file
View File

View File

@@ -1,47 +0,0 @@
import os, subprocess
import shutil
from pathlib import Path
from classes import *
from config import Config
from logger import logger
def create_content(filename):
filename = Path(f"{Config.CONTENT_DIR}/{filename}.md")
if not filename.exists():
logger.debug(f"Creating new content {filename}")
new_content_item = ContentItem(filename)
new_content_item.create_content()
def edit_content(filename):
logger.debug(f"Editing content {filename}")
editor = os.environ.get('EDITOR', 'vim')
# Create the content file if not exists
if not Path(f"{Config.CONTENT_DIR}/{filename}.md").exists():
create_content(filename)
filename = Path(f"{Config.CONTENT_DIR}/{filename}.md")
subprocess.call([editor, filename])
def init_site_old():
logger.debug(f"Initializing new site")
# Recreate output directory if necessary
output_dir = Path(Config.OUTPUT_DIR)
if output_dir.exists():
shutil.rmtree(output_dir)
output_dir.mkdir()
# Create output directory subtree
for dir in [ Path(Config.CONTENT_DIR), Path(Config.STATIC_DIR), Path(Config.TEMPLATE_DIR),
Path(Config.OUTPUT_IMG_DIR), Path(Config.OUTPUT_CSS_DIR), Path(Config.OUTPUT_JS_DIR)]:
dir.mkdir(exist_ok=True)
# Create "About.md" content item
create_content("about")
def init_site():
site = Site()
site.init_site()
def build_site():
site = Site()
site.build()

View File

@@ -1,18 +1,96 @@
#!/usr/bin/env python3
from argparser import argparser
from classes import *
from functions import *
from pathlib import Path
import frontmatter
import markdown
from jinja2 import Environment, FileSystemLoader
import shutil
from config import Config, logger
def main():
args = argparser.parse_args()
match args.command:
case "build":
build_site()
case "init":
init_site()
case "new" | "create" | "edit":
edit_content(args.filename)
content_dir = Path('content')
template_dir = Path('templates')
output_dir = Path(Config.OUTPUT_DIR)
img_dir = Path('public/images')
assets_dir = Path('public/assets')
assets_css_dir = Path('public/assets/css')
assets_js_dir = Path('public/assets/js')
static_dir = Path('static')
header_image = Config.HEADER_IMAGE or '/static/header.jpg'
if __name__ == '__main__':
main()
class ContentItemPrototype:
def render_content(self):
if self.image_src_file.exists():
shutil.copyfile(self.image_src_file, output_dir / self.image)
if self.custom_css_src_file.exists():
shutil.copyfile(self.custom_css_src_file, output_dir / self.custom_css)
if self.custom_js_src_file.exists():
shutil.copyfile(self.custom_js_src_file, output_dir / self.custom_js)
with self.content_output_path.open("w", encoding="utf-8") as f:
f.write(content_item_template.render(content_item = self, page_title = self.title))
def __init__(self, md_file):
self.slug = md_file.stem
self.data = frontmatter.load(md_file)
self.html = markdown.markdown(self.data.content)
self.preview = self.html[:300] + "<a href=" + f"{self.slug}.html" + ">... read more</a>"
self.title = self.data.get("title", self.slug)
self.omit_second_title = self.data.get("omit_second_title", False)
self.date = self.data.get("data", "2000-01-01T00:00:00+03:00")
self.content_output_path = output_dir / f"{self.slug}.html"
self.image_src_file = Path(f"{content_dir}/{md_file.stem}.jpg")
self.image = f"images/{md_file.stem}.jpg" if self.image_src_file.exists() else None
self.custom_css_src_file = Path(f"{content_dir}/{md_file.stem}.css")
self.custom_css = f"assets/css/{md_file.stem}.css" if self.custom_css_src_file.exists() else None
self.custom_js_src_file = Path(f"{content_dir}/{md_file.stem}.js")
self.custom_js = f"assets/js/{md_file.stem}.js" if self.custom_js_src_file.exists() else None
# Special handling for the 'about' item
if self.slug == 'about':
self.image = 'static/about.jpg'
logger.info(f"Building the '{Config.MAIN_PAGE_TITLE}' site.")
# Recreate the output dir if needed
if output_dir.exists():
shutil.rmtree(output_dir)
output_dir.mkdir()
# Create public subdirs
img_dir.mkdir()
assets_dir.mkdir()
assets_css_dir.mkdir()
assets_js_dir.mkdir()
# Copy static files if exist
if static_dir.exists():
shutil.copytree(static_dir, output_dir / "static")
# Prepare template rendering engine
env = Environment(loader=FileSystemLoader(str(template_dir)))
env.globals['header_image'] = header_image
index_template = env.get_template("index.html")
content_item_template = env.get_template("content_item.html")
# Parse the content files
content_items = []
for md_file in content_dir.glob("*.md"):
current_content_item = ContentItemPrototype(md_file)
current_content_item.render_content()
content_items.append({
"slug": current_content_item.slug,
"title": current_content_item.title,
"date": current_content_item.date,
"preview": markdown.markdown(current_content_item.preview),
"image": current_content_item.image,
})
# Render the index file
with (output_dir / "index.html").open("w", encoding="utf-8") as f:
f.write(index_template.render(page_title = Config.MAIN_PAGE_TITLE, content_items=content_items))
# Render the about file
about_content = ContentItemPrototype(Path('static/about.md'))
about_content.render_content()
# Move 'robots.txt' into output_dir
shutil.copyfile(static_dir / 'robots.txt', output_dir / 'robots.txt')
logger.info(f"Created {len(content_items)} content items.")

View File

@@ -1,8 +0,0 @@
from jinja2 import Environment, FileSystemLoader
from config import Config
# Prepare template rendering engine
env = Environment(loader=FileSystemLoader(Config.TEMPLATES_DIR))
env.globals['header_image'] = Config.HEADER_IMAGE
index_template = env.get_template("index.html")
content_item_template = env.get_template("content_item.html")

View File

@@ -1,20 +0,0 @@
import logging, sys
from config import Config
# Logging config section
LOG_TO = sys.stdout
LOG_LEVEL = logging.DEBUG
logger = logging.getLogger(Config.APP_NAME)
logger.setLevel(LOG_LEVEL)
stdout_handler = logging.StreamHandler(LOG_TO)
stdout_handler.setLevel(LOG_LEVEL)
formatter = logging.Formatter(
'[%(asctime)s] %(name)s: %(levelname)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
stdout_handler.setFormatter(formatter)
if not logger.hasHandlers():
logger.addHandler(stdout_handler)
logger.propagate = False

View File

@@ -1,4 +1,3 @@
argparse
python-frontmatter
jinja2
markdown

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 B

View File

@@ -1,182 +0,0 @@
default = """
<!doctype html>
<html data-bs-theme="light">
<head>
{% set base = "" %}
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4Q6Gf2aSP4eDXB8Miphtr37CMZZQ5oXLH2yaXMJ2w8e2ZtHTl7GptT4jmndRuHDT" crossorigin="anonymous">
<title>{{ page_title }}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
.top_header {
position: relative;
height: 250px;
background: rgb(70, 70, 124);
background-image: url("{{ parent_path }}static/header.jpg");
background-size: cover;
background-position: center;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.top_header_text {
position: absolute;
bottom: 10px;
left: 10px;
color: white;
font-weight: bold;
font-size: 2.5rem;
background: rgba(0, 0, 0, 0.05);
border-radius: 5px;
padding: 10px 20px;
border-radius: 10px;
}
/*
@media (min-width: 768px) {
.top_header_text {
font-size: 4rem;
}
}
*/
article {
font-size: 1.1rem;
line-height: 1.6;
}
</style>
{% block head_includes %}
{% endblock %}
</head>
<body>
<div class="top_header">
<div class="col-auto">
<h1 class="top_header_text"><a style="border-radius: 5px; padding: 10px; opacity: 0.7; background: black">{{ page_title }}</a></h1>
</div>
</div>
<div class="row justify-content-end pe-2 py-2" >
<div class="col-auto fw-bold">
<a class="link-body-emphasis mx-2 text-decoration-none" href="{{ parent_path }}index.html">Home</a>
<a class="link-body-emphasis mx-2 text-decoration-none" id="categories_toggle" href="#none">Categories</a>
<a class="link-body-emphasis mx-2 text-decoration-none" href="{{ parent_path }}static/about.html">About</a>
<a class="link-body-emphasis mx-2 text-decoration-none" id="dark_theme_toggle" href="#none">🌓︎</a>
</div>
</div>
<div id="categories_container" class="container">
<div id="categories_menu" class="row justify-content-center mx-auto mb-2" style="display: none">
{% for category in categories %}
<a href="{{ parent_path }}categories/{{ category }}.html" class="mx-1 text-decoration-none">{{ category }}</a>
{% endfor %}
</div>
</div>
<div id="content" class="container-fluid mb-2">
{% block content %}
{% endblock %}
</div>
<div id="footer" class="container-fluid">
<div id="footer-data" class="row my-1">
{% block footer_includes %}
{% endblock %}
</div>
<div class="row small d-flex text-muted justify-content-end">
<div class="col-auto" style="font-size: 0.75rem;">
<p>Page created with <a class="text-decoration-none" href="#microgen">microgen</a></p>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.min.js" integrity="sha384-RuyvpeZCxMJCqVUGFI0Do1mQrods/hhxYlcVfGPOfQtPJh0JCw12tUAZ/Mv10S7D" crossorigin="anonymous"></script>
<script defer>
const saved_color_theme = localStorage.getItem('saved_color_theme');
if (saved_color_theme) {
document.documentElement.setAttribute('data-bs-theme', saved_color_theme);
}
document.getElementById("categories_toggle").onclick = function() {
categories_menu_item = document.getElementById("categories_menu");
categories_menu_item.style.display = categories_menu_item.style.display === "none" ? "block" : "none"
};
document.getElementById("dark_theme_toggle").onclick = function() {
html = document.documentElement;
current_color_theme = html.getAttribute('data-bs-theme');
new_color_theme = current_color_theme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-bs-theme', new_color_theme);
localStorage.setItem('saved_color_theme', new_color_theme);
}
</script>
</body>
"""
index = """
{% extends "default.html" %}
{% block head_includes %}
{% endblock %}
{% block content %}
<div class="row justify-content-between g-4 mb-4 py-4">
{% for content_item in content_items %}
<div class="col-auto align-items-stretch d-flex mx-2" style="width: 100%; width: 24em;">
<div class="card h-100 px-0 rounded mx-1 my-3" style="width: 100%; width: 22em;">
<div class="card-header">
{% if content_item.image_file %}
<img src="{{ parent_path }}static/images/{{ content_item.image_file }}" alt="{{ content_item.title }}" class="card-img-top img-fluid mx-auto d-block object-fit-fill rounded" style="height: 200px; width: auto;">
{% else %}
<img src="{{ parent_path }}static/images/1x1.png" alt="{{ content_item.title }}" class="card-img-top img-fluid mx-auto d-block object-fit-scale rounded" style="height: 200px; width: auto;">
{% endif %}
</div>
<div class="card-body">
<h5 class="fw-bold"><a class="text-decoration-none text-body" href="{{ parent_path }}content/{{ content_item.slug}}.html">{{ content_item.title }}</a></h5>
<p class="card-text mb-2">{{ content_item.preview | safe}}<a class="text-decoration-none" href="{{ parent_path }}content/{{ content_item.slug}}.html">... read more</a> </p>
</div>
<div class="card-footer" style="height: 4em;">
{% for item_category in content_item.categories %}
<small><a href="{{ parent_path }}categories/{{ item_category }}.html" class="text-muted text-decoration-none mx-1">{{ item_category }}</a></small>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
"""
content_item="""
{% extends "default.html" %}
{% block head_includes %}
{% if content_item.css_file %}
<link rel="stylesheet" href="{{ parent_path }}static/css/{{ content_item.slug }}.css">
{% endif %}
{% endblock %}
{% block content %}
<div class="container mt-4 mb-5">
{% if content_item.image_file %}<img src="{{ parent_path }}static/images/{{ content_item.image_file }}" class="img-fluid mb-5 rounded w-100 h-auto">{% endif %}
{% if not content_item.omit_second_title %}
<h1>{{ content_item.title }}</h1>
{% endif %}
<article>{{ content_item.html | safe }}</article>
<div id="categories">
{% for category in content_item.categories %}
<a href="{{ parent_path }}categories/{{ category }}.html" class="mx-1 text-decoration-none small text-muted">{{ category }} </a>
{% endfor %}
</div>
<a href="../index.html" class="btn btn-secondary mt-4">← Back</a>
</div>
{% endblock %}
{% block footer_includes %}
{% if content_item.js_file %}
<script src="{{ parent_path}}static/js/{{ content_item.slug }}.js"></script>
{% endif %}
{% endblock %}
"""

View File

@@ -1,31 +1,24 @@
{% extends "default.html" %}
{% block head_includes %}
{% if content_item.css_file %}
<link rel="stylesheet" href="{{ parent_path }}static/css/{{ content_item.slug }}.css">
{% if content_item.custom_css %}
<link href="{{ content_item.custom_css }}">
{% endif %}
{% endblock %}
{% block content %}
<div class="container mt-4 mb-5">
{% if content_item.image_file %}<img src="{{ parent_path }}static/images/{{ content_item.image_file }}" class="img-fluid mb-5 rounded w-100 h-auto">{% endif %}
{% if content_item.image %}<img src="{{ content_item.image }}" class="img-fluid mb-5" style="border-radius: 5px;">{% endif %}
{% if not content_item.omit_second_title %}
<h1>{{ content_item.title }}</h1>
{% endif %}
<article>{{ content_item.html | safe }}</article>
<div id="categories">
{% for category in content_item.categories %}
<a href="{{ parent_path }}categories/{{ category }}.html" class="mx-1 text-decoration-none small text-muted">{{ category }} </a>
{% endfor %}
</div>
<a href="../index.html" class="btn btn-secondary mt-4">← Back</a>
<a href="index.html" class="btn btn-secondary mt-4">← Back</a>
</div>
{% endblock %}
{% block footer_includes %}
{% if content_item.js_file %}
<script src="{{ parent_path}}static/js/{{ content_item.slug }}.js"></script>
{% if content_item.custom_js %}
<script src="{{ content_item.custom_js }}"></script>
{% endif %}
{% endblock %}

View File

@@ -1,23 +1,18 @@
<!doctype html>
<html data-bs-theme="light">
<html>
<head>
{% set base = "" %}
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4Q6Gf2aSP4eDXB8Miphtr37CMZZQ5oXLH2yaXMJ2w8e2ZtHTl7GptT4jmndRuHDT" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<title>{{ page_title }}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
.top_header {
position: relative;
height: 250px;
background: rgb(70, 70, 124);
background-image: url("{{ parent_path }}static/header.jpg");
background-image: url("{{ header_image }}");
background-size: cover;
background-position: center;
display: flex;
@@ -38,77 +33,58 @@
padding: 10px 20px;
border-radius: 10px;
}
/*
.top_menu a {
font-weight: bold;
color: #333;
padding: 6px 12px;
margin-left: 10px;
text-decoration: none;
transition: background 0.2s ease;
}
.top_menu a:hover {
background: rgba(255, 255, 255, 0.3);
}
@media (min-width: 768px) {
.top_header_text {
font-size: 4rem;
}
}
*/
.card-title {
font-weight: bold;
}
article {
font-size: 1.1rem;
line-height: 1.6;
}
</style>
{% block head_includes %}
{% endblock %}
</style>
</head>
<body>
<div class="top_header">
<div class="col-auto">
<div >
<h1 class="top_header_text"><a style="border-radius: 5px; padding: 10px; opacity: 0.7; background: black">{{ page_title }}</a></h1>
</div>
</div>
<div class="row justify-content-end pe-2 py-2" >
<div class="col-auto fw-bold">
<a class="link-body-emphasis mx-2 text-decoration-none" href="{{ parent_path }}index.html">Home</a>
<a class="link-body-emphasis mx-2 text-decoration-none" id="categories_toggle" href="#none">Categories</a>
<a class="link-body-emphasis mx-2 text-decoration-none" href="{{ parent_path }}static/about.html">About</a>
<a class="link-body-emphasis mx-2 text-decoration-none" id="dark_theme_toggle" href="#none">🌓︎</a>
</div>
</div>
<div id="categories_container" class="container">
<div id="categories_menu" class="row justify-content-center mx-auto mb-2" style="display: none">
{% for category in categories %}
<a href="{{ parent_path }}categories/{{ category }}.html" class="mx-1 text-decoration-none">{{ category }}</a>
{% endfor %}
</div>
<div class="top_menu d-flex justify-content-end" style="margin-bottom: 20px;">
<a href="index.html">Home</a>
<a href="about.html">About</a>
</div>
<div id="content" class="container-fluid mb-2">
{% block content %}
{% endblock %}
</div>
<div id="footer" class="container-fluid">
<div id="footer-data" class="row my-1">
<div id="content">{% block content %}{% endblock %}</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js" integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2" crossorigin="anonymous"></script>
{% block footer_includes %}
{% endblock %}
</div>
<div class="row small d-flex text-muted justify-content-end">
<div class="col-auto" style="font-size: 0.75rem;">
<p>Page created with <a class="text-decoration-none" href="#microgen">microgen</a></p>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.min.js" integrity="sha384-RuyvpeZCxMJCqVUGFI0Do1mQrods/hhxYlcVfGPOfQtPJh0JCw12tUAZ/Mv10S7D" crossorigin="anonymous"></script>
<script defer>
const saved_color_theme = localStorage.getItem('saved_color_theme');
if (saved_color_theme) {
document.documentElement.setAttribute('data-bs-theme', saved_color_theme);
}
document.getElementById("categories_toggle").onclick = function() {
categories_menu_item = document.getElementById("categories_menu");
categories_menu_item.style.display = categories_menu_item.style.display === "none" ? "block" : "none"
};
document.getElementById("dark_theme_toggle").onclick = function() {
html = document.documentElement;
current_color_theme = html.getAttribute('data-bs-theme');
new_color_theme = current_color_theme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-bs-theme', new_color_theme);
localStorage.setItem('saved_color_theme', new_color_theme);
}
</script>
</body>

View File

@@ -1,33 +1,19 @@
{% extends "default.html" %}
{% block head_includes %}
{% endblock %}
{% block content %}
<div class="row justify-content-between g-4 mb-4 py-4">
<div class="row row-cols-1 row-cols-md-3 g-4 px-1 py-2">
{% for content_item in content_items %}
<div class="col-auto align-items-stretch d-flex mx-2" style="width: 100%; width: 24em;">
<div class="card h-100 px-0 rounded mx-1 my-3" style="width: 100%; width: 22em;">
<div class="card-header">
{% if content_item.image_file %}
<img src="{{ parent_path }}static/images/{{ content_item.image_file }}" alt="{{ content_item.title }}" class="card-img-top img-fluid mx-auto d-block object-fit-fill rounded" style="height: 200px; width: auto;">
{% else %}
<img src="{{ parent_path }}static/images/1x1.png" alt="{{ content_item.title }}" class="card-img-top img-fluid mx-auto d-block object-fit-scale rounded" style="height: 200px; width: auto;">
{% endif %}
</div>
<div class="card-body">
<h5 class="fw-bold"><a class="text-decoration-none text-body" href="{{ parent_path }}content/{{ content_item.slug}}.html">{{ content_item.title }}</a></h5>
<p class="card-text mb-2">{{ content_item.preview | safe}}<a class="text-decoration-none" href="{{ parent_path }}content/{{ content_item.slug}}.html">... read more</a> </p>
</div>
<div class="card-footer" style="height: 4em;">
{% for item_category in content_item.categories %}
<small><a href="{{ parent_path }}categories/{{ item_category }}.html" class="text-muted text-decoration-none mx-1">{{ item_category }}</a></small>
{% endfor %}
<div class="col mb-3">
<div class="card h-100" style="border-radius: 5px;">
<img src="{{ content_item.image }}" alt="{{ content_item.title }}" class="content-cover-img card-img-top img-fluid" style="object-fit: cover; height: 200px; border-radius: 5px;">
<div class="card-body d-flex flex-column">
<h5 class="card-title"><a class="text-decoration-none text-body" href="{{ content_item.slug}}.html ">{{ content_item.title }}</a></h5>
<p class="card-text mb-3">{{ content_item.preview | safe}}</p>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}