Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e11a6fc0b |
@@ -47,12 +47,8 @@ import {
|
|||||||
recentForwardTargetsAtom,
|
recentForwardTargetsAtom,
|
||||||
addRecentForwardTarget,
|
addRecentForwardTarget,
|
||||||
} from '../../../state/recentForwardTargets';
|
} from '../../../state/recentForwardTargets';
|
||||||
import {
|
import { buildForwardContent, buildPlaintextAttachmentContent } from './forwardContent';
|
||||||
buildForwardContent,
|
import { useLocalRoomNames } from '../../../hooks/useRoomMeta';
|
||||||
buildPlaintextAttachmentContent,
|
|
||||||
forwardEncryptionWarning,
|
|
||||||
} from './forwardContent';
|
|
||||||
import { useLocalRoomNames, getLocalRoomName } from '../../../hooks/useRoomMeta';
|
|
||||||
|
|
||||||
type RoomRowProps = {
|
type RoomRowProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
@@ -279,14 +275,6 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
|
|||||||
// Selection persists across query changes: a room selected then filtered out
|
// Selection persists across query changes: a room selected then filtered out
|
||||||
// of the rendered slice stays selected.
|
// of the rendered slice stays selected.
|
||||||
const [selectedRoomIds, setSelectedRoomIds] = useState<Set<string>>(new Set());
|
const [selectedRoomIds, setSelectedRoomIds] = useState<Set<string>>(new Set());
|
||||||
const encryptionWarning = forwardEncryptionWarning(
|
|
||||||
!!mx.getRoom(mEvent.getRoomId() ?? '')?.hasEncryptionStateEvent(),
|
|
||||||
[...selectedRoomIds]
|
|
||||||
.map((id) => mx.getRoom(id))
|
|
||||||
.filter((r): r is Room => !!r && !r.hasEncryptionStateEvent())
|
|
||||||
// The name the user sees (their own "Rename for me" name, if set).
|
|
||||||
.map((r) => getLocalRoomName(mx, r.roomId) ?? r.name),
|
|
||||||
);
|
|
||||||
|
|
||||||
const toggleRoom = useCallback((roomId: string) => {
|
const toggleRoom = useCallback((roomId: string) => {
|
||||||
setSelectedRoomIds((prev) => {
|
setSelectedRoomIds((prev) => {
|
||||||
@@ -602,13 +590,7 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
|
|||||||
shrink="No"
|
shrink="No"
|
||||||
direction="Column"
|
direction="Column"
|
||||||
style={{ padding: `${config.space.S200} ${config.space.S400}` }}
|
style={{ padding: `${config.space.S200} ${config.space.S400}` }}
|
||||||
gap="200"
|
|
||||||
>
|
>
|
||||||
{encryptionWarning && (
|
|
||||||
<Text size="T200" role="note" style={{ color: color.Warning.Main }}>
|
|
||||||
{encryptionWarning}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
<Button
|
<Button
|
||||||
variant="Primary"
|
variant="Primary"
|
||||||
size="400"
|
size="400"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { test } from 'node:test';
|
import { test } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { MatrixClient, MatrixEvent } from 'matrix-js-sdk';
|
import { MatrixClient, MatrixEvent } from 'matrix-js-sdk';
|
||||||
import { buildForwardContent, forwardEncryptionWarning, getForwardedMeta } from './forwardContent';
|
import { buildForwardContent, getForwardedMeta } from './forwardContent';
|
||||||
|
|
||||||
// Pure content builder buildForwardContent: refuses undecryptable events, forwards
|
// Pure content builder buildForwardContent: refuses undecryptable events, forwards
|
||||||
// the latest edit (`m.new_content`), and strips reply fallbacks + `m.relates_to`.
|
// the latest edit (`m.new_content`), and strips reply fallbacks + `m.relates_to`.
|
||||||
@@ -171,16 +171,3 @@ test('getForwardedMeta rejects malformed metadata', () => {
|
|||||||
assert.equal(getForwardedMeta({ 'io.lotus.forwarded': { sender: 1 } }), undefined);
|
assert.equal(getForwardedMeta({ 'io.lotus.forwarded': { sender: 1 } }), undefined);
|
||||||
assert.equal(getForwardedMeta({}), undefined);
|
assert.equal(getForwardedMeta({}), undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('forwardEncryptionWarning: only from an encrypted room into unencrypted ones (#330)', () => {
|
|
||||||
assert.equal(forwardEncryptionWarning(false, ['general']), undefined);
|
|
||||||
assert.equal(forwardEncryptionWarning(true, []), undefined);
|
|
||||||
assert.match(
|
|
||||||
forwardEncryptionWarning(true, ['general']) ?? '',
|
|
||||||
/^general isn't encrypted\..*that room, along with who originally wrote it\.$/,
|
|
||||||
);
|
|
||||||
assert.match(
|
|
||||||
forwardEncryptionWarning(true, ['a', 'b']) ?? '',
|
|
||||||
/^2 of the rooms you picked aren't encrypted\..*those rooms/,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -122,23 +122,3 @@ export async function buildPlaintextAttachmentContent(
|
|||||||
|
|
||||||
return plainContent;
|
return plainContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* [Gitea #330] The note shown when forwarding out of an encrypted room into
|
|
||||||
* rooms that aren't: the text (and a re-uploaded attachment) becomes readable
|
|
||||||
* by the server, and the original author is shown with it. Undefined when
|
|
||||||
* there's nothing to warn about.
|
|
||||||
*/
|
|
||||||
export function forwardEncryptionWarning(
|
|
||||||
sourceEncrypted: boolean,
|
|
||||||
unencryptedDestNames: string[],
|
|
||||||
): string | undefined {
|
|
||||||
if (!sourceEncrypted || unencryptedDestNames.length === 0) return undefined;
|
|
||||||
const where =
|
|
||||||
unencryptedDestNames.length === 1
|
|
||||||
? `${unencryptedDestNames[0]} isn't encrypted.`
|
|
||||||
: `${unencryptedDestNames.length} of the rooms you picked aren't encrypted.`;
|
|
||||||
return `${where} The forwarded message (and any attachment) will be readable by the server and everyone in ${
|
|
||||||
unencryptedDestNames.length === 1 ? 'that room' : 'those rooms'
|
|
||||||
}, along with who originally wrote it.`;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import {
|
|||||||
markAutoTranslateTried,
|
markAutoTranslateTried,
|
||||||
} from '../state/translation';
|
} from '../state/translation';
|
||||||
import { chromeTranslationEngine as engine } from '../utils/translation/chromeEngine';
|
import { chromeTranslationEngine as engine } from '../utils/translation/chromeEngine';
|
||||||
import { sameLanguage } from '../utils/translation/langUtils';
|
import { sameLanguage, translateSkipMessage } from '../utils/translation/langUtils';
|
||||||
|
import { toastQueueAtom } from '../state/toast';
|
||||||
|
|
||||||
export type MsgTranslationStatus =
|
export type MsgTranslationStatus =
|
||||||
| 'idle' // not translating (original shown)
|
| 'idle' // not translating (original shown)
|
||||||
@@ -52,6 +53,7 @@ export const useMessageTranslation = (eventId: string, text: string): MsgTransla
|
|||||||
// Write-only: reading the whole cache array here (via useAtom) would
|
// Write-only: reading the whole cache array here (via useAtom) would
|
||||||
// resubscribe this message to every other message's writes. [Gitea #39]
|
// resubscribe this message to every other message's writes. [Gitea #39]
|
||||||
const setCache = useSetAtom(translationCacheAtom);
|
const setCache = useSetAtom(translationCacheAtom);
|
||||||
|
const setToast = useSetAtom(toastQueueAtom);
|
||||||
|
|
||||||
// The key includes a fingerprint of `text`, so an edit (same event id, new
|
// The key includes a fingerprint of `text`, so an edit (same event id, new
|
||||||
// body) misses the old entry and re-translates instead of showing stale
|
// body) misses the old entry and re-translates instead of showing stale
|
||||||
@@ -124,6 +126,16 @@ export const useMessageTranslation = (eventId: string, text: string): MsgTransla
|
|||||||
if (!from || sameLanguage(from, targetLang)) {
|
if (!from || sameLanguage(from, targetLang)) {
|
||||||
setStatus('skipped');
|
setStatus('skipped');
|
||||||
setActive(false);
|
setActive(false);
|
||||||
|
// [Gitea #287] Say why nothing happened instead of silently undoing
|
||||||
|
// the click (no chip shows once translation is switched back off).
|
||||||
|
setToast({
|
||||||
|
id: `translate-skipped-${eventId}-${Date.now()}`,
|
||||||
|
displayName: 'Translate',
|
||||||
|
body: translateSkipMessage(from, targetLang),
|
||||||
|
roomName: '',
|
||||||
|
roomId: '',
|
||||||
|
onClick: () => undefined,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setFromLang(from);
|
setFromLang(from);
|
||||||
@@ -154,7 +166,7 @@ export const useMessageTranslation = (eventId: string, text: string): MsgTransla
|
|||||||
// Invalidate this run's writes if inputs change / unmount.
|
// Invalidate this run's writes if inputs change / unmount.
|
||||||
runIdRef.current += 1;
|
runIdRef.current += 1;
|
||||||
};
|
};
|
||||||
}, [active, cached, supported, text, targetLang, key, setActive, setCache]);
|
}, [active, cached, supported, text, targetLang, key, setActive, setCache, setToast, eventId]);
|
||||||
|
|
||||||
// When switched off, drop back to idle (keep the cached translation around so
|
// When switched off, drop back to idle (keep the cached translation around so
|
||||||
// toggling back on is instant).
|
// toggling back on is instant).
|
||||||
|
|||||||
@@ -85,3 +85,9 @@ export function languageName(code: string, uiLocale = 'en'): string {
|
|||||||
export function isSupportedTargetLang(code: string): boolean {
|
export function isSupportedTargetLang(code: string): boolean {
|
||||||
return CURATED_NAME.has(normalizeLang(code));
|
return CURATED_NAME.has(normalizeLang(code));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Pure: why a manual Translate did nothing (#287). */
|
||||||
|
export const translateSkipMessage = (from: string | undefined, targetLang: string): string =>
|
||||||
|
from
|
||||||
|
? `This message is already in ${languageName(targetLang) || targetLang}.`
|
||||||
|
: "Couldn't tell what language this message is in, so it wasn't translated.";
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { translateSkipMessage } from './langUtils';
|
||||||
|
|
||||||
|
test('translateSkipMessage explains a Translate that did nothing (#287)', () => {
|
||||||
|
assert.equal(translateSkipMessage('en', 'en'), 'This message is already in English.');
|
||||||
|
assert.match(translateSkipMessage(undefined, 'en'), /^Couldn't tell what language/);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user