mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-24 23:56:51 +00:00
Add "new" badge and store seen state
This commit is contained in:
@@ -21,6 +21,7 @@ export namespace Store {
|
||||
export const ID_TREE_VIEW_MODE = 'misode_tree_view_mode'
|
||||
export const ID_COLORMAP = 'misode_colormap'
|
||||
export const ID_GENERATOR_HISTORY = 'misode_generator_history'
|
||||
export const ID_WHATS_NEW_SEEN = 'misode_whats_new_seen'
|
||||
|
||||
export function getLanguage() {
|
||||
return localStorage.getItem(ID_LANGUAGE) ?? 'en'
|
||||
@@ -186,4 +187,22 @@ export namespace Store {
|
||||
history.push(id)
|
||||
localStorage.setItem(ID_GENERATOR_HISTORY, JSON.stringify(history.slice(-50)))
|
||||
}
|
||||
|
||||
export function getWhatsNewSeen(): { id: string, time: string }[] {
|
||||
return JSON.parse(localStorage.getItem(ID_WHATS_NEW_SEEN) ?? '[]')
|
||||
}
|
||||
|
||||
export function seeWhatsNew(ids: string[]) {
|
||||
const now = new Date().toISOString()
|
||||
const items = getWhatsNewSeen()
|
||||
for (const id of ids) {
|
||||
const old = items.find(i => i.id === id)
|
||||
if (old) {
|
||||
old.time = now
|
||||
} else {
|
||||
items.push({ id, time: now })
|
||||
}
|
||||
}
|
||||
localStorage.setItem(ID_WHATS_NEW_SEEN, JSON.stringify(items))
|
||||
}
|
||||
}
|
||||
|
||||
16
src/app/components/whatsnew/WhatsNewEntry.tsx
Normal file
16
src/app/components/whatsnew/WhatsNewEntry.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { marked } from 'marked'
|
||||
import type { WhatsNewItem } from '../../services/DataFetcher.js'
|
||||
import { WhatsNewTime } from './WhatsNewTime.jsx'
|
||||
|
||||
interface EntryProps {
|
||||
item: WhatsNewItem,
|
||||
}
|
||||
export function WhatsNewEntry({ item }: EntryProps) {
|
||||
return <article class="whats-new-entry">
|
||||
<a href={item.url} target="_blank">
|
||||
<WhatsNewTime item={item} />
|
||||
<h2>{item.title}</h2>
|
||||
</a>
|
||||
<div class="guide-content" dangerouslySetInnerHTML={{ __html: marked(item.body) }} />
|
||||
</article>
|
||||
}
|
||||
14
src/app/components/whatsnew/WhatsNewTime.tsx
Normal file
14
src/app/components/whatsnew/WhatsNewTime.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useLocale } from '../../contexts/Locale.jsx'
|
||||
import type { WhatsNewItem } from '../../services/DataFetcher.js'
|
||||
|
||||
interface Props {
|
||||
item: WhatsNewItem,
|
||||
short?: boolean,
|
||||
}
|
||||
export function WhatsNewTime({ item, short }: Props) {
|
||||
const { locale } = useLocale()
|
||||
return <time dateTime={item.createdAt} title={Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long' }).format(new Date(item.createdAt))}>
|
||||
{(!short || item.seenAt !== undefined) && Intl.DateTimeFormat(undefined, { day: 'numeric',month: 'long', year: 'numeric' }).format(new Date(item.createdAt))}
|
||||
{item.seenAt === undefined && <span class="new-badge">{locale('whats_new.new')}</span>}
|
||||
</time>
|
||||
}
|
||||
2
src/app/components/whatsnew/index.ts
Normal file
2
src/app/components/whatsnew/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './WhatsNewEntry.jsx'
|
||||
export * from './WhatsNewTime.jsx'
|
||||
@@ -3,6 +3,7 @@ import contributors from '../../contributors.json'
|
||||
import { Store } from '../Store.js'
|
||||
import { shuffle } from '../Utils.js'
|
||||
import { Card, ChangelogEntry, Footer, GeneratorCard, Giscus, GuideCard, ToolCard, ToolGroup } from '../components/index.js'
|
||||
import { WhatsNewTime } from '../components/whatsnew/WhatsNewTime.jsx'
|
||||
import { useLocale, useTitle } from '../contexts/index.js'
|
||||
import { useAsync } from '../hooks/useAsync.js'
|
||||
import { useMediaQuery } from '../hooks/useMediaQuery.js'
|
||||
@@ -26,14 +27,15 @@ export function Home({}: Props) {
|
||||
<div class="card-column">
|
||||
<PopularGenerators />
|
||||
{smallScreen && <FavoriteGenerators />}
|
||||
{smallScreen && <WhatsNew />}
|
||||
<Changelog />
|
||||
{smallScreen && <Guides />}
|
||||
<Versions />
|
||||
{smallScreen && <Tools />}
|
||||
<WhatsNew />
|
||||
</div>
|
||||
{!smallScreen && <div class="card-column">
|
||||
<FavoriteGenerators />
|
||||
<WhatsNew />
|
||||
<Guides />
|
||||
<Tools />
|
||||
</div>}
|
||||
@@ -142,7 +144,7 @@ function WhatsNew() {
|
||||
const { value: items } = useAsync(fetchWhatsNew)
|
||||
|
||||
return <ToolGroup title={locale('whats_new')} link="/whats-new/" titleIcon="megaphone">
|
||||
{items?.slice(0, 3).map(item => <Card overlay={Intl.DateTimeFormat(undefined, { day: 'numeric',month: 'long', year: 'numeric' }).format(new Date(item.createdAt))}>{item.title}</Card>)}
|
||||
{items?.slice(0, 3).map(item => <Card link="/whats-new/" overlay={<WhatsNewTime item={item} short={true} />}>{item.title}</Card>)}
|
||||
</ToolGroup>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { marked } from 'marked'
|
||||
import { useEffect } from 'preact/hooks'
|
||||
import { Store } from '../Store.js'
|
||||
import { ErrorPanel, Footer } from '../components/index.js'
|
||||
import { WhatsNewEntry } from '../components/whatsnew/WhatsNewEntry.jsx'
|
||||
import { useLocale, useTitle } from '../contexts/index.js'
|
||||
import { useActiveTimeout } from '../hooks/useActiveTimout.js'
|
||||
import { useAsync } from '../hooks/useAsync.js'
|
||||
import type { WhatsNewItem } from '../services/DataFetcher.js'
|
||||
import { fetchWhatsNew } from '../services/DataFetcher.js'
|
||||
|
||||
interface Props {
|
||||
@@ -14,6 +16,18 @@ export function WhatsNew({}: Props) {
|
||||
|
||||
const { value: items, error } = useAsync(fetchWhatsNew)
|
||||
|
||||
const [storeTime, startStoreTime] = useActiveTimeout()
|
||||
useEffect(() => {
|
||||
if (items !== undefined) {
|
||||
startStoreTime()
|
||||
}
|
||||
}, [items])
|
||||
useEffect(() => {
|
||||
if (items !== undefined && storeTime) {
|
||||
Store.seeWhatsNew(items.map(i => i.id))
|
||||
}
|
||||
}, [items, storeTime])
|
||||
|
||||
return <main>
|
||||
<div class="container whats-new">
|
||||
<p>{locale('whats_new.description')}</p>
|
||||
@@ -23,16 +37,3 @@ export function WhatsNew({}: Props) {
|
||||
<Footer />
|
||||
</main>
|
||||
}
|
||||
|
||||
interface EntryProps {
|
||||
item: WhatsNewItem,
|
||||
}
|
||||
function WhatsNewEntry({ item }: EntryProps) {
|
||||
return <article class="whats-new-entry">
|
||||
<a href={item.url} target="_blank">
|
||||
<time dateTime={item.createdAt} title={Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long' }).format(new Date(item.createdAt))}>{Intl.DateTimeFormat(undefined, { day: 'numeric',month: 'long', year: 'numeric' }).format(new Date(item.createdAt))}</time>
|
||||
<h2>{item.title}</h2>
|
||||
</a>
|
||||
<div class="guide-content" dangerouslySetInnerHTML={{ __html: marked(item.body) }} />
|
||||
</article>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { CollectionRegistry } from '@mcschema/core'
|
||||
import config from '../Config.js'
|
||||
import { Store } from '../Store.js'
|
||||
import { message } from '../Utils.js'
|
||||
import type { BlockStateRegistry, VersionId } from './Schemas.js'
|
||||
|
||||
@@ -287,11 +288,19 @@ export interface WhatsNewItem {
|
||||
body: string,
|
||||
url: string,
|
||||
createdAt: string,
|
||||
seenAt?: string,
|
||||
}
|
||||
|
||||
export async function fetchWhatsNew(): Promise<WhatsNewItem[]> {
|
||||
try {
|
||||
const whatsNew = await cachedFetch<WhatsNewItem[]>(whatsNewUrl, { refresh: true })
|
||||
const seenState = Store.getWhatsNewSeen()
|
||||
for (const { id, time } of seenState) {
|
||||
const item = whatsNew.find(i => i.id === id)
|
||||
if (item) {
|
||||
item.seenAt = time
|
||||
}
|
||||
}
|
||||
return whatsNew
|
||||
} catch (e) {
|
||||
throw new Error(`Error occured while fetching what's new: ${message(e)}`)
|
||||
|
||||
@@ -255,6 +255,7 @@
|
||||
"weight": "Weight",
|
||||
"whats_new": "What's new?",
|
||||
"whats_new.description": "Stay informed about all the latest development on misode.github.io. Read below to find out which features have recently been added.",
|
||||
"whats_new.new": "NEW",
|
||||
"world": "World Settings",
|
||||
"worldgen": "Worldgen",
|
||||
"worldgen/biome": "Biome",
|
||||
|
||||
@@ -2417,6 +2417,10 @@ hr {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.whats-new-entry time .new-badge {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.whats-new-entry h2 {
|
||||
color: var(--text-1);
|
||||
padding: 10px 0 15px;
|
||||
@@ -2433,6 +2437,15 @@ hr {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.new-badge {
|
||||
background-color: var(--accent-primary);
|
||||
color: var(--background-1);
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
border-radius: 1000px;
|
||||
padding: 1px 4px;
|
||||
}
|
||||
|
||||
.ace_editor,
|
||||
.ace_gutter,
|
||||
.ace_gutter .ace_layer,
|
||||
|
||||
Reference in New Issue
Block a user