Skip to content
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
20 changes: 20 additions & 0 deletions app/Devnull/Data/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,26 @@ public function snippet(): string
return self::snippetOf($this->body, count($this->images), $this->voice !== null);
}

/**
* The links in its body, in the order it draws them, which its rows open by their place among them.
*
* @return list<BodyLink>
*/
public function links(): array
{
$links = [];

foreach ($this->content as $block) {
foreach ($block->runs as $run) {
if ($run->link !== null) {
$links[] = $run->link;
}
}
}

return $links;
}

private static function snippetOf(string $body, int $images, bool $hasVoice = false): string
{
if ($hasVoice) {
Expand Down
17 changes: 17 additions & 0 deletions app/NativeComponents/Conversation.php
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,23 @@ public function openLink(int $id): void
}
}

/**
* Opens a link in a message's body, by its place among the body's links: a
* commit or another page on dev/null the app has a screen for here in the
* app, anything else in the browser.
*/
public function openMessageLink(int $id, int $index): void
{
$link = $this->findMessage($id)?->links()[$index] ?? null;
$screen = $link?->commit !== null ? '/commits/'.$link->commit : SiteLink::screen($link?->href);

if ($screen !== null) {
$this->openInTab($screen);
} elseif (SiteLink::isWebUrl($link?->href)) {
Browser::inApp((string) $link?->href);
}
}

/**
* Pins the message at once, first in the list as the latest pinned, as the web does.
*/
Expand Down
26 changes: 19 additions & 7 deletions resources/views/native/partials/message-body.blade.php
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
{{--
A message's body, drawn from its segments as a commit's is (see partials/commit-body): prose with tappable
mentions and inline code, hex colors beside their swatch, quotes behind a rule on the left, and fenced code as
mono text in a box. $ink is the bubble's text colour, so a body on your lane reads in that lane's label colour,
and $quoteRule the quote's rule: amber, or the label colour on your lane, as the web's pale rule in your own
bubbles.
mentions, links and inline code, hex colors beside their swatch, quotes behind a rule on the left, and fenced
code as mono text in a box. $ink is the bubble's text colour, so a body on your lane reads in that lane's label
colour, and $quoteRule the quote's rule: amber, or the label colour on your lane, as the web's pale rule in your
own bubbles.
A link is in $linkInk, Commit Blue off your lane and the lane's label colour on it, with a named link's owner
muted in $linkPrefixInk, and opens by its place among the body's links (see Conversation::openMessageLink).
$maxLines caps the body to the first block at that many lines, for the pinned sheet's preview, so its links work
before Show more.
--}}
@foreach ($message->content as $block)
@php($linkInk ??= $mentionInk)
@php($linkPrefixInk ??= 'text-theme-ink-muted')
@php($maxLines ??= null)
@php($linkIndex = 0)
@foreach ($maxLines === null ? $message->content : array_slice($message->content, 0, 1) as $block)
@if ($block->isCode())
<native:column class="w-full mt-1 mb-1 rounded-md border border-theme-hairline bg-theme-wash">
<native:text :text="$block->code" font="mono" class="w-full px-3 py-2 text-[13px] leading-snug text-theme-ink whitespace-pre-wrap select-text" />
<native:text :text="$block->code" :max-lines="$maxLines ?? 0" font="mono" class="w-full px-3 py-2 text-[13px] leading-snug text-theme-ink whitespace-pre-wrap select-text" />
</native:column>
@else
@if ($block->isQuote())
<native:column class="my-1 pl-3 border-l-2 {{ $quoteRule }}">
@endif
<native:text class="text-[15px] leading-snug {{ $ink }} whitespace-pre-wrap">@foreach ($block->runs as $run)
<native:text :max-lines="$maxLines ?? 0" class="text-[15px] leading-snug {{ $ink }} whitespace-pre-wrap">@foreach ($block->runs as $run)
@if ($run->isMention())
<native:text :text="$run->text" font="semibold" class="font-semibold {{ $mentionInk }}" @press="{{ $run->isOrganization ? 'openOrganization' : 'openProfile' }}('{{ $run->username }}')" />
@elseif ($run->isCode)
<native:text :text="$run->text" font="mono" class="text-[13px]" />
@elseif ($run->link?->isNamed())
<native:text font="mono" class="text-[13px] {{ $linkInk }}" @press="openMessageLink({{ $message->id }}, {{ $linkIndex++ }})"><native:text :text="$run->link->prefix" class="{{ $linkPrefixInk }}" />&#x2060;<native:text :text="$run->link->shown" /></native:text>
@elseif ($run->link)
<native:text :text="$run->link->shown" class="{{ $linkInk }}" @press="openMessageLink({{ $message->id }}, {{ $linkIndex++ }})" />
@elseif ($run->isColor())
<native:text><native:text text="■" class="text-[#{{ $run->color }}] dark:text-[#{{ $run->color }}]" />&#x202F;<native:text :text="$run->text" /></native:text>
@else
Expand Down
4 changes: 2 additions & 2 deletions resources/views/native/partials/message-item.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@
@if ($message->isPending())
<native:pressable ref="bubble-{{ $key }}" class="flex-shrink" :context-menu="$menu">
<native:column class="px-3 py-2 rounded-2xl {{ $message->status === 'rejected' ? 'border border-theme-destructive bg-theme-surface' : 'bg-theme-'.$myColour.'/60' }}">
@include('native.partials.message-body', ['ink' => $message->status === 'rejected' ? 'text-theme-ink' : 'text-theme-on-'.$myColour, 'mentionInk' => 'text-theme-on-'.$myColour, 'quoteRule' => $message->status === 'rejected' ? 'border-theme-lane-5' : 'border-theme-on-'.$myColour])
@include('native.partials.message-body', ['ink' => $message->status === 'rejected' ? 'text-theme-ink' : 'text-theme-on-'.$myColour, 'mentionInk' => 'text-theme-on-'.$myColour, 'quoteRule' => $message->status === 'rejected' ? 'border-theme-lane-5' : 'border-theme-on-'.$myColour, 'linkPrefixInk' => $message->status === 'rejected' ? 'text-theme-ink-muted' : 'text-theme-on-'.$myColour.'/70'])
</native:column>
</native:pressable>
@else
<native:pressable ref="bubble-{{ $key }}" class="flex-shrink" @press="hideActions" @doubleTap="heart({{ $message->id }})" :context-menu="$menu" a11y-hint="{{ $replyable ? 'Swipe sideways for React and Reply' : '' }}">
<native:column class="px-3 py-2 rounded-2xl {{ $mine ? 'bg-theme-'.$myColour : 'bg-theme-wash' }}">
@include('native.partials.message-body', ['ink' => $mine ? 'text-theme-on-'.$myColour : 'text-theme-ink', 'mentionInk' => $mine ? 'text-theme-on-'.$myColour : 'text-theme-primary', 'quoteRule' => $mine ? 'border-theme-on-'.$myColour : 'border-theme-lane-5'])
@include('native.partials.message-body', ['ink' => $mine ? 'text-theme-on-'.$myColour : 'text-theme-ink', 'mentionInk' => $mine ? 'text-theme-on-'.$myColour : 'text-theme-primary', 'quoteRule' => $mine ? 'border-theme-on-'.$myColour : 'border-theme-lane-5', 'linkPrefixInk' => $mine ? 'text-theme-on-'.$myColour.'/70' : 'text-theme-ink-muted'])
</native:column>
</native:pressable>
@endif
Expand Down
4 changes: 3 additions & 1 deletion resources/views/native/partials/pinned-message.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@

@if ($message->body !== '')
@if ($isLong && ! $isExpanded)
<native:text :text="$message->body" class="w-full text-[15px] leading-snug text-theme-ink whitespace-pre-wrap" :max-lines="\App\NativeComponents\Conversation::PinnedLines" />
<native:column class="w-full">
@include('native.partials.message-body', ['ink' => 'text-theme-ink', 'mentionInk' => 'text-theme-primary', 'quoteRule' => 'border-theme-lane-5', 'maxLines' => \App\NativeComponents\Conversation::PinnedLines])
</native:column>
@else
<native:column class="w-full">
@include('native.partials.message-body', ['ink' => 'text-theme-ink', 'mentionInk' => 'text-theme-primary', 'quoteRule' => 'border-theme-lane-5'])
Expand Down
51 changes: 51 additions & 0 deletions tests/Feature/Screens/ConversationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,32 @@ function pulledAvatars(TestableComponent $screen, int $id): array
->assertNativeCalledTimes('Browser.OpenInApp', 1);
});

it('opens a link in a message body, in the app for a page on dev/null and in the browser for any other', function () {
$commit = '0199e5c4-0000-7000-8000-0000000000c1';

serveRemoteThread(threadJson([
remoteMessageJson(['id' => 1, 'body' => "read https://nativephp.com/docs or https://dev-null.sh/@grace then https://dev-null.sh/@ada/commits/{$commit}"]),
], ['read_through_id' => 1]));

$screen = finishRefreshes(Native::visit('/remotes/conversations/ada'));

expect(paragraphRuns($screen, 'read'))->toBe([
['read '],
['nativephp.com/docs', 'link'],
[' or '],
['dev-null.sh/@grace', 'link'],
[' then '],
['ada/00000c1', 'link'],
]);

$screen->press('openMessageLink(1, 0)')
->assertNativeCalled('Browser.OpenInApp', fn (array $params): bool => $params['url'] === 'https://nativephp.com/docs')
->assertNoNavigation();

finishRefreshes(Native::visit('/remotes/conversations/ada'))->press('openMessageLink(1, 1)')->assertNavigatedTo('/remotes/users/grace');
finishRefreshes(Native::visit('/remotes/conversations/ada'))->press('openMessageLink(1, 2)')->assertNavigatedTo('/remotes/commits/'.$commit);
});

it("opens a mentioned organization's page, and a mentioned user's profile, in the tab", function () {
serveRemoteThread(threadJson([
remoteMessageJson(['id' => 1, 'body' => 'ask @laravel or @grace', 'mentions' => [
Expand Down Expand Up @@ -1148,6 +1174,31 @@ function serveOrgThread(array $thread, ?array $poll = null, ?Closure $pins = nul
expect(findNode(nodeByRef($screen, 'pin-1'), fn (array $node): bool => ($node['props']['max_lines'] ?? null) === 3))->toBeNull();
});

it('opens a link in a pinned message, in the sheet and in its three-line preview', function () {
$commit = '0199e5c4-0000-7000-8000-0000000000c1';
$long = "release checklist: https://nativephp.com/docs\n1. tag 2.4\n2. post the changelog\n3. ping the mobile folks";

serveOrgThread(orgThreadJson([], ['pinned' => [
orgMessageJson(['id' => 1, 'body' => $long]),
orgMessageJson(['id' => 2, 'body' => 'fixed in https://dev-null.sh/@ada/commits/'.$commit]),
]]));

$screen = finishRefreshes(Native::visit('/remotes/orgs/org-1'))->tap('pinned-bar');

$preview = findNode(nodeByRef($screen, 'pin-1'), fn (array $node): bool => ($node['props']['max_lines'] ?? null) === 3);
$link = $preview === null ? null : findNode($preview, fn (array $node): bool => ($node['props']['text'] ?? null) === 'nativephp.com/docs');

expect($preview)->not->toBeNull()
->and($link['on_press'] ?? null)->not->toBeNull();

$screen->press('openMessageLink(1, 0)')
->assertNativeCalled('Browser.OpenInApp', fn (array $params): bool => $params['url'] === 'https://nativephp.com/docs');

finishRefreshes(Native::visit('/remotes/orgs/org-1'))->tap('pinned-bar')
->press('openMessageLink(2, 0)')
->assertNavigatedTo('/remotes/commits/'.$commit);
});

it('opens Reply, React and Copy text on a long press on a message, yours or theirs, and keeps its tap and double tap', function () {
serveRemoteThread(threadJson([
remoteMessageJson(['id' => 1, 'body' => 'did you see the PR?']),
Expand Down
Loading