@@ -345,6 +352,22 @@
tags={entry.tags}
inline
/>
+ {#if imagePreview}
+
+
+
+ {/if}
{relativeTime(entry.created_at)}
diff --git a/src/routes/Home.test.ts b/src/routes/Home.test.ts
index b947386..1ba36c5 100644
--- a/src/routes/Home.test.ts
+++ b/src/routes/Home.test.ts
@@ -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')
+ })
+})
diff --git a/src/routes/Profile.svelte b/src/routes/Profile.svelte
index a3aafe1..dd7d638 100644
--- a/src/routes/Profile.svelte
+++ b/src/routes/Profile.svelte
@@ -436,7 +436,6 @@
ownerName={firstName}
ownerEmojis={account.emojis}
accounts={topEightAccounts}
- viewAllHref={`#/@${account.acct}/friends`}
missing={topEightMissing}
loading={topEightLoading}
/>
diff --git a/src/routes/Profile.test.ts b/src/routes/Profile.test.ts
index e02b737..331d5ea 100644
--- a/src/routes/Profile.test.ts
+++ b/src/routes/Profile.test.ts
@@ -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()
diff --git a/src/styles/blog.css b/src/styles/blog.css
index df81f07..b8eb254 100644
--- a/src/styles/blog.css
+++ b/src/styles/blog.css
@@ -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);
+}
diff --git a/src/styles/chrome.css b/src/styles/chrome.css
index 9bf9afd..74cdcda 100644
--- a/src/styles/chrome.css
+++ b/src/styles/chrome.css
@@ -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 {
diff --git a/src/styles/profile.css b/src/styles/profile.css
index 69de457..6507bd5 100644
--- a/src/styles/profile.css
+++ b/src/styles/profile.css
@@ -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));