Skip to content
Open
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
13 changes: 6 additions & 7 deletions app/Devnull/CommitMenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@

/**
* What More holds, on the right of a commit's action row (see
* partials/commit-actions): Share link on every commit; Pin to profile or
* Unpin from profile on your own published commit, not a reply; then, after a
* divider, Report commit on someone else's published commit, or Delete commit
* partials/commit-actions): Pin to profile or Unpin from profile on your own
* published commit, not a reply; then, after a divider, Report commit on someone else's published commit, or Delete commit
* on your own. Each item names the commit, so a tap never rebuilds anything
* from an attribute (see ShowsCommits).
*/
Expand All @@ -26,17 +25,17 @@ public static function items(Commit $commit, ?User $viewer): array
$isOwn = $viewer !== null && $commit->author->id === $viewer->id;
$items = [];

$items[] = self::item($commit, 'share', 'Share link', Lucide::Link, 'shareCommit');

if ($isOwn && $commit->isApproved() && $commit->parentId === null) {
$items[] = self::item($commit, 'pin', $commit->isPinned ? 'Unpin from profile' : 'Pin to profile', Lucide::Pin, 'togglePin');
}

if ($viewer !== null && ! $isOwn && $commit->isApproved()) {
$items[] = NavAction::divider();
$items[] = self::item($commit, 'report', 'Report commit', Lucide::Flag, 'reportCommit');
} elseif ($isOwn) {
$items[] = NavAction::divider();
if ($items !== []) {
$items[] = NavAction::divider();
}

$items[] = self::item($commit, 'delete', 'Delete commit', Lucide::Trash2, 'confirmDelete', destructive: true);
}

Expand Down
2 changes: 2 additions & 0 deletions app/Icons/Lucide.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ enum Lucide
case Settings2;
case MessageSquareReply;
case Link;
case Share;
case Star;
case Bookmark;
case Trash2;
Expand Down Expand Up @@ -136,6 +137,7 @@ public function custom(bool $filled = false): ?string
self::Settings2 => 'settings-2',
self::MessageSquareReply => 'message-square-reply',
self::Link => 'link',
self::Share => 'share',
self::Star => $filled ? 'star-filled' : 'star',
self::Bookmark => $filled ? 'bookmark-filled' : 'bookmark',
self::Trash2 => 'trash-2',
Expand Down
2 changes: 1 addition & 1 deletion app/NativeComponents/Layouts/TabsLayout.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public function navBar(NativeComponent $screen): ?NavBar
->displayMode('inline');

if ($screen instanceof Log) {
$navBar->titleView(Image::make(self::logo())->height(28)->alt('devnull'));
$navBar->titleView(Image::make(self::logo())->width(105)->height(28)->fit(1)->alt('devnull'));
}

return $navBar;
Expand Down
1 change: 1 addition & 0 deletions resources/icons/share.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 22 additions & 17 deletions resources/views/native/partials/commit-actions.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
and its count, Quote, Reply, and in Commit Blue "› N replies" when the row has no thread under it already. On the
right: More.

Share link, Report and Delete live in More, which opens the platform's own menu (a UIMenu on iOS, a dropdown
on Android, see CommitMenu for what's in it). Tapping More only opens the menu.
Share is its own button at the end of the row. Pin, Report and Delete live in More, which opens the platform's own
menu (a UIMenu on iOS, a dropdown on Android, see CommitMenu for what's in it), and is left off when there's
nothing in it. Tapping More only opens the menu.

Reply opens the commit with its reply composer focused. In a feed ($expandable) the replies link opens the thread in
place, as on the web; elsewhere it opens the commit. Like the web, someone else's commit that isn't approved gets no
Expand Down Expand Up @@ -54,10 +55,11 @@
@php($canGivePastel = $pasteis !== null && $viewer !== null && ! $isOwn && $commit->isApproved())
@php($pastelLabel = \App\Devnull\Display::count($commit->pasteisCount, 'pastel', 'pastéis'))
@php($pastelSize = $compact ? 18 : 20)
@php($menu = \App\Devnull\CommitMenu::items($commit, $viewer))
@php($starLabel = ($commit->starred ? 'Unstar commit' : 'Star commit').($commit->starsCount > 0 ? ', '.\App\Devnull\Display::count($commit->starsCount, 'star', 'stars') : ''))
<native:row ref="actions-{{ $commit->id }}" class="w-full -ml-2 -my-[6] items-center">
@if ($canReact && $viewer !== null && ! $commit->isDeleted())
<native:pressable ref="star-{{ $commit->id }}" @press="toggleStar('{{ $commit->id }}')" class="flex-row min-w-[44] h-[36] px-2 gap-1 items-center" a11y-label="{{ $starLabel }}">
<native:pressable ref="star-{{ $commit->id }}" @press="toggleStar('{{ $commit->id }}')" class="flex-row flex-1 min-w-[44] h-[36] px-2 gap-1 items-center" a11y-label="{{ $starLabel }}">
<native:icon :ios="\App\Icons\Lucide::Star->ios($commit->starred)" :android="\App\Icons\Lucide::Star->android($commit->starred)" :size="$iconSize" class="{{ $commit->starred ? 'text-theme-primary' : 'text-theme-ink-muted' }}" />
@if ($commit->starsCount > 0)
<native:text font="mono" class="{{ $labelSize }} {{ $commit->starred ? 'text-theme-primary' : 'text-theme-ink-muted' }}">{{ \App\Devnull\Display::compactCount($commit->starsCount) }}</native:text>
Expand All @@ -66,19 +68,19 @@
@endif

@if ($canGivePastel)
<native:pressable ref="pastel-{{ $commit->id }}" @press="givePastel('{{ $commit->id }}')" class="flex-row min-w-[44] h-[36] px-2 gap-1 items-center" a11y-label="Give a pastel, {{ $pastelLabel }}, {{ $pasteis->left() }} left today">
<native:pressable ref="pastel-{{ $commit->id }}" @press="givePastel('{{ $commit->id }}')" class="flex-row flex-1 min-w-[44] h-[36] px-2 gap-1 items-center" a11y-label="Give a pastel, {{ $pastelLabel }}, {{ $pasteis->left() }} left today">
<native:image :src="\App\Devnull\Data\Pasteis::Image" :width="$pastelSize" :height="$pastelSize" :fit="1" />
<native:text font="mono" class="{{ $labelSize }} text-theme-ink-muted">{{ \App\Devnull\Display::compactCount($commit->pasteisCount) }}</native:text>
</native:pressable>
@elseif ($pasteis !== null)
<native:row ref="pastel-count-{{ $commit->id }}" class="min-w-[44] h-[36] px-2 gap-1 items-center" a11y-label="{{ $pastelLabel }}">
<native:row ref="pastel-count-{{ $commit->id }}" class="flex-1 min-w-[44] h-[36] px-2 gap-1 items-center" a11y-label="{{ $pastelLabel }}">
<native:image :src="\App\Devnull\Data\Pasteis::Image" :width="$pastelSize" :height="$pastelSize" :fit="1" />
<native:text font="mono" class="{{ $labelSize }} text-theme-ink-muted">{{ \App\Devnull\Display::compactCount($commit->pasteisCount) }}</native:text>
</native:row>
@endif

@if ($withQuote && $canReact)
<native:pressable ref="quote-{{ $commit->id }}" @press="quoteCommit('{{ $commit->id }}')" class="flex-row min-w-[44] h-[36] px-2 gap-[3] items-center justify-center" a11y-label="{{ $hasQuoteStash ? 'Quote, you have an unsent quote here' : 'Quote' }}">
<native:pressable ref="quote-{{ $commit->id }}" @press="quoteCommit('{{ $commit->id }}')" class="flex-row flex-1 min-w-[44] h-[36] px-2 gap-[3] items-center" a11y-label="{{ $hasQuoteStash ? 'Quote, you have an unsent quote here' : 'Quote' }}">
<native:icon :ios="\App\Icons\Lucide::Quote->ios()" :android="\App\Icons\Lucide::Quote->android()" :size="$iconSize" class="text-theme-ink-muted" />
@if ($hasQuoteStash)
<native:column ref="quote-stash-{{ $commit->id }}" class="w-[5] h-[5] rounded-full bg-theme-ink-muted" />
Expand All @@ -87,19 +89,16 @@
@endif

@if ($withReply)
<native:pressable ref="reply-to-{{ $commit->id }}" @press="replyTo('{{ $commit->id }}')" class="flex-row min-w-[44] h-[36] px-2 gap-[6] items-center {{ $showsReplies ? 'justify-center' : '' }}" a11y-label="{{ $hasReplyStash ? 'Reply, you have an unsent reply here' : 'Reply' }}">
<native:pressable ref="reply-to-{{ $commit->id }}" @press="replyTo('{{ $commit->id }}')" class="flex-row flex-1 min-w-[44] h-[36] px-2 gap-[3] items-center" a11y-label="{{ $hasReplyStash ? 'Reply, you have an unsent reply here' : 'Reply' }}">
<native:icon :ios="\App\Icons\Lucide::MessageSquareReply->ios()" :android="\App\Icons\Lucide::MessageSquareReply->android()" :size="$iconSize" class="text-theme-ink-muted" />
@unless ($showsReplies)
<native:text class="{{ $labelSize }} text-theme-ink-muted">Reply</native:text>
@endunless
@if ($hasReplyStash)
<native:column ref="reply-stash-{{ $commit->id }}" class="w-[5] h-[5] -ml-[3] rounded-full bg-theme-ink-muted" />
@endif
</native:pressable>
@endif

@if ($showsReplies)
<native:pressable ref="replies-{{ $commit->id }}" @press="{{ $repliesPress }}" class="flex-row h-[36] px-2 gap-1 items-center" a11y-label="{{ $isOpen ? 'Hide replies' : \App\Devnull\Display::count($repliesCount, 'reply', 'replies') }}">
<native:pressable ref="replies-{{ $commit->id }}" @press="{{ $repliesPress }}" class="flex-row flex-1 h-[36] px-2 gap-1 items-center" a11y-label="{{ $isOpen ? 'Hide replies' : \App\Devnull\Display::count($repliesCount, 'reply', 'replies') }}">
@if ($isOpen)
<native:icon :ios="\App\Icons\Lucide::ChevronDown->ios()" :android="\App\Icons\Lucide::ChevronDown->android()" :size="14" class="text-theme-primary" />
<native:text class="{{ $labelSize }} text-theme-primary">Hide replies</native:text>
Expand All @@ -110,11 +109,17 @@
</native:pressable>
@endif

<native:spacer />
@unless ($commit->isDeleted())
<native:pressable ref="share-{{ $commit->id }}" @press="shareCommit('{{ $commit->id }}')" class="flex-row flex-1 min-w-[44] h-[36] px-2 items-center" a11y-label="Share link">
<native:icon :ios="\App\Icons\Lucide::Share->ios()" :android="\App\Icons\Lucide::Share->android()" :size="$iconSize" class="text-theme-ink-muted" />
</native:pressable>
@endunless

<native:pressable ref="more-{{ $commit->id }}" :menu="\App\Devnull\CommitMenu::items($commit, $viewer)" a11y-label="More">
<native:column class="min-w-[44] h-[36] items-center justify-center">
<native:icon :ios="\App\Icons\Lucide::Ellipsis->ios()" :android="\App\Icons\Lucide::Ellipsis->android()" :size="$iconSize" class="text-theme-ink-muted" />
</native:column>
</native:pressable>
@if ($menu !== [])
<native:pressable ref="more-{{ $commit->id }}" :menu="$menu" a11y-label="More">
<native:column class="min-w-[44] h-[36] items-center justify-center">
<native:icon :ios="\App\Icons\Lucide::Ellipsis->ios()" :android="\App\Icons\Lucide::Ellipsis->android()" :size="$iconSize" class="text-theme-ink-muted" />
</native:column>
</native:pressable>
@endif
</native:row>
4 changes: 2 additions & 2 deletions resources/views/native/partials/commit-row-content.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
smaller and in muted ink, with a smaller avatar and action row. They still open their own pages.

A list builds and measures every node of a row as it scrolls in, so the content is one column: the rail's 64pt
gutter is its left margin, and the hairline under it is its bottom border. A border draws inside the box, so the
gutter is its left padding, so the hairline under it, its bottom border, runs the row's full width. A border draws inside the box, so the
bottom padding takes the hairline's 1pt to keep the row as tall as it was.
--}}
@php($author = $commit->author)
Expand All @@ -29,7 +29,7 @@
@php($inset = $focused ? 16 : 12)
@php($padding = 'pt-['.$inset.']'.($threadBelow ? '' : ' pb-['.($hasHairline ? $inset + 1 : $inset).']'))
@php($metaSize = $muted ? 'text-xs' : 'text-[13px]')
<native:column class="flex-1 ml-[64] {{ $muted ? 'min-h-[56]' : 'min-h-[64]' }} {{ $padding }} pr-4 gap-1 {{ $hasHairline ? 'border-b border-theme-hairline' : '' }}">
<native:column class="flex-1 pl-[64] {{ $muted ? 'min-h-[56]' : 'min-h-[64]' }} {{ $padding }} pr-4 gap-1 {{ $hasHairline ? 'border-b border-theme-hairline' : '' }}">
<native:row class="w-full gap-[6] items-center">
<native:text font="semibold" class="{{ $focused ? 'text-base' : ($muted ? 'text-sm' : 'text-[15px]') }} {{ $muted ? 'text-theme-ink-muted' : 'text-theme-ink' }} flex-shrink" :max-lines="1">{{ $author->name }}</native:text>
<native:text font="mono" class="{{ $metaSize }} text-theme-ink-muted flex-shrink" :max-lines="1">{{ $author->handle() }}</native:text>
Expand Down
10 changes: 2 additions & 8 deletions resources/views/native/partials/thread-reply.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
@php($avatarLine = 76 + $indent)
@if ($reply->isDeleted())
@php($opened = $tombstones && array_key_exists($reply->id, $tombstoneReplies ?? []))
<native:row native:key="thread-reply-{{ $reply->id }}" ref="thread-reply-{{ $reply->id }}" class="w-full bg-theme-surface">
<native:row native:key="thread-reply-{{ $reply->id }}" ref="thread-reply-{{ $reply->id }}" class="w-full bg-theme-surface {{ $last && ! $muted ? 'border-b border-theme-hairline pb-[1]' : '' }}">
<native:column :width="64" />
<native:column class="flex-1">
<native:row class="w-full pr-4 pt-[10] gap-2 {{ $last && $more === null ? 'pb-3' : 'pb-1' }} {{ $indent > 0 ? 'pl-['.$indent.']' : '' }}">
Expand All @@ -50,9 +50,6 @@
@if ($more !== null)
@include('native.partials.thread-more', ['commit' => $thread ?? $reply])
@endif
@if ($last && ! $muted)
<native:column ref="thread-end-{{ $reply->id }}" class="w-full h-[1] bg-theme-hairline" />
@endif
</native:column>
@include('native.partials.thread-links')
@if ($opened && ($tombstoneReplies[$reply->id] ?? []) !== [])
Expand All @@ -62,7 +59,7 @@
</native:row>
@else
@php($commit = $reply)
<native:pressable native:key="thread-reply-{{ $reply->id }}" ref="thread-reply-{{ $reply->id }}" @press="openCommit('{{ $reply->id }}')" class="w-full bg-theme-surface">
<native:pressable native:key="thread-reply-{{ $reply->id }}" ref="thread-reply-{{ $reply->id }}" @press="openCommit('{{ $reply->id }}')" class="w-full bg-theme-surface {{ $last && ! $muted ? 'border-b border-theme-hairline pb-[1]' : '' }}">
<native:row class="w-full">
<native:column :width="64" />
<native:column class="flex-1">
Expand All @@ -87,9 +84,6 @@
@if ($more !== null)
@include('native.partials.thread-more', ['commit' => $thread ?? $reply])
@endif
@if ($last && ! $muted)
<native:column ref="thread-end-{{ $reply->id }}" class="w-full h-[1] bg-theme-hairline" />
@endif
</native:column>
@include('native.partials.thread-links')
</native:row>
Expand Down
7 changes: 5 additions & 2 deletions tests/Feature/Screens/CommitCardTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,13 @@ function logOf(array $commits): TestableComponent
? array_values(array_filter($node['children'], fn (array $child): bool => $child['type'] !== 'top_bar_action'))[0]['layout']
: $node['layout'];

foreach (['star-c1', 'quote-c1', 'more-c1'] as $ref) {
// Star, Quote and Reply share one target size and centre what they hold, so the row reads evenly.
foreach (['star-c1', 'quote-c1', 'reply-to-c1'] as $ref) {
expect($target($ref))->toMatchArray(['min_width' => 44.0, 'height' => 36.0]);
}

expect($target('more-c1'))->toMatchArray(['min_width' => 44.0, 'height' => 36.0]);

expect($target('reply-to-c1')['height'])->toBe(36.0)
->and($target('replies-c1')['height'])->toBe(36.0)
// 6pt more above and below than the 24pt the row used to be, so it's pulled in by that much.
Expand All @@ -119,7 +122,7 @@ function logOf(array $commits): TestableComponent
// One column beside the rail, its gutter a margin and the hairline under it a border, and buttons that are their
// own rows. Wrapped the old way, this row took 47 views in eight levels.
expect($row['layout']['flex_direction'])->toBe(1)
->and($content['layout']['margin'])->toBe([0.0, 0.0, 0.0, 64.0])
->and($content['layout']['padding'][3])->toBe(64.0)
->and($content['props']['border_b'])->toBe(1.0)
->and(array_column(nodeByRef($screen, 'star-c1')['children'], 'type'))->toBe(['icon', 'text'])
->and(count(findNodes($row, fn (): bool => true)))->toBeLessThanOrEqual(35);
Expand Down
14 changes: 5 additions & 9 deletions tests/Feature/Screens/CommitMenuTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,22 @@
expect($more['props']['has_menu'] ?? null)->toBeTrue()
->and($more['on_press'] ?? $more['props']['on_press'] ?? null)->toBeNull();

// Someone else's published commit: share it or report it.
// Someone else's published commit: report it. Share is a button of its own in the row.
expect(menuItems($screen, 'more-c4'))->toBe([
['share-c4', 'Share link', 'custom:link'],
'divider',
['report-c4', 'Report commit', 'custom:flag'],
])
// Theirs, waiting for approval: only its link, as the web leaves the rest off.
->and(menuItems($screen, 'more-c3'))->toBe([['share-c3', 'Share link', 'custom:link']]);
// Theirs, waiting for approval: nothing to put in More, so it isn't drawn.
->and(findNode($screen->tree(), fn (array $node): bool => ($node['ref'] ?? null) === 'more-c3'))->toBeNull()
->and(nodeByRef($screen, 'share-c3')['type'])->toBe('pressable');

// Your own published one: share it, pin it to your profile, or delete it.
// Your own published one: pin it to your profile, or delete it.
expect(menuItems($screen, 'more-c2'))->toBe([
['share-c2', 'Share link', 'custom:link'],
['pin-c2', 'Pin to profile', $platform === 'ios' ? 'pin' : 'push_pin'],
'divider',
['delete-c2', 'Delete commit', 'custom:trash-2'],
])
// Yours, waiting for approval: the same, but it can't be pinned yet.
->and(menuItems($screen, 'more-c1'))->toBe([
['share-c1', 'Share link', 'custom:link'],
'divider',
['delete-c1', 'Delete commit', 'custom:trash-2'],
]);

Expand Down
11 changes: 5 additions & 6 deletions tests/Feature/Screens/CommitSheetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,12 @@ function reportFormOnLog(array $reportAnswer = [null, 204]): TestableComponent

$screen = Native::visit('/log', platform: 'ios');

// Someone else's published commit: after a divider, the web's Report.
expect(array_slice(menuItems($screen, 'more-c3'), -2))->toBe(['divider', ['report-c3', 'Report commit', 'custom:flag']]);
// Someone else's published commit: the web's Report.
expect(menuItems($screen, 'more-c3'))->toBe([['report-c3', 'Report commit', 'custom:flag']]);

// Your own commit, and someone else's that isn't published, can't be reported.
foreach (['c2', 'c1'] as $id) {
expect(array_column(array_filter(menuItems($screen, 'more-'.$id), is_array(...)), 0))->not->toContain('report-'.$id);
}
// Your own commit can't be reported, and someone else's that isn't published has no More at all.
expect(array_column(array_filter(menuItems($screen, 'more-c2'), is_array(...)), 0))->not->toContain('report-c2')
->and(findNode($screen->tree(), fn (array $node): bool => ($node['ref'] ?? null) === 'more-c1'))->toBeNull();

// The sheet is there, waiting, but down.
expect(sheetProps($screen)['visible'])->toBeFalse();
Expand Down
Loading
Loading