diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e0e18329..903ecbf89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.125.0] - Not released ### Added +- The site administrator can now define a list of "privacy control groups" in + the config (the _privacyControlGroups_ entry). These groups will be always + shown in the feed selector of new or existing posts. The pages of such groups + do not show posts by default, and such groups cannot be subscribed to. - Support for the new "Notify of new comments" feature: - User can now turn on/off notification for new comments on the specific post via the "More" menu item; diff --git a/config/default.js b/config/default.js index a37939e79..cd2536855 100644 --- a/config/default.js +++ b/config/default.js @@ -174,4 +174,13 @@ export default { boostyProject: null, }, }, + + privacyControlGroups: { + hidePosts: true, // Hide posts on these groups pages + disableSubscriptions: true, // Disable subscriptions on these groups + groups: { + // Define groups like this: + // 'public-groupname': { label: 'Makes post public', privacy: 'public' } + }, + }, }; diff --git a/src/components/feeds-selector/options.jsx b/src/components/feeds-selector/options.jsx index ab789329e..5fc0f6919 100644 --- a/src/components/feeds-selector/options.jsx +++ b/src/components/feeds-selector/options.jsx @@ -1,3 +1,4 @@ +/* global CONFIG */ import { faGlobeAmericas, faHome, @@ -8,10 +9,8 @@ import { faUserFriends, faUserSlash, faExternalLinkAlt, - // faUsers, - // faUsersSlash, } from '@fortawesome/free-solid-svg-icons'; -import { uniq } from 'lodash-es'; +import { uniq, uniqBy } from 'lodash-es'; import { useEffect, useMemo } from 'react'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import cn from 'classnames'; @@ -121,10 +120,15 @@ export function useSelectedOptions(usernames, fixedFeedNames) { const groupOptions = useMemo( () => - mySubscriptions - .filter((u) => u?.type === 'group' && u.youCan.includes('post')) - .map((u) => toOption(u, me)) - .sort(compareOptions), + uniqBy( + [ + ...privacyControlOptions(), + ...mySubscriptions + .filter((u) => u?.type === 'group' && u.youCan.includes('post')) + .map((u) => toOption(u, me)), + ], + 'value', + ).sort(compareOptions), [me, mySubscriptions], ); @@ -218,3 +222,14 @@ export function toOption(user, me) { privacy: user.type === 'group' || isMe ? getPrivacy(user) : 'user', }; } + +function privacyControlOptions() { + return [...Object.entries(CONFIG.privacyControlGroups.groups)].map( + ([value, { label, privacy }]) => ({ + label, + value, + privacy, + type: ACC_GROUP, + }), + ); +} diff --git a/src/components/user-card.jsx b/src/components/user-card.jsx index 14311ead5..0e79e77a5 100644 --- a/src/components/user-card.jsx +++ b/src/components/user-card.jsx @@ -1,3 +1,4 @@ +/* global CONFIG */ import { Component, createRef, useCallback } from 'react'; import { Link } from 'react-router'; import { connect } from 'react-redux'; @@ -81,6 +82,10 @@ class UserCard extends Component { renderSubscribeBlock() { const { props } = this; + const allowToSubscribe = + !CONFIG.privacyControlGroups.disableSubscriptions || + !CONFIG.privacyControlGroups.groups[props.user.username]; + if (props.subscribed) { return ( @@ -98,11 +103,13 @@ class UserCard extends Component { ); } - return ( - - Subscribe - - ); + if (allowToSubscribe) { + return ( + + Subscribe + + ); + } } return null; diff --git a/src/components/user-profile-head-actions.jsx b/src/components/user-profile-head-actions.jsx index 7347dc620..1494d78fc 100644 --- a/src/components/user-profile-head-actions.jsx +++ b/src/components/user-profile-head-actions.jsx @@ -1,3 +1,4 @@ +/* global CONFIG */ import { Link } from 'react-router'; import cn from 'classnames'; import { faClock } from '@fortawesome/free-regular-svg-icons'; @@ -40,6 +41,10 @@ export function UserProfileHeadActions({ const showBanDialog = useShowBanDialog(user); const showDisableBansDialog = useShowDisableBansDialog(user, isCurrentUserAdmin); + const allowToSubscribe = + !CONFIG.privacyControlGroups.disableSubscriptions || + !CONFIG.privacyControlGroups.groups[user.username]; + return (
{isBanned ? ( @@ -61,7 +66,7 @@ export function UserProfileHeadActions({ Unsubscribe )} - {!user.isGone && !inSubscriptions && user.isPrivate === '0' && ( + {!user.isGone && !inSubscriptions && user.isPrivate === '0' && allowToSubscribe && (
  • Subscribe
  • diff --git a/src/components/user.jsx b/src/components/user.jsx index deaa35e85..45e3cd484 100644 --- a/src/components/user.jsx +++ b/src/components/user.jsx @@ -1,5 +1,5 @@ /* global CONFIG */ -import { useEffect, useMemo } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { Helmet } from 'react-helmet'; import { connect } from 'react-redux'; import * as _ from 'lodash-es'; @@ -19,6 +19,7 @@ import Breadcrumbs from './breadcrumbs'; import ErrorBoundary from './error-boundary'; import UserProfile from './user-profile'; import UserFeed from './user-feed'; +import { ButtonLink } from './button-link'; const UserHandler = (props) => { // Redirect to canonical username in URI (/uSErNAme/likes?offset=30 → /username/likes?offset=30) @@ -45,6 +46,17 @@ const UserHandler = (props) => { props.viewUser.username, ]); + const [forceShowContent, setForceShowContent] = useState(false); + const displayPosts = useCallback(() => setForceShowContent(true), []); + + const allowToPost = + !CONFIG.privacyControlGroups.hidePosts || + !CONFIG.privacyControlGroups.groups[props.viewUser.username]; + const showContent = + forceShowContent || + !CONFIG.privacyControlGroups.hidePosts || + !CONFIG.privacyControlGroups.groups[props.viewUser.username]; + const nameForTitle = useMemo( () => props.viewUser.username === props.viewUser.screenName @@ -87,6 +99,7 @@ const UserHandler = (props) => { { />
    - + {showContent ? ( + + ) : ( +
    +

    + You are on the {props.viewUser.username} group page. This is a + technical group, it just helps to customize the visibility of posts in other feeds. +

    +

    + The posts in this group itself are fairly random and not organized around any + particular topic. Still want to see them?{' '} + Yes, show me posts +

    +
    + )} );