Compare commits

..

27 commits

Author SHA1 Message Date
36e46f8cfd
feat: allow publishing posts without indexing them 2025-05-21 10:50:55 +02:00
38d59de6c0
fix: unreadable on mobile 2025-05-18 18:27:02 +02:00
c42bfbe72d
chore: update dependencies 2025-05-18 18:26:24 +02:00
96633340cd
feat: style changes for print previews 2025-05-15 12:33:55 +02:00
c4a83cf5be
update portfolio sections 2025-05-15 11:54:27 +02:00
9d5f6bc5fb
fix: tags layout and overflow 2025-05-06 19:34:10 +02:00
00c7db9bec
chore: upgrade seo plugin 2025-05-06 19:08:24 +02:00
2a2c59d2b8
feat: upgrade wagtail, tags fixed classes 2025-05-06 18:50:52 +02:00
39f2433824
feat: update mastodon instance 2025-04-11 15:32:30 +02:00
edb7a185d8
upgrade wagtail 2025-03-16 18:08:43 +01:00
762d800ce4
add newsletter tag 2025-02-13 11:01:07 +01:00
e7cab9534e
Improve search look and feel 2025-02-13 10:53:55 +01:00
41508948c6
upgrade wagtail 2025-02-13 10:41:17 +01:00
f8f7021198
add newsletter anchor 2025-02-13 10:39:17 +01:00
21121e77ad
fix: height 2024-12-16 12:06:21 +01:00
3659cf902a
chore: bump version, 1.8.0 2024-12-16 12:01:26 +01:00
e9e5e7d30b
feat: portfolio styles 2024-12-16 12:00:33 +01:00
666fe03f2d
chore: update Django 2024-12-16 11:45:40 +01:00
7d29024865
version 1.7.0 2024-12-01 14:41:16 +01:00
6d7dd1f2af
fix: accessibility 2024-12-01 14:40:58 +01:00
6e63965602
fix: stop auto-tree updates for i18n, pip updates 2024-12-01 14:33:46 +01:00
9db1c5a8fe
update gitignore 2024-12-01 14:22:11 +01:00
7b42bb92c1
delete accidentally commited images 2024-12-01 14:21:09 +01:00
917695a1da
chore: updates to the content API (for use in CIBUS) 2024-11-30 12:25:02 +01:00
1b3a2dec72
feat: update wagtail to 6.3.1 2024-11-23 15:00:00 +01:00
310fd5ed7a
feat: enable translations and rest api 2024-11-23 14:59:51 +01:00
e98db55a16
feat: enable translations 2024-11-23 14:26:14 +01:00
46 changed files with 480 additions and 60 deletions

5
.gitignore vendored
View file

@ -165,4 +165,7 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
.env.production .env.production
media/images/
media/original_images/

View file

@ -17,7 +17,7 @@
<excludeFolder url="file://$MODULE_DIR$/env" /> <excludeFolder url="file://$MODULE_DIR$/env" />
<excludeFolder url="file://$MODULE_DIR$/venv" /> <excludeFolder url="file://$MODULE_DIR$/venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.13 (iamkonstantin-web)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.12 virtualenv at ~/Developer/personal/iamkonstantin-web/venv" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="PyDocumentationSettings"> <component name="PyDocumentationSettings">

2
.idea/misc.xml generated
View file

@ -3,5 +3,5 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="Python 3.12 (iamkonstantin-web)" /> <option name="sdkName" value="Python 3.12 (iamkonstantin-web)" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (iamkonstantin-web)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 virtualenv at ~/Developer/personal/iamkonstantin-web/venv" project-jdk-type="Python SDK" />
</project> </project>

1
.tool-versions Normal file
View file

@ -0,0 +1 @@
python 3.12.8

View file

@ -1,12 +1,9 @@
.PHONY: help build publish .PHONY: help bump publish
VERSION = 1.5.0 VERSION = 1.9.15
help: help:
@perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
build:
@docker build -t code.headbright.be/konstantin/iamkonstantin:$(VERSION) .
publish: publish:
# something to try: --provenance=false # something to try: --provenance=false
@docker buildx build -t code.headbright.be/konstantin/iamkonstantin:$(VERSION) --platform linux/arm64 --push . @docker buildx build -t code.headbright.be/konstantin/iamkonstantin:$(VERSION) --platform linux/arm64 --push .

View file

@ -1,3 +1,4 @@
from wagtail.api import APIField
from wagtail.blocks import ( from wagtail.blocks import (
CharBlock, CharBlock,
ChoiceBlock, ChoiceBlock,
@ -14,6 +15,13 @@ class ImageBlock(StructBlock):
caption = CharBlock(required=False) caption = CharBlock(required=False)
attribution = CharBlock(required=False) attribution = CharBlock(required=False)
api_fields = [
APIField('image'),
# Adds a URL to a rendered thumbnail of the image to the API
APIField('caption'),
APIField('attribution'),
]
class Meta: class Meta:
icon = "image" icon = "image"
template = "base/blocks/image_block.html" template = "base/blocks/image_block.html"

View file

@ -5,6 +5,8 @@ from urllib.parse import urlparse
from base.indexnow import get_key from base.indexnow import get_key
import requests import requests
from blog.models import BlogPage
@hooks.register('after_publish_page') @hooks.register('after_publish_page')
def after_publish_page(request, page): def after_publish_page(request, page):
@ -14,6 +16,9 @@ def after_publish_page(request, page):
if urlparse(page_url).hostname == "localhost": if urlparse(page_url).hostname == "localhost":
print("not notifying indexnow for localhost" + get_key() + ", page url: " + page_url) print("not notifying indexnow for localhost" + get_key() + ", page url: " + page_url)
return return
if urlparse(page_url).path.endswith("--priv") or urlparse(page_url).path.endswith("--priv/"):
print("not notifying indexnow for blog page --priv")
return
session = requests.Session() session = requests.Session()
session.post( session.post(
"https://api.indexnow.org/indexnow", "https://api.indexnow.org/indexnow",

View file

@ -0,0 +1,218 @@
# Generated by Django 5.1.6 on 2025-05-06 17:00
import django.db.models.deletion
from django.db import migrations, models
from home.models import HomePage
STRUCT_ORG_FIELDS = [
"struct_org_type",
"struct_org_name",
"struct_org_logo_id",
"struct_org_image_id",
"struct_org_phone",
"struct_org_address_street",
"struct_org_address_locality",
"struct_org_address_region",
"struct_org_address_postal",
"struct_org_address_country",
"struct_org_geo_lat",
"struct_org_geo_lng",
"struct_org_hours",
"struct_org_actions",
"struct_org_extra_json", ]
# put here every model names of yours that could have been filled with structured seo data;
# order will matter when searching for pages data
SEO_MODELS = [HomePage]
def fill_settings_from_pages_struct_org(apps, schema_editor):
"""
Search for pages where seo struct info was filled and use that to
fill new settings struct data
"""
SeoSettings = apps.get_model("wagtailseo", "SeoSettings")
Site = apps.get_model("wagtailcore", "Site")
for site in Site.objects.all().select_related("root_page"):
for model_name in SEO_MODELS:
model = apps.get_model("home", model_name)
page = model.objects.filter(
path__startswith=site.root_page.path,
depth__gte=site.root_page.depth
).order_by(
'path'
).exclude(struct_org_name__exact="").first()
# if you are sure that only root pages were used to fill structured data,
# you can directly use:
# page = site.root_page.specific if site.root_page.specific._meta.model_name in SEO_MODELS else None
if page is not None:
seo_settings, _ = SeoSettings.objects.get_or_create(site=site)
for field in STRUCT_ORG_FIELDS:
setattr(seo_settings, field, getattr(page, field))
seo_settings.save()
break
def fill_pages_from_settings_struct_org(apps, schema_editor):
"""
The reverse migration.
For every site, find the most top-level page inheriting from SeoMixin
and fill its struct information using the site's settings
"""
SeoSettings = apps.get_model("wagtailseo", "SeoSettings")
for seo_settings in SeoSettings.objects.all().select_related("site", "site__root_page"):
for model_name in SEO_MODELS:
model = apps.get_model("home", model_name)
page = model.objects.filter(
path__startswith=seo_settings.site.root_page.path,
depth__gte=seo_settings.site.root_page.depth
).order_by('path').first()
if page is not None:
for field in STRUCT_ORG_FIELDS:
setattr(page, field, getattr(seo_settings, field))
page.save()
break
class Migration(migrations.Migration):
dependencies = [
('blog', '0014_alter_blogpage_body'),
('wagtailimages', '0027_image_description'),
("wagtailseo", "0003_seosettings_struct_org_fields"),
]
operations = [
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_actions',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_address_country',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_address_locality',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_address_postal',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_address_region',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_address_street',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_extra_json',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_geo_lat',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_geo_lng',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_hours',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_image',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_logo',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_name',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_phone',
),
migrations.RemoveField(
model_name='blogindexpage',
name='struct_org_type',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_actions',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_address_country',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_address_locality',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_address_postal',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_address_region',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_address_street',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_extra_json',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_geo_lat',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_geo_lng',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_hours',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_image',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_logo',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_name',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_phone',
),
migrations.RemoveField(
model_name='blogpage',
name='struct_org_type',
),
migrations.AlterField(
model_name='blogindexpage',
name='og_image',
field=models.ForeignKey(blank=True, help_text='Shown when linking to this page on social media. If blank, may show an image from the page, or the default from Settings > SEO.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image', verbose_name='Preview image'),
),
migrations.AlterField(
model_name='blogpage',
name='og_image',
field=models.ForeignKey(blank=True, help_text='Shown when linking to this page on social media. If blank, may show an image from the page, or the default from Settings > SEO.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image', verbose_name='Preview image'),
),
]

View file

@ -5,6 +5,8 @@ from django.db import models
from modelcluster.fields import ParentalKey, ParentalManyToManyField from modelcluster.fields import ParentalKey, ParentalManyToManyField
from modelcluster.contrib.taggit import ClusterTaggableManager from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase from taggit.models import TaggedItemBase
from wagtail.api import APIField
from wagtail.images.api.fields import ImageRenditionField
from wagtail.models import Page, Orderable from wagtail.models import Page, Orderable
from wagtail.fields import RichTextField, StreamField from wagtail.fields import RichTextField, StreamField
@ -74,6 +76,14 @@ class BlogPage(SeoMixin, Page):
else: else:
return None return None
# Export fields over the API
api_fields = [
APIField('gallery_images'),
# Adds a URL to a rendered thumbnail of the image to the API
APIField('body'),
APIField('intro'),
APIField('tags'),
]
search_fields = Page.search_fields + [ search_fields = Page.search_fields + [
index.SearchField('intro'), index.SearchField('intro'),
index.SearchField('body'), index.SearchField('body'),
@ -122,6 +132,9 @@ class BlogPageGalleryImage(Orderable):
FieldPanel('image'), FieldPanel('image'),
FieldPanel('caption'), FieldPanel('caption'),
] ]
api_fields = [
APIField('image'),
]
@register_snippet @register_snippet

View file

@ -5,7 +5,7 @@
{% block body_class %}template-blogpage{% endblock %} {% block body_class %}template-blogpage{% endblock %}
{% block content %} {% block content %}
<section class="flex flex-col items-center justify-center h-full px-0 md:px-4 lg:px-8"> <section class="flex flex-col justify-center h-full px-0 md:px-4 lg:px-8">
<article class="mb-16 px-1 md:px-4 lg:px-8"> <article class="mb-16 px-1 md:px-4 lg:px-8">
<h1>{{ page.title }}</h1> <h1>{{ page.title }}</h1>
@ -54,12 +54,12 @@
{% with tags=page.tags.all %} {% with tags=page.tags.all %}
{% if tags %} {% if tags %}
<div class="flex items-center gap-x-4 text-xs my-6"> <div class="flex items-center justify-center gap-x-4 text-xs my-6">
<div class="tags"> <div class="tags">
<h2 class="sr-only">Tags</h2> <h2 class="sr-only">Tags</h2>
<ul class="flex"> <ul class="flex">
{% for tag in tags %} {% for tag in tags %}
<li class="space-x-2"><span class="emoji">🏷</span> <li><span class="emoji">🏷</span>
<a class="pr-2" href="{% slugurl 'tags' %}?tag={{ tag }}">{{ tag }}</a> <a class="pr-2" href="{% slugurl 'tags' %}?tag={{ tag }}">{{ tag }}</a>
</li> </li>
{% endfor %} {% endfor %}
@ -74,13 +74,9 @@
</article> </article>
<nav class="w-full text-center px-2 lg:px-4"> <nav class="w-full text-center px-2 lg:px-4 return">
<a href="{{ page.get_parent.url }}" class="font-bold">Return to blog</a> <a href="{{ page.get_parent.url }}" class="font-bold">Return to blog</a>
</nav> </nav>
</section> </section>
<section>
{% include 'newsletter/snippets/signup_form.html' %}
</section>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,80 @@
# Generated by Django 5.1.6 on 2025-05-06 17:00
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0006_alter_homepage_body'),
('wagtailimages', '0027_image_description'),
]
operations = [
migrations.RemoveField(
model_name='homepage',
name='struct_org_actions',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_address_country',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_address_locality',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_address_postal',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_address_region',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_address_street',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_extra_json',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_geo_lat',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_geo_lng',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_hours',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_image',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_logo',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_name',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_phone',
),
migrations.RemoveField(
model_name='homepage',
name='struct_org_type',
),
migrations.AlterField(
model_name='homepage',
name='og_image',
field=models.ForeignKey(blank=True, help_text='Shown when linking to this page on social media. If blank, may show an image from the page, or the default from Settings > SEO.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image', verbose_name='Preview image'),
),
]

View file

@ -9,13 +9,13 @@
> >
<figure class="w-1/2" style="min-width: 24rem"> <figure class="w-1/2" style="min-width: 24rem">
<figcaption class="sr-only"> <figcaption class="sr-only">
Drawing of my computer desk where we see a computer with an open Terminal Hero image
</figcaption>
<img src="/static/images/my-office.png" alt="Drawing of my computer desk where we see a computer with an open Terminal
with Elixir code snippets. On the shelf behind we can see programming with Elixir code snippets. On the shelf behind we can see programming
books for Rust, Elixir and JavaScript. The books are arranges vertically books for Rust, Elixir and JavaScript. The books are arranges vertically
next to a small flower pot. The flower has big green leaves. A cup of next to a small flower pot. The flower has big green leaves. A cup of
steaming coffee sits on the desk, to the left of the computer. The cup has a Python logo on it. steaming coffee sits on the desk, to the left of the computer. The cup has a Python logo on it." />
</figcaption>
<img src="/static/images/my-office.png" />
</figure> </figure>
<section class="w-100 text-lg dark:text-white mx-2"> <section class="w-100 text-lg dark:text-white mx-2">
<h1 <h1
@ -25,8 +25,8 @@
><img ><img
width="48px" width="48px"
height="48px" height="48px"
src="static/images/waving-hand-sign_1f44b.png" /></span alt="waving hand emoji"
>Hi! I'm Konstantin src="static/images/waving-hand-sign_1f44b.png" /></span>Hi! I'm Konstantin
</h1> </h1>
</section> </section>
@ -50,7 +50,7 @@
<ul class="mx-auto mt-8 grid max-w-2xl grid-cols-1 gap-x-8 gap-y-20 lg:mx-0 lg:max-w-none lg:grid-cols-3"> <ul class="mx-auto mt-8 grid max-w-2xl grid-cols-1 gap-x-8 gap-y-20 lg:mx-0 lg:max-w-none lg:grid-cols-3">
{% for post in recent_blog_items %} {% for post in recent_blog_items %}
{% with post=post.specific %} {% with post=post.specific %}
<article class="flex flex-col items-center justify-start max-w-xl"> <li class="flex flex-col items-center justify-start max-w-xl">
<div class="group relative w-full"> <div class="group relative w-full">
<h3 class=""> <h3 class="">
<a href="{% pageurl post %}"> <a href="{% pageurl post %}">
@ -60,6 +60,7 @@
</h3> </h3>
<p class="mt-5 leading-6">{{ post.intro }}</p> <p class="mt-5 leading-6">{{ post.intro }}</p>
</div> </div>
{% if post.main_image %} {% if post.main_image %}
<div class="relative mx-auto"> <div class="relative mx-auto">
{% with post.main_image as main_image %} {% with post.main_image as main_image %}
@ -69,7 +70,7 @@
<div class="absolute inset-0 rounded-2xl ring-1 ring-inset ring-gray-900/10"></div> <div class="absolute inset-0 rounded-2xl ring-1 ring-inset ring-gray-900/10"></div>
</div> </div>
{% endif %} {% endif %}
</article> </li>
{% endwith %} {% endwith %}
{% endfor %} {% endfor %}
</ul> </ul>
@ -81,7 +82,4 @@
</div> </div>
</section> </section>
<section>
{% include 'newsletter/snippets/signup_form.html' %}
</section>
{% endblock content %} {% endblock content %}

15
iamkonstantin_web/api.py Normal file
View file

@ -0,0 +1,15 @@
from wagtail.api.v2.views import PagesAPIViewSet
from wagtail.api.v2.router import WagtailAPIRouter
from wagtail.images.api.v2.views import ImagesAPIViewSet
from wagtail.documents.api.v2.views import DocumentsAPIViewSet
# Create the router. "wagtailapi" is the URL namespace
api_router = WagtailAPIRouter('wagtailapi')
# Add the three endpoints using the "register_endpoint" method.
# The first parameter is the name of the endpoint (such as pages, images). This
# is used in the URL of the endpoint
# The second parameter is the endpoint class that handles the requests
api_router.register_endpoint('pages', PagesAPIViewSet)
api_router.register_endpoint('images', ImagesAPIViewSet)
api_router.register_endpoint('documents', DocumentsAPIViewSet)

View file

@ -35,6 +35,7 @@ INSTALLED_APPS = [
"wagtail.contrib.settings", "wagtail.contrib.settings",
"wagtail.contrib.forms", "wagtail.contrib.forms",
"wagtail.contrib.redirects", "wagtail.contrib.redirects",
"wagtail.contrib.simple_translation",
"wagtail.embeds", "wagtail.embeds",
"wagtail.sites", "wagtail.sites",
"wagtail.users", "wagtail.users",
@ -43,6 +44,9 @@ INSTALLED_APPS = [
"wagtail.images", "wagtail.images",
"wagtail.search", "wagtail.search",
"wagtail.admin", "wagtail.admin",
"wagtail.locales",
"wagtail.api.v2",
"rest_framework",
"wagtail", "wagtail",
"modelcluster", "modelcluster",
"taggit", "taggit",
@ -68,6 +72,7 @@ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
"wagtail.contrib.redirects.middleware.RedirectMiddleware", "wagtail.contrib.redirects.middleware.RedirectMiddleware",
"blog.middleware.BlogRedirectMiddleware", "blog.middleware.BlogRedirectMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django_browser_reload.middleware.BrowserReloadMiddleware" "django_browser_reload.middleware.BrowserReloadMiddleware"
] ]
@ -128,7 +133,7 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/ # https://docs.djangoproject.com/en/5.0/topics/i18n/
LANGUAGE_CODE = "en-us" LANGUAGE_CODE = "en"
TIME_ZONE = "UTC" TIME_ZONE = "UTC"
@ -136,6 +141,18 @@ USE_I18N = True
USE_TZ = True USE_TZ = True
WAGTAIL_I18N_ENABLED = True
USE_L10N = True # allows dates to be shown in the user's locale
WAGTAIL_CONTENT_LANGUAGES = LANGUAGES = [
('en', "English"),
('fr', "French"),
('es', "Spanish"),
('nl', "Dutch"),
]
WAGTAILSIMPLETRANSLATION_SYNC_PAGE_TREE = False
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/ # https://docs.djangoproject.com/en/5.0/howto/static-files/
@ -186,6 +203,9 @@ WAGTAILSEARCH_BACKENDS = {
# Base URL to use when referring to full URLs within the Wagtail admin backend - # Base URL to use when referring to full URLs within the Wagtail admin backend -
# e.g. in notification emails. Don't include '/admin' or a trailing slash # e.g. in notification emails. Don't include '/admin' or a trailing slash
WAGTAILADMIN_BASE_URL = "https://iamkonstantin.eu" WAGTAILADMIN_BASE_URL = "https://iamkonstantin.eu"
WAGTAILAPI_BASE_URL = "https://iamkonstantin.eu"
WAGTAILAPI_SEARCH_ENABLED = True
WAGTAIL_CODE_BLOCK_LANGUAGES = ( WAGTAIL_CODE_BLOCK_LANGUAGES = (
('bash', 'Bash/Shell'), ('bash', 'Bash/Shell'),

View file

@ -4,7 +4,8 @@
<html lang="en" class="h-full antialiased"> <html lang="en" class="h-full antialiased">
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta name="fediverse:creator" content="@konstantin@toot.iamkonstantin.eu" /> <meta name="viewport" content="width=device-width">
<meta name="fediverse:creator" content="@iamkonstantin@mastodon.social" />
{% include "wagtailseo/meta.html" %} {% include "wagtailseo/meta.html" %}
<title> <title>
{% block title %} {% block title %}
@ -80,5 +81,6 @@
{# Override this in templates to add extra javascript #} {# Override this in templates to add extra javascript #}
{% endblock %} {% endblock %}
{% include "wagtailseo/struct_data.html" %} {% include "wagtailseo/struct_data.html" %}
{% include "wagtailseo/struct_org_data.html" %}
</body> </body>
</html> </html>

View file

@ -3,7 +3,7 @@
<a href="#main" class="skip-link">Skip to content</a> <a href="#main" class="skip-link">Skip to content</a>
{% get_site_root as site_root %} {% get_site_root as site_root %}
<nav class="w-full flex justify-center my-8"> <nav class="w-full flex justify-center my-8 main">
<ul class="flex space-x-8"> <ul class="flex space-x-8">
<li><a href="{% pageurl site_root %}">{{ site_root.title }}</a></li> <li><a href="{% pageurl site_root %}">{{ site_root.title }}</a></li>
{% for menuitem in site_root.get_children.live.in_menu %} {% for menuitem in site_root.get_children.live.in_menu %}

View file

@ -1,4 +1,5 @@
from django.conf import settings from django.conf import settings
from django.conf.urls.i18n import i18n_patterns
from django.urls import include, path from django.urls import include, path
from django.contrib import admin from django.contrib import admin
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
@ -8,6 +9,7 @@ from wagtail import urls as wagtail_urls
from wagtail.documents import urls as wagtaildocs_urls from wagtail.documents import urls as wagtaildocs_urls
from blog.feeds import RssBlogFeed from blog.feeds import RssBlogFeed
from iamkonstantin_web.api import api_router
from newsletter import views as newsletter_views from newsletter import views as newsletter_views
from search import views as search_views from search import views as search_views
from wagtail.contrib.sitemaps.views import sitemap from wagtail.contrib.sitemaps.views import sitemap
@ -25,6 +27,17 @@ urlpatterns = [
path('newsletter/thanks', newsletter_views.thanks, name='thanks') path('newsletter/thanks', newsletter_views.thanks, name='thanks')
] ]
urlpatterns += [
path('api/v2/', api_router.urls),
]
# Translatable URLs
# These will be available under a language code prefix. For example /en/search/
urlpatterns += i18n_patterns(
path("", include(wagtail_urls)),
prefix_default_language=False,
)
if settings.DEBUG: if settings.DEBUG:
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.contrib.staticfiles.urls import staticfiles_urlpatterns
@ -34,12 +47,12 @@ if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += path("__reload__/", include("django_browser_reload.urls")), urlpatterns += path("__reload__/", include("django_browser_reload.urls")),
urlpatterns = urlpatterns + [ # urlpatterns = urlpatterns + [
# For anything not caught by a more specific rule above, hand over to # # For anything not caught by a more specific rule above, hand over to
# Wagtail's page serving mechanism. This should be the last pattern in # # Wagtail's page serving mechanism. This should be the last pattern in
# the list: # # the list:
path("", include(wagtail_urls)), # path("", include(wagtail_urls)),
# Alternatively, if you want Wagtail pages to be served from a subpath # # Alternatively, if you want Wagtail pages to be served from a subpath
# of your site, rather than the site root: # # of your site, rather than the site root:
# path("pages/", include(wagtail_urls)), # # path("pages/", include(wagtail_urls)),
] # ]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

View file

@ -1,4 +1,4 @@
<div class="py-16 sm:py-24 lg:py-32"> <div class="py-16 sm:py-24 lg:py-32">
<iframe data-w-type="embedded" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://sgx7y.mjt.lu/wgt/sgx7y/xtn1/form?c=c9b8015e" width="100%" style="height: 0;"></iframe> <iframe data-w-type="embedded" title="Newsletter" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://sgx7y.mjt.lu/wgt/sgx7y/xtn1/form?c=c9b8015e" width="100%" style="height: 0;"></iframe>
<script type="text/javascript" src="https://app.mailjet.com/pas-nc-embedded-v1.js"></script> <script type="text/javascript" src="https://app.mailjet.com/pas-nc-embedded-v1.js"></script>
</div> </div>

View file

@ -4,8 +4,14 @@
{% block body_class %}template-portfolio{% endblock %} {% block body_class %}template-portfolio{% endblock %}
{% block content %}
<h1>{{ page.title }}</h1>
{{ page.body }} {% block content %}
<section class="flex flex-col items-center justify-center h-full px-0 md:px-4 lg:px-8">
<article class="mb-16 px-1 md:px-4 lg:px-8">
<h1>{{ page.title }}</h1>
<div class="blog-content w-full">
{{ page.body }}
</div>
</article>
</section>
{% endblock %} {% endblock %}

View file

@ -1,8 +1,8 @@
Django>=4.2,<5.2 Django>=5.2.1,<5.3
wagtail>=6.3,<6.4 wagtail>=6.4,<7.1
whitenoise>=6.6,<7.0 whitenoise>=6.6,<7.0
wagtailcodeblock>=1.29.0.2,<2.0 wagtailcodeblock>=1.29.0.2,<2.0
django-tailwind>=3.6.0 django-tailwind>=3.6.0
django-browser-reload>=1.12 django-browser-reload>=1.17
Wand==0.6.13 Wand==0.6.13
wagtail-seo==2.5.0 wagtail-seo==3.0.0

View file

@ -13,13 +13,11 @@
<form action="{% url 'search' %}" method="get" class="container"> <form action="{% url 'search' %}" method="get" class="container">
<div class="flex flex-col space-y-4"> <div class="flex flex-col space-y-4">
<label for="query" class="sr-only block text-sm font-medium leading-6">Search</label> <label for="query" class="sr-only block text-sm font-medium leading-6">Search</label>
<div class="mt-2"> <div class="mt-2 flex space-x-4">
<input type="text" placeholder="Type search keywords..." <input type="text" placeholder="Type search keywords..."
class="block w-full rounded-md border-0 py-1.5 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-klavender sm:text-sm sm:leading-6" class="block w-full rounded-xl border-0 py-1.5 px-2 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-klavender sm:text-sm sm:leading-6"
id="query" name="query"{% if search_query %} value="{{ search_query }}"{% endif %}> id="query" name="query"{% if search_query %} value="{{ search_query }}"{% endif %}>
</div>
<div>
<input type="submit" class="primary-button" value="Search"> <input type="submit" class="primary-button" value="Search">
</div> </div>
</div> </div>
@ -30,7 +28,7 @@
</div> </div>
</section> </section>
<section class="h-full px-0 md:px-4 lg:px-8"> <section class="h-full px-0 md:px-4 lg:px-8 search">
<div class="px-0 md:px-4 lg:px-8"> <div class="px-0 md:px-4 lg:px-8">
<h2 class="sr-only">Search results</h2> <h2 class="sr-only">Search results</h2>
@ -48,6 +46,8 @@
<h4><a href="{% pageurl result %}">{{ result }}</a></h4> <h4><a href="{% pageurl result %}">{{ result }}</a></h4>
{% if result.search_description %} {% if result.search_description %}
{{ result.search_description }} {{ result.search_description }}
{% elif result.post.intro %}
{{ result.post.intro }}
{% endif %} {% endif %}
</li> </li>
{% endfor %} {% endfor %}

View file

@ -45,7 +45,7 @@
} }
h3 { h3 {
@apply mt-12 mb-2 text-xl sm:text-2xl lg:text-2xl leading-none font-extrabold tracking-tight text-black dark:text-white; @apply mt-10 mb-3 text-xl sm:text-2xl lg:text-2xl leading-none font-extrabold tracking-tight text-black dark:text-white;
} }
h4 { h4 {
@ -56,11 +56,23 @@
@apply my-2 mb-3 text-lg leading-relaxed; @apply my-2 mb-3 text-lg leading-relaxed;
} }
li {
@apply my-2 text-lg;
}
.search li {
@apply my-6;
}
.search li h4 {
@apply inline;
}
.blog-content ul, .home-content ul { .blog-content ul, .home-content ul {
@apply list-none list-inside; @apply list-none list-inside;
} }
.blog-content ul li::before, .home-content ul li::before { .blog-content ul li::before, .home-content ul li::before, .search li::before {
content: "👉"; content: "👉";
font-family: NotoEmoji; font-family: NotoEmoji;
@apply inline-block mr-2 py-1; @apply inline-block mr-2 py-1;
@ -77,24 +89,57 @@
@media print { @media print {
h1 { h1 {
@apply mt-6 mb-4 text-xl; @apply m-0 my-2 text-xl;
} }
h2 { h2 {
@apply mt-4 mb-2 text-base; @apply m-0 mt-2 text-base;
} }
h3 { h3 {
@apply text-base; @apply m-0 mt-2 text-base;
}
h4 {
@apply m-0 text-base;
} }
p { p {
@apply text-sm my-1; @apply text-sm my-1;
} }
li {
@apply text-sm my-0;
}
.ppb { .ppb {
page-break-before: always; page-break-before: always;
} }
nav.main {
@apply hidden;
}
main {
@apply mx-0 px-0;
}
footer {
@apply hidden !important;
}
nav.return {
@apply hidden;
}
/*.blog-content ul, .home-content ul {*/
/* @apply list-none list-inside list-disc;*/
/* @apply inline-block mr-0;*/
/*}*/
/*.blog-content ul li::before, .home-content ul li::before, .search li::before {*/
/* content: "";*/
/*}*/
} }
.primary-button { .primary-button {
@ -112,11 +157,11 @@
} }
.tags ul { .tags ul {
@apply flex space-x-4 text-center text-sm; @apply flex-col sm:flex-row sm:space-x-2 text-center text-sm;
} }
.tags li { .tags li {
@apply p-4 inline-flex items-center rounded-2xl bg-green-50 px-2 py-1 font-medium text-green-700 ring-1 ring-inset ring-green-600/20; @apply p-3 inline-flex items-center rounded-2xl px-2 py-1 text-sm text-black dark:text-white ring-1 ring-inset ring-orange-700/20 dark:ring-white bg-orange-50 dark:bg-transparent;
} }
.tags ul li::before { .tags ul li::before {
@ -125,7 +170,7 @@
} }
.tags a { .tags a {
@apply border-b-0 w-full; @apply border-b-0 w-full m-0;
} }
.blog-pages a { .blog-pages a {