feat: add syndication target selection to compose form

- Fetch syndication targets from Micropub config
- Display checkboxes for each target in compose form
- Include mp-syndicate-to in Micropub request
- Include optional content/comments for likes and reposts
This commit is contained in:
Ricardo
2026-02-06 20:44:53 +01:00
parent fabadd8585
commit 99cb4de4e8
4 changed files with 79 additions and 6 deletions
+62 -5
View File
@@ -315,6 +315,38 @@ function ensureString(value) {
return String(value); return String(value);
} }
/**
* Fetch syndication targets from Micropub config
* @param {object} application - Indiekit application
* @param {string} token - Auth token
* @returns {Promise<Array>} Syndication targets
*/
async function getSyndicationTargets(application, token) {
try {
const micropubEndpoint = application.micropubEndpoint;
if (!micropubEndpoint) return [];
const micropubUrl = micropubEndpoint.startsWith("http")
? micropubEndpoint
: new URL(micropubEndpoint, application.url).href;
const configUrl = `${micropubUrl}?q=config`;
const configResponse = await fetch(configUrl, {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
},
});
if (!configResponse.ok) return [];
const config = await configResponse.json();
return config["syndicate-to"] || [];
} catch {
return [];
}
}
/** /**
* Compose response form * Compose response form
* @param {object} request - Express request * @param {object} request - Express request
@@ -322,6 +354,8 @@ function ensureString(value) {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export async function compose(request, response) { export async function compose(request, response) {
const { application } = request.app.locals;
// Support both long-form (replyTo) and short-form (reply) query params // Support both long-form (replyTo) and short-form (reply) query params
const { const {
replyTo, replyTo,
@@ -334,12 +368,19 @@ export async function compose(request, response) {
bookmark, bookmark,
} = request.query; } = request.query;
// Fetch syndication targets if user is authenticated
const token = request.session?.access_token;
const syndicationTargets = token
? await getSyndicationTargets(application, token)
: [];
response.render("compose", { response.render("compose", {
title: request.__("microsub.compose.title"), title: request.__("microsub.compose.title"),
replyTo: ensureString(replyTo || reply), replyTo: ensureString(replyTo || reply),
likeOf: ensureString(likeOf || like), likeOf: ensureString(likeOf || like),
repostOf: ensureString(repostOf || repost), repostOf: ensureString(repostOf || repost),
bookmarkOf: ensureString(bookmarkOf || bookmark), bookmarkOf: ensureString(bookmarkOf || bookmark),
syndicationTargets,
baseUrl: request.baseUrl, baseUrl: request.baseUrl,
}); });
} }
@@ -357,6 +398,7 @@ export async function submitCompose(request, response) {
const likeOf = request.body["like-of"]; const likeOf = request.body["like-of"];
const repostOf = request.body["repost-of"]; const repostOf = request.body["repost-of"];
const bookmarkOf = request.body["bookmark-of"]; const bookmarkOf = request.body["bookmark-of"];
const syndicateTo = request.body["mp-syndicate-to"];
// Debug logging // Debug logging
console.info( console.info(
@@ -369,6 +411,7 @@ export async function submitCompose(request, response) {
likeOf, likeOf,
repostOf, repostOf,
bookmarkOf, bookmarkOf,
syndicateTo,
}); });
// Get Micropub endpoint // Get Micropub endpoint
@@ -396,16 +439,22 @@ export async function submitCompose(request, response) {
micropubData.append("h", "entry"); micropubData.append("h", "entry");
if (likeOf) { if (likeOf) {
// Like post (no content needed) // Like post - content is optional comment
micropubData.append("like-of", likeOf); micropubData.append("like-of", likeOf);
if (content && content.trim()) {
micropubData.append("content", content.trim());
}
} else if (repostOf) { } else if (repostOf) {
// Repost (no content needed) // Repost - content is optional comment
micropubData.append("repost-of", repostOf); micropubData.append("repost-of", repostOf);
if (content && content.trim()) {
micropubData.append("content", content.trim());
}
} else if (bookmarkOf) { } else if (bookmarkOf) {
// Bookmark (content optional) // Bookmark - content is optional comment
micropubData.append("bookmark-of", bookmarkOf); micropubData.append("bookmark-of", bookmarkOf);
if (content) { if (content && content.trim()) {
micropubData.append("content", content); micropubData.append("content", content.trim());
} }
} else if (inReplyTo) { } else if (inReplyTo) {
// Reply // Reply
@@ -416,6 +465,14 @@ export async function submitCompose(request, response) {
micropubData.append("content", content || ""); micropubData.append("content", content || "");
} }
// Add syndication targets
if (syndicateTo) {
const targets = Array.isArray(syndicateTo) ? syndicateTo : [syndicateTo];
for (const target of targets) {
micropubData.append("mp-syndicate-to", target);
}
}
// Debug: log what we're sending // Debug: log what we're sending
console.info("[Microsub] Sending to Micropub:", { console.info("[Microsub] Sending to Micropub:", {
url: micropubUrl, url: micropubUrl,
+2
View File
@@ -45,6 +45,8 @@
"content": "What's on your mind?", "content": "What's on your mind?",
"comment": "Add a comment (optional)", "comment": "Add a comment (optional)",
"commentHint": "Your comment will be included when this is syndicated", "commentHint": "Your comment will be included when this is syndicated",
"syndicateTo": "Syndicate to",
"syndicateHint": "Select where to cross-post this",
"submit": "Post", "submit": "Post",
"cancel": "Cancel", "cancel": "Cancel",
"replyTo": "Replying to", "replyTo": "Replying to",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@rmdes/indiekit-endpoint-microsub", "name": "@rmdes/indiekit-endpoint-microsub",
"version": "1.0.14", "version": "1.0.15",
"description": "Microsub endpoint for Indiekit. Enables subscribing to feeds and reading content using the Microsub protocol.", "description": "Microsub endpoint for Indiekit. Enables subscribing to feeds and reading content using the Microsub protocol.",
"keywords": [ "keywords": [
"indiekit", "indiekit",
+14
View File
@@ -72,6 +72,20 @@
<span id="char-count">0</span> characters <span id="char-count">0</span> characters
</div> </div>
{# Syndication targets #}
{% if syndicationTargets and syndicationTargets.length %}
<fieldset class="compose__syndication">
<legend>{{ __("microsub.compose.syndicateTo") }}</legend>
<p class="hint">{{ __("microsub.compose.syndicateHint") }}</p>
{% for target in syndicationTargets %}
<label class="syndication-target">
<input type="checkbox" name="mp-syndicate-to" value="{{ target.uid }}"{% if target.checked %} checked{% endif %}>
<span class="syndication-target__name">{{ target.name }}</span>
</label>
{% endfor %}
</fieldset>
{% endif %}
<div class="button-group"> <div class="button-group">
{{ button({ {{ button({
text: __("microsub.compose.submit") text: __("microsub.compose.submit")