Switch to new.css and cleanup markup & models
This commit is contained in:
parent
8bf3438746
commit
e23b988c55
30 changed files with 347 additions and 628 deletions
|
@ -5,7 +5,7 @@ from django.contrib.admin import register
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
from .models import Article, Page, User
|
from .models import Article, User
|
||||||
|
|
||||||
admin.site.register(User, UserAdmin)
|
admin.site.register(User, UserAdmin)
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||||
("status", "published_at"),
|
("status", "published_at"),
|
||||||
("created_at", "updated_at"),
|
("created_at", "updated_at"),
|
||||||
"views_count",
|
"views_count",
|
||||||
|
"has_code",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -92,13 +93,3 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||||
messages.success(request, "Item has been unpublished")
|
messages.success(request, "Item has been unpublished")
|
||||||
return redirect(".")
|
return redirect(".")
|
||||||
return super().response_change(request, obj)
|
return super().response_change(request, obj)
|
||||||
|
|
||||||
|
|
||||||
@register(Page)
|
|
||||||
class PageAdmin(ArticleAdmin):
|
|
||||||
list_display = ["position"] + ArticleAdmin.list_display
|
|
||||||
|
|
||||||
def get_fieldsets(self, request, obj=None):
|
|
||||||
article_fieldsets = copy.deepcopy(ArticleAdmin.fieldsets)
|
|
||||||
article_fieldsets[0][1]["fields"][0] = ("title", "slug", "position")
|
|
||||||
return article_fieldsets
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from articles.models import Article, Page
|
from articles.models import Article
|
||||||
from attachments.models import Attachment
|
from attachments.models import Attachment
|
||||||
|
|
||||||
IGNORED_PATHS = [
|
IGNORED_PATHS = [
|
||||||
|
@ -8,12 +8,6 @@ IGNORED_PATHS = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def pages(request):
|
|
||||||
if request.path in IGNORED_PATHS:
|
|
||||||
return {}
|
|
||||||
return {"pages": Page.objects.filter(status=Article.PUBLISHED).exclude(position=0)}
|
|
||||||
|
|
||||||
|
|
||||||
def drafts_count(request):
|
def drafts_count(request):
|
||||||
if request.path in IGNORED_PATHS:
|
if request.path in IGNORED_PATHS:
|
||||||
return {}
|
return {}
|
||||||
|
@ -52,3 +46,11 @@ def open_graph_image_url(request):
|
||||||
if open_graph_image:
|
if open_graph_image:
|
||||||
url = open_graph_image.processed_file.get_full_absolute_url(request)
|
url = open_graph_image.processed_file.get_full_absolute_url(request)
|
||||||
return {"open_graph_image_url": url}
|
return {"open_graph_image_url": url}
|
||||||
|
|
||||||
|
|
||||||
|
def blog_metadata(request):
|
||||||
|
context = {}
|
||||||
|
context["blog_title"] = settings.BLOG["title"]
|
||||||
|
context["blog_description"] = settings.BLOG["description"]
|
||||||
|
context["blog_author"] = settings.BLOG["author"]
|
||||||
|
return context
|
||||||
|
|
18
articles/migrations/0023_article_has_code.py
Normal file
18
articles/migrations/0023_article_has_code.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.1.3 on 2020-12-24 16:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("articles", "0022_article_keywords"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="article",
|
||||||
|
name="has_code",
|
||||||
|
field=models.BooleanField(blank=True, default=False),
|
||||||
|
),
|
||||||
|
]
|
32
articles/migrations/0024_auto_20201224_1746.py
Normal file
32
articles/migrations/0024_auto_20201224_1746.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# Generated by Django 3.1.3 on 2020-12-24 16:46
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def forwards_func(apps, schema_editor):
|
||||||
|
Article = apps.get_model("articles", "Article")
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
Article.objects.using(db_alias).filter(slug="about-me").update(is_home=True)
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_func(apps, schema_editor):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("articles", "0023_article_has_code"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="article",
|
||||||
|
name="is_home",
|
||||||
|
field=models.BooleanField(blank=True, default=False),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name="Page",
|
||||||
|
),
|
||||||
|
migrations.RunPython(forwards_func, reverse_func),
|
||||||
|
]
|
|
@ -18,11 +18,6 @@ class User(AbstractUser):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ArticleManager(models.Manager):
|
|
||||||
def get_queryset(self):
|
|
||||||
return super().get_queryset().filter(page__isnull=True)
|
|
||||||
|
|
||||||
|
|
||||||
class AdminUrlMixin:
|
class AdminUrlMixin:
|
||||||
def get_admin_url(self):
|
def get_admin_url(self):
|
||||||
content_type = ContentType.objects.get_for_model(self.__class__)
|
content_type = ContentType.objects.get_for_model(self.__class__)
|
||||||
|
@ -49,18 +44,14 @@ class Article(AdminUrlMixin, models.Model):
|
||||||
views_count = models.IntegerField(default=0)
|
views_count = models.IntegerField(default=0)
|
||||||
slug = models.SlugField(unique=True, max_length=255)
|
slug = models.SlugField(unique=True, max_length=255)
|
||||||
keywords = models.CharField(max_length=255, blank=True)
|
keywords = models.CharField(max_length=255, blank=True)
|
||||||
|
has_code = models.BooleanField(default=False, blank=True)
|
||||||
objects = models.Manager()
|
is_home = models.BooleanField(default=False, blank=True)
|
||||||
without_pages = ArticleManager()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["-published_at"]
|
ordering = ["-published_at"]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
type_ = "Article"
|
return f"{self.title}"
|
||||||
if hasattr(self, "page"):
|
|
||||||
type_ = "Page"
|
|
||||||
return f"{self.title} ({type_})"
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("article-detail", kwargs={"slug": self.slug})
|
return reverse("article-detail", kwargs={"slug": self.slug})
|
||||||
|
@ -116,11 +107,3 @@ class Article(AdminUrlMixin, models.Model):
|
||||||
if not self.slug:
|
if not self.slug:
|
||||||
self.slug = slugify(self.title)
|
self.slug = slugify(self.title)
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Page(Article):
|
|
||||||
objects = models.Manager()
|
|
||||||
position = models.IntegerField(default=0)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ["position", "-published_at"]
|
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
.codehilite .hll { background-color: #49483e }
|
|
||||||
.codehilite { background: #232629; color: #cccccc }
|
|
||||||
.codehilite .c { color: #777777; font-style: italic } /* Comment */
|
|
||||||
.codehilite .err { color: #a61717; background-color: #e3d2d2 } /* Error */
|
|
||||||
.codehilite .k { color: #7686bb; font-weight: bold } /* Keyword */
|
|
||||||
.codehilite .ch { color: #777777; font-style: italic } /* Comment.Hashbang */
|
|
||||||
.codehilite .cm { color: #777777; font-style: italic } /* Comment.Multiline */
|
|
||||||
.codehilite .cp { color: #777777; font-style: italic } /* Comment.Preproc */
|
|
||||||
.codehilite .cpf { color: #777777; font-style: italic } /* Comment.PreprocFile */
|
|
||||||
.codehilite .c1 { color: #777777; font-style: italic } /* Comment.Single */
|
|
||||||
.codehilite .cs { color: #777777; font-style: italic } /* Comment.Special */
|
|
||||||
.codehilite .gp { color: #ffffff } /* Generic.Prompt */
|
|
||||||
.codehilite .kc { color: #7686bb; font-weight: bold } /* Keyword.Constant */
|
|
||||||
.codehilite .kd { color: #7686bb; font-weight: bold } /* Keyword.Declaration */
|
|
||||||
.codehilite .kn { color: #7686bb; font-weight: bold } /* Keyword.Namespace */
|
|
||||||
.codehilite .kp { color: #7686bb; font-weight: bold } /* Keyword.Pseudo */
|
|
||||||
.codehilite .kr { color: #7686bb; font-weight: bold } /* Keyword.Reserved */
|
|
||||||
.codehilite .kt { color: #7686bb; font-weight: bold } /* Keyword.Type */
|
|
||||||
.codehilite .m { color: #4FB8CC } /* Literal.Number */
|
|
||||||
.codehilite .s { color: #51cc99 } /* Literal.String */
|
|
||||||
.codehilite .nf { color: #6a6aff } /* Name.Function */
|
|
||||||
.codehilite .nx { color: #e2828e } /* Name.Other */
|
|
||||||
.codehilite .nv { color: #7AB4DB; font-weight: bold } /* Name.Variable */
|
|
||||||
.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
|
|
||||||
.codehilite .mb { color: #4FB8CC } /* Literal.Number.Bin */
|
|
||||||
.codehilite .mf { color: #4FB8CC } /* Literal.Number.Float */
|
|
||||||
.codehilite .mh { color: #4FB8CC } /* Literal.Number.Hex */
|
|
||||||
.codehilite .mi { color: #4FB8CC } /* Literal.Number.Integer */
|
|
||||||
.codehilite .mo { color: #4FB8CC } /* Literal.Number.Oct */
|
|
||||||
.codehilite .sa { color: #51cc99 } /* Literal.String.Affix */
|
|
||||||
.codehilite .sb { color: #51cc99 } /* Literal.String.Backtick */
|
|
||||||
.codehilite .sc { color: #51cc99 } /* Literal.String.Char */
|
|
||||||
.codehilite .dl { color: #51cc99 } /* Literal.String.Delimiter */
|
|
||||||
.codehilite .sd { color: #51cc99 } /* Literal.String.Doc */
|
|
||||||
.codehilite .s2 { color: #51cc99 } /* Literal.String.Double */
|
|
||||||
.codehilite .se { color: #51cc99 } /* Literal.String.Escape */
|
|
||||||
.codehilite .sh { color: #51cc99 } /* Literal.String.Heredoc */
|
|
||||||
.codehilite .si { color: #51cc99 } /* Literal.String.Interpol */
|
|
||||||
.codehilite .sx { color: #51cc99 } /* Literal.String.Other */
|
|
||||||
.codehilite .sr { color: #51cc99 } /* Literal.String.Regex */
|
|
||||||
.codehilite .s1 { color: #51cc99 } /* Literal.String.Single */
|
|
||||||
.codehilite .ss { color: #51cc99 } /* Literal.String.Symbol */
|
|
||||||
.codehilite .fm { color: #6a6aff } /* Name.Function.Magic */
|
|
||||||
.codehilite .vc { color: #7AB4DB; font-weight: bold } /* Name.Variable.Class */
|
|
||||||
.codehilite .vg { color: #BE646C; font-weight: bold } /* Name.Variable.Global */
|
|
||||||
.codehilite .vi { color: #7AB4DB; font-weight: bold } /* Name.Variable.Instance */
|
|
||||||
.codehilite .vm { color: #7AB4DB; font-weight: bold } /* Name.Variable.Magic */
|
|
||||||
.codehilite .il { color: #4FB8CC } /* Literal.Number.Integer.Long */
|
|
|
@ -1,66 +0,0 @@
|
||||||
.codehilite .hll { background-color: #ffffcc }
|
|
||||||
.codehilite { background: #ffffff; }
|
|
||||||
.codehilite .c { color: #aaaaaa; font-style: italic } /* Comment */
|
|
||||||
.codehilite .err { color: #FF0000; background-color: #FFAAAA } /* Error */
|
|
||||||
.codehilite .k { color: #0000aa } /* Keyword */
|
|
||||||
.codehilite .ch { color: #aaaaaa; font-style: italic } /* Comment.Hashbang */
|
|
||||||
.codehilite .cm { color: #aaaaaa; font-style: italic } /* Comment.Multiline */
|
|
||||||
.codehilite .cp { color: #4c8317 } /* Comment.Preproc */
|
|
||||||
.codehilite .cpf { color: #aaaaaa; font-style: italic } /* Comment.PreprocFile */
|
|
||||||
.codehilite .c1 { color: #aaaaaa; font-style: italic } /* Comment.Single */
|
|
||||||
.codehilite .cs { color: #0000aa; font-style: italic } /* Comment.Special */
|
|
||||||
.codehilite .gd { color: #aa0000 } /* Generic.Deleted */
|
|
||||||
.codehilite .ge { font-style: italic } /* Generic.Emph */
|
|
||||||
.codehilite .gr { color: #aa0000 } /* Generic.Error */
|
|
||||||
.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
|
||||||
.codehilite .gi { color: #00aa00 } /* Generic.Inserted */
|
|
||||||
.codehilite .go { color: #888888 } /* Generic.Output */
|
|
||||||
.codehilite .gp { color: #555555 } /* Generic.Prompt */
|
|
||||||
.codehilite .gs { font-weight: bold } /* Generic.Strong */
|
|
||||||
.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
|
||||||
.codehilite .gt { color: #aa0000 } /* Generic.Traceback */
|
|
||||||
.codehilite .kc { color: #0000aa } /* Keyword.Constant */
|
|
||||||
.codehilite .kd { color: #0000aa } /* Keyword.Declaration */
|
|
||||||
.codehilite .kn { color: #0000aa } /* Keyword.Namespace */
|
|
||||||
.codehilite .kp { color: #0000aa } /* Keyword.Pseudo */
|
|
||||||
.codehilite .kr { color: #0000aa } /* Keyword.Reserved */
|
|
||||||
.codehilite .kt { color: #00aaaa } /* Keyword.Type */
|
|
||||||
.codehilite .m { color: #009999 } /* Literal.Number */
|
|
||||||
.codehilite .s { color: #aa5500 } /* Literal.String */
|
|
||||||
.codehilite .na { color: #1e90ff } /* Name.Attribute */
|
|
||||||
.codehilite .nb { color: #00aaaa } /* Name.Builtin */
|
|
||||||
.codehilite .nc { color: #00aa00; text-decoration: underline } /* Name.Class */
|
|
||||||
.codehilite .no { color: #aa0000 } /* Name.Constant */
|
|
||||||
.codehilite .nd { color: #888888 } /* Name.Decorator */
|
|
||||||
.codehilite .ni { color: #880000; font-weight: bold } /* Name.Entity */
|
|
||||||
.codehilite .nf { color: #00aa00 } /* Name.Function */
|
|
||||||
.codehilite .nn { color: #00aaaa; text-decoration: underline } /* Name.Namespace */
|
|
||||||
.codehilite .nt { color: #1e90ff; font-weight: bold } /* Name.Tag */
|
|
||||||
.codehilite .nv { color: #aa0000 } /* Name.Variable */
|
|
||||||
.codehilite .ow { color: #0000aa } /* Operator.Word */
|
|
||||||
.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
|
|
||||||
.codehilite .mb { color: #009999 } /* Literal.Number.Bin */
|
|
||||||
.codehilite .mf { color: #009999 } /* Literal.Number.Float */
|
|
||||||
.codehilite .mh { color: #009999 } /* Literal.Number.Hex */
|
|
||||||
.codehilite .mi { color: #009999 } /* Literal.Number.Integer */
|
|
||||||
.codehilite .mo { color: #009999 } /* Literal.Number.Oct */
|
|
||||||
.codehilite .sa { color: #aa5500 } /* Literal.String.Affix */
|
|
||||||
.codehilite .sb { color: #aa5500 } /* Literal.String.Backtick */
|
|
||||||
.codehilite .sc { color: #aa5500 } /* Literal.String.Char */
|
|
||||||
.codehilite .dl { color: #aa5500 } /* Literal.String.Delimiter */
|
|
||||||
.codehilite .sd { color: #aa5500 } /* Literal.String.Doc */
|
|
||||||
.codehilite .s2 { color: #aa5500 } /* Literal.String.Double */
|
|
||||||
.codehilite .se { color: #aa5500 } /* Literal.String.Escape */
|
|
||||||
.codehilite .sh { color: #aa5500 } /* Literal.String.Heredoc */
|
|
||||||
.codehilite .si { color: #aa5500 } /* Literal.String.Interpol */
|
|
||||||
.codehilite .sx { color: #aa5500 } /* Literal.String.Other */
|
|
||||||
.codehilite .sr { color: #009999 } /* Literal.String.Regex */
|
|
||||||
.codehilite .s1 { color: #aa5500 } /* Literal.String.Single */
|
|
||||||
.codehilite .ss { color: #0000aa } /* Literal.String.Symbol */
|
|
||||||
.codehilite .bp { color: #00aaaa } /* Name.Builtin.Pseudo */
|
|
||||||
.codehilite .fm { color: #00aa00 } /* Name.Function.Magic */
|
|
||||||
.codehilite .vc { color: #aa0000 } /* Name.Variable.Class */
|
|
||||||
.codehilite .vg { color: #aa0000 } /* Name.Variable.Global */
|
|
||||||
.codehilite .vi { color: #aa0000 } /* Name.Variable.Instance */
|
|
||||||
.codehilite .vm { color: #aa0000 } /* Name.Variable.Magic */
|
|
||||||
.codehilite .il { color: #009999 } /* Literal.Number.Integer.Long */
|
|
12
articles/static/edit-keymap.js
Normal file
12
articles/static/edit-keymap.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
window.onload = function () {
|
||||||
|
const adminLinkElement = document.querySelector(".article-detail .metadata a.admin-link");
|
||||||
|
if (adminLinkElement === undefined || adminLinkElement === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const adminLocation = adminLinkElement.href;
|
||||||
|
document.addEventListener("keydown", event => {
|
||||||
|
if (event.code === "KeyE") {
|
||||||
|
window.location = adminLocation;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,32 +0,0 @@
|
||||||
function activateDarkMode() {
|
|
||||||
document.getElementById("code-dark").removeAttribute("disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
function activateLightMode() {
|
|
||||||
document.getElementById("code-dark").setAttribute("disabled", "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
function darkModeListener(e) {
|
|
||||||
if (e.matches) {
|
|
||||||
activateDarkMode();
|
|
||||||
} else {
|
|
||||||
activateLightMode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
||||||
darkModeListener(mql);
|
|
||||||
mql.addListener(darkModeListener);
|
|
||||||
|
|
||||||
window.onload = function () {
|
|
||||||
const adminLinkElement = document.querySelector(".article-detail .metadata a.admin-link");
|
|
||||||
if (adminLinkElement === undefined || adminLinkElement === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const adminLocation = adminLinkElement.href;
|
|
||||||
document.addEventListener("keydown", event => {
|
|
||||||
if (event.code === "KeyE") {
|
|
||||||
window.location = adminLocation;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,310 +0,0 @@
|
||||||
:root {
|
|
||||||
--accent: #226997;
|
|
||||||
--accent-light: #7c96a9;
|
|
||||||
--main: #111111;
|
|
||||||
--main2: #575757;
|
|
||||||
--main3: #7d7d7d;
|
|
||||||
--background: #ffffff;
|
|
||||||
--background2: #f7f7f7;
|
|
||||||
--background3: #d0d0d0;
|
|
||||||
--success-background: #d4edda;
|
|
||||||
--success-text: #155724;
|
|
||||||
--error-background: #f8d7da;
|
|
||||||
--error-text: #721c24;
|
|
||||||
--warning-background: #fff3cd;
|
|
||||||
--warning-text: #856404;
|
|
||||||
--description-margin: .2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 110%;
|
|
||||||
max-width: calc(640px + 2em);
|
|
||||||
padding: 8px 0 3em;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0 1em;
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
color: var(--main);
|
|
||||||
background-color: var(--background);
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
body img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--main);
|
|
||||||
text-decoration: none;
|
|
||||||
border-bottom: .3ex solid var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-page h2, .article-detail h2 {
|
|
||||||
margin-top: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-list h2 a {
|
|
||||||
border-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pic-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pic-container .profile-pic {
|
|
||||||
max-width: 200px;
|
|
||||||
min-width: 100px;
|
|
||||||
max-height: 200px;
|
|
||||||
min-height: 100px;
|
|
||||||
border-radius: 10%;
|
|
||||||
padding: 1rem;
|
|
||||||
flex-shrink: 1;
|
|
||||||
flex-grow: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-list + .article-list {
|
|
||||||
margin-top: 4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-list h2, h1 {
|
|
||||||
margin-bottom: var(--description-margin);
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-detail h1 {
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metadata {
|
|
||||||
margin-top: var(--description-margin);
|
|
||||||
color: var(--main2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.codehilite {
|
|
||||||
border-radius: .5ex;
|
|
||||||
background-color: var(--background2);
|
|
||||||
border: 1px solid var(--background3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.codehilite pre {
|
|
||||||
padding: 0;
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.comments {
|
|
||||||
border-top: 2px dashed var(--background3);
|
|
||||||
margin-top: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
border-top: 2px solid var(--background2);
|
|
||||||
margin-top: 2em;
|
|
||||||
font-size: 80%;
|
|
||||||
color: var(--main3);
|
|
||||||
}
|
|
||||||
|
|
||||||
footer a {
|
|
||||||
color: var(--main3);
|
|
||||||
border-bottom-color: var(--accent-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
th, td {
|
|
||||||
padding: 1ex;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr {
|
|
||||||
border-bottom: 1px solid var(--background3);
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:hover {
|
|
||||||
background-color: var(--background2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pill {
|
|
||||||
font-size: 60%;
|
|
||||||
background-color: var(--main2);
|
|
||||||
color: var(--background2);
|
|
||||||
padding: .5ex 1ex;
|
|
||||||
border-radius: 1ex;
|
|
||||||
vertical-align: 15%;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
background-color: var(--background2);
|
|
||||||
margin: 0;
|
|
||||||
padding: 1px .5em 1px 1.5em;
|
|
||||||
border-left: 6px solid var(--background3);
|
|
||||||
}
|
|
||||||
|
|
||||||
details {
|
|
||||||
padding: .6rem 1rem;
|
|
||||||
background: var(--background2);
|
|
||||||
border: 1px solid var(--background3);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
details .codehilite {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FORMS */
|
|
||||||
.helptext {
|
|
||||||
color: var(--main3);
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.helptext a {
|
|
||||||
color: var(--main3);
|
|
||||||
}
|
|
||||||
|
|
||||||
p.helptext {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
form table th {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
form table tr th {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
form tr.required th {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
form table tr {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
background-color: var(--background2);
|
|
||||||
border-radius: .5ex;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
form button[type=submit] {
|
|
||||||
font-size: 1em;
|
|
||||||
padding: .9ex 1.2ex;
|
|
||||||
border-radius: .8ex;
|
|
||||||
background-color: var(--accent);
|
|
||||||
border: none;
|
|
||||||
color: var(--background);
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .error, form .error .helptext, .errorlist {
|
|
||||||
color: var(--error-text);
|
|
||||||
background-color: var(--error-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.errorlist {
|
|
||||||
margin-top: 0;
|
|
||||||
list-style-type: none;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.errorlist.nonfield {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr.spacer {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea, input {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* MESSAGES */
|
|
||||||
.messages p {
|
|
||||||
background-color: var(--background2);
|
|
||||||
padding: .5ex 1ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages .error {
|
|
||||||
background-color: var(--error-background);
|
|
||||||
color: var(--error-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages .success {
|
|
||||||
background-color: var(--success-background);
|
|
||||||
color: var(--success-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages .warning {
|
|
||||||
background-color: var(--warning-background);
|
|
||||||
color: var(--warning-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* LINKS */
|
|
||||||
a:hover, a:focus {
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: var(--accent);
|
|
||||||
color: var(--background);
|
|
||||||
}
|
|
||||||
|
|
||||||
footer a:hover, footer a:focus {
|
|
||||||
background-color: var(--accent-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--accent: #226997;
|
|
||||||
--main: #eeeeee;
|
|
||||||
--main2: #cecece;
|
|
||||||
--main3: #b1b1b1;
|
|
||||||
--background: #111111;
|
|
||||||
--background2: #282828;
|
|
||||||
--success-background: #155724;
|
|
||||||
--success-text: #d4edda;
|
|
||||||
--error-background: #721c24;
|
|
||||||
--error-text: #f8d7da;
|
|
||||||
--warning-background: #856404;
|
|
||||||
--warning-text: #fff3cd;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
opacity: .75;
|
|
||||||
transition: opacity .2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
img:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#manage ul ul li img {
|
|
||||||
opacity: 1;
|
|
||||||
filter: invert(1);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
{% if user.is_authenticated %}
|
|
||||||
| <a class="admin-link" href="{{ article.get_admin_url }}">Edit</a>
|
|
||||||
{% endif %}
|
|
|
@ -1,21 +1,14 @@
|
||||||
{% extends 'articles/base.html' %}
|
{% extends 'articles/base.html' %}
|
||||||
|
|
||||||
{% block title %}{{ article.title }} | {% endblock %}
|
{% block title %}{{ article.title }} | {% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<article class="article-detail">
|
<article>
|
||||||
<h1>{{ article.title }}{% if article.status != article.PUBLISHED %}
|
<h1>{{ article.title }}{% if article.status != article.PUBLISHED %}
|
||||||
({{ article.status }}){% endif %}</h1>
|
({{ article.status }}){% endif %}</h1>
|
||||||
{% include "articles/metadata_snippet.html" %}
|
{% include "articles/snippets/metadata.html" %}
|
||||||
<div>
|
<div>
|
||||||
{{ article.get_formatted_content|safe }}
|
{{ article.get_formatted_content|safe }}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<section class="comments">
|
|
||||||
<h2>Comments</h2>
|
|
||||||
<p>
|
|
||||||
Comments are hard to manage. I tried enabling them but I only got spam.
|
|
||||||
If you want to react to this content or interact with me, please head to the
|
|
||||||
<a href="/about-me/">about me</a> page. 😉
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,38 +1,44 @@
|
||||||
{% extends 'articles/base.html' %}
|
{% extends 'articles/base.html' %}
|
||||||
|
|
||||||
|
{% block override_header %}
|
||||||
|
{% include "articles/snippets/style_home.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}{% endblock %}
|
{% block title %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{{ blog_title }}{% if title %} · {{ title }}{% endif %}</h1>
|
<section>
|
||||||
<p class="metadata">{{ blog_description }} {% include "articles/admin_link_snippet.html" %}</p>
|
|
||||||
<section class="blog-posts">
|
|
||||||
<h2>Blog posts</h2>
|
<h2>Blog posts</h2>
|
||||||
<ul>
|
<table>
|
||||||
|
<tr><th scope="col">Date</th><th scope="col">Title</th></tr>
|
||||||
{% for article in articles %}
|
{% for article in articles %}
|
||||||
<li>
|
<tr>
|
||||||
{% include "articles/datetime_snippet.html" %}
|
<td>{% include "articles/snippets/datetime.html" %}</td>
|
||||||
: <a href="{% url 'article-detail' slug=article.slug %}">{{ article.title }}</a>
|
<td><a href="{% url 'article-detail' slug=article.slug %}">{{ article.title }}</a></td>
|
||||||
</li>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<li>No article here. Come back later 🙂</li>
|
<tr><td colspan="2">No article here. Come back later 🙂</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</table>
|
||||||
</section>
|
</section>
|
||||||
<nav class="pagination">
|
<nav class="pagination">
|
||||||
<div class="older">
|
<div>
|
||||||
{% if page_obj.has_next %}
|
{% if page_obj.has_next %}
|
||||||
<a href="?page={{ page_obj.next_page_number }}">⇠ Older</a>
|
<a href="?page={{ page_obj.next_page_number }}">⇠ Older</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="newer">
|
<div>
|
||||||
{% if page_obj.has_previous %}
|
{% if page_obj.has_previous %}
|
||||||
<a href="?page={{ page_obj.previous_page_number }}">Newer ⇢</a>
|
<a href="?page={{ page_obj.previous_page_number }}">Newer ⇢</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<section class="index-page">
|
{% if article %}
|
||||||
<h2>{{ article.title }}</h2>
|
<section>
|
||||||
|
<h2>{{ article.title }}{% include "articles/snippets/admin_link.html" %}</h2>
|
||||||
<div>
|
<div>
|
||||||
{{ article.get_formatted_content|safe }}
|
{{ article.get_formatted_content|safe }}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -6,57 +6,45 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="author" content="Gabriel Augendre">
|
<meta name="author" content="Gabriel Augendre">
|
||||||
<meta name="color-scheme" content="light dark">
|
<meta name="color-scheme" content="light dark">
|
||||||
{% if article %}
|
{% include "articles/snippets/page_metadata.html" %}
|
||||||
<meta name="keywords" content="{{ article.keywords }}">
|
<title>{% block title %}Home | {% endblock %}{{ blog_title }} by {{ blog_author }}</title>
|
||||||
<meta name="description" content="{{ article.get_description }}">
|
|
||||||
<meta property="og:title" content="{{ article.title }}" />
|
|
||||||
<meta property="og:type" content="article">
|
|
||||||
<meta property="og:description" content="{{ article.get_description }}">
|
|
||||||
<meta property="og:site_name" content="{{ blog_title }}">
|
|
||||||
{% endif %}
|
|
||||||
{% if open_graph_image_url %}
|
|
||||||
<meta property="og:image" content="{{ open_graph_image_url }}">
|
|
||||||
{% endif %}
|
|
||||||
<title>{% block title %}Home | {% endblock %}Gab's Notes by Gabriel Augendre</title>
|
|
||||||
<link rel="stylesheet" id="code-light" href="{% static 'code-light.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" id="code-dark" href="{% static 'code-dark.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'style.css' %}" type="text/css">
|
|
||||||
<script src="{% static 'scripts.js' %}" async></script>
|
|
||||||
<link rel="alternate" type="application/rss+xml" title="Gab's Notes » Feed" href="{% url 'complete-feed' %}">
|
<link rel="alternate" type="application/rss+xml" title="Gab's Notes » Feed" href="{% url 'complete-feed' %}">
|
||||||
{% include "articles/favicon.html" %}
|
{% include "articles/snippets/style.html" %}
|
||||||
{% include "articles/analytics.html" %}
|
{% if article and article.has_code %}
|
||||||
|
{% include "articles/snippets/code_style.html" %}
|
||||||
|
{% endif %}
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<script src="{% static 'edit-keymap.js' %}" async></script>
|
||||||
|
{% endif %}
|
||||||
|
{% include "articles/snippets/favicon.html" %}
|
||||||
|
{% include "articles/snippets/analytics.html" %}
|
||||||
|
{% block override_header %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav>
|
<header>
|
||||||
<a href="{% url 'articles-list' %}">Articles list</a>
|
<h1>{{ blog_title }}</h1>
|
||||||
|
<p>{{ blog_description }}</p>
|
||||||
|
<nav>
|
||||||
|
<a href="{% url 'articles-list' %}">Home</a>
|
||||||
|
·
|
||||||
|
<a href="{% url 'complete-feed' %}">RSS</a>
|
||||||
·
|
·
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<a href="{% url 'admin:articles_article_add' %}">Write</a>
|
<a href="{% url 'admin:articles_article_add' %}">Write</a>
|
||||||
|
·
|
||||||
<a href="{% url 'drafts-list' %}">View drafts <span class="pill">{{ drafts_count }}</span></a>
|
<a href="{% url 'drafts-list' %}">View drafts <span class="pill">{{ drafts_count }}</span></a>
|
||||||
·
|
·
|
||||||
<a href="{% url 'admin:index' %}">Admin</a>
|
<a href="{% url 'admin:index' %}">Admin</a>
|
||||||
|
·
|
||||||
<a href="{% url 'admin:logout' %}?next=/">Log out</a>
|
<a href="{% url 'admin:logout' %}?next=/">Log out</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'admin:login' %}?next=/">Log in</a>
|
<a href="{% url 'admin:login' %}?next=/">Log in</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if pages %}
|
</nav>
|
||||||
·
|
</header>
|
||||||
{% for page in pages %}
|
|
||||||
<a href="{% url 'article-detail' slug=page.slug %}">{{ page.title }}</a>
|
{% block content %}
|
||||||
{% endfor %}
|
{% endblock %}
|
||||||
{% endif %}
|
|
||||||
</nav>
|
|
||||||
{% if messages %}
|
|
||||||
<div class="messages">
|
|
||||||
{% for message in messages %}
|
|
||||||
<p class="{{ message.level_tag }}">{{ message|safe }}</p>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="content">
|
|
||||||
{% block content %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
<p class="metadata">
|
|
||||||
{% include "articles/datetime_snippet.html" %}
|
|
||||||
{% include "articles/admin_link_snippet.html" %}
|
|
||||||
</p>
|
|
3
articles/templates/articles/snippets/admin_link.html
Normal file
3
articles/templates/articles/snippets/admin_link.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
| <a id="admin-link" href="{{ article.get_admin_url }}">Edit</a>
|
||||||
|
{% endif %}
|
121
articles/templates/articles/snippets/code_style.html
Normal file
121
articles/templates/articles/snippets/code_style.html
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
<style>
|
||||||
|
@media(prefers-color-scheme: light) {
|
||||||
|
.codehilite .hll { background-color: #ffffcc }
|
||||||
|
.codehilite { background: #ffffff; }
|
||||||
|
.codehilite .c { color: #aaaaaa; font-style: italic } /* Comment */
|
||||||
|
.codehilite .err { color: #FF0000; background-color: #FFAAAA } /* Error */
|
||||||
|
.codehilite .k { color: #0000aa } /* Keyword */
|
||||||
|
.codehilite .ch { color: #aaaaaa; font-style: italic } /* Comment.Hashbang */
|
||||||
|
.codehilite .cm { color: #aaaaaa; font-style: italic } /* Comment.Multiline */
|
||||||
|
.codehilite .cp { color: #4c8317 } /* Comment.Preproc */
|
||||||
|
.codehilite .cpf { color: #aaaaaa; font-style: italic } /* Comment.PreprocFile */
|
||||||
|
.codehilite .c1 { color: #aaaaaa; font-style: italic } /* Comment.Single */
|
||||||
|
.codehilite .cs { color: #0000aa; font-style: italic } /* Comment.Special */
|
||||||
|
.codehilite .gd { color: #aa0000 } /* Generic.Deleted */
|
||||||
|
.codehilite .ge { font-style: italic } /* Generic.Emph */
|
||||||
|
.codehilite .gr { color: #aa0000 } /* Generic.Error */
|
||||||
|
.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||||
|
.codehilite .gi { color: #00aa00 } /* Generic.Inserted */
|
||||||
|
.codehilite .go { color: #888888 } /* Generic.Output */
|
||||||
|
.codehilite .gp { color: #555555 } /* Generic.Prompt */
|
||||||
|
.codehilite .gs { font-weight: bold } /* Generic.Strong */
|
||||||
|
.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||||
|
.codehilite .gt { color: #aa0000 } /* Generic.Traceback */
|
||||||
|
.codehilite .kc { color: #0000aa } /* Keyword.Constant */
|
||||||
|
.codehilite .kd { color: #0000aa } /* Keyword.Declaration */
|
||||||
|
.codehilite .kn { color: #0000aa } /* Keyword.Namespace */
|
||||||
|
.codehilite .kp { color: #0000aa } /* Keyword.Pseudo */
|
||||||
|
.codehilite .kr { color: #0000aa } /* Keyword.Reserved */
|
||||||
|
.codehilite .kt { color: #00aaaa } /* Keyword.Type */
|
||||||
|
.codehilite .m { color: #009999 } /* Literal.Number */
|
||||||
|
.codehilite .s { color: #aa5500 } /* Literal.String */
|
||||||
|
.codehilite .na { color: #1e90ff } /* Name.Attribute */
|
||||||
|
.codehilite .nb { color: #00aaaa } /* Name.Builtin */
|
||||||
|
.codehilite .nc { color: #00aa00; text-decoration: underline } /* Name.Class */
|
||||||
|
.codehilite .no { color: #aa0000 } /* Name.Constant */
|
||||||
|
.codehilite .nd { color: #888888 } /* Name.Decorator */
|
||||||
|
.codehilite .ni { color: #880000; font-weight: bold } /* Name.Entity */
|
||||||
|
.codehilite .nf { color: #00aa00 } /* Name.Function */
|
||||||
|
.codehilite .nn { color: #00aaaa; text-decoration: underline } /* Name.Namespace */
|
||||||
|
.codehilite .nt { color: #1e90ff; font-weight: bold } /* Name.Tag */
|
||||||
|
.codehilite .nv { color: #aa0000 } /* Name.Variable */
|
||||||
|
.codehilite .ow { color: #0000aa } /* Operator.Word */
|
||||||
|
.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
|
||||||
|
.codehilite .mb { color: #009999 } /* Literal.Number.Bin */
|
||||||
|
.codehilite .mf { color: #009999 } /* Literal.Number.Float */
|
||||||
|
.codehilite .mh { color: #009999 } /* Literal.Number.Hex */
|
||||||
|
.codehilite .mi { color: #009999 } /* Literal.Number.Integer */
|
||||||
|
.codehilite .mo { color: #009999 } /* Literal.Number.Oct */
|
||||||
|
.codehilite .sa { color: #aa5500 } /* Literal.String.Affix */
|
||||||
|
.codehilite .sb { color: #aa5500 } /* Literal.String.Backtick */
|
||||||
|
.codehilite .sc { color: #aa5500 } /* Literal.String.Char */
|
||||||
|
.codehilite .dl { color: #aa5500 } /* Literal.String.Delimiter */
|
||||||
|
.codehilite .sd { color: #aa5500 } /* Literal.String.Doc */
|
||||||
|
.codehilite .s2 { color: #aa5500 } /* Literal.String.Double */
|
||||||
|
.codehilite .se { color: #aa5500 } /* Literal.String.Escape */
|
||||||
|
.codehilite .sh { color: #aa5500 } /* Literal.String.Heredoc */
|
||||||
|
.codehilite .si { color: #aa5500 } /* Literal.String.Interpol */
|
||||||
|
.codehilite .sx { color: #aa5500 } /* Literal.String.Other */
|
||||||
|
.codehilite .sr { color: #009999 } /* Literal.String.Regex */
|
||||||
|
.codehilite .s1 { color: #aa5500 } /* Literal.String.Single */
|
||||||
|
.codehilite .ss { color: #0000aa } /* Literal.String.Symbol */
|
||||||
|
.codehilite .bp { color: #00aaaa } /* Name.Builtin.Pseudo */
|
||||||
|
.codehilite .fm { color: #00aa00 } /* Name.Function.Magic */
|
||||||
|
.codehilite .vc { color: #aa0000 } /* Name.Variable.Class */
|
||||||
|
.codehilite .vg { color: #aa0000 } /* Name.Variable.Global */
|
||||||
|
.codehilite .vi { color: #aa0000 } /* Name.Variable.Instance */
|
||||||
|
.codehilite .vm { color: #aa0000 } /* Name.Variable.Magic */
|
||||||
|
.codehilite .il { color: #009999 } /* Literal.Number.Integer.Long */
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(prefers-color-scheme: dark) {
|
||||||
|
.codehilite .hll { background-color: #49483e }
|
||||||
|
.codehilite { background: #232629; color: #cccccc }
|
||||||
|
.codehilite .c { color: #777777; font-style: italic } /* Comment */
|
||||||
|
.codehilite .err { color: #a61717; background-color: #e3d2d2 } /* Error */
|
||||||
|
.codehilite .k { color: #7686bb; font-weight: bold } /* Keyword */
|
||||||
|
.codehilite .ch { color: #777777; font-style: italic } /* Comment.Hashbang */
|
||||||
|
.codehilite .cm { color: #777777; font-style: italic } /* Comment.Multiline */
|
||||||
|
.codehilite .cp { color: #777777; font-style: italic } /* Comment.Preproc */
|
||||||
|
.codehilite .cpf { color: #777777; font-style: italic } /* Comment.PreprocFile */
|
||||||
|
.codehilite .c1 { color: #777777; font-style: italic } /* Comment.Single */
|
||||||
|
.codehilite .cs { color: #777777; font-style: italic } /* Comment.Special */
|
||||||
|
.codehilite .gp { color: #ffffff } /* Generic.Prompt */
|
||||||
|
.codehilite .kc { color: #7686bb; font-weight: bold } /* Keyword.Constant */
|
||||||
|
.codehilite .kd { color: #7686bb; font-weight: bold } /* Keyword.Declaration */
|
||||||
|
.codehilite .kn { color: #7686bb; font-weight: bold } /* Keyword.Namespace */
|
||||||
|
.codehilite .kp { color: #7686bb; font-weight: bold } /* Keyword.Pseudo */
|
||||||
|
.codehilite .kr { color: #7686bb; font-weight: bold } /* Keyword.Reserved */
|
||||||
|
.codehilite .kt { color: #7686bb; font-weight: bold } /* Keyword.Type */
|
||||||
|
.codehilite .m { color: #4FB8CC } /* Literal.Number */
|
||||||
|
.codehilite .s { color: #51cc99 } /* Literal.String */
|
||||||
|
.codehilite .nf { color: #6a6aff } /* Name.Function */
|
||||||
|
.codehilite .nx { color: #e2828e } /* Name.Other */
|
||||||
|
.codehilite .nv { color: #7AB4DB; font-weight: bold } /* Name.Variable */
|
||||||
|
.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
|
||||||
|
.codehilite .mb { color: #4FB8CC } /* Literal.Number.Bin */
|
||||||
|
.codehilite .mf { color: #4FB8CC } /* Literal.Number.Float */
|
||||||
|
.codehilite .mh { color: #4FB8CC } /* Literal.Number.Hex */
|
||||||
|
.codehilite .mi { color: #4FB8CC } /* Literal.Number.Integer */
|
||||||
|
.codehilite .mo { color: #4FB8CC } /* Literal.Number.Oct */
|
||||||
|
.codehilite .sa { color: #51cc99 } /* Literal.String.Affix */
|
||||||
|
.codehilite .sb { color: #51cc99 } /* Literal.String.Backtick */
|
||||||
|
.codehilite .sc { color: #51cc99 } /* Literal.String.Char */
|
||||||
|
.codehilite .dl { color: #51cc99 } /* Literal.String.Delimiter */
|
||||||
|
.codehilite .sd { color: #51cc99 } /* Literal.String.Doc */
|
||||||
|
.codehilite .s2 { color: #51cc99 } /* Literal.String.Double */
|
||||||
|
.codehilite .se { color: #51cc99 } /* Literal.String.Escape */
|
||||||
|
.codehilite .sh { color: #51cc99 } /* Literal.String.Heredoc */
|
||||||
|
.codehilite .si { color: #51cc99 } /* Literal.String.Interpol */
|
||||||
|
.codehilite .sx { color: #51cc99 } /* Literal.String.Other */
|
||||||
|
.codehilite .sr { color: #51cc99 } /* Literal.String.Regex */
|
||||||
|
.codehilite .s1 { color: #51cc99 } /* Literal.String.Single */
|
||||||
|
.codehilite .ss { color: #51cc99 } /* Literal.String.Symbol */
|
||||||
|
.codehilite .fm { color: #6a6aff } /* Name.Function.Magic */
|
||||||
|
.codehilite .vc { color: #7AB4DB; font-weight: bold } /* Name.Variable.Class */
|
||||||
|
.codehilite .vg { color: #BE646C; font-weight: bold } /* Name.Variable.Global */
|
||||||
|
.codehilite .vi { color: #7AB4DB; font-weight: bold } /* Name.Variable.Instance */
|
||||||
|
.codehilite .vm { color: #7AB4DB; font-weight: bold } /* Name.Variable.Magic */
|
||||||
|
.codehilite .il { color: #4FB8CC } /* Literal.Number.Integer.Long */
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,3 +1,4 @@
|
||||||
|
{% load i18n %}
|
||||||
{% if article.published_at %}
|
{% if article.published_at %}
|
||||||
<time datetime="{{ article.published_at|date:CUSTOM_ISO }}">{{ article.published_at|date:ISO_DATE }}</time>
|
<time datetime="{{ article.published_at|date:CUSTOM_ISO }}">{{ article.published_at|date:ISO_DATE }}</time>
|
||||||
{% else %}
|
{% else %}
|
4
articles/templates/articles/snippets/metadata.html
Normal file
4
articles/templates/articles/snippets/metadata.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<p>
|
||||||
|
Published on {% include "articles/snippets/datetime.html" %}
|
||||||
|
{% include "articles/snippets/admin_link.html" %}
|
||||||
|
</p>
|
11
articles/templates/articles/snippets/page_metadata.html
Normal file
11
articles/templates/articles/snippets/page_metadata.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{% if article %}
|
||||||
|
<meta name="keywords" content="{{ article.keywords }}">
|
||||||
|
<meta name="description" content="{{ article.get_description }}">
|
||||||
|
<meta property="og:title" content="{{ article.title }}" />
|
||||||
|
<meta property="og:type" content="article">
|
||||||
|
<meta property="og:description" content="{{ article.get_description }}">
|
||||||
|
<meta property="og:site_name" content="{{ blog_title }}">
|
||||||
|
{% endif %}
|
||||||
|
{% if open_graph_image_url %}
|
||||||
|
<meta property="og:image" content="{{ open_graph_image_url }}">
|
||||||
|
{% endif %}
|
29
articles/templates/articles/snippets/style.html
Normal file
29
articles/templates/articles/snippets/style.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<style>
|
||||||
|
/* new.css 1.1.2 */
|
||||||
|
:root{--nc-font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--nc-font-mono:Consolas,monaco,'Ubuntu Mono','Liberation Mono','Courier New',Courier,monospace;--nc-tx-1:#000000;--nc-tx-2:#1A1A1A;--nc-bg-1:#FFFFFF;--nc-bg-2:#F6F8FA;--nc-bg-3:#E5E7EB;--nc-lk-1:#0070F3;--nc-lk-2:#0366D6;--nc-lk-tx:#FFFFFF;--nc-ac-1:#79FFE1;--nc-ac-tx:#0C4047}@media (prefers-color-scheme:dark){:root{--nc-tx-1:#ffffff;--nc-tx-2:#eeeeee;--nc-bg-1:#000000;--nc-bg-2:#111111;--nc-bg-3:#222222;--nc-lk-1:#3291FF;--nc-lk-2:#0070F3;--nc-lk-tx:#FFFFFF;--nc-ac-1:#7928CA;--nc-ac-tx:#FFFFFF}}*{margin:0;padding:0}address,area,article,aside,audio,blockquote,datalist,details,dl,fieldset,figure,form,iframe,img,input,meter,nav,ol,optgroup,option,output,p,pre,progress,ruby,section,table,textarea,ul,video{margin-bottom:1rem}button,html,input,select{font-family:var(--nc-font-sans)}body{margin:0 auto;max-width:750px;padding:2rem;border-radius:6px;overflow-x:hidden;word-break:break-word;overflow-wrap:break-word;background:var(--nc-bg-1);color:var(--nc-tx-2);font-size:1.03rem;line-height:1.5}::selection{background:var(--nc-ac-1);color:var(--nc-ac-tx)}h1,h2,h3,h4,h5,h6{line-height:1;color:var(--nc-tx-1);padding-top:.875rem}h1,h2,h3{color:var(--nc-tx-1);padding-bottom:2px;margin-bottom:8px;border-bottom:1px solid var(--nc-bg-2)}h4,h5,h6{margin-bottom:.3rem}h1{font-size:2.25rem}h2{font-size:1.85rem}h3{font-size:1.55rem}h4{font-size:1.25rem}h5{font-size:1rem}h6{font-size:.875rem}a{color:var(--nc-lk-1)}a:hover{color:var(--nc-lk-2)}abbr:hover{cursor:help}blockquote{padding:1.5rem;background:var(--nc-bg-2);border-left:5px solid var(--nc-bg-3)}abbr{cursor:help}blockquote :last-child{padding-bottom:0;margin-bottom:0}header{background:var(--nc-bg-2);border-bottom:1px solid var(--nc-bg-3);padding:2rem 1.5rem;margin:-2rem calc(0px - (50vw - 50%)) 2rem;padding-left:calc(50vw - 50%);padding-right:calc(50vw - 50%)}header h1,header h2,header h3{padding-bottom:0;border-bottom:0}header>:first-child{margin-top:0;padding-top:0}header>:last-child{margin-bottom:0}a button,button,input[type=button],input[type=reset],input[type=submit]{font-size:1rem;display:inline-block;padding:6px 12px;text-align:center;text-decoration:none;white-space:nowrap;background:var(--nc-lk-1);color:var(--nc-lk-tx);border:0;border-radius:4px;box-sizing:border-box;cursor:pointer;color:var(--nc-lk-tx)}a button[disabled],button[disabled],input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{cursor:default;opacity:.5;cursor:not-allowed}.button:focus,.button:hover,button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=reset]:focus,input[type=reset]:hover,input[type=submit]:focus,input[type=submit]:hover{background:var(--nc-lk-2)}code,kbd,pre,samp{font-family:var(--nc-font-mono)}code,kbd,pre,samp{background:var(--nc-bg-2);border:1px solid var(--nc-bg-3);border-radius:4px;padding:3px 6px;font-size:.9rem}kbd{border-bottom:3px solid var(--nc-bg-3)}pre{padding:1rem 1.4rem;max-width:100%;overflow:auto}pre code{background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}code pre{display:inline;background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}details{padding:.6rem 1rem;background:var(--nc-bg-2);border:1px solid var(--nc-bg-3);border-radius:4px}summary{cursor:pointer;font-weight:700}details[open]{padding-bottom:.75rem}details[open] summary{margin-bottom:6px}details[open]>:last-child{margin-bottom:0}dt{font-weight:700}dd::before{content:'→ '}hr{border:0;border-bottom:1px solid var(--nc-bg-3);margin:1rem auto}fieldset{margin-top:1rem;padding:2rem;border:1px solid var(--nc-bg-3);border-radius:4px}legend{padding:auto .5rem}table{border-collapse:collapse;width:100%}td,th{border:1px solid var(--nc-bg-3);text-align:left;padding:.5rem}th{background:var(--nc-bg-2)}tr:nth-child(even){background:var(--nc-bg-2)}table caption{font-weight:700;margin-bottom:.5rem}textarea{max-width:100%}ol,ul{padding-left:2rem}li{margin-top:.4rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}mark{padding:3px 6px;background:var(--nc-ac-1);color:var(--nc-ac-tx)}input,select,textarea{padding:6px 12px;margin-bottom:.5rem;background:var(--nc-bg-2);color:var(--nc-tx-2);border:1px solid var(--nc-bg-3);border-radius:4px;box-shadow:none;box-sizing:border-box}img{max-width:100%}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
max-width: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
border-top: 2px solid var(--nc-bg-2);
|
||||||
|
margin-top: 2em;
|
||||||
|
font-size: 80%;
|
||||||
|
color: var(--nc-tx-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer > :first-child {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill {
|
||||||
|
font-size: 60%;
|
||||||
|
background-color: var(--nc-tx-2);
|
||||||
|
color: var(--nc-bg-1);
|
||||||
|
padding: .5ex 1ex;
|
||||||
|
border-radius: 1ex;
|
||||||
|
vertical-align: 15%;
|
||||||
|
}
|
||||||
|
</style>
|
23
articles/templates/articles/snippets/style_home.html
Normal file
23
articles/templates/articles/snippets/style_home.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<style>
|
||||||
|
.pic-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pic-container .profile-pic {
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 100px;
|
||||||
|
max-height: 200px;
|
||||||
|
min-height: 100px;
|
||||||
|
border-radius: 10%;
|
||||||
|
padding: 1rem;
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,7 +1,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from articles.models import Article, Page, User
|
from articles.models import Article, User
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
|
@ -40,17 +40,3 @@ def unpublished_article(author: User) -> Article:
|
||||||
slug="some-draft-article-slug",
|
slug="some-draft-article-slug",
|
||||||
content="## some draft article markdown\n\n[a draft article link](https://article.com)",
|
content="## some draft article markdown\n\n[a draft article link](https://article.com)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def published_page(author: User) -> Page:
|
|
||||||
return Page.objects.create(
|
|
||||||
title="Some interesting page title",
|
|
||||||
status=Article.PUBLISHED,
|
|
||||||
author=author,
|
|
||||||
published_at=timezone.now(),
|
|
||||||
slug="some-page-slug",
|
|
||||||
content="## some page markdown\n\n[a page link](https://page.com)",
|
|
||||||
position=2,
|
|
||||||
)
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django.test import Client
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
|
|
||||||
from articles.models import Article, Page, User
|
from articles.models import Article, User
|
||||||
from articles.views.feeds import CompleteFeed
|
from articles.views.feeds import CompleteFeed
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,11 +22,3 @@ def test_feed_limits_number_of_articles(client: Client, author: User):
|
||||||
res = client.get(reverse("complete-feed"))
|
res = client.get(reverse("complete-feed"))
|
||||||
content = res.content.decode("utf-8")
|
content = res.content.decode("utf-8")
|
||||||
assert content.count("<item>") == CompleteFeed.FEED_LIMIT
|
assert content.count("<item>") == CompleteFeed.FEED_LIMIT
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_page_not_rendered_in_feed(client: Client, published_page: Page):
|
|
||||||
res = client.get(reverse("complete-feed"))
|
|
||||||
assert res.status_code == 200
|
|
||||||
content = res.content.decode("utf-8")
|
|
||||||
assert published_page.title not in content
|
|
||||||
|
|
|
@ -3,20 +3,16 @@ from django.test import Client
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
|
|
||||||
from articles.models import Article, Page, User
|
from articles.models import Article, User
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_can_access_list(
|
def test_can_access_list(client: Client, published_article: Article):
|
||||||
client: Client, published_article: Article, published_page: Page
|
|
||||||
):
|
|
||||||
res = client.get(reverse("articles-list"))
|
res = client.get(reverse("articles-list"))
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
content = res.content.decode("utf-8")
|
content = res.content.decode("utf-8")
|
||||||
for art in [published_article, published_page]:
|
assert published_article.title in content
|
||||||
assert art.title in content
|
|
||||||
assert published_article.get_abstract() not in content
|
assert published_article.get_abstract() not in content
|
||||||
assert published_page.get_formatted_content() not in content
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
@ -43,11 +39,6 @@ def test_access_article_by_slug(client: Client, published_article: Article):
|
||||||
_test_access_article_by_slug(client, published_article)
|
_test_access_article_by_slug(client, published_article)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_access_page_by_slug(client: Client, published_page: Page):
|
|
||||||
_test_access_article_by_slug(client, published_page)
|
|
||||||
|
|
||||||
|
|
||||||
def _test_access_article_by_slug(client: Client, item: Article):
|
def _test_access_article_by_slug(client: Client, item: Article):
|
||||||
res = client.get(reverse("article-detail", kwargs={"slug": item.slug}))
|
res = client.get(reverse("article-detail", kwargs={"slug": item.slug}))
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
|
@ -11,7 +11,7 @@ class CompleteFeed(Feed):
|
||||||
description = settings.BLOG["description"]
|
description = settings.BLOG["description"]
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
return Article.without_pages.filter(status=Article.PUBLISHED).order_by(
|
return Article.objects.filter(status=Article.PUBLISHED).order_by(
|
||||||
"-published_at"
|
"-published_at"
|
||||||
)[: self.FEED_LIMIT]
|
)[: self.FEED_LIMIT]
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
|
||||||
from articles.models import Article, Page
|
from articles.models import Article
|
||||||
|
|
||||||
|
|
||||||
class BaseArticleListView(generic.ListView):
|
class BaseArticleListView(generic.ListView):
|
||||||
|
@ -21,14 +19,14 @@ class BaseArticleListView(generic.ListView):
|
||||||
class ArticlesListView(BaseArticleListView):
|
class ArticlesListView(BaseArticleListView):
|
||||||
model = Article
|
model = Article
|
||||||
context_object_name = "articles"
|
context_object_name = "articles"
|
||||||
queryset = Article.without_pages.filter(status=Article.PUBLISHED)
|
queryset = Article.objects.filter(status=Article.PUBLISHED)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
index_page = Page.objects.filter(
|
home_article = Article.objects.filter(
|
||||||
status=Article.PUBLISHED, position=0
|
status=Article.PUBLISHED, is_home=True
|
||||||
).first() # type: Page
|
).first() # type: Article
|
||||||
context["article"] = index_page
|
context["article"] = home_article
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,10 +53,8 @@ class ArticleDetailView(generic.DetailView):
|
||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(status=Article.PUBLISHED)
|
return queryset.filter(status=Article.PUBLISHED)
|
||||||
|
|
||||||
def get_object(self, queryset=None) -> Union[Article, Page]:
|
def get_object(self, queryset=None) -> Article:
|
||||||
obj = super().get_object(queryset) # type: Article
|
obj = super().get_object(queryset) # type: Article
|
||||||
if hasattr(obj, "page"):
|
|
||||||
obj = obj.page # type: Page
|
|
||||||
if not self.request.user.is_authenticated:
|
if not self.request.user.is_authenticated:
|
||||||
obj.views_count = F("views_count") + 1
|
obj.views_count = F("views_count") + 1
|
||||||
obj.save(update_fields=["views_count"])
|
obj.save(update_fields=["views_count"])
|
||||||
|
|
|
@ -96,12 +96,12 @@ TEMPLATES = [
|
||||||
"django.template.context_processors.request",
|
"django.template.context_processors.request",
|
||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
"articles.context_processors.pages",
|
|
||||||
"articles.context_processors.drafts_count",
|
"articles.context_processors.drafts_count",
|
||||||
"articles.context_processors.date_format",
|
"articles.context_processors.date_format",
|
||||||
"articles.context_processors.git_version",
|
"articles.context_processors.git_version",
|
||||||
"articles.context_processors.plausible",
|
"articles.context_processors.plausible",
|
||||||
"articles.context_processors.open_graph_image_url",
|
"articles.context_processors.open_graph_image_url",
|
||||||
|
"articles.context_processors.blog_metadata",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -176,6 +176,7 @@ AUTH_USER_MODEL = "articles.User"
|
||||||
|
|
||||||
BLOG = {
|
BLOG = {
|
||||||
"title": "Gab's Notes",
|
"title": "Gab's Notes",
|
||||||
|
"author": "Gabriel Augendre",
|
||||||
"description": "My take on tech-related subjects (but not only).",
|
"description": "My take on tech-related subjects (but not only).",
|
||||||
"base_url": os.getenv("BLOG_BASE_URL", "https://gabnotes.org/"),
|
"base_url": os.getenv("BLOG_BASE_URL", "https://gabnotes.org/"),
|
||||||
"repo": {
|
"repo": {
|
||||||
|
|
Reference in a new issue