Skip to content

Commit 7128b94

Browse files
committed
compose: Have narrowLink take an optional nearMessageId
1 parent 95c5663 commit 7128b94

File tree

2 files changed

+39
-8
lines changed

2 files changed

+39
-8
lines changed

lib/model/compose.dart

+21-4
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,22 @@ String _encodeHashComponent(String str) {
117117
}
118118

119119
/// A URL to the given [Narrow], on `store`'s realm.
120-
Uri narrowLink(PerAccountStore store, Narrow narrow) {
120+
///
121+
/// To include /near/{messageId} in the link, pass a non-null [nearMessageId].
122+
// Why take [nearMessageId] in a param, instead of looking for it in [narrow]?
123+
//
124+
// A reasonable question: after all, the "near" part of a near link (e.g., for
125+
// quote-and-reply) does take the same form as other operator/operand pairs
126+
// that we represent with [ApiNarrowElement]s, like "/stream/48-mobile".
127+
//
128+
// But unlike those other elements, we choose not to give the "near" element
129+
// an [ApiNarrowElement] representation, because it doesn't have quite that role:
130+
// it says where to look in a list of messages, but it doesn't filter the list down.
131+
// In fact, from a brief look at server code, it seems to be *ignored*
132+
// if you include it in the `narrow` param in get-messages requests.
133+
// When you want to point the server to a location in a message list, you
134+
// you do so by passing the `anchor` param.
135+
Uri narrowLink(PerAccountStore store, Narrow narrow, {int? nearMessageId}) {
121136
final apiNarrow = narrow.apiEncode();
122137
final fragment = StringBuffer('narrow');
123138
for (ApiNarrowElement element in apiNarrow) {
@@ -153,8 +168,10 @@ Uri narrowLink(PerAccountStore store, Narrow narrow) {
153168
fragment.write(element.operand.toString());
154169
}
155170
}
171+
172+
if (nearMessageId != null) {
173+
fragment.write('/near/$nearMessageId');
174+
}
175+
156176
return store.account.realmUrl.replace(fragment: fragment.toString());
157177
}
158-
159-
// TODO more, like /near links to messages in conversations
160-
// (also to be used in quote-and-reply)

test/model/compose_test.dart

+18-4
Original file line numberDiff line numberDiff line change
@@ -223,22 +223,27 @@ hello
223223
group('narrowLink', () {
224224
test('AllMessagesNarrow', () {
225225
final store = eg.store();
226-
check(narrowLink(store, const AllMessagesNarrow())).equals(store.account.realmUrl.resolve('#narrow'));
226+
check(narrowLink(store, const AllMessagesNarrow()))
227+
.equals(store.account.realmUrl.resolve('#narrow'));
228+
check(narrowLink(store, const AllMessagesNarrow(), nearMessageId: 1))
229+
.equals(store.account.realmUrl.resolve('#narrow/near/1'));
227230
});
228231

229232
test('StreamNarrow / TopicNarrow', () {
230233
void checkNarrow(String expectedFragment, {
231234
required int streamId,
232235
required String name,
233236
String? topic,
237+
int? nearMessageId,
234238
}) {
235239
assert(expectedFragment.startsWith('#'), 'wrong-looking expectedFragment');
236240
final store = eg.store();
237241
store.addStream(eg.stream(streamId: streamId, name: name));
238242
final narrow = topic == null
239243
? StreamNarrow(streamId)
240244
: TopicNarrow(streamId, topic);
241-
check(narrowLink(store, narrow)).equals(store.account.realmUrl.resolve(expectedFragment));
245+
check(narrowLink(store, narrow, nearMessageId: nearMessageId))
246+
.equals(store.account.realmUrl.resolve(expectedFragment));
242247
}
243248

244249
checkNarrow(streamId: 1, name: 'announce', '#narrow/stream/1-announce');
@@ -247,26 +252,32 @@ hello
247252
checkNarrow(streamId: 415, name: 'chat.zulip.org', '#narrow/stream/415-chat.2Ezulip.2Eorg');
248253
checkNarrow(streamId: 419, name: 'français', '#narrow/stream/419-fran.C3.A7ais');
249254
checkNarrow(streamId: 403, name: 'Hshs[™~}(.', '#narrow/stream/403-Hshs.5B.E2.84.A2~.7D.28.2E');
255+
checkNarrow(streamId: 60, name: 'twitter', nearMessageId: 1570686, '#narrow/stream/60-twitter/near/1570686');
250256

251257
checkNarrow(streamId: 48, name: 'mobile', topic: 'Welcome screen UI',
252258
'#narrow/stream/48-mobile/topic/Welcome.20screen.20UI');
253259
checkNarrow(streamId: 243, name: 'mobile-team', topic: 'Podfile.lock clash #F92',
254260
'#narrow/stream/243-mobile-team/topic/Podfile.2Elock.20clash.20.23F92');
255261
checkNarrow(streamId: 377, name: 'translation/zh_tw', topic: '翻譯 "stream"',
256262
'#narrow/stream/377-translation.2Fzh_tw/topic/.E7.BF.BB.E8.AD.AF.20.22stream.22');
263+
checkNarrow(streamId: 42, name: 'Outreachy 2016-2017', topic: '2017-18 Stream?', nearMessageId: 302690,
264+
'#narrow/stream/42-Outreachy-2016-2017/topic/2017-18.20Stream.3F/near/302690');
257265
});
258266

259267
test('DmNarrow', () {
260268
void checkNarrow(String expectedFragment, String legacyExpectedFragment, {
261269
required List<int> allRecipientIds,
262270
required int selfUserId,
271+
int? nearMessageId,
263272
}) {
264273
assert(expectedFragment.startsWith('#'), 'wrong-looking expectedFragment');
265274
final store = eg.store();
266275
final narrow = DmNarrow(allRecipientIds: allRecipientIds, selfUserId: selfUserId);
267-
check(narrowLink(store, narrow)).equals(store.account.realmUrl.resolve(expectedFragment));
276+
check(narrowLink(store, narrow, nearMessageId: nearMessageId))
277+
.equals(store.account.realmUrl.resolve(expectedFragment));
268278
store.connection.zulipFeatureLevel = 176;
269-
check(narrowLink(store, narrow)).equals(store.account.realmUrl.resolve(legacyExpectedFragment));
279+
check(narrowLink(store, narrow, nearMessageId: nearMessageId))
280+
.equals(store.account.realmUrl.resolve(legacyExpectedFragment));
270281
}
271282

272283
checkNarrow(allRecipientIds: [1], selfUserId: 1,
@@ -281,6 +292,9 @@ hello
281292
checkNarrow(allRecipientIds: [1, 2, 3, 4], selfUserId: 4,
282293
'#narrow/dm/1,2,3,4-group',
283294
'#narrow/pm-with/1,2,3,4-group');
295+
checkNarrow(allRecipientIds: [1, 2], selfUserId: 1, nearMessageId: 12345,
296+
'#narrow/dm/1,2-dm/near/12345',
297+
'#narrow/pm-with/1,2-pm/near/12345');
284298
});
285299

286300
// TODO other Narrow subclasses as we add them:

0 commit comments

Comments
 (0)