Tweak design and AP tag supports

main
Thomas Sileo 2022-07-01 19:35:34 +02:00
parent 9a4643fa3e
commit d164d6d2dd
8 changed files with 189 additions and 66 deletions

View File

@ -551,15 +551,35 @@ async def tag_by_name(
db_session: AsyncSession = Depends(get_db_session),
_: httpsig.HTTPSigInfo = Depends(httpsig.httpsig_checker),
) -> ActivityPubResponse | templates.TemplateResponse:
where = [
models.TaggedOutboxObject.tag == tag,
models.OutboxObject.visibility == ap.VisibilityEnum.PUBLIC,
models.OutboxObject.is_deleted.is_(False),
]
tagged_count = await db_session.scalar(
select(func.count(models.OutboxObject.id))
.join(models.TaggedOutboxObject)
.where(*where)
)
if not tagged_count:
raise HTTPException(status_code=404)
outbox_objects = await db_session.execute(
select(models.OutboxObject.ap_id)
.join(models.TaggedOutboxObject)
.where(*where)
.order_by(models.OutboxObject.ap_published_at.desc())
.limit(20)
)
# TODO(ts): implement HTML version
# if is_activitypub_requested(request):
return ActivityPubResponse(
{
"@context": ap.AS_CTX, # XXX: extended ctx?
"@context": ap.AS_CTX,
"id": BASE_URL + f"/t/{tag}",
"type": "OrderedCollection",
"totalItems": 0,
"orderedItems": [],
"totalItems": tagged_count,
"orderedItems": [outbox_object.ap_id for outbox_object in outbox_objects],
}
)
@ -876,7 +896,8 @@ async def robots_file():
return """User-agent: *
Disallow: /followers
Disallow: /following
Disallow: /admin"""
Disallow: /admin
Disallow: /remote_follow"""
async def _get_outbox_for_feed(db_session: AsyncSession) -> list[models.OutboxObject]:

View File

@ -1,20 +1,92 @@
$font-stack: Helvetica, sans-serif;
$background: #002B36; // solarized background color
// font-family: Inconsolata, monospace;
$primary-color: #e14eea;
$secondary-color: #32cd32;
$muted-color: #586e75; // solarized comment text
// #93a1a1; solarized body text
body {
font-family: $font-stack;
font-size: 20px;
line-height: 32px;
background: $background;
color: #ccc;
margin: 0;
padding: 0;
display: flex;
min-height: 100vh;
flex-direction: column;
}
a {
text-decoration: none;
}
.activity-main {
a {
color: #ddd;
}
}
.activity-wrap {
}
code, pre {
color: #859900; // #cb4b16; // #268bd2; // #2aa198;
font-family: 'Inconsolata, monospace';
}
header {
.title {
font-size: 1.3em;
text-decoration: none;
.handle {
font-size: 0.85em;
color: #93a1a1;
}
}
.counter {
color: #93a1a1;
}
}
.purple {
color: #e14eea;
}
a {
color: #e14eea;
}
.green, a:hover {
color: #32cd32;
}
#main {
flex: 1;
}
main {
max-width: 800px;
margin: 20px auto;
width: 100%;
max-width: 960px;
margin: 30px auto;
}
footer {
max-width: 800px;
width: 100%;
max-width: 960px;
margin: 20px auto;
color: #93a1a1;
}
.actor-box {
display: flex;
column-gap: 20px;
margin:20px 0 10px 0;
.icon-box {
flex: 0 0 50px;
}
.actor-handle {
font-size: 0.85em;
line-height: 1em;
color: #93a1a1;
}
.actor-icon {
margin-top: 5px;
max-width: 50px;
}
}
#notifications, #followers, #following {
ul {
@ -26,47 +98,47 @@ footer {
display: inline-block;
}
}
.actor-box {
a {
text-decoration: none;
#admin {
}
.activity-bar {
input[type=submit], button {
font-size: 20px;
line-height: 32px;
font-family: "Inconsolata, monospace";
background: none!important;
border: none;
padding: 0!important;
cursor: pointer;
color: #e14eea;
}
input[type=submit]:hover, button:hover {
color: #32cd32;
}
}
#admin {
.navbar {
display: grid;
grid-template-rows: auto;
grid-template-columns: 1fr;
grid-auto-flow: dense;
justify-items: stretch;
align-items: stretch;
column-gap: 20px;
}
.logo {
grid-column:-3;
padding: 5px;
}
.menus {
display: flex;
flex-direction: row;
justify-content: start;
grid-column: 1;
}
.menus * {
padding: 5px;
}
}
nav.flexbox {
display: flex;
justify-content: space-between;
align-items: center;
input[type=submit] {
font-size: 20px;
line-height: 32px;
font-family: "Inconsolata, monospace";
background: none!important;
border: none;
padding: 0!important;
cursor: pointer;
color: #e14eea;
}
input[type=submit]:hover {
color: #32cd32;
}
ul {
display: flex;
flex-wrap: wrap;
align-items: center;
list-style-type: none;
margin: 0;
@ -81,12 +153,13 @@ nav.flexbox {
margin-right: 0px;
}
}
}
#admin {
a.active {
font-weight: bold;
a {
text-decoration: none;
}
a.active {
color: #32cd32;
font-weight: bold;
}
}
@ -94,12 +167,11 @@ nav.flexbox {
margin: 0 auto;
padding: 30px 0;
.actor-icon {
width:48px;
width: 50px;
margin-right: 15px;
img {
max-width: 48px;
}
margin-top: 5px;
}
.activity-content {
display: flex;
align-items:flex-start;
@ -112,6 +184,9 @@ nav.flexbox {
font-weight:normal;
margin-left: 5px;
}
.actor-handle {
color: #93a1a1;
}
.activity-date { float:right; }
}
}

View File

@ -18,7 +18,7 @@
</select>
</p>
{% for emoji in emojis %}
<span class="ji">{{ emoji | emojify | safe }}</span>
<span class="ji">{{ emoji | emojify(True) | safe }}</span>
{% endfor %}
{% for emoji in custom_emojis %}
<span class="ji"><img src="{{ emoji.icon.url }}" alt="{{ emoji.name }}" title="{{ emoji.name }}" class="custom-emoji"></span>

View File

@ -3,8 +3,8 @@
<div class="h-card p-author">
<data class="u-photo" value="{{ local_actor.icon_url }}"></data>
<a href="{{ local_actor.url }}" class="u-url u-uid no-hover title">
<span style="font-size:1.1em;">{{ local_actor.name }}</span>
<span style="font-size:0.85em;" class="p-name subtitle-username">{{ local_actor.handle }}</span>
<span class="name">{{ local_actor.name }}</span>
<span class="p-name handle">{{ local_actor.handle }}</span>
</a>
<div class="p-note summary">
@ -22,8 +22,8 @@
<nav class="flexbox">
<ul>
<li>{{ header_link("index", "Notes") }}</li>
<li>{{ header_link("followers", "Followers") }} <span>{{ followers_count }}</span></li>
<li>{{ header_link("following", "Following") }} <span>{{ following_count }}</span></li>
<li>{{ header_link("followers", "Followers") }} <span class="counter">{{ followers_count }}</span></li>
<li>{{ header_link("following", "Following") }} <span class="counter">{{ following_count }}</span></li>
<li>{{ header_link("get_remote_follow", "Remote follow") }}</li>
</ul>
</nav>

View File

@ -2,6 +2,7 @@
{% extends "layout.html" %}
{% block head %}
{% if outbox_object %}
<link rel="alternate" href="{{ request.url }}" type="application/activity+json">
<meta name="description" content="{{ outbox_object.content | html2text | trim | truncate(50) }}">
<meta content="article" property="og:type" />
@ -11,6 +12,7 @@
<meta content="{{ outbox_object.content | html2text | trim | truncate(50) }}" property="og:description" />
<meta content="{{ local_actor.icon_url }}" property="og:image" />
<meta content="summary" property="twitter:card" />
{% endif %}
{% endblock %}
{% block content %}

View File

@ -136,13 +136,13 @@
{% macro display_actor(actor, actors_metadata) %}
{% set metadata = actors_metadata.get(actor.ap_id) %}
<div style="display: flex;column-gap: 20px;margin:20px 0 10px 0;" class="actor-box h-card p-author">
<div style="flex: 0 0 48px;">
<img src="{{ actor.resized_icon_url }}" style="max-width:45px;">
<div class="actor-box h-card p-author">
<div class="icon-box">
<img src="{{ actor.resized_icon_url }}" class="actor-icon">
</div>
<a href="{{ actor.url }}" class="u-url" style="">
<div><strong>{{ actor.display_name | clean_html(actor) | safe }}</strong></div>
<div class="p-name">{{ actor.handle }}</div>
<div class="actor-handle p-name">{{ actor.handle }}</div>
</a>
</div>
{% if is_admin and metadata %}
@ -171,14 +171,13 @@
{% macro display_og_meta(object) %}
{% if object.og_meta %}
{% for og_meta in object.og_meta %}
<div style="display:flex;column-gap: 20px;margin:20px 0;">
<div class="activity-og-meta" style="display:flex;column-gap: 20px;margin:20px 0;">
{% if og_meta.image %}
<div>
<img src="{{ og_meta.image | media_proxy_url }}" style="max-width:200px;">
</div>
<div>
<a href="{{ og_meta.url }}">{{ og_meta.title }}</a>
{% if og_meta.description %}<p>{{ og_meta.description }}</p>{% endif %}
<small style="display:block;">{{ og_meta.site_name }}</small>
</div>
{% endif %}
@ -279,7 +278,7 @@
<img src="{{ object.actor.resized_icon_url }}" alt="" class="actor-icon">
<div class="activity-header">
<strong>{{ object.actor.display_name }}</strong>
<a href="{{ object.actor.url}}" class="p-author h-card">{{ object.actor.handle }}</a>
<a href="{{ object.actor.url}}" class="actor-handle p-author h-card">{{ object.actor.handle }}</a>
<span class="activity-date" title="{{ object.ap_published_at.isoformat() }}">
{{ object.visibility.value }}
<a href="{{ object.url }}" class="u-url u-uid"><time class="dt-published" datetime="{{ object.ap_published_at }}">{{ object.ap_published_at | timeago }}</time></a>

View File

@ -16,6 +16,19 @@ from tests import factories
from tests.utils import generate_admin_session_cookies
def test_outbox__no_activities(
db: Session,
client: TestClient,
) -> None:
response = client.get("/outbox", headers={"Accept": ap.AP_CONTENT_TYPE})
assert response.status_code == 200
json_response = response.json()
assert json_response["totalItems"] == 0
assert json_response["orderedItems"] == []
def test_send_follow_request(
db: Session,
client: TestClient,

13
tests/test_tags.py 100644
View File

@ -0,0 +1,13 @@
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from app import activitypub as ap
def test_tags__no_tags(
db: Session,
client: TestClient,
) -> None:
response = client.get("/t/nope", headers={"Accept": ap.AP_CONTENT_TYPE})
assert response.status_code == 404