forked from github/cinny
🦉 allowing external images as setting
For the config.json (server-level default for new users):
```json
{
"defaultSettings": {
"externalImages": "homeserver"
}
}
```
User's own choice in the UI always overrides the config.json default.
This commit is contained in:
@@ -5,6 +5,7 @@ import { HTMLReactParserOptions } from 'html-react-parser';
|
||||
import { Avatar, Box, Chip, Header, Icon, Icons, Text, config } from 'folds';
|
||||
import { Opts as LinkifyOpts } from 'linkifyjs';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { useAllowExternalImages } from '../../../owl/hooks/useAllowExternalImages';
|
||||
import {
|
||||
factoryRenderLinkifyWithMention,
|
||||
getReactCustomHtmlParser,
|
||||
@@ -76,6 +77,7 @@ export function SearchResultGroup({
|
||||
}: SearchResultGroupProps) {
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const allowExternalImages = useAllowExternalImages(mx, room);
|
||||
const highlightRegex = useMemo(() => makeHighlightRegex(highlights), [highlights]);
|
||||
|
||||
const powerLevels = usePowerLevels(room);
|
||||
@@ -106,6 +108,7 @@ export function SearchResultGroup({
|
||||
linkifyOpts,
|
||||
highlightRegex,
|
||||
useAuthentication,
|
||||
allowExternalImages,
|
||||
handleSpoilerClick: spoilerClickHandler,
|
||||
handleMentionClick: mentionClickHandler,
|
||||
}),
|
||||
@@ -117,6 +120,7 @@ export function SearchResultGroup({
|
||||
mentionClickHandler,
|
||||
spoilerClickHandler,
|
||||
useAuthentication,
|
||||
allowExternalImages,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ import { useMentionClickHandler } from '../../hooks/useMentionClickHandler';
|
||||
import { useSpoilerClickHandler } from '../../hooks/useSpoilerClickHandler';
|
||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { useAllowExternalImages } from '../../../owl/hooks/useAllowExternalImages';
|
||||
import { useIgnoredUsers } from '../../hooks/useIgnoredUsers';
|
||||
import { useImagePackRooms } from '../../hooks/useImagePackRooms';
|
||||
import { useIsDirectRoom } from '../../hooks/useRoom';
|
||||
@@ -447,6 +448,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
const showUrlPreview = room.hasEncryptionStateEvent() ? encUrlPreview : urlPreview;
|
||||
const [showHiddenEvents] = useSetting(settingsAtom, 'showHiddenEvents');
|
||||
const [showDeveloperTools] = useSetting(settingsAtom, 'developerTools');
|
||||
const allowExternalImages = useAllowExternalImages(mx, room);
|
||||
|
||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
||||
@@ -528,10 +530,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
getReactCustomHtmlParser(mx, room.roomId, {
|
||||
linkifyOpts,
|
||||
useAuthentication,
|
||||
allowExternalImages,
|
||||
handleSpoilerClick: spoilerClickHandler,
|
||||
handleMentionClick: mentionClickHandler,
|
||||
}),
|
||||
[mx, room, linkifyOpts, spoilerClickHandler, mentionClickHandler, useAuthentication]
|
||||
[mx, room, linkifyOpts, spoilerClickHandler, mentionClickHandler, useAuthentication, allowExternalImages]
|
||||
);
|
||||
const parseMemberEvent = useMemberEventParser();
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ import { RenderMatrixEvent, useMatrixEventRenderer } from '../../../hooks/useMat
|
||||
import { RenderMessageContent } from '../../../components/RenderMessageContent';
|
||||
import { useSetting } from '../../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../../state/settings';
|
||||
import { useAllowExternalImages } from '../../../../owl/hooks/useAllowExternalImages';
|
||||
import * as customHtmlCss from '../../../styles/CustomHtml.css';
|
||||
import { EncryptedContent } from '../message';
|
||||
import { Image } from '../../../components/media';
|
||||
@@ -273,6 +274,7 @@ export const RoomPinMenu = forwardRef<HTMLDivElement, RoomPinMenuProps>(
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
|
||||
const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
|
||||
const allowExternalImages = useAllowExternalImages(mx, room);
|
||||
|
||||
const direct = useIsDirectRoom();
|
||||
const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
|
||||
@@ -307,10 +309,11 @@ export const RoomPinMenu = forwardRef<HTMLDivElement, RoomPinMenuProps>(
|
||||
getReactCustomHtmlParser(mx, room.roomId, {
|
||||
linkifyOpts,
|
||||
useAuthentication,
|
||||
allowExternalImages,
|
||||
handleSpoilerClick: spoilerClickHandler,
|
||||
handleMentionClick: mentionClickHandler,
|
||||
}),
|
||||
[mx, room, linkifyOpts, mentionClickHandler, spoilerClickHandler, useAuthentication]
|
||||
[mx, room, linkifyOpts, mentionClickHandler, spoilerClickHandler, useAuthentication, allowExternalImages]
|
||||
);
|
||||
|
||||
const renderMatrixEvent = useMatrixEventRenderer<[MatrixEvent, string, GetContentCallback]>(
|
||||
|
||||
@@ -32,7 +32,7 @@ import FocusTrap from 'focus-trap-react';
|
||||
import { Page, PageContent, PageHeader } from '../../../components/page';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
import { useSetting } from '../../../state/hooks/settings';
|
||||
import { DateFormat, MessageLayout, MessageSpacing, settingsAtom } from '../../../state/settings';
|
||||
import { DateFormat, ExternalImageMode, MessageLayout, MessageSpacing, settingsAtom } from '../../../state/settings';
|
||||
import { SettingTile } from '../../../components/setting-tile';
|
||||
import { KeySymbol } from '../../../utils/key-symbol';
|
||||
import { isMacOS } from '../../../utils/user-agent';
|
||||
@@ -879,6 +879,80 @@ function SelectMessageSpacing() {
|
||||
);
|
||||
}
|
||||
|
||||
const externalImageItems: { mode: ExternalImageMode; name: string }[] = [
|
||||
{ mode: 'never', name: 'Never' },
|
||||
{ mode: 'homeserver', name: 'Homeserver Only' },
|
||||
{ mode: 'always', name: 'Always' },
|
||||
];
|
||||
|
||||
function SelectExternalImages() {
|
||||
const [menuCords, setMenuCords] = useState<RectCords>();
|
||||
const [externalImages, setExternalImages] = useSetting(settingsAtom, 'externalImages');
|
||||
|
||||
const handleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
setMenuCords(evt.currentTarget.getBoundingClientRect());
|
||||
};
|
||||
|
||||
const handleSelect = (mode: ExternalImageMode) => {
|
||||
setExternalImages(mode);
|
||||
setMenuCords(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
size="300"
|
||||
variant="Secondary"
|
||||
outlined
|
||||
fill="Soft"
|
||||
radii="300"
|
||||
after={<Icon size="300" src={Icons.ChevronBottom} />}
|
||||
onClick={handleMenu}
|
||||
>
|
||||
<Text size="T300">
|
||||
{externalImageItems.find((i) => i.mode === externalImages)?.name ?? externalImages}
|
||||
</Text>
|
||||
</Button>
|
||||
<PopOut
|
||||
anchor={menuCords}
|
||||
offset={5}
|
||||
position="Bottom"
|
||||
align="End"
|
||||
content={
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
onDeactivate: () => setMenuCords(undefined),
|
||||
clickOutsideDeactivates: true,
|
||||
isKeyForward: (evt: KeyboardEvent) =>
|
||||
evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
|
||||
isKeyBackward: (evt: KeyboardEvent) =>
|
||||
evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Menu>
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||
{externalImageItems.map((item) => (
|
||||
<MenuItem
|
||||
key={item.mode}
|
||||
size="300"
|
||||
variant={externalImages === item.mode ? 'Primary' : 'Surface'}
|
||||
radii="300"
|
||||
onClick={() => handleSelect(item.mode)}
|
||||
>
|
||||
<Text size="T300">{item.name}</Text>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Box>
|
||||
</Menu>
|
||||
</FocusTrap>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Messages() {
|
||||
const [legacyUsernameColor, setLegacyUsernameColor] = useSetting(
|
||||
settingsAtom,
|
||||
@@ -966,6 +1040,12 @@ function Messages() {
|
||||
after={<Switch variant="Primary" value={encUrlPreview} onChange={setEncUrlPreview} />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="External Images"
|
||||
after={<SelectExternalImages />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="Show Hidden Events"
|
||||
|
||||
@@ -65,6 +65,7 @@ import {
|
||||
import { RenderMessageContent } from '../../../components/RenderMessageContent';
|
||||
import { useSetting } from '../../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../../state/settings';
|
||||
import { useAllowExternalImages } from '../../../../owl/hooks/useAllowExternalImages';
|
||||
import { Image } from '../../../components/media';
|
||||
import { ImageViewer } from '../../../components/image-viewer';
|
||||
import { GetContentCallback, MessageEvent, StateEvent } from '../../../../types/matrix/room';
|
||||
@@ -236,6 +237,7 @@ function RoomNotificationsGroupComp({
|
||||
const theme = useTheme();
|
||||
const accessibleTagColors = useAccessiblePowerTagColors(theme.kind, creatorsTag, powerLevelTags);
|
||||
|
||||
const allowExternalImages = useAllowExternalImages(mx, room);
|
||||
const mentionClickHandler = useMentionClickHandler(room.roomId);
|
||||
const spoilerClickHandler = useSpoilerClickHandler();
|
||||
|
||||
@@ -253,10 +255,11 @@ function RoomNotificationsGroupComp({
|
||||
getReactCustomHtmlParser(mx, room.roomId, {
|
||||
linkifyOpts,
|
||||
useAuthentication,
|
||||
allowExternalImages,
|
||||
handleSpoilerClick: spoilerClickHandler,
|
||||
handleMentionClick: mentionClickHandler,
|
||||
}),
|
||||
[mx, room, linkifyOpts, mentionClickHandler, spoilerClickHandler, useAuthentication]
|
||||
[mx, room, linkifyOpts, mentionClickHandler, spoilerClickHandler, useAuthentication, allowExternalImages]
|
||||
);
|
||||
|
||||
const renderMatrixEvent = useMatrixEventRenderer<[IRoomEvent, string, GetContentCallback]>(
|
||||
|
||||
@@ -319,6 +319,7 @@ export const getReactCustomHtmlParser = (
|
||||
handleSpoilerClick?: ReactEventHandler<HTMLElement>;
|
||||
handleMentionClick?: ReactEventHandler<HTMLElement>;
|
||||
useAuthentication?: boolean;
|
||||
allowExternalImages?: boolean;
|
||||
}
|
||||
): HTMLReactParserOptions => {
|
||||
const opts: HTMLReactParserOptions = {
|
||||
@@ -475,9 +476,17 @@ export const getReactCustomHtmlParser = (
|
||||
|
||||
if (name === 'img') {
|
||||
const isMxc = typeof props.src === 'string' && props.src.startsWith('mxc://');
|
||||
const isExternal = !isMxc && typeof props.src === 'string';
|
||||
const htmlSrc = isMxc
|
||||
? mxcUrlToHttp(mx, props.src, params.useAuthentication)
|
||||
: props.src;
|
||||
if (isExternal && !params.allowExternalImages) {
|
||||
return (
|
||||
<a href={props.src} target="_blank" rel="noreferrer noopener">
|
||||
{props.alt || props.title || props.src}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
if (htmlSrc && 'data-mx-emoticon' in props) {
|
||||
return (
|
||||
<span className={css.EmoticonBase}>
|
||||
|
||||
@@ -15,6 +15,8 @@ export enum MessageLayout {
|
||||
Bubble = 2,
|
||||
}
|
||||
|
||||
export type ExternalImageMode = 'always' | 'homeserver' | 'never';
|
||||
|
||||
export interface Settings {
|
||||
themeId?: string;
|
||||
useSystemTheme: boolean;
|
||||
@@ -39,6 +41,7 @@ export interface Settings {
|
||||
encUrlPreview: boolean;
|
||||
showHiddenEvents: boolean;
|
||||
legacyUsernameColor: boolean;
|
||||
externalImages: ExternalImageMode;
|
||||
|
||||
showNotifications: boolean;
|
||||
isNotificationSounds: boolean;
|
||||
@@ -73,6 +76,7 @@ const defaultSettings: Settings = {
|
||||
encUrlPreview: false,
|
||||
showHiddenEvents: false,
|
||||
legacyUsernameColor: false,
|
||||
externalImages: 'never',
|
||||
|
||||
showNotifications: true,
|
||||
isNotificationSounds: true,
|
||||
|
||||
14
src/owl/hooks/useAllowExternalImages.ts
Normal file
14
src/owl/hooks/useAllowExternalImages.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useMemo } from 'react';
|
||||
import { MatrixClient, Room } from 'matrix-js-sdk';
|
||||
import { useSetting } from '../../app/state/hooks/settings';
|
||||
import { settingsAtom } from '../../app/state/settings';
|
||||
|
||||
export function useAllowExternalImages(mx: MatrixClient, room: Room): boolean {
|
||||
const [externalImages] = useSetting(settingsAtom, 'externalImages');
|
||||
return useMemo(() => {
|
||||
if (externalImages === 'always') return true;
|
||||
if (externalImages === 'never') return false;
|
||||
const roomDomain = room.roomId.split(':')[1];
|
||||
return roomDomain === mx.getDomain();
|
||||
}, [externalImages, room.roomId, mx]);
|
||||
}
|
||||
Reference in New Issue
Block a user