Compare commits

...

3 Commits

Author SHA1 Message Date
Ryan Rix 19439a4b4d update arroyo_rs 2023-12-29 21:41:48 -08:00
Ryan Rix 7aefd9450a per-domain dynamic site theming
basically cribbed from the old fastapi site, and this all needs to be
cleaned up and organized better but it works. a bit more subtle this time.
2023-12-29 21:40:52 -08:00
Ryan Rix 59a207e366 move admin section in to models 2023-12-29 21:40:02 -08:00
19 changed files with 1038 additions and 60 deletions

View File

@ -9,7 +9,7 @@
#+begin_src python :tangle arcology/models.py
from __future__ import annotations
from typing import Optional
from typing import Optional, List
from django.db import models
from django.conf import settings
@ -137,29 +137,42 @@ class Page(models.Model):
title = models.CharField(max_length=512)
allow_crawl = models.BooleanField(default=False)
def urlize_self(self):
site = self.site
return site.urlize_page(self)
def collect_keywords(self):
return self.file.keyword_set
def collect_links(self):
link_objs = self.file.outbound_links.all()
ret = dict()
for el in link_objs:
try:
h = el.dest_heading
page = h.path.page_set.first()
site = page.site
link = site.urlize_page(page)
ret[h.node_id] = link
logger.info(f"link {link} from {el}")
url = h.to_url()
ret[h.node_id] = url
logger.info(f"link {url} from {el}")
except roam.models.Heading.DoesNotExist:
logger.info(f"{el} does not have dest")
return ret
def collect_backlinks(self) -> List[Link]:
my_headings = self.file.heading_set.all()
return set(roam.models.Link.objects.filter(dest_heading__in=my_headings))
def to_html(self, links):
opts = native.ExportOptions(
link_retargets=links
)
return native.htmlize_file(self.file.path, opts)
@classmethod
def create_from_arroyo(cls, doc: native.Document) -> Page:
f = roam.models.File.objects.get(path=doc.path)
@ -340,6 +353,35 @@ class Migration(migrations.Migration):
]
#+end_src
** NEXT admin
dont worry too much about these;
they are just used to validate that the data is ingested properly, to be honest.
#+begin_src python :tangle arcology/admin.py
from django.contrib import admin
import arcology.models
class DomainInline(admin.TabularInline):
model = arcology.models.SiteDomain
@admin.register(arcology.models.Site)
class SiteAdmin(admin.ModelAdmin):
inlines = [DomainInline]
@admin.register(arcology.models.Page)
class PageAdmin(admin.ModelAdmin):
pass
@admin.register(arcology.models.Feed)
class FeedAdmin(admin.ModelAdmin):
pass
#+end_src
* NEXT the web server
"""arcology URL Configuration
@ -367,16 +409,18 @@ from arcology import views
urlpatterns = [
path("admin/", admin.site.urls),
path("", views.index),
path("sitemap", views.sitemap),
path("sitemap", views.sitemap, name="sitemap"),
path("sites.css", views.site_css, name="site-css"),
# ensure these ones are last because they're greedy!
re_path("(?P<key>[0-9a-zA-Z/_\-]+\.xml)", views.feed),
re_path("(?P<key>[0-9a-zA-Z/_\-]+)", views.org_page),
re_path("(?P<key>[0-9a-zA-Z/_\-]+\.xml)", views.feed, name="feed"),
re_path("(?P<key>[0-9a-zA-Z/_\-]+)", views.org_page, name="org-page"),
]
#+end_src
#+begin_src python :tangle arcology/views.py
import logging
from django.http import HttpResponse, HttpResponseNotFound
from django.shortcuts import render, get_object_or_404
from arcology.models import Page, Feed, Site
from roam.models import Link
@ -409,14 +453,27 @@ def org_page(request, key):
site = Site.from_request(request)
if site.key == "localhost":
full_key = key
new_site_key = key.split("/")[0]
site = Site.objects.filter(key=new_site_key).first()
else:
full_key = f"{site.key}/{key}"
the_page = Page.objects.get(route_key=full_key)
the_page = get_object_or_404(Page, route_key=full_key)
links = the_page.collect_links()
page_html = the_page.to_html(links)
return HttpResponse(page_html)
logger.warn(site)
return render(request, "arcology/page.html", dict(
site=site,
page=the_page,
head_title=f"{the_page.title} - {site.title}",
html_content=page_html,
backlinks=the_page.collect_backlinks(),
keywords=the_page.collect_keywords().all(),
))
#+end_src
** NEXT sitemap
@ -443,31 +500,331 @@ async def feed(request, key):
return HttpResponse(f"feed for {the_feed.file.path}")
#+end_src
** NEXT admin
** Per-Site link color CSS endpoint
dont worry too much about these;
they are just used to validate that the data is ingested properly, to be honest.
This endpoint generates a dynamic CSS file that colorizes internal URLs based on the [[id:20231229T164611.256424][The Arcology's Site List]].
#+begin_src python :tangle arcology/admin.py
from django.contrib import admin
import arcology.models
class DomainInline(admin.TabularInline):
model = arcology.models.SiteDomain
@admin.register(arcology.models.Site)
class SiteAdmin(admin.ModelAdmin):
inlines = [DomainInline]
@admin.register(arcology.models.Page)
class PageAdmin(admin.ModelAdmin):
pass
@admin.register(arcology.models.Feed)
class FeedAdmin(admin.ModelAdmin):
pass
#+begin_src python :tangle arcology/views.py
def site_css(request):
sites = Site.objects.all()
stanzas = []
for site in sites:
for domain in site.sitedomain_set.all():
stanzas.append(f'''
a[href*="{domain.domain}"] {{
border-radius: 0.25em;
padding: 0.1em;
background-color: {site.link_color}66;
}}
a[href*="{domain.domain}"]:hover {{
background-color: {site.link_color}FF !important;
}}
''')
stanzas.append(f'''
a[href*="/404"] {{
color: var(--alert);
text-decoration: line-through;
}}
a[href*="/404"]::after {{
content: " ⚠";
}}
a[href*="/404"]::before {{
content: "⚠ ";
}}
''')
return HttpResponse(stanzas, content_type="text/css")
#+end_src
** Templates
Okay, now comes the fun part; we have HTML rendering of the org pages themselves working, now to put them on a page looking slick. Idk what to do here; I kind of want to just Tufte-CSS-ify this thing, while still maintaining subtle color-themeing and my typographic choices. Oughta just try to sketch this out first, but I'm gonna just set up the scaffolding for injecting Site specific variables and a basic HTML template that the org docs can be dropped in to.
for now it's largely lifted from [[id:arcology/fastapi/base.html.j2][Base HTML Template]] and [[id:arcology/fastapi/page.html.j2][Page HTML Templates]] from the FastAPI prototype.
#+begin_src jinja2 :tangle arcology/templates/arcology/app.html :mkdirp yes
<!DOCTYPE html>
<html>
<head>
{% load static %}
<link rel="stylesheet" href="{% static 'arcology/css/app.css' %}"/>
<link rel="stylesheet" href="{% static 'arcology/css/vulf.css' %}"/>
<link rel="stylesheet" href="{% static 'arcology/css/default-colors.css' %}"/>
<link rel="stylesheet" href="{% url 'site-css' %}"/>
{% if site and site.css_file %}
<link rel="stylesheet" href="{% static site.css_file %}"/>
{% endif %}
<meta name="author" content="Ryan Rix"/>
<meta name="generator" content="Arcology Site Engine https://engine.arcology.garden/"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{{head_title | default:"The Arcology Project" }}{% endblock %}</title>
{% block extra_head %}{% endblock %}
</head>
<body>
<header>
<div class="header-content">
{% block h1 %}
<h1><a href='/'>{{ site.title }}</a></h1>
<h2>{{ page.title }}</h2>
{% endblock %}
<div>
&bull; <a class="internal" href="https://thelionsrear.com">Life</a>
&bull; <a class="internal" href="https://arcology.garden">Tech</a>
&bull; <a class="internal" href="https://cce.whatthefuck.computer">Emacs</a>
&bull; <a class="internal" href="https://engine.arcology.garden">Arcology</a>
&bull;
</div>
</div>
</header>
<div class="content">
{% block content %}{% endblock %}
</div>
<footer>
<hr/>
&copy; 02023 <a href="https://arcology.garden/people/rrix">Ryan Rix</a> &lt;<a href="mailto:site@whatthefuck.computer">site@whatthefuck.computer</a>&gt;
<br/>
<p>
Care has been taken to publish accurate information to
long-lived URLs, but context and content as well as URLs may
change without notice.
</p>
<p>
This site collects no personal information from visitors, nor
stores any identifying tokens. If you or your personal
information ended up in public notes please email me for
correction or removal. A single bit cookie may be stored on
your device if you choose to change appearance settings below.
</p>
<p>
Email me with questions, comments, insights, kind criticism.
blow horn, good luck.
</p>
<p>
<a href="/sitemap/">View the Site Map</a>
</p>
<p>
<a href="https://fediring.net/previous?host=arcology.garden">&larr;</a>
<a href="https://fediring.net/">Fediring</a>
<a href="https://fediring.net/next?host=arcology.garden">&rarr;</a>
</p>
<!--
<p>
<input type="checkbox" id="boredom-mode"><label for="boredom-mode">I do not like your aesthetic sensibilities!!</label>
</p>
<script type="text/javascript">
<<boredom>>
</script>
-->
</footer>
</body>
</html>
#+end_src
#+begin_src jinja2 :tangle arcology/templates/arcology/page.html
{% extends "arcology/app.html" %}
{% block title %}{{ head_title }}{% endblock %}
{% block head %}
{% for feed in feeds %}
<link rel="alternate" type="application/atom+xml" href="{{ feed.url }}" title="{{ feed.title }}" />
{% endfor %}
{% if page.allow_crawl is none or page.allow_crawl is '"nil"' %}
<meta name="robots" content="noarchive noimageindex noindex nofollow"/>
{% else %}
<meta name="robots" content=""/>
{% endif %}
{% endblock %}
{% block content %}
{{ html_content | safe }}
<section class="sidebar">
<div class="backlinks">
<h3>Pages Linking Here</h3>
<ul class="backlinks">
{% for backlink in backlinks %}
<li>{{ backlink.to_backlink_html|safe }}</li>
{% endfor %}
</ul>
</div>
<div class="keywords">
<h3>Page Metadata Keywords</h3>
<ul class="keywords">
{% for keyword in keywords %}
<li><pre>{{ keyword.keyword }} = {{ keyword.value }}</pre></li>
{% endfor %}
</ul>
</div>
</section>
{% endblock %}
#+end_src
*** CSS
:PROPERTIES:
:ID: 20231229T164608.815737
:END:
this will be extended.
rather than using emoji for each site, it would be nice to subtly color them based on the link_color... will need to Do Some Bullshit to make that work though maybe.
#+begin_src css :tangle static/arcology/css/app.css :mkdirp yes
body {
font-family: "Vulf Mono", monospace;
font-style: italic;
font-size: medium;
background-color: var(--white);
color: var(--black);
margin: 0;
}
#+end_src
#+begin_src css :tangle static/arcology/css/app.css :mkdirp yes
header {
background-color: var(--light-gray);
border-radius: 0.25em;
margin-top: 0;
}
header > .header-content {
padding: 1em;
max-width: 120ch;
margin-left: auto;
margin-right: auto;
}
header h1, header h2 {
margin-top: 0;
display: inline;
}
header h2:before {
content: " — ";
}
#+end_src
#+begin_src css :tangle static/arcology/css/app.css :mkdirp yes
.content {
margin-left: auto;
margin-right: auto;
padding: 1em;
padding-top: 0;
display: flex;
flex-flow: row wrap;
max-width: 120ch;
}
.content > section, main {
display: inline-block;
flex-grow: 1;
flex-shrink: 1;
flex-basis: 40em;
padding: 1em;
overflow: auto;
}
.content > section.sidebar {
flex-grow: 0;
flex-shrink: 1;
flex-basis: 30ch;
}
section.sidebar {
display: flex;
flex-flow: column wrap;
}
section.sidebar > div.backlinks {
flex-grow: 1;
}
#+end_src
#+begin_src css :tangle static/arcology/css/app.css :mkdirp yes
footer {
margin-left: auto;
margin-right: auto;
max-width: 120ch;
font-size: smaller;
text-align: center;
}
footer a {
font-weight: 500;
}
#+end_src
Some [[https://medium.com/@massimo.cassandro/flexbox-separators-b284d6d7b747][hacks]].
#+begin_src css :tangle static/arcology/css/app.css :mkdirp yes
.content::before {
align-self: stretch;
content: '';
border: 1px dotted var(--medium-gray);
margin-top: 1em;
margin-bottom: 1em;
}
.content > *:first-child {
order: -1;
}
a {
color: var(--primary);
}
a::visited {
color: var(--secondary);
}
#+end_src
There are per-site CSS in [[id:20231229T164611.256424][The Arcology's Site List]].
*** Generataing =@font-face= rules for a bunch of fonts
[[id:cce/vulfpeck_fonts_are_fun][Vulfpeck Fonts]] are pulled in with this code-gen because writing =@font-face= rules does not bring joy and I don't have the right to redistribute these files, so I won't check it in at all.
#+NAME: font-face-tbl
| VulfSans | Regular | 500 | |
| VulfMono | Regular | 500 | |
| VulfSans | Bold | 800 | |
| VulfMono | Bold | 800 | |
| VulfSans | Italic | 500 | italic |
| VulfMono | Italic | 500 | italic |
| VulfSans | Bold_Italic | 800 | italic |
| VulfMono | Bold_Italic | 800 | italic |
| VulfSans | Light | 300 | |
| VulfMono | Light | 300 | |
| VulfSans | Light_Italic | 500 | italic |
| VulfMono | Light_Italic | 500 | italic |
#+NAME: gen_font_faces
#+begin_src elisp :var tbl=font-face-tbl :results none
(with-temp-buffer
(-map (pcase-lambda (`(,first ,second ,weight ,style))
(insert
(s-join "\n" (list
"@font-face {"
"font-family: " (if (equal first "VulfMono")
"\"Vulf Mono\""
"\"Vulf Sans\"")
"; src:"
(concat "url('/static/arcology/fonts/" first "-" second ".woff') format('woff'),")
(concat "url('/static/arcology/fonts/" first "-" second ".woff2') format('woff2'),")
(concat "url('/static/arcology/fonts/" first "-" second ".ttf') format('truetype');")
"font-weight: " (number-to-string weight) ";"
(unless (equal style "")
(concat "font-style: " style ";"))
"}"))))
tbl)
(write-file "~/org/arcology-django/static/arcology/css/vulf.css"))
#+end_src

View File

@ -1,6 +1,6 @@
# [[file:../arcology.org::*Data Models for routing to org-roam pages][Data Models for routing to org-roam pages:1]]
from __future__ import annotations
from typing import Optional
from typing import Optional, List
from django.db import models
from django.conf import settings
@ -71,29 +71,42 @@ class Page(models.Model):
title = models.CharField(max_length=512)
allow_crawl = models.BooleanField(default=False)
def urlize_self(self):
site = self.site
return site.urlize_page(self)
def collect_keywords(self):
return self.file.keyword_set
def collect_links(self):
link_objs = self.file.outbound_links.all()
ret = dict()
for el in link_objs:
try:
h = el.dest_heading
page = h.path.page_set.first()
site = page.site
link = site.urlize_page(page)
ret[h.node_id] = link
logger.info(f"link {link} from {el}")
url = h.to_url()
ret[h.node_id] = url
logger.info(f"link {url} from {el}")
except roam.models.Heading.DoesNotExist:
logger.info(f"{el} does not have dest")
return ret
def collect_backlinks(self) -> List[Link]:
my_headings = self.file.heading_set.all()
return set(roam.models.Link.objects.filter(dest_heading__in=my_headings))
def to_html(self, links):
opts = native.ExportOptions(
link_retargets=links
)
return native.htmlize_file(self.file.path, opts)
@classmethod
def create_from_arroyo(cls, doc: native.Document) -> Page:
f = roam.models.File.objects.get(path=doc.path)

View File

@ -151,6 +151,19 @@ AUTH_PASSWORD_VALIDATORS = [
STATIC_URL = "/static/"
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}
STATICFILES_DIRS = [
BASE_DIR / "arcology" / "static",
BASE_DIR / "static",
]
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

View File

@ -1 +1 @@
[{"title":"The Lion's Rear","key":"lionsrear","css_file":"lionsrear.css","link_color":"#87af87","domains":["thelionsrear.com","rix.si"]},{"title":"The Arcology Garden","key":"garden","css_file":"garden.css","link_color":"#a6dc68","domains":["arcology.garden","whatthefuck.computer"]},{"title":"The Complete Computer","key":"cce","css_file":"cce.css","link_color":"#cc9660","domains":["cce.whatthefuck.computer","cce.rix.si"]},{"title":"The Arcology Site Engine","key":"arcology","css_file":"arcology.css","link_color":"#67b4f8","domains":["engine.arcology.garden"]},{"title":"Local Dev Environment","key":"localhost","css_file":"arcology.css","link_color":"#67b4f8","domains":["127.0.0.1:8000","localhost:8000"]}]
[{"title":"The Lion's Rear","key":"lionsrear","css_file":"arcology/css/lionsrear.css","link_color":"#006f00","domains":["thelionsrear.com","rix.si"]},{"title":"The Arcology Garden","key":"garden","css_file":"arcology/css/garden.css","link_color":"#444fcf","domains":["arcology.garden","whatthefuck.computer"]},{"title":"The Complete Computer","key":"cce","css_file":"arcology/css/cce.css","link_color":"#dd0020","domains":["cce.whatthefuck.computer","cce.rix.si"]},{"title":"The Arcology Site Engine","key":"arcology","css_file":"arcology/css/arcology.css","link_color":"#8448aa","domains":["engine.arcology.garden"]},{"title":"Local Dev Environment","key":"localhost","css_file":"arcology/css/arcology.css","link_color":"#075192","domains":["127.0.0.1:8000","localhost:8000"]}]

View File

@ -0,0 +1,87 @@
{# [[file:../../../arcology.org::*Templates][Templates:1]] #}
<!DOCTYPE html>
<html>
<head>
{% load static %}
<link rel="stylesheet" href="{% static 'arcology/css/app.css' %}"/>
<link rel="stylesheet" href="{% static 'arcology/css/vulf.css' %}"/>
<link rel="stylesheet" href="{% static 'arcology/css/default-colors.css' %}"/>
<link rel="stylesheet" href="{% url 'site-css' %}"/>
{% if site and site.css_file %}
<link rel="stylesheet" href="{% static site.css_file %}"/>
{% endif %}
<meta name="author" content="Ryan Rix"/>
<meta name="generator" content="Arcology Site Engine https://engine.arcology.garden/"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{{head_title | default:"The Arcology Project" }}{% endblock %}</title>
{% block extra_head %}{% endblock %}
</head>
<body>
<header>
<div class="header-content">
{% block h1 %}
<h1><a href='/'>{{ site.title }}</a></h1>
<h2>{{ page.title }}</h2>
{% endblock %}
<div>
&bull; <a class="internal" href="https://thelionsrear.com">Life</a>
&bull; <a class="internal" href="https://arcology.garden">Tech</a>
&bull; <a class="internal" href="https://cce.whatthefuck.computer">Emacs</a>
&bull; <a class="internal" href="https://engine.arcology.garden">Arcology</a>
&bull;
</div>
</div>
</header>
<div class="content">
{% block content %}{% endblock %}
</div>
<footer>
<hr/>
&copy; 02023 <a href="https://arcology.garden/people/rrix">Ryan Rix</a> &lt;<a href="mailto:site@whatthefuck.computer">site@whatthefuck.computer</a>&gt;
<br/>
<p>
Care has been taken to publish accurate information to
long-lived URLs, but context and content as well as URLs may
change without notice.
</p>
<p>
This site collects no personal information from visitors, nor
stores any identifying tokens. If you or your personal
information ended up in public notes please email me for
correction or removal. A single bit cookie may be stored on
your device if you choose to change appearance settings below.
</p>
<p>
Email me with questions, comments, insights, kind criticism.
blow horn, good luck.
</p>
<p>
<a href="/sitemap/">View the Site Map</a>
</p>
<p>
<a href="https://fediring.net/previous?host=arcology.garden">&larr;</a>
<a href="https://fediring.net/">Fediring</a>
<a href="https://fediring.net/next?host=arcology.garden">&rarr;</a>
</p>
<!--
<p>
<input type="checkbox" id="boredom-mode"><label for="boredom-mode">I do not like your aesthetic sensibilities!!</label>
</p>
<script type="text/javascript">
<<boredom>>
</script>
-->
</footer>
</body>
</html>
{# Templates:1 ends here #}

View File

@ -0,0 +1,40 @@
{# [[file:../../../arcology.org::*Templates][Templates:2]] #}
{% extends "arcology/app.html" %}
{% block title %}{{ head_title }}{% endblock %}
{% block head %}
{% for feed in feeds %}
<link rel="alternate" type="application/atom+xml" href="{{ feed.url }}" title="{{ feed.title }}" />
{% endfor %}
{% if page.allow_crawl is none or page.allow_crawl is '"nil"' %}
<meta name="robots" content="noarchive noimageindex noindex nofollow"/>
{% else %}
<meta name="robots" content=""/>
{% endif %}
{% endblock %}
{% block content %}
{{ html_content | safe }}
<section class="sidebar">
<div class="backlinks">
<h3>Pages Linking Here</h3>
<ul class="backlinks">
{% for backlink in backlinks %}
<li>{{ backlink.to_backlink_html|safe }}</li>
{% endfor %}
</ul>
</div>
<div class="keywords">
<h3>Page Metadata Keywords</h3>
<ul class="keywords">
{% for keyword in keywords %}
<li><pre>{{ keyword.keyword }} = {{ keyword.value }}</pre></li>
{% endfor %}
</ul>
</div>
</section>
{% endblock %}
{# Templates:2 ends here #}

View File

@ -7,9 +7,10 @@ from arcology import views
urlpatterns = [
path("admin/", admin.site.urls),
path("", views.index),
path("sitemap", views.sitemap),
path("sitemap", views.sitemap, name="sitemap"),
path("sites.css", views.site_css, name="site-css"),
# ensure these ones are last because they're greedy!
re_path("(?P<key>[0-9a-zA-Z/_\-]+\.xml)", views.feed),
re_path("(?P<key>[0-9a-zA-Z/_\-]+)", views.org_page),
re_path("(?P<key>[0-9a-zA-Z/_\-]+\.xml)", views.feed, name="feed"),
re_path("(?P<key>[0-9a-zA-Z/_\-]+)", views.org_page, name="org-page"),
]
# the web server:1 ends here

View File

@ -1,6 +1,7 @@
# [[file:../arcology.org::*the web server][the web server:2]]
import logging
from django.http import HttpResponse, HttpResponseNotFound
from django.shortcuts import render, get_object_or_404
from arcology.models import Page, Feed, Site
from roam.models import Link
@ -22,14 +23,27 @@ def org_page(request, key):
site = Site.from_request(request)
if site.key == "localhost":
full_key = key
new_site_key = key.split("/")[0]
site = Site.objects.filter(key=new_site_key).first()
else:
full_key = f"{site.key}/{key}"
the_page = Page.objects.get(route_key=full_key)
the_page = get_object_or_404(Page, route_key=full_key)
links = the_page.collect_links()
page_html = the_page.to_html(links)
return HttpResponse(page_html)
logger.warn(site)
return render(request, "arcology/page.html", dict(
site=site,
page=the_page,
head_title=f"{the_page.title} - {site.title}",
html_content=page_html,
backlinks=the_page.collect_backlinks(),
keywords=the_page.collect_keywords().all(),
))
# org-page:1 ends here
# [[file:../arcology.org::*sitemap][sitemap:1]]
@ -46,3 +60,34 @@ async def feed(request, key):
the_feed = Feed.objects.get(route_key=full_key)
return HttpResponse(f"feed for {the_feed.file.path}")
# feed:1 ends here
# [[file:../arcology.org::*Per-Site link color CSS endpoint][Per-Site link color CSS endpoint:1]]
def site_css(request):
sites = Site.objects.all()
stanzas = []
for site in sites:
for domain in site.sitedomain_set.all():
stanzas.append(f'''
a[href*="{domain.domain}"] {{
border-radius: 0.25em;
padding: 0.1em;
background-color: {site.link_color}66;
}}
a[href*="{domain.domain}"]:hover {{
background-color: {site.link_color}FF !important;
}}
''')
stanzas.append(f'''
a[href*="/404"] {{
color: var(--alert);
text-decoration: line-through;
}}
a[href*="/404"]::after {{
content: "";
}}
a[href*="/404"]::before {{
content: "";
}}
''')
return HttpResponse(stanzas, content_type="text/css")
# Per-Site link color CSS endpoint:1 ends here

View File

@ -9,18 +9,21 @@
user-configuration ; these could be generated from org tables if i'm feeling like a proper sicko. They should at least be much more legible but that's true of all the code i'm pulling in here..
* The Site List
* The Arcology's Site List
:PROPERTIES:
:ID: 20231229T164611.256424
:END:
These can be customized by the user:
#+NAME: sites-config
| title | key | css file | link color |
|--------------------------+-----------+---------------+------------|
| The Lion's Rear | lionsrear | lionsrear.css | #87af87 |
| The Arcology Garden | garden | garden.css | #a6dc68 |
| The Complete Computer | cce | cce.css | #cc9660 |
| The Arcology Site Engine | arcology | arcology.css | #67b4f8 |
| Local Dev Environment | localhost | arcology.css | #67b4f8 |
| The Lion's Rear | lionsrear | lionsrear.css | #006f00 |
| The Arcology Garden | garden | garden.css | #444fcf |
| The Complete Computer | cce | cce.css | #dd0020 |
| The Arcology Site Engine | arcology | arcology.css | #8448aa |
| Local Dev Environment | localhost | arcology.css | #075192 |
#+NAME: domains-config
| key | domain |
@ -44,7 +47,7 @@ Oh we doing some code gen in here, this is just gonna generate some JSON for now
(pcase-let* ((`(,title ,key ,css ,link) siterow))
`((title . ,title)
(key . ,key)
(css_file . ,css)
(css_file . ,(format "arcology/css/%s" css))
(link_color . ,link)
(domains . ,(-map #'cadr
(-filter (pcase-lambda (`(,dkey ,domain))
@ -59,6 +62,62 @@ That generates JSON that goes in to =arcology/settings/sites.json= for the [[id:
<<elisp-join-tables()>>
#+end_src
** NEXT How to re-seed these if you change the configuration?
** Per-site CSS
These are generated from a table, too, because I have brainworms.
Note that they're basically the same, but you can customize it further to your satisfaction.
#+NAME: site-colors
| Site | alert | primary | secondary | success | warning | white | gray2 | gray3 | gray4 | black |
|----------------+---------+---------+-----------+---------+---------+---------+---------+---------+---------+---------|
| default-colors | #cc6960 | #707231 | #ebbe7b | #67b4f8 | #7e5c41 | #fcf6ed | #f6e5cb | #baad9b | #82796c | #211f1c |
| garden | #cc6960 | #707231 | #ebbe7b | #67b4f8 | #7e5c41 | #fcf6ed | #f6e5cb | #baad9b | #82796c | #211f1c |
| lionsrear | #cc6960 | #707231 | #ebbe7b | #67b4f8 | #7e5c41 | #cfdcc2 | #87af87 | #a0aa96 | #82796c | #211f1c |
| cce | #cc6960 | #707231 | #ebbe7b | #67b4f8 | #7e5c41 | #fcfcfc | #dcdcec | #cacada | #808090 | #211f1c |
| arcology | #cc6960 | #707231 | #ebbe7b | #67b4f8 | #7e5c41 | #fcf6ed | #f6e5cb | #baad9b | #82796c | #211f1c |
#+NAME: mk-site-colors
#+begin_src emacs-lisp :var colors=site-colors :var site="default-colors" :results none
(pcase-let ((`((,name ,alert ,primary ,secondary ,success ,warning ,white ,gray2 ,gray3 , gray4 ,black))
(-filter (lambda (row)
(equal (first row) site))
colors)))
(s-join "\n"
(list ":root {"
(concat " --alert: " alert ";")
(concat " --primary: " primary ";")
(concat " --secondary: " secondary ";")
(concat " --success: " success ";")
(concat " --warning: " warning ";")
(concat " --white: " white ";")
(concat " --light-gray: " gray2 ";")
(concat " --medium-gray: " gray3 ";")
(concat " --dark-gray: " gray4 ";")
(concat " --black: " black ";")
"}")))
#+end_src
#+begin_src css :tangle static/arcology/css/default-colors.css :noweb yes
<<mk-site-colors(site-colors, "default-colors")>>
#+end_src
#+begin_src css :tangle static/arcology/css/cce.css :noweb yes
<<mk-site-colors(site-colors, "cce")>>
#+end_src
#+begin_src css :tangle static/arcology/css/lionsrear.css :noweb yes
<<mk-site-colors(site-colors, "lionsrear")>>
#+end_src
#+begin_src css :tangle static/arcology/css/garden.css :noweb yes
<<mk-site-colors(site-colors, "garden")>>
#+end_src
#+begin_src css :tangle static/arcology/css/arcology.css :noweb yes
<<mk-site-colors(site-colors, "arcology")>>
#+end_src
* Service Configuration
:PROPERTIES:
:ID: 20231217T155611.177995
@ -372,6 +431,19 @@ AUTH_PASSWORD_VALIDATORS = [
STATIC_URL = "/static/"
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}
STATICFILES_DIRS = [
BASE_DIR / "arcology" / "static",
BASE_DIR / "static",
]
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

View File

@ -6,11 +6,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1703734847,
"lastModified": 1703908423,
"narHash": "sha256-55zlSQULXCmUf88tPSGc/g7RiYnuCo14eaP1kcA6V00=",
"ref": "refs/heads/main",
"rev": "f7f944d0afb9b790cbc33bf023a1ca4cbcc0b134",
"revCount": 127,
"rev": "26a7bfe085b01fff19b287191e4f2b989d363667",
"revCount": 129,
"type": "git",
"url": "https://code.rix.si/rrix/arroyo"
},

View File

@ -118,6 +118,15 @@ class Heading(models.Model):
through_fields=("dest_heading", "source_heading"),
)
def to_url(self) -> str:
page = self.path.page_set.first()
page_url = page.urlize_self()
if self.level == 0:
return page_url
else:
return f"{page_url}#{self.node_id}"
@classmethod
def create_from_arroyo(cls, doc: native.Document) -> List[Heading]:
return [
@ -213,6 +222,18 @@ class Link(models.Model):
to_field="node_id",
)
def to_backlink_html(self) -> str:
try:
h = self.source_heading
page = h.path.page_set.first()
url = h.to_url()
title = page.title
return f'''<a class="internal" href="{url}">{title}</a>'''
except Heading.DoesNotExist:
logger.info(f"{self} does not have dest heading.")
return f'''<a class="dead-link" href="/404?text={self.title|iriencode}">{self.title}</a>'''
@classmethod
def create_from_arroyo(cls, doc: native.Document) -> List[Link]:
ret = []

View File

@ -98,6 +98,15 @@ class Heading(models.Model):
through_fields=("dest_heading", "source_heading"),
)
def to_url(self) -> str:
page = self.path.page_set.first()
page_url = page.urlize_self()
if self.level == 0:
return page_url
else:
return f"{page_url}#{self.node_id}"
@classmethod
def create_from_arroyo(cls, doc: native.Document) -> List[Heading]:
return [
@ -187,6 +196,18 @@ class Link(models.Model):
to_field="node_id",
)
def to_backlink_html(self) -> str:
try:
h = self.source_heading
page = h.path.page_set.first()
url = h.to_url()
title = page.title
return f'''<a class="internal" href="{url}">{title}</a>'''
except Heading.DoesNotExist:
logger.info(f"{self} does not have dest heading.")
return f'''<a class="dead-link" href="/404?text={self.title|iriencode}">{self.title}</a>'''
@classmethod
def create_from_arroyo(cls, doc: native.Document) -> List[Link]:
ret = []

105
static/arcology/css/app.css Normal file
View File

@ -0,0 +1,105 @@
/* [[file:../../../arcology.org::*CSS][CSS:1]] */
body {
font-family: "Vulf Mono", monospace;
font-style: italic;
font-size: medium;
background-color: var(--white);
color: var(--black);
margin: 0;
}
/* CSS:1 ends here */
/* [[file:../../../arcology.org::*CSS][CSS:2]] */
header {
background-color: var(--light-gray);
border-radius: 0.25em;
margin-top: 0;
}
header > .header-content {
padding: 1em;
max-width: 120ch;
margin-left: auto;
margin-right: auto;
}
header h1, header h2 {
margin-top: 0;
display: inline;
}
header h2:before {
content: " — ";
}
/* CSS:2 ends here */
/* [[file:../../../arcology.org::*CSS][CSS:3]] */
.content {
margin-left: auto;
margin-right: auto;
padding: 1em;
padding-top: 0;
display: flex;
flex-flow: row wrap;
max-width: 120ch;
}
.content > section, main {
display: inline-block;
flex-grow: 1;
flex-shrink: 1;
flex-basis: 40em;
padding: 1em;
overflow: auto;
}
.content > section.sidebar {
flex-grow: 0;
flex-shrink: 1;
flex-basis: 30ch;
}
section.sidebar {
display: flex;
flex-flow: column wrap;
}
section.sidebar > div.backlinks {
flex-grow: 1;
}
/* CSS:3 ends here */
/* [[file:../../../arcology.org::*CSS][CSS:4]] */
footer {
margin-left: auto;
margin-right: auto;
max-width: 120ch;
font-size: smaller;
text-align: center;
}
footer a {
font-weight: 500;
}
/* CSS:4 ends here */
/* [[file:../../../arcology.org::*CSS][CSS:5]] */
.content::before {
align-self: stretch;
content: '';
border: 1px dotted var(--medium-gray);
margin-top: 1em;
margin-bottom: 1em;
}
.content > *:first-child {
order: -1;
}
a {
color: var(--primary);
}
a::visited {
color: var(--secondary);
}
/* CSS:5 ends here */

View File

@ -0,0 +1,14 @@
/* [[file:../../../configuration.org::*Per-site CSS][Per-site CSS:6]] */
:root {
--alert: #cc6960;
--primary: #707231;
--secondary: #ebbe7b;
--success: #67b4f8;
--warning: #7e5c41;
--white: #fcf6ed;
--light-gray: #f6e5cb;
--medium-gray: #baad9b;
--dark-gray: #82796c;
--black: #211f1c;
}
/* Per-site CSS:6 ends here */

View File

@ -0,0 +1,14 @@
/* [[file:../../../configuration.org::*Per-site CSS][Per-site CSS:3]] */
:root {
--alert: #cc6960;
--primary: #707231;
--secondary: #ebbe7b;
--success: #67b4f8;
--warning: #7e5c41;
--white: #fcfcfc;
--light-gray: #dcdcec;
--medium-gray: #cacada;
--dark-gray: #808090;
--black: #211f1c;
}
/* Per-site CSS:3 ends here */

View File

@ -0,0 +1,14 @@
/* [[file:../../../configuration.org::*Per-site CSS][Per-site CSS:2]] */
:root {
--alert: #cc6960;
--primary: #707231;
--secondary: #ebbe7b;
--success: #67b4f8;
--warning: #7e5c41;
--white: #fcf6ed;
--light-gray: #f6e5cb;
--medium-gray: #baad9b;
--dark-gray: #82796c;
--black: #211f1c;
}
/* Per-site CSS:2 ends here */

View File

@ -0,0 +1,14 @@
/* [[file:../../../configuration.org::*Per-site CSS][Per-site CSS:5]] */
:root {
--alert: #cc6960;
--primary: #707231;
--secondary: #ebbe7b;
--success: #67b4f8;
--warning: #7e5c41;
--white: #fcf6ed;
--light-gray: #f6e5cb;
--medium-gray: #baad9b;
--dark-gray: #82796c;
--black: #211f1c;
}
/* Per-site CSS:5 ends here */

View File

@ -0,0 +1,14 @@
/* [[file:../../../configuration.org::*Per-site CSS][Per-site CSS:4]] */
:root {
--alert: #cc6960;
--primary: #707231;
--secondary: #ebbe7b;
--success: #67b4f8;
--warning: #7e5c41;
--white: #cfdcc2;
--light-gray: #87af87;
--medium-gray: #a0aa96;
--dark-gray: #82796c;
--black: #211f1c;
}
/* Per-site CSS:4 ends here */

View File

@ -0,0 +1,133 @@
@font-face {
font-family:
"Vulf Sans"
; src:
url('/static/arcology/fonts/VulfSans-Regular.woff') format('woff'),
url('/static/arcology/fonts/VulfSans-Regular.woff2') format('woff2'),
url('/static/arcology/fonts/VulfSans-Regular.ttf') format('truetype');
font-weight:
500
;
}@font-face {
font-family:
"Vulf Mono"
; src:
url('/static/arcology/fonts/VulfMono-Regular.woff') format('woff'),
url('/static/arcology/fonts/VulfMono-Regular.woff2') format('woff2'),
url('/static/arcology/fonts/VulfMono-Regular.ttf') format('truetype');
font-weight:
500
;
}@font-face {
font-family:
"Vulf Sans"
; src:
url('/static/arcology/fonts/VulfSans-Bold.woff') format('woff'),
url('/static/arcology/fonts/VulfSans-Bold.woff2') format('woff2'),
url('/static/arcology/fonts/VulfSans-Bold.ttf') format('truetype');
font-weight:
800
;
}@font-face {
font-family:
"Vulf Mono"
; src:
url('/static/arcology/fonts/VulfMono-Bold.woff') format('woff'),
url('/static/arcology/fonts/VulfMono-Bold.woff2') format('woff2'),
url('/static/arcology/fonts/VulfMono-Bold.ttf') format('truetype');
font-weight:
800
;
}@font-face {
font-family:
"Vulf Sans"
; src:
url('/static/arcology/fonts/VulfSans-Italic.woff') format('woff'),
url('/static/arcology/fonts/VulfSans-Italic.woff2') format('woff2'),
url('/static/arcology/fonts/VulfSans-Italic.ttf') format('truetype');
font-weight:
500
;
font-style: italic;
}@font-face {
font-family:
"Vulf Mono"
; src:
url('/static/arcology/fonts/VulfMono-Italic.woff') format('woff'),
url('/static/arcology/fonts/VulfMono-Italic.woff2') format('woff2'),
url('/static/arcology/fonts/VulfMono-Italic.ttf') format('truetype');
font-weight:
500
;
font-style: italic;
}@font-face {
font-family:
"Vulf Sans"
; src:
url('/static/arcology/fonts/VulfSans-Bold_Italic.woff') format('woff'),
url('/static/arcology/fonts/VulfSans-Bold_Italic.woff2') format('woff2'),
url('/static/arcology/fonts/VulfSans-Bold_Italic.ttf') format('truetype');
font-weight:
800
;
font-style: italic;
}@font-face {
font-family:
"Vulf Mono"
; src:
url('/static/arcology/fonts/VulfMono-Bold_Italic.woff') format('woff'),
url('/static/arcology/fonts/VulfMono-Bold_Italic.woff2') format('woff2'),
url('/static/arcology/fonts/VulfMono-Bold_Italic.ttf') format('truetype');
font-weight:
800
;
font-style: italic;
}@font-face {
font-family:
"Vulf Sans"
; src:
url('/static/arcology/fonts/VulfSans-Light.woff') format('woff'),
url('/static/arcology/fonts/VulfSans-Light.woff2') format('woff2'),
url('/static/arcology/fonts/VulfSans-Light.ttf') format('truetype');
font-weight:
300
;
}@font-face {
font-family:
"Vulf Mono"
; src:
url('/static/arcology/fonts/VulfMono-Light.woff') format('woff'),
url('/static/arcology/fonts/VulfMono-Light.woff2') format('woff2'),
url('/static/arcology/fonts/VulfMono-Light.ttf') format('truetype');
font-weight:
300
;
}@font-face {
font-family:
"Vulf Sans"
; src:
url('/static/arcology/fonts/VulfSans-Light_Italic.woff') format('woff'),
url('/static/arcology/fonts/VulfSans-Light_Italic.woff2') format('woff2'),
url('/static/arcology/fonts/VulfSans-Light_Italic.ttf') format('truetype');
font-weight:
500
;
font-style: italic;
}@font-face {
font-family:
"Vulf Mono"
; src:
url('/static/arcology/fonts/VulfMono-Light_Italic.woff') format('woff'),
url('/static/arcology/fonts/VulfMono-Light_Italic.woff2') format('woff2'),
url('/static/arcology/fonts/VulfMono-Light_Italic.ttf') format('truetype');
font-weight:
500
;
font-style: italic;
}