Skip to content

Commit

Permalink
Merge pull request #110 from guardian/pf/anchor-tags-for-feed
Browse files Browse the repository at this point in the history
Use anchor tags for feed
  • Loading branch information
bryophyta authored Jan 27, 2025
2 parents 53f0c61 + c29af33 commit 6221335
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 230 deletions.
4 changes: 2 additions & 2 deletions newswires/client/src/Feed.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EuiEmptyPrompt, EuiLoadingLogo, EuiPageTemplate } from '@elastic/eui';
import { useSearch } from './context/SearchContext.tsx';
import { WireItemTable } from './WireItemTable';
import { WireItemList } from './WireItemList.tsx';

export const Feed = () => {
const { state } = useSearch();
Expand All @@ -27,7 +27,7 @@ export const Feed = () => {
)}
{(status == 'success' || status == 'offline') &&
queryData.results.length > 0 && (
<WireItemTable wires={queryData.results} />
<WireItemList wires={queryData.results} />
)}
</EuiPageTemplate.Section>
);
Expand Down
31 changes: 31 additions & 0 deletions newswires/client/src/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { MouseEventHandler } from 'react';
import { useCallback } from 'react';
import { useSearch } from './context/SearchContext';
import { configToUrl } from './urlState';

export const Link = ({
children,
to,
}: {
children: React.ReactNode;
to: string;
}) => {
const { handleSelectItem, config } = useSearch();
const href = configToUrl({ ...config, view: 'item', itemId: to });

const onClick: MouseEventHandler<HTMLAnchorElement> = useCallback(
(e) => {
if (!(e.getModifierState('Meta') || e.getModifierState('Control'))) {
e.preventDefault();
handleSelectItem(to);
}
},
[to, handleSelectItem],
);

return (
<a href={href} onClick={onClick}>
{children}
</a>
);
};
32 changes: 1 addition & 31 deletions newswires/client/src/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import { css } from '@emotion/react';
import { useCallback, useMemo, useState } from 'react';
import { useSearch } from './context/SearchContext.tsx';
import { SearchBox } from './SearchBox';
import { AAPBrand, APBrand, PABrand, reutersBrand } from './sharedStyles';
import type { Query } from './sharedTypes';
import { recognisedSuppliers, supplierData } from './suppliers.ts';

function decideLabelForQueryBadge(query: Query): string {
const { supplier, q, bucket } = query;
Expand All @@ -28,36 +28,6 @@ function decideLabelForQueryBadge(query: Query): string {
return labels.filter((label) => label.length > 0).join(' ');
}

const supplierData: Record<
string,
{
label: string;
colour: string;
}
> = {
REUTERS: { label: 'Reuters', colour: reutersBrand },
AP: {
label: 'AP',
colour: APBrand,
},
AAP: {
label: 'AAP',
colour: AAPBrand,
},
PA: {
label: 'PA',
colour: PABrand,
},
GuAP: {
label: 'AP (Gu)',
colour: APBrand,
},
GuReuters: {
label: 'Reuters (Gu)',
colour: reutersBrand,
},
};
const recognisedSuppliers = Object.keys(supplierData);
const buckets = [
{ id: 'no-sports', name: 'No Sports' },
{ id: 'pa-home', name: 'PA Home' },
Expand Down
190 changes: 190 additions & 0 deletions newswires/client/src/WireItemList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import {
EuiBadge,
EuiButton,
EuiText,
useEuiBackgroundColor,
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import { useEffect, useRef, useState } from 'react';
import sanitizeHtml from 'sanitize-html';
import { useSearch } from './context/SearchContext.tsx';
import { formatTimestamp } from './formatTimestamp.ts';
import { Link } from './Link.tsx';
import type { WireData } from './sharedTypes.ts';
import { getSupplierInfo } from './suppliers.ts';

const fadeOutBackground = css`
animation: fadeOut ease-out 15s;
@keyframes fadeOut {
from {
background-color: aliceblue;
}
to {
background-color: white;
}
}
`;

export const WireItemList = ({ wires }: { wires: WireData[] }) => {
const { config, loadMoreResults } = useSearch();

const [isLoadingMore, setIsLoadingMore] = useState<boolean>(false);

const selectedWireId = config.itemId;

const handleLoadMoreResults = () => {
if (wires.length > 0) {
setIsLoadingMore(true);

const beforeId = Math.min(...wires.map((wire) => wire.id)).toString();

void loadMoreResults(beforeId).finally(() => {
setIsLoadingMore(false);
});
}
};

return (
<>
<ul>
{wires.map(({ id, content, supplier, highlight, isFromRefresh }) => (
<li key={id}>
<WirePreviewCard
id={id}
supplier={supplier}
content={content}
isFromRefresh={isFromRefresh}
highlight={highlight}
selected={selectedWireId == id.toString()}
/>
</li>
))}
</ul>
<EuiButton
isLoading={isLoadingMore}
css={css`
margin-top: 12px;
`}
onClick={handleLoadMoreResults}
>
{isLoadingMore ? 'Loading' : 'Load more'}
</EuiButton>
</>
);
};

const WirePreviewCard = ({
id,
supplier,
content,
highlight,
selected,
isFromRefresh,
}: {
id: number;
supplier: string;
content: WireData['content'];
highlight: string;
selected: boolean;
isFromRefresh: boolean;
}) => {
const ref = useRef<HTMLDivElement>(null);

useEffect(() => {
if (selected && ref.current) {
ref.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
});
}
}, [selected]);

const theme = useEuiTheme();
const accentBgColor = useEuiBackgroundColor('accent');

const supplierInfo = getSupplierInfo(supplier);

const supplierLabel = supplierInfo?.label ?? supplier;
const supplierColour = supplierInfo?.colour ?? theme.euiTheme.colors.text;

const hasSlug = content.slug && content.slug.length > 0;

const mainHeadingContent = hasSlug ? content.slug : content.headline;

const cardGrid = css`
display: grid;
gap: 0.5rem;
align-items: baseline;
grid-template-areas: 'badge title date' 'content content content';
grid-template-columns: min-content 1fr auto;
grid-template-rows: auto auto;
`;

return (
<Link to={id.toString()}>
<div
ref={ref}
css={[
cardGrid,
css`
&:hover {
background-color: ${theme.euiTheme.colors.lightestShade};
border-left: 4px solid ${theme.euiTheme.colors.accent};
}
border-left: 4px solid
${selected ? theme.euiTheme.colors.primary : 'transparent'};
border-bottom: 1px solid ${theme.euiTheme.colors.mediumShade};
padding: 0.5rem;
box-sizing: content-box;
color: ${theme.euiTheme.colors.text};
background-color: ${selected ? accentBgColor : 'inherit'};
${isFromRefresh ? fadeOutBackground : ''}
`,
]}
>
<EuiBadge color={supplierColour}>{supplierLabel}</EuiBadge>
<h3>
<p>{mainHeadingContent}</p>
</h3>
{content.versionCreated
? formatTimestamp(content.versionCreated)
.split(', ')
.map((part) => (
<EuiText
size="xs"
key={part}
css={css`
padding-left: 5px;
`}
>
{part}
</EuiText>
))
: ''}
<div
css={css`
grid-area: content;
`}
>
{hasSlug && <p>{content.headline}</p>}
{highlight && highlight.trim().length > 0 && (
<EuiText
size="xs"
css={css`
margin-top: 0.1rem;
padding: 0.1rem 0.5rem;
background-color: ${theme.euiTheme.colors.highlight};
justify-self: start;
`}
>
<p
dangerouslySetInnerHTML={{ __html: sanitizeHtml(highlight) }}
/>
</EuiText>
)}
</div>
</div>
</Link>
);
};
Loading

0 comments on commit 6221335

Please sign in to comment.