Compare commits
3
Commits
c0e8334f03
...
1ab7514741
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ab7514741 | ||
|
|
2b1f4ee5bc | ||
|
|
b71f9c95c6 |
@@ -47,6 +47,9 @@ type RenderImageProps = {
|
||||
onError: () => void;
|
||||
onClick: () => void;
|
||||
tabIndex: number;
|
||||
role: 'button';
|
||||
'aria-label': string;
|
||||
onKeyDown: (evt: React.KeyboardEvent<HTMLElement>) => void;
|
||||
};
|
||||
export type ImageContentProps = {
|
||||
body: string;
|
||||
@@ -188,6 +191,15 @@ export const ImageContent = as<'div', ImageContentProps>(
|
||||
onError: handleError,
|
||||
onClick: () => (onOpenViewer ? onOpenViewer() : setViewer(true)),
|
||||
tabIndex: 0,
|
||||
// [Gitea #164] Keyboard users could Tab to the image but not open it.
|
||||
role: 'button',
|
||||
'aria-label': `View image: ${body}`,
|
||||
onKeyDown: (evt) => {
|
||||
if (evt.key !== 'Enter' && evt.key !== ' ') return;
|
||||
evt.preventDefault();
|
||||
if (onOpenViewer) onOpenViewer();
|
||||
else setViewer(true);
|
||||
},
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -1176,6 +1176,18 @@ export const Message = React.memo(
|
||||
};
|
||||
|
||||
const isThreadedMessage = mEvent.threadRootId !== undefined;
|
||||
// [Gitea #165] A root that already has a thread: the same action opens it,
|
||||
// so say "View Thread (N replies)" rather than "Reply in Thread".
|
||||
// The SDK gives a thread root its own id as threadRootId, so without this
|
||||
// a root (the one message that HAS a thread) got no thread action at all.
|
||||
const isThreadRoot = !!mEvent.getId() && mEvent.threadRootId === mEvent.getId();
|
||||
const showThreadAction = !isThreadedMessage || isThreadRoot;
|
||||
const existingThread = mEvent.getId() ? room.getThread(mEvent.getId()!) : null;
|
||||
const threadReplies = existingThread?.length ?? 0;
|
||||
const threadActionLabel =
|
||||
threadReplies > 0
|
||||
? `View Thread (${threadReplies} ${threadReplies === 1 ? 'reply' : 'replies'})`
|
||||
: 'Reply in Thread';
|
||||
|
||||
// The full action menu, shared by the desktop PopOut and the touch
|
||||
// bottom sheet (#166).
|
||||
@@ -1273,10 +1285,12 @@ export const Message = React.memo(
|
||||
</Text>
|
||||
</MenuItem>
|
||||
)}
|
||||
{!isThreadedMessage && (
|
||||
{showThreadAction && (
|
||||
<MenuItem
|
||||
size="300"
|
||||
after={<Icon src={Icons.ThreadPlus} size="100" />}
|
||||
after={
|
||||
<Icon src={threadReplies > 0 ? Icons.Thread : Icons.ThreadPlus} size="100" />
|
||||
}
|
||||
radii="300"
|
||||
data-event-id={mEvent.getId()}
|
||||
onClick={(evt: React.MouseEvent<HTMLButtonElement>) => {
|
||||
@@ -1285,7 +1299,7 @@ export const Message = React.memo(
|
||||
}}
|
||||
>
|
||||
<Text className={css.MessageMenuItemText} as="span" size="T300" truncate>
|
||||
Reply in Thread
|
||||
{threadActionLabel}
|
||||
</Text>
|
||||
</MenuItem>
|
||||
)}
|
||||
@@ -1465,16 +1479,16 @@ export const Message = React.memo(
|
||||
>
|
||||
<Icon src={Icons.ReplyArrow} size="100" />
|
||||
</IconButton>
|
||||
{!isThreadedMessage && (
|
||||
{showThreadAction && (
|
||||
<IconButton
|
||||
onClick={(ev) => onReplyClick(ev, true)}
|
||||
data-event-id={mEvent.getId()}
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
aria-label="Reply in thread"
|
||||
aria-label={threadReplies > 0 ? 'View thread' : 'Reply in thread'}
|
||||
>
|
||||
<Icon src={Icons.ThreadPlus} size="100" />
|
||||
<Icon src={threadReplies > 0 ? Icons.Thread : Icons.ThreadPlus} size="100" />
|
||||
</IconButton>
|
||||
)}
|
||||
{canEditEventOrCaption(mx, mEvent) && onEditId && (
|
||||
|
||||
@@ -37,11 +37,15 @@ import { zIndices } from '../styles/zIndex';
|
||||
import { OIDC_CALLBACK_PATH } from './paths';
|
||||
import { OidcCallback } from './auth/oidc/OidcCallback';
|
||||
|
||||
// The emoji families (Twemoji when "Twitter emoji" is on, Twemoji flags on
|
||||
// Windows — see SystemEmojiFeature) must sit before the generic family, or the
|
||||
// chosen font's stack silently drops them.
|
||||
const EMOJI_FONTS = 'var(--font-flags), var(--font-emoji)';
|
||||
const FONT_MAP: Record<string, string> = {
|
||||
system: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
||||
inter: "'InterVariable', sans-serif",
|
||||
'jetbrains-mono': "'JetBrains Mono', monospace",
|
||||
'fira-code': "'Fira Code', monospace",
|
||||
system: `system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', ${EMOJI_FONTS}, sans-serif`,
|
||||
inter: `'InterVariable', ${EMOJI_FONTS}, sans-serif`,
|
||||
'jetbrains-mono': `'JetBrains Mono', ${EMOJI_FONTS}, monospace`,
|
||||
'fira-code': `'Fira Code', ${EMOJI_FONTS}, monospace`,
|
||||
};
|
||||
|
||||
function AppearanceEffects() {
|
||||
|
||||
@@ -91,6 +91,13 @@ const LogoHighlightSVG = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/lotu
|
||||
|
||||
const INVITE_NOTIFY_ARM_DELAY_MS = 3000;
|
||||
|
||||
const IS_WINDOWS =
|
||||
typeof navigator !== 'undefined' &&
|
||||
/Windows/i.test(
|
||||
(navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ??
|
||||
navigator.userAgent,
|
||||
);
|
||||
|
||||
function SystemEmojiFeature() {
|
||||
const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
|
||||
|
||||
@@ -99,6 +106,12 @@ function SystemEmojiFeature() {
|
||||
} else {
|
||||
document.documentElement.style.setProperty('--font-emoji', 'Twemoji_DISABLED');
|
||||
}
|
||||
// Windows has no flag emoji glyphs (🇺🇸 shows as "US"): use Twemoji for the
|
||||
// flag letters only, keeping every other emoji native.
|
||||
document.documentElement.style.setProperty(
|
||||
'--font-flags',
|
||||
IS_WINDOWS ? 'TwemojiFlags' : 'TwemojiFlags_DISABLED',
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
+15
-1
@@ -6,6 +6,19 @@
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Country flags only (regional indicator letters U+1F1E6–1F1FF), from the same
|
||||
Twemoji file. Windows' emoji font has no flag glyphs and draws 🇺🇸 as the
|
||||
letters "US"; SystemEmojiFeature enables this family on Windows. The
|
||||
unicode-range means the file is only fetched when a flag is on screen. */
|
||||
@font-face {
|
||||
font-family: TwemojiFlags;
|
||||
src:
|
||||
url('../public/font/Twemoji.Mozilla.v15.1.0.woff2'),
|
||||
url('../public/font/Twemoji.Mozilla.v15.1.0.ttf');
|
||||
unicode-range: U+1F1E6-1F1FF;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
--tc-link: hsl(213deg 100% 45%);
|
||||
|
||||
@@ -28,7 +41,8 @@
|
||||
--mx-uc-8: hsl(94, 100%, 35%);
|
||||
|
||||
--font-emoji: 'Twemoji_DISABLED';
|
||||
--font-secondary: 'InterVariable', var(--font-emoji), sans-serif;
|
||||
--font-flags: 'TwemojiFlags_DISABLED';
|
||||
--font-secondary: 'InterVariable', var(--font-flags), var(--font-emoji), sans-serif;
|
||||
}
|
||||
|
||||
.dark-theme,
|
||||
|
||||
Reference in New Issue
Block a user