From 66f48ca9821779bd7ec149833e01cf88abaf414f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 18 Jan 2023 22:41:54 +0100 Subject: [PATCH 1/4] Friendica support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/normalizers/instance.ts | 3 ++ app/soapbox/utils/features.ts | 73 +++++++++++++++++------------ package.json | 2 +- yarn.lock | 2 +- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/app/soapbox/normalizers/instance.ts b/app/soapbox/normalizers/instance.ts index 35d419392c..fa7700fb5f 100644 --- a/app/soapbox/normalizers/instance.ts +++ b/app/soapbox/normalizers/instance.ts @@ -140,6 +140,9 @@ export const normalizeInstance = (instance: Record) => { return isNumber(value) ? value : getAttachmentLimit(software); }); + // Urls can't be null, fix for Friendica + if (instance.get('urls') === null) instance.delete('urls'); + // Normalize version normalizeVersion(instance); fixTakahe(instance); diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index 9bc89ff852..08f6a50b05 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -1,6 +1,7 @@ /* eslint sort-keys: "error" */ import { List as ImmutableList, Map as ImmutableMap } from 'immutable'; import { createSelector } from 'reselect'; +import semverCoerce from 'semver/functions/coerce'; import gte from 'semver/functions/gte'; import lt from 'semver/functions/lt'; import semverParse from 'semver/functions/parse'; @@ -15,18 +16,18 @@ const overrides = custom('features'); /** Truthy array convenience function */ const any = (arr: Array): boolean => arr.some(Boolean); +/** + * Friendica. + * @see {@link https://friendi.ca/} + */ +export const FRIENDICA = 'Friendica'; + /** * Mastodon, the software upon which this is all based. * @see {@link https://joinmastodon.org/} */ export const MASTODON = 'Mastodon'; -/** - * Pleroma, a feature-rich alternative written in Elixir. - * @see {@link https://pleroma.social/} - */ -export const PLEROMA = 'Pleroma'; - /** * Mitra, a Rust backend with deep Ethereum integrations. * @see {@link https://codeberg.org/silverpill/mitra} @@ -40,29 +41,10 @@ export const MITRA = 'Mitra'; export const PIXELFED = 'Pixelfed'; /** - * Truth Social, the Mastodon fork powering truthsocial.com - * @see {@link https://help.truthsocial.com/open-source} + * Pleroma, a feature-rich alternative written in Elixir. + * @see {@link https://pleroma.social/} */ -export const TRUTHSOCIAL = 'TruthSocial'; - -/** - * Rebased, the recommended backend for Soapbox. - * @see {@link https://gitlab.com/soapbox-pub/rebased} - */ -// NOTE: Rebased is named 'soapbox' for legacy reasons. -export const REBASED = 'soapbox'; - -/** - * glitch-soc, fork of Mastodon with a number of experimental features. - * @see {@link https://glitch-soc.github.io/docs/} - */ -export const GLITCH = 'glitch'; - -/** - * Akkoma, a Pleroma fork. - * @see {@link https://akkoma.dev/AkkomaGang/akkoma} - */ -export const AKKOMA = 'akkoma'; +export const PLEROMA = 'Pleroma'; /** * Takahē, backend with support for serving multiple domains. @@ -70,11 +52,36 @@ export const AKKOMA = 'akkoma'; */ export const TAKAHE = 'Takahe'; +/** + * Truth Social, the Mastodon fork powering truthsocial.com + * @see {@link https://help.truthsocial.com/open-source} + */ +export const TRUTHSOCIAL = 'TruthSocial'; + /** * Wildebeest, backend running on top of Cloudflare Pages. */ export const WILDEBEEST = 'Wildebeest'; +/** + * Akkoma, a Pleroma fork. + * @see {@link https://akkoma.dev/AkkomaGang/akkoma} + */ +export const AKKOMA = 'akkoma'; + +/** + * glitch-soc, fork of Mastodon with a number of experimental features. + * @see {@link https://glitch-soc.github.io/docs/} + */ +export const GLITCH = 'glitch'; + +/** + * Rebased, the recommended backend for Soapbox. + * @see {@link https://gitlab.com/soapbox-pub/rebased} + */ +// NOTE: Rebased is named 'soapbox' for legacy reasons. +export const REBASED = 'soapbox'; + /** Parse features for the given instance */ const getInstanceFeatures = (instance: Instance) => { const v = parseVersion(instance.version); @@ -207,6 +214,7 @@ const getInstanceFeatures = (instance: Instance) => { * @see GET /api/v1/bookmarks */ bookmarks: any([ + v.software === FRIENDICA, v.software === MASTODON && gte(v.compatVersion, '3.1.0'), v.software === PLEROMA && gte(v.version, '0.9.9'), v.software === PIXELFED, @@ -301,6 +309,7 @@ const getInstanceFeatures = (instance: Instance) => { * @see {@link https://docs.joinmastodon.org/methods/timelines/conversations/} */ conversations: any([ + v.software === FRIENDICA, v.software === MASTODON && gte(v.compatVersion, '2.6.0'), v.software === PLEROMA && gte(v.version, '0.9.9'), v.software === PIXELFED, @@ -312,6 +321,7 @@ const getInstanceFeatures = (instance: Instance) => { * @see GET /api/v1/timelines/direct */ directTimeline: any([ + v.software === FRIENDICA, v.software === MASTODON && lt(v.compatVersion, '3.0.0'), v.software === PLEROMA && gte(v.version, '0.9.9'), ]), @@ -321,6 +331,7 @@ const getInstanceFeatures = (instance: Instance) => { * @see PATCH /api/v1/accounts/update_credentials */ editProfile: any([ + v.software === FRIENDICA, v.software === MASTODON, v.software === MITRA, v.software === PIXELFED, @@ -479,6 +490,7 @@ const getInstanceFeatures = (instance: Instance) => { * @see GET /api/v1/timelines/list/:list_id */ lists: any([ + v.software === FRIENDICA, v.software === MASTODON && gte(v.compatVersion, '2.1.0'), v.software === PLEROMA && gte(v.version, '0.9.9'), ]), @@ -592,6 +604,7 @@ const getInstanceFeatures = (instance: Instance) => { * @see {@link https://docs.joinmastodon.org/methods/instance/directory/} */ profileDirectory: any([ + v.software === FRIENDICA, v.software === MASTODON && gte(v.compatVersion, '3.0.0'), features.includes('profile_directory'), ]), @@ -726,6 +739,7 @@ const getInstanceFeatures = (instance: Instance) => { * @see {@link https://docs.joinmastodon.org/methods/accounts/suggestions/} */ suggestions: any([ + v.software === FRIENDICA, v.software === MASTODON && gte(v.compatVersion, '2.4.3'), v.software === TRUTHSOCIAL, features.includes('v2_suggestions'), @@ -816,9 +830,10 @@ export const parseVersion = (version: string): Backend => { const regex = /^([\w+.]*)(?: \(compatible; ([\w]*) (.*)\))?$/; const match = regex.exec(version); - const semver = match ? semverParse(match[3] || match[1]) : null; + const semver = match ? semverParse(semverCoerce(match[3] || match[1], { loose: true })) : null; const compat = match ? semverParse(match[1]) : null; + if (match && semver && compat) { return { build: semver.build[0], diff --git a/package.json b/package.json index 9230aa400e..06f47abdc8 100644 --- a/package.json +++ b/package.json @@ -175,7 +175,7 @@ "sass": "^1.20.3", "sass-loader": "^13.0.0", "seedrandom": "^3.0.5", - "semver": "^7.3.2", + "semver": "^7.3.8", "stringz": "^2.0.0", "substring-trie": "^1.0.2", "terser-webpack-plugin": "^5.2.3", diff --git a/yarn.lock b/yarn.lock index 2aa5a0c0fb..a1fb69a9bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10220,7 +10220,7 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@7.3.5, semver@7.x, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: +semver@7.3.5, semver@7.x, semver@^7.3.4, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== From 7d1e13c28d83ee1d699f10831f572da281644272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 18 Jan 2023 21:53:33 +0000 Subject: [PATCH 2/4] Update features.ts --- app/soapbox/utils/features.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index 08f6a50b05..a62babc447 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -17,7 +17,7 @@ const overrides = custom('features'); const any = (arr: Array): boolean => arr.some(Boolean); /** - * Friendica. + * Friendica, decentralized social platform implementing multiple federation protocols. * @see {@link https://friendi.ca/} */ export const FRIENDICA = 'Friendica'; From afd4a3933790239e3f43671a08295b0550102298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 19 Jan 2023 00:15:32 +0100 Subject: [PATCH 3/4] Friendica, version parsing fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/features/edit-profile/index.tsx | 3 ++- app/soapbox/utils/features.ts | 9 +++++---- app/soapbox/utils/html.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/soapbox/features/edit-profile/index.tsx b/app/soapbox/features/edit-profile/index.tsx index 000c79aaa7..6f14c63545 100644 --- a/app/soapbox/features/edit-profile/index.tsx +++ b/app/soapbox/features/edit-profile/index.tsx @@ -1,3 +1,4 @@ +import { List as ImmutableList } from 'immutable'; import React, { useState, useEffect, useMemo } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; @@ -125,7 +126,7 @@ const accountToCredentials = (account: Account): AccountCredentials => { display_name: account.display_name, note: account.source.get('note'), locked: account.locked, - fields_attributes: [...account.source.get>('fields', []).toJS()], + fields_attributes: [...account.source.get>('fields', ImmutableList()).toJS()], stranger_notifications: account.getIn(['pleroma', 'notification_settings', 'block_from_strangers']) === true, accepts_email_list: account.getIn(['pleroma', 'accepts_email_list']) === true, hide_followers: hideNetwork, diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index 08f6a50b05..3a898b7d91 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -624,6 +624,7 @@ const getInstanceFeatures = (instance: Instance) => { * @see GET /api/v1/timelines/public */ publicTimeline: any([ + v.software === FRIENDICA, v.software === MASTODON, v.software === PLEROMA, v.software === TAKAHE, @@ -739,7 +740,6 @@ const getInstanceFeatures = (instance: Instance) => { * @see {@link https://docs.joinmastodon.org/methods/accounts/suggestions/} */ suggestions: any([ - v.software === FRIENDICA, v.software === MASTODON && gte(v.compatVersion, '2.4.3'), v.software === TRUTHSOCIAL, features.includes('v2_suggestions'), @@ -750,6 +750,7 @@ const getInstanceFeatures = (instance: Instance) => { * @see GET /api/v2/suggestions */ suggestionsV2: any([ + v.software === FRIENDICA, v.software === MASTODON && gte(v.compatVersion, '3.4.0'), v.software === TRUTHSOCIAL, features.includes('v2_suggestions'), @@ -830,9 +831,9 @@ export const parseVersion = (version: string): Backend => { const regex = /^([\w+.]*)(?: \(compatible; ([\w]*) (.*)\))?$/; const match = regex.exec(version); - const semver = match ? semverParse(semverCoerce(match[3] || match[1], { loose: true })) : null; - const compat = match ? semverParse(match[1]) : null; - + const semverString = match && (match[3] || match[1]); + const semver = match ? semverParse(semverString) || semverCoerce(semverString) : null; + const compat = match ? semverParse(match[1]) || semverCoerce(match[1]) : null; if (match && semver && compat) { return { diff --git a/app/soapbox/utils/html.ts b/app/soapbox/utils/html.ts index 32a47caed7..98754bab06 100644 --- a/app/soapbox/utils/html.ts +++ b/app/soapbox/utils/html.ts @@ -1,6 +1,6 @@ /** Convert HTML to a plaintext representation, preserving whitespace. */ // NB: This function can still return unsafe HTML -export const unescapeHTML = (html: string): string => { +export const unescapeHTML = (html: string = ''): string => { const wrapper = document.createElement('div'); wrapper.innerHTML = html.replace(//g, '\n').replace(/<\/p><[^>]*>/g, '\n\n').replace(/<[^>]*>/g, ''); return wrapper.textContent || ''; From 6963c44564f5cde9c2c6b58f3334a69638aa687e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 19 Jan 2023 00:16:59 +0100 Subject: [PATCH 4/4] Update CHANGELOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 198564927b..4cdc244133 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Admin: redirect the homepage to any URL. +- Compatibility: added compatibility with Friendica. ### Changed @@ -52,7 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Chats: fix jumpy scrollbar. - Composer: fix alignment of icon in submit button. - Login: add a border around QR codes. -- Composer: don't display action button in reply indicator +- Composer: don't display action button in reply indicator. ## [3.0.0] - 2022-12-25