Merge branch 'main' into shuuji3/feat/emoji-reactions

This commit is contained in:
TAKAHASHI Shuuji 2024-12-19 13:21:38 +09:00 committed by GitHub
commit abf1b05695
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 4588 additions and 3282 deletions

View file

@ -90,7 +90,7 @@ We've added some `UnoCSS` utilities styles to help you with that:
## Internationalization
We are using [vue-i18n](https://vue-i18n.intlify.dev/) via [nuxt-i18n](https://v8.i18n.nuxtjs.org/) to handle internationalization.
We are using [vue-i18n](https://vue-i18n.intlify.dev/) via [nuxt-i18n](https://i18n.nuxtjs.org/) to handle internationalization.
You can check the current [translation status](https://docs.elk.zone/docs/guide/contributing#translation-status): more instructions on the table caption.

View file

@ -1,27 +1,32 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
defineProps<{
const props = defineProps<{
account: mastodon.v1.Account
square?: boolean
}>()
const loaded = ref(false)
const error = ref(false)
const preferredMotion = usePreferredReducedMotion()
const accountAvatarSrc = computed(() => {
return preferredMotion.value === 'reduce' ? (props.account?.avatarStatic ?? props.account.avatar) : props.account.avatar
})
</script>
<template>
<img
:key="account.avatar"
:key="props.account.avatar"
width="400"
height="400"
select-none
:src="(error || !loaded) ? 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' : account.avatar"
:alt="$t('account.avatar_description', [account.username])"
:src="(error || !loaded) ? 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' : accountAvatarSrc"
:alt="$t('account.avatar_description', [props.account.username])"
loading="lazy"
class="account-avatar"
:class="(loaded ? 'bg-base' : 'bg-gray:10') + (square ? ' ' : ' rounded-full')"
:style="{ 'clip-path': square ? `url(#avatar-mask)` : 'none' }"
:class="(loaded ? 'bg-base' : 'bg-gray:10') + (props.square ? ' ' : ' rounded-full')"
:style="{ 'clip-path': props.square ? `url(#avatar-mask)` : 'none' }"
v-bind="$attrs"
@load="loaded = true"
@error="error = true"

View file

@ -14,11 +14,11 @@ const router = useRouter()
useCommands(() => command
? options.map(tab => ({
scope: 'Tabs',
name: tab.display,
icon: tab.icon ?? 'i-ri:file-list-2-line',
onActivate: () => router.replace(tab.to),
}))
scope: 'Tabs',
name: tab.display,
icon: tab.icon ?? 'i-ri:file-list-2-line',
onActivate: () => router.replace(tab.to),
}))
: [])
</script>

View file

@ -25,13 +25,13 @@ function toValidName(option: string) {
useCommands(() => command
? tabs.value.map(tab => ({
scope: 'Tabs',
scope: 'Tabs',
name: tab.display,
icon: tab.icon ?? 'i-ri:file-list-2-line',
name: tab.display,
icon: tab.icon ?? 'i-ri:file-list-2-line',
onActivate: () => modelValue.value = tab.name,
}))
onActivate: () => modelValue.value = tab.name,
}))
: [])
</script>

View file

@ -1,5 +1,6 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
import type { ConfirmDialogChoice } from '~/types'
import {
isCommandPanelOpen,
isConfirmDialogOpen,
@ -13,7 +14,6 @@ import {
isReportDialogOpen,
isSigninDialogOpen,
} from '~/composables/dialog'
import type { ConfirmDialogChoice } from '~/types'
const isMac = useIsMac()

View file

@ -1,3 +1,6 @@
<script setup lang="ts">
</script>
<template>
<span shrink-0 aspect="1/1" sm:h-8 xl:h-10 class="rtl-flip"><svg
xmlns="http://www.w3.org/2000/svg" w-full

View file

@ -1,10 +1,10 @@
<script setup lang="ts">
defineModel<boolean>()
const model = defineModel<boolean>()
</script>
<template>
<NavBottomMoreMenu
v-slot="{ toggleVisible, show }" v-model="modelValue!" flex flex-row items-center
v-slot="{ toggleVisible, show }" v-model="model!" flex flex-row items-center
place-content-center h-full flex-1 cursor-pointer
>
<button

View file

@ -1,16 +1,38 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
// Add undocumented 'annual_report' type introduced in v4.3
// ref. https://github.com/mastodon/documentation/issues/1211#:~:text=api/v1/annual_reports
type NotificationType = mastodon.v1.Notification['type'] | 'annual_report'
type Notification = Omit<mastodon.v1.Notification, 'type'> & { type: NotificationType }
const { notification } = defineProps<{
notification: mastodon.v1.Notification
notification: Notification
}>()
const { t } = useI18n()
// list of notification types Elk currently implemented
// type 'favourite' and 'reblog' should always rendered by NotificationGroupedLikes
const supportedNotificationTypes: NotificationType[] = [
'follow',
'admin.sign_up',
'admin.report',
'follow_request',
'update',
'mention',
'poll',
'update',
'status',
'annual_report',
]
// well-known emoji reactions types Elk does not support yet
const unsupportedEmojiReactionTypes = ['pleroma:emoji_reaction', 'reaction']
if (unsupportedEmojiReactionTypes.includes(notification.type))
if (unsupportedEmojiReactionTypes.includes(notification.type) || !supportedNotificationTypes.includes(notification.type)) {
console.warn(`[DEV] ${t('notification.missing_type')} '${notification.type}' (notification.id: ${notification.id})`)
}
</script>
<template>
@ -95,11 +117,21 @@ if (unsupportedEmojiReactionTypes.includes(notification.type))
<template v-else-if="notification.type === 'mention' || notification.type === 'poll' || notification.type === 'status'">
<StatusCard :status="notification.status!" />
</template>
<template v-else-if="!unsupportedEmojiReactionTypes.includes(notification.type)">
<!-- prevent showing errors for dev for known emoji reaction types -->
<!-- type 'favourite' and 'reblog' should always rendered by NotificationGroupedLikes -->
<div text-red font-bold>
[DEV] {{ $t('notification.missing_type') }} '{{ notification.type }}'
<template v-else-if="notification.type === 'annual_report'">
<div flex p4 items-center bg-shaded>
<div i-mdi:party-popper text-xl me-4 color-purple />
<div class="content-rich">
<p>
Your 2024 <NuxtLink to="/tags/Wrapstodon">
#Wrapstodon
</NuxtLink> awaits! Unveil your year's highlights and memorable moments on Mastodon!
</p>
<p>
<NuxtLink :to="`https://${currentServer}/notifications`" target="_blank">
View #Wrapstodon on Mastodon
</NuxtLink>
</p>
</div>
</div>
</template>
</article>

View file

@ -1,8 +1,8 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
import type { GroupedAccountLike, NotificationSlot } from '~/types'
// @ts-expect-error missing types
import { DynamicScrollerItem } from 'vue-virtual-scroller'
import type { GroupedAccountLike, NotificationSlot } from '~/types'
const { paginator, stream } = defineProps<{
paginator: mastodon.Paginator<mastodon.v1.Notification[], mastodon.rest.v1.ListNotificationsParams>

View file

@ -1,8 +1,8 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
import type { DraftItem } from '~/types'
import { EditorContent } from '@tiptap/vue-3'
import stringLength from 'string-length'
import type { DraftItem } from '~/types'
const {
threadComposer,
@ -522,6 +522,7 @@ function stopQuestionMarkPropagation(e: KeyboardEvent) {
v-if="!threadIsActive || isFinalItemOfThread"
btn-solid rounded-3 text-sm w-full flex="~ gap1" items-center md:w-fit class="publish-button"
:aria-disabled="isPublishDisabled || isExceedingCharacterLimit" aria-describedby="publish-tooltip"
:disabled="isPublishDisabled || isExceedingCharacterLimit"
@click="publish"
>
<span v-if="isSending" block animate-spin preserve-3d>

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { formatTimeAgo } from '@vueuse/core'
import type { DraftItem } from '~/types'
import { formatTimeAgo } from '@vueuse/core'
const route = useRoute()
const { formatNumber } = useHumanReadableNumber()

View file

@ -30,7 +30,7 @@ const defaultSelectedNavButtonNames = computed<NavButtonName[]>(() =>
: ['explore', 'local', 'federated', 'moreMenu'],
)
const navButtonNamesSetting = useLocalStorage<NavButtonName[]>(STORAGE_KEY_BOTTOM_NAV_BUTTONS, defaultSelectedNavButtonNames.value)
const selectedNavButtonNames = ref<NavButtonName[]>([])
const selectedNavButtonNames = ref<NavButtonName[]>(navButtonNamesSetting.value)
const selectedNavButtons = computed<NavButton[]>(() =>
selectedNavButtonNames.value.map(name =>

View file

@ -1,10 +1,42 @@
<script setup lang="ts">
import type { ThemeColors } from '~/composables/settings'
import { THEME_COLORS } from '~/constants'
const themes = await import('~/constants/themes.json').then((r) => {
const map = new Map<'dark' | 'light', [string, ThemeColors][]>([['dark', []], ['light', []]])
const themes = r.default as [string, ThemeColors][]
for (const [key, theme] of themes) {
map.get('dark')!.push([key, theme])
map.get('light')!.push([key, {
...theme,
'--c-primary': `color-mix(in srgb, ${theme['--c-primary']}, black 25%)`,
}])
}
return map
})
const themes = await import('~/constants/themes.json').then(r => r.default) as [string, ThemeColors][]
const settings = useUserSettings()
const currentTheme = computed(() => settings.value.themeColors?.['--theme-color-name'] || themes[0][1]['--theme-color-name'])
const media = useMediaQuery('(prefers-color-scheme: dark)')
const colorMode = useColorMode()
const useThemes = shallowRef<[string, ThemeColors][]>([])
watch(() => colorMode.preference, (cm) => {
const dark = cm === 'dark' || (cm === 'system' && media.value)
const newThemes = dark ? themes.get('dark')! : themes.get('light')!
const key = settings.value.themeColors?.['--theme-color-name'] || THEME_COLORS.defaultTheme
for (const [k, theme] of newThemes) {
if (k === key) {
settings.value.themeColors = theme
break
}
}
useThemes.value = newThemes
}, { immediate: true, flush: 'post' })
const currentTheme = computed(() => settings.value.themeColors?.['--theme-color-name'] || THEME_COLORS.defaultTheme)
function updateTheme(theme: ThemeColors) {
settings.value.themeColors = theme
@ -18,10 +50,11 @@ function updateTheme(theme: ThemeColors) {
</h2>
<div flex="~ gap4 wrap" p2 role="group" aria-labelledby="interface-tc">
<button
v-for="[key, theme] in themes" :key="key"
v-for="[key, theme] in useThemes" :key="key"
:style="{
'background': key,
'--local-ring-color': key,
'--rgb-primary': theme['--rgb-primary'],
'background': theme['--c-primary'],
'--local-ring-color': theme['--c-primary'],
}"
type="button"
:class="currentTheme === theme['--theme-color-name'] ? 'ring-2' : 'scale-90'"

View file

@ -62,6 +62,7 @@ const timeago = useTimeAgo(() => status.value.createdAt, timeAgoOptions)
const isSelfReply = computed(() => status.value.inReplyToAccountId === status.value.account.id)
const collapseRebloggedBy = computed(() => rebloggedBy.value?.id === status.value.account.id)
const isDM = computed(() => status.value.visibility === 'direct')
const isPinned = computed(() => status.value.pinned)
const showUpperBorder = computed(() => props.newer && !directReply.value)
const showReplyTo = computed(() => !replyToMain.value && !directReply.value)
@ -75,6 +76,19 @@ const forceShow = ref(false)
<div :h="showUpperBorder ? '1px' : '0'" w-auto bg-border mb-1 z--1 />
<slot name="meta">
<!-- Pinned status -->
<div flex="~ col" justify-between>
<div
v-if="isPinned"
flex="~ gap2" items-center h-auto text-sm text-orange
m="is-5" p="t-1 is-5"
relative text-secondary ws-nowrap
>
<div i-ri:pushpin-line />
<span>{{ $t('status.pinned') }}</span>
</div>
</div>
<!-- Line connecting to previous status -->
<template v-if="status.inReplyToAccountId">
<StatusReplyingTo

View file

@ -1,10 +1,15 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
const paginator = useMastoClient().v1.timelines.public.list({ limit: 30, local: true })
const stream = useStreaming(client => client.public.local.subscribe())
function reorderAndFilter(items: mastodon.v1.Status[]) {
return reorderedTimeline(items, 'public')
}
</script>
<template>
<div>
<TimelinePaginator v-bind="{ paginator, stream }" context="public" />
<TimelinePaginator v-bind="{ paginator, stream }" :preprocess="reorderAndFilter" context="public" />
</div>
</template>

View file

@ -1,8 +1,8 @@
<script setup lang="ts">
import { getEmojiMatchesInText } from '@iconify/utils/lib/emoji/replace/find'
import { emojiFilename, emojiPrefix, emojiRegEx } from '~~/config/emojis'
import type { CommandHandler } from '~/composables/command'
import type { CustomEmoji, Emoji } from '~/composables/tiptap/suggestion'
import { getEmojiMatchesInText } from '@iconify/utils/lib/emoji/replace/find'
import { emojiFilename, emojiPrefix, emojiRegEx } from '~~/config/emojis'
import { isCustomEmoji } from '~/composables/tiptap/suggestion'
const { items, command } = defineProps<{

View file

@ -1,8 +1,8 @@
import type { LocaleObject } from '@nuxtjs/i18n'
import type { ComputedRef } from 'vue'
import type { SearchResult } from '~/composables/masto/search'
import Fuse from 'fuse.js'
import { defineStore } from 'pinia'
import type { SearchResult } from '~/composables/masto/search'
// @unocss-include

View file

@ -90,6 +90,11 @@ export function parseMastodonHTML(
inReplyToStatus,
} = options
// remove newline before Tags
html = html.replace(/\n(<[^>]+>)/g, (_1, raw) => {
return raw
})
if (markdown) {
// Handle code blocks
html = html

View file

@ -1,6 +1,6 @@
import type { mastodon } from 'masto'
import { STORAGE_KEY_FIRST_VISIT } from '~/constants'
import type { ConfirmDialogChoice, ConfirmDialogOptions, DraftItem, ErrorDialogData } from '~/types'
import { STORAGE_KEY_FIRST_VISIT } from '~/constants'
export const confirmDialogChoice = ref<ConfirmDialogChoice>()
export const confirmDialogLabel = ref<ConfirmDialogOptions>()

View file

@ -2,8 +2,8 @@ import type { Pausable } from '@vueuse/core'
import type { mastodon } from 'masto'
import type { Ref } from 'vue'
import type { ElkInstance } from '../users'
import { createRestAPIClient, createStreamingAPIClient } from 'masto'
import type { UserLogin } from '~/types'
import { createRestAPIClient, createStreamingAPIClient } from 'masto'
export function createMasto() {
return {

View file

@ -1,6 +1,6 @@
import type { DraftItem } from '~~/types'
import type { mastodon } from 'masto'
import type { Ref } from 'vue'
import type { DraftItem } from '~~/types'
import { fileOpen } from 'browser-fs-access'
export function usePublish(options: {

View file

@ -1,8 +1,8 @@
import type { mastodon } from 'masto'
import type { ComputedRef, Ref } from 'vue'
import { STORAGE_KEY_DRAFTS } from '~/constants'
import type { DraftItem, DraftMap } from '~/types'
import type { Mutable } from '~/types/utils'
import { STORAGE_KEY_DRAFTS } from '~/constants'
export const currentUserDrafts = (import.meta.server || process.test)
? computed<DraftMap>(() => ({}))

View file

@ -2,8 +2,10 @@ import type { MaybeRefOrGetter, RemovableRef } from '@vueuse/core'
import type { mastodon } from 'masto'
import type { EffectScope, Ref } from 'vue'
import type { ElkMasto } from './masto/masto'
import { withoutProtocol } from 'ufo'
import type { PushNotificationPolicy, PushNotificationRequest } from '~/composables/push-notifications/types'
import type { UserLogin } from '~/types'
import type { Overwrite } from '~/types/utils'
import { withoutProtocol } from 'ufo'
import {
DEFAULT_POST_CHARS_LIMIT,
STORAGE_KEY_CURRENT_USER_HANDLE,
@ -12,8 +14,6 @@ import {
STORAGE_KEY_NOTIFICATION_POLICY,
STORAGE_KEY_SERVERS,
} from '~/constants'
import type { UserLogin } from '~/types'
import type { Overwrite } from '~/types/utils'
const mock = process.mock

View file

@ -1,7 +1,7 @@
import type { EmojiRegexMatch } from '@iconify/utils/lib/emoji/replace/find'
import { getEmojiMatchesInText } from '@iconify/utils/lib/emoji/replace/find'
// @unimport-disabled
import { emojiFilename, emojiPrefix, emojiRegEx } from '@iconify-emoji/twemoji'
import { getEmojiMatchesInText } from '@iconify/utils/lib/emoji/replace/find'
// Re-export everything from package
export * from '@iconify-emoji/twemoji'

View file

@ -75,11 +75,13 @@ export const countryLocaleVariants: Record<string, (LocaleObjectData & { country
const locales: LocaleObjectData[] = [
{
// @ts-expect-error en used as placeholder
code: 'en',
file: 'en.json',
name: 'English',
},
({
// @ts-expect-error ar used as placeholder
code: 'ar',
file: 'ar.json',
name: 'العربية',
@ -110,6 +112,7 @@ const locales: LocaleObjectData[] = [
},
} satisfies LocaleObjectData),
{
// @ts-expect-error ca used as placeholder
code: 'ca',
file: 'ca.json',
name: 'Català',
@ -150,6 +153,7 @@ const locales: LocaleObjectData[] = [
name: 'Nederlands',
},
{
// @ts-expect-error es used as placeholder
code: 'es',
file: 'es.json',
name: 'Español',
@ -203,6 +207,7 @@ const locales: LocaleObjectData[] = [
},
},
{
// @ts-expect-error pt used as placeholder
code: 'pt',
file: 'pt.json',
name: 'Português',

View file

@ -31,6 +31,7 @@ export const HANDLED_MASTO_URLS = /^(https?:\/\/)?([\w\-]+\.)+\w+\/(@[@\w\-.]+)(
export const NOTIFICATION_FILTER_TYPES: mastodon.v1.NotificationType[] = ['status', 'reblog', 'follow', 'follow_request', 'favourite', 'poll', 'update', 'admin.sign_up', 'admin.report']
export const THEME_COLORS = {
defaultTheme: '#cc7d24',
themeDark: '#111111',
themeLight: '#fafafa',
backgroundDark: '#fafafa',

View file

@ -4,7 +4,7 @@ export default defineAppConfig({
description: 'A nimble Mastodon web client.',
image: 'https://docs.elk.zone/elk-screenshot.png',
socials: {
twitter: 'elk_zone',
// twitter: 'elk_zone',
github: 'elk-zone/elk',
mastodon: {
label: 'Mastodon',

View file

@ -1,8 +1,11 @@
export default defineNuxtConfig({
extends: '@nuxt-themes/docus',
vite: {
optimizeDeps: {
include: ['scule'],
},
},
compatibilityDate: '2024-11-07',
})

View file

@ -12,7 +12,7 @@
"theme-colors": "^0.1.0"
},
"devDependencies": {
"@nuxt-themes/docus": "^1.15.0",
"nuxt": "^3.13.2"
"@nuxt-themes/docus": "^1.15.1",
"nuxt": "^3.14.1592"
}
}

View file

@ -1,8 +1,17 @@
import { defineTheme } from 'pinceau'
import { getColors } from 'theme-colors'
const light = getColors('#995e1b')
const primary = Object
.entries(getColors('#d98018'))
.reduce((acc, [key, value]) => {
acc[key] = {
initial: light[key]!,
dark: value,
}
return acc
}, {} as Record<string | number, { initial: string, dark: string }>)
export default defineTheme({
color: {
primary: getColors('#d98018'),
},
color: { primary },
})

View file

@ -7,6 +7,8 @@
"route_loaded": "S'ha carregat la pàgina {0}"
},
"account": {
"authorize": "Autoritza seguiment",
"authorized": "Has autoritzat la sol·licitud",
"avatar_description": "Avatar de {0}",
"blocked_by": "Estàs bloquejat per aquest usuari.",
"blocked_domains": "Dominis bloquejats",
@ -25,6 +27,7 @@
"follows_you": "Et segueix",
"go_to_profile": "Vés al perfil",
"joined": "S'ha unit",
"lock": "Compte privat",
"moved_title": "ha indicat que el seu nou compte és ara:",
"muted_users": "Usuaris silenciats",
"muting": "Silenciat",
@ -35,26 +38,34 @@
"posts": "Publicacions",
"posts_count": "{0} Publicacions|{0} Publicació|{0} Publicacions",
"profile_description": "Capçalera del perfil de {0}",
"profile_personal_note": "Nota Personal",
"profile_unavailable": "Perfil no disponible",
"reject": "rebutja seguiment",
"rejected": "Has rebutjat la sol·licitud",
"request_follow": "Sol·licitud de seguiment",
"requested": "{0} ha sol·licitat seguir-te",
"unblock": "Desbloqueja",
"unfollow": "Deixa de seguir",
"unmute": "Activa el so",
"view_other_followers": "És possible que no es mostrin els seguidors d'altres instàncies.",
"view_other_following": "És possible que no es mostrin els seguits d'altres instàncies."
"view_other_following": "És possible que no es mostrin els seguits d'altres instàncies.",
"withdraw follow request": "Retira la sol·licitud de seguiment"
},
"action": {
"apply": "Aplicar",
"bookmark": "Afegir marcador",
"bookmarked": "Afegit a marcadors",
"boost": "Estímul",
"boost": "Impel·lir",
"boost_count": "{0}",
"boosted": "Potenciat",
"boosted": "Impel·lit",
"clear": "Esborrar",
"clear_publish_failed": "Esborra els errors de publicació",
"clear_save_failed": "Esborra els errors de desar",
"clear_upload_failed": "Esborra els errors de càrrega de fitxers",
"close": "Tanca",
"compose": "Redactar",
"confirm": "Confirmeu",
"done": "Fet",
"edit": "Edita",
"enter_app": "Entra a l'aplicació",
"favourite": "Favorit",
@ -62,8 +73,10 @@
"favourited": "Favorit",
"more": "Més",
"next": "Següent",
"open_image_preview_dialog": "Obrir diàleg de vista prèvia de la imatge",
"prev": "Anterior",
"publish": "Publicar",
"publish_thread": "Publicar fil",
"reply": "Respon",
"reply_count": "{0}",
"reset": "Restableix",
@ -98,6 +111,8 @@
"error": "ERROR",
"fetching": "S'està recuperant...",
"in": "en",
"no_bookmarks": "Encara no hi ha cap publicacions marcades",
"no_favourites": "Encara no hi ha cap publicacions preferides",
"not_found": "404 No trobat",
"offline_desc": "Sembla que estàs fora de línia. Comproveu la vostra connexió de xarxa."
},
@ -109,12 +124,14 @@
"block_account": {
"cancel": "Cancel·lar",
"confirm": "Bloc",
"description": "Confirmes que vols bloquejar {0}?"
"description": "Confirmes que vols bloquejar {0}?",
"title": "Bloquejar el compte"
},
"block_domain": {
"cancel": "Cancel·lar",
"confirm": "Bloc",
"description": "Confirmes que vols bloquejar {0}?"
"description": "Confirmes que vols bloquejar {0}?",
"title": "Bloquejar el domini"
},
"common": {
"cancel": "No",
@ -123,27 +140,37 @@
"delete_list": {
"cancel": "Cancel·lar",
"confirm": "Suprimeix",
"description": "Esteu segur que voleu suprimir la llista \"{0}\"?"
"description": "Confirmes que vols la llista \"{0}\"?",
"title": "Suprimir la llista"
},
"delete_posts": {
"cancel": "Cancel·lar",
"confirm": "Suprimeix",
"description": "Esteu segur que voleu suprimir aquesta publicació?"
"description": "Confirmes que vols suprimir aquesta publicació?",
"title": "Suprimir la publicació"
},
"mute_account": {
"cancel": "Cancel·lar",
"confirm": "Silenciar",
"description": "Confirmes que vols silenciar {0}?"
"confirm": "Silencia",
"days": "dies|dia|dies",
"description": "Confirmes que vols silenciar {0}?",
"hours": "hores|hora|hores",
"minute": "minuts|minut|minuts",
"notifications": "Silenciar notificacions",
"specify_duration": "Especificar la durada del silenciat",
"title": "Silenciar el compte"
},
"show_reblogs": {
"cancel": "Cancel·lar",
"confirm": "Espectacle",
"description": "Confirmes que vols mostrar els augments de {0}?"
"confirm": "Mostra",
"description": "Confirmes que vols mostrar els impulsos de {0}?",
"title": "Mostrar els impulsos"
},
"unfollow": {
"cancel": "Cancel·lar",
"confirm": "Deixa de seguir",
"description": "Estàs segur que vols deixar de seguir?"
"description": "Estàs segur que vols deixar de seguir?",
"title": "Deixar de seguir"
}
},
"conversation": {
@ -196,11 +223,49 @@
"error": "S'ha produït un error en crear la llista",
"error_prefix": "Error:",
"list_title_placeholder": "Títol de la llista",
"manage": "Gestionar les llistes",
"modify_account": "Modificar llistes amb compte",
"remove_account": "Elimina el compte de la llista",
"save": "Desa els canvis"
},
"magic_keys": {
"dialog_header": "Dreceres de teclat",
"groups": {
"actions": {
"boost": "Impel·lir",
"command_mode": "Mode de comanda",
"compose": "Redactar",
"favourite": "Preferit",
"search": "Cerca",
"show_new_items": "Mostrar noves publicacions",
"title": "Accions"
},
"media": {
"title": "Multimèdia"
},
"navigation": {
"go_to_bookmarks": "Marcadors",
"go_to_conversations": "Converses",
"go_to_explore": "Explorar",
"go_to_favourites": "Preferits",
"go_to_federated": "Federats",
"go_to_home": "Inici",
"go_to_lists": "Llistes",
"go_to_local": "Local",
"go_to_notifications": "Notificacions",
"go_to_profile": "Perfil",
"go_to_search": "Cercar",
"go_to_settings": "Configuració",
"next_status": "Estat següent",
"previous_status": "Estat anterior",
"shortcut_help": "Drecera d'ajuda",
"title": "Navegació"
}
},
"sequence_then": "seguit de"
},
"menu": {
"add_personal_note": "Afegeix una nota personal a {0}",
"block_account": "Bloqueja {0}",
"block_domain": "Bloqueja el domini {0}",
"copy_link_to_post": "Copia l'enllaç a aquesta publicació",
@ -209,15 +274,18 @@
"delete_and_redraft": "Esborra i torna a redactar",
"direct_message_account": "Missatge directe {0}",
"edit": "Edita",
"hide_reblogs": "Amaga els augments de {0}",
"hide_reblogs": "Amaga els impulsos de {0}",
"mention_account": "Esmenta {0}",
"mute_account": "Silencia {0}",
"mute_conversation": "Silencia aquesta publicació",
"open_in_original_site": "Obre al lloc original",
"pin_on_profile": "Fixa al perfil",
"remove_personal_note": "Elimina nota personal de {0}",
"report_account": "Denuncia {0}",
"share_account": "Comparteix {0}",
"share_post": "Comparteix aquesta publicació",
"show_favourited_and_boosted_by": "Mostra qui ha fet favorits i qui ha augmentat",
"show_reblogs": "Mostra els augments de {0}",
"show_favourited_and_boosted_by": "Mostra qui ha fet favorits i qui ha impel·lit",
"show_reblogs": "Mostra els impulsos de {0}",
"show_untranslated": "Mostra sense traduir",
"toggle_theme": {
"dark": "Canvia a mode fosc",
@ -226,10 +294,14 @@
"translate_post": "Tradueix",
"unblock_account": "Desbloqueja {0}",
"unblock_domain": "Desbloqueja el domini {0}",
"unfollow_account": "Deixa de seguir {0}",
"unmute_account": "Deixa de silenciar {0}",
"unmute_conversation": "Deixa de silenciar aquesta publicació",
"unpin_on_profile": "No fixis al perfil"
},
"modals": {
"aria_label_close": "Tanca"
},
"nav": {
"back": "Torna",
"blocked_domains": "Dominis bloquejats",
@ -238,13 +310,16 @@
"built_at": "Construït {0}",
"compose": "Redactar",
"conversations": "Converses",
"docs": "Documentació",
"explore": "Explora",
"favourites": "Preferits",
"federated": "Federat",
"hashtags": "Etiquetes",
"home": "Inici",
"list": "Llista",
"lists": "Llistes",
"local": "Local",
"more_menu": "Més opcions",
"muted_users": "Usuaris silenciats",
"notifications": "Notificacions",
"privacy": "Privadesa",
@ -264,6 +339,7 @@
"followed_you_count": "{0} persones us han seguit|{0} persones us han seguit|{0} persones us han seguit",
"missing_type": "FALTA notificació.tipus:",
"reblogged_post": "ha reblogjat la teva publicació",
"reported": "{0} va denunciar {1}",
"request_to_follow": "demanat que us segueixi",
"signed_up": "registrat",
"update_status": "ha actualitzat la seva publicació"
@ -274,10 +350,26 @@
"reply_to_account": "Respon a {0}",
"replying": "Responent"
},
"polls": {
"allow_multiple": "Permit múltiples opcions",
"cancel": "Cancel·lar",
"create": "Crear enquesta",
"disallow_multiple": "No permetre múltiples opcions",
"expiration": "Caducitat de l'enquesta",
"hide_votes": "Amaga el total de vots fins al final de l'enquesta",
"option_placeholder": "Opció de l'enquesta {current}/{max}",
"remove_option": "Elimina opció",
"settings": "Opcions de l'enquesta",
"show_votes": "Mostra sempre el total de vots"
},
"pwa": {
"dismiss": "Descartar",
"install": "Instal·lar",
"install_title": "Instal·leu Elk",
"install_title": "Instal·lar Elk",
"screenshots": {
"dark": "Captura de pantalla d'Elk funcionant en mode fosc",
"light": "Captura de pantalla d'Elk funcionant en mode clar"
},
"title": "Nova actualització d'Elk disponible!",
"update": "Actualització",
"update_available_short": "Actualitza Elk",
@ -304,6 +396,42 @@
}
}
},
"report": {
"additional_comments": "Comentaris addicionals",
"another_server": "L'usuari que estàs denunciant pertany a un altre servidor",
"anything_else": "Hi ha alguna cosa més que penses que hem de saber?",
"block_desc": "No podràs veure més publicacions d'aquest usuari. No podran veure les teves publicacions ni seguir-te. Els indicarà que estan bloquejats.",
"dontlike": "No m'agrada",
"dontlike_desc": "No és alguna cosa que vulguis veure",
"forward": "Sí, continuar amb la denúncia a {0}",
"forward_question": "Vols enviar també una còpia anònima d'aquesta denúncia a aquest servidor?",
"further_actions": {
"limit": {
"description": "Aquestes són les opcions per controlar allò que veus:",
"title": "No vols veure això?"
},
"report": {
"description": "Mentre ho revisem, aquestes són les accions que pots prendre:",
"title": "Gràcies per denunciar-ho, ho investigarem."
}
},
"limiting": "Limitar a {0}",
"mute_desc": "No podràs veure més publicacions d'aquest usuari. Encara poden seguir-te i veure les teves publicacions. No sabran que estan silenciats.",
"other": "És una altra cosa",
"other_desc": "La novetat no encaixa en altres categories.",
"reporting": "Denunciant a {0}",
"select_many": "Selecciona totes les que corresponguin:",
"select_one": "Selecciona l'opció més adequada:",
"select_posts": "Hi ha alguna publicació de suport per a aquesta denúncia?",
"select_posts_other": "Hi ha alguna altra publicació de suport per a aquesta denúncia?",
"spam": "És spam",
"spam_desc": "Enllaços maliciosos, interaccions falses o respostes repetitives",
"submit": "Enviar denúncia",
"unfollow_desc": "Ja no veuràs les publicacions d'aquest usuari a la teva línia de temps. És possible que encara vegis les seves publicacions en altres llocs.",
"violation": "Viola una o més regles del servidor.",
"whats_wrong_account": "Explica'ns què passa amb aquest compte",
"whats_wrong_post": "Explica'ns què passa amb aquesta publicació."
},
"search": {
"search_desc": "Cerca persones i hashtags",
"search_empty": "No s'ha pogut trobar res per a aquests termes de cerca"
@ -326,6 +454,8 @@
"label": "Configuració del compte"
},
"interface": {
"bottom_nav": "Navegació inferior",
"bottom_nav_instructions": "Tria els teus botons de navegació preferits, fins a cinc per a la navegació inferior. Has d'incloure el botó \"Més opcions\".",
"color_mode": "Mode de color",
"dark_mode": "Fosc",
"default": "(per defecte)",
@ -337,7 +467,9 @@
},
"language": {
"display_language": "Idioma de visualització",
"how_to_contribute": "Com puc contribuir?",
"label": "Idioma",
"post_language": "Idioma de publicació",
"status": "Estat de la traducció: {0}/{1} ({2}%)",
"translations": {
"add": "Afegeix",
@ -403,26 +535,38 @@
},
"notifications_settings": "Notificacions",
"preferences": {
"embedded_media": "Reproductor multimèdia incrustat",
"embedded_media_description": "Mostrar un reproductor incrustat en lloc de la targeta de vista prèvia normal en expandir enllaços compartits de transmissió multimèdia.",
"enable_autoplay": "Activa la reproducció automàtica",
"enable_data_saving": "Activa l'emmagatzematge de dades",
"enable_data_saving_description": "Deseu les dades evitant que els fitxers adjunts es carreguin automàticament.",
"enable_pinch_to_zoom": "Activa el pessic per fer zoom",
"github_cards": "Targetes GitHub",
"github_cards_description": "Quan es publica un enllaç de GitHub, es mostra una targeta HTML accessible que utilitza el meta del gràfic social en lloc de la imatge social.",
"grayscale_mode": "Mode d'escala de grisos",
"hide_account_hover_card": "Amaga la targeta de desplaçament del compte",
"hide_alt_indi_on_posts": "Amaga l'indicador alt a les publicacions",
"hide_boost_count": "Amaga el recompte d'impulsos",
"hide_favorite_count": "Amaga el recompte de favorits",
"hide_follower_count": "Amaga el recompte de seguidors",
"hide_gif_indi_on_posts": "Amaga l'indicador de gif a les publicacions",
"hide_news": "Amaga les notícies",
"hide_reply_count": "Amaga el recompte de respostes",
"hide_tag_hover_card": "Amaga la targeta flotant de l'etiqueta",
"hide_translation": "Amaga la traducció",
"hide_username_emojis": "Amaga els emojis del nom d'usuari",
"hide_username_emojis_description": "Amaga els emojis dels noms d'usuari a les línies de temps. Els emojis encara seran visibles als seus perfils.",
"label": "Preferències",
"optimize_for_low_performance_device": "Optimitza per a dispositius de baix rendiment",
"title": "Característiques experimentals",
"use_star_favorite_icon": "Utiliza l'icona d'estrella per a favorits",
"user_picker": "Selector d'usuaris",
"user_picker_description": "Mostra tots els avatars dels comptes registrats a la part inferior esquerra perquè puguis canviar ràpidament entre ells.",
"virtual_scroll": "Desplaçament virtual",
"wellbeing": "Benestar"
"virtual_scroll_description": "Utilitza una llista virtual a les línies de temps, de manera que es pugui representar una quantitat més gran d'elements de manera eficaç.",
"wellbeing": "Benestar",
"zen_mode": "Mode Zen",
"zen_mode_description": "Amaga les seccions laterals de la interfície tret que el cursor del ratolí estigui sobre elles. També amaga alguns elements de les línies de temps."
},
"profile": {
"appearance": {
@ -457,21 +601,32 @@
},
"state": {
"attachments_exceed_server_limit": "El nombre de fitxers adjunts ha superat el límit per missatge.",
"attachments_limit_audio_error": "S'ha superat la mida màxima d'àudio: {0}",
"attachments_limit_error": "S'ha superat el límit per publicació",
"attachments_limit_image_error": "S'ha superat la mida màxima d'imatge: {0}",
"attachments_limit_unknown_error": "S'ha superat la mida màxima de fitxer: {0}",
"attachments_limit_video_error": "S'ha superat la mida màxima de vídeo: {0}",
"edited": "(Edit)",
"editing": "Edició",
"loading": "Carregant...",
"publish_failed": "No s'ha pogut publicar",
"publishing": "Publicació",
"save_failed": "Error de desar",
"upload_failed": "La càrrega ha fallat",
"uploading": "S'està carregant..."
},
"status": {
"account": {
"suspended_message": "El compte d'aquest missatge ha estat suspès.",
"suspended_show": "Mostrar contingut igualment?"
},
"boosted_by": "Impulsat per",
"edited": "Editat {0}",
"embedded_warning": "Reproduir això pot revelar la teva adreça IP a altres persones.",
"favourited_by": "Favorit per",
"filter_hidden_phrase": "Filtrat per",
"filter_show_anyway": "Mostra de totes maneres",
"gif": "GIF",
"img_alt": {
"ALT": "ALT",
"desc": "Descripció",
@ -486,6 +641,7 @@
"replying_to": "Responent a {0}",
"show_full_thread": "Mostra el fil complet",
"someone": "algú",
"spoiler_media_hidden": "Multimèdia amagada",
"spoiler_show_less": "Mostra menys",
"spoiler_show_more": "Mostra més",
"thread": "Fil",
@ -502,10 +658,22 @@
"list": "Llista",
"media": "Mitjans de comunicació",
"news": "Notícies",
"notifications_all": "Tots",
"notifications_mention": "Esmenta",
"notifications_admin": {
"report": "Informes",
"sign_up": "Inscripcions"
},
"notifications_all": "Totes",
"notifications_favourite": "Preferides",
"notifications_follow": "T'han seguit",
"notifications_follow_request": "Sol·licituds de seguiment",
"notifications_mention": "Esmentes",
"notifications_more_tooltip": "Filtrar notificacions per tipus",
"notifications_poll": "Enquestes",
"notifications_reblog": "Impulsos",
"notifications_status": "Publicacions",
"notifications_update": "Actualitzacions",
"posts": "Publicacions",
"posts_with_replies": "Publicacions"
"posts_with_replies": "Publicacions i respostes"
},
"tag": {
"follow": "Seguiu",
@ -557,6 +725,7 @@
"add_emojis": "Afegeix emojis",
"add_media": "Afegiu imatges, un vídeo o un fitxer d'àudio",
"add_publishable_content": "Afegeix contingut per publicar",
"add_thread_item": "Afegeix publicació al fil",
"change_content_visibility": "Canvia la visibilitat del contingut",
"change_language": "Canviar d'idioma",
"emoji": "Emoji",
@ -566,6 +735,8 @@
"open_editor_tools": "Eines de l'editor",
"pick_an_icon": "Trieu una icona",
"publish_failed": "Tanca els missatges fallits a la part superior de l'editor per tornar a publicar publicacions",
"remove_thread_item": "Elimina publicació del fil",
"start_thread": "Comença un fil",
"toggle_bold": "Commuta la negreta",
"toggle_code_block": "Commuta el bloc de codi",
"toggle_italic": "Canvia la cursiva"

View file

@ -633,6 +633,7 @@
"dismiss": "Dismiss",
"read": "Read {0} description"
},
"pinned": "Pinned post",
"poll": {
"count": "{0} votes|{0} vote|{0} votes",
"ends": "ends {0}",

View file

@ -49,7 +49,7 @@
"unmute": "Dejar de silenciar",
"view_other_followers": "Puede que no se muestren los seguidores de otras instancias.",
"view_other_following": "Puede que no se muestren los seguidos de otras instancias.",
"withdraw_follow_request": "Retirar solitud de seguimiento"
"withdraw_follow_request": "Retirar solicitud de seguimiento"
},
"action": {
"apply": "Aplicar",

View file

@ -713,6 +713,7 @@
"year_past": "0 évvel ezelőtt|múlt évben|{n} évvel ezelőtt"
},
"timeline": {
"no_posts": "Itt nincsenek bejegyzések.",
"show_new_items": "{v} új elem megjelenítése|{v} új elem megjelenítése|{v} új elem megjelenítése",
"view_older_posts": "Előfordulhat, hogy más instancekről származó régebbi bejegyzések nem jelennek meg."
},

View file

@ -1,6 +1,6 @@
import type { BuildInfo } from '~/types'
import { createResolver, defineNuxtModule } from '@nuxt/kit'
import { isCI } from 'std-env'
import type { BuildInfo } from '~/types'
import { getEnv, version } from '../config/env'
const { resolve } = createResolver(import.meta.url)

View file

@ -2,6 +2,9 @@
publish = "dist"
command = "pnpm run build"
[build.environment]
NODE_OPTIONS = '--max-old-space-size=4096'
# Redirect to Discord server
[[redirects]]
from = "https://chat.elk.zone"

View file

@ -311,9 +311,16 @@ export default defineNuxtConfig({
lazy: true,
strategy: 'no_prefix',
detectBrowserLanguage: false,
langDir: 'locales',
// relative to i18n dir on rootDir: not yet v4 compat layout
langDir: '../locales',
defaultLocale: 'en-US',
experimental: {
generatedLocaleFilePathFormat: 'relative',
},
vueI18n: './config/i18n.config.ts',
bundle: {
optimizeTranslationDirective: false,
},
},
pwa,
staleDep: {

View file

@ -2,7 +2,7 @@
"name": "@elk-zone/elk",
"type": "module",
"version": "0.15.1",
"packageManager": "pnpm@9.12.3",
"packageManager": "pnpm@9.15.0",
"license": "MIT",
"homepage": "https://elk.zone/",
"main": "./nuxt.config.ts",
@ -16,6 +16,7 @@
"start": "PORT=5314 node .output/server/index.mjs",
"start:https": "PORT=5314 node ./https-dev-config/local-https-server.mjs",
"lint": "eslint --cache .",
"lint:fix": "eslint --cache --fix .",
"typecheck": "stale-dep && nuxi typecheck",
"prepare": "ignore-dependency-scripts \"tsx scripts/prepare.ts\"",
"generate": "nuxi generate",
@ -40,8 +41,8 @@
"@nuxt/devtools": "^1.5.2",
"@nuxt/test-utils": "^3.14.3",
"@nuxtjs/color-mode": "^3.4.4",
"@nuxtjs/i18n": "^8.5.3",
"@pinia/nuxt": "^0.5.4",
"@nuxtjs/i18n": "^9.1.1",
"@pinia/nuxt": "^0.9.0",
"@tiptap/core": "2.2.4",
"@tiptap/extension-bold": "2.2.4",
"@tiptap/extension-character-count": "2.2.4",
@ -56,9 +57,9 @@
"@tiptap/starter-kit": "2.2.4",
"@tiptap/suggestion": "2.2.4",
"@tiptap/vue-3": "2.2.4",
"@unocss/nuxt": "^0.63.6",
"@unocss/nuxt": "^0.65.0",
"@upstash/redis": "^1.27.1",
"@vercel/kv": "^2.0.0",
"@vercel/kv": "^3.0.0",
"@vue-macros/nuxt": "^1.11.12",
"@vueuse/core": "^11.0.3",
"@vueuse/gesture": "^2.0.0",
@ -85,15 +86,15 @@
"lru-cache": "^11.0.0",
"masto": "^6.10.1",
"node-emoji": "^2.1.3",
"nuxt-security": "^1.4.3",
"nuxt-security": "^2.0.0",
"page-lifecycle": "^0.1.2",
"pinia": "^2.2.2",
"postcss-nested": "^6.0.1",
"prosemirror-highlight": "^0.9.0",
"pinia": "^2.2.6",
"postcss-nested": "^7.0.0",
"prosemirror-highlight": "^0.11.0",
"rollup-plugin-node-polyfills": "^0.2.1",
"shiki": "^1.0.0",
"shiki": "^1.22.2",
"simple-git": "^3.19.1",
"slimeform": "^0.9.1",
"slimeform": "^0.10.0",
"stale-dep": "^0.7.0",
"std-env": "^3.7.0",
"string-length": "^5.0.1",
@ -105,7 +106,7 @@
"ufo": "^1.5.3",
"ultrahtml": "^1.5.3",
"unimport": "^3.10.0",
"vite-plugin-pwa": "^0.20.5",
"vite-plugin-pwa": "^0.21.0",
"vue-advanced-cropper": "^2.8.9",
"vue-virtual-scroller": "2.0.0-beta.8",
"workbox-build": "^7.1.1",
@ -113,48 +114,45 @@
"ws": "^8.15.1"
},
"devDependencies": {
"@antfu/eslint-config": "^3.7.3",
"@antfu/ni": "^0.23.0",
"@antfu/eslint-config": "^3.12.0",
"@antfu/ni": "^0.23.1",
"@nuxt/schema": "^3.14.1592",
"@types/chroma-js": "^2.4.4",
"@types/file-saver": "^2.0.7",
"@types/fnando__sparkline": "^0.3.7",
"@types/fs-extra": "^11.0.4",
"@types/js-yaml": "^4.0.9",
"@types/wicg-file-system-access": "^2023.10.5",
"@types/ws": "^8.5.12",
"@unlazy/nuxt": "^0.11.3",
"@unocss/eslint-config": "^0.63.6",
"@types/ws": "^8.5.13",
"@unlazy/nuxt": "^0.12.1",
"@unocss/eslint-config": "^0.65.1",
"@vue/test-utils": "2.4.6",
"bumpp": "^9.7.1",
"bumpp": "^9.9.1",
"consola": "^3.2.3",
"eslint": "^9.12.0",
"eslint-plugin-format": "^0.1.2",
"eslint": "^9.17.0",
"eslint-plugin-format": "^0.1.3",
"flat": "^6.0.1",
"fs-extra": "^11.2.0",
"lint-staged": "^15.2.10",
"nuxt": "^3.13.2",
"prettier": "^3.3.3",
"lint-staged": "^15.2.11",
"nuxt": "^3.14.1592",
"prettier": "^3.4.2",
"sharp": "^0.33.5",
"sharp-ico": "^0.1.5",
"simple-git-hooks": "^2.11.1",
"tsx": "^4.19.2",
"typescript": "^5.4.4",
"vitest": "2.1.3",
"vitest": "2.1.8",
"vue-tsc": "^2.1.6"
},
"pnpm": {
"patchedDependencies": {
"@vueuse/motion": "patches/@vueuse__motion.patch",
"pinceau": "patches/pinceau.patch",
"vue-i18n": "patches/vue-i18n.patch",
"nuxt-security": "patches/nuxt-security.patch",
"nuxt": "patches/nuxt.patch"
"pinceau": "patches/pinceau.patch"
}
},
"resolutions": {
"nuxt-component-meta": "0.8.2",
"unstorage": "^1.12.0",
"vitest": "2.1.3",
"nuxt-component-meta": "0.9.0",
"unstorage": "^1.13.1",
"vitest": "2.1.8",
"vue": "^3.5.4"
},
"simple-git-hooks": {

View file

@ -10,11 +10,21 @@ const { t } = useI18n()
const account = await fetchAccountByHandle(handle.value)
// we need to ensure `pinned === true` on status
// because this prop is appeared only on current account's posts
function applyPinned(statuses: mastodon.v1.Status[]) {
return statuses.map((status) => {
status.pinned = true
return status
})
}
function reorderAndFilter(items: mastodon.v1.Status[]) {
return reorderedTimeline(items, 'account')
}
const paginator = useMastoClient().v1.accounts.$select(account.id).statuses.list({ limit: 30, excludeReplies: true })
const pinnedPaginator = useMastoClient().v1.accounts.$select(account.id).statuses.list({ pinned: true })
const postPaginator = useMastoClient().v1.accounts.$select(account.id).statuses.list({ limit: 30, excludeReplies: true })
if (account) {
useHydratedHead({
@ -26,6 +36,9 @@ if (account) {
<template>
<div>
<AccountTabs />
<TimelinePaginator :paginator="paginator" :preprocess="reorderAndFilter" context="account" :account="account" />
<TimelinePaginator :paginator="pinnedPaginator" :preprocess="applyPinned" context="account" :account="account" :end-message="false" />
<!-- Upper border -->
<div h="1px" w-auto bg-border mb-1 />
<TimelinePaginator :paginator="postPaginator" :preprocess="reorderAndFilter" context="account" :account="account" />
</div>
</template>

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
import { NOTIFICATION_FILTER_TYPES } from '~/constants'
import type { CommonRouteTabMoreOption, CommonRouteTabOption } from '~/types'
import { NOTIFICATION_FILTER_TYPES } from '~/constants'
definePageMeta({
middleware: 'auth',

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
/* eslint-disable no-alert */
import { fileOpen } from 'browser-fs-access'
import type { UserLogin } from '~/types'
import { fileOpen } from 'browser-fs-access'
const { t } = useI18n()

View file

@ -1,39 +0,0 @@
diff --git a/dist/shared/motion.6686175e.d.cts b/dist/shared/motion.6686175e.d.cts
index d118e42ad332997dd203623a8a2bd5109ecdd04f..6ec3bad16555148ec11461e90e2a68116bf1d887 100644
--- a/dist/shared/motion.6686175e.d.cts
+++ b/dist/shared/motion.6686175e.d.cts
@@ -594,7 +594,7 @@ interface SpringControls {
values: MotionProperties;
}
type MotionInstanceBindings<T extends string, V extends MotionVariants<T>> = Record<string, MotionInstance<T, V>>;
-declare module '@vue/runtime-core' {
+declare module 'vue' {
interface ComponentCustomProperties {
$motions?: MotionInstanceBindings<any, any>;
}
diff --git a/dist/shared/motion.6686175e.d.mts b/dist/shared/motion.6686175e.d.mts
index d118e42ad332997dd203623a8a2bd5109ecdd04f..6ec3bad16555148ec11461e90e2a68116bf1d887 100644
--- a/dist/shared/motion.6686175e.d.mts
+++ b/dist/shared/motion.6686175e.d.mts
@@ -594,7 +594,7 @@ interface SpringControls {
values: MotionProperties;
}
type MotionInstanceBindings<T extends string, V extends MotionVariants<T>> = Record<string, MotionInstance<T, V>>;
-declare module '@vue/runtime-core' {
+declare module 'vue' {
interface ComponentCustomProperties {
$motions?: MotionInstanceBindings<any, any>;
}
diff --git a/dist/shared/motion.6686175e.d.ts b/dist/shared/motion.6686175e.d.ts
index d118e42ad332997dd203623a8a2bd5109ecdd04f..6ec3bad16555148ec11461e90e2a68116bf1d887 100644
--- a/dist/shared/motion.6686175e.d.ts
+++ b/dist/shared/motion.6686175e.d.ts
@@ -594,7 +594,7 @@ interface SpringControls {
values: MotionProperties;
}
type MotionInstanceBindings<T extends string, V extends MotionVariants<T>> = Record<string, MotionInstance<T, V>>;
-declare module '@vue/runtime-core' {
+declare module 'vue' {
interface ComponentCustomProperties {
$motions?: MotionInstanceBindings<any, any>;
}

View file

@ -1,11 +0,0 @@
diff --git a/dist/runtime/nitro/plugins/40-preprocessHtml.mjs b/dist/runtime/nitro/plugins/40-preprocessHtml.mjs
index ce2df85b5951ac48adb0f3ecbfa9daaff5e4d6cf..cb47bd2858e46f141e75e6ebccf44079687968b7 100644
--- a/dist/runtime/nitro/plugins/40-preprocessHtml.mjs
+++ b/dist/runtime/nitro/plugins/40-preprocessHtml.mjs
@@ -1,5 +1,5 @@
import { defineNitroPlugin } from "#imports";
-import * as cheerio from "cheerio/lib/slim";
+import * as cheerio from "cheerio/slim";
import { resolveSecurityRules } from "../utils/index.mjs";
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook("render:html", (html, { event }) => {

View file

@ -1,12 +0,0 @@
diff --git a/dist/index.mjs b/dist/index.mjs
index a5c61adc0c21b5df5b1a3ffcf81d2469c7f96873..9241cf294b398b43a4a5555f39746a6c844d0b0f 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -2183,6 +2183,7 @@ function createTransformPlugin(nuxt, getComponents, mode) {
}
return createUnplugin(() => ({
name: "nuxt:components:imports",
+ enforce: "post",
transformInclude(id) {
id = normalize(id);
return id.startsWith("virtual:") || id.startsWith("\0virtual:") || id.startsWith(nuxt.options.buildDir) || !isIgnored(id);

View file

@ -1,13 +0,0 @@
diff --git a/dist/vue-i18n.d.ts b/dist/vue-i18n.d.ts
index 8d5c4fc0e551ab3beccfcaa67764818a2c4c6756..0cee95f214e03add239d3df5e91ad2a8a154ac1e 100644
--- a/dist/vue-i18n.d.ts
+++ b/dist/vue-i18n.d.ts
@@ -3125,7 +3125,7 @@ export declare type WarnHtmlInMessageLevel = 'off' | 'warn' | 'error';
export { }
-declare module '@vue/runtime-core' {
+declare module 'vue' {
/**
* Component Custom Options for Vue I18n
*

View file

@ -1,6 +1,6 @@
import type { UserLogin } from '~/types'
import { useAsyncIDBKeyval } from '~/composables/idb'
import { STORAGE_KEY_USERS } from '~/constants'
import type { UserLogin } from '~/types'
const mock = process.mock

View file

@ -1,3 +1,5 @@
import type { Locale } from '#i18n'
export default defineNuxtPlugin(async (nuxt) => {
const t = nuxt.vueApp.config.globalProperties.$t
const d = nuxt.vueApp.config.globalProperties.$d
@ -11,14 +13,14 @@ export default defineNuxtPlugin(async (nuxt) => {
const i18n = useNuxtApp().$i18n
const { setLocale, locales } = i18n
const userSettings = useUserSettings()
const lang = computed(() => userSettings.value.language)
const lang = computed(() => userSettings.value.language as Locale)
const supportLanguages = unref(locales).map(locale => locale.code)
if (!supportLanguages.includes(lang.value))
userSettings.value.language = getDefaultLanguage(supportLanguages)
if (lang.value !== i18n.locale)
await setLocale(userSettings.value.language)
await setLocale(userSettings.value.language as Locale)
watch([lang, isHydrated], () => {
if (isHydrated.value && lang.value !== i18n.locale)

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
import chroma from 'chroma-js'
import type { ThemeColors } from '~/composables/settings'
import chroma from 'chroma-js'
// #cc7d24 -> hcl(67.14,62.19,59.56)
export const themesColor = Array.from(

View file

@ -1,9 +1,9 @@
import type { LocaleEntry } from '../docs/types'
import type { ElkTranslationStatus } from '~/types/translation-status'
import { Buffer } from 'node:buffer'
import { readFile, writeFile } from 'node:fs/promises'
import { createResolver } from '@nuxt/kit'
import { flatten } from 'flat'
import type { ElkTranslationStatus } from '~/types/translation-status'
import { countryLocaleVariants, currentLocales } from '../config/i18n'
export const localeData: [code: string, file: string[], title: string][]

View file

@ -1,5 +1,7 @@
import { stringifyQuery } from 'ufo'
import { defaultUserAgent } from '~/server/utils/shared'
export default defineEventHandler(async (event) => {
let { server, origin } = getRouterParams(event)
server = server.toLocaleLowerCase().trim()
@ -24,6 +26,9 @@ export default defineEventHandler(async (event) => {
try {
const result: any = await $fetch(`https://${server}/oauth/token`, {
method: 'POST',
headers: {
'user-agent': defaultUserAgent,
},
body: {
client_id: app.client_id,
client_secret: app.client_secret,

View file

@ -1,20 +1,21 @@
import type { AppInfo } from '~/types'
// @ts-expect-error virtual import
import { env } from '#build-info'
// @ts-expect-error virtual import
import { driver } from '#storage-config'
import { $fetch } from 'ofetch'
import kv from 'unstorage/drivers/cloudflare-kv-http'
import fs from 'unstorage/drivers/fs'
import memory from 'unstorage/drivers/memory'
import vercelKVDriver from 'unstorage/drivers/vercel-kv'
import cached from '../cache-driver'
// @ts-expect-error virtual import
import { env } from '#build-info'
// @ts-expect-error virtual import
import { driver } from '#storage-config'
import { version } from '~/config/env'
import { APP_NAME } from '~/constants'
import type { AppInfo } from '~/types'
import cached from '../cache-driver'
const storage = useStorage<AppInfo>()
@ -48,9 +49,14 @@ export function getRedirectURI(origin: string, server: string) {
return `${origin}/api/${server}/oauth/${encodeURIComponent(origin)}`
}
export const defaultUserAgent = `${APP_NAME}/${version}`
async function fetchAppInfo(origin: string, server: string) {
const app: AppInfo = await $fetch(`https://${server}/api/v1/apps`, {
method: 'POST',
headers: {
'user-agent': defaultUserAgent,
},
body: {
client_name: APP_NAME + (env !== 'release' ? ` (${env})` : ''),
website: 'https://elk.zone',

View file

@ -1,7 +1,7 @@
import type { ContentParseOptions } from '~/composables/content-parse'
import { format } from 'prettier'
import { render as renderTree } from 'ultrahtml'
import { describe, expect, it } from 'vitest'
import type { ContentParseOptions } from '~/composables/content-parse'
describe('html-parse', () => {
it('empty', async () => {