feat:将每日新闻改为starlight皮肤站
This commit is contained in:
@ -1,64 +1,31 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ExternalIcon, NewspaperIcon } from '@modrinth/assets'
|
import { ExternalIcon } from '@modrinth/assets'
|
||||||
import {
|
import { defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||||
defineMessages,
|
|
||||||
injectNotificationManager,
|
|
||||||
useFormatDateTime,
|
|
||||||
useVIntl,
|
|
||||||
} from '@modrinth/ui'
|
|
||||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
import { computed, ref } from 'vue'
|
|
||||||
|
|
||||||
import { useNetworkStatus } from '@/composables/useNetworkStatus'
|
|
||||||
import { get_minecraft_news, type MinecraftNewsItem } from '@/helpers/mc_news'
|
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const { handleError } = injectNotificationManager()
|
const { handleError } = injectNotificationManager()
|
||||||
const { offline } = useNetworkStatus()
|
|
||||||
const formatDate = useFormatDateTime({ dateStyle: 'medium' })
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
news: { id: 'app.home.news.title', defaultMessage: 'Minecraft news' },
|
widgetTitle: { id: 'app.home.skin-site.title', defaultMessage: 'StarLight skin site' },
|
||||||
openArticle: { id: 'app.home.news.open-article', defaultMessage: 'Read on minecraft.net' },
|
luckyIndex: { id: 'app.home.skin-site.lucky-index', defaultMessage: 'Lucky index' },
|
||||||
|
jump: { id: 'app.home.skin-site.jump', defaultMessage: 'Jump to StarLight skin site' },
|
||||||
})
|
})
|
||||||
|
|
||||||
const NEWS_COUNT = 12
|
const SKIN_PROFILE_URL = 'https://skin.starlight.cool/profile'
|
||||||
const NEWS_SKELETON_COUNT = 4
|
const SKIN_HOME_URL = 'https://skin.starlight.cool/'
|
||||||
|
|
||||||
const newsItems = ref<MinecraftNewsItem[]>([])
|
async function openProfile() {
|
||||||
const loading = ref(true)
|
|
||||||
const htmlDecoder = document.createElement('textarea')
|
|
||||||
|
|
||||||
get_minecraft_news(NEWS_COUNT)
|
|
||||||
.then((items) => {
|
|
||||||
newsItems.value = items.map((item) => ({
|
|
||||||
...item,
|
|
||||||
title: decodeHtmlEntities(item.title),
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
newsItems.value = []
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false
|
|
||||||
})
|
|
||||||
|
|
||||||
const visible = computed(() => !offline.value && (loading.value || newsItems.value.length > 0))
|
|
||||||
|
|
||||||
function newsDateLabel(item: MinecraftNewsItem): string | null {
|
|
||||||
if (!item.date) return null
|
|
||||||
const parsed = new Date(item.date)
|
|
||||||
return Number.isNaN(parsed.getTime()) ? null : formatDate(parsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
function decodeHtmlEntities(value: string): string {
|
|
||||||
htmlDecoder.innerHTML = value
|
|
||||||
return htmlDecoder.value
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openArticle(item: MinecraftNewsItem) {
|
|
||||||
try {
|
try {
|
||||||
await openUrl(item.read_more_url)
|
await openUrl(SKIN_PROFILE_URL)
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openSkinSite() {
|
||||||
|
try {
|
||||||
|
await openUrl(SKIN_HOME_URL)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error)
|
handleError(error)
|
||||||
}
|
}
|
||||||
@ -66,59 +33,34 @@ async function openArticle(item: MinecraftNewsItem) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section
|
<section class="flex min-w-0 flex-col gap-3 border-0 border-b-[1px] border-solid border-[--brand-gradient-border] p-4">
|
||||||
v-if="visible"
|
|
||||||
class="flex min-w-0 flex-col gap-3 border-0 border-b-[1px] border-solid border-[--brand-gradient-border] p-4"
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<NewspaperIcon class="size-4 shrink-0 text-secondary" aria-hidden="true" />
|
<ExternalIcon class="size-4 shrink-0 text-secondary" aria-hidden="true" />
|
||||||
<h2 class="m-0 truncate text-lg">
|
<h2 class="m-0 truncate text-lg">
|
||||||
{{ formatMessage(messages.news) }}
|
{{ formatMessage(messages.widgetTitle) }}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<ul v-if="loading" class="m-0 flex list-none flex-col gap-1.5 p-0" aria-hidden="true">
|
|
||||||
<li
|
<div class="flex flex-col gap-2">
|
||||||
v-for="index in NEWS_SKELETON_COUNT"
|
|
||||||
:key="index"
|
|
||||||
class="flex animate-pulse items-center gap-2.5 px-1.5 py-1.5"
|
|
||||||
>
|
|
||||||
<div class="h-9 w-16 shrink-0 rounded-lg bg-button-bg" />
|
|
||||||
<div class="flex min-w-0 flex-1 flex-col gap-1.5">
|
|
||||||
<div class="h-3 w-full rounded bg-button-bg" />
|
|
||||||
<div class="h-3 w-1/2 rounded bg-button-bg" />
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul v-else class="m-0 flex list-none flex-col p-0">
|
|
||||||
<li v-for="item in newsItems" :key="`${item.date ?? ''}:${item.title}`" class="group min-w-0">
|
|
||||||
<button
|
<button
|
||||||
v-tooltip="formatMessage(messages.openArticle)"
|
|
||||||
type="button"
|
type="button"
|
||||||
class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg border-0 bg-transparent px-1.5 py-1.5 text-left transition-colors hover:bg-button-bg"
|
class="flex w-full cursor-pointer items-center justify-between rounded-lg border border-[--brand-gradient-border] bg-transparent px-3 py-2 text-left transition-colors hover:bg-button-bg"
|
||||||
@click="openArticle(item)"
|
@click="openProfile"
|
||||||
>
|
>
|
||||||
<img
|
<span class="text-sm font-semibold text-contrast">
|
||||||
v-if="item.image_url"
|
{{ formatMessage(messages.luckyIndex) }}
|
||||||
:src="item.image_url"
|
|
||||||
alt=""
|
|
||||||
class="h-9 w-16 shrink-0 rounded-lg object-cover"
|
|
||||||
loading="lazy"
|
|
||||||
/>
|
|
||||||
<div v-else class="h-9 w-16 shrink-0 rounded-lg bg-button-bg" />
|
|
||||||
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
||||||
<span class="line-clamp-2 text-sm font-semibold leading-snug text-contrast">
|
|
||||||
{{ item.title }}
|
|
||||||
</span>
|
</span>
|
||||||
<span v-if="newsDateLabel(item)" class="truncate text-xs text-secondary">
|
<ExternalIcon class="size-4 shrink-0 text-secondary" aria-hidden="true" />
|
||||||
{{ newsDateLabel(item) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<ExternalIcon
|
|
||||||
class="size-3.5 shrink-0 text-secondary opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
</li>
|
|
||||||
</ul>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex w-full cursor-pointer items-center justify-center gap-2 rounded-lg bg-[--brand-gradient] px-3 py-2 font-semibold text-[var(--color-contrast)] transition-opacity hover:opacity-90"
|
||||||
|
@click="openSkinSite"
|
||||||
|
>
|
||||||
|
<ExternalIcon class="size-4 shrink-0" aria-hidden="true" />
|
||||||
|
<span>{{ formatMessage(messages.jump) }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user