diff --git a/markata.toml b/markata.toml
index 22d581e1..40badb72 100644
--- a/markata.toml
+++ b/markata.toml
@@ -33,7 +33,7 @@ hooks = [
# "markata.plugins.subroute",
"markata.plugins.docs",
# "markata.plugins.prevnext",
- "markata.plugins.service_worker",
+ # "markata.plugins.service_worker",
"default",
]
disabled_hooks = [
@@ -282,9 +282,9 @@ plugin = "markata.plugins.mdit_details:details_plugin"
[[markata.render_markdown.extensions]]
plugin = "mdit_py_plugins.anchors:anchors_plugin"
-[markata.render_markdown.extensions.config]
-permalink = true
-permalinkSymbol = ''
+# [markata.render_markdown.extensions.config]
+# permalink = true
+# permalinkSymbol = ''
[[markata.render_markdown.extensions]]
plugin = "markata.plugins.md_it_wikilinks:wikilinks_plugin"
diff --git a/markata/__init__.py b/markata/__init__.py
index cd01c8f3..ef2055b3 100644
--- a/markata/__init__.py
+++ b/markata/__init__.py
@@ -3,6 +3,7 @@
# annotations needed to return self
from __future__ import annotations
+import copy
import atexit
import datetime
import hashlib
@@ -30,6 +31,7 @@
logger = logging.getLogger("markata")
+
DEFAULT_MD_EXTENSIONS = [
"codehilite",
"markdown.extensions.admonition",
@@ -55,7 +57,7 @@
DEFAULT_HOOKS = [
"markata.plugins.copy_assets",
- "markata.plugins.heading_link",
+ # "markata.plugins.heading_link",
"markata.plugins.pyinstrument",
"markata.plugins.glob",
"markata.plugins.load",
@@ -65,7 +67,7 @@
# "markata.plugins.generator",
"markata.plugins.feeds",
"markata.plugins.auto_description",
- "markata.plugins.seo",
+ # "markata.plugins.seo",
"markata.plugins.post_template",
"markata.plugins.covers",
"markata.plugins.publish_html",
@@ -85,7 +87,7 @@
"markata.plugins.post_model",
"markata.plugins.config_model",
"markata.plugins.create_models",
- "markata.plugins.jinja_md",
+ # "markata.plugins.jinja_md",
]
DEFUALT_CONFIG = {
@@ -106,6 +108,7 @@ class HooksConfig(pydantic.BaseModel):
class Markata:
def __init__(self: "Markata", console: Console = None, config=None) -> None:
+ self.__version__ = __version__
self.stages_ran = set()
self.threded = False
self._cache = None
@@ -306,7 +309,7 @@ def get_config(
def make_hash(self, *keys: str) -> str:
str_keys = [str(key) for key in keys]
- return hashlib.md5("".join(str_keys).encode("utf-8")).hexdigest()
+ return hashlib.sha256("".join(str_keys).encode("utf-8")).hexdigest()
@property
def content_dir_hash(self: "Markata") -> str:
diff --git a/markata/plugins/feeds.py b/markata/plugins/feeds.py
index 67f7cd57..886ecadd 100644
--- a/markata/plugins/feeds.py
+++ b/markata/plugins/feeds.py
@@ -193,6 +193,7 @@
from pathlib import Path
from typing import TYPE_CHECKING, Any, List, Optional
+from markata import background
import pydantic
import typer
from jinja2 import Template, Undefined
@@ -233,7 +234,7 @@ class FeedConfig(pydantic.BaseModel):
"""
- template: str = Path(__file__).parent / "default_post_template.html.jinja"
+ template: str = "feed.html"
rss_template: str = Path(__file__).parent / "default_rss_template.xml"
sitemap_template: str = Path(__file__).parent / "default_sitemap_template.xml"
xsl_template: str = Path(__file__).parent / "default_xsl_template.xsl"
@@ -486,14 +487,17 @@ def create_page(
posts = feed.posts
- cards = [
- create_card(markata, post, feed.config.card_template, cache) for post in posts
- ]
- cards.insert(0, "
")
- cards = "".join(cards)
+ # card_futures = [
+ # create_card(markata, post, feed.config.card_template, cache) for post in posts
+ # ]
+ # cards = [card.result() for card in card_futures]
- template = get_template(feed.config.template)
+ # cards.insert(0, "")
+ # cards = "".join(cards)
+
+ # template = get_template(feed.config.template)
+ template = markata.config.jinja_env.get_template(feed.config.template)
rss_template = get_template(feed.config.rss_template)
sitemap_template = get_template(feed.config.sitemap_template)
output_file = Path(markata.config.output_dir) / feed.config.slug / "index.html"
@@ -512,7 +516,7 @@ def create_page(
"feeds",
template,
__version__,
- cards,
+ # cards,
markata.config.url,
markata.config.description,
feed.config.title,
@@ -526,7 +530,8 @@ def create_page(
feed_html = template.render(
markata=markata,
__version__=__version__,
- body=cards,
+ # body=cards,
+ posts=posts,
url=markata.config.url,
description=markata.config.description,
title=feed.config.title,
@@ -534,8 +539,7 @@ def create_page(
today=datetime.datetime.today(),
config=markata.config,
)
- with markata.cache as cache:
- markata.cache.set(key, feed_html)
+ cache.set(key, feed_html)
feed_rss = rss_template.render(markata=markata, feed=feed)
feed_sitemap = sitemap_template.render(markata=markata, feed=feed)
@@ -545,6 +549,7 @@ def create_page(
sitemap_output_file.write_text(feed_sitemap)
+@background.task
def create_card(
markata: "Markata",
post: "Post",
diff --git a/markata/plugins/glob.py b/markata/plugins/glob.py
index c98196ab..7ad7ae9d 100644
--- a/markata/plugins/glob.py
+++ b/markata/plugins/glob.py
@@ -1,6 +1,7 @@
"""Default glob plugin"""
from pathlib import Path
from typing import List, TYPE_CHECKING, Union
+from markata import background
from more_itertools import flatten
import pydantic
@@ -68,6 +69,7 @@ def glob(markata: "Markata") -> None:
with markata.cache as cache:
cache.set(key, spec)
+ @background.task
def check_spec(file: str) -> bool:
key = markata.make_hash("glob", "check_spec", file)
check = markata.precache.get(key)
@@ -79,4 +81,6 @@ def check_spec(file: str) -> bool:
cache.set(key, check)
return check
- markata.files = [file for file in markata.files if not check_spec(str(file))]
+ file_checks = [(file, check_spec(str(file))) for file in markata.files]
+ [check.result() for _, check in file_checks]
+ markata.files = [file for file, check in file_checks if check]
diff --git a/markata/plugins/jinja_md.py b/markata/plugins/jinja_md.py
index 4fe66b02..77b0a40a 100644
--- a/markata/plugins/jinja_md.py
+++ b/markata/plugins/jinja_md.py
@@ -133,14 +133,14 @@ def run(self, arg, caller):
"""
from pathlib import Path
-from typing import List, TYPE_CHECKING
+from typing import TYPE_CHECKING, List
import jinja2
-from jinja2 import TemplateSyntaxError, Undefined, UndefinedError, nodes
-from jinja2.ext import Extension
import pathspec
import pkg_resources
import pydantic
+from jinja2 import TemplateSyntaxError, Undefined, UndefinedError, nodes
+from jinja2.ext import Extension
from markata import __version__
from markata.hookspec import hook_impl, register_attr
@@ -186,7 +186,7 @@ class _SilentUndefined(Undefined):
# Example
```python
template = '{{ variable }}'
- article.content = Template( template, undefined=_SilentUndefined).render()
+ post.content = Template( template, undefined=_SilentUndefined).render()
```
"""
@@ -222,39 +222,39 @@ def pre_render(markata: "Markata") -> None:
config = markata.config.jinja_md
ignore_spec = pathspec.PathSpec.from_lines("gitwildmatch", config.ignore)
- # for article in markata.iter_articles(description="jinja_md"):
+ # for post in markata.iter_articles(description="jinja_md"):
jinja_env = jinja2.Environment(
extensions=[IncludeRawExtension, *register_jinja_extensions(config)],
)
- for article in markata.articles:
- if article.get("jinja", True) and not ignore_spec.match_file(article["path"]):
+ for post in markata.posts:
+ if post.get("jinja", True) and not ignore_spec.match_file(post["path"]):
try:
- key = markata.make_hash(article.content)
+ key = markata.make_hash(post.content)
content_from_cache = markata.precache.get(key)
if content_from_cache is None:
- article.content = jinja_env.from_string(article.content).render(
+ post.content = jinja_env.from_string(post.content).render(
__version__=__version__,
- **article,
- post=article,
+ **post,
+ post=post,
)
with markata.cache:
- markata.cache.set(key, article.content)
+ markata.cache.set(key, post.content)
else:
- article.content = content_from_cache
+ post.content = content_from_cache
# prevent double rendering
- article.jinja = False
+ post.jinja = False
except TemplateSyntaxError as e:
- errorline = article.content.split("\n")[e.lineno - 1]
+ errorline = post.content.split("\n")[e.lineno - 1]
msg = f"""
- Error while processing post {article['path']}
+ Error while processing post {post['path']}
{errorline}
"""
raise PostTemplateSyntaxError(msg, lineno=e.lineno)
except UndefinedError as e:
- raise UndefinedError(f'{e} in {article["path"]}')
+ raise UndefinedError(f'{e} in {post["path"]}')
class JinjaMdConfig(pydantic.BaseModel):
diff --git a/markata/plugins/load.py b/markata/plugins/load.py
index 80a256ac..31736112 100644
--- a/markata/plugins/load.py
+++ b/markata/plugins/load.py
@@ -3,6 +3,7 @@
from pathlib import Path
from typing import TYPE_CHECKING, Callable, List, Optional
+from markata import background
import frontmatter
import pydantic
from rich.progress import BarColumn, Progress
@@ -30,13 +31,17 @@ def load(markata: "MarkataMarkdown") -> None:
console=markata.console,
)
markata.console.log(f"found {len(markata.files)} posts")
+ post_futures = [get_post(article, markata) for article in markata.files]
+ posts = [post.result() for post in post_futures if post is not None]
+
markata.posts_obj = markata.Posts.parse_obj(
- {"posts": [get_post(article, markata) for article in markata.files]},
+ {"posts": posts},
)
markata.posts = markata.posts_obj.posts
markata.articles = markata.posts
+@background.task
def get_post(path: Path, markata: "Markata") -> Optional[Callable]:
if markata.Post:
post = pydantic_get_post(path=path, markata=markata)
diff --git a/markata/plugins/post_model.py b/markata/plugins/post_model.py
index 07867139..c4cafb65 100644
--- a/markata/plugins/post_model.py
+++ b/markata/plugins/post_model.py
@@ -44,6 +44,7 @@ class Post(pydantic.BaseModel):
arbitrary_types_allowed=True,
extra="allow",
)
+ template: Optional[str] = "post.html"
def __repr_args__(self: "Post") -> "ReprArgs":
return [
@@ -52,6 +53,20 @@ def __repr_args__(self: "Post") -> "ReprArgs":
if key in self.markata.config.post_model.repr_include
]
+ @property
+ def key(self: "Post") -> List[str]:
+ return self.markata.make_hash(
+ self.slug,
+ self.slug,
+ self.href,
+ self.published,
+ self.description,
+ self.content,
+ self.date,
+ self.title,
+ self.template,
+ )
+
@property
def metadata(self: "Post") -> Dict:
"for backwards compatability"
@@ -177,7 +192,7 @@ def parse_markdown(cls, markata, path: Union[Path, str], **kwargs) -> "Post":
**fm,
}
- return markata.Post(**post_args)
+ return markata.Post.parse_obj(post_args)
def dumps(self):
"""
@@ -195,6 +210,12 @@ def index_slug_is_empty(cls, v, *, values):
return ""
return v
+ @pydantic.validator("slug", pre=True, always=True)
+ def no_double_slash_in_slug(cls, v, *, values):
+ if v is None:
+ return v
+ return v.replace("//", "/")
+
@pydantic.validator("href", pre=True, always=True)
def default_href(cls, v, *, values):
if v:
@@ -346,3 +367,4 @@ def config_model(markata: "Markata") -> None:
class PostFactory(ModelFactory):
__model__ = Post
+ __model__ = Post
diff --git a/markata/plugins/post_template.py b/markata/plugins/post_template.py
index 9161a905..e9d835eb 100644
--- a/markata/plugins/post_template.py
+++ b/markata/plugins/post_template.py
@@ -69,19 +69,20 @@
```
"""
+from functools import lru_cache
import inspect
from pathlib import Path
-from typing import TYPE_CHECKING, List, Union
+from typing import List, Optional, TYPE_CHECKING, Union
import jinja2
-import pydantic
from jinja2 import Template, Undefined
+
from more_itertools import flatten
+import pydantic
-from markata import __version__
+from markata import __version__, background
from markata.hookspec import hook_impl
-env = jinja2.Environment()
if TYPE_CHECKING:
from markata import Markata
@@ -169,20 +170,39 @@ def html(self):
class Config(pydantic.BaseModel):
head: HeadConfig = HeadConfig()
style: Style = Style()
- post_template: str = None
-
- @pydantic.validator("post_template", pre=True, always=True)
- def default_post_template(cls, v):
- if v is None:
- return (
- Path(__file__).parent / "default_post_template.html.jinja"
- ).read_text()
- if isinstance(v, Path):
- return v.read_text()
- if isinstance(v, str) and Path(v).exists():
- return Path(v).read_text()
+ post_template: str = "post.html"
+ dynamic_templates_dir: Path = Path(".markata.cache/templates")
+ templates_dir: List[Path] = pydantic.Field(
+ [Path("templates"), Path(__file__).parents[1] / "templates"],
+ )
+ env_options: dict = {}
+
+ @pydantic.validator("templates_dir", pre=True, always=True)
+ def dynamic_templates_in_templates_dir(cls, v, *, values):
+ if values["dynamic_templates_dir"] not in v:
+ v.append(values["dynamic_templates_dir"])
return v
+ @property
+ def jinja_loader(self):
+ return jinja2.FileSystemLoader(self.templates_dir)
+
+ @property
+ def jinja_env(
+ self,
+ ):
+ if hasattr(self, "_jinja_env"):
+ return self._jinja_env
+ self.env_options.setdefault("loader", self.jinja_loader)
+ self.env_options.setdefault("undefined", SilentUndefined)
+ self.env_options.setdefault("lstrip_blocks", True)
+ self.env_options.setdefault("trim_blocks", True)
+
+ env = jinja2.Environment(**self.env_options)
+
+ self._jinja_env = env
+ return env
+
class PostOverrides(pydantic.BaseModel):
head: HeadConfig = HeadConfig()
@@ -191,6 +211,13 @@ class PostOverrides(pydantic.BaseModel):
class Post(pydantic.BaseModel):
config_overrides: PostOverrides = PostOverrides()
+ template: Optional[str] = None
+
+ @pydantic.validator("template", pre=True, always=True)
+ def default_template(cls, v, *, values):
+ if v is None:
+ return values["markata"].config.post_template
+ return v
@hook_impl(tryfirst=True)
@@ -228,6 +255,15 @@ def pre_render(markata: "Markata") -> None:
allowing an simpler jinja template. This enablees the use of the
`markata.head.text` list in configuration.
"""
+
+ markata.config.dynamic_templates_dir.mkdir(parents=True, exist_ok=True)
+ head_template = markata.config.dynamic_templates_dir / "head.html"
+ head_template.write_text(
+ markata.config.jinja_env.get_template("base_head.html").render(
+ {"markata": markata}
+ ),
+ )
+
for article in [a for a in markata.articles if "config_overrides" in a]:
raw_text = article.get("config_overrides", {}).get("head", {}).get("text", "")
@@ -239,48 +275,95 @@ def pre_render(markata: "Markata") -> None:
@hook_impl
def render(markata: "Markata") -> None:
- template = Template(markata.config.post_template, undefined=SilentUndefined)
+ # with markata.cache as cache:
+ # for article in markata.articles:
+ # merged_config = markata.config
+ # key = markata.make_hash(
+ # "post_template",
+ # __version__,
+ # merged_config,
+ # article.key,
+ # )
- if "{{" in str(markata.config.get("head", {})):
- Template(
- str(markata.config.get("head", {})),
- undefined=SilentUndefined,
- )
- else:
- pass
+ # article._html = markata.precache.get(key)
+
+ # futures = [
+ # (article, render_article(markata, article, cache))
+ # for article in markata.articles
+ # ]
+ futures = []
merged_config = markata.config
- for article in [a for a in markata.articles if hasattr(a, "html")]:
- # TODO do we need to handle merge??
- # if head_template:
- # head = eval(
- # head_template.render(
- # __version__=__version__,
- # config=_full_config,
- # **article,
- # )
- # )
-
- # merged_config = {
- # **_full_config,
- # **{"head": head},
- # }
-
- # merged_config = always_merger.merge(
- # merged_config,
- # copy.deepcopy(
- # article.get(
- # "config_overrides",
- # {},
- # )
- # ),
- # )
-
- article.html = template.render(
- __version__=__version__,
- body=article.html,
- toc=markata.md.toc, # type: ignore
- config=merged_config,
- post=article,
- **article.metadata,
+ for article in markata.articles:
+ key = markata.make_hash(
+ "post_template",
+ __version__,
+ merged_config,
+ article.key,
)
+ html = markata.precache.get(key)
+
+ if html is not None:
+ article.html = html
+ else:
+ futures.append((article, render_article(markata, article)))
+
+ for article, future in futures:
+ article.html = future.result()
+ # cache.set(key, article.html)
+
+
+@lru_cache()
+def get_template(markata, template):
+ try:
+ return markata.config.jinja_env.get_template(template)
+ except jinja2.TemplateNotFound:
+ # try to load it as a file
+ ...
+
+ try:
+ return Template(Path(template).read_text(), undefined=SilentUndefined)
+ except FileNotFoundError:
+ # default to load it as a string
+ ...
+ return Template(template, undefined=SilentUndefined)
+
+
+@background.task
+def render_article(markata, article):
+ merged_config = markata.config
+ template = get_template(markata, article.template)
+ # TODO do we need to handle merge??
+ # if head_template:
+ # head = eval(
+ # head_template.render(
+ # __version__=__version__,
+ # config=_full_config,
+ # **article,
+ # )
+ # )
+
+ # merged_config = {
+ # **_full_config,
+ # **{"head": head},
+ # }
+
+ # merged_config = always_merger.merge(
+ # merged_config,
+ # copy.deepcopy(
+ # article.get(
+ # "config_overrides",
+ # {},
+ # )
+ # ),
+ # )
+
+ html = template.render(
+ __version__=__version__,
+ body=article.article_html,
+ toc=markata.md.toc, # type: ignore
+ config=merged_config,
+ post=article,
+ **article.metadata,
+ )
+ return html
diff --git a/markata/templates/base.html b/markata/templates/base.html
new file mode 100644
index 00000000..23643590
--- /dev/null
+++ b/markata/templates/base.html
@@ -0,0 +1,50 @@
+
+
+
+
+ {% block head %}
+ {% if post.title or config.title %}
+ {{ post.title or config.title }}
+ {% endif %}
+
+
+ {% if post.description or config.description %}
+
+ {% endif %} {% if config.icon %}
+
+ {% endif %}
+
+
+
+
+
+ {% if 'markata.plugins.service_worker' in config.hooks %}
+
+ {% endif %}
+
+ {% include "head.html" %}
+ {% endblock %}
+
+
+
+ {% include "nav.html" %}
+ {% block content %} {% endblock %}
+ {% block footer %} {% endblock %}
+
+
+
diff --git a/markata/templates/base_head.html b/markata/templates/base_head.html
new file mode 100644
index 00000000..7d5e322e
--- /dev/null
+++ b/markata/templates/base_head.html
@@ -0,0 +1,7 @@
+{% for meta in markata.config.head.meta %}
+
+{% endfor %}
+
+{% for link in markata.config.head.link %}
+
+{% endfor %}
diff --git a/markata/templates/feed.html b/markata/templates/feed.html
new file mode 100644
index 00000000..70811023
--- /dev/null
+++ b/markata/templates/feed.html
@@ -0,0 +1,19 @@
+{% extends "base.html" %}
+{% block content %}
+
+
+
+
+{% endblock %}
diff --git a/markata/templates/nav.html b/markata/templates/nav.html
new file mode 100644
index 00000000..80720a7b
--- /dev/null
+++ b/markata/templates/nav.html
@@ -0,0 +1,12 @@
+
+
diff --git a/markata/templates/post.css b/markata/templates/post.css
new file mode 100644
index 00000000..1af7ddbb
--- /dev/null
+++ b/markata/templates/post.css
@@ -0,0 +1,957 @@
+:root {
+ --color-bg:{{markata.config.style.color_bg}};
+ --color-bg-code:{{markata.config.style.color_bg_code}};
+ --color-text:{{markata.config.style.color_text}};
+ --color-link:{{markata.config.style.color_link}};
+ --color-accent:{{markata.config.style.color_accent}};
+ --overlay-brightness:{{markata.config.style.overlay_brightness}};
+ --body-width:{{markata.config.style.body_width}};
+}
+
+[data-theme="dark"] {
+ --color-bg:{{markata.config.style.color_bg}};
+ --color-bg-code:{{markata.config.style.color_bg_code}};
+ --color-text:{{markata.config.style.color_text}};
+ --color-link:{{markata.config.style.color_link}};
+ --color-accent:{{markata.config.style.color_accent}};
+ --overlay-brightness:{{markata.config.style.overlay_brightness}};
+ --body-width:{{markata.config.style.body_width}};
+ --audio-filter: invert(100%);
+}
+
+[data-theme="light"] {
+ --color-bg:{{markata.config.style.color_bg_light}};
+ --color-bg-2:{{markata.config.style.color_bg_light_2}};
+ --color-bg-code:{{markata.config.style.color_bg_code_light}};
+ --color-text:{{markata.config.style.color_text_light}};
+ --color-link:{{markata.config.style.color_link_light}};
+ --color-accent:{{markata.config.style.color_accent_light}};
+ --overlay-brightness:{{markata.config.style.overlay_brightness_light}};
+ --body-width:{{markata.config.style.body_width}};
+ --audio-filter: invert(0%);
+}
+
+audio {
+ filter: var(--audio-filter);
+ width: -webkit-fill-available;
+ margin: 1rem 5rem
+}
+
+html {
+ font-family: "Space Mono", monospace;
+ background: var(--color-bg);
+ color: var(--color-text);
+}
+
+a {
+ color: var(--color-link);
+}
+
+main a {
+ max-width: 100%;
+}
+
+.heading-permalink {
+ font-size: .7em;
+}
+
+body {
+ max-width: var(--body-width);
+ margin: 5rem auto;
+ padding: 0 .5rem;
+ font-size: 1rem;
+ line-height: 1.56;
+}
+
+blockquote {
+ background: var(--color-bg);
+ filter: brightness(var(--overlay-brightness));
+ border-left: 4px solid var(--color-accent);
+ border-radius: 4px;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #f1fa8c,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+ padding-left: 1rem;
+ margin: 1rem;
+}
+
+li.post {
+ list-style-type: None;
+ padding: .2rem 0;
+}
+
+pre.wrapper {
+ padding: 0;
+ box-shadow: 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ margin: 2rem;
+}
+
+pre {
+ margin: 0;
+ padding: 1rem;
+ min-width: -webkit-fill-available;
+ max-width: fit-content;
+ overflow-x: auto;
+}
+
+pre .filepath {
+ margin: 0;
+ padding-left: 1rem;
+ border-radius: 4px 4px 0 0;
+ background: black;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+pre .filepath p {
+ margin: 0
+}
+
+pre .filepath .right {
+ display: flex;
+ gap: .2rem;
+ align-items: center;
+}
+
+pre::-webkit-scrollbar {
+ height: 4px;
+ background-color: transparent;
+}
+
+pre::-webkit-scrollbar-thumb {
+ background-color: #d3d3d32e;
+ border-radius: 2px;
+}
+
+pre::-webkit-scrollbar-track {
+ background-color: transparent;
+}
+
+.copy-wrapper {
+ background: none;
+ position: absolute;
+ width: 100%;
+ z-index: 100;
+ display: flex;
+ justify-content: flex-end;
+}
+
+button.copy {
+ z-index: 100;
+ background: none;
+ fill: #ffffff45;
+ border: none;
+ width: 32px;
+ align-self: flex-end;
+ top: 0;
+ right: 0;
+ margin: 0.5rem 0.2rem;
+
+}
+
+button.copy:hover {
+ fill: white
+}
+
+a.help {
+ fill: #ffffff45;
+}
+
+a.help:hover {
+ fill: white;
+}
+
+a.help svg {
+ height: 24px;
+ width: 24px;
+}
+
+.highlight {
+ background: var(--color-bg-code);
+ color: var(--color-text);
+ filter: brightness(var(--overlay-brightness));
+ border-radius: 0 0 4px 4px;
+}
+
+.highlight .c {
+ color: #8b8b8b
+}
+
+/* Comment */
+.highlight .err {
+ color: #960050;
+ background-color: #1e0010
+}
+
+/* Error */
+.highlight .k {
+ color: #c678dd
+}
+
+/* Keyword */
+.highlight .l {
+ color: #ae81ff
+}
+
+/* Literal */
+.highlight .n {
+ color: #abb2bf
+}
+
+/* Name */
+.highlight .o {
+ color: #c678dd
+}
+
+/* Operator */
+.highlight .p {
+ color: #abb2bf
+}
+
+/* Punctuation */
+.highlight .ch {
+ color: #8b8b8b
+}
+
+/* Comment.Hashbang */
+.highlight .cm {
+ color: #8b8b8b
+}
+
+/* Comment.Multiline */
+.highlight .cp {
+ color: #8b8b8b
+}
+
+/* Comment.Preproc */
+.highlight .cpf {
+ color: #8b8b8b
+}
+
+/* Comment.PreprocFile */
+.highlight .c1 {
+ color: #8b8b8b
+}
+
+/* Comment.Single */
+.highlight .cs {
+ color: #8b8b8b
+}
+
+/* Comment.Special */
+.highlight .gd {
+ color: #c678dd
+}
+
+/* Generic.Deleted */
+.highlight .ge {
+ font-style: italic
+}
+
+/* Generic.Emph */
+.highlight .gi {
+ color: #a6e22e
+}
+
+/* Generic.Inserted */
+.highlight .gs {
+ font-weight: bold
+}
+
+/* Generic.Strong */
+.highlight .gu {
+ color: #8b8b8b
+}
+
+/* Generic.Subheading */
+.highlight .kc {
+ color: #c678dd
+}
+
+/* Keyword.Constant */
+.highlight .kd {
+ color: #c678dd
+}
+
+/* Keyword.Declaration */
+.highlight .kn {
+ color: #c678dd
+}
+
+/* Keyword.Namespace */
+.highlight .kp {
+ color: #c678dd
+}
+
+/* Keyword.Pseudo */
+.highlight .kr {
+ color: #c678dd
+}
+
+/* Keyword.Reserved */
+.highlight .kt {
+ color: #c678dd
+}
+
+/* Keyword.Type */
+.highlight .ld {
+ color: #e6db74
+}
+
+/* Literal.Date */
+.highlight .m {
+ color: #ae81ff
+}
+
+/* Literal.Number */
+.highlight .s {
+ color: #e6db74
+}
+
+/* Literal.String */
+.highlight .na {
+ color: #a6e22e
+}
+
+/* Name.Attribute */
+.highlight .nb {
+ color: #98c379
+}
+
+/* Name.Builtin */
+.highlight .nc {
+ color: #abb2bf
+}
+
+/* Name.Class */
+.highlight .no {
+ color: #c678dd
+}
+
+/* Name.Constant */
+.highlight .nd {
+ color: #abb2bf
+}
+
+/* Name.Decorator */
+.highlight .ni {
+ color: #abb2bf
+}
+
+/* Name.Entity */
+.highlight .ne {
+ color: #a6e22e
+}
+
+/* Name.Exception */
+.highlight .nf {
+ color: #61afef
+}
+
+/* Name.Function */
+.highlight .nl {
+ color: #abb2bf
+}
+
+/* Name.Label */
+.highlight .nn {
+ color: #abb2bf
+}
+
+/* Name.Namespace */
+.highlight .nx {
+ color: #a6e22e
+}
+
+/* Name.Other */
+.highlight .py {
+ color: #abb2bf
+}
+
+/* Name.Property */
+.highlight .nt {
+ color: #c678dd
+}
+
+/* Name.Tag */
+.highlight .nv {
+ color: #abb2bf
+}
+
+/* Name.Variable */
+.highlight .ow {
+ color: #c678dd
+}
+
+/* Operator.Word */
+.highlight .w {
+ color: #abb2bf
+}
+
+/* Text.Whitespace */
+.highlight .mb {
+ color: #ae81ff
+}
+
+/* Literal.Number.Bin */
+.highlight .mf {
+ color: #ae81ff
+}
+
+/* Literal.Number.Float */
+.highlight .mh {
+ color: #ae81ff
+}
+
+/* Literal.Number.Hex */
+.highlight .mi {
+ color: #ae81ff
+}
+
+/* Literal.Number.Integer */
+.highlight .mo {
+ color: #ae81ff
+}
+
+/* Literal.Number.Oct */
+.highlight .sa {
+ color: #e6db74
+}
+
+/* Literal.String.Affix */
+.highlight .sb {
+ color: #e6db74
+}
+
+/* Literal.String.Backtick */
+.highlight .sc {
+ color: #e6db74
+}
+
+/* Literal.String.Char */
+.highlight .dl {
+ color: #e6db74
+}
+
+/* Literal.String.Delimiter */
+.highlight .sd {
+ color: #98c379
+}
+
+/* Literal.String.Doc */
+.highlight .s2 {
+ color: #98c379
+}
+
+/* Literal.String.Double */
+.highlight .se {
+ color: #ae81ff
+}
+
+/* Literal.String.Escape */
+.highlight .sh {
+ color: #e6db74
+}
+
+/* Literal.String.Heredoc */
+.highlight .si {
+ color: #e6db74
+}
+
+/* Literal.String.Interpol */
+.highlight .sx {
+ color: #e6db74
+}
+
+/* Literal.String.Other */
+.highlight .sr {
+ color: #e6db74
+}
+
+/* Literal.String.Regex */
+.highlight .s1 {
+ color: #e6db74
+}
+
+/* Literal.String.Single */
+.highlight .ss {
+ color: #e6db74
+}
+
+/* Literal.String.Symbol */
+.highlight .bp {
+ color: #abb2bf
+}
+
+/* Name.Builtin.Pseudo */
+.highlight .fm {
+ color: #61afef
+}
+
+/* Name.Function.Magic */
+.highlight .vc {
+ color: #abb2bf
+}
+
+/* Name.Variable.Class */
+.highlight .vg {
+ color: #abb2bf
+}
+
+/* Name.Variable.Global */
+.highlight .vi {
+ color: #abb2bf
+}
+
+/* Name.Variable.Instance */
+.highlight .vm {
+ color: #abb2bf
+}
+
+/* Name.Variable.Magic */
+.highlight .il {
+ color: #ae81ff
+}
+
+/* Literal.Number.Integer.Long */
+
+/* Tab style starts here */
+.tabbed-set {
+ position: relative;
+ display: flex;
+ flex-wrap: wrap;
+ margin: 1em 0;
+ border-radius: 0.1rem;
+}
+
+.tabbed-set>input {
+ display: none;
+}
+
+.tabbed-set label {
+ width: auto;
+ padding: 0.9375em 1.25em 0.78125em;
+ font-weight: 700;
+ font-size: 0.84em;
+ white-space: nowrap;
+ border-bottom: 0.15rem solid transparent;
+ border-top-left-radius: 0.1rem;
+ border-top-right-radius: 0.1rem;
+ cursor: pointer;
+ transition: background-color 250ms, color 250ms;
+}
+
+.tabbed-set .tabbed-content {
+ width: 100%;
+ display: none;
+ box-shadow: 0 -.05rem #ddd;
+}
+
+.tabbed-set input {
+ position: absolute;
+ opacity: 0;
+}
+
+/* fonts */
+h1 {
+ font-weight: 700;
+}
+
+h1#title a {
+ font-size: 16px;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ margin-top: 3rem;
+}
+
+h1 {
+ font-size: 2.5em;
+ margin-top: 5rem;
+}
+
+h2 {
+ font-size: 1.63rem;
+ margin-top: 5rem;
+}
+
+
+
+p {
+ font-size: 21px;
+ font-style: normal;
+ font-variant: normal;
+ font-weight: 400;
+ line-height: 1.5;
+}
+
+@media only screen and (max-width: 700px) {
+ p {
+ font-size: 18px;
+ }
+}
+
+@media only screen and (max-width: 600px) {
+ p {
+ font-size: 16px;
+ }
+}
+
+@media only screen and (max-width: 500px) {
+ p {
+ font-size: 14px;
+ }
+}
+
+@media only screen and (max-width: 400px) {
+ p {
+ font-size: 12px;
+ }
+}
+
+
+pre {
+ font-style: normal;
+ font-variant: normal;
+ font-weight: 400;
+ line-height: 18.5714px;
+ */
+}
+
+a {
+ font-weight: 600;
+ text-decoration-color: var(--color-accent);
+ color: var(--color-link);
+ padding: .3rem .5rem;
+ display: inline-block;
+}
+
+.admonition,
+details {
+ box-shadow: 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+ margin: 5rem 0;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ text-align: left;
+ padding: 0;
+ border: 0;
+
+}
+
+.admonition {
+ padding-bottom: 1rem;
+}
+
+details[open] {
+ padding-bottom: .5rem;
+}
+
+.admonition p {
+ padding: .2rem .6rem;
+}
+
+.admonition-title,
+.details-title,
+summary {
+ background: var(--color-bg-2);
+ padding: 0;
+ margin: 0;
+ position: sticky;
+ top: 0;
+ z-index: 10;
+}
+
+summary:hover {
+ cursor: pointer;
+}
+
+summary.admonition-title,
+summary.details-title {
+ padding: .5rem;
+ padding-left: 1rem;
+}
+
+.note {
+ border-left: 4px solid #f1fa8c;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #f1fa8c,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+}
+
+.note>.admonition-title {
+ border-bottom: 1px solid #3c3d2d;
+}
+
+.abstract {
+ border-left: 4px solid #8be9fd;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #8be9fd,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+}
+
+.abstract>.admonition-title {
+ border-bottom: 1px solid #2c3a3f;
+}
+
+.info {
+ border-left: 4px solid;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #8bb0fd,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+}
+
+.info>.admonition-title {
+ border-bottom: 1px solid #2c313f;
+}
+
+.tip {
+ border-left: 4px solid #008080;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #008080,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+}
+
+.tip>.admonition-title {
+ border-bottom: 1px solid #1b2a2b;
+}
+
+.success {
+ border-left: 4px solid #50fa7b;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #50fa7b,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+}
+
+.success>.admonition-title {
+ border-bottom: 1px solid #263e2b;
+}
+
+.question {
+ border-left: 4px solid #a7fcbd;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #a7fcbd,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+}
+
+.question>.admonition-title {
+ border-bottom: 1px solid #303e35;
+}
+
+.warning {
+ border-left: 4px solid #ffb86c;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #ffb86c,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+}
+
+.warning>.admonition-title {
+ border-bottom: 1px solid #3f3328;
+}
+
+.failure {
+ border-left: 4px solid #b23b3b;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #b23b3b,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+}
+
+.failure>.admonition-title {
+ border-bottom: 1px solid #34201f;
+}
+
+.danger {
+ border-left: 4px solid #ff5555;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #ff5555,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+}
+
+.danger>.admonition-title {
+ border-bottom: 1px solid #402523;
+}
+
+.bug {
+ border-left: 4px solid #b2548a;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #b2548a,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+}
+
+.bug>.admonition-title {
+ border-bottom: 1px solid #32232c;
+}
+
+.example {
+ border-left: 4px solid #bd93f9;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #bd93f9,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+}
+
+.example>.admonition-title {
+ border-bottom: 1px solid #332d3e;
+}
+
+.source {
+ border-left: 4px solid #bd93f9;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #bd93f9,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+}
+
+.source>.admonition-title {
+ border-bottom: 1px solid #332d3e;
+}
+
+.quote {
+ border-left: 4px solid #999;
+ box-shadow:
+ -0.8rem 0rem 1rem -1rem #999,
+ 0.2rem 0rem 1rem rgb(0, 0, 0, .4);
+}
+
+.quote>.admonition-title {
+ border-bottom: 1px solid #2d2e2f;
+}
+
+table {
+ margin: 1rem 0;
+ border-collapse: collapse;
+ border-spacing: 0;
+ display: block;
+ max-width: -moz-fit-content;
+ max-width: fit-content;
+ overflow-x: auto;
+ white-space: nowrap;
+}
+
+table thead th {
+ border: solid 1px var(--color-text);
+ padding: 10px;
+ text-align: left;
+}
+
+table tbody td {
+ border: solid 1px var(--color-text);
+ padding: 10px;
+}
+
+.theme-switch {
+ z-index: 10;
+ display: inline-block;
+ height: 34px;
+ position: relative;
+ width: 60px;
+
+ display: flex;
+ justify-content: flex-end;
+ margin-right: 1rem;
+ margin-left: auto;
+ position: fixed;
+ right: 1rem;
+ top: 1rem;
+}
+
+.theme-switch input {
+ display: none;
+
+}
+
+.slider {
+ background-color: #ccc;
+ bottom: 0;
+ cursor: pointer;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+ transition: .4s;
+}
+
+.slider:before {
+ background-color: #fff;
+ bottom: 4px;
+ content: "";
+ height: 26px;
+ left: 4px;
+ position: absolute;
+ transition: .4s;
+ width: 26px;
+}
+
+input:checked+.slider {
+ background-color: #343434;
+}
+
+input:checked+.slider:before {
+ background-color: #848484;
+}
+
+input:checked+.slider:before {
+ transform: translateX(26px);
+}
+
+.slider.round {
+ border-radius: 34px;
+}
+
+.slider.round:before {
+ border-radius: 50%;
+}
+
+img {
+ width: 100%;
+ width: -moz-available;
+ width: -webkit-fill-available;
+}
+
+a {
+ max-width: -moz-available;
+ max-width: -webkit-fill-available;
+}
+
+details>* {
+ margin: 1rem;
+}
+
+.admonition>* {
+ margin: 1rem;
+}
+
+p.admonition-title,
+summary {
+ margin: 0;
+ padding-left: 1.2rem;
+}
+
+.small {
+ font-size: .9rem;
+ color: #888;
+}
+
+admonition+admonition {
+ margin-top: 20rem;
+}
+
+::-webkit-scrollbar {
+ height: 12px;
+ background-color: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ background-color: #d3d3d32e;
+ border-radius: 6px;
+}
+
+::-webkit-scrollbar-track {
+ background-color: transparent;
+}
diff --git a/markata/templates/post.html b/markata/templates/post.html
new file mode 100644
index 00000000..b188cc88
--- /dev/null
+++ b/markata/templates/post.html
@@ -0,0 +1,9 @@
+{% extends "base.html" %}
+{% block content %}
+
+ {% include "title.html" %}
+
+
+{% endblock %}
diff --git a/markata/templates/theme.js b/markata/templates/theme.js
new file mode 100644
index 00000000..9ccd87e6
--- /dev/null
+++ b/markata/templates/theme.js
@@ -0,0 +1,56 @@
+
+ function setTheme(theme) {
+ document.documentElement.setAttribute("data-theme", theme);
+ }
+
+ function detectColorSchemeOnLoad() {
+ //local storage is used to override OS theme settings
+ if (localStorage.getItem("theme")) {
+ if (localStorage.getItem("theme") == "dark") {
+ setTheme("dark");
+ } else if (localStorage.getItem("theme") == "light") {
+ setTheme("light");
+ }
+ } else if (!window.matchMedia) {
+ //matchMedia method not supported
+ setTheme("light");
+ return false;
+ } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
+ //OS theme setting detected as dark
+ setTheme("dark");
+ } else {
+ setTheme("light");
+ }
+ }
+ detectColorSchemeOnLoad();
+ document.addEventListener(
+ "DOMContentLoaded",
+ function () {
+ //identify the toggle switch HTML element
+ const toggleSwitch = document.querySelector(
+ '#theme-switch input[type="checkbox"]',
+ );
+
+ //function that changes the theme, and sets a localStorage variable to track the theme between page loads
+ function switchTheme(e) {
+ if (e.target.checked) {
+ localStorage.setItem("theme", "dark");
+ document.documentElement.setAttribute("data-theme", "dark");
+ toggleSwitch.checked = true;
+ } else {
+ localStorage.setItem("theme", "light");
+ document.documentElement.setAttribute("data-theme", "light");
+ toggleSwitch.checked = false;
+ }
+ }
+
+ //listener for changing themes
+ toggleSwitch.addEventListener("change", switchTheme, false);
+
+ //pre-check the dark-theme checkbox if dark-theme is set
+ if (document.documentElement.getAttribute("data-theme") == "dark") {
+ toggleSwitch.checked = true;
+ }
+ },
+ false,
+ );
diff --git a/markata/templates/title.html b/markata/templates/title.html
new file mode 100644
index 00000000..cfe56855
--- /dev/null
+++ b/markata/templates/title.html
@@ -0,0 +1,31 @@
+