fix: robust mastodon dedup with MutationObserver + URL normalization
Build & Deploy / build-and-deploy (push) Successful in 1m59s
Build & Deploy / build-and-deploy (push) Successful in 1m59s
- MutationObserver set up before fetching so webmention NEW cards added after the dedup pass are also removed immediately (race condition fix) - Normalize /@handle and /users/handle URL variants to same form before comparing — covers Mastodon instances that use either format - Add wm-owner-reply-slot div + Reply link to bsky/mastodon Alpine cards to match build-time webmention card structure
This commit is contained in:
@@ -228,8 +228,12 @@
|
||||
</div>
|
||||
<div class="text-surface-700 dark:text-surface-300 prose dark:prose-invert prose-sm max-w-none"
|
||||
x-text="reply.text"></div>
|
||||
<a :href="reply.url"
|
||||
class="wm-reply-btn hidden text-xs text-primary-600 dark:text-primary-400 hover:underline mt-2"
|
||||
target="_blank" rel="noopener">Reply on Bluesky</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wm-owner-reply-slot ml-13 mt-2"></div>
|
||||
</li>
|
||||
</template>
|
||||
{% endif %}
|
||||
@@ -258,8 +262,12 @@
|
||||
</div>
|
||||
<div class="text-surface-700 dark:text-surface-300 prose dark:prose-invert prose-sm max-w-none"
|
||||
x-html="reply.content"></div>
|
||||
<a :href="reply.url"
|
||||
class="wm-reply-btn hidden text-xs text-primary-600 dark:text-primary-400 hover:underline mt-2"
|
||||
target="_blank" rel="noopener">Reply on Mastodon</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wm-owner-reply-slot ml-13 mt-2"></div>
|
||||
</li>
|
||||
</template>
|
||||
{% endif %}
|
||||
|
||||
+33
-4
@@ -8,7 +8,34 @@ document.addEventListener("alpine:init", () => {
|
||||
mastodonReplies: [],
|
||||
loading: true,
|
||||
|
||||
// Normalized mastodon author URLs for dedup — populated after _fetchMastodon
|
||||
_mastodonAuthorNormed: new Set(),
|
||||
|
||||
// Normalize /@handle and /users/handle to the same form for URL comparison
|
||||
_normalizeActorUrl(url) {
|
||||
return (url || "").replace(/\/(users\/|@)/, "/").replace(/\/$/, "").toLowerCase();
|
||||
},
|
||||
|
||||
async init() {
|
||||
// Watch for webmention cards added AFTER our dedup pass (race condition with webmentions.js)
|
||||
const repliesList = this.$el.querySelector("ul");
|
||||
if (repliesList) {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const m of mutations) {
|
||||
for (const node of m.addedNodes) {
|
||||
if (node.nodeType === 1 && node.dataset.authorUrl) {
|
||||
if (this._mastodonAuthorNormed.has(this._normalizeActorUrl(node.dataset.authorUrl))) {
|
||||
node.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
observer.observe(repliesList, { childList: true });
|
||||
// Disconnect after page is hidden — observer is no longer needed
|
||||
document.addEventListener("visibilitychange", () => observer.disconnect(), { once: true });
|
||||
}
|
||||
|
||||
const tasks = [];
|
||||
if (bskyUrl) tasks.push(this._fetchBsky(bskyUrl));
|
||||
if (mastodonInstance && apIdentity) tasks.push(this._fetchMastodon(postUrl, mastodonInstance, apIdentity));
|
||||
@@ -98,11 +125,13 @@ document.addEventListener("alpine:init", () => {
|
||||
favourites: s.favourites_count || 0,
|
||||
}));
|
||||
|
||||
// Remove webmention cards (build-time or client-fetched) that duplicate mastodon replies.
|
||||
// Webmention cards have data-author-url; Alpine x-for cards do not.
|
||||
const mastodonAuthorUrls = new Set(this.mastodonReplies.map((r) => r.author.url));
|
||||
// Build normalized author URL set for dedup (covers /@handle and /users/handle variants)
|
||||
this._mastodonAuthorNormed = new Set(
|
||||
this.mastodonReplies.map((r) => this._normalizeActorUrl(r.author.url))
|
||||
);
|
||||
// Remove webmention cards (build-time or NEW) that duplicate mastodon replies
|
||||
document.querySelectorAll(".webmention-replies li[data-author-url]").forEach((li) => {
|
||||
if (mastodonAuthorUrls.has(li.dataset.authorUrl)) li.remove();
|
||||
if (this._mastodonAuthorNormed.has(this._normalizeActorUrl(li.dataset.authorUrl))) li.remove();
|
||||
});
|
||||
} catch {
|
||||
// fail silently
|
||||
|
||||
Reference in New Issue
Block a user