Merge commit 'd13d7b4566d551d027830f7dfc987ba3a8e2dd4f' into glitch-soc/merge-upstream
Conflicts are too numerous to list, but they are all caused by upstream's theming changes and all have to do with the differences between upstream and glitch-soc in the theming systems. They were all manually resolved by adapting the relevant code.
This commit is contained in:
commit
4fbd1ad7ae
@ -21,9 +21,7 @@ import { reducerWithInitialState } from '@/mastodon/reducers';
|
|||||||
import { defaultMiddleware } from '@/mastodon/store/store';
|
import { defaultMiddleware } from '@/mastodon/store/store';
|
||||||
import { mockHandlers, unhandledRequestHandler } from '@/testing/api';
|
import { mockHandlers, unhandledRequestHandler } from '@/testing/api';
|
||||||
|
|
||||||
// If you want to run the dark theme during development,
|
import '../app/javascript/styles/application.scss';
|
||||||
// you can change the below to `/application.scss`
|
|
||||||
import '../app/javascript/styles/mastodon-light.scss';
|
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
import { modes } from './modes';
|
import { modes } from './modes';
|
||||||
|
|
||||||
|
|||||||
@ -343,8 +343,9 @@ GEM
|
|||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
io-console (0.8.2)
|
io-console (0.8.2)
|
||||||
irb (1.16.0)
|
irb (1.17.0)
|
||||||
pp (>= 0.6.0)
|
pp (>= 0.6.0)
|
||||||
|
prism (>= 1.3.0)
|
||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
jd-paperclip-azure (3.0.0)
|
jd-paperclip-azure (3.0.0)
|
||||||
|
|||||||
@ -11,18 +11,12 @@ class ApplicationController < ActionController::Base
|
|||||||
include CacheConcern
|
include CacheConcern
|
||||||
include PreloadingConcern
|
include PreloadingConcern
|
||||||
include DomainControlHelper
|
include DomainControlHelper
|
||||||
include ThemingConcern
|
|
||||||
include DatabaseHelper
|
include DatabaseHelper
|
||||||
include AuthorizedFetchHelper
|
include AuthorizedFetchHelper
|
||||||
include SelfDestructHelper
|
include SelfDestructHelper
|
||||||
|
|
||||||
helper_method :current_account
|
helper_method :current_account
|
||||||
helper_method :current_session
|
helper_method :current_session
|
||||||
helper_method :current_flavour
|
|
||||||
helper_method :current_skin
|
|
||||||
helper_method :current_theme
|
|
||||||
helper_method :color_scheme
|
|
||||||
helper_method :contrast
|
|
||||||
helper_method :single_user_mode?
|
helper_method :single_user_mode?
|
||||||
helper_method :use_seamless_external_login?
|
helper_method :use_seamless_external_login?
|
||||||
helper_method :sso_account_settings
|
helper_method :sso_account_settings
|
||||||
@ -176,25 +170,6 @@ class ApplicationController < ActionController::Base
|
|||||||
@current_session = SessionActivation.find_by(session_id: cookies.signed['_session_id']) if cookies.signed['_session_id'].present?
|
@current_session = SessionActivation.find_by(session_id: cookies.signed['_session_id']) if cookies.signed['_session_id'].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def color_scheme
|
|
||||||
current = current_user&.setting_color_scheme
|
|
||||||
return current if current && current != 'auto'
|
|
||||||
|
|
||||||
return 'dark' if current_skin.include?('default') || current_skin.include?('contrast')
|
|
||||||
return 'light' if current_skin.include?('light')
|
|
||||||
|
|
||||||
'auto'
|
|
||||||
end
|
|
||||||
|
|
||||||
def contrast
|
|
||||||
current = current_user&.setting_contrast
|
|
||||||
return current if current && current != 'auto'
|
|
||||||
|
|
||||||
return 'high' if current_skin.include?('contrast')
|
|
||||||
|
|
||||||
'auto'
|
|
||||||
end
|
|
||||||
|
|
||||||
def respond_with_error(code)
|
def respond_with_error(code)
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.any { render "errors/#{code}", layout: 'error', status: code, formats: [:html] }
|
format.any { render "errors/#{code}", layout: 'error', status: code, formats: [:html] }
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ThemingConcern
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def current_flavour
|
|
||||||
@current_flavour ||= [current_user&.setting_flavour, Setting.flavour, 'glitch', 'vanilla'].find { |flavour| Themes.instance.flavours.include?(flavour) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_skin
|
|
||||||
@current_skin ||= begin
|
|
||||||
skins = Themes.instance.skins_for(current_flavour)
|
|
||||||
[current_user&.setting_skin, Setting.skin, 'system', 'default'].find { |skin| skins.include?(skin) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_theme
|
|
||||||
# NOTE: this is slightly different from upstream, as it's a derived value used
|
|
||||||
# for the sole purpose of pointing to the appropriate stylesheet pack
|
|
||||||
[current_flavour, current_skin]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -89,12 +89,6 @@ module ApplicationHelper
|
|||||||
Rails.env.production? ? site_title : "#{site_title} (Dev)"
|
Rails.env.production? ? site_title : "#{site_title} (Dev)"
|
||||||
end
|
end
|
||||||
|
|
||||||
def page_color_scheme
|
|
||||||
return content_for(:force_color_scheme) if content_for(:force_color_scheme)
|
|
||||||
|
|
||||||
color_scheme
|
|
||||||
end
|
|
||||||
|
|
||||||
def label_for_scope(scope)
|
def label_for_scope(scope)
|
||||||
safe_join [
|
safe_join [
|
||||||
tag.samp(scope, class: { 'scope-danger' => SessionActivation::DEFAULT_SCOPES.include?(scope.to_s) }),
|
tag.samp(scope, class: { 'scope-danger' => SessionActivation::DEFAULT_SCOPES.include?(scope.to_s) }),
|
||||||
|
|||||||
@ -20,9 +20,6 @@ module ThemeHelper
|
|||||||
def theme_style_tags(flavour_and_skin)
|
def theme_style_tags(flavour_and_skin)
|
||||||
flavour, theme = flavour_and_skin
|
flavour, theme = flavour_and_skin
|
||||||
|
|
||||||
# TODO: get rid of that when we retire the themes and perform the settings migration
|
|
||||||
theme = 'default' if %w(mastodon-light contrast system).include?(theme)
|
|
||||||
|
|
||||||
vite_stylesheet_tag "skins/#{flavour}/#{theme}", type: :virtual, media: 'all', crossorigin: 'anonymous'
|
vite_stylesheet_tag "skins/#{flavour}/#{theme}", type: :virtual, media: 'all', crossorigin: 'anonymous'
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -51,6 +48,33 @@ module ThemeHelper
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def current_flavour
|
||||||
|
[current_user&.setting_flavour, Setting.flavour, 'glitch', 'vanilla'].find { |flavour| Themes.instance.flavours.include?(flavour) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_skin
|
||||||
|
skins = Themes.instance.skins_for(current_flavour)
|
||||||
|
[current_user&.setting_skin, Setting.skin, 'system', 'default'].find { |skin| skins.include?(skin) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_theme
|
||||||
|
# NOTE: this is slightly different from upstream, as it's a derived value used
|
||||||
|
# for the sole purpose of pointing to the appropriate stylesheet pack
|
||||||
|
[current_flavour, current_skin]
|
||||||
|
end
|
||||||
|
|
||||||
|
def color_scheme
|
||||||
|
current_user&.setting_color_scheme || 'auto'
|
||||||
|
end
|
||||||
|
|
||||||
|
def contrast
|
||||||
|
current_user&.setting_contrast || 'auto'
|
||||||
|
end
|
||||||
|
|
||||||
|
def page_color_scheme
|
||||||
|
content_for(:force_color_scheme).presence || color_scheme
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def active_custom_stylesheet
|
def active_custom_stylesheet
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { useSearchParam } from '@/mastodon/hooks/useSearchParam';
|
|
||||||
|
|
||||||
export function useFilters() {
|
|
||||||
const [boosts, setBoosts] = useSearchParam('boosts');
|
|
||||||
const [replies, setReplies] = useSearchParam('replies');
|
|
||||||
|
|
||||||
const handleSetBoosts = useCallback(
|
|
||||||
(value: boolean) => {
|
|
||||||
setBoosts(value ? '1' : null);
|
|
||||||
},
|
|
||||||
[setBoosts],
|
|
||||||
);
|
|
||||||
const handleSetReplies = useCallback(
|
|
||||||
(value: boolean) => {
|
|
||||||
setReplies(value ? '1' : null);
|
|
||||||
},
|
|
||||||
[setReplies],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
boosts: boosts === '1',
|
|
||||||
replies: replies === '1',
|
|
||||||
setBoosts: handleSetBoosts,
|
|
||||||
setReplies: handleSetReplies,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
import type { FC, ReactNode } from 'react';
|
||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { useStorage } from '@/mastodon/hooks/useStorage';
|
||||||
|
|
||||||
|
interface AccountTimelineContextValue {
|
||||||
|
accountId: string;
|
||||||
|
boosts: boolean;
|
||||||
|
replies: boolean;
|
||||||
|
showAllPinned: boolean;
|
||||||
|
setBoosts: (value: boolean) => void;
|
||||||
|
setReplies: (value: boolean) => void;
|
||||||
|
onShowAllPinned: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccountTimelineContext =
|
||||||
|
createContext<AccountTimelineContextValue | null>(null);
|
||||||
|
|
||||||
|
export const AccountTimelineProvider: FC<{
|
||||||
|
accountId: string;
|
||||||
|
children: ReactNode;
|
||||||
|
}> = ({ accountId, children }) => {
|
||||||
|
const { getItem, setItem } = useStorage({
|
||||||
|
type: 'session',
|
||||||
|
prefix: `filters-${accountId}:`,
|
||||||
|
});
|
||||||
|
const [boosts, setBoosts] = useState(
|
||||||
|
() => (getItem('boosts') === '0' ? false : true), // Default to enabled.
|
||||||
|
);
|
||||||
|
const [replies, setReplies] = useState(() =>
|
||||||
|
getItem('replies') === '1' ? true : false,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSetBoosts = useCallback(
|
||||||
|
(value: boolean) => {
|
||||||
|
setBoosts(value);
|
||||||
|
setItem('boosts', value ? '1' : '0');
|
||||||
|
},
|
||||||
|
[setBoosts, setItem],
|
||||||
|
);
|
||||||
|
const handleSetReplies = useCallback(
|
||||||
|
(value: boolean) => {
|
||||||
|
setReplies(value);
|
||||||
|
setItem('replies', value ? '1' : '0');
|
||||||
|
},
|
||||||
|
[setReplies, setItem],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [showAllPinned, setShowAllPinned] = useState(false);
|
||||||
|
const handleShowAllPinned = useCallback(() => {
|
||||||
|
setShowAllPinned(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Memoize the context value to avoid unnecessary re-renders.
|
||||||
|
const value = useMemo(
|
||||||
|
() => ({
|
||||||
|
accountId,
|
||||||
|
boosts,
|
||||||
|
replies,
|
||||||
|
showAllPinned,
|
||||||
|
setBoosts: handleSetBoosts,
|
||||||
|
setReplies: handleSetReplies,
|
||||||
|
onShowAllPinned: handleShowAllPinned,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
accountId,
|
||||||
|
boosts,
|
||||||
|
handleSetBoosts,
|
||||||
|
handleSetReplies,
|
||||||
|
handleShowAllPinned,
|
||||||
|
replies,
|
||||||
|
showAllPinned,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccountTimelineContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</AccountTimelineContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useAccountContext() {
|
||||||
|
const values = useContext(AccountTimelineContext);
|
||||||
|
if (!values) {
|
||||||
|
throw new Error(
|
||||||
|
'useAccountFilters must be used within an AccountTimelineProvider',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
@ -13,8 +13,7 @@ import { useOverflowButton } from '@/mastodon/hooks/useOverflow';
|
|||||||
import { selectAccountFeaturedTags } from '@/mastodon/selectors/accounts';
|
import { selectAccountFeaturedTags } from '@/mastodon/selectors/accounts';
|
||||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
|
|
||||||
import { useFilters } from '../hooks/useFilters';
|
import { useAccountContext } from './context';
|
||||||
|
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
export const FeaturedTags: FC<{ accountId: string }> = ({ accountId }) => {
|
export const FeaturedTags: FC<{ accountId: string }> = ({ accountId }) => {
|
||||||
@ -83,7 +82,7 @@ export const FeaturedTags: FC<{ accountId: string }> = ({ accountId }) => {
|
|||||||
function useTagNavigate() {
|
function useTagNavigate() {
|
||||||
// Get current account, tag, and filters.
|
// Get current account, tag, and filters.
|
||||||
const { acct, tagged } = useParams<{ acct: string; tagged?: string }>();
|
const { acct, tagged } = useParams<{ acct: string; tagged?: string }>();
|
||||||
const { boosts, replies } = useFilters();
|
const { boosts, replies } = useAccountContext();
|
||||||
|
|
||||||
const history = useAppHistory();
|
const history = useAppHistory();
|
||||||
|
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import { Icon } from '@/mastodon/components/icon';
|
|||||||
import KeyboardArrowDownIcon from '@/material-icons/400-24px/keyboard_arrow_down.svg?react';
|
import KeyboardArrowDownIcon from '@/material-icons/400-24px/keyboard_arrow_down.svg?react';
|
||||||
|
|
||||||
import { AccountTabs } from '../components/tabs';
|
import { AccountTabs } from '../components/tabs';
|
||||||
import { useFilters } from '../hooks/useFilters';
|
|
||||||
|
|
||||||
|
import { useAccountContext } from './context';
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
export const AccountFilters: FC = () => {
|
export const AccountFilters: FC = () => {
|
||||||
@ -42,7 +42,7 @@ const FilterDropdown: FC = () => {
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { boosts, replies, setBoosts, setReplies } = useFilters();
|
const { boosts, replies, setBoosts, setReplies } = useAccountContext();
|
||||||
const handleChange: ChangeEventHandler<HTMLInputElement> = useCallback(
|
const handleChange: ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
const { name, checked } = event.target;
|
const { name, checked } = event.target;
|
||||||
@ -101,7 +101,6 @@ const FilterDropdown: FC = () => {
|
|||||||
<Overlay
|
<Overlay
|
||||||
show={open}
|
show={open}
|
||||||
target={buttonRef}
|
target={buttonRef}
|
||||||
flip
|
|
||||||
placement='bottom-start'
|
placement='bottom-start'
|
||||||
rootClose
|
rootClose
|
||||||
onHide={handleHide}
|
onHide={handleHide}
|
||||||
|
|||||||
@ -25,12 +25,11 @@ import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
|||||||
|
|
||||||
import { AccountHeader } from '../components/account_header';
|
import { AccountHeader } from '../components/account_header';
|
||||||
import { LimitedAccountHint } from '../components/limited_account_hint';
|
import { LimitedAccountHint } from '../components/limited_account_hint';
|
||||||
import { useFilters } from '../hooks/useFilters';
|
|
||||||
|
|
||||||
|
import { AccountTimelineProvider, useAccountContext } from './context';
|
||||||
import { FeaturedTags } from './featured_tags';
|
import { FeaturedTags } from './featured_tags';
|
||||||
import { AccountFilters } from './filters';
|
import { AccountFilters } from './filters';
|
||||||
import {
|
import {
|
||||||
PinnedStatusProvider,
|
|
||||||
renderPinnedStatusHeader,
|
renderPinnedStatusHeader,
|
||||||
usePinnedStatusIds,
|
usePinnedStatusIds,
|
||||||
} from './pinned_statuses';
|
} from './pinned_statuses';
|
||||||
@ -56,13 +55,13 @@ const AccountTimelineV2: FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
|||||||
|
|
||||||
// Add this key to remount the timeline when accountId changes.
|
// Add this key to remount the timeline when accountId changes.
|
||||||
return (
|
return (
|
||||||
<PinnedStatusProvider>
|
<AccountTimelineProvider accountId={accountId}>
|
||||||
<InnerTimeline
|
<InnerTimeline
|
||||||
accountId={accountId}
|
accountId={accountId}
|
||||||
key={accountId}
|
key={accountId}
|
||||||
multiColumn={multiColumn}
|
multiColumn={multiColumn}
|
||||||
/>
|
/>
|
||||||
</PinnedStatusProvider>
|
</AccountTimelineProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -71,7 +70,7 @@ const InnerTimeline: FC<{ accountId: string; multiColumn: boolean }> = ({
|
|||||||
multiColumn,
|
multiColumn,
|
||||||
}) => {
|
}) => {
|
||||||
const { tagged } = useParams<{ tagged?: string }>();
|
const { tagged } = useParams<{ tagged?: string }>();
|
||||||
const { boosts, replies } = useFilters();
|
const { boosts, replies } = useAccountContext();
|
||||||
const key = timelineKey({
|
const key = timelineKey({
|
||||||
type: 'account',
|
type: 'account',
|
||||||
userId: accountId,
|
userId: accountId,
|
||||||
|
|||||||
@ -1,12 +1,5 @@
|
|||||||
import type { FC, ReactNode } from 'react';
|
import type { FC } from 'react';
|
||||||
import {
|
import { useEffect, useMemo } from 'react';
|
||||||
createContext,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
@ -28,42 +21,9 @@ import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
|||||||
import { isRedesignEnabled } from '../common';
|
import { isRedesignEnabled } from '../common';
|
||||||
import { PinnedBadge } from '../components/badges';
|
import { PinnedBadge } from '../components/badges';
|
||||||
|
|
||||||
|
import { useAccountContext } from './context';
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
const PinnedStatusContext = createContext<{
|
|
||||||
showAllPinned: boolean;
|
|
||||||
onShowAllPinned: () => void;
|
|
||||||
}>({
|
|
||||||
showAllPinned: false,
|
|
||||||
onShowAllPinned: () => {
|
|
||||||
throw new Error('No onShowAllPinned provided');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const PinnedStatusProvider: FC<{ children: ReactNode }> = ({
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const [showAllPinned, setShowAllPinned] = useState(false);
|
|
||||||
const handleShowAllPinned = useCallback(() => {
|
|
||||||
setShowAllPinned(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Memoize so the context doesn't change every render.
|
|
||||||
const value = useMemo(
|
|
||||||
() => ({
|
|
||||||
showAllPinned,
|
|
||||||
onShowAllPinned: handleShowAllPinned,
|
|
||||||
}),
|
|
||||||
[handleShowAllPinned, showAllPinned],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PinnedStatusContext.Provider value={value}>
|
|
||||||
{children}
|
|
||||||
</PinnedStatusContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function usePinnedStatusIds({
|
export function usePinnedStatusIds({
|
||||||
accountId,
|
accountId,
|
||||||
tagged,
|
tagged,
|
||||||
@ -89,7 +49,7 @@ export function usePinnedStatusIds({
|
|||||||
selectTimelineByKey(state, pinnedKey),
|
selectTimelineByKey(state, pinnedKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { showAllPinned } = useContext(PinnedStatusContext);
|
const { showAllPinned } = useAccountContext();
|
||||||
|
|
||||||
const pinnedTimelineItems = pinnedTimeline?.items; // Make a const to avoid the React Compiler complaining.
|
const pinnedTimelineItems = pinnedTimeline?.items; // Make a const to avoid the React Compiler complaining.
|
||||||
const pinnedStatusIds = useMemo(() => {
|
const pinnedStatusIds = useMemo(() => {
|
||||||
@ -125,7 +85,7 @@ export const renderPinnedStatusHeader: StatusHeaderRenderFn = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const PinnedShowAllButton: FC = () => {
|
export const PinnedShowAllButton: FC = () => {
|
||||||
const { onShowAllPinned } = useContext(PinnedStatusContext);
|
const { onShowAllPinned } = useAccountContext();
|
||||||
|
|
||||||
if (!isRedesignEnabled()) {
|
if (!isRedesignEnabled()) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
64
app/javascript/mastodon/hooks/useStorage.ts
Normal file
64
app/javascript/mastodon/hooks/useStorage.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
|
export function useStorage({
|
||||||
|
type = 'local',
|
||||||
|
prefix = '',
|
||||||
|
}: { type?: 'local' | 'session'; prefix?: string } = {}) {
|
||||||
|
const storageType = type === 'local' ? 'localStorage' : 'sessionStorage';
|
||||||
|
const isAvailable = useMemo(
|
||||||
|
() => storageAvailable(storageType),
|
||||||
|
[storageType],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getItem = useCallback(
|
||||||
|
(key: string) => {
|
||||||
|
if (!isAvailable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return window[storageType].getItem(prefix ? `${prefix};${key}` : key);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isAvailable, storageType, prefix],
|
||||||
|
);
|
||||||
|
const setItem = useCallback(
|
||||||
|
(key: string, value: string) => {
|
||||||
|
if (!isAvailable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
window[storageType].setItem(prefix ? `${prefix};${key}` : key, value);
|
||||||
|
} catch {}
|
||||||
|
},
|
||||||
|
[isAvailable, storageType, prefix],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isAvailable,
|
||||||
|
getItem,
|
||||||
|
setItem,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests the storage availability for the given type. Taken from MDN:
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
|
||||||
|
export function storageAvailable(type: 'localStorage' | 'sessionStorage') {
|
||||||
|
let storage;
|
||||||
|
try {
|
||||||
|
storage = window[type];
|
||||||
|
const x = '__storage_test__';
|
||||||
|
storage.setItem(x, x);
|
||||||
|
storage.removeItem(x);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return (
|
||||||
|
e instanceof DOMException &&
|
||||||
|
e.name === 'QuotaExceededError' &&
|
||||||
|
// acknowledge QuotaExceededError only if there's something already stored
|
||||||
|
storage &&
|
||||||
|
storage.length !== 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +0,0 @@
|
|||||||
@use '@/flavours/glitch/styles/contrast';
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
en:
|
|
||||||
skins:
|
|
||||||
glitch:
|
|
||||||
contrast: High contrast
|
|
||||||
cs:
|
|
||||||
skins:
|
|
||||||
glitch:
|
|
||||||
contrast: Vysoký kontrast
|
|
||||||
es:
|
|
||||||
skins:
|
|
||||||
glitch:
|
|
||||||
contrast: Alto contraste
|
|
||||||
@ -1 +0,0 @@
|
|||||||
@use '@/flavours/glitch/styles/mastodon-light';
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
en:
|
|
||||||
skins:
|
|
||||||
glitch:
|
|
||||||
mastodon-light: Mastodon (light)
|
|
||||||
cs:
|
|
||||||
skins:
|
|
||||||
glitch:
|
|
||||||
mastodon-light: Mastodon (světlý)
|
|
||||||
es:
|
|
||||||
skins:
|
|
||||||
glitch:
|
|
||||||
mastodon-light: Mastodon (claro)
|
|
||||||
@ -1 +0,0 @@
|
|||||||
@use '@/styles/contrast';
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
en:
|
|
||||||
skins:
|
|
||||||
vanilla:
|
|
||||||
contrast: High contrast
|
|
||||||
cs:
|
|
||||||
skins:
|
|
||||||
vanilla:
|
|
||||||
contrast: Vysoký kontrast
|
|
||||||
es:
|
|
||||||
skins:
|
|
||||||
vanilla:
|
|
||||||
contrast: Alto contraste
|
|
||||||
@ -1 +0,0 @@
|
|||||||
@use '@/styles/mastodon-light';
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
en:
|
|
||||||
skins:
|
|
||||||
vanilla:
|
|
||||||
mastodon-light: Mastodon (light)
|
|
||||||
cs:
|
|
||||||
skins:
|
|
||||||
vanilla:
|
|
||||||
mastodon-light: Mastodon (světlý)
|
|
||||||
es:
|
|
||||||
skins:
|
|
||||||
glitch:
|
|
||||||
mastodon-light: Mastodon (claro)
|
|
||||||
@ -1 +0,0 @@
|
|||||||
@use 'common';
|
|
||||||
@ -1 +0,0 @@
|
|||||||
@use 'common';
|
|
||||||
@ -18,6 +18,8 @@ class ActivityPub::FetchFeaturedTagsCollectionService < BaseService
|
|||||||
private
|
private
|
||||||
|
|
||||||
def process_items(items)
|
def process_items(items)
|
||||||
|
return if items.nil?
|
||||||
|
|
||||||
names = items.filter_map { |item| item['type'] == 'Hashtag' && item['name']&.delete_prefix('#') }.take(FeaturedTag::LIMIT)
|
names = items.filter_map { |item| item['type'] == 'Hashtag' && item['name']&.delete_prefix('#') }.take(FeaturedTag::LIMIT)
|
||||||
tags = names.index_by { |name| HashtagNormalizer.new.normalize(name) }
|
tags = names.index_by { |name| HashtagNormalizer.new.normalize(name) }
|
||||||
normalized_names = tags.keys
|
normalized_names = tags.keys
|
||||||
|
|||||||
@ -29,7 +29,12 @@ class BlockDomainService < BaseService
|
|||||||
suspend_accounts!
|
suspend_accounts!
|
||||||
end
|
end
|
||||||
|
|
||||||
DomainClearMediaWorker.perform_async(domain_block.id) if domain_block.reject_media?
|
if domain_block.suspend?
|
||||||
|
# Account images and attachments are already handled by `suspend_accounts!`
|
||||||
|
PurgeCustomEmojiWorker.perform_async(blocked_domain)
|
||||||
|
elsif domain_block.reject_media?
|
||||||
|
DomainClearMediaWorker.perform_async(domain_block.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def silence_accounts!
|
def silence_accounts!
|
||||||
|
|||||||
@ -27,7 +27,7 @@ class CreateCollectionService
|
|||||||
@accounts_to_add.each do |account_to_add|
|
@accounts_to_add.each do |account_to_add|
|
||||||
raise Mastodon::NotPermittedError, I18n.t('accounts.errors.cannot_be_added_to_collections') unless AccountPolicy.new(@account, account_to_add).feature?
|
raise Mastodon::NotPermittedError, I18n.t('accounts.errors.cannot_be_added_to_collections') unless AccountPolicy.new(@account, account_to_add).feature?
|
||||||
|
|
||||||
@collection.collection_items.build(account: account_to_add)
|
@collection.collection_items.build(account: account_to_add, state: :accepted)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,6 @@
|
|||||||
class DateOfBirthValidator < ActiveModel::EachValidator
|
class DateOfBirthValidator < ActiveModel::EachValidator
|
||||||
def validate_each(record, attribute, value)
|
def validate_each(record, attribute, value)
|
||||||
record.errors.add(attribute, :below_limit) if value.present? && value.to_date > min_age.ago
|
record.errors.add(attribute, :below_limit) if value.present? && value.to_date > min_age.ago
|
||||||
rescue Date::Error
|
|
||||||
record.errors.add(attribute, :invalid)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
= vite_client_tag
|
= vite_client_tag
|
||||||
= vite_react_refresh_tag
|
= vite_react_refresh_tag
|
||||||
= vite_polyfills_tag
|
= vite_polyfills_tag
|
||||||
= theme_style_tags current_theme
|
= theme_style_tags ['glitch', 'default']
|
||||||
= vite_preload_file_tag "mastodon/locales/#{I18n.locale}.json" # TODO: fix preload for flavour
|
= vite_preload_file_tag "mastodon/locales/#{I18n.locale}.json" # TODO: fix preload for flavour
|
||||||
= render_initial_state
|
= render_initial_state
|
||||||
= flavoured_vite_typescript_tag 'embed.tsx', integrity: true, crossorigin: 'anonymous'
|
= flavoured_vite_typescript_tag 'embed.tsx', integrity: true, crossorigin: 'anonymous'
|
||||||
|
|||||||
@ -21,8 +21,8 @@
|
|||||||
selected: current_user.time_zone || Time.zone.tzinfo.name,
|
selected: current_user.time_zone || Time.zone.tzinfo.name,
|
||||||
wrapper: :with_label
|
wrapper: :with_label
|
||||||
|
|
||||||
- if Mastodon::Feature.new_theme_options_enabled?
|
.fields-group
|
||||||
.fields-group
|
= f.simple_fields_for :settings, current_user.settings do |ff|
|
||||||
.input.horizontal-options
|
.input.horizontal-options
|
||||||
= ff.input :'web.color_scheme',
|
= ff.input :'web.color_scheme',
|
||||||
as: :radio_buttons,
|
as: :radio_buttons,
|
||||||
|
|||||||
15
app/workers/purge_custom_emoji_worker.rb
Normal file
15
app/workers/purge_custom_emoji_worker.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class PurgeCustomEmojiWorker
|
||||||
|
include Sidekiq::IterableJob
|
||||||
|
|
||||||
|
def build_enumerator(domain, cursor:)
|
||||||
|
return if domain.blank?
|
||||||
|
|
||||||
|
active_record_batches_enumerator(CustomEmoji.by_domain_and_subdomains(domain), cursor:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def each_iteration(custom_emojis, _domain)
|
||||||
|
AttachmentBatch.new(CustomEmoji, custom_emojis).delete
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -2022,10 +2022,7 @@ en:
|
|||||||
review_link: Review terms of service
|
review_link: Review terms of service
|
||||||
title: The terms of service of %{domain} are changing
|
title: The terms of service of %{domain} are changing
|
||||||
themes:
|
themes:
|
||||||
contrast: Mastodon (High contrast)
|
default: Mastodon
|
||||||
default: Mastodon (Dark)
|
|
||||||
mastodon-light: Mastodon (Light)
|
|
||||||
system: Automatic (use system theme)
|
|
||||||
time:
|
time:
|
||||||
formats:
|
formats:
|
||||||
default: "%b %d, %Y, %H:%M"
|
default: "%b %d, %Y, %H:%M"
|
||||||
|
|||||||
@ -20,7 +20,7 @@ defaults: &defaults
|
|||||||
preview_sensitive_media: false
|
preview_sensitive_media: false
|
||||||
noindex: false
|
noindex: false
|
||||||
flavour: 'glitch'
|
flavour: 'glitch'
|
||||||
skin: 'system'
|
skin: 'default'
|
||||||
trends: true
|
trends: true
|
||||||
trendable_by_default: false
|
trendable_by_default: false
|
||||||
trending_status_cw: true
|
trending_status_cw: true
|
||||||
|
|||||||
19
db/migrate/20260209142402_migrate_default_theme_setting.rb
Normal file
19
db/migrate/20260209142402_migrate_default_theme_setting.rb
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class MigrateDefaultThemeSetting < ActiveRecord::Migration[8.0]
|
||||||
|
class Setting < ApplicationRecord; end
|
||||||
|
|
||||||
|
def up
|
||||||
|
Setting.reset_column_information
|
||||||
|
|
||||||
|
setting = Setting.find_by(var: 'theme')
|
||||||
|
return unless setting.present? && setting.attributes['value'].present?
|
||||||
|
|
||||||
|
theme = YAML.safe_load(setting.attributes['value'], permitted_classes: [ActiveSupport::HashWithIndifferentAccess, Symbol])
|
||||||
|
return unless %w(mastodon-light contrast system).include?(theme)
|
||||||
|
|
||||||
|
setting.update_column('value', "--- default\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down; end
|
||||||
|
end
|
||||||
31
db/migrate/20260209143308_migrate_user_theme.rb
Normal file
31
db/migrate/20260209143308_migrate_user_theme.rb
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class MigrateUserTheme < ActiveRecord::Migration[8.0]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
# Dummy classes, to make migration possible across version changes
|
||||||
|
class User < ApplicationRecord; end
|
||||||
|
|
||||||
|
def up
|
||||||
|
User.where.not(settings: nil).find_each do |user|
|
||||||
|
settings = Oj.load(user.attributes_before_type_cast['settings'])
|
||||||
|
next if settings.nil? || settings['theme'].blank? || %w(system default mastodon-light contrast).exclude?(settings['theme'])
|
||||||
|
|
||||||
|
case settings['theme']
|
||||||
|
when 'default'
|
||||||
|
settings['web.color_scheme'] = 'dark'
|
||||||
|
settings['web.contrast'] = 'auto'
|
||||||
|
when 'contrast'
|
||||||
|
settings['web.color_scheme'] = 'dark'
|
||||||
|
settings['web.contrast'] = 'high'
|
||||||
|
when 'mastodon-light'
|
||||||
|
settings['web.color_scheme'] = 'light'
|
||||||
|
settings['web.contrast'] = 'auto'
|
||||||
|
end
|
||||||
|
|
||||||
|
settings['theme'] = 'default'
|
||||||
|
|
||||||
|
user.update_column('settings', Oj.dump(settings))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2026_01_27_141820) do
|
ActiveRecord::Schema[8.0].define(version: 2026_02_09_143308) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pg_catalog.plpgsql"
|
enable_extension "pg_catalog.plpgsql"
|
||||||
|
|
||||||
|
|||||||
@ -154,6 +154,11 @@ namespace :tests do
|
|||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unless Setting.theme == 'default'
|
||||||
|
puts 'Default theme setting not migrated as expected'
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
|
||||||
puts 'No errors found. Database state is consistent with a successful migration process.'
|
puts 'No errors found. Database state is consistent with a successful migration process.'
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -177,7 +182,8 @@ namespace :tests do
|
|||||||
(id, thing_type, thing_id, var, value, created_at, updated_at)
|
(id, thing_type, thing_id, var, value, created_at, updated_at)
|
||||||
VALUES
|
VALUES
|
||||||
(7, NULL, NULL, 'timeline_preview', E'--- false\n', now(), now()),
|
(7, NULL, NULL, 'timeline_preview', E'--- false\n', now(), now()),
|
||||||
(8, NULL, NULL, 'trends_as_landing_page', E'--- false\n', now(), now());
|
(8, NULL, NULL, 'trends_as_landing_page', E'--- false\n', now(), now()),
|
||||||
|
(9, NULL, NULL, 'theme', E'--- system', now(), now());
|
||||||
|
|
||||||
/* Doorkeeper records
|
/* Doorkeeper records
|
||||||
While the `read:me` scope was technically not valid in 3.3.0,
|
While the `read:me` scope was technically not valid in 3.3.0,
|
||||||
|
|||||||
@ -6,35 +6,13 @@ RSpec.describe ThemeHelper do
|
|||||||
describe 'theme_style_tags' do
|
describe 'theme_style_tags' do
|
||||||
let(:result) { helper.theme_style_tags(theme) }
|
let(:result) { helper.theme_style_tags(theme) }
|
||||||
|
|
||||||
context 'when using "system" theme' do
|
|
||||||
let(:theme) { ['glitch', 'system'] }
|
|
||||||
|
|
||||||
it 'returns the default theme' do
|
|
||||||
expect(html_links.first.attributes.symbolize_keys)
|
|
||||||
.to include(
|
|
||||||
href: have_attributes(value: match(/contrast/))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when using "default" theme' do
|
context 'when using "default" theme' do
|
||||||
let(:theme) { ['glitch', 'default'] }
|
let(:theme) { ['glitch', 'default'] }
|
||||||
|
|
||||||
it 'returns the default stylesheet' do
|
it 'returns the default stylesheet' do
|
||||||
expect(html_links.last.attributes.symbolize_keys)
|
expect(html_links.last.attributes.symbolize_keys)
|
||||||
.to include(
|
.to include(
|
||||||
href: have_attributes(value: match(/contrast/))
|
href: have_attributes(value: match(/default/))
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when using other theme' do
|
|
||||||
let(:theme) { ['glitch', 'contrast'] }
|
|
||||||
|
|
||||||
it 'returns the theme stylesheet without color scheme information' do
|
|
||||||
expect(html_links.first.attributes.symbolize_keys)
|
|
||||||
.to include(
|
|
||||||
href: have_attributes(value: match(/contrast/))
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -122,6 +100,48 @@ RSpec.describe ThemeHelper do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#current_theme' do
|
||||||
|
subject { helper.current_theme }
|
||||||
|
|
||||||
|
context 'when user is not signed in' do
|
||||||
|
context 'when theme was not changed in settings' do
|
||||||
|
it { is_expected.to eq(['glitch', 'default']) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is signed in' do
|
||||||
|
before { allow(helper).to receive(:current_user).and_return(current_user) }
|
||||||
|
|
||||||
|
let(:current_user) { Fabricate :user }
|
||||||
|
|
||||||
|
context 'when user did not set theme' do
|
||||||
|
it { is_expected.to eq(['glitch', 'default']) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user set theme' do
|
||||||
|
before { current_user.settings.update(skin: 'alternate', noindex: false) }
|
||||||
|
|
||||||
|
context 'when theme is not valid' do
|
||||||
|
it { is_expected.to eq(['glitch', 'default']) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#page_color_scheme' do
|
||||||
|
subject { helper.page_color_scheme }
|
||||||
|
|
||||||
|
context 'when force_color_scheme is present' do
|
||||||
|
before { helper.content_for(:force_color_scheme) { 'value' } }
|
||||||
|
|
||||||
|
it { is_expected.to eq('value') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when force_color_scheme is absent' do
|
||||||
|
it { is_expected.to eq('auto') }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def html_links
|
def html_links
|
||||||
|
|||||||
@ -13,8 +13,6 @@ RSpec.describe 'Settings preferences appearance page' do
|
|||||||
expect(page)
|
expect(page)
|
||||||
.to have_private_cache_control
|
.to have_private_cache_control
|
||||||
|
|
||||||
# TODO: glitch-soc's option is elsewhere
|
|
||||||
# select 'contrast', from: theme_selection_field
|
|
||||||
check confirm_reblog_field
|
check confirm_reblog_field
|
||||||
uncheck confirm_delete_field
|
uncheck confirm_delete_field
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ RSpec.describe DateOfBirthValidator do
|
|||||||
context 'with an invalid date' do
|
context 'with an invalid date' do
|
||||||
let(:invalid_date) { '76.830.10' }
|
let(:invalid_date) { '76.830.10' }
|
||||||
|
|
||||||
it { is_expected.to_not allow_values(invalid_date).for(:date_of_birth) }
|
it { is_expected.to_not allow_values(invalid_date).for(:date_of_birth).with_message(:blank) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a date below the age limit' do
|
context 'with a date below the age limit' do
|
||||||
|
|||||||
33
spec/workers/purge_custom_emoji_worker_spec.rb
Normal file
33
spec/workers/purge_custom_emoji_worker_spec.rb
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe PurgeCustomEmojiWorker do
|
||||||
|
let(:worker) { described_class.new }
|
||||||
|
|
||||||
|
let(:domain) { 'evil' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Fabricate(:custom_emoji)
|
||||||
|
Fabricate(:custom_emoji, domain: 'example.com')
|
||||||
|
Fabricate.times(5, :custom_emoji, domain: domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
context 'when domain is nil' do
|
||||||
|
it 'does not delete emojis' do
|
||||||
|
expect { worker.perform(nil) }
|
||||||
|
.to_not(change(CustomEmoji, :count))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when passing a domain' do
|
||||||
|
it 'deletes emojis from this domain only' do
|
||||||
|
expect { worker.perform(domain) }
|
||||||
|
.to change { CustomEmoji.where(domain: domain).count }.to(0)
|
||||||
|
.and not_change { CustomEmoji.local.count }
|
||||||
|
.and(not_change { CustomEmoji.where(domain: 'example.com').count })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -376,6 +376,7 @@ const startServer = async () => {
|
|||||||
req.scopes = result.rows[0].scopes.split(' ');
|
req.scopes = result.rows[0].scopes.split(' ');
|
||||||
req.accountId = result.rows[0].account_id;
|
req.accountId = result.rows[0].account_id;
|
||||||
req.chosenLanguages = result.rows[0].chosen_languages;
|
req.chosenLanguages = result.rows[0].chosen_languages;
|
||||||
|
req.permissions = result.rows[0].permissions;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accessTokenId: result.rows[0].id,
|
accessTokenId: result.rows[0].id,
|
||||||
@ -601,13 +602,13 @@ const startServer = async () => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} kind
|
* @param {string} kind
|
||||||
* @param {ResolvedAccount} account
|
* @param {Request} req
|
||||||
* @returns {Promise.<{ localAccess: boolean, remoteAccess: boolean }>}
|
* @returns {Promise.<{ localAccess: boolean, remoteAccess: boolean }>}
|
||||||
*/
|
*/
|
||||||
const getFeedAccessSettings = async (kind, account) => {
|
const getFeedAccessSettings = async (kind, req) => {
|
||||||
const access = { localAccess: true, remoteAccess: true };
|
const access = { localAccess: true, remoteAccess: true };
|
||||||
|
|
||||||
if (account.permissions & PERMISSION_VIEW_FEEDS) {
|
if (req.permissions & PERMISSION_VIEW_FEEDS) {
|
||||||
return access;
|
return access;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user