M activist/background.py => activist/background.py +2 -2
@@ 13,7 13,7 @@ from .config import settings
logger = logging.getLogger(__name__)
-class UrecoverableBacgroundTaskException(Exception):
+class UrecoverableBackgroundTaskException(Exception):
"Raising this exception from a task cause task to fail directly without retry"
pass
@@ 103,7 103,7 @@ class TaskType:
self._set_state("failed")
return
- if isinstance(e, UrecoverableBacgroundTaskException):
+ if isinstance(e, UrecoverableBackgroundTaskException):
logger.info("task failed and requested to be dequeued")
self._set_state("failed")
return
M activist/db/sqlbuilder.py => activist/db/sqlbuilder.py +7 -0
@@ 123,6 123,13 @@ class Query(QueryBase):
for k, v in kwargs.items():
self.tables.append(_Table(name=k, alias=v))
+ def avec(self, **kwargs) -> Self:
+ for k,v in kwargs.items():
+ self.data['WITH'].append(f"k as ({v})")
+ if isinstance(v, QueryBase):
+ self.parameters += v.parameters
+ return self
+
def select(self, *args, **kwargs) -> Self:
"""
SELECT
M activist/static/style.css => activist/static/style.css +54 -17
@@ 85,7 85,9 @@ dl>dd {
text-wrap: nowrap;
}
-p:last-child { margin-bottom: 0;}
+p:last-child {
+ margin-bottom: 0;
+}
@media screen and (min-width: 600px) {
dl.kv {
@@ 129,7 131,7 @@ p:last-child { margin-bottom: 0;}
/* search bar */
.searchbar {
- width: 100%;
+ width: 100%;
}
.searchbar .input-group-btn {
@@ 178,10 180,10 @@ footer.navbar.mobile>.navbar-section>.btn:not(:first-child) {
.object-action-bar {
- display: flex;
- flex-direction: revert;
+ display: flex;
+ flex-direction: revert;
align-items: center;
- gap: 0.5rem;
+ gap: 0.5rem;
}
.object-activities-summary {
@@ 256,27 258,61 @@ body.article article>header>h1::after {
overflow-y: auto;
}
+
+/** grid - images in post */
+.img-grid {
+ display: grid;
+ grid-template-columns: auto auto;
+ grid-auto-rows: 200px;
+ grid-gap: 0.2rem;
+}
+
+.img-grid-1 {
+ grid-template-columns: auto;
+ grid-template-rows: 400px;
+}
+
+.img-grid-2 {
+ grid-template-columns: auto auto;
+ grid-template-rows: 400px;
+}
+
+.img-grid-odd .img-area-2 {
+ grid-row: 1 / span 2;
+ grid-column: 2;
+}
+
+.img-grid>img {
+ display: block;
+ object-fit: cover;
+ width: 100%;
+ height: 100%;
+ border-radius: 10px;
+}
+
+
/** photos page */
.card.photo {
position: relative;
overflow: hidden;
}
+
/* keep .card.photo square https://stackoverflow.com/a/28985475 */
.card.photo::before {
- content: "";
- display: block;
- padding-top: 100%;
+ content: "";
+ display: block;
+ padding-top: 100%;
}
-.card.photo > .card-image {
- position: absolute;
- height: 100%;
- width: 100%;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
+.card.photo>.card-image {
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
}
.card.photo>.card-header {
@@ 301,7 337,8 @@ body.article article>header>h1::after {
align-items: start;
padding-bottom: 0.5rem;
}
-.card-body > .smaller-tile:last-child {
+
+.card-body>.smaller-tile:last-child {
padding-bottom: 0;
}
M activist/tasks.py => activist/tasks.py +2 -2
@@ 6,7 6,7 @@ import json
import logging
import datetime
from typing import Optional
-from .background import background, UrecoverableBacgroundTaskException
+from .background import background, UrecoverableBackgroundTaskException
from .consts import AS_PUBLIC, ACTORS
from .config import settings
from . import activitypub
@@ 25,7 25,7 @@ def publish(obj_id:str):
logger.debug("publish post %r", obj_id)
asobj = db.Object.get(obj_id)
if asobj is None:
- raise UrecoverableBacgroundTaskException(f"Publish Task. Object not found '{obj_id}'")
+ raise UrecoverableBackgroundTaskException(f"Publish Task. Object not found '{obj_id}'")
obj = asobj.data
recipients = []
for prop in [ "to", "bto", "cc", "bcc", "audience" ]:
M activist/templates/f/object.tpl.j2 => activist/templates/f/object.tpl.j2 +32 -27
@@ 8,23 8,24 @@
{% set actor = obj.attributedTo %}
<div class="card">
<div class="object-activities-summary text-tiny text-gray">
- {% if obj.inReplyTo_id %}
- <i class="icon i-message-circle-reply"></i>
- in reply to <a href="{{ url_for('thread', uri=obj.inReplyTo_id|urlsafe )}}">{{ obj.inReplyTo.attributedTo.data|firstof('name', 'preferredUsername') }}</a><br>
- {% endif %}
- {% set firstactivity = obj.get_first_activity() %}
- {% if firstactivity %}
- <i class="{{ class(
- 'icon', {
- 'i-star': firstactivity.type == 'Like',
- 'i-star-off': firstactivity.type == 'Dislike',
- 'i-rotate-ccw-square': firstactivity.type == 'Announce',
- }) }}"></i>
- {{ firstactivity.type|pastaction(Announce = 'reshared') }} by <a href="{{ firstactivity.actor_id }}">{{ firstactivity.actor.data|firstof('name', 'preferredUsername') }}</a>
- {% endif %}
-
+ {% if obj.inReplyTo_id %}
+ <i class="icon i-message-circle-reply"></i>
+ in reply to <a href="{{ url_for('thread', uri=obj.inReplyTo_id|urlsafe )}}">{{ obj.inReplyTo.attributedTo.data|firstof('name', 'preferredUsername') }}</a><br>
+ {% endif %}
+ {% set firstactivity = obj.get_first_activity() %}
+ {% if firstactivity %}
+ <i class="{{ class(
+ 'icon', {
+ 'i-star': firstactivity.type == 'Like',
+ 'i-star-off': firstactivity.type == 'Dislike',
+ 'i-rotate-ccw-square': firstactivity.type == 'Announce',
+ }) }}"></i>
+ {{ firstactivity.type|pastaction(Announce = 'reshared') }} by <a href="{{ firstactivity.actor_id }}">{{ firstactivity.actor.data|firstof('name', 'preferredUsername') }}</a>
+ {% endif %}
</div>
+
{% include "f/header.tpl.j2" %}
+
{% if obj.data.name %}
<div class="card-header">
<div class="card-title h5">
@@ 33,15 34,20 @@
</div>
</div>
{% endif %}
+
<div class="card-body">
{{ obj.data.content }}
- {% set attachments = obj.data.attachment %}
- {% for a in attachments %}
- {# TODO: check type and mediaType #}
- <img src="{{ a.url }}" class="img-responsive">
- {% endfor %}
- </div>
+ {% set attachments = obj.data.attachment %}
+ {% set acount = attachments|count %}
+ {% set isodd = acount % 2 %}
+ <div class="img-grid img-grid-{{acount}} img-grid-{{ 'odd' if isodd else 'even' }}">
+ {% for a in attachments %}
+ {# TODO: check type and mediaType #}
+ <img src="{{ a.url }}" class="img-responsive img-area-{{ loop.index }}">
+ {% endfor %}
+ </div>
+ </div>
{% if obj.data.url and obj.data.mediaType and obj.data.mediaType.startswith("image/") %}
<div class="card-image">
@@ 55,12 61,11 @@
{% set qreplies = obj.replies() %}
{% if replies and qreplies.count() > 0 %}
- <div class="card-body">
- {% for obj in qreplies.fetch() %}
- {% include "f/object-small.tpl.j2" %}
- {% endfor %}
- </div>
+ <div class="card-body">
+ {% for obj in qreplies.fetch() %}
+ {% include "f/object-small.tpl.j2" %}
+ {% endfor %}
+ </div>
{% endif %}
-
</div>