<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Manage Open Social Web handles not consistent after disabled Federation]]></title><description><![CDATA[<p dir="auto">I've already disabled Federation from ACP on this NodeBB (docker deployed). Register/Login screen no longer have "Manage Open Social Web handles" button. But at topic or category view, click "Log in to reply" or "Log in to Post", still have "Manage Open Social Web handles" link showing up.</p>
<p dir="auto">Answer from AI:</p>
<ul>
<li>The modal is rendered from public/templates/modals/intents/trigger.tpl.</li>
<li>The link text comes from the language key login:manage-social-web-handles.</li>
<li>The click handler is in public/src/modules/intents.js (_intentsHandler), and it is wired up for guest “Log in to reply/post” buttons by:<br />
public/src/client/topic.js<br />
public/src/client/category.js<br />
public/src/client/world.js</li>
</ul>
<p dir="auto">The ACP Federation → Enable ActivityPub toggle (meta.config.activitypubEnabled) already controls the “Manage Open Social Web Handles” button on the /login and /register pages via osw_logins, but the topic/category/world guest modal does not check that flag. That is a core bug.</p>
]]></description><link>https://host1.bosio.info/topic/ada7824f-7876-4762-9da2-b6a4025b3143/manage-open-social-web-handles-not-consistent-after-disabled-federation</link><generator>RSS for Node</generator><lastBuildDate>Tue, 25 Aug 2026 21:39:02 GMT</lastBuildDate><atom:link href="https://host1.bosio.info/topic/ada7824f-7876-4762-9da2-b6a4025b3143.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 29 Jun 2026 08:26:21 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Manage Open Social Web handles not consistent after disabled Federation on Thu, 23 Jul 2026 23:42:25 GMT]]></title><description><![CDATA[<p dir="auto"><a href="https://community.nodebb.org/user/jasonwch" rel="nofollow ugc">@jasonwch</a> try latest develop branch if you don't want to wait for 4.15</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107656</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107656</guid><dc:creator><![CDATA[julian@community.nodebb.org]]></dc:creator><pubDate>Thu, 23 Jul 2026 23:42:25 GMT</pubDate></item><item><title><![CDATA[Reply to Manage Open Social Web handles not consistent after disabled Federation on Thu, 23 Jul 2026 23:40:54 GMT]]></title><description><![CDATA[<p dir="auto"><a href="https://github.com/NodeBB/NodeBB/issues/14396" rel="nofollow ugc">https://github.com/NodeBB/NodeBB/issues/14396</a></p>
<p dir="auto">It's still not at 4.14.2. all reply, post, upvote still open social prompt even though ferderation is off</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107655</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107655</guid><dc:creator><![CDATA[jasonwch@community.nodebb.org]]></dc:creator><pubDate>Thu, 23 Jul 2026 23:40:54 GMT</pubDate></item><item><title><![CDATA[Reply to Manage Open Social Web handles not consistent after disabled Federation on Wed, 15 Jul 2026 09:06:23 GMT]]></title><description><![CDATA[<p dir="auto">&gt; <a href="https://community.nodebb.org/user/julian" rel="nofollow ugc">@julian</a> <a href="https://community.nodebb.org/post/107444" rel="nofollow ugc">said</a>:<br />
&gt;<br />
&gt; the Activity Intents behaviour</p>
<p dir="auto"><s># <strong>EEEHHH? WHAT THE FUCK IS THAT BUTTON?!?!</strong> &lt;img class="not-responsive emoji" src="<a href="https://community.nodebb.org/assets/plugins/nodebb-plugin-emoji/emoji/android/1f635.png?v=32e548a362d" rel="nofollow ugc">https://community.nodebb.org/assets/plugins/nodebb-plugin-emoji/emoji/android/1f635.png?v=32e548a362d</a>" title="<img src="https://host1.bosio.info/assets/plugins/nodebb-plugin-emoji/emoji/android/1f635.png?v=5a1f7d4f361" class="not-responsive emoji emoji-android emoji--dizzy_face" style="height:23px;width:auto;vertical-align:middle" title=":dizzy_face:" alt="😵" />" /&gt; &lt;img class="not-responsive emoji" src="<a href="https://community.nodebb.org/assets/plugins/nodebb-plugin-emoji/emoji/android/1f635.png?v=32e548a362d" rel="nofollow ugc">https://community.nodebb.org/assets/plugins/nodebb-plugin-emoji/emoji/android/1f635.png?v=32e548a362d</a>" title="<img src="https://host1.bosio.info/assets/plugins/nodebb-plugin-emoji/emoji/android/1f635.png?v=5a1f7d4f361" class="not-responsive emoji emoji-android emoji--dizzy_face" style="height:23px;width:auto;vertical-align:middle" title=":dizzy_face:" alt="😵" />" /&gt; &lt;img class="not-responsive emoji" src="<a href="https://community.nodebb.org/assets/plugins/nodebb-plugin-emoji/emoji/android/1f635.png?v=32e548a362d" rel="nofollow ugc">https://community.nodebb.org/assets/plugins/nodebb-plugin-emoji/emoji/android/1f635.png?v=32e548a362d</a>" title="<img src="https://host1.bosio.info/assets/plugins/nodebb-plugin-emoji/emoji/android/1f635.png?v=5a1f7d4f361" class="not-responsive emoji emoji-android emoji--dizzy_face" style="height:23px;width:auto;vertical-align:middle" title=":dizzy_face:" alt="😵" />" /&gt;</s></p>
<p dir="auto"><img src="https://community.nodebb.org/assets/uploads/files/1784106186584-what-the-fuck-gif-2.gif" alt="what-the-fuck-gif-2.gif" class=" img-fluid img-markdown" /></p>
<p dir="auto">Uhm... I meant to say:</p>
<p dir="auto"><strong>"Oops, I'm the administrator of a federated NodeBB server and I've never noticed that button, nor did I understand how to use it. Where can I find documentation that explains how it works?"</strong></p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107580</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107580</guid><dc:creator><![CDATA[informapirata@community.nodebb.org]]></dc:creator><pubDate>Wed, 15 Jul 2026 09:06:23 GMT</pubDate></item><item><title><![CDATA[Reply to Manage Open Social Web handles not consistent after disabled Federation on Tue, 14 Jul 2026 16:04:01 GMT]]></title><description><![CDATA[<p dir="auto"><a href="https://community.nodebb.org/user/jasonwch" rel="nofollow ugc">@jasonwch</a> latest NodeBB should have this patched already, thanks.</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107578</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107578</guid><dc:creator><![CDATA[julian@community.nodebb.org]]></dc:creator><pubDate>Tue, 14 Jul 2026 16:04:01 GMT</pubDate></item><item><title><![CDATA[Reply to Manage Open Social Web handles not consistent after disabled Federation on Tue, 14 Jul 2026 14:02:09 GMT]]></title><description><![CDATA[<p dir="auto"><a href="https://community.nodebb.org/user/julian" rel="nofollow ugc">@julian</a> <a href="https://community.nodebb.org/user/baris" rel="nofollow ugc">@baris</a></p>
<p dir="auto">Hope this help, the following 2 files are patched by me at 4.13.2. Hope this can be adjust with 4.14 then merge to 4.14.1</p>
<pre><code>// location: api.js
'use strict';

const validator = require('validator');
const nconf = require('nconf');

const meta = require('../meta');
const user = require('../user');
const categories = require('../categories');
const plugins = require('../plugins');
const translator = require('../translator');
const languages = require('../languages');
const { generateToken } = require('../middleware/csrf');
const utils = require('../utils');

const apiController = module.exports;

const url = nconf.get('url');
const relative_path = nconf.get('relative_path');
const upload_url = nconf.get('upload_url');
const asset_base_url = nconf.get('asset_base_url');
const socketioTransports = nconf.get('socket.io:transports') || ['polling', 'websocket'];
const socketioOrigins = nconf.get('socket.io:origins');
const websocketAddress = nconf.get('socket.io:address') || '';
const fontawesome_pro = nconf.get('fontawesome:pro') || false;
const fontawesome_styles = utils.getFontawesomeStyles();
const fontawesome_version = utils.getFontawesomeVersion();

apiController.loadConfig = async function (req) {
	const config = {
		url,
		relative_path,
		upload_url,
		asset_base_url,
		assetBaseUrl: asset_base_url, // deprecate in 1.20.x
		siteTitle: validator.escape(String(meta.config.title || meta.config.browserTitle || 'NodeBB')),
		browserTitle: validator.escape(String(meta.config.browserTitle || meta.config.title || 'NodeBB')),
		description: validator.escape(String(meta.config.description || '')),
		keywords: validator.escape(String(meta.config.keywords || '')),
		'brand:logo': validator.escape(String(meta.config['brand:logo'])),
		titleLayout: (meta.config.titleLayout || '{pageTitle} | {browserTitle}').replace(/{/g, '{').replace(/}/g, '}'),
		showSiteTitle: meta.config.showSiteTitle === 1,
		maintenanceMode: meta.config.maintenanceMode === 1,
		postQueue: meta.config.postQueue,
		minimumTitleLength: meta.config.minimumTitleLength,
		maximumTitleLength: meta.config.maximumTitleLength,
		minimumPostLength: meta.config.minimumPostLength,
		maximumPostLength: meta.config.maximumPostLength,
		minimumTagsPerTopic: meta.config.minimumTagsPerTopic || 0,
		maximumTagsPerTopic: meta.config.maximumTagsPerTopic || 5,
		minimumTagLength: meta.config.minimumTagLength || 3,
		maximumTagLength: meta.config.maximumTagLength || 15,
		undoTimeout: meta.config.undoTimeout || 0,
		useOutgoingLinksPage: meta.config.useOutgoingLinksPage === 1,
		outgoingLinksWhitelist: meta.config.useOutgoingLinksPage === 1 ? meta.config['outgoingLinks:whitelist'] : undefined,
		allowGuestHandles: meta.config.allowGuestHandles === 1,
		allowTopicsThumbnail: meta.config.allowTopicsThumbnail === 1,
		usePagination: meta.config.usePagination === 1,
		disableChat: meta.config.disableChat === 1,
		disableChatMessageEditing: meta.config.disableChatMessageEditing === 1,
		maximumChatMessageLength: meta.config.maximumChatMessageLength || 1000,
		socketioTransports,
		socketioOrigins,
		websocketAddress,
		maxReconnectionAttempts: meta.config.maxReconnectionAttempts,
		reconnectionDelay: meta.config.reconnectionDelay,
		topicsPerPage: meta.config.topicsPerPage || 20,
		postsPerPage: meta.config.postsPerPage || 20,
		maximumFileSize: meta.config.maximumFileSize,
		convertPastedImageTo: meta.config.convertPastedImageTo,
		'theme:id': meta.config['theme:id'],
		'theme:src': meta.config['theme:src'],
		defaultLang: meta.config.defaultLang || 'en-GB',
		userLang: req.query.lang ? validator.escape(String(req.query.lang)) : (meta.config.defaultLang || 'en-GB'),
		loggedIn: !!req.user,
		uid: req.uid,
		'cache-buster': meta.config['cache-buster'] || '',
		topicPostSort: meta.config.topicPostSort || 'oldest_to_newest',
		categoryTopicSort: meta.config.categoryTopicSort || 'recently_replied',
		csrf_token: req.uid &amp;gt;= 0 ? generateToken(req) : false,
		searchEnabled: plugins.hooks.hasListeners('filter:search.query'),
		searchDefaultInQuick: meta.config.searchDefaultInQuick || 'titles',
		bootswatchSkin: meta.config.bootswatchSkin || '',
		'composer:showHelpTab': meta.config['composer:showHelpTab'] === 1,
		enablePostHistory: meta.config.enablePostHistory === 1,
		timeagoCutoff: meta.config.timeagoCutoff !== '' ? Math.max(0, parseInt(meta.config.timeagoCutoff, 10)) : meta.config.timeagoCutoff,
		timeagoCodes: languages.timeagoCodes,
		cookies: {
			enabled: meta.config.cookieConsentEnabled === 1,
			message: translator.escape(validator.escape(meta.config.cookieConsentMessage || '&lsqb;&lsqb;global:cookies.message&rsqb;&rsqb;')).replace(/\\/g, '\\\\'),
			dismiss: translator.escape(validator.escape(meta.config.cookieConsentDismiss || '&lsqb;&lsqb;global:cookies.accept&rsqb;&rsqb;')).replace(/\\/g, '\\\\'),
			link: translator.escape(validator.escape(meta.config.cookieConsentLink || '&lsqb;&lsqb;global:cookies.learn-more&rsqb;&rsqb;')).replace(/\\/g, '\\\\'),
			link_url: translator.escape(validator.escape(meta.config.cookieConsentLinkUrl || 'https://www.cookiesandyou.com')).replace(/\\/g, '\\\\'),
		},
		thumbs: {
			size: meta.config.topicThumbSize,
		},
		emailPrompt: meta.config.emailPrompt,
		useragent: {
			isSafari: req.useragent &amp;amp;&amp;amp; req.useragent.isSafari,
		},
		fontawesome: {
			pro: fontawesome_pro,
			styles: fontawesome_styles,
			version: fontawesome_version,
		},
		activitypub: {
			enabled: !!meta.config.activitypubEnabled,
			probe: meta.config.activitypubEnabled &amp;amp;&amp;amp; meta.config.activitypubProbe,
			worldDefaultCid: meta.config.activitypubWorldDefaultCid,
		},
		tinycon: {
			color: meta.config.tinyconColor,
			background: meta.config.tinyconBackground,
		},
	};

	let settings = config;
	let isAdminOrGlobalMod;
	if (req.loggedIn) {
		([settings, isAdminOrGlobalMod] = await Promise.all([
			user.getSettings(req.uid),
			user.isAdminOrGlobalMod(req.uid),
		]));
	}

	// Handle old skin configs
	const oldSkins = ['default'];
	settings.bootswatchSkin = oldSkins.includes(settings.bootswatchSkin) ? '' : settings.bootswatchSkin;

	config.usePagination = settings.usePagination;
	config.topicsPerPage = settings.topicsPerPage;
	config.postsPerPage = settings.postsPerPage;
	config.userLang = validator.escape(
		String((req.query.lang ? req.query.lang : null) || settings.userLang || config.defaultLang)
	);
	config.acpLang = validator.escape(String((req.query.lang ? req.query.lang : null) || settings.acpLang));
	config.openOutgoingLinksInNewTab = settings.openOutgoingLinksInNewTab;
	config.topicPostSort = settings.topicPostSort || config.topicPostSort;
	config.categoryTopicSort = settings.categoryTopicSort || config.categoryTopicSort;
	config.topicSearchEnabled = settings.topicSearchEnabled || false;
	config.disableCustomUserSkins = meta.config.disableCustomUserSkins === 1;
	config.defaultBootswatchSkin = config.bootswatchSkin;
	if (!config.disableCustomUserSkins &amp;amp;&amp;amp; settings.bootswatchSkin) {
		if (settings.bootswatchSkin === 'noskin') {
			config.bootswatchSkin = '';
		} else if (settings.bootswatchSkin !== '' &amp;amp;&amp;amp; await meta.css.isSkinValid(settings.bootswatchSkin)) {
			config.bootswatchSkin = settings.bootswatchSkin;
		}
	}
	config.hideReadNotifications = settings.hideReadNotifications;

	// Overrides based on privilege
	config.disableChatMessageEditing = isAdminOrGlobalMod ? false : config.disableChatMessageEditing;

	return await plugins.hooks.fire('filter:config.get', config);
};

apiController.getConfig = async function (req, res) {
	const config = await apiController.loadConfig(req);
	res.json(config);
};

apiController.getModerators = async function (req, res) {
	const moderators = await categories.getModerators(req.params.cid);
	res.json({ moderators: moderators });
};

require('../promisify')(apiController, ['getConfig', 'getObject', 'getModerators']);
</code></pre>
<pre><code>// Location: intents.js
'use strict';

import { dialog } from 'bootbox';
import { get } from 'api';
import storage from 'storage';
import { translate, translateKeys } from 'translator';

import * as alerts from './alerts';

const STORAGE_KEY = 'ap:intents:handles';

function getStoredData() {
	try {
		return storage.getItem(STORAGE_KEY);
	} catch (e) {
		return null;
	}
}

function setStoredData(data) {
	try {
		storage.setItem(STORAGE_KEY, data);
	} catch (e) {
		// Storage full or unavailable — silently fail
	}
}

const INTENT_DISPLAY_MAP = {
	create: '&lsqb;&lsqb;intents:display.create&rsqb;&rsqb;',
	like: '&lsqb;&lsqb;intents:display.like&rsqb;&rsqb;',
	dislike: '&lsqb;&lsqb;intents:display.dislike&rsqb;&rsqb;',
	follow: '&lsqb;&lsqb;intents:display.follow&rsqb;&rsqb;',
	object: '&lsqb;&lsqb;intents:display.object&rsqb;&rsqb;',
};

async function mapIntentNames(intents) {
	return await translateKeys(Object.keys(intents).map(intent =&amp;gt; `${INTENT_DISPLAY_MAP[intent.toLowerCase()]}`));
}

export function list() {
	const raw = getStoredData();
	if (!raw) {
		return new Map();
	}

	let handles;
	try {
		handles = JSON.parse(raw);
	} catch (e) {
		// Corrupt data — reset
		return new Map();
	}

	const map = new Map();
	if (Array.isArray(handles)) {
		handles.forEach(entry =&amp;gt; {
			if (entry &amp;amp;&amp;amp; entry.handle &amp;amp;&amp;amp; typeof entry.intents === 'object' &amp;amp;&amp;amp; !Array.isArray(entry.intents)) {
				map.set(entry.handle, entry.intents);
			}
		});
	}
	return map;
}

export function save(handle, intents) {
	if (typeof handle !== 'string' || !handle.trim()) {
		return;
	}
	handle = handle.trim();
	if (!intents || typeof intents !== 'object' || Array.isArray(intents)) {
		return;
	}

	const map = list();
	map.set(handle, intents);

	const entries = Array.from(map.entries()).map(([h, i]) =&amp;gt; ({ handle: h, intents: i }));
	setStoredData(JSON.stringify(entries));
}

export async function refresh(handle) {
	if (typeof handle !== 'string' || !handle.trim()) {
		return null;
	}
	handle = handle.trim().replace(/^@/, '');

	const result = await get(`/api/v3/intents/query/${handle}`);
	if (result &amp;amp;&amp;amp; result.intents &amp;amp;&amp;amp; typeof result.intents === 'object') {
		save(handle, result.intents);
		return { intents: Object.keys(result.intents) };
	}
	return null;
}

export async function register() {
	let map = list();
	let handles = await Promise.all(Array.from(map.entries()).map(async ([handle, intents]) =&amp;gt; ({
		handle,
		intents: (await mapIntentNames(intents)).join(', '),
	})));

	app.parseAndTranslate('modals/intents/register', {
		description: '&lsqb;&lsqb;intents:description&rsqb;&rsqb;',
		handles,
	}, (html) =&amp;gt; {
		const modal = dialog({
			title: '&lsqb;&lsqb;intents:title&rsqb;&rsqb;',
			message: html,
		});

		const handleInput = modal.find('#intents-handle-input');
		const submitBtn = modal.find('#intents-register-btn');

		const validateHandle = () =&amp;gt; {
			const val = handleInput.val().trim();
			// Validate: must be in format @username@domain or username@domain
			const valid = /^@?[\w.-]+@[\w.-]+\.[\w]{2,}$/.test(val);
			submitBtn.prop('disabled', !valid);
		};

		handleInput.on('input', validateHandle);
		validateHandle();

		modal.find('#intents-register-form').on('submit', async (ev) =&amp;gt; {
			ev.preventDefault();
			const handle = handleInput.val().trim();
			submitBtn.prop('disabled', true);

			try {
				await refresh(handle);
				map = list();
				handles = await Promise.all(Array.from(map.entries()).map(async ([handle, intents]) =&amp;gt; ({
					handle,
					intents: (await mapIntentNames(intents)).join(', '),
				})));
				const html = await app.parseAndTranslate('modals/intents/register', 'handles', { handles });
				modal.find('#intents-registered-list').html(html);
			} catch (e) {
				alerts.error(e.message);
			} finally {
				handleInput.val('');
				submitBtn.prop('disabled', false);
			}
		});

		modal.on('click', '[data-action="remove"]', function () {
			const handleToRemove = $(this).attr('data-handle');
			const map = list();
			map.delete(handleToRemove);
			const entries = Array.from(map.entries()).map(([h, i]) =&amp;gt; ({ handle: h, intents: i }));
			setStoredData(JSON.stringify(entries));
			$(this).closest('li').remove();

			if (!map.size) {
				modal.find('#intents-registered-list').closest('hr').next('h6, p').remove();
				modal.find('#intents-registered-list').closest('hr').prev('p').after('&lt;p class="text-muted mt-3"&gt;&lsqb;&lsqb;intents:no-handles&rsqb;&rsqb;&lt;/p&gt;');
			}
		});
	});
}

const INTENTS_GUEST_SELECTORS = '[component="topic/reply/guest"], [component="category/post/guest"]';

// called by various init scripts in different pages' js to add handlers for "Log in to post" buttons, et al.
export function addHandlers() {
	document.removeEventListener('click', _intentsHandler);
	document.addEventListener('click', _intentsHandler, true); // capture phase
}

function _intentsHandler(e) {
	if (!config.activitypub || !config.activitypub.enabled) {
		return;
	}

	const target = e.target.closest(INTENTS_GUEST_SELECTORS);
	if (target) {
		e.preventDefault();
		e.stopPropagation();

		const tid = ajaxify.data.tid;
		const cid = ajaxify.data.cid;
		const payload = {};

		if (tid) {
			payload.inReplyTo = utils.isNumber(ajaxify.data.mainPid) ? `${config.url}/post/${ajaxify.data.mainPid}` : ajaxify.data.mainPid;
			payload.content = `@${ajaxify.data.author.userslug}`;
		} else if (cid) {
			payload.content = `@${ajaxify.data.handleFull}`;
		}

		trigger('create', payload);
	}
}

export async function trigger(intent, parameters) {

	if (!config.activitypub || !config.activitypub.enabled) {
		ajaxify.go('login');
		return;
	}

	const map = list();
	const requiredIntent = intent.toLowerCase();
	const displayKey = INTENT_DISPLAY_MAP[requiredIntent];

	const displayIntent = (await translate(`${displayKey || intent}`));

	const entries = Array.from(map.entries())
		.filter(([, intents]) =&amp;gt; intents &amp;amp;&amp;amp; typeof intents === 'object' &amp;amp;&amp;amp; requiredIntent in intents)
		.map(([handle, intents]) =&amp;gt; ({ handle, intents }));

	const matchingHandles = await Promise.all(entries.map(async ({ handle, intents }) =&amp;gt; ({
		handle,
		intents: (await mapIntentNames(intents)).join(', '),
	})));

	app.parseAndTranslate('modals/intents/trigger', {
		displayIntent,
		matchingHandles,
		hasAnyHandles: map.size &amp;gt; 0,
	}, (html) =&amp;gt; {
		const modal = dialog({
			title: `[[intents:trigger-title, ${displayIntent}]]`,
			message: html,
		});

		// Handle intent execution from a registered handle
		modal.on('click', '[data-action="execute-intent"]', function () {
			const handle = $(this).attr('data-handle');
			const intents = map.get(handle);
			let url = intents &amp;amp;&amp;amp; intents[requiredIntent];

			// Replace template placeholders with URL-encoded parameter values
			if (url &amp;amp;&amp;amp; parameters &amp;amp;&amp;amp; typeof parameters === 'object') {
				Object.keys(parameters).forEach((prop) =&amp;gt; {
					const value = parameters[prop];
					const match = `{${prop}}`;
					if (url.includes(match)) {
						url = url.replaceAll(match, encodeURIComponent(value));
					}
				});
			}

			// Remove any unmatched placeholders
			url = url?.replaceAll(/\{[^}]+\}/g, '');

			if (url) {
				// Validate URL scheme to prevent XSS via javascript: data: etc.
				try {
					const parsed = new URL(url);
					if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
						window.location.href = url;
					}
				} catch (e) {
					// Invalid URL, silently ignore
				}
			}
		});

		// Open handle registration modal
		modal.on('click', '[data-action="open-register"]', function () {
			modal.modal('hide');
			setTimeout(() =&amp;gt; register(), 300);
		});

		// Redirect to login
		modal.on('click', '[data-action="go-login"]', function () {
			modal.modal('hide');
			ajaxify.go('login');
		});
	});
}
</code></pre>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107577</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107577</guid><dc:creator><![CDATA[jasonwch@community.nodebb.org]]></dc:creator><pubDate>Tue, 14 Jul 2026 14:02:09 GMT</pubDate></item><item><title><![CDATA[Reply to Manage Open Social Web handles not consistent after disabled Federation on Fri, 10 Jul 2026 13:18:20 GMT]]></title><description><![CDATA[<p dir="auto"><a href="https://community.nodebb.org/user/jasonwch" rel="nofollow ugc">@jasonwch</a> no, I javent looked into the issue yet 🫤</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107535</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107535</guid><dc:creator><![CDATA[julian@community.nodebb.org]]></dc:creator><pubDate>Fri, 10 Jul 2026 13:18:20 GMT</pubDate></item><item><title><![CDATA[Reply to Manage Open Social Web handles not consistent after disabled Federation on Fri, 10 Jul 2026 03:21:45 GMT]]></title><description><![CDATA[<p dir="auto">This patch seems isn't in place with 4.14?</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107519</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107519</guid><dc:creator><![CDATA[jasonwch@community.nodebb.org]]></dc:creator><pubDate>Fri, 10 Jul 2026 03:21:45 GMT</pubDate></item><item><title><![CDATA[Reply to Manage Open Social Web handles not consistent after disabled Federation on Mon, 06 Jul 2026 13:27:50 GMT]]></title><description><![CDATA[<p dir="auto"><a href="https://community.nodebb.org/user/baris" rel="nofollow ugc">@baris</a> <a href="https://community.nodebb.org/user/julian" rel="nofollow ugc">@julian</a> upvote and downvote also have this issue. Please help to fix as well. Thanks</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107506</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107506</guid><dc:creator><![CDATA[jasonwch@community.nodebb.org]]></dc:creator><pubDate>Mon, 06 Jul 2026 13:27:50 GMT</pubDate></item><item><title><![CDATA[Reply to Manage Open Social Web handles not consistent after disabled Federation on Tue, 30 Jun 2026 02:59:37 GMT]]></title><description><![CDATA[<p dir="auto"><a href="https://community.nodebb.org/user/julian" rel="nofollow ugc">@julian</a> Yep, I am temporarily patch the files and inject the code, will wait for the fix from upstream</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107449</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107449</guid><dc:creator><![CDATA[jasonwch@community.nodebb.org]]></dc:creator><pubDate>Tue, 30 Jun 2026 02:59:37 GMT</pubDate></item><item><title><![CDATA[Reply to Manage Open Social Web handles not consistent after disabled Federation on Mon, 29 Jun 2026 14:58:12 GMT]]></title><description><![CDATA[<p dir="auto">&gt; julian <a href="https://community.nodebb.org/post/107444" rel="nofollow ugc">said</a>:<br />
&gt;<br />
&gt; the Activity Intents behaviour is meant to be active even if federation is disabled.</p>
<p dir="auto">Actually, you're right, it's not meant to work without federation</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107447</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107447</guid><dc:creator><![CDATA[julian@community.nodebb.org]]></dc:creator><pubDate>Mon, 29 Jun 2026 14:58:12 GMT</pubDate></item><item><title><![CDATA[Reply to Manage Open Social Web handles not consistent after disabled Federation on Mon, 29 Jun 2026 13:41:01 GMT]]></title><description><![CDATA[<p dir="auto">Thanks so much</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107445</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107445</guid><dc:creator><![CDATA[jasonwch@community.nodebb.org]]></dc:creator><pubDate>Mon, 29 Jun 2026 13:41:01 GMT</pubDate></item><item><title><![CDATA[Reply to Manage Open Social Web handles not consistent after disabled Federation on Mon, 29 Jun 2026 13:15:16 GMT]]></title><description><![CDATA[<p dir="auto"><a href="https://community.nodebb.org/user/jasonwch" rel="nofollow ugc">@jasonwch</a> the Activity Intents behaviour is meant to be active even if federation is disabled.</p>
<p dir="auto">I'll fix up the behaviour so it is consistent even if federation is disabled.</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107444</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107444</guid><dc:creator><![CDATA[julian@community.nodebb.org]]></dc:creator><pubDate>Mon, 29 Jun 2026 13:15:16 GMT</pubDate></item></channel></rss>