mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
feat(app): give the topic and bio composers channel-aware autocomplete (#2002)
This commit is contained in:
1 parent
1153327c75
commit
2ca756d219
5 files changed
+25
-14
No files matched your search
+1
-2
@@ -4,7 +4,6 @@ import styles from '@app/features/channel/components/modals/channel_tabs/Channel
|
|||||||
import {
|
import {
|
||||||
type FormInputs,
|
type FormInputs,
|
||||||
MAX_TOPIC_LENGTH,
|
MAX_TOPIC_LENGTH,
|
||||||
TOPIC_AUTOCOMPLETE_TRIGGERS,
|
|
||||||
} from '@app/features/channel/components/modals/channel_tabs/channel_overview_tab/shared';
|
} from '@app/features/channel/components/modals/channel_tabs/channel_overview_tab/shared';
|
||||||
import type {Channel} from '@app/features/channel/models/Channel';
|
import type {Channel} from '@app/features/channel/models/Channel';
|
||||||
import type {FlatEmoji} from '@app/features/emoji/types/EmojiTypes';
|
import type {FlatEmoji} from '@app/features/emoji/types/EmojiTypes';
|
||||||
@@ -292,7 +291,7 @@ export const ChannelOverviewTopicEditor = observer(
|
|||||||
placeholder={i18n._(ADD_A_TOPIC_TO_THIS_CHANNEL_DESCRIPTOR)}
|
placeholder={i18n._(ADD_A_TOPIC_TO_THIS_CHANNEL_DESCRIPTOR)}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
channel={channel}
|
channel={channel}
|
||||||
allowedTriggers={[...TOPIC_AUTOCOMPLETE_TRIGGERS]}
|
allowSpecialMentions={false}
|
||||||
markdown={true}
|
markdown={true}
|
||||||
markdownParserFlags={TOPIC_MARKDOWN_PARSER_FLAGS}
|
markdownParserFlags={TOPIC_MARKDOWN_PARSER_FLAGS}
|
||||||
singleLine={false}
|
singleLine={false}
|
||||||
|
|||||||
-2
@@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import type {ChannelRtcRegion} from '@app/features/channel/commands/ChannelCommands';
|
import type {ChannelRtcRegion} from '@app/features/channel/commands/ChannelCommands';
|
||||||
import type {TriggerType} from '@app/features/messaging/utils/AutocompleteTriggerPolicy';
|
|
||||||
import type {ComboboxOption} from '@app/features/ui/components/form/FormCombobox';
|
import type {ComboboxOption} from '@app/features/ui/components/form/FormCombobox';
|
||||||
import {VOICE_CHANNEL_BITRATE_MAX, VOICE_CHANNEL_BITRATE_MIN} from '@fluxer/constants/src/LimitConstants';
|
import {VOICE_CHANNEL_BITRATE_MAX, VOICE_CHANNEL_BITRATE_MIN} from '@fluxer/constants/src/LimitConstants';
|
||||||
|
|
||||||
@@ -24,7 +23,6 @@ export const BITRATE_KBPS_MIN = VOICE_CHANNEL_BITRATE_MIN / 1000;
|
|||||||
export const BITRATE_KBPS_MAX = VOICE_CHANNEL_BITRATE_MAX / 1000;
|
export const BITRATE_KBPS_MAX = VOICE_CHANNEL_BITRATE_MAX / 1000;
|
||||||
export const BITRATE_KBPS_MARKERS: ReadonlyArray<number> = [BITRATE_KBPS_MIN, 64, 128, 256, BITRATE_KBPS_MAX];
|
export const BITRATE_KBPS_MARKERS: ReadonlyArray<number> = [BITRATE_KBPS_MIN, 64, 128, 256, BITRATE_KBPS_MAX];
|
||||||
export const MAX_TOPIC_LENGTH = 1024;
|
export const MAX_TOPIC_LENGTH = 1024;
|
||||||
export const TOPIC_AUTOCOMPLETE_TRIGGERS: Array<TriggerType> = ['emoji', 'mention', 'channel'];
|
|
||||||
|
|
||||||
export interface RtcRegionOption extends ComboboxOption<string | null> {
|
export interface RtcRegionOption extends ComboboxOption<string | null> {
|
||||||
region: ChannelRtcRegion | null;
|
region: ChannelRtcRegion | null;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export interface LexicalRichInputProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
channel?: Channel | null;
|
channel?: Channel | null;
|
||||||
allowedTriggers?: Array<TriggerType>;
|
allowedTriggers?: Array<TriggerType>;
|
||||||
|
allowSpecialMentions?: boolean;
|
||||||
markdown?: boolean;
|
markdown?: boolean;
|
||||||
markdownParserFlags?: number;
|
markdownParserFlags?: number;
|
||||||
singleLine?: boolean;
|
singleLine?: boolean;
|
||||||
@@ -69,6 +70,7 @@ export const LexicalRichInput = ({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
channel = null,
|
channel = null,
|
||||||
allowedTriggers,
|
allowedTriggers,
|
||||||
|
allowSpecialMentions = true,
|
||||||
markdown = true,
|
markdown = true,
|
||||||
markdownParserFlags,
|
markdownParserFlags,
|
||||||
singleLine = false,
|
singleLine = false,
|
||||||
@@ -130,12 +132,8 @@ export const LexicalRichInput = ({
|
|||||||
useLexicalAutocomplete({
|
useLexicalAutocomplete({
|
||||||
channel,
|
channel,
|
||||||
handleRef,
|
handleRef,
|
||||||
allowedTriggers:
|
allowedTriggers: allowedTriggers ?? (channel == null ? SAFE_CONTEXT_FREE_TRIGGERS : SAFE_CHANNEL_TRIGGERS),
|
||||||
allowedTriggers == null
|
allowSpecialMentions,
|
||||||
? channel == null
|
|
||||||
? SAFE_CONTEXT_FREE_TRIGGERS
|
|
||||||
: SAFE_CHANNEL_TRIGGERS
|
|
||||||
: allowedTriggers,
|
|
||||||
allowMediaOptions: false,
|
allowMediaOptions: false,
|
||||||
maxActualLength: maxLength,
|
maxActualLength: maxLength,
|
||||||
onExceedMaxLength,
|
onExceedMaxLength,
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ interface UseLexicalAutocompleteParams {
|
|||||||
handleRef: RefObject<ComposerHandle | null>;
|
handleRef: RefObject<ComposerHandle | null>;
|
||||||
allowedTriggers?: Array<TriggerType>;
|
allowedTriggers?: Array<TriggerType>;
|
||||||
allowMediaOptions?: boolean;
|
allowMediaOptions?: boolean;
|
||||||
|
allowSpecialMentions?: boolean;
|
||||||
maxActualLength?: number;
|
maxActualLength?: number;
|
||||||
onExceedMaxLength?: () => void;
|
onExceedMaxLength?: () => void;
|
||||||
i18n: I18n;
|
i18n: I18n;
|
||||||
@@ -161,6 +162,7 @@ export type {TriggerType} from '@app/features/messaging/utils/AutocompleteTrigge
|
|||||||
export function useLexicalAutocomplete({
|
export function useLexicalAutocomplete({
|
||||||
channel,
|
channel,
|
||||||
handleRef,
|
handleRef,
|
||||||
|
allowSpecialMentions,
|
||||||
allowedTriggers,
|
allowedTriggers,
|
||||||
allowMediaOptions = true,
|
allowMediaOptions = true,
|
||||||
maxActualLength,
|
maxActualLength,
|
||||||
@@ -287,7 +289,8 @@ export function useLexicalAutocomplete({
|
|||||||
setState: setGifState,
|
setState: setGifState,
|
||||||
});
|
});
|
||||||
|
|
||||||
const canMentionEveryone = channel != null && Permission.can(Permissions.MENTION_EVERYONE, channel);
|
const canMentionEveryone =
|
||||||
|
allowSpecialMentions !== false && channel != null && Permission.can(Permissions.MENTION_EVERYONE, channel);
|
||||||
const canUseCommand = useCallback(
|
const canUseCommand = useCallback(
|
||||||
(command: Command) => {
|
(command: Command) => {
|
||||||
if (command.type === 'simple') {
|
if (command.type === 'simple') {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {ExpressionPickerPopout} from '@app/features/expressions/components/popou
|
|||||||
import {LexicalRichInput, type LexicalRichInputHandle} from '@app/features/lexical/composer/LexicalRichInput';
|
import {LexicalRichInput, type LexicalRichInputHandle} from '@app/features/lexical/composer/LexicalRichInput';
|
||||||
import {MarkdownContext} from '@app/features/messaging/components/markdown/renderers/RendererTypes';
|
import {MarkdownContext} from '@app/features/messaging/components/markdown/renderers/RendererTypes';
|
||||||
import {useMarkdownKeybinds} from '@app/features/messaging/hooks/useMarkdownKeybinds';
|
import {useMarkdownKeybinds} from '@app/features/messaging/hooks/useMarkdownKeybinds';
|
||||||
import type {TriggerType} from '@app/features/messaging/utils/AutocompleteTriggerPolicy';
|
|
||||||
import {getParserFlagsForContext} from '@app/features/messaging/utils/markdown/MarkdownParserFlags';
|
import {getParserFlagsForContext} from '@app/features/messaging/utils/markdown/MarkdownParserFlags';
|
||||||
import type {MentionSegment} from '@app/features/messaging/utils/TextareaSegmentManager';
|
import type {MentionSegment} from '@app/features/messaging/utils/TextareaSegmentManager';
|
||||||
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
|
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
|
||||||
@@ -15,6 +14,7 @@ import surfaceStyles from '@app/features/ui/components/form/FormSurface.module.c
|
|||||||
import FocusRing from '@app/features/ui/focus_ring/FocusRing';
|
import FocusRing from '@app/features/ui/focus_ring/FocusRing';
|
||||||
import {Popout} from '@app/features/ui/popover/PopoverPopout';
|
import {Popout} from '@app/features/ui/popover/PopoverPopout';
|
||||||
import styles from '@app/features/user/components/modals/tabs/my_profile_tab/BioEditor.module.css';
|
import styles from '@app/features/user/components/modals/tabs/my_profile_tab/BioEditor.module.css';
|
||||||
|
import {showUserErrorModal} from '@app/features/user/utils/UserErrorModalUtils';
|
||||||
import {msg} from '@lingui/core/macro';
|
import {msg} from '@lingui/core/macro';
|
||||||
import {Trans, useLingui} from '@lingui/react/macro';
|
import {Trans, useLingui} from '@lingui/react/macro';
|
||||||
import {SmileyIcon} from '@phosphor-icons/react';
|
import {SmileyIcon} from '@phosphor-icons/react';
|
||||||
@@ -26,7 +26,14 @@ import {useCallback, useEffect, useId, useRef, useState} from 'react';
|
|||||||
const ABOUT_ME_DESCRIPTOR = msg({message: 'About me'});
|
const ABOUT_ME_DESCRIPTOR = msg({message: 'About me'});
|
||||||
const OPEN_EMOJI_PICKER_DESCRIPTOR = msg({message: 'Open emoji picker'});
|
const OPEN_EMOJI_PICKER_DESCRIPTOR = msg({message: 'Open emoji picker'});
|
||||||
const BIO_MARKDOWN_PARSER_FLAGS = getParserFlagsForContext(MarkdownContext.RESTRICTED_USER_BIO);
|
const BIO_MARKDOWN_PARSER_FLAGS = getParserFlagsForContext(MarkdownContext.RESTRICTED_USER_BIO);
|
||||||
const BIO_ALLOWED_TRIGGERS: ReadonlyArray<TriggerType> = Object.freeze(['emoji']);
|
const ABOUT_ME_IS_TOO_LONG_DESCRIPTOR = msg({
|
||||||
|
message: 'About me is too long',
|
||||||
|
comment: 'Error modal title shown when inserting an emoji into the profile bio would exceed the bio limit.',
|
||||||
|
});
|
||||||
|
const SHORTEN_YOUR_ABOUT_ME_AND_TRY_AGAIN_DESCRIPTOR = msg({
|
||||||
|
message: 'Shorten your about me and try again.',
|
||||||
|
comment: 'Body of the error modal shown when inserting an emoji into the profile bio would exceed the bio limit.',
|
||||||
|
});
|
||||||
const COMPOSER_SURFACE_INTERACTIVE_SELECTOR =
|
const COMPOSER_SURFACE_INTERACTIVE_SELECTOR =
|
||||||
'[data-channel-textarea], button, a, input, textarea, select, [role="button"]';
|
'[data-channel-textarea], button, a, input, textarea, select, [role="button"]';
|
||||||
|
|
||||||
@@ -96,6 +103,12 @@ export const BioEditor = observer(
|
|||||||
},
|
},
|
||||||
[onEmojiPickerOpenChange, onEmojiSelect],
|
[onEmojiPickerOpenChange, onEmojiSelect],
|
||||||
);
|
);
|
||||||
|
const handleExceedMaxLength = useCallback(() => {
|
||||||
|
showUserErrorModal(
|
||||||
|
i18n._(ABOUT_ME_IS_TOO_LONG_DESCRIPTOR),
|
||||||
|
i18n._(SHORTEN_YOUR_ABOUT_ME_AND_TRY_AGAIN_DESCRIPTOR),
|
||||||
|
);
|
||||||
|
}, [i18n]);
|
||||||
let resolvedPlaceholder = '';
|
let resolvedPlaceholder = '';
|
||||||
if (placeholder != null) {
|
if (placeholder != null) {
|
||||||
resolvedPlaceholder = placeholder;
|
resolvedPlaceholder = placeholder;
|
||||||
@@ -226,12 +239,12 @@ export const BioEditor = observer(
|
|||||||
placeholder={resolvedPlaceholder}
|
placeholder={resolvedPlaceholder}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
channel={null}
|
channel={null}
|
||||||
allowedTriggers={[...BIO_ALLOWED_TRIGGERS]}
|
|
||||||
markdown={true}
|
markdown={true}
|
||||||
markdownParserFlags={BIO_MARKDOWN_PARSER_FLAGS}
|
markdownParserFlags={BIO_MARKDOWN_PARSER_FLAGS}
|
||||||
singleLine={false}
|
singleLine={false}
|
||||||
size="form"
|
size="form"
|
||||||
maxLength={actualMaxLength}
|
maxLength={actualMaxLength}
|
||||||
|
onExceedMaxLength={handleExceedMaxLength}
|
||||||
className={styles.editor}
|
className={styles.editor}
|
||||||
autocompleteAnchor={wrapperRef.current}
|
autocompleteAnchor={wrapperRef.current}
|
||||||
id={editableId}
|
id={editableId}
|
||||||
|
|||||||
Reference in new issue
Block a user