feat: tags.pub global hashtag discovery integration (v3.8.0)

- Add setGlobalFollow/removeGlobalFollow/getFollowedTagsWithState to
  followed-tags storage; unfollowTag now preserves global follow state
- Add followTagGloballyController/unfollowTagGloballyController that
  send AP Follow/Undo via Fedify to tags.pub actor URLs
- Register POST /admin/reader/follow-tag-global and unfollow-tag-global
  routes with plugin reference for Fedify access
- Tag timeline controller passes isGloballyFollowed + error query param
- Tag timeline template adds global follow/unfollow buttons with globe
  indicator and inline error display
- Wire GET /api/v1/followed_tags to return real data with globalFollow state
- Add i18n keys: followGlobally, unfollowGlobally, globallyFollowing,
  globalFollowError
This commit is contained in:
Ricardo
2026-03-22 00:22:47 +01:00
parent 0d8b2d0f11
commit 944917b3f0
8 changed files with 237 additions and 11 deletions

View File

@@ -21,6 +21,7 @@
import express from "express";
import { serializeStatus } from "../entities/status.js";
import { parseLimit, buildPaginationQuery, setPaginationHeaders } from "../helpers/pagination.js";
import { getFollowedTagsWithState } from "../../storage/followed-tags.js";
const router = express.Router(); // eslint-disable-line new-cap
@@ -276,8 +277,29 @@ router.get("/api/v1/featured_tags", (req, res) => {
// ─── Followed tags ──────────────────────────────────────────────────────────
router.get("/api/v1/followed_tags", (req, res) => {
res.json([]);
router.get("/api/v1/followed_tags", async (req, res, next) => {
try {
const collections = req.app.locals.mastodonCollections;
if (!collections?.ap_followed_tags) {
return res.json([]);
}
const pluginOptions = req.app.locals.mastodonPluginOptions || {};
const publicationUrl = pluginOptions.publicationUrl || "";
const tags = await getFollowedTagsWithState({ ap_followed_tags: collections.ap_followed_tags });
const response = tags.map((doc) => ({
id: doc._id.toString(),
name: doc.tag,
url: `${publicationUrl.replace(/\/$/, "")}/tags/${doc.tag}`,
history: [],
following: true,
}));
res.json(response);
} catch (error) {
next(error);
}
});
// ─── Suggestions ────────────────────────────────────────────────────────────