d9f26beac5
Key upstream changes merged: - Settings admin page (ap_settings collection, GET/POST controller, Nunjucks template) - ObjectId-based status IDs (fixes same-second collision bug in cursor pagination) - Status edit history stored in ap_status_edits, GET /statuses/:id/history endpoint - Keyword filters for Mastodon API timelines (apply-filters.js middleware) - resolve-reply-ids.js helper for threading - Filter replies from public/local/hashtag timelines - Non-expiring access tokens - Media upload improvements (express-fileupload, bridge IndieAuth token) - Startup gate: defer background tasks until host readiness signal - Own posts exempt from timeline retention cleanup - Various Mastodon API fixes (account IDs, update_credentials, interactions) Conflict resolutions: - pagination.js: adopted upstream's ObjectId approach (removed cursor-based encodeCursor/decodeCursor) - statuses.js: adopted upstream's PUT rewrite (cleaner ownership check, edit history storage via Micropub fetch); kept GET /statuses/:id with pinnedIds/replyIdMap; adopted ObjectId-only findTimelineItemById - compose.js: kept our isDirect/senderActorUrl (DM feature) + added upstream's mediaEndpoint - activitypub-compose.njk: kept DM notice block + added enctype="multipart/form-data" - Removed unused processStatusContent function and Update import from statuses.js Fork-specific changes preserved: - allowPrivateAddress: true in federation-setup.js - Canonical Like activity IDs + Like dispatcher in federation-setup.js - Repost commentary in jf2-to-as2.js - /api/ap-url endpoint in index.js - Direct Follow workaround for tags.pub Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
146 lines
5.4 KiB
Plaintext
146 lines
5.4 KiB
Plaintext
{% extends "layouts/ap-reader.njk" %}
|
|
|
|
{% from "heading/macro.njk" import heading with context %}
|
|
{% from "textarea/macro.njk" import textarea with context %}
|
|
{% from "file-input/macro.njk" import fileInput with context %}
|
|
{% from "tag-input/macro.njk" import tagInput with context %}
|
|
|
|
{% block readercontent %}
|
|
{# Reply context — show the post being replied to #}
|
|
{% if replyContext %}
|
|
<div class="ap-compose__context">
|
|
<div class="ap-compose__context-label">{{ __("activitypub.reader.replyingTo") }}</div>
|
|
{% if replyContext.author %}
|
|
<div class="ap-compose__context-author">
|
|
<a href="{{ replyContext.author.url }}">{{ replyContext.author.name }}</a>
|
|
</div>
|
|
{% endif %}
|
|
{% if replyContext.content and (replyContext.content.html or replyContext.content.text) %}
|
|
<div class="ap-card__content ap-compose__context-text">
|
|
{{ replyContext.content.html | safe if replyContext.content.html else replyContext.content.text | truncate(300) }}
|
|
</div>
|
|
{% endif %}
|
|
<a href="{{ replyTo }}" class="ap-compose__context-link" target="_blank" rel="noopener">{{ replyTo }}</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if isDirect %}
|
|
<div class="ap-compose__dm-notice">
|
|
🔒 {{ __("activitypub.compose.directNotice") if __("activitypub.compose.directNotice") != "activitypub.compose.directNotice" else "Direct message — reply stays private" }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<form method="post" action="{{ mountPath }}/admin/reader/compose" class="ap-compose__form" enctype="multipart/form-data">
|
|
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
{% if replyTo %}
|
|
<input type="hidden" name="in-reply-to" value="{{ replyTo }}">
|
|
{% endif %}
|
|
{% if isDirect %}
|
|
<input type="hidden" name="is-direct" value="true">
|
|
<input type="hidden" name="sender-actor-url" value="{{ senderActorUrl }}">
|
|
{% endif %}
|
|
|
|
{# Content warning toggle + summary #}
|
|
<div class="ap-compose__cw">
|
|
<label class="ap-compose__cw-toggle">
|
|
<input type="checkbox" name="cw-enabled" id="cw-toggle"
|
|
onchange="document.getElementById('cw-text').style.display = this.checked ? 'block' : 'none'">
|
|
{{ __("activitypub.compose.cwLabel") }}
|
|
</label>
|
|
<input type="text" name="summary" id="cw-text" class="ap-compose__cw-input"
|
|
placeholder="{{ __('activitypub.compose.cwPlaceholder') }}"
|
|
style="display: none">
|
|
</div>
|
|
|
|
{# Rich content editor (EasyMDE with media browser) #}
|
|
<div class="ap-compose__editor">
|
|
{{ textarea({
|
|
name: "content",
|
|
label: "Content",
|
|
rows: 8,
|
|
field: {
|
|
attributes: {
|
|
editor: true,
|
|
"editor-endpoint": mediaEndpoint,
|
|
"editor-id": "ap-compose-content",
|
|
"editor-locale": application.locale if application else "en",
|
|
"editor-image-upload": true
|
|
}
|
|
}
|
|
}) }}
|
|
</div>
|
|
|
|
{# Featured image #}
|
|
<div class="ap-compose__media">
|
|
{{ fileInput({
|
|
name: "photo",
|
|
label: "Photo",
|
|
field: {
|
|
attributes: {
|
|
endpoint: mediaEndpoint
|
|
}
|
|
},
|
|
accept: "image/*",
|
|
attributes: {
|
|
placeholder: "https://"
|
|
}
|
|
}) }}
|
|
</div>
|
|
|
|
{# Tags / categories #}
|
|
<div class="ap-compose__tags">
|
|
{{ tagInput({
|
|
name: "category",
|
|
label: "Tags",
|
|
optional: true,
|
|
hint: "Separate with commas"
|
|
}) }}
|
|
</div>
|
|
|
|
{# Visibility — hidden for direct messages #}
|
|
{% if not isDirect %}
|
|
<fieldset class="ap-compose__visibility">
|
|
<legend>{{ __("activitypub.compose.visibilityLabel") }}</legend>
|
|
<label class="ap-compose__visibility-option">
|
|
<input type="radio" name="visibility" value="public" checked>
|
|
{{ __("activitypub.compose.visibilityPublic") }}
|
|
</label>
|
|
<label class="ap-compose__visibility-option">
|
|
<input type="radio" name="visibility" value="unlisted">
|
|
{{ __("activitypub.compose.visibilityUnlisted") }}
|
|
</label>
|
|
<label class="ap-compose__visibility-option">
|
|
<input type="radio" name="visibility" value="followers">
|
|
{{ __("activitypub.compose.visibilityFollowers") }}
|
|
</label>
|
|
</fieldset>
|
|
{% endif %}
|
|
|
|
{# Syndication targets — hidden for direct messages #}
|
|
{% if syndicationTargets.length > 0 and not isDirect %}
|
|
<fieldset class="ap-compose__syndication">
|
|
<legend>{{ __("activitypub.compose.syndicateLabel") }}</legend>
|
|
{% for target in syndicationTargets %}
|
|
<label class="ap-compose__syndication-target">
|
|
<input type="checkbox" name="mp-syndicate-to" value="{{ target.uid }}" {{ "checked" if target.defaultChecked }}>
|
|
{{ target.name }}
|
|
</label>
|
|
{% endfor %}
|
|
</fieldset>
|
|
{% endif %}
|
|
|
|
<div class="ap-compose__actions">
|
|
<button type="submit" class="ap-compose__submit">
|
|
{% if isDirect %}
|
|
🔒 {{ __("activitypub.compose.submitDirect") if __("activitypub.compose.submitDirect") != "activitypub.compose.submitDirect" else "Send direct reply" }}
|
|
{% else %}
|
|
{{ __("activitypub.compose.submitMicropub") }}
|
|
{% endif %}
|
|
</button>
|
|
<a href="{{ mountPath }}/admin/reader/notifications" class="ap-compose__cancel">
|
|
{{ __("activitypub.compose.cancel") }}
|
|
</a>
|
|
</div>
|
|
</form>
|
|
{% endblock %}
|