Compare commits

...
1 Commits
Author SHA1 Message Date
Moon.eth c76bab55dd various fixes 2026-08-05 08:04:06 +09:00
10 changed files with 120 additions and 26 deletions
-3
View File
@@ -74,9 +74,7 @@
</form>
<p class="site-account-links">
<a href="#/settings">Settings</a>
{#if session.signedIn}
<span aria-hidden="true">|</span>
<button
type="button"
class="link-button site-header-logout"
@@ -85,7 +83,6 @@
LogOut
</button>
{:else}
<span aria-hidden="true">|</span>
<a href="#/login">LogIn</a>
{/if}
</p>
+19
View File
@@ -0,0 +1,19 @@
import { render } from '@testing-library/svelte'
import { describe, expect, it } from 'vitest'
import { APP_SERVICES } from '$lib/app-services'
import { account, session, testServices } from '$test/fixtures'
import SiteHeader from './SiteHeader.svelte'
describe('SiteHeader account controls', () => {
it('shows one visible logout control and leaves Settings to the main navigation', () => {
const services = testServices({
session: session({ signedIn: true, token: 'token', me: account() }),
})
const view = render(SiteHeader, {
context: new Map([[APP_SERVICES, services]]),
})
expect(view.getByRole('button', { name: 'LogOut' })).toHaveClass('site-header-logout')
expect(view.queryByRole('link', { name: 'Settings' })).not.toBeInTheDocument()
})
})
@@ -9,7 +9,6 @@
ownerName: string
ownerEmojis?: CustomEmoji[]
accounts: Account[]
viewAllHref: string
missing?: string[]
loading?: boolean
}
@@ -18,7 +17,6 @@
ownerName,
ownerEmojis,
accounts,
viewAllHref,
missing = [],
loading = false,
}: Props = $props()
@@ -45,7 +43,4 @@
{/if}
{/if}
<p class="top-eight-view-all">
<a href={viewAllHref}>View All of <EmojiText text={ownerName} emojis={ownerEmojis} />'s Friends</a>
</p>
</Module>
+23
View File
@@ -200,6 +200,12 @@
return fallbackMood(status.account.id)
}
/** The compact home row otherwise has no output for a media-only status. */
function imagePreviewFor(status: Status) {
if (toPlainText(status.spoiler_text || status.content)) return null
return status.media_attachments.find((attachment) => attachment.type === 'image') ?? null
}
function messageOf(cause: unknown): string {
return cause instanceof Error ? cause.message : 'Could not load that.'
}
@@ -332,6 +338,7 @@
{#each friendStatus as status (status.id)}
{@const entry = status.reblog ?? status}
{@const author = accountForStatus(entry, preferences.heleneposting)}
{@const imagePreview = imagePreviewFor(entry)}
<li class="status-line" data-account={entry.account.acct}>
<Avatar account={author} />
<div class="status-line-body">
@@ -345,6 +352,22 @@
tags={entry.tags}
inline
/>
{#if imagePreview}
<a
class="status-line-media"
data-sensitive={entry.sensitive ? 'true' : 'false'}
href={`#/blog/${entry.id}`}
aria-label={imagePreview.description || 'View image post'}
>
<img
class="status-line-media-image"
src={imagePreview.preview_url ?? imagePreview.url}
alt={imagePreview.description ?? ''}
loading="lazy"
decoding="async"
/>
</a>
{/if}
<a class="status-line-time" href={`#/blog/${entry.id}`}>
{relativeTime(entry.created_at)}
</a>
+39 -1
View File
@@ -2,7 +2,7 @@ import { render, waitFor } from '@testing-library/svelte'
import { describe, expect, it, vi } from 'vitest'
import { APP_SERVICES } from '$lib/app-services'
import { TIMELINE_REFRESH, TimelineRefreshController } from '$lib/timeline-refresh'
import { account, session, testServices } from '$test/fixtures'
import { account, session, status, testServices } from '$test/fixtures'
import Home from './Home.svelte'
describe('Home timeline refresh', () => {
@@ -38,3 +38,41 @@ describe('Home timeline refresh', () => {
expect(fetchNotifications).toHaveBeenCalledOnce()
})
})
describe('Home Friend Status previews', () => {
it('shows a small preview for an image-only post', async () => {
const imagePost = status({
id: 'image-only',
content: '',
media_attachments: [
{
id: 'photo',
type: 'image',
url: 'https://media.example/full.jpg',
preview_url: 'https://media.example/small.jpg',
description: 'A tiny cat',
},
],
})
const fetchTimeline = vi.fn(async (_api, kind: string) => ({
items: kind === 'home' ? [imagePost] : [],
links: {},
}))
const services = testServices({
session: session({ token: 'token', me: account(), signedIn: true }),
endpoints: {
fetchTimeline,
fetchFollowing: vi.fn().mockResolvedValue({ items: [], links: {} }),
fetchNotifications: vi.fn().mockResolvedValue({ items: [], links: {} }),
},
})
const view = render(Home, {
context: new Map([[APP_SERVICES, services]]),
})
const preview = await view.findByRole('img', { name: 'A tiny cat' })
expect(preview).toHaveAttribute('src', 'https://media.example/small.jpg')
expect(preview).toHaveClass('status-line-media-image')
expect(preview.closest('a')).toHaveAttribute('href', '#/blog/image-only')
})
})
-1
View File
@@ -436,7 +436,6 @@
ownerName={firstName}
ownerEmojis={account.emojis}
accounts={topEightAccounts}
viewAllHref={`#/@${account.acct}/friends`}
missing={topEightMissing}
loading={topEightLoading}
/>
+2 -4
View File
@@ -144,10 +144,8 @@ describe('Profile', () => {
expect(await view.findByText('Bob')).toBeInTheDocument()
expect(await view.findByText('Carol')).toBeInTheDocument()
expect(view.container.querySelector('.top-eight-space .friend-count')).not.toBeInTheDocument()
expect(view.getByRole('link', { name: "View All of Alice's Friends" })).toHaveAttribute(
'href',
'#/@alice/friends',
)
expect(view.queryByRole('link', { name: "View All of Alice's Friends" })).not.toBeInTheDocument()
expect(view.getByRole('link', { name: '[view all]' })).toHaveAttribute('href', '#/@alice/friends')
expect(view.getAllByText('Hello from my profile.').length).toBeGreaterThan(0)
expect(view.queryByText('My top 8:')).not.toBeInTheDocument()
expect(view.queryByText('@bob@remote.test')).not.toBeInTheDocument()
+28 -6
View File
@@ -32,19 +32,22 @@
}
.blog-entry-header {
display: flex;
gap: 8px;
align-items: flex-start;
margin-bottom: 6px;
position: relative;
margin-bottom: 0;
}
.blog-entry-avatar {
flex: 0 0 auto;
position: absolute;
inset: 0 auto auto 0;
}
.blog-entry-byline {
min-width: 0;
flex: 1;
margin-left: calc(var(--ms-avatar-size) + 8px);
}
.blog-entry[data-compact='true'] .blog-entry-byline {
margin-left: 0;
}
.blog-entry-author {
@@ -704,3 +707,22 @@
color: var(--ms-muted-fg);
font-size: var(--ms-font-size-small);
}
.status-line-media {
display: block;
width: min(96px, 100%);
margin: 4px 0;
}
.status-line-media-image {
display: block;
width: 100%;
max-height: 72px;
object-fit: cover;
border: 1px solid var(--ms-avatar-border);
background: var(--ms-table-stripe-bg);
}
.status-line-media[data-sensitive='true'] .status-line-media-image {
filter: blur(8px);
}
+9
View File
@@ -132,6 +132,15 @@
margin: 0;
}
/* `.link-button` normally uses the page's navy link colour. In the navy
utility bar that makes LogOut disappear, so mutations in this region use
the same high-contrast token as its anchors. */
.site-header-logout,
.site-header-logout:hover,
.site-header-logout:focus-visible {
color: var(--ms-chrome-link);
}
/* ---------------------------------------------------------------- nav row */
.site-nav {
-6
View File
@@ -260,12 +260,6 @@
font-size: var(--ms-font-size-small);
}
.top-eight-view-all {
margin: 22px 0 2px;
text-align: right;
font-weight: 700;
}
@media (max-width: 520px) {
.top-eight-grid {
grid-template-columns: repeat(2, minmax(64px, 1fr));