Skip to content

Support saved snippets #1391

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

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
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
Binary file modified assets/icons/ZulipIcons.ttf
Binary file not shown.
3 changes: 3 additions & 0 deletions assets/icons/message_square_text.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/icons/plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 44 additions & 0 deletions assets/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,50 @@
"@composeBoxAttachFromCameraTooltip": {
"description": "Tooltip for compose box icon to attach an image from the camera to the message."
},
"composeBoxShowSavedSnippetsTooltip": "Show saved snippets",
"@composeBoxShowSavedSnippetsTooltip": {
"description": "Tooltip for compose box icon to show a list of saved snippets."
},
"noSavedSnippets": "No saved snippets",
"@noSavedSnippets": {
"description": "Text to show on the saved snippets bottom sheet when there are no saved snippets."
},
"newSavedSnippetButton": "New",
"@newSavedSnippetButton": {
"description": "Label for adding a new saved snippet."
},
"newSavedSnippetTitle": "New snippet",
"@newSavedSnippetTitle": {
"description": "Title for the bottom sheet to add a new saved snippet."
},
"newSavedSnippetTitleHint": "Title",
"@newSavedSnippetTitleHint": {
"description": "Hint text for the title input when adding a new saved snippet."
},
"newSavedSnippetContentHint": "Content",
"@newSavedSnippetContentHint": {
"description": "Hint text for the content input when adding a new saved snippet."
},
"errorFailedToCreateSavedSnippet": "Failed to create saved snippet",
"@errorFailedToCreateSavedSnippet": {
"description": "Error message when the saved snippet failed to be created."
},
"savedSnippetTitleValidationErrorEmpty": "Title cannot be empty.",
"@savedSnippetTitleValidationErrorEmpty": {
"description": "Validation error message when the title of the saved snippet is empty."
},
"savedSnippetTitleValidationErrorTooLong": "Title length shouldn't be greater than 60 characters.",
"@savedSnippetTitleValidationErrorTooLong": {
"description": "Validation error message when the title of the saved snippet is too long."
},
"savedSnippetContentValidationErrorEmpty": "Content cannot be empty.",
"@savedSnippetContentValidationErrorEmpty": {
"description": "Validation error message when the content of the saved snippet is empty."
},
"savedSnippetContentValidationErrorTooLong": "Content length shouldn't be greater than 10000 characters.",
"@savedSnippetContentValidationErrorTooLong": {
"description": "Validation error message when the content of the saved snippet is too long."
},
"composeBoxGenericContentHint": "Type a message",
"@composeBoxGenericContentHint": {
"description": "Hint text for content input when sending a message."
Expand Down
54 changes: 54 additions & 0 deletions lib/api/model/events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ sealed class Event {
case 'update': return RealmUserUpdateEvent.fromJson(json);
default: return UnexpectedEvent.fromJson(json);
}
case 'saved_snippets':
switch (json['op'] as String) {
case 'add': return SavedSnippetsAddEvent.fromJson(json);
case 'remove': return SavedSnippetsRemoveEvent.fromJson(json);
default: return UnexpectedEvent.fromJson(json);
}
case 'stream':
switch (json['op'] as String) {
case 'create': return ChannelCreateEvent.fromJson(json);
Expand Down Expand Up @@ -336,6 +342,54 @@ class RealmUserUpdateEvent extends RealmUserEvent {
Map<String, dynamic> toJson() => _$RealmUserUpdateEventToJson(this);
}

/// A Zulip event of type `saved_snippets`.
///
/// The corresponding API docs are in several places for
/// different values of `op`; see subclasses.
sealed class SavedSnippetsEvent extends Event {
@override
@JsonKey(includeToJson: true)
String get type => 'saved_snippets';

String get op;

SavedSnippetsEvent({required super.id});
}

/// A [SavedSnippetsEvent] with op `add`: https://zulip.com/api/get-events#saved_snippets-add
@JsonSerializable(fieldRename: FieldRename.snake)
class SavedSnippetsAddEvent extends SavedSnippetsEvent {
@override
String get op => 'add';

final SavedSnippet savedSnippet;

SavedSnippetsAddEvent({required super.id, required this.savedSnippet});

factory SavedSnippetsAddEvent.fromJson(Map<String, dynamic> json) =>
_$SavedSnippetsAddEventFromJson(json);

@override
Map<String, dynamic> toJson() => _$SavedSnippetsAddEventToJson(this);
}

/// A [SavedSnippetsEvent] with op `remove`: https://zulip.com/api/get-events#saved_snippets-remove
@JsonSerializable(fieldRename: FieldRename.snake)
class SavedSnippetsRemoveEvent extends SavedSnippetsEvent {
@override
String get op => 'remove';

final int savedSnippetId;

SavedSnippetsRemoveEvent({required super.id, required this.savedSnippetId});

factory SavedSnippetsRemoveEvent.fromJson(Map<String, dynamic> json) =>
_$SavedSnippetsRemoveEventFromJson(json);

@override
Map<String, dynamic> toJson() => _$SavedSnippetsRemoveEventToJson(this);
}

/// A Zulip event of type `stream`.
///
/// The corresponding API docs are in several places for
Expand Down
32 changes: 32 additions & 0 deletions lib/api/model/events.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions lib/api/model/initial_snapshot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class InitialSnapshot {

final List<RecentDmConversation> recentPrivateConversations;

final List<SavedSnippet>? savedSnippets; // TODO(server-10)

final List<Subscription> subscriptions;

final UnreadMessagesSnapshot unreadMsgs;
Expand Down Expand Up @@ -129,6 +131,7 @@ class InitialSnapshot {
required this.serverTypingStartedWaitPeriodMilliseconds,
required this.realmEmoji,
required this.recentPrivateConversations,
required this.savedSnippets,
required this.subscriptions,
required this.unreadMsgs,
required this.streams,
Expand Down
5 changes: 5 additions & 0 deletions lib/api/model/initial_snapshot.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions lib/api/model/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,30 @@ enum UserRole{
}
}

/// An item in `saved_snippets` from the initial snapshot.
///
/// For docs, search for "saved_snippets:"
/// in <https://zulip.com/api/register-queue>.
@JsonSerializable(fieldRename: FieldRename.snake)
class SavedSnippet {
SavedSnippet({
required this.id,
required this.title,
required this.content,
required this.dateCreated,
});

final int id;
final String title;
final String content;
final int dateCreated;

factory SavedSnippet.fromJson(Map<String, Object?> json) =>
_$SavedSnippetFromJson(json);

Map<String, dynamic> toJson() => _$SavedSnippetToJson(this);
}

/// As in `streams` in the initial snapshot.
///
/// Not called `Stream` because dart:async uses that name.
Expand Down
15 changes: 15 additions & 0 deletions lib/api/model/model.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions lib/api/route/saved_snippets.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import '../core.dart';

/// https://zulip.com/api/create-saved-snippet
Future<void> createSavedSnippet(ApiConnection connection, {
required String title,
required String content,
}) {
return connection.post('createSavedSnippet', (_) {}, 'saved_snippets', {
'title': RawParameter(title),
'content': RawParameter(content),
});
}
66 changes: 66 additions & 0 deletions lib/generated/l10n/zulip_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,72 @@ abstract class ZulipLocalizations {
/// **'Take a photo'**
String get composeBoxAttachFromCameraTooltip;

/// Tooltip for compose box icon to show a list of saved snippets.
///
/// In en, this message translates to:
/// **'Show saved snippets'**
String get composeBoxShowSavedSnippetsTooltip;

/// Text to show on the saved snippets bottom sheet when there are no saved snippets.
///
/// In en, this message translates to:
/// **'No saved snippets'**
String get noSavedSnippets;

/// Label for adding a new saved snippet.
///
/// In en, this message translates to:
/// **'New'**
String get newSavedSnippetButton;

/// Title for the bottom sheet to add a new saved snippet.
///
/// In en, this message translates to:
/// **'New snippet'**
String get newSavedSnippetTitle;

/// Hint text for the title input when adding a new saved snippet.
///
/// In en, this message translates to:
/// **'Title'**
String get newSavedSnippetTitleHint;

/// Hint text for the content input when adding a new saved snippet.
///
/// In en, this message translates to:
/// **'Content'**
String get newSavedSnippetContentHint;

/// Error message when the saved snippet failed to be created.
///
/// In en, this message translates to:
/// **'Failed to create saved snippet'**
String get errorFailedToCreateSavedSnippet;

/// Validation error message when the title of the saved snippet is empty.
///
/// In en, this message translates to:
/// **'Title cannot be empty.'**
String get savedSnippetTitleValidationErrorEmpty;

/// Validation error message when the title of the saved snippet is too long.
///
/// In en, this message translates to:
/// **'Title length shouldn\'t be greater than 60 characters.'**
String get savedSnippetTitleValidationErrorTooLong;

/// Validation error message when the content of the saved snippet is empty.
///
/// In en, this message translates to:
/// **'Content cannot be empty.'**
String get savedSnippetContentValidationErrorEmpty;

/// Validation error message when the content of the saved snippet is too long.
///
/// In en, this message translates to:
/// **'Content length shouldn\'t be greater than 10000 characters.'**
String get savedSnippetContentValidationErrorTooLong;

/// Hint text for content input when sending a message.
///
/// In en, this message translates to:
Expand Down
33 changes: 33 additions & 0 deletions lib/generated/l10n/zulip_localizations_ar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,39 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
@override
String get composeBoxAttachFromCameraTooltip => 'Take a photo';

@override
String get composeBoxShowSavedSnippetsTooltip => 'Show saved snippets';

@override
String get noSavedSnippets => 'No saved snippets';

@override
String get newSavedSnippetButton => 'New';

@override
String get newSavedSnippetTitle => 'New snippet';

@override
String get newSavedSnippetTitleHint => 'Title';

@override
String get newSavedSnippetContentHint => 'Content';

@override
String get errorFailedToCreateSavedSnippet => 'Failed to create saved snippet';

@override
String get savedSnippetTitleValidationErrorEmpty => 'Title cannot be empty.';

@override
String get savedSnippetTitleValidationErrorTooLong => 'Title length shouldn\'t be greater than 60 characters.';

@override
String get savedSnippetContentValidationErrorEmpty => 'Content cannot be empty.';

@override
String get savedSnippetContentValidationErrorTooLong => 'Content length shouldn\'t be greater than 10000 characters.';

@override
String get composeBoxGenericContentHint => 'Type a message';

Expand Down
Loading