* Various Toot! fixes * Use a fallback 1x1 pixel image for missing headers, same as Mastodon. The header and header_static are _not_ optional nor nullable according to the spec. * Try removing some fields which probably shouldn't be set. * Pagination with next/prev.
This commit is contained in:
parent
b03d9f0e12
commit
cc7824394b
|
@ -900,7 +900,13 @@ class Post(StatorModel):
|
||||||
if mention.username
|
if mention.username
|
||||||
],
|
],
|
||||||
"tags": (
|
"tags": (
|
||||||
[{"name": tag, "url": "/tag/{tag}/"} for tag in self.hashtags]
|
[
|
||||||
|
{
|
||||||
|
"name": tag,
|
||||||
|
"url": f"https://{self.author.domain.uri_domain}/tags/{tag}/",
|
||||||
|
}
|
||||||
|
for tag in self.hashtags
|
||||||
|
]
|
||||||
if self.hashtags
|
if self.hashtags
|
||||||
else []
|
else []
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,4 +1,50 @@
|
||||||
|
import dataclasses
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.http import HttpRequest
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class PaginationResult:
|
||||||
|
#: A list of objects that matched the pagination query.
|
||||||
|
results: list[models.Model]
|
||||||
|
#: The actual applied limit, which may be different from what was requested.
|
||||||
|
limit: int
|
||||||
|
sort_attribute: str
|
||||||
|
|
||||||
|
def next(self, request: HttpRequest, allowed_params: list[str]):
|
||||||
|
"""
|
||||||
|
Returns a URL to the next page of results.
|
||||||
|
"""
|
||||||
|
if not self.results:
|
||||||
|
return None
|
||||||
|
|
||||||
|
params = self.filter_params(request, allowed_params)
|
||||||
|
params["max_id"] = self.results[-1].pk
|
||||||
|
|
||||||
|
return f"{request.build_absolute_uri(request.path)}?{urllib.parse.urlencode(params)}"
|
||||||
|
|
||||||
|
def prev(self, request: HttpRequest, allowed_params: list[str]):
|
||||||
|
"""
|
||||||
|
Returns a URL to the previous page of results.
|
||||||
|
"""
|
||||||
|
if not self.results:
|
||||||
|
return None
|
||||||
|
|
||||||
|
params = self.filter_params(request, allowed_params)
|
||||||
|
params["min_id"] = self.results[0].pk
|
||||||
|
|
||||||
|
return f"{request.build_absolute_uri(request.path)}?{urllib.parse.urlencode(params)}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def filter_params(request: HttpRequest, allowed_params: list[str]):
|
||||||
|
params = {}
|
||||||
|
for key in allowed_params:
|
||||||
|
value = request.GET.get(key, None)
|
||||||
|
if value:
|
||||||
|
params[key] = value
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
class MastodonPaginator:
|
class MastodonPaginator:
|
||||||
|
@ -34,6 +80,7 @@ class MastodonPaginator:
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
**{self.sort_attribute + "__lt": getattr(anchor, self.sort_attribute)}
|
**{self.sort_attribute + "__lt": getattr(anchor, self.sort_attribute)}
|
||||||
)
|
)
|
||||||
|
|
||||||
if since_id:
|
if since_id:
|
||||||
try:
|
try:
|
||||||
anchor = self.anchor_model.objects.get(pk=since_id)
|
anchor = self.anchor_model.objects.get(pk=since_id)
|
||||||
|
@ -42,9 +89,10 @@ class MastodonPaginator:
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
**{self.sort_attribute + "__gt": getattr(anchor, self.sort_attribute)}
|
**{self.sort_attribute + "__gt": getattr(anchor, self.sort_attribute)}
|
||||||
)
|
)
|
||||||
|
|
||||||
if min_id:
|
if min_id:
|
||||||
# Min ID requires items _immediately_ newer than specified, so we
|
# Min ID requires items _immediately_ newer than specified, so we
|
||||||
# invert the ordering to accomodate
|
# invert the ordering to accommodate
|
||||||
try:
|
try:
|
||||||
anchor = self.anchor_model.objects.get(pk=min_id)
|
anchor = self.anchor_model.objects.get(pk=min_id)
|
||||||
except self.anchor_model.DoesNotExist:
|
except self.anchor_model.DoesNotExist:
|
||||||
|
@ -54,4 +102,10 @@ class MastodonPaginator:
|
||||||
).order_by(self.sort_attribute)
|
).order_by(self.sort_attribute)
|
||||||
else:
|
else:
|
||||||
queryset = queryset.order_by("-" + self.sort_attribute)
|
queryset = queryset.order_by("-" + self.sort_attribute)
|
||||||
return list(queryset[: min(limit or self.default_limit, self.max_limit)])
|
|
||||||
|
limit = min(limit or self.default_limit, self.max_limit)
|
||||||
|
return PaginationResult(
|
||||||
|
results=list(queryset[:limit]),
|
||||||
|
limit=limit,
|
||||||
|
sort_attribute=self.sort_attribute,
|
||||||
|
)
|
||||||
|
|
|
@ -44,8 +44,8 @@ class Account(Schema):
|
||||||
group: bool
|
group: bool
|
||||||
discoverable: bool
|
discoverable: bool
|
||||||
moved: Union[None, bool, "Account"]
|
moved: Union[None, bool, "Account"]
|
||||||
suspended: bool
|
suspended: bool = False
|
||||||
limited: bool
|
limited: bool = False
|
||||||
created_at: str
|
created_at: str
|
||||||
last_status_at: str | None = Field(...)
|
last_status_at: str | None = Field(...)
|
||||||
statuses_count: int
|
statuses_count: int
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from ninja import Field
|
from ninja import Field
|
||||||
|
|
||||||
|
@ -91,7 +92,8 @@ def account(request, id: str):
|
||||||
@api_router.get("/v1/accounts/{id}/statuses", response=list[schemas.Status])
|
@api_router.get("/v1/accounts/{id}/statuses", response=list[schemas.Status])
|
||||||
@identity_required
|
@identity_required
|
||||||
def account_statuses(
|
def account_statuses(
|
||||||
request,
|
request: HttpRequest,
|
||||||
|
response: HttpResponse,
|
||||||
id: str,
|
id: str,
|
||||||
exclude_reblogs: bool = False,
|
exclude_reblogs: bool = False,
|
||||||
exclude_replies: bool = False,
|
exclude_replies: bool = False,
|
||||||
|
@ -119,16 +121,37 @@ def account_statuses(
|
||||||
queryset = queryset.filter(attachments__pk__isnull=False)
|
queryset = queryset.filter(attachments__pk__isnull=False)
|
||||||
if tagged:
|
if tagged:
|
||||||
queryset = queryset.tagged_with(tagged)
|
queryset = queryset.tagged_with(tagged)
|
||||||
|
|
||||||
paginator = MastodonPaginator(Post)
|
paginator = MastodonPaginator(Post)
|
||||||
posts = paginator.paginate(
|
pager = paginator.paginate(
|
||||||
queryset,
|
queryset,
|
||||||
min_id=min_id,
|
min_id=min_id,
|
||||||
max_id=max_id,
|
max_id=max_id,
|
||||||
since_id=since_id,
|
since_id=since_id,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
)
|
)
|
||||||
interactions = PostInteraction.get_post_interactions(posts, request.identity)
|
|
||||||
return [post.to_mastodon_json(interactions=interactions) for post in queryset]
|
if pager.results:
|
||||||
|
params = [
|
||||||
|
"limit",
|
||||||
|
"id",
|
||||||
|
"exclude_reblogs",
|
||||||
|
"exclude_replies",
|
||||||
|
"only_media",
|
||||||
|
"pinned",
|
||||||
|
"tagged",
|
||||||
|
]
|
||||||
|
response.headers["Link"] = ", ".join(
|
||||||
|
(
|
||||||
|
f'<{pager.next(request, params)}>; rel="next"',
|
||||||
|
f'<{pager.prev(request, params)}>; rel="prev"',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
interactions = PostInteraction.get_post_interactions(
|
||||||
|
pager.results, request.identity
|
||||||
|
)
|
||||||
|
return [post.to_mastodon_json(interactions=interactions) for post in pager.results]
|
||||||
|
|
||||||
|
|
||||||
@api_router.post("/v1/accounts/{id}/follow", response=schemas.Relationship)
|
@api_router.post("/v1/accounts/{id}/follow", response=schemas.Relationship)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
|
||||||
from activities.models import PostInteraction, TimelineEvent
|
from activities.models import PostInteraction, TimelineEvent
|
||||||
from activities.services import TimelineService
|
from activities.services import TimelineService
|
||||||
from api import schemas
|
from api import schemas
|
||||||
|
@ -9,7 +11,8 @@ from api.views.base import api_router
|
||||||
@api_router.get("/v1/notifications", response=list[schemas.Notification])
|
@api_router.get("/v1/notifications", response=list[schemas.Notification])
|
||||||
@identity_required
|
@identity_required
|
||||||
def notifications(
|
def notifications(
|
||||||
request,
|
request: HttpRequest,
|
||||||
|
response: HttpResponse,
|
||||||
max_id: str | None = None,
|
max_id: str | None = None,
|
||||||
since_id: str | None = None,
|
since_id: str | None = None,
|
||||||
min_id: str | None = None,
|
min_id: str | None = None,
|
||||||
|
@ -33,15 +36,27 @@ def notifications(
|
||||||
[base_types[r] for r in requested_types]
|
[base_types[r] for r in requested_types]
|
||||||
)
|
)
|
||||||
paginator = MastodonPaginator(TimelineEvent)
|
paginator = MastodonPaginator(TimelineEvent)
|
||||||
events = paginator.paginate(
|
pager = paginator.paginate(
|
||||||
queryset,
|
queryset,
|
||||||
min_id=min_id,
|
min_id=min_id,
|
||||||
max_id=max_id,
|
max_id=max_id,
|
||||||
since_id=since_id,
|
since_id=since_id,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
)
|
)
|
||||||
interactions = PostInteraction.get_event_interactions(events, request.identity)
|
|
||||||
|
if pager.results:
|
||||||
|
params = ["limit", "account_id"]
|
||||||
|
response.headers["Link"] = ", ".join(
|
||||||
|
(
|
||||||
|
f'<{pager.next(request, params)}>; rel="next"',
|
||||||
|
f'<{pager.prev(request, params)}>; rel="prev"',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
interactions = PostInteraction.get_event_interactions(
|
||||||
|
pager.results, request.identity
|
||||||
|
)
|
||||||
return [
|
return [
|
||||||
event.to_mastodon_notification_json(interactions=interactions)
|
event.to_mastodon_notification_json(interactions=interactions)
|
||||||
for event in events
|
for event in pager.results
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
|
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||||
|
|
||||||
from activities.models import Post, PostInteraction
|
from activities.models import Post, PostInteraction
|
||||||
from activities.services import TimelineService
|
from activities.services import TimelineService
|
||||||
from api import schemas
|
from api import schemas
|
||||||
from api.decorators import identity_required
|
from api.decorators import identity_required
|
||||||
from api.pagination import MastodonPaginator
|
from api.pagination import MastodonPaginator
|
||||||
from api.views.base import api_router
|
from api.views.base import api_router
|
||||||
|
from core.models import Config
|
||||||
|
|
||||||
|
|
||||||
@api_router.get("/v1/timelines/home", response=list[schemas.Status])
|
@api_router.get("/v1/timelines/home", response=list[schemas.Status])
|
||||||
@identity_required
|
@identity_required
|
||||||
def home(
|
def home(
|
||||||
request,
|
request: HttpRequest,
|
||||||
|
response: HttpResponse,
|
||||||
max_id: str | None = None,
|
max_id: str | None = None,
|
||||||
since_id: str | None = None,
|
since_id: str | None = None,
|
||||||
min_id: str | None = None,
|
min_id: str | None = None,
|
||||||
|
@ -17,24 +21,35 @@ def home(
|
||||||
):
|
):
|
||||||
paginator = MastodonPaginator(Post)
|
paginator = MastodonPaginator(Post)
|
||||||
queryset = TimelineService(request.identity).home()
|
queryset = TimelineService(request.identity).home()
|
||||||
events = paginator.paginate(
|
pager = paginator.paginate(
|
||||||
queryset,
|
queryset,
|
||||||
min_id=min_id,
|
min_id=min_id,
|
||||||
max_id=max_id,
|
max_id=max_id,
|
||||||
since_id=since_id,
|
since_id=since_id,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
)
|
)
|
||||||
interactions = PostInteraction.get_event_interactions(events, request.identity)
|
interactions = PostInteraction.get_event_interactions(
|
||||||
|
pager.results, request.identity
|
||||||
|
)
|
||||||
|
|
||||||
|
if pager.results:
|
||||||
|
response.headers["Link"] = ", ".join(
|
||||||
|
(
|
||||||
|
f"<{pager.next(request, ['limit'])}>; rel=\"next\"",
|
||||||
|
f"<{pager.prev(request, ['limit'])}>; rel=\"prev\"",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
event.subject_post.to_mastodon_json(interactions=interactions)
|
event.subject_post.to_mastodon_json(interactions=interactions)
|
||||||
for event in events
|
for event in pager.results
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@api_router.get("/v1/timelines/public", response=list[schemas.Status])
|
@api_router.get("/v1/timelines/public", response=list[schemas.Status])
|
||||||
@identity_required
|
|
||||||
def public(
|
def public(
|
||||||
request,
|
request: HttpRequest,
|
||||||
|
response: HttpResponse,
|
||||||
local: bool = False,
|
local: bool = False,
|
||||||
remote: bool = False,
|
remote: bool = False,
|
||||||
only_media: bool = False,
|
only_media: bool = False,
|
||||||
|
@ -43,6 +58,9 @@ def public(
|
||||||
min_id: str | None = None,
|
min_id: str | None = None,
|
||||||
limit: int = 20,
|
limit: int = 20,
|
||||||
):
|
):
|
||||||
|
if not request.identity and not Config.system.public_timeline:
|
||||||
|
return JsonResponse({"error": "public timeline is disabled"}, status=422)
|
||||||
|
|
||||||
if local:
|
if local:
|
||||||
queryset = TimelineService(request.identity).local()
|
queryset = TimelineService(request.identity).local()
|
||||||
else:
|
else:
|
||||||
|
@ -52,21 +70,34 @@ def public(
|
||||||
if only_media:
|
if only_media:
|
||||||
queryset = queryset.filter(attachments__id__isnull=True)
|
queryset = queryset.filter(attachments__id__isnull=True)
|
||||||
paginator = MastodonPaginator(Post)
|
paginator = MastodonPaginator(Post)
|
||||||
posts = paginator.paginate(
|
pager = paginator.paginate(
|
||||||
queryset,
|
queryset,
|
||||||
min_id=min_id,
|
min_id=min_id,
|
||||||
max_id=max_id,
|
max_id=max_id,
|
||||||
since_id=since_id,
|
since_id=since_id,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
)
|
)
|
||||||
interactions = PostInteraction.get_post_interactions(posts, request.identity)
|
|
||||||
return [post.to_mastodon_json(interactions=interactions) for post in posts]
|
if pager.results:
|
||||||
|
params = ["limit", "local", "remote", "only_media"]
|
||||||
|
response.headers["Link"] = ", ".join(
|
||||||
|
(
|
||||||
|
f'<{pager.next(request, params)}>; rel="next"',
|
||||||
|
f'<{pager.prev(request, params)}>; rel="prev"',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
interactions = PostInteraction.get_post_interactions(
|
||||||
|
pager.results, request.identity
|
||||||
|
)
|
||||||
|
return [post.to_mastodon_json(interactions=interactions) for post in pager.results]
|
||||||
|
|
||||||
|
|
||||||
@api_router.get("/v1/timelines/tag/{hashtag}", response=list[schemas.Status])
|
@api_router.get("/v1/timelines/tag/{hashtag}", response=list[schemas.Status])
|
||||||
@identity_required
|
@identity_required
|
||||||
def hashtag(
|
def hashtag(
|
||||||
request,
|
request: HttpRequest,
|
||||||
|
response: HttpResponse,
|
||||||
hashtag: str,
|
hashtag: str,
|
||||||
local: bool = False,
|
local: bool = False,
|
||||||
only_media: bool = False,
|
only_media: bool = False,
|
||||||
|
@ -83,21 +114,34 @@ def hashtag(
|
||||||
if only_media:
|
if only_media:
|
||||||
queryset = queryset.filter(attachments__id__isnull=True)
|
queryset = queryset.filter(attachments__id__isnull=True)
|
||||||
paginator = MastodonPaginator(Post)
|
paginator = MastodonPaginator(Post)
|
||||||
posts = paginator.paginate(
|
pager = paginator.paginate(
|
||||||
queryset,
|
queryset,
|
||||||
min_id=min_id,
|
min_id=min_id,
|
||||||
max_id=max_id,
|
max_id=max_id,
|
||||||
since_id=since_id,
|
since_id=since_id,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
)
|
)
|
||||||
interactions = PostInteraction.get_post_interactions(posts, request.identity)
|
|
||||||
return [post.to_mastodon_json(interactions=interactions) for post in posts]
|
if pager.results:
|
||||||
|
params = ["limit", "local", "hashtag", "only_media"]
|
||||||
|
response.headers["Link"] = ", ".join(
|
||||||
|
(
|
||||||
|
f'<{pager.next(request, params)}>; rel="next"',
|
||||||
|
f'<{pager.prev(request, params)}>; rel="prev"',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
interactions = PostInteraction.get_post_interactions(
|
||||||
|
pager.results, request.identity
|
||||||
|
)
|
||||||
|
return [post.to_mastodon_json(interactions=interactions) for post in pager.results]
|
||||||
|
|
||||||
|
|
||||||
@api_router.get("/v1/conversations", response=list[schemas.Status])
|
@api_router.get("/v1/conversations", response=list[schemas.Status])
|
||||||
@identity_required
|
@identity_required
|
||||||
def conversations(
|
def conversations(
|
||||||
request,
|
request: HttpRequest,
|
||||||
|
response: HttpResponse,
|
||||||
max_id: str | None = None,
|
max_id: str | None = None,
|
||||||
since_id: str | None = None,
|
since_id: str | None = None,
|
||||||
min_id: str | None = None,
|
min_id: str | None = None,
|
||||||
|
|
|
@ -408,7 +408,7 @@ schemas = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.Z"
|
||||||
DATETIME_TZ_FORMAT = "%Y-%m-%dT%H:%M:%S+00:00"
|
DATETIME_TZ_FORMAT = "%Y-%m-%dT%H:%M:%S+00:00"
|
||||||
DATETIME_MS_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
|
DATETIME_MS_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
|
||||||
|
|
||||||
|
@ -497,7 +497,10 @@ def get_str_or_id(value: str | dict | None) -> str | None:
|
||||||
|
|
||||||
|
|
||||||
def format_ld_date(value: datetime.datetime) -> str:
|
def format_ld_date(value: datetime.datetime) -> str:
|
||||||
return value.strftime(DATETIME_FORMAT)
|
# We chop the timestamp to be identical to the timestamps returned by
|
||||||
|
# Mastodon's API, because some clients like Toot! (for iOS) are especially
|
||||||
|
# picky about timestamp parsing.
|
||||||
|
return f"{value.strftime(DATETIME_MS_FORMAT)[:-4]}Z"
|
||||||
|
|
||||||
|
|
||||||
def parse_ld_date(value: str | None) -> datetime.datetime | None:
|
def parse_ld_date(value: str | None) -> datetime.datetime | None:
|
||||||
|
|
|
@ -221,6 +221,7 @@ class Config(models.Model):
|
||||||
identity_max_per_user: int = 5
|
identity_max_per_user: int = 5
|
||||||
identity_max_age: int = 24 * 60 * 60
|
identity_max_age: int = 24 * 60 * 60
|
||||||
inbox_message_purge_after: int = 24 * 60 * 60
|
inbox_message_purge_after: int = 24 * 60 * 60
|
||||||
|
public_timeline: bool = True
|
||||||
|
|
||||||
hashtag_unreviewed_are_public: bool = True
|
hashtag_unreviewed_are_public: bool = True
|
||||||
hashtag_stats_max_age: int = 60 * 60
|
hashtag_stats_max_age: int = 60 * 60
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 81 B |
|
@ -801,6 +801,8 @@ class Identity(StatorModel):
|
||||||
from activities.models import Emoji
|
from activities.models import Emoji
|
||||||
|
|
||||||
header_image = self.local_image_url()
|
header_image = self.local_image_url()
|
||||||
|
missing = StaticAbsoluteUrl("img/missing.png").absolute
|
||||||
|
|
||||||
metadata_value_text = (
|
metadata_value_text = (
|
||||||
" ".join([m["value"] for m in self.metadata]) if self.metadata else ""
|
" ".join([m["value"] for m in self.metadata]) if self.metadata else ""
|
||||||
)
|
)
|
||||||
|
@ -810,14 +812,14 @@ class Identity(StatorModel):
|
||||||
return {
|
return {
|
||||||
"id": self.pk,
|
"id": self.pk,
|
||||||
"username": self.username or "",
|
"username": self.username or "",
|
||||||
"acct": self.username if self.local else self.handle,
|
"acct": self.handle,
|
||||||
"url": self.absolute_profile_uri() or "",
|
"url": self.absolute_profile_uri() or "",
|
||||||
"display_name": self.name or "",
|
"display_name": self.name or "",
|
||||||
"note": self.summary or "",
|
"note": self.summary or "",
|
||||||
"avatar": self.local_icon_url().absolute,
|
"avatar": self.local_icon_url().absolute,
|
||||||
"avatar_static": self.local_icon_url().absolute,
|
"avatar_static": self.local_icon_url().absolute,
|
||||||
"header": header_image.absolute if header_image else None,
|
"header": header_image.absolute if header_image else missing,
|
||||||
"header_static": header_image.absolute if header_image else None,
|
"header_static": header_image.absolute if header_image else missing,
|
||||||
"locked": False,
|
"locked": False,
|
||||||
"fields": (
|
"fields": (
|
||||||
[
|
[
|
||||||
|
|
|
@ -51,10 +51,6 @@ class BasicSettings(AdminSettingsPage):
|
||||||
"help_text": "Displayed on the homepage and the about page.\nUse Markdown for formatting.",
|
"help_text": "Displayed on the homepage and the about page.\nUse Markdown for formatting.",
|
||||||
"display": "textarea",
|
"display": "textarea",
|
||||||
},
|
},
|
||||||
"site_frontpage_posts": {
|
|
||||||
"title": "Show Posts On Front Page",
|
|
||||||
"help_text": "Whether to show some recent posts on the logged-out homepage.",
|
|
||||||
},
|
|
||||||
"site_icon": {
|
"site_icon": {
|
||||||
"title": "Site Icon",
|
"title": "Site Icon",
|
||||||
"help_text": "Minimum size 64x64px. Should be square.",
|
"help_text": "Minimum size 64x64px. Should be square.",
|
||||||
|
@ -93,13 +89,20 @@ class BasicSettings(AdminSettingsPage):
|
||||||
"title": "Unreviewed Emoji Are Public",
|
"title": "Unreviewed Emoji Are Public",
|
||||||
"help_text": "Public Emoji may appear as images, instead of shortcodes",
|
"help_text": "Public Emoji may appear as images, instead of shortcodes",
|
||||||
},
|
},
|
||||||
|
"public_timeline": {
|
||||||
|
"title": "Public Timeline",
|
||||||
|
"help_text": "If enabled, allows anonymous access to the public timeline",
|
||||||
|
},
|
||||||
|
"site_frontpage_posts": {
|
||||||
|
"title": "Show Public Timeline On Front Page",
|
||||||
|
"help_text": "Whether to show some recent posts on the logged-out homepage",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
layout = {
|
layout = {
|
||||||
"Branding": [
|
"Branding": [
|
||||||
"site_name",
|
"site_name",
|
||||||
"site_about",
|
"site_about",
|
||||||
"site_frontpage_posts",
|
|
||||||
"site_icon",
|
"site_icon",
|
||||||
"site_banner",
|
"site_banner",
|
||||||
"highlight_color",
|
"highlight_color",
|
||||||
|
@ -115,6 +118,10 @@ class BasicSettings(AdminSettingsPage):
|
||||||
"hashtag_unreviewed_are_public",
|
"hashtag_unreviewed_are_public",
|
||||||
"emoji_unreviewed_are_public",
|
"emoji_unreviewed_are_public",
|
||||||
],
|
],
|
||||||
|
"Timelines": [
|
||||||
|
"public_timeline",
|
||||||
|
"site_frontpage_posts",
|
||||||
|
],
|
||||||
"Identities": [
|
"Identities": [
|
||||||
"identity_max_per_user",
|
"identity_max_per_user",
|
||||||
"identity_min_length",
|
"identity_min_length",
|
||||||
|
|
Loading…
Reference in New Issue