Skip to content

"Copy link to topic" in topic action sheet #1762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions assets/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@
"@actionSheetOptionMarkTopicAsRead": {
"description": "Option to mark a specific topic as read in the action sheet."
},
"actionSheetOptionCopyTopicLink": "Copy link to topic",
"@actionSheetOptionCopyTopicLink": {
"description": "Label for copy topic link button in action sheet."
},
"errorWebAuthOperationalErrorTitle": "Something went wrong",
"@errorWebAuthOperationalErrorTitle": {
"description": "Error title when third-party authentication has an operational error (not necessarily caused by invalid credentials)."
Expand Down Expand Up @@ -361,6 +365,10 @@
"@successMessageLinkCopied": {
"description": "Message when link of a message was copied to the user's system clipboard."
},
"successTopicLinkCopied": "Topic link copied",
"@successTopicLinkCopied": {
"description": "Message when link of a topic was copied to the user's system clipboard."
},
"successChannelLinkCopied": "Channel link copied",
"@successChannelLinkCopied": {
"description": "Message when link of a channel was copied to the user's system clipboard."
Expand Down
12 changes: 12 additions & 0 deletions lib/generated/l10n/zulip_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,12 @@ abstract class ZulipLocalizations {
/// **'Mark topic as read'**
String get actionSheetOptionMarkTopicAsRead;

/// Label for copy topic link button in action sheet.
///
/// In en, this message translates to:
/// **'Copy link to topic'**
String get actionSheetOptionCopyTopicLink;

/// Error title when third-party authentication has an operational error (not necessarily caused by invalid credentials).
///
/// In en, this message translates to:
Expand Down Expand Up @@ -625,6 +631,12 @@ abstract class ZulipLocalizations {
/// **'Message link copied'**
String get successMessageLinkCopied;

/// Message when link of a topic was copied to the user's system clipboard.
///
/// In en, this message translates to:
/// **'Topic link copied'**
String get successTopicLinkCopied;

/// Message when link of a channel was copied to the user's system clipboard.
///
/// In en, this message translates to:
Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_ar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
@override
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';

@override
String get actionSheetOptionCopyTopicLink => 'Copy link to topic';

@override
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';

Expand Down Expand Up @@ -303,6 +306,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
@override
String get successMessageLinkCopied => 'Message link copied';

@override
String get successTopicLinkCopied => 'Topic link copied';

@override
String get successChannelLinkCopied => 'Channel link copied';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_de.dart
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ class ZulipLocalizationsDe extends ZulipLocalizations {
@override
String get actionSheetOptionMarkTopicAsRead => 'Thema als gelesen markieren';

@override
String get actionSheetOptionCopyTopicLink => 'Copy link to topic';

@override
String get errorWebAuthOperationalErrorTitle => 'Etwas ist schiefgelaufen';

Expand Down Expand Up @@ -316,6 +319,9 @@ class ZulipLocalizationsDe extends ZulipLocalizations {
@override
String get successMessageLinkCopied => 'Nachrichtenlink kopiert';

@override
String get successTopicLinkCopied => 'Topic link copied';

@override
String get successChannelLinkCopied => 'Channel link copied';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
@override
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';

@override
String get actionSheetOptionCopyTopicLink => 'Copy link to topic';

@override
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';

Expand Down Expand Up @@ -303,6 +306,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
@override
String get successMessageLinkCopied => 'Message link copied';

@override
String get successTopicLinkCopied => 'Topic link copied';

@override
String get successChannelLinkCopied => 'Channel link copied';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_fr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ class ZulipLocalizationsFr extends ZulipLocalizations {
@override
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';

@override
String get actionSheetOptionCopyTopicLink => 'Copy link to topic';

@override
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';

Expand Down Expand Up @@ -303,6 +306,9 @@ class ZulipLocalizationsFr extends ZulipLocalizations {
@override
String get successMessageLinkCopied => 'Message link copied';

@override
String get successTopicLinkCopied => 'Topic link copied';

@override
String get successChannelLinkCopied => 'Channel link copied';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_it.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ class ZulipLocalizationsIt extends ZulipLocalizations {
String get actionSheetOptionMarkTopicAsRead =>
'Segna l\'argomento come letto';

@override
String get actionSheetOptionCopyTopicLink => 'Copy link to topic';

@override
String get errorWebAuthOperationalErrorTitle => 'Qualcosa è andato storto';

Expand Down Expand Up @@ -313,6 +316,9 @@ class ZulipLocalizationsIt extends ZulipLocalizations {
@override
String get successMessageLinkCopied => 'Collegamento messaggio copiato';

@override
String get successTopicLinkCopied => 'Topic link copied';

@override
String get successChannelLinkCopied => 'Channel link copied';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_ja.dart
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
@override
String get actionSheetOptionMarkTopicAsRead => 'トピックを既読にする';

@override
String get actionSheetOptionCopyTopicLink => 'Copy link to topic';

@override
String get errorWebAuthOperationalErrorTitle => '問題が発生しました';

Expand Down Expand Up @@ -300,6 +303,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
@override
String get successMessageLinkCopied => 'Message link copied';

@override
String get successTopicLinkCopied => 'Topic link copied';

@override
String get successChannelLinkCopied => 'Channel link copied';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_nb.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations {
@override
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';

@override
String get actionSheetOptionCopyTopicLink => 'Copy link to topic';

@override
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';

Expand Down Expand Up @@ -303,6 +306,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations {
@override
String get successMessageLinkCopied => 'Message link copied';

@override
String get successTopicLinkCopied => 'Topic link copied';

@override
String get successChannelLinkCopied => 'Channel link copied';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_pl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
String get actionSheetOptionMarkTopicAsRead =>
'Oznacz wątek jako przeczytany';

@override
String get actionSheetOptionCopyTopicLink => 'Copy link to topic';

@override
String get errorWebAuthOperationalErrorTitle => 'Coś poszło nie tak';

Expand Down Expand Up @@ -311,6 +314,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
@override
String get successMessageLinkCopied => 'Skopiowano odnośnik wiadomości';

@override
String get successTopicLinkCopied => 'Topic link copied';

@override
String get successChannelLinkCopied => 'Channel link copied';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_ru.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
String get actionSheetOptionMarkTopicAsRead =>
'Отметить тему как прочитанную';

@override
String get actionSheetOptionCopyTopicLink => 'Copy link to topic';

@override
String get errorWebAuthOperationalErrorTitle => 'Что-то пошло не так';

Expand Down Expand Up @@ -312,6 +315,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
@override
String get successMessageLinkCopied => 'Ссылка на сообщение скопирована';

@override
String get successTopicLinkCopied => 'Topic link copied';

@override
String get successChannelLinkCopied => 'Channel link copied';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_sk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations {
@override
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';

@override
String get actionSheetOptionCopyTopicLink => 'Copy link to topic';

@override
String get errorWebAuthOperationalErrorTitle => 'Niečo sa pokazilo';

Expand Down Expand Up @@ -303,6 +306,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations {
@override
String get successMessageLinkCopied => 'Message link copied';

@override
String get successTopicLinkCopied => 'Topic link copied';

@override
String get successChannelLinkCopied => 'Channel link copied';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_sl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ class ZulipLocalizationsSl extends ZulipLocalizations {
@override
String get actionSheetOptionMarkTopicAsRead => 'Označi temo kot prebrano';

@override
String get actionSheetOptionCopyTopicLink => 'Copy link to topic';

@override
String get errorWebAuthOperationalErrorTitle => 'Nekaj je šlo narobe';

Expand Down Expand Up @@ -323,6 +326,9 @@ class ZulipLocalizationsSl extends ZulipLocalizations {
String get successMessageLinkCopied =>
'Povezava do sporočila je bila kopirana';

@override
String get successTopicLinkCopied => 'Topic link copied';

@override
String get successChannelLinkCopied => 'Channel link copied';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_uk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ class ZulipLocalizationsUk extends ZulipLocalizations {
@override
String get actionSheetOptionMarkTopicAsRead => 'Позначити тему як прочитану';

@override
String get actionSheetOptionCopyTopicLink => 'Copy link to topic';

@override
String get errorWebAuthOperationalErrorTitle => 'Щось пішло не так';

Expand Down Expand Up @@ -314,6 +317,9 @@ class ZulipLocalizationsUk extends ZulipLocalizations {
String get successMessageLinkCopied =>
'Посилання на повідомлення скопійовано';

@override
String get successTopicLinkCopied => 'Topic link copied';

@override
String get successChannelLinkCopied => 'Channel link copied';

Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations_zh.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ class ZulipLocalizationsZh extends ZulipLocalizations {
@override
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';

@override
String get actionSheetOptionCopyTopicLink => 'Copy link to topic';

@override
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';

Expand Down Expand Up @@ -303,6 +306,9 @@ class ZulipLocalizationsZh extends ZulipLocalizations {
@override
String get successMessageLinkCopied => 'Message link copied';

@override
String get successTopicLinkCopied => 'Topic link copied';

@override
String get successChannelLinkCopied => 'Channel link copied';

Expand Down
38 changes: 29 additions & 9 deletions lib/widgets/action_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -389,15 +389,9 @@ void showTopicActionSheet(BuildContext context, {
pageContext: context));
}

if (optionButtons.isEmpty) {
// TODO(a11y): This case makes a no-op gesture handler; as a consequence,
// we're presenting some UI (to people who use screen-reader software) as
// though it offers a gesture interaction that it doesn't meaningfully
// offer, which is confusing. The solution here is probably to remove this
// is-empty case by having at least one button that's always present,
// such as "copy link to topic".
return;
}
Comment on lines -392 to -400
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

optionButtons.add(CopyTopicLinkButton(
narrow: TopicNarrow(channelId, topic, with_: someMessageIdInTopic),
pageContext: context));

_showActionSheet(pageContext, optionButtons: optionButtons);
}
Expand Down Expand Up @@ -618,6 +612,32 @@ class MarkTopicAsReadButton extends ActionSheetMenuItemButton {
}
}

class CopyTopicLinkButton extends ActionSheetMenuItemButton {
const CopyTopicLinkButton({
super.key,
required this.narrow,
required super.pageContext,
});

final TopicNarrow narrow;

@override IconData get icon => ZulipIcons.link;

@override
String label(ZulipLocalizations localizations) {
return localizations.actionSheetOptionCopyTopicLink;
}

@override void onPressed() async {
final localizations = ZulipLocalizations.of(pageContext);
final store = PerAccountStoreWidget.of(pageContext);

PlatformActions.copyWithPopup(context: pageContext,
successContent: Text(localizations.successTopicLinkCopied),
data: ClipboardData(text: narrowLink(store, narrow).toString()));
}
}

/// Show a sheet of actions you can take on a message in the message list.
///
/// Must have a [MessageListPage] ancestor.
Expand Down
2 changes: 1 addition & 1 deletion lib/widgets/message_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ class MessageListAppBarTitle extends StatelessWidget {
behavior: HitTestBehavior.translucent,
onLongPress: () {
final someMessage = MessageListPage.ancestorOf(context)
.model?.messages.firstOrNull;
.model?.messages.lastOrNull;
// If someMessage is null, the topic action sheet won't have a
// resolve/unresolve button. That seems OK; in that case we're
// either still fetching messages (and the user can reopen the
Expand Down
Loading