1 Commits
themes ... main

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

3
.gitignore vendored
View File

@@ -169,5 +169,4 @@ cython_debug/
#.idea/
public
content/*
config.yml
content/*

View File

@@ -1,16 +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)
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,203 +0,0 @@
from datetime import datetime
import os
import sys
import shutil
from collections import defaultdict
import markdown
import frontmatter
from pathlib import Path
from logger import logger
from config import config
class ContentItem:
def render_content(self, categories, target_file = False):
if target_file:
self.target_file = Path(target_file)
logger.debug(f"Rendering {self.source_file} to {self.target_file}")
if hasattr(config, "footer"):
footer_data = config.footer
else:
footer_data = ''
try:
if self.image_file and Path(self.image_file).exists():
self.image_file = Path(self.image_file)
image_targetfile = Path(f"{config.output_dir}/{config.static_dir}/images/{self.source_file.stem}.jpg")
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"
else:
self.image_file = False
except Exception as e:
logger.error(f"Couldn't render image file {self.image_file}: {e}")
try:
if self.audio_file and Path(self.audio_file).exists():
self.audio_file = Path(self.audio_file)
audio_targetfile = Path(f"{config.output_dir}/{config.static_dir}/audio/{self.audio_file.name}")
logger.debug(f"Copying {self.audio_file} to {audio_targetfile}")
shutil.copyfile(self.audio_file, audio_targetfile)
self.audio_file = f"{self.audio_file.stem}.mp3"
except Exception as e:
logger.error(f"Couldn't render audio file {self.audio_file}: {e}")
try:
if self.css_file and Path(self.css_file).exists():
self.css_file = Path(self.css_file)
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)
except Exception as e:
logger.error(f"Couldn't render CSS file {self.css_file}: {e}")
try:
if self.js_file and Path(self.js_file).exists():
self.js_file = Path(self.js_file)
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)
except Exception as e:
logger.error(f"Couldn't render JS file {self.js_file}: {e}")
try:
with self.target_file.open("w", encoding="utf-8") as f:
f.write(config.content_item_template.render(content_item = self, page_title = f"{config.site_name}: {self.title}", parent_path = '../', categories = categories, footer_data = footer_data))
except Exception as e:
logger.error(f"Couldn't render content file: {e}")
def parse_content(self):
logger.debug(f"Parsing file {self.source_file}")
try:
self.source_file = Path(self.source_file)
self.parent_dir = self.source_file.parent # Most likely './content'
self.slug = self.source_file.stem
self.target_file = Path(f"{config.output_dir}/{self.source_file.parent}/{self.source_file.stem}.html")
self.data = frontmatter.load(self.source_file)
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') # For markdown newline rendering
self.html = markdown.markdown(self.data.content)
cover_image_path = f"{self.source_file.parent}/{self.source_file.stem}.jpg"
self.image_file = cover_image_path if Path(cover_image_path).exists() else ""
audio_filepath = f"{self.source_file.parent}/{self.source_file.stem}.mp3"
self.audio_file = audio_filepath if Path(audio_filepath).exists() else ""
css_filepath = f"{self.source_file.parent}/{self.source_file.stem}.css"
self.css_file = css_filepath if Path(css_filepath).exists() else ""
js_filepath = f"{self.source_file.parent}/{self.source_file.stem}.js"
self.js_file = js_filepath if Path(js_filepath).exists() else ""
except Exception as e:
logger.error(f"Parser error, {e}")
def create_content(self):
with open(self.source_file, mode="w", encoding="utf-8") as f:
f.writelines([
"---\n",
f"title: {self.source_file.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, file):
self.source_file = file
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):
# Exit if current directory not empty
if os.path.isdir('.') and os.listdir('.'):
logger.error("Current directory is not empty.")
sys.exit(1)
logger.info("Initializing new site")
config.create_default_config()
# Create directories
for subdir in [self.content_dir]:
os.makedirs(subdir, exist_ok=True)
# Copy default theme
shutil.copytree(f"{config.base_dir}/themes/default", "themes/default", dirs_exist_ok=True)
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 exists
if self.output_dir.exists():
shutil.rmtree(self.output_dir)
self.output_dir.mkdir()
# Create public subdirs
subdirs = ["categories", "content", "static"]
for subdir in subdirs:
subdir = self.output_dir / subdir
subdir.mkdir(parents=True, exist_ok=True)
# Copy theme's static dir to 'public/static
shutil.copytree(f"{config.base_dir}/themes/{config.theme}/static", f"{self.output_dir}/static", 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(f'themes/{config.theme}/content/about.md'))
about_content.parse_content()
about_content.render_content(categories = self.categories, target_file='public/static/about.html')
# Render the index file
if hasattr(config, "footer"):
footer_data = config.footer
else:
footer_data = ''
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(config.index_template.render(page_title = config.site_name, content_items=visible_content_items, categories = self.categories, footer_data = footer_data))
# Render the categories indices
if hasattr(config, "footer"):
footer_data = config.footer
else:
footer_data = ''
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", "default")]
with (category_index).open(mode="w", encoding="utf-8") as f:
f.write(config.index_template.render(page_title=f"{config.site_name}: [ {category} ]", content_items=category_items, categories = self.categories, parent_path = '../', footer_data = footer_data))
logger.info(f"Created {len(self.content_items)} content items.")

View File

@@ -1,69 +1,24 @@
import os, sys, yaml
from jinja2 import Environment, FileSystemLoader
from pathlib import Path
import logging, os, sys
# Main config section
class Config:
def __init__(self):
self.config_path = "config.yml"
self.app_name = "microgen"
self.app_description = "Simplistic static site generator"
self.app_src_url = f"https://git.exocortex.ru/Exocortex/{self.app_name}"
self.content_dir = 'content'
self.templates_dir = 'templates'
self.static_dir = 'static'
self.output_dir = 'public'
self.header_image = 'static/header.jpg'
self.site_name = f"{self.app_name} library"
self.theme = "default"
self.debug = False
if os.path.isfile (self.config_path):
with open(self.config_path, "r") as f:
config = yaml.safe_load(f)
if config:
self.update_from_dict(config)
APP_NAME = "hydrogen"
MAIN_PAGE_TITLE = "Selected poems"
OUTPUT_DIR = os.environ.get('OUTPUT_DIR') or 'public'
HEADER_IMAGE = 'static/header.jpg'
LOG_TO = sys.stdout
LOG_LEVEL = logging.DEBUG
# Get the script base_dir and check for templates dir
if Path(f'themes/{self.theme}/templates').exists():
self.base_dir = "."
elif getattr(sys, 'frozen', False):
# Running fron Pyinstaller-packed binary
self.base_dir = Path(sys._MEIPASS)
else:
# Running as a plain Python app
self.base_dir = Path(__file__).resolve().parent
# Prepare template rendering engine
if Path(f"themes/{self.theme}/templates").exists():
# Use the templates from the initialized site instance and the installed theme
jinja_env_dir = f"themes/{self.theme}/templates"
#logger.debug(f"Using locally initialized templates from {jinja_env_dir}")
else:
# Use default templates from the default theme shipped with the app
# i.e. PyInstaller-packed single executable
#logger.debug("Using shipped default temlpates.")
jinja_env_dir = f"{self.base_dir}/themes/default/templates"
self.env = Environment(loader=FileSystemLoader(f"{jinja_env_dir}"))
self.env.globals['header_image'] = f"{self.base_dir}/themes/{self.theme}/static/images/header.jpg"
self.index_template = self.env.get_template("index.html")
self.content_item_template = self.env.get_template("content_item.html")
def update_from_dict(self, config_dict):
for key, value in config_dict.items():
setattr(self, key, value)
def __repr__(self):
return f"{self.__dict__}"
def create_default_config(self):
defaults = {
"site_name": self.site_name,
"theme": "default",
"debug": False,
"footer": "'' # Author / copyright / date / links, can be plaintext or valid HTML"
}
with open(self.config_path, mode="w", encoding="utf-8") as f:
yaml.safe_dump(defaults, f)
config = Config()
# 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,29 +0,0 @@
import os, subprocess
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():
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 helpers 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()
return
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,19 +0,0 @@
import sys
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
from config import Config
# get the scrip directory
if getattr(sys, 'frozen', False):
# Running fron Pyinstaller-packed binary
base_dir = Path(sys._MEIPASS)
else:
# Running as a plain Python app
base_dir = Path(__file__).resolve().parent
# Prepare template rendering engine
jinja_env_dir = f"{base_dir}/themes/{Config.THEME}/templates"
env = Environment(loader=FileSystemLoader(f"{jinja_env_dir}"))
env.globals['header_image'] = f"{base_dir}/static/header.jpg"
index_template = env.get_template("index.html")
content_item_template = env.get_template("content_item.html")

View File

@@ -1,22 +0,0 @@
import logging, sys
from config import config
# Logging config section
LOG_TO = sys.stdout
LOG_LEVEL = logging.INFO
if config.debug == True:
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,5 +1,3 @@
argparse
bs4
python-frontmatter
jinja2
markdown

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

6
static/about.md Normal file
View File

@@ -0,0 +1,6 @@
---
title: "About"
omit_second_title: True
---

View File

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

2
static/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow: /

View File

@@ -0,0 +1,24 @@
{% extends "default.html" %}
{% block head_includes %}
{% 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 %}<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>
<a href="index.html" class="btn btn-secondary mt-4">← Back</a>
</div>
{% endblock %}
{% block footer_includes %}
{% if content_item.custom_js %}
<script src="{{ content_item.custom_js }}"></script>
{% endif %}
{% endblock %}

90
templates/default.html Normal file
View File

@@ -0,0 +1,90 @@
<!doctype html>
<html>
<head>
{% set base = "" %}
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<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>
.top_header {
position: relative;
height: 250px;
background: rgb(70, 70, 124);
background-image: url("{{ header_image }}");
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;
}
.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;
}
{% block head_includes %}
{% endblock %}
</style>
</head>
<body>
<div class="top_header">
<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="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">{% 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 %}
</body>

19
templates/index.html Normal file
View File

@@ -0,0 +1,19 @@
{% extends "default.html" %}
{% block content %}
<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 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 %}

View File

@@ -1,9 +0,0 @@
---
title: "About"
omit_second_title: True
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

View File

@@ -1,37 +0,0 @@
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: 150px;
/*background: rgb(70, 70, 124);*/
overflow: hidden;
object-fit: cover;
}
.top_header_text {
position: absolute;
bottom: 0;
left: 0;
color: rgba(255,255,255,0.75);
font-weight: bold;
font-size: 1em;
line-height: 2em;
/*background: rgba(0, 0, 0, 0.75);*/
background: #121212;
opacity: 0.95;
width: 100%;
text-decoration: none;
}
article {
font-size: 1.1rem;
line-height: 1.6;
}
#footer-data, #footer-data-secondary {
line-height: 2em;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 B

View File

@@ -1,20 +0,0 @@
console.log('Default theme loaded.')
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);
}

View File

@@ -1,44 +0,0 @@
{% 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 }}" alt="{{ content_item.image_file }}" class="img-fluid mb-5 rounded w-100 h-auto dotr">
{% endif %}
{% if not content_item.omit_second_title %}
<h1>{{ content_item.title }}</h1>
{% endif %}
<article>{{ content_item.html | safe }}</article>
{% if content_item.audio_file %}
<div id="audio" class="mb-4 mt-4">
<audio controls>
<source src="{{ parent_path }}static/audio/{{ content_item.audio_file }}" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</div>
{% endif %}
<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,64 +0,0 @@
<!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">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
<link href="{{ parent_path }}static/css/theme.css" rel="stylesheet">
<title>{{ page_title }}</title>
{% block head_includes %}
{% endblock %}
</head>
<body>
<div id="top_wrapper">
<div id= "top_header" class="row top_header">
<img id="top_header_image" src="{{ parent_path }}static/images/header.jpg" class="img-fluid">
<div id="top_title" class="row">
<a class="top_header_text ms-1 text-decoration-none">{{ page_title }}</a>
</div>
</div>
<div id="menu_container" class="row justify-content-end pe-2 py-2" >
<div id="menu_wrapper" 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>
<div id="content_wrapper" class="container-fluid mb-2">
{% block content %}
{% endblock %}
</div>
<div id="footer_wrapper" class="container-fluid">
<div id="footer-data" class="row d-flex text-muted justify-content-end">
{% block footer_includes %}
{% endblock %}
{% if footer_data %}
<div class="d-flex align-items-center justify-content-center">
<p class="my-0">{{ footer_data }}</p>
</div>
{% endif %}
</div>
<div id="footer-data-secondary" class="row d-flex text-muted justify-content-end">
<div class="d-flex align-items-center justify-content-center">
<p class="small my-0" style="font-size: 0.75rem;">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 src="{{ parent_path }}static/js/theme.js"></script>
</body>

View File

@@ -1,28 +0,0 @@
{% extends "default.html" %}
{% block content %}
<div class="row justify-content-center 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 %}