feat: add revisions to detail page

This commit is contained in:
Mathias Schopmans
2024-02-22 17:03:21 +01:00
committed by Mathias Schopmans
parent 0aa51f2db4
commit d5cb8410e7
11 changed files with 276 additions and 13 deletions

View File

@@ -150,13 +150,17 @@ function postProcessItems(items: Item[]): {
random: [Math.sqrt(Math.random()), Math.random()] as [number, number], random: [Math.sqrt(Math.random()), Math.random()] as [number, number],
flag: getFlag(item, latestRelease), flag: getFlag(item, latestRelease),
// only keep revision which ring or body is different // only keep revision which ring or body is different
revisions: item.revisions?.filter((revision, index, revisions) => { revisions: item.revisions
?.filter((revision, index, revisions) => {
const { ring, body } = revision; const { ring, body } = revision;
return ( return (
ring !== item.ring || ring !== item.ring ||
(body != "" && body != item.body && body !== revisions[index - 1]?.body) (body != "" &&
body != item.body &&
body !== revisions[index - 1]?.body)
); );
}), })
.reverse(),
})); }));
return { releases, tags, items: processedItems }; return { releases, tags, items: processedItems };

View File

@@ -18,6 +18,10 @@
border: none; border: none;
} }
.icon {
fill: var(--highlight);
}
@media (min-width: 768px) { @media (min-width: 768px) {
.filter { .filter {
flex: 1 1 auto; flex: 1 1 auto;

View File

@@ -7,7 +7,6 @@ interface QueryFilterProps {
value?: string; value?: string;
onChange: (value: string) => void; onChange: (value: string) => void;
} }
export function QueryFilter({ value, onChange }: QueryFilterProps) { export function QueryFilter({ value, onChange }: QueryFilterProps) {
const _onChange = (e: ChangeEvent<HTMLInputElement>) => { const _onChange = (e: ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value); onChange(e.target.value);
@@ -22,7 +21,7 @@ export function QueryFilter({ value, onChange }: QueryFilterProps) {
onChange={_onChange} onChange={_onChange}
/> />
<button className={styles.button} type="submit"> <button className={styles.button} type="submit">
<Search /> <Search className={styles.icon} />
</button> </button>
</div> </div>
); );

View File

@@ -0,0 +1,71 @@
.revision {
padding: 30px 0 15px 35px;
margin-left: 5px;
border-left: 1px solid var(--border);
}
.release {
display: block;
text-align: center;
text-transform: uppercase;
font-size: 12px;
line-height: 1.2;
width: 50px;
height: 50px;
padding: 10px 0;
border-radius: 50%;
border: 1px solid var(--border);
background: var(--background);
float: left;
margin: -15px 0 0 -60px;
}
.ring {
float: left;
margin: -45px 0 0 0;
}
.content {
background: var(--foreground);
color: var(--text);
border-radius: 6px;
padding: 30px 15px;
}
.content a {
color: var(--highlight);
}
@media (min-width: 768px) {
.revision {
padding: 30px 0 15px 50px;
margin-left: 38px;
}
.release {
font-size: 18px;
width: 75px;
height: 75px;
padding: 15px 0;
margin: -15px 0 0 -90px;
}
.ring {
margin-left: -15px;
}
.content {
padding: 30px;
}
}
/* special styles for revisions without content */
.revision.noContent {
.content {
background: none;
}
.ring {
margin-top: -20px;
}
}

View File

@@ -0,0 +1,48 @@
import styles from "./ItemDetail.module.css";
import { RingBadge } from "@/components/Badge/Badge";
import { Item } from "@/lib/types";
import { cn } from "@/lib/utils";
interface ItemProps {
item: Item;
}
export function ItemDetail({ item }: ItemProps) {
return (
<>
<h1 className={styles.title}>{item.title}</h1>
<div className={styles.revisions}>
<Revision release={item.release} ring={item.ring} body={item.body} />
{item.revisions?.map((revision, index) => (
<Revision key={index} {...revision} />
))}
</div>
</>
);
}
interface RevisionProps {
release: string;
ring: string;
body?: string;
}
function Revision({ release, ring, body }: RevisionProps) {
const date = new Date(release);
const formattedDate = date.toLocaleDateString("en-US", {
month: "short",
year: "numeric",
});
return (
<div className={cn(styles.revision, !body && styles.noContent)}>
<time dateTime={release} className={styles.release}>
{formattedDate}
</time>
<div className={styles.content}>
<RingBadge className={styles.ring} ring={ring} size="large" />
{body ? <div dangerouslySetInnerHTML={{ __html: body }} /> : null}
</div>
</div>
);
}

View File

@@ -34,6 +34,7 @@
.link { .link {
display: block; display: block;
padding: 10px; padding: 10px;
border-radius: 6px;
&.isFadedOut { &.isFadedOut {
opacity: 0.65; opacity: 0.65;

View File

@@ -0,0 +1,22 @@
.layout {
}
.sidebar {
}
.content {
margin-bottom: 60px;
}
@media (min-width: 1024px) {
.layout {
display: flex;
}
.sidebar {
width: 360px;
padding: 110px 0 0 60px;
}
.content {
flex: 1;
}
}

View File

@@ -2,7 +2,10 @@ import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useMemo } from "react"; import { useMemo } from "react";
import styles from "./[id].module.css";
import { RingBadge } from "@/components/Badge/Badge"; import { RingBadge } from "@/components/Badge/Badge";
import { ItemDetail } from "@/components/ItemDetail/ItemDetail";
import { ItemList } from "@/components/ItemList/ItemList"; import { ItemList } from "@/components/ItemList/ItemList";
import { import {
getItem, getItem,
@@ -33,10 +36,15 @@ const ItemPage: CustomPage = () => {
<meta name="description" content={quadrant.description} /> <meta name="description" content={quadrant.description} />
</Head> </Head>
<h1>{item.title}</h1> <div className={styles.layout}>
<RingBadge ring={item.ring} size="large" /> <section className={styles.content}>
<div dangerouslySetInnerHTML={{ __html: item.body }}></div> <ItemDetail item={item} />
</section>
<aside className={styles.sidebar}>
<h3>{quadrant.title}</h3>
<ItemList items={relatedItems} activeId={item.id} /> <ItemList items={relatedItems} activeId={item.id} />
</aside>
</div>
</> </>
); );
}; };

View File

@@ -6,6 +6,7 @@ import { Layout, type LayoutClass } from "@/components/Layout/Layout";
import { formatTitle } from "@/lib/format"; import { formatTitle } from "@/lib/format";
import { assetUrl } from "@/lib/utils"; import { assetUrl } from "@/lib/utils";
import "@/styles/globals.css"; import "@/styles/globals.css";
import "@/styles/hljs.css";
export type CustomPage<P = {}, IP = P> = NextPage<P, IP> & { export type CustomPage<P = {}, IP = P> = NextPage<P, IP> & {
layoutClass?: LayoutClass; layoutClass?: LayoutClass;

View File

@@ -7,6 +7,7 @@
--foreground: #fff; --foreground: #fff;
--background: #173d7a; --background: #173d7a;
--text: #575757;
--highlight: #029df7; --highlight: #029df7;
--border: rgba(255, 255, 255, 0.1); --border: rgba(255, 255, 255, 0.1);
@@ -95,6 +96,14 @@ ol {
margin-bottom: 1em; margin-bottom: 1em;
} }
pre {
margin-bottom: 1em;
}
code {
font-family: var(--font-mono);
}
input { input {
background: var(--foreground); background: var(--foreground);
color: var(--background); color: var(--background);

96
src/styles/hljs.css Normal file
View File

@@ -0,0 +1,96 @@
.hljs-subst {
/* var(--highlight-color) */
color: #2f3337;
}
.hljs-comment {
/* var(--highlight-comment) */
color: #656e77;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-meta .hljs-keyword,
.hljs-doctag,
.hljs-section {
/* var(--highlight-keyword) */
color: #015692;
}
.hljs-attr {
/* var(--highlight-attribute); */
color: #015692;
}
.hljs-attribute {
/* var(--highlight-symbol) */
color: #803378;
}
.hljs-name,
.hljs-type,
.hljs-number,
.hljs-selector-id,
.hljs-quote,
.hljs-template-tag {
/* var(--highlight-namespace) */
color: #b75501;
}
.hljs-selector-class {
/* var(--highlight-keyword) */
color: #015692;
}
.hljs-string,
.hljs-regexp,
.hljs-symbol,
.hljs-variable,
.hljs-template-variable,
.hljs-link,
.hljs-selector-attr {
/* var(--highlight-variable) */
color: #54790d;
}
.hljs-meta,
.hljs-selector-pseudo {
/* var(--highlight-keyword) */
color: #015692;
}
.hljs-built_in,
.hljs-title,
.hljs-literal {
/* var(--highlight-literal) */
color: #b75501;
}
.hljs-bullet,
.hljs-code {
/* var(--highlight-punctuation) */
color: #535a60;
}
.hljs-meta .hljs-string {
/* var(--highlight-variable) */
color: #54790d;
}
.hljs-deletion {
/* var(--highlight-deletion) */
color: #c02d2e;
}
.hljs-addition {
/* var(--highlight-addition) */
color: #2f6f44;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}