Desktop: Lotus links use the public web app and open in-app (#248) #249
@@ -83,8 +83,6 @@ import { copyToClipboard } from '../../../utils/dom';
|
|||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
import { getMatrixToRoomEvent } from '../../../plugins/matrix-to';
|
import { getMatrixToRoomEvent } from '../../../plugins/matrix-to';
|
||||||
import { getLotusRoomPermalink } from '../../../plugins/lotus-permalink';
|
import { getLotusRoomPermalink } from '../../../plugins/lotus-permalink';
|
||||||
import { getOriginBaseUrl } from '../../../pages/pathUtils';
|
|
||||||
import { useClientConfig } from '../../../hooks/useClientConfig';
|
|
||||||
import { getViaServers } from '../../../plugins/via-servers';
|
import { getViaServers } from '../../../plugins/via-servers';
|
||||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
import { useRoomPinnedEvents } from '../../../hooks/useRoomPinnedEvents';
|
import { useRoomPinnedEvents } from '../../../hooks/useRoomPinnedEvents';
|
||||||
@@ -98,6 +96,7 @@ import { useLongPress } from '../../../hooks/useLongPress';
|
|||||||
import { ActionSheet } from '../../../components/action-sheet';
|
import { ActionSheet } from '../../../components/action-sheet';
|
||||||
import { useBookmarks } from '../../../hooks/useBookmarks';
|
import { useBookmarks } from '../../../hooks/useBookmarks';
|
||||||
import { PresenceRingAvatar } from '../../../components/presence';
|
import { PresenceRingAvatar } from '../../../components/presence';
|
||||||
|
import { useLotusShareBase } from '../../../hooks/useLotusLinkBase';
|
||||||
import { AvatarDecoration } from '../../../components/avatar-decoration/AvatarDecoration';
|
import { AvatarDecoration } from '../../../components/avatar-decoration/AvatarDecoration';
|
||||||
|
|
||||||
// Delivery status indicator for own messages
|
// Delivery status indicator for own messages
|
||||||
@@ -460,18 +459,11 @@ export const MessageCopyLotusLinkItem = as<
|
|||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
>(({ room, mEvent, onClose, ...props }, ref) => {
|
>(({ room, mEvent, onClose, ...props }, ref) => {
|
||||||
const { hashRouter } = useClientConfig();
|
const lotusBase = useLotusShareBase();
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
const eventId = mEvent.getId();
|
const eventId = mEvent.getId();
|
||||||
if (!eventId) return;
|
if (!eventId) return;
|
||||||
copyToClipboard(
|
copyToClipboard(getLotusRoomPermalink(lotusBase, room.roomId, eventId, getViaServers(room)));
|
||||||
getLotusRoomPermalink(
|
|
||||||
getOriginBaseUrl(hashRouter),
|
|
||||||
room.roomId,
|
|
||||||
eventId,
|
|
||||||
getViaServers(room),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
onClose?.();
|
onClose?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { lotusLinkBases, lotusShareBase } from './useLotusLinkBase';
|
||||||
|
|
||||||
|
const LOCAL = 'http://localhost:44548/#/';
|
||||||
|
const WEB = 'https://chat.lotusguild.org';
|
||||||
|
|
||||||
|
test('web: shared links use the page origin', () => {
|
||||||
|
assert.equal(lotusShareBase(WEB, false, WEB), WEB);
|
||||||
|
assert.deepEqual(lotusLinkBases(WEB, false, undefined), [WEB]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('desktop with webAppUrl: shared links use the public web app', () => {
|
||||||
|
assert.equal(lotusShareBase(LOCAL, true, `${WEB}/`), WEB);
|
||||||
|
assert.deepEqual(lotusLinkBases(LOCAL, true, WEB), [LOCAL, WEB]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('desktop without a valid webAppUrl falls back to the origin', () => {
|
||||||
|
[undefined, '', 'http://chat.lotusguild.org', 'nonsense'].forEach((v) => {
|
||||||
|
assert.equal(lotusShareBase(LOCAL, true, v), LOCAL, String(v));
|
||||||
|
assert.deepEqual(lotusLinkBases(LOCAL, true, v), [LOCAL]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useClientConfig } from './useClientConfig';
|
||||||
|
import { isTauri } from './useTauri';
|
||||||
|
import { getOriginBaseUrl } from '../pages/pathUtils';
|
||||||
|
import { resolveWebAppUrl } from '../utils/callInBrowser';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Gitea #248] Base URL for Lotus links meant for other people.
|
||||||
|
*
|
||||||
|
* On the web that's this page's origin. In the desktop app the page is served
|
||||||
|
* from a local address (and hash-routed), which nobody else can open, so use
|
||||||
|
* the public web app from config `webAppUrl` (web path routing) instead.
|
||||||
|
* Without a valid `webAppUrl` it falls back to the origin, as before.
|
||||||
|
*/
|
||||||
|
export const lotusShareBase = (originBase: string, desktop: boolean, webAppUrl: unknown): string =>
|
||||||
|
(desktop && resolveWebAppUrl(webAppUrl)) || originBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every base a Lotus link to this deployment may start with: the origin, plus
|
||||||
|
* the public web app in the desktop app, so a shared https link opens in-app.
|
||||||
|
*/
|
||||||
|
export const lotusLinkBases = (
|
||||||
|
originBase: string,
|
||||||
|
desktop: boolean,
|
||||||
|
webAppUrl: unknown,
|
||||||
|
): string[] => {
|
||||||
|
const shared = lotusShareBase(originBase, desktop, webAppUrl);
|
||||||
|
return shared === originBase ? [originBase] : [originBase, shared];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useLotusShareBase = (): string => {
|
||||||
|
const { hashRouter, webAppUrl } = useClientConfig();
|
||||||
|
return useMemo(
|
||||||
|
() => lotusShareBase(getOriginBaseUrl(hashRouter), isTauri(), webAppUrl),
|
||||||
|
[hashRouter, webAppUrl],
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -77,6 +77,7 @@ import { KeyboardShortcutsDialog, useKeyboardShortcutsTrigger } from '../../feat
|
|||||||
import { useRoomsListener } from '../../hooks/useRoomsListener';
|
import { useRoomsListener } from '../../hooks/useRoomsListener';
|
||||||
import { threadNotificationsAtom } from '../../state/threadNotifications';
|
import { threadNotificationsAtom } from '../../state/threadNotifications';
|
||||||
import { roomIdToActiveThreadIdAtomFamily } from '../../state/room/thread';
|
import { roomIdToActiveThreadIdAtomFamily } from '../../state/room/thread';
|
||||||
|
import { lotusLinkBases } from '../../hooks/useLotusLinkBase';
|
||||||
import {
|
import {
|
||||||
getThreadNotificationMode,
|
getThreadNotificationMode,
|
||||||
shouldNotifyThreadReply,
|
shouldNotifyThreadReply,
|
||||||
@@ -118,11 +119,12 @@ function SystemEmojiFeature() {
|
|||||||
|
|
||||||
// [Gitea #103] Mirror the privacy toggle into the html parser's module flag.
|
// [Gitea #103] Mirror the privacy toggle into the html parser's module flag.
|
||||||
function LotusPermalinkFeature() {
|
function LotusPermalinkFeature() {
|
||||||
const { hashRouter } = useClientConfig();
|
const { hashRouter, webAppUrl } = useClientConfig();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLotusPermalinkBase(getOriginBaseUrl(hashRouter));
|
// [Gitea #248] In the desktop app, links to the public web app count too.
|
||||||
|
setLotusPermalinkBase(lotusLinkBases(getOriginBaseUrl(hashRouter), isTauriApp(), webAppUrl));
|
||||||
return () => setLotusPermalinkBase(undefined);
|
return () => setLotusPermalinkBase(undefined);
|
||||||
}, [hashRouter]);
|
}, [hashRouter, webAppUrl]);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,13 +49,7 @@ import { roomToParentsAtom } from '../../../state/room/roomToParents';
|
|||||||
import { allRoomsAtom } from '../../../state/room-list/roomList';
|
import { allRoomsAtom } from '../../../state/room-list/roomList';
|
||||||
import { useAnyRoomLiveCall } from '../../../hooks/useSpaceLiveCall';
|
import { useAnyRoomLiveCall } from '../../../hooks/useSpaceLiveCall';
|
||||||
import { LiveDot } from './SpaceTabs.css';
|
import { LiveDot } from './SpaceTabs.css';
|
||||||
import {
|
import { getSpaceLobbyPath, getSpacePath, joinPathComponent } from '../../pathUtils';
|
||||||
getOriginBaseUrl,
|
|
||||||
getSpaceLobbyPath,
|
|
||||||
getSpacePath,
|
|
||||||
joinPathComponent,
|
|
||||||
} from '../../pathUtils';
|
|
||||||
import { useClientConfig } from '../../../hooks/useClientConfig';
|
|
||||||
import {
|
import {
|
||||||
SidebarAvatar,
|
SidebarAvatar,
|
||||||
SidebarItem,
|
SidebarItem,
|
||||||
@@ -102,6 +96,7 @@ import { settingsAtom } from '../../../state/settings';
|
|||||||
import { useOpenSpaceSettings } from '../../../state/hooks/spaceSettings';
|
import { useOpenSpaceSettings } from '../../../state/hooks/spaceSettings';
|
||||||
import { useRoomCreators } from '../../../hooks/useRoomCreators';
|
import { useRoomCreators } from '../../../hooks/useRoomCreators';
|
||||||
import { useRoomPermissions } from '../../../hooks/useRoomPermissions';
|
import { useRoomPermissions } from '../../../hooks/useRoomPermissions';
|
||||||
|
import { useLotusShareBase } from '../../../hooks/useLotusLinkBase';
|
||||||
import { InviteUserPrompt } from '../../../components/invite-user-prompt';
|
import { InviteUserPrompt } from '../../../components/invite-user-prompt';
|
||||||
|
|
||||||
type SpaceMenuProps = {
|
type SpaceMenuProps = {
|
||||||
@@ -112,7 +107,7 @@ type SpaceMenuProps = {
|
|||||||
const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
|
const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
|
||||||
({ room, requestClose, onUnpin }, ref) => {
|
({ room, requestClose, onUnpin }, ref) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const { hashRouter } = useClientConfig();
|
const lotusBase = useLotusShareBase();
|
||||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||||
const roomToParents = useAtomValue(roomToParentsAtom);
|
const roomToParents = useAtomValue(roomToParentsAtom);
|
||||||
const powerLevels = usePowerLevels(room);
|
const powerLevels = usePowerLevels(room);
|
||||||
@@ -152,9 +147,7 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
|
|||||||
const handleCopyLotusLink = () => {
|
const handleCopyLotusLink = () => {
|
||||||
const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId);
|
const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId);
|
||||||
const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room);
|
const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room);
|
||||||
copyToClipboard(
|
copyToClipboard(getLotusSpacePermalink(lotusBase, roomIdOrAlias, viaServers));
|
||||||
getLotusSpacePermalink(getOriginBaseUrl(hashRouter), roomIdOrAlias, viaServers),
|
|
||||||
);
|
|
||||||
requestClose();
|
requestClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -39,13 +39,7 @@ import {
|
|||||||
NavItemContent,
|
NavItemContent,
|
||||||
NavLink,
|
NavLink,
|
||||||
} from '../../../components/nav';
|
} from '../../../components/nav';
|
||||||
import {
|
import { getSpaceLobbyPath, getSpaceRoomPath, getSpaceSearchPath } from '../../pathUtils';
|
||||||
getOriginBaseUrl,
|
|
||||||
getSpaceLobbyPath,
|
|
||||||
getSpaceRoomPath,
|
|
||||||
getSpaceSearchPath,
|
|
||||||
} from '../../pathUtils';
|
|
||||||
import { useClientConfig } from '../../../hooks/useClientConfig';
|
|
||||||
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../../utils/matrix';
|
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../../utils/matrix';
|
||||||
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
|
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
|
||||||
import {
|
import {
|
||||||
@@ -92,6 +86,7 @@ import { ContainerColor } from '../../../styles/ContainerColor.css';
|
|||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import { BreakWord } from '../../../styles/Text.css';
|
import { BreakWord } from '../../../styles/Text.css';
|
||||||
import { InviteUserPrompt } from '../../../components/invite-user-prompt';
|
import { InviteUserPrompt } from '../../../components/invite-user-prompt';
|
||||||
|
import { useLotusShareBase } from '../../../hooks/useLotusLinkBase';
|
||||||
import { useCallEmbed } from '../../../hooks/useCallEmbed';
|
import { useCallEmbed } from '../../../hooks/useCallEmbed';
|
||||||
|
|
||||||
type SpaceMenuProps = {
|
type SpaceMenuProps = {
|
||||||
@@ -100,7 +95,7 @@ type SpaceMenuProps = {
|
|||||||
};
|
};
|
||||||
const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClose }, ref) => {
|
const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClose }, ref) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const { hashRouter } = useClientConfig();
|
const lotusBase = useLotusShareBase();
|
||||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||||
const [developerTools] = useSetting(settingsAtom, 'developerTools');
|
const [developerTools] = useSetting(settingsAtom, 'developerTools');
|
||||||
const roomToParents = useAtomValue(roomToParentsAtom);
|
const roomToParents = useAtomValue(roomToParentsAtom);
|
||||||
@@ -137,9 +132,7 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClo
|
|||||||
const handleCopyLotusLink = () => {
|
const handleCopyLotusLink = () => {
|
||||||
const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId);
|
const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId);
|
||||||
const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room);
|
const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room);
|
||||||
copyToClipboard(
|
copyToClipboard(getLotusSpacePermalink(lotusBase, roomIdOrAlias, viaServers));
|
||||||
getLotusSpacePermalink(getOriginBaseUrl(hashRouter), roomIdOrAlias, viaServers),
|
|
||||||
);
|
|
||||||
requestClose();
|
requestClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -122,11 +122,15 @@ const cleanHref = (href: string): string =>
|
|||||||
stripTrackingOnRender ? stripTrackingParams(href) : href;
|
stripTrackingOnRender ? stripTrackingParams(href) : href;
|
||||||
|
|
||||||
// [Gitea #130] Links to THIS deployment's room routes render and click like
|
// [Gitea #130] Links to THIS deployment's room routes render and click like
|
||||||
// matrix.to links. The base is set once from the client config
|
// matrix.to links. The bases are set once from the client config
|
||||||
// (ClientNonUIFeatures) because this module has no access to hooks.
|
// (ClientNonUIFeatures) because this module has no access to hooks. [Gitea
|
||||||
let lotusPermalinkBase: string | undefined;
|
// #248] Several: the desktop app knows both its local origin and the public web
|
||||||
export const setLotusPermalinkBase = (baseUrl: string | undefined): void => {
|
// app (`webAppUrl`), and people share the public one.
|
||||||
lotusPermalinkBase = baseUrl;
|
let lotusPermalinkBases: string[] = [];
|
||||||
|
export const setLotusPermalinkBase = (baseUrls: string | string[] | undefined): void => {
|
||||||
|
lotusPermalinkBases = (Array.isArray(baseUrls) ? baseUrls : [baseUrls]).filter(
|
||||||
|
(b): b is string => !!b,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* The matrix.to form of `href` when it is a matrix.to link or a Lotus
|
* The matrix.to form of `href` when it is a matrix.to link or a Lotus
|
||||||
@@ -134,7 +138,11 @@ export const setLotusPermalinkBase = (baseUrl: string | undefined): void => {
|
|||||||
*/
|
*/
|
||||||
export const toMatrixToHref = (href: string): string | undefined => {
|
export const toMatrixToHref = (href: string): string | undefined => {
|
||||||
if (testMatrixTo(href)) return href;
|
if (testMatrixTo(href)) return href;
|
||||||
return lotusPermalinkBase ? lotusPermalinkToMatrixTo(lotusPermalinkBase, href) : undefined;
|
for (const base of lotusPermalinkBases) {
|
||||||
|
const matrixTo = lotusPermalinkToMatrixTo(base, href);
|
||||||
|
if (matrixTo) return matrixTo;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LINKIFY_OPTS: LinkifyOpts = {
|
export const LINKIFY_OPTS: LinkifyOpts = {
|
||||||
|
|||||||
Reference in New Issue
Block a user