Merge branch 'pull-v0.13.2'
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful

This commit is contained in:
nikurasu 2024-04-07 14:35:26 +02:00
commit b217b4dc25
Signed by: Nikurasu
GPG key ID: 9E7F14C03EF1F271
64 changed files with 3237 additions and 2590 deletions

View file

@ -32,6 +32,7 @@ jobs:
- name: 🧪 Test project - name: 🧪 Test project
run: pnpm test:ci run: pnpm test:ci
timeout-minutes: 10
- name: 📝 Lint - name: 📝 Lint
run: pnpm lint run: pnpm lint

View file

@ -38,7 +38,7 @@ jobs:
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
platforms: linux/amd64 platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.metal.outputs.tags }} tags: ${{ steps.metal.outputs.tags }}
labels: ${{ steps.metal.outputs.labels }} labels: ${{ steps.metal.outputs.labels }}

View file

@ -83,7 +83,7 @@ Simple approach used by most websites of relying on direction set in HTML elemen
We've added some `UnoCSS` utilities styles to help you with that: We've added some `UnoCSS` utilities styles to help you with that:
- Do not use `left/right` padding and margin: for example `pl-1`. Use `padding-inline-start/end` instead. So `pl-1` should be `ps-1`, `pr-1` should be `pe-1`. The same rules apply to margin. - Do not use `left/right` padding and margin: for example `pl-1`. Use `padding-inline-start/end` instead. So `pl-1` should be `ps-1`, `pr-1` should be `pe-1`. The same rules apply to margin.
- Do not use `rtl-` classes, such as `rtl-left-0`. - Do not use `rtl-` classes, such as `rtl-left-0`.
- For icons that should be rotated for RTL, add `class="rtl-flip"`. This can only be used for icons outside of elements with `dir="auto"`, such as timeline, and is the only exception from the rule above. For icons inside the timeline, it might not work as expected. - For icons that should be rotated for RTL, add `class="rtl-flip"`. This can only be used for icons outside of elements with `dir="auto"`, such as timeline, and is the only exception to the rule above. For icons inside the timeline, it might not work as expected.
- For absolute positioned elements, don't use `left/right`: for example `left-0`. Use `inset-inline-start/end` instead. `UnoCSS` shortcuts are `inset-is` for `inset-inline-start` and `inset-ie` for `inset-inline-end`. Example: `left-0` should be replaced with `inset-is-0`. - For absolute positioned elements, don't use `left/right`: for example `left-0`. Use `inset-inline-start/end` instead. `UnoCSS` shortcuts are `inset-is` for `inset-inline-start` and `inset-ie` for `inset-inline-end`. Example: `left-0` should be replaced with `inset-is-0`.
- If you need to change the border radius for an entire left or right side, use `border-inline-start/end`. `UnoCSS` shortcuts are `rounded-is` for left side, `rounded-ie` for right side. Example: `rounded-l-5` should be replaced with `rounded-ie-5`. - If you need to change the border radius for an entire left or right side, use `border-inline-start/end`. `UnoCSS` shortcuts are `rounded-is` for left side, `rounded-ie` for right side. Example: `rounded-l-5` should be replaced with `rounded-ie-5`.
- If you need to change the border radius for one corner, use `border-start-end-radius` and similar rules. `UnoCSS` shortcuts are `rounded` + top/bottom as either `-bs` (top) or `-be` (bottom) + left/right as either `-is` (left) or `-ie` (right). Example: `rounded-tl-0` should be replaced with `rounded-bs-is-0`. - If you need to change the border radius for one corner, use `border-start-end-radius` and similar rules. `UnoCSS` shortcuts are `rounded` + top/bottom as either `-bs` (top) or `-be` (bottom) + left/right as either `-is` (left) or `-ie` (right). Example: `rounded-tl-0` should be replaced with `rounded-bs-is-0`.

View file

@ -8,6 +8,7 @@ import type { UnwrapRef } from 'vue'
const { const {
paginator, paginator,
stream, stream,
eventType,
keyProp = 'id', keyProp = 'id',
virtualScroller = false, virtualScroller = false,
preprocess, preprocess,
@ -17,6 +18,7 @@ const {
keyProp?: keyof T keyProp?: keyof T
virtualScroller?: boolean virtualScroller?: boolean
stream?: mastodon.streaming.Subscription stream?: mastodon.streaming.Subscription
eventType?: 'update' | 'notification'
preprocess?: (items: (U | T)[]) => U[] preprocess?: (items: (U | T)[]) => U[]
endMessage?: boolean | string endMessage?: boolean | string
}>() }>()
@ -44,7 +46,7 @@ defineSlots<{
const { t } = useI18n() const { t } = useI18n()
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
const { items, prevItems, update, state, endAnchor, error } = usePaginator(paginator, toRef(() => stream), preprocess) const { items, prevItems, update, state, endAnchor, error } = usePaginator(paginator, toRef(() => stream), eventType, preprocess)
nuxtApp.hook('elk-logo:click', () => { nuxtApp.hook('elk-logo:click', () => {
update() update()

View file

@ -19,8 +19,8 @@ const tabs = computed(() => {
}) })
}) })
function toValidName(otpion: string) { function toValidName(option: string) {
return otpion.toLowerCase().replace(/[^a-zA-Z0-9]/g, '-') return option.toLowerCase().replace(/[^a-zA-Z0-9]/g, '-')
} }
useCommands(() => command useCommands(() => command

View file

@ -30,10 +30,12 @@ const vAutoFocus = (el: HTMLElement) => el.focus()
</NuxtLink> </NuxtLink>
{{ $t('help.desc_para6') }} {{ $t('help.desc_para6') }}
</p> </p>
<NuxtLink hover:text-primary href="https://github.com/sponsors/elk-zone" target="_blank">
{{ $t('help.desc_para3') }} {{ $t('help.desc_para3') }}
<p flex="~ gap-2 wrap" mxa> </NuxtLink>
<p flex="~ gap-2 wrap justify-center" mxa>
<template v-for="team of elkTeamMembers" :key="team.github"> <template v-for="team of elkTeamMembers" :key="team.github">
<NuxtLink :href="`https://github.com/sponsors/${team.github}`" target="_blank" external rounded-full transition duration-300 border="~ transparent" hover="scale-105 border-primary"> <NuxtLink :href="team.link" target="_blank" external rounded-full transition duration-300 border="~ transparent" hover="scale-105 border-primary">
<img :src="`/avatars/${team.github}-100x100.png`" :alt="team.display" rounded-full w-15 h-15 height="60" width="60"> <img :src="`/avatars/${team.github}-100x100.png`" :alt="team.display" rounded-full w-15 h-15 height="60" width="60">
</NuxtLink> </NuxtLink>
</template> </template>

View file

@ -37,7 +37,7 @@ onUnmounted(() => locked.value = false)
</script> </script>
<template> <template>
<div relative h-full w-full flex pt-12 w-100vh @click="onClick"> <div relative h-full w-full flex pt-12 @click="onClick">
<button <button
v-if="hasNext" pointer-events-auto btn-action-icon bg="black/20" :aria-label="$t('action.previous')" v-if="hasNext" pointer-events-auto btn-action-icon bg="black/20" :aria-label="$t('action.previous')"
hover:bg="black/40" dark:bg="white/30" dark-hover:bg="white/20" absolute top="1/2" right-1 z5 hover:bg="black/40" dark:bg="white/30" dark-hover:bg="white/20" absolute top="1/2" right-1 z5

View file

@ -15,7 +15,7 @@ const emit = defineEmits<{
const modelValue = defineModel<number>({ required: true }) const modelValue = defineModel<number>({ required: true })
const slideGap = 20 const slideGap = 20
const doubleTapTreshold = 250 const doubleTapThreshold = 250
const view = ref() const view = ref()
const slider = ref() const slider = ref()
@ -36,6 +36,8 @@ const isPinching = ref(false)
const maxZoomOut = ref(1) const maxZoomOut = ref(1)
const isZoomedIn = computed(() => scale.value > 1) const isZoomedIn = computed(() => scale.value > 1)
const enableAutoplay = usePreferences('enableAutoplay')
function goToFocusedSlide() { function goToFocusedSlide() {
scale.value = 1 scale.value = 1
x.value = slide.value[modelValue.value].offsetLeft * scale.value x.value = slide.value[modelValue.value].offsetLeft * scale.value
@ -147,7 +149,7 @@ function handleLastDrag(tap: boolean, swipe: Vector2, movement: Vector2, positio
let lastTapAt = 0 let lastTapAt = 0
function handleTap([positionX, positionY]: Vector2) { function handleTap([positionX, positionY]: Vector2) {
const now = Date.now() const now = Date.now()
const isDoubleTap = now - lastTapAt < doubleTapTreshold const isDoubleTap = now - lastTapAt < doubleTapThreshold
lastTapAt = now lastTapAt = now
if (!isDoubleTap) if (!isDoubleTap)
@ -218,7 +220,7 @@ function handleZoomDrag([deltaX, deltaY]: Vector2) {
function handleSlideDrag([movementX, movementY]: Vector2) { function handleSlideDrag([movementX, movementY]: Vector2) {
goToFocusedSlide() goToFocusedSlide()
if (Math.abs(movementY) > Math.abs(movementX)) // vertical movement is more then horizontal if (Math.abs(movementY) > Math.abs(movementX)) // vertical movement is more than horizontal
y.value -= movementY / scale.value y.value -= movementY / scale.value
else else
x.value -= movementX / scale.value x.value -= movementX / scale.value
@ -264,8 +266,12 @@ const imageStyle = computed(() => ({
items-center items-center
justify-center justify-center
> >
<img <component
:is="item.type === 'gifv' ? 'video' : 'img'"
ref="image" ref="image"
:autoplay="enableAutoplay"
controls
loop
select-none select-none
max-w-full max-w-full
max-h-full max-h-full
@ -273,7 +279,7 @@ const imageStyle = computed(() => ({
:draggable="false" :draggable="false"
:src="item.url || item.previewUrl" :src="item.url || item.previewUrl"
:alt="item.description || ''" :alt="item.description || ''"
> />
</div> </div>
</div> </div>
</div> </div>

View file

@ -34,7 +34,13 @@ const { busy, oauth, singleInstanceServer } = useSignIn()
<strong>{{ currentServer }}</strong> <strong>{{ currentServer }}</strong>
</i18n-t> </i18n-t>
</button> </button>
<button v-else btn-solid text-sm px-2 py-1 text-center xl:hidden @click="openSigninDialog()"> <button
v-else
flex="~ row"
gap-x-1 items-center justify-center btn-solid text-sm px-2 py-1 xl:hidden
@click="openSigninDialog()"
>
<span aria-hidden="true" block i-ri:login-circle-line class="rtl-flip" />
{{ $t('action.sign_in') }} {{ $t('action.sign_in') }}
</button> </button>
</template> </template>

View file

@ -4,6 +4,13 @@ import type { mastodon } from 'masto'
const { notification } = defineProps<{ const { notification } = defineProps<{
notification: mastodon.v1.Notification notification: mastodon.v1.Notification
}>() }>()
const { t } = useI18n()
// well-known emoji reactions types Elk does not support yet
const unsupportedEmojiReactionTypes = ['pleroma:emoji_reaction', 'reaction']
if (unsupportedEmojiReactionTypes.includes(notification.type))
console.warn(`[DEV] ${t('notification.missing_type')} '${notification.type}' (notification.id: ${notification.id})`)
</script> </script>
<template> <template>
@ -88,7 +95,8 @@ const { notification } = defineProps<{
<template v-else-if="notification.type === 'mention' || notification.type === 'poll' || notification.type === 'status'"> <template v-else-if="notification.type === 'mention' || notification.type === 'poll' || notification.type === 'status'">
<StatusCard :status="notification.status!" /> <StatusCard :status="notification.status!" />
</template> </template>
<template v-else> <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 --> <!-- type 'favourite' and 'reblog' should always rendered by NotificationGroupedLikes -->
<div text-red font-bold> <div text-red font-bold>
[DEV] {{ $t('notification.missing_type') }} '{{ notification.type }}' [DEV] {{ $t('notification.missing_type') }} '{{ notification.type }}'

View file

@ -25,7 +25,7 @@ function includeNotificationsForStatusCard({ type, status }: mastodon.v1.Notific
// Group by type (and status when applicable) // Group by type (and status when applicable)
function groupId(item: mastodon.v1.Notification): string { function groupId(item: mastodon.v1.Notification): string {
// If the update is related to an status, group notifications from the same account (boost + favorite the same status) // If the update is related to a status, group notifications from the same account (boost + favorite the same status)
const id = item.status const id = item.status
? { ? {
status: item.status?.id, status: item.status?.id,
@ -171,6 +171,7 @@ const { formatNumber } = useHumanReadableNumber()
:paginator="paginator" :paginator="paginator"
:preprocess="preprocess" :preprocess="preprocess"
:stream="stream" :stream="stream"
eventType="notification"
:virtualScroller="virtualScroller" :virtualScroller="virtualScroller"
> >
<template #updater="{ number, update }"> <template #updater="{ number, update }">

View file

@ -87,7 +87,9 @@ function editPollOptionDraft(event: Event, index: number) {
} }
function deletePollOption(index: number) { function deletePollOption(index: number) {
draft.value.params.poll!.options = draft.value.params.poll!.options.slice().splice(index, 1) const newPollOptions = draft.value.params.poll!.options.slice()
newPollOptions.splice(index, 1)
draft.value.params.poll!.options = newPollOptions
trimPollOptions() trimPollOptions()
} }
@ -138,7 +140,7 @@ const characterCount = computed(() => {
length -= fullMatch.length - (before + username).length - 1 // - 1 for the @ length -= fullMatch.length - (before + username).length - 1 // - 1 for the @
if (draft.value.mentions) { if (draft.value.mentions) {
// + 1 is needed as mentions always need a space seperator at the end // + 1 is needed as mentions always need a space separator at the end
length += draft.value.mentions.map((mention) => { length += draft.value.mentions.map((mention) => {
const [handle] = mention.split('@') const [handle] = mention.split('@')
return `@${handle}` return `@${handle}`
@ -156,6 +158,8 @@ const isExceedingCharacterLimit = computed(() => {
const postLanguageDisplay = computed(() => languagesNameList.find(i => i.code === (draft.value.params.language || preferredLanguage))?.nativeName) const postLanguageDisplay = computed(() => languagesNameList.find(i => i.code === (draft.value.params.language || preferredLanguage))?.nativeName)
const isDM = computed(() => draft.value.params.visibility === 'direct')
async function handlePaste(evt: ClipboardEvent) { async function handlePaste(evt: ClipboardEvent) {
const files = evt.clipboardData?.files const files = evt.clipboardData?.files
if (!files || files.length === 0) if (!files || files.length === 0)
@ -275,12 +279,16 @@ onDeactivated(() => {
</ol> </ol>
</CommonErrorMessage> </CommonErrorMessage>
<div relative flex-1 flex flex-col> <div relative flex-1 flex flex-col min-h-30>
<EditorContent <EditorContent
:editor="editor" :editor="editor"
flex max-w-full flex max-w-full
:class="shouldExpanded ? 'min-h-30 md:max-h-[calc(100vh-200px)] sm:max-h-[calc(100vh-400px)] max-h-35 of-y-auto overscroll-contain' : ''" :class="{
'md:max-h-[calc(100vh-200px)] sm:max-h-[calc(100vh-400px)] max-h-35 of-y-auto overscroll-contain': shouldExpanded,
'py2 px3.5 bg-dm rounded-4 me--1 ms--1 mt--1': isDM,
}"
@keydown="stopQuestionMarkPropagation" @keydown="stopQuestionMarkPropagation"
@keydown.esc.prevent="editor?.commands.blur()"
/> />
</div> </div>

View file

@ -82,6 +82,7 @@ function activate() {
placeholder-text-secondary placeholder-text-secondary
@keydown.down.prevent="shift(1)" @keydown.down.prevent="shift(1)"
@keydown.up.prevent="shift(-1)" @keydown.up.prevent="shift(-1)"
@keydown.esc.prevent="input?.blur()"
@keypress.enter="activate" @keypress.enter="activate"
> >
<button v-if="query.length" btn-action-icon text-secondary @click="query = ''; input?.focus()"> <button v-if="query.length" btn-action-icon text-secondary @click="query = ''; input?.focus()">

View file

@ -10,9 +10,11 @@ const props = defineProps<{
external?: true external?: true
large?: true large?: true
match?: boolean match?: boolean
target?: string
}>() }>()
const router = useRouter() const router = useRouter()
const scrollOnClick = computed(() => props.to && !(props.target === '_blank' || props.external))
useCommand({ useCommand({
scope: 'Settings', scope: 'Settings',
@ -39,11 +41,12 @@ useCommand({
:disabled="disabled" :disabled="disabled"
:to="to" :to="to"
:external="external" :external="external"
:target="target"
exact-active-class="text-primary" exact-active-class="text-primary"
:class="disabled ? 'op25 pointer-events-none ' : match ? 'text-primary' : ''" :class="disabled ? 'op25 pointer-events-none ' : match ? 'text-primary' : ''"
block w-full group focus:outline-none block w-full group focus:outline-none
:tabindex="disabled ? -1 : null" :tabindex="disabled ? -1 : null"
@click="to ? $scrollToTop() : undefined" @click="scrollOnClick ? $scrollToTop() : undefined"
> >
<div <div
w-full flex px5 py3 md:gap2 gap4 items-center w-full flex px5 py3 md:gap2 gap4 items-center

View file

@ -16,7 +16,7 @@ const { disabled = false } = defineProps<{
:class="disabled ? 'opacity-50 cursor-not-allowed' : ''" :class="disabled ? 'opacity-50 cursor-not-allowed' : ''"
> >
<div <div
w-full flex w-fit px5 py3 md:gap2 gap4 items-center w-full flex px5 py3 md:gap2 gap4 items-center
transition-250 transition-250
:class="disabled ? '' : 'group-hover:bg-active'" :class="disabled ? '' : 'group-hover:bg-active'"
group-focus-visible:ring="2 current" group-focus-visible:ring="2 current"

View file

@ -55,7 +55,7 @@ function reply() {
<div flex-1> <div flex-1>
<StatusActionButton <StatusActionButton
:content="$t('action.boost')" :content="$t(status.reblogged ? 'action.boosted' : 'action.boost')"
:text="!getPreferences(userSettings, 'hideBoostCount') && status.reblogsCount ? status.reblogsCount : ''" :text="!getPreferences(userSettings, 'hideBoostCount') && status.reblogsCount ? status.reblogsCount : ''"
color="text-green" hover="text-green" elk-group-hover="bg-green/10" color="text-green" hover="text-green" elk-group-hover="bg-green/10"
icon="i-ri:repeat-line" icon="i-ri:repeat-line"
@ -77,7 +77,7 @@ function reply() {
<div flex-1> <div flex-1>
<StatusActionButton <StatusActionButton
:content="$t('action.favourite')" :content="$t(status.favourited ? 'action.favourited' : 'action.favourite')"
:text="!getPreferences(userSettings, 'hideFavoriteCount') && status.favouritesCount ? status.favouritesCount : ''" :text="!getPreferences(userSettings, 'hideFavoriteCount') && status.favouritesCount ? status.favouritesCount : ''"
:color="useStarFavoriteIcon ? 'text-yellow' : 'text-rose'" :color="useStarFavoriteIcon ? 'text-yellow' : 'text-rose'"
:hover="useStarFavoriteIcon ? 'text-yellow' : 'text-rose'" :hover="useStarFavoriteIcon ? 'text-yellow' : 'text-rose'"
@ -100,7 +100,7 @@ function reply() {
<div flex-none> <div flex-none>
<StatusActionButton <StatusActionButton
:content="$t('action.bookmark')" :content="$t(status.bookmarked ? 'action.bookmarked' : 'action.bookmark')"
:color="useStarFavoriteIcon ? 'text-rose' : 'text-yellow'" :color="useStarFavoriteIcon ? 'text-rose' : 'text-yellow'"
:hover="useStarFavoriteIcon ? 'text-rose' : 'text-yellow'" :hover="useStarFavoriteIcon ? 'text-rose' : 'text-yellow'"
:elk-group-hover="useStarFavoriteIcon ? 'bg-rose/10' : 'bg-yellow/10' " :elk-group-hover="useStarFavoriteIcon ? 'bg-rose/10' : 'bg-yellow/10' "

View file

@ -144,7 +144,7 @@ function showFavoritedAndBoostedBy() {
<template #popper> <template #popper>
<div flex="~ col"> <div flex="~ col">
<template v-if="getPreferences(userSettings, 'zenMode')"> <template v-if="getPreferences(userSettings, 'zenMode') && !details">
<CommonDropdownItem <CommonDropdownItem
:text="$t('action.reply')" :text="$t('action.reply')"
icon="i-ri:chat-1-line" icon="i-ri:chat-1-line"

View file

@ -68,6 +68,7 @@ const video = ref<HTMLVideoElement | undefined>()
const prefersReducedMotion = usePreferredReducedMotion() const prefersReducedMotion = usePreferredReducedMotion()
const isAudio = computed(() => attachment.type === 'audio') const isAudio = computed(() => attachment.type === 'audio')
const isVideo = computed(() => attachment.type === 'video') const isVideo = computed(() => attachment.type === 'video')
const isGif = computed(() => attachment.type === 'gifv')
const enableAutoplay = usePreferences('enableAutoplay') const enableAutoplay = usePreferences('enableAutoplay')
@ -164,7 +165,7 @@ watch(shouldLoadAttachment, () => {
<button <button
type="button" type="button"
relative relative
@click="!shouldLoadAttachment ? loadAttachment() : null" @click="!shouldLoadAttachment ? loadAttachment() : openMediaPreview(attachments ? attachments : [attachment], attachments?.indexOf(attachment) || 0)"
> >
<video <video
ref="video" ref="video"
@ -248,12 +249,13 @@ watch(shouldLoadAttachment, () => {
</button> </button>
</template> </template>
<div <div
v-if="attachment.description && !getPreferences(userSettings, 'hideAltIndicatorOnPosts')" :class="isAudio ? [] : [ :class="isAudio ? [] : [
'absolute left-2', 'absolute left-2',
isVideo ? 'top-2' : 'bottom-2', isVideo ? 'top-2' : 'bottom-2',
]" ]"
flex gap-col-2
> >
<VDropdown :distance="6" placement="bottom-start"> <VDropdown v-if="attachment.description && !getPreferences(userSettings, 'hideAltIndicatorOnPosts')" :distance="6" placement="bottom-start">
<button <button
font-bold text-sm font-bold text-sm
:class="isAudio :class="isAudio
@ -281,6 +283,14 @@ watch(shouldLoadAttachment, () => {
</div> </div>
</template> </template>
</VDropdown> </VDropdown>
<div v-if="isGif && !getPreferences(userSettings, 'hideGifIndicatorOnPosts')">
<button
aria-hidden font-bold text-sm
rounded-1 bg-black:65 text-white px1.2 py0.2 pointer-events-none
>
{{ $t('status.gif') }}
</button>
</div>
</div> </div>
</div> </div>
</template> </template>

View file

@ -37,7 +37,7 @@ const allowEmbeddedMedia = computed(() => status.card?.html && embeddedMediaPref
<div <div
space-y-3 space-y-3
:class="{ :class="{
'pt2 pb0.5 px3.5 bg-dm rounded-4 me--1': isDM, 'py2 px3.5 bg-dm rounded-4 me--1': isDM,
'ms--3.5 mt--1 ms--1': isDM && context !== 'details', 'ms--3.5 mt--1 ms--1': isDM && context !== 'details',
}" }"
> >
@ -68,7 +68,6 @@ const allowEmbeddedMedia = computed(() => status.card?.html && embeddedMediaPref
:status="status.reblog" border="~ rounded" :status="status.reblog" border="~ rounded"
:actions="false" :actions="false"
/> />
<div v-if="isDM" />
</StatusSpoiler> </StatusSpoiler>
</div> </div>
</template> </template>

View file

@ -31,7 +31,7 @@ useHydratedHead({
<template> <template>
<div :id="`status-${status.id}`" flex flex-col gap-2 pt2 pb1 ps-3 pe-4 relative :lang="status.language ?? undefined" aria-roledescription="status-details"> <div :id="`status-${status.id}`" flex flex-col gap-2 pt2 pb1 ps-3 pe-4 relative :lang="status.language ?? undefined" aria-roledescription="status-details">
<StatusActionsMore :status="status" absolute inset-ie-2 top-2 @after-edit="$emit('refetchStatus')" /> <StatusActionsMore :status="status" :details="true" absolute inset-ie-2 top-2 @after-edit="$emit('refetchStatus')" />
<NuxtLink :to="getAccountRoute(status.account)" rounded-full hover:bg-active transition-100 pe5 me-a> <NuxtLink :to="getAccountRoute(status.account)" rounded-full hover:bg-active transition-100 pe5 me-a>
<AccountHoverWrapper :account="status.account"> <AccountHoverWrapper :account="status.account">
<AccountInfo :account="status.account" /> <AccountInfo :account="status.account" />

View file

@ -14,7 +14,8 @@ function onclick(evt: MouseEvent | KeyboardEvent) {
const path = evt.composedPath() as HTMLElement[] const path = evt.composedPath() as HTMLElement[]
const el = path.find(el => ['A', 'BUTTON', 'IMG', 'VIDEO'].includes(el.tagName?.toUpperCase())) const el = path.find(el => ['A', 'BUTTON', 'IMG', 'VIDEO'].includes(el.tagName?.toUpperCase()))
const text = window.getSelection()?.toString() const text = window.getSelection()?.toString()
if (!el && !text) const isCustomEmoji = el?.parentElement?.classList.contains('custom-emoji')
if ((!el && !text) || isCustomEmoji)
go(evt) go(evt)
} }

View file

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
const paginator = useMastoClient().v1.timelines.public.list({ limit: 30, local: true }) const paginator = useMastoClient().v1.timelines.public.list({ limit: 30, local: true })
const stream = useStreaming(client => client.direct.subscribe()) const stream = useStreaming(client => client.public.local.subscribe())
</script> </script>
<template> <template>

View file

@ -3,8 +3,10 @@ import type { BuildInfo } from '~~/types'
export interface Team { export interface Team {
github: string github: string
display: string display: string
twitter: string twitter?: string
mastodon: string mastodon: string
link: string
sponsors?: string
} }
export const elkTeamMembers: Team[] = [ export const elkTeamMembers: Team[] = [
@ -13,24 +15,43 @@ export const elkTeamMembers: Team[] = [
display: 'Anthony Fu', display: 'Anthony Fu',
twitter: 'antfu7', twitter: 'antfu7',
mastodon: 'antfu@webtoo.ls', mastodon: 'antfu@webtoo.ls',
link: '/m.webtoo.ls/@antfu',
}, },
{ {
github: 'patak-dev', github: 'patak-dev',
display: 'Patak', display: 'Patak',
twitter: 'patak_dev', twitter: 'patak_dev',
mastodon: 'patak@webtoo.ls', mastodon: 'patak@webtoo.ls',
link: '/m.webtoo.ls/@patak',
}, },
{ {
github: 'danielroe', github: 'danielroe',
display: 'Daniel Roe', display: 'Daniel Roe',
twitter: 'danielcroe', twitter: 'danielcroe',
mastodon: 'daniel@roe.dev', mastodon: 'daniel@roe.dev',
link: '/mastodon.roe.dev/@daniel',
}, },
{ {
github: 'sxzz', github: 'sxzz',
display: '三咲智子 Kevin Deng', display: '三咲智子 Kevin Deng',
twitter: 'sanxiaozhizi', twitter: 'sanxiaozhizi',
mastodon: 'sxzz@webtoo.ls', mastodon: 'sxzz@webtoo.ls',
link: '/m.webtoo.ls/@sxzz',
},
{
github: 'userquin',
display: 'Joaquín Sánchez',
twitter: 'userquin',
mastodon: 'userquin@webtoo.ls',
link: '/m.webtoo.ls/@userquin',
sponsors: 'elk-zone', // sponsors/userquin isn't enabled
},
{
github: 'shuuji3',
display: 'TAKAHASHI Shuuji',
mastodon: 'shuuji3@webtoo.ls',
link: '/m.webtoo.ls/@shuuji3',
sponsors: 'elk-zone', // sponsors/shuuji3 isn't enabled
}, },
].sort(() => Math.random() - 0.5) ].sort(() => Math.random() - 0.5)

View file

@ -245,24 +245,12 @@ export function useCommands(cmds: () => CommandProvider[]) {
export function provideGlobalCommands() { export function provideGlobalCommands() {
const { locale, t } = useI18n() const { locale, t } = useI18n()
const { locales } = useI18n() as { locales: ComputedRef<LocaleObject[]> } const { locales } = useI18n() as { locales: ComputedRef<LocaleObject[]> }
const router = useRouter()
const users = useUsers() const users = useUsers()
const masto = useMasto() const masto = useMasto()
const colorMode = useColorMode() const colorMode = useColorMode()
const userSettings = useUserSettings() const userSettings = useUserSettings()
const { singleInstanceServer, oauth } = useSignIn() const { singleInstanceServer, oauth } = useSignIn()
useCommand({
scope: 'Navigation',
name: () => t('nav.settings'),
icon: 'i-ri:settings-3-line',
onActivate() {
router.push('/settings')
},
})
useCommand({ useCommand({
scope: 'Preferences', scope: 'Preferences',

View file

@ -623,7 +623,7 @@ function transformCollapseMentions(status?: mastodon.v1.Status, inReplyToStatus?
// We have a special case for single mentions that are part of a reply. // We have a special case for single mentions that are part of a reply.
// We already have the replying to badge in this case or the status is connected to the previous one. // We already have the replying to badge in this case or the status is connected to the previous one.
// This is needed because the status doesn't included the in Reply to handle, only the account id. // This is needed because the status doesn't include the in Reply to handle, only the account id.
// But this covers the majority of cases. // But this covers the majority of cases.
const showMentions = !(contextualMentionsCount === 0 || (mentionsCount === 1 && status?.inReplyToAccountId)) const showMentions = !(contextualMentionsCount === 0 || (mentionsCount === 1 && status?.inReplyToAccountId))
const grouped = contextualMentionsCount > 2 const grouped = contextualMentionsCount > 2

View file

@ -77,3 +77,32 @@ export function useTimeAgoOptions(short = false): UseTimeAgoOptions<false> {
}, },
} }
} }
export function useFileSizeFormatter() {
const { locale } = useI18n()
const formatters = computed(() => ([
Intl.NumberFormat(locale.value, {
style: 'unit',
unit: 'megabyte',
unitDisplay: 'narrow',
maximumFractionDigits: 0,
}),
Intl.NumberFormat(locale.value, {
style: 'unit',
unit: 'kilobyte',
unitDisplay: 'narrow',
maximumFractionDigits: 0,
}),
]))
const megaByte = 1024 * 1024
function formatFileSize(size: number) {
return size >= megaByte
? formatters.value[0].format(size / megaByte)
: formatters.value[1].format(size / 1024)
}
return { formatFileSize }
}

View file

@ -155,6 +155,7 @@ export type MediaAttachmentUploadError = [filename: string, message: string]
export function useUploadMediaAttachment(draft: Ref<Draft>) { export function useUploadMediaAttachment(draft: Ref<Draft>) {
const { client } = useMasto() const { client } = useMasto()
const { t } = useI18n() const { t } = useI18n()
const { formatFileSize } = useFileSizeFormatter()
const isUploading = ref<boolean>(false) const isUploading = ref<boolean>(false)
const isExceedingAttachmentLimit = ref<boolean>(false) const isExceedingAttachmentLimit = ref<boolean>(false)
@ -224,8 +225,32 @@ export function useUploadMediaAttachment(draft: Ref<Draft>) {
// TODO: display some kind of message if too many media are selected // TODO: display some kind of message if too many media are selected
// DONE // DONE
const limit = currentInstance.value!.configuration?.statuses.maxMediaAttachments || 4 const limit = currentInstance.value!.configuration?.statuses.maxMediaAttachments || 4
const maxVideoSize = currentInstance.value!.configuration?.mediaAttachments.videoSizeLimit || 0
const maxImageSize = currentInstance.value!.configuration?.mediaAttachments.imageSizeLimit || 0
for (const file of files.slice(0, limit)) { for (const file of files.slice(0, limit)) {
if (draft.value.attachments.length < limit) { if (draft.value.attachments.length < limit) {
if (file.type.startsWith('image/')) {
if (maxImageSize > 0 && file.size > maxImageSize) {
failedAttachments.value = [...failedAttachments.value, [file.name, t('state.attachments_limit_image_error', [formatFileSize(maxImageSize)])]]
continue
}
}
else {
if (maxVideoSize > 0 && file.size > maxVideoSize) {
const key
= file.type.startsWith('audio/')
? 'state.attachments_limit_audio_error'
: file.type.startsWith('video/')
? 'state.attachments_limit_video_error'
: 'state.attachments_limit_unknown_error'
const errorMessage = t(key, [formatFileSize(maxVideoSize)])
failedAttachments.value = [
...failedAttachments.value,
[file.name, errorMessage],
]
continue
}
}
isExceedingAttachmentLimit.value = false isExceedingAttachmentLimit.value = false
try { try {
const attachment = await client.value.v1.media.create({ const attachment = await client.value.v1.media.create({

View file

@ -5,6 +5,7 @@ import type { PaginatorState } from '~/types'
export function usePaginator<T, P, U = T>( export function usePaginator<T, P, U = T>(
_paginator: mastodon.Paginator<T[], P>, _paginator: mastodon.Paginator<T[], P>,
stream: Ref<mastodon.streaming.Subscription | undefined>, stream: Ref<mastodon.streaming.Subscription | undefined>,
eventType: 'update' | 'notification' = 'update',
preprocess: (items: (T | U)[]) => U[] = items => items as unknown as U[], preprocess: (items: (T | U)[]) => U[] = items => items as unknown as U[],
buffer = 10, buffer = 10,
) { ) {
@ -34,10 +35,10 @@ export function usePaginator<T, P, U = T>(
return return
for await (const entry of stream) { for await (const entry of stream) {
if (entry.event === 'update') { if (entry.event === eventType) {
const status = entry.payload const status = entry.payload
if ('uri' in entry) if ('uri' in status)
cacheStatus(status, undefined, true) cacheStatus(status, undefined, true)
const index = prevItems.value.findIndex((i: any) => i.id === status.id) const index = prevItems.value.findIndex((i: any) => i.id === status.id)

View file

@ -9,6 +9,7 @@ export type ColorMode = 'light' | 'dark' | 'system'
export interface PreferencesSettings { export interface PreferencesSettings {
hideAltIndicatorOnPosts: boolean hideAltIndicatorOnPosts: boolean
hideGifIndicatorOnPosts: boolean
hideBoostCount: boolean hideBoostCount: boolean
hideReplyCount: boolean hideReplyCount: boolean
hideFavoriteCount: boolean hideFavoriteCount: boolean
@ -64,6 +65,7 @@ export function getDefaultLanguage(languages: string[]) {
export const DEFAULT__PREFERENCES_SETTINGS: PreferencesSettings = { export const DEFAULT__PREFERENCES_SETTINGS: PreferencesSettings = {
hideAltIndicatorOnPosts: false, hideAltIndicatorOnPosts: false,
hideGifIndicatorOnPosts: false,
hideBoostCount: false, hideBoostCount: false,
hideReplyCount: false, hideReplyCount: false,
hideFavoriteCount: false, hideFavoriteCount: false,

View file

@ -63,6 +63,9 @@ export function useTiptap(options: UseTiptapOptions) {
Mention Mention
.extend({ name: 'hashtag' }) .extend({ name: 'hashtag' })
.configure({ .configure({
renderHTML({ options, node }) {
return ['span', { 'data-type': 'hashtag', 'data-id': node.attrs.id }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`]
},
suggestion: TiptapHashtagSuggestion, suggestion: TiptapHashtagSuggestion,
}), }),
Mention Mention

View file

@ -44,7 +44,7 @@ When you are ready to submit work back to the main Elk repo, create a PR.
- base branch should be **main** - base branch should be **main**
- Head repository should be your fork - Head repository should be your fork
- Compare branch should be your working branch you want to submit - Compare branch should be your working branch you want to submit
If you don't see four drop downs, be sure you are comparing across forks. If you don't see four drop-downs, be sure you are comparing across forks.
10. Add a description of the changes your request makes 10. Add a description of the changes your request makes
11. Select **Add Pull Request** 11. Select **Add Pull Request**
@ -65,7 +65,7 @@ Avoid screenshots until Elk reaches a stable release.
### Standards ### Standards
Write in **American English** using spelling as found in [Merriam Webster](https://www.merriam-webster.com). Write in **American English** using spelling as found in [Merriam-Webster](https://www.merriam-webster.com).
Translation and localization will be handled separately as/when availability or necessity allow. Translation and localization will be handled separately as/when availability or necessity allow.
Use [**semantic linefeeds**](https://rhodesmill.org/brandon/2012/one-sentence-per-line/) with no more than one sentence per line. Use [**semantic linefeeds**](https://rhodesmill.org/brandon/2012/one-sentence-per-line/) with no more than one sentence per line.

View file

@ -1,6 +1,6 @@
<script> <script>
export default { export default {
name: 'ToogleIcon', name: 'ToggleIcon',
props: { up: Boolean }, props: { up: Boolean },
} }
</script> </script>

View file

@ -116,7 +116,7 @@ async function copyToClipboard() {
> >
<td :class="[{ expandable: !isSource }]"> <td :class="[{ expandable: !isSource }]">
<div> <div>
<ToogleIcon v-if="!isSource" :up="hidden || key !== locale" /> <ToggleIcon v-if="!isSource" :up="hidden || key !== locale" />
{{ title }} {{ title }}
</div> </div>
</td> </td>

View file

@ -56,6 +56,6 @@ On your project page open the Deploys tab, click on "Trigger deploy" and "Deploy
## Use a custom domain ## Use a custom domain
If you want to use a custom domain, go to "Domain settings" on your Netlify project page, and press "Add custom domain". If your domain is not bought from Netlify, it will ask you to add a CNAME record. Do that. If you want to use a custom domain, go to "Domain settings" on your Netlify project page, and press "Add custom domain". If your domain is not bought from Netlify, it will ask you to add a CNAME record. Do that.
Once the custom domain is added, you'll need to add an SSL/TLS certificate. At the bottom of the page press "Verify DNS configuration" and if it succeeds, press "Provision certificate". If that fails, you may need to wait some time until your DNS propagetes. Once the custom domain is added, you'll need to add an SSL/TLS certificate. At the bottom of the page press "Verify DNS configuration" and if it succeeds, press "Provision certificate". If that fails, you may need to wait some time until your DNS propagates.
And that's it! Enjoy your instance's Elk! And that's it! Enjoy your instance's Elk!

View file

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

View file

@ -13,6 +13,6 @@
}, },
"devDependencies": { "devDependencies": {
"@nuxt-themes/docus": "^1.15.0", "@nuxt-themes/docus": "^1.15.0",
"nuxt": "^3.10.3" "nuxt": "^3.11.2"
} }
} }

View file

@ -196,14 +196,14 @@
"title": "Preview deploy" "title": "Preview deploy"
}, },
"desc_highlight": "Expect some bugs and missing features here and there.", "desc_highlight": "Expect some bugs and missing features here and there.",
"desc_para1": "Thanks for your interest in trying out Elk, our work-in-progress Mastodon web client!", "desc_para1": "Elk is a nimble Mastodon web client. You can login to your Mastodon account and use it to interact with the fediverse.",
"desc_para2": "we are working hard on the development and improving it over time.", "desc_para2": "Elk is Open Source and we're actively improving it as a community project. Join us and let's build it together!",
"desc_para3": "To boost development, you can sponsor the Team through GitHub Sponsors. We hope you enjoy Elk!", "desc_para3": "To boost development, you can sponsor the Team through GitHub Sponsors. We hope you enjoy Elk!",
"desc_para4": "Elk is Open Source. If you'd like to help with testing, giving feedback, or contributing,", "desc_para4": "If you'd like to report a bug, help us testing, give feedback, or contribute,",
"desc_para5": "reach out to us on GitHub", "desc_para5": "reach out to us on GitHub",
"desc_para6": "and get involved.", "desc_para6": "and get involved.",
"footer_team": "The Elk Team", "footer_team": "The Elk Team",
"title": "Elk is in Preview!" "title": "Welcome to Elk!"
}, },
"language": { "language": {
"search": "Search" "search": "Search"
@ -306,6 +306,7 @@
"built_at": "Built {0}", "built_at": "Built {0}",
"compose": "Compose", "compose": "Compose",
"conversations": "Conversations", "conversations": "Conversations",
"docs": "Documentation",
"explore": "Explore", "explore": "Explore",
"favourites": "Favorites", "favourites": "Favorites",
"federated": "Federated", "federated": "Federated",
@ -459,6 +460,7 @@
}, },
"language": { "language": {
"display_language": "Display Language", "display_language": "Display Language",
"how_to_contribute": "How to contribute?",
"label": "Language", "label": "Language",
"post_language": "Posting Language", "post_language": "Posting Language",
"status": "Translation status: {0}/{1} ({2}%)", "status": "Translation status: {0}/{1} ({2}%)",
@ -540,6 +542,7 @@
"hide_boost_count": "Hide boost count", "hide_boost_count": "Hide boost count",
"hide_favorite_count": "Hide favorite count", "hide_favorite_count": "Hide favorite count",
"hide_follower_count": "Hide following/follower count", "hide_follower_count": "Hide following/follower count",
"hide_gif_indi_on_posts": "Hide gif indicator on posts",
"hide_news": "Hide news", "hide_news": "Hide news",
"hide_reply_count": "Hide reply count", "hide_reply_count": "Hide reply count",
"hide_tag_hover_card": "Hide tag hover card", "hide_tag_hover_card": "Hide tag hover card",
@ -591,7 +594,11 @@
}, },
"state": { "state": {
"attachments_exceed_server_limit": "The number of attachments exceeded the limit per post.", "attachments_exceed_server_limit": "The number of attachments exceeded the limit per post.",
"attachments_limit_audio_error": "Maximum audio size exceeded: {0}",
"attachments_limit_error": "Limit per post exceeded", "attachments_limit_error": "Limit per post exceeded",
"attachments_limit_image_error": "Maximum image size exceeded: {0}",
"attachments_limit_unknown_error": "Maximum file size exceeded: {0}",
"attachments_limit_video_error": "Maximum video size exceeded: {0}",
"edited": "(Edited)", "edited": "(Edited)",
"editing": "Editing", "editing": "Editing",
"loading": "Loading...", "loading": "Loading...",
@ -612,6 +619,7 @@
"favourited_by": "Favorited By", "favourited_by": "Favorited By",
"filter_hidden_phrase": "Filtered by", "filter_hidden_phrase": "Filtered by",
"filter_show_anyway": "Show anyway", "filter_show_anyway": "Show anyway",
"gif": "GIF",
"img_alt": { "img_alt": {
"ALT": "ALT", "ALT": "ALT",
"desc": "Description", "desc": "Description",

View file

@ -184,6 +184,9 @@
"label": "Usuarios en línea" "label": "Usuarios en línea"
} }
}, },
"state": {
"attachments_limit_video_error": "Tamaño máximo de video excedido: {0}"
},
"status": { "status": {
"spoiler_show_less": "Menos" "spoiler_show_less": "Menos"
}, },

View file

@ -149,7 +149,12 @@
"mute_account": { "mute_account": {
"cancel": "Cancelar", "cancel": "Cancelar",
"confirm": "Silenciar", "confirm": "Silenciar",
"days": "días|día|días",
"description": "¿Estás seguro que quieres silenciar a {0}?", "description": "¿Estás seguro que quieres silenciar a {0}?",
"hours": "horas|hora|horas",
"minute": "minutos|minuto|minutos",
"notifications": "Silenciar notificaciones",
"specify_duration": "Especificar la duración del silenciado",
"title": "Silenciar cuenta" "title": "Silenciar cuenta"
}, },
"show_reblogs": { "show_reblogs": {
@ -301,6 +306,7 @@
"built_at": "Compilado {0}", "built_at": "Compilado {0}",
"compose": "Redactar", "compose": "Redactar",
"conversations": "Conversaciones", "conversations": "Conversaciones",
"docs": "Documentación",
"explore": "Explorar", "explore": "Explorar",
"favourites": "Favoritas", "favourites": "Favoritas",
"federated": "Federados", "federated": "Federados",
@ -454,6 +460,7 @@
}, },
"language": { "language": {
"display_language": "Idioma de pantalla", "display_language": "Idioma de pantalla",
"how_to_contribute": "¿Cómo contribuir?",
"label": "Idioma", "label": "Idioma",
"post_language": "Idioma de publicación", "post_language": "Idioma de publicación",
"status": "Estado traducción: {0}/{1} ({2}%)", "status": "Estado traducción: {0}/{1} ({2}%)",
@ -535,6 +542,7 @@
"hide_boost_count": "Ocultar contador de retoots", "hide_boost_count": "Ocultar contador de retoots",
"hide_favorite_count": "Ocultar número de publicaciones favoritas", "hide_favorite_count": "Ocultar número de publicaciones favoritas",
"hide_follower_count": "Ocultar número de seguidores", "hide_follower_count": "Ocultar número de seguidores",
"hide_gif_indi_on_posts": "Ocultar indicador de gif en publicaciones",
"hide_news": "Ocultar noticias", "hide_news": "Ocultar noticias",
"hide_reply_count": "Ocultar número de respuestas", "hide_reply_count": "Ocultar número de respuestas",
"hide_tag_hover_card": "Ocultar tarjeta flotante de etiqueta", "hide_tag_hover_card": "Ocultar tarjeta flotante de etiqueta",
@ -586,7 +594,11 @@
}, },
"state": { "state": {
"attachments_exceed_server_limit": "Número máximo de archivos adjuntos por publicación excedido.", "attachments_exceed_server_limit": "Número máximo de archivos adjuntos por publicación excedido.",
"attachments_limit_audio_error": "Tamaño máximo de audio excedido: {0}",
"attachments_limit_error": "Límite por publicación excedido", "attachments_limit_error": "Límite por publicación excedido",
"attachments_limit_image_error": "Tamaño máximo de imagen excedido: {0}",
"attachments_limit_unknown_error": "Tamaño máximo de archivo excedido: {0}",
"attachments_limit_video_error": "Tamaño máximo de vídeo excedido: {0}",
"edited": "(Editado)", "edited": "(Editado)",
"editing": "Editando", "editing": "Editando",
"loading": "Cargando...", "loading": "Cargando...",
@ -607,6 +619,7 @@
"favourited_by": "Marcado como favorita por", "favourited_by": "Marcado como favorita por",
"filter_hidden_phrase": "Filtrado por", "filter_hidden_phrase": "Filtrado por",
"filter_show_anyway": "Mostrar de todas formas", "filter_show_anyway": "Mostrar de todas formas",
"gif": "GIF",
"img_alt": { "img_alt": {
"ALT": "ALT", "ALT": "ALT",
"desc": "Descripción", "desc": "Descripción",
@ -703,7 +716,7 @@
"tooltip": { "tooltip": {
"add_content_warning": "Añadir advertencia de contenido", "add_content_warning": "Añadir advertencia de contenido",
"add_emojis": "Agregar emojis", "add_emojis": "Agregar emojis",
"add_media": "Añadir imágenes, video o audio", "add_media": "Añadir imágenes, vídeo o audio",
"add_publishable_content": "Publicar contenido", "add_publishable_content": "Publicar contenido",
"change_content_visibility": "Cambiar visibilidad de contenido", "change_content_visibility": "Cambiar visibilidad de contenido",
"change_language": "Cambiar idioma", "change_language": "Cambiar idioma",

View file

@ -459,6 +459,7 @@
}, },
"language": { "language": {
"display_language": "Interfazearen hizkuntza", "display_language": "Interfazearen hizkuntza",
"how_to_contribute": "Nola lagun dezaket?",
"label": "Hizkuntza", "label": "Hizkuntza",
"post_language": "Bidalketen hizkuntza", "post_language": "Bidalketen hizkuntza",
"status": "Itzulpenaren egoera: {1} kateetatik {0} itzulita (%{2}a)", "status": "Itzulpenaren egoera: {1} kateetatik {0} itzulita (%{2}a)",

View file

@ -27,6 +27,7 @@
"follows_you": "Követ téged", "follows_you": "Követ téged",
"go_to_profile": "Ugrás a profilhoz", "go_to_profile": "Ugrás a profilhoz",
"joined": "Csatlakozott", "joined": "Csatlakozott",
"lock": "Zárolt",
"moved_title": "jelezte, hogy az új fiók mostantól:", "moved_title": "jelezte, hogy az új fiók mostantól:",
"muted_users": "Némított felhasználók", "muted_users": "Némított felhasználók",
"muting": "Némított", "muting": "Némított",
@ -79,7 +80,7 @@
"save": "Ment", "save": "Ment",
"save_changes": "Változások mentése", "save_changes": "Változások mentése",
"sign_in": "Bejelentkezés", "sign_in": "Bejelentkezés",
"sign_in_to": "Bejelentkezés: {0}", "sign_in_to": "Bejelentkezve: {0}",
"switch_account": "Fiók váltás", "switch_account": "Fiók váltás",
"vote": "Szavazás" "vote": "Szavazás"
}, },
@ -94,6 +95,7 @@
"activate": "Aktivál", "activate": "Aktivál",
"complete": "Befejez", "complete": "Befejez",
"compose_desc": "Új bejegyzés írása", "compose_desc": "Új bejegyzés írása",
"n_people_in_the_past_n_days": "{0} ember az elmúlt {1} napban",
"select_lang": "Nyelv kiválasztása", "select_lang": "Nyelv kiválasztása",
"sign_in_desc": "Létező fiók hozzáadása", "sign_in_desc": "Létező fiók hozzáadása",
"switch_account": "Váltás: {0}", "switch_account": "Váltás: {0}",
@ -119,12 +121,14 @@
"block_account": { "block_account": {
"cancel": "Mégsem", "cancel": "Mégsem",
"confirm": "Blokkol", "confirm": "Blokkol",
"description": "Biztosan blokkolja? {0}" "description": "Biztosan blokkolja? {0}",
"title": "Hozzáférés blokkolása"
}, },
"block_domain": { "block_domain": {
"cancel": "Mégsem", "cancel": "Mégsem",
"confirm": "Blokkol", "confirm": "Blokkol",
"description": "Biztosan blokkolja? {0}" "description": "Biztosan blokkolja? {0}",
"title": "Domain blokkolása"
}, },
"common": { "common": {
"cancel": "Mégsem", "cancel": "Mégsem",
@ -133,27 +137,37 @@
"delete_list": { "delete_list": {
"cancel": "Mégsem", "cancel": "Mégsem",
"confirm": "Töröl", "confirm": "Töröl",
"description": "Biztosan törli a listát? \"{0}\"" "description": "Biztosan törli a listát? \"{0}\"",
"title": "Lista törlése"
}, },
"delete_posts": { "delete_posts": {
"cancel": "Mégsem", "cancel": "Mégsem",
"confirm": "Töröl", "confirm": "Töröl",
"description": "Biztosan törli a bejegyzést?" "description": "Biztosan törli a bejegyzést?",
"title": "Bejegyzés törlése"
}, },
"mute_account": { "mute_account": {
"cancel": "Mégsem", "cancel": "Mégsem",
"confirm": "Némít", "confirm": "Némít",
"description": "Biztosan némítja? {0}" "days": "napok|nap|napok",
"description": "Biztosan némítja? {0}",
"hours": "órák|óra|órák",
"minute": "perc|perc|perc",
"notifications": "Értesítések némítása",
"specify_duration": "Határozzon meg némítási időtartamot",
"title": "Hozzáférés némítása"
}, },
"show_reblogs": { "show_reblogs": {
"cancel": "Mégsem", "cancel": "Mégsem",
"confirm": "Mutat", "confirm": "Mutat",
"description": "Biztosan megjeleníti a Turbót tőle? {0}" "description": "Biztosan megjeleníti a Kiemelést tőle? {0}",
"title": "Kiemelés megjenítése"
}, },
"unfollow": { "unfollow": {
"cancel": "Mégsem", "cancel": "Mégsem",
"confirm": "Követés leállítása", "confirm": "Követés leállítása",
"description": "Biztosan leállítja a követését?" "description": "Biztosan leállítja a követését?",
"title": "Követés elállítása"
} }
}, },
"conversation": { "conversation": {
@ -168,6 +182,7 @@
}, },
"error": { "error": {
"account_not_found": "Fiók {0} nem található", "account_not_found": "Fiók {0} nem található",
"explore_list_empty": "Nincs most semmi érdekes. Térj vissza később!",
"file_size_cannot_exceed_n_mb": "A fájl mérete nem haladhatja meg a {0}MB-ot", "file_size_cannot_exceed_n_mb": "A fájl mérete nem haladhatja meg a {0}MB-ot",
"sign_in_error": "Nem lehetséges a csatlakozás.", "sign_in_error": "Nem lehetséges a csatlakozás.",
"status_not_found": "A bejegyzés nem található", "status_not_found": "A bejegyzés nem található",
@ -218,14 +233,25 @@
"compose": "Közzétesz", "compose": "Közzétesz",
"favourite": "Kedvenc", "favourite": "Kedvenc",
"search": "Keresés", "search": "Keresés",
"show_new_items": "Új elemek mutatása",
"title": "Műveletek" "title": "Műveletek"
}, },
"media": { "media": {
"title": "Média" "title": "Média"
}, },
"navigation": { "navigation": {
"go_to_bookmarks": "Könyvjelzők",
"go_to_conversations": "Párbeszédek",
"go_to_explore": "Felfedez",
"go_to_favourites": "Kedvencek",
"go_to_federated": "Összesített",
"go_to_home": "Otthon", "go_to_home": "Otthon",
"go_to_lists": "Listák",
"go_to_local": "Helyi",
"go_to_notifications": "Értesítések", "go_to_notifications": "Értesítések",
"go_to_profile": "Profil",
"go_to_search": "Keresés",
"go_to_settings": "Beállítások",
"next_status": "Következő bejegyzés", "next_status": "Következő bejegyzés",
"previous_status": "Előző bejegyzés", "previous_status": "Előző bejegyzés",
"shortcut_help": "Gyors segítség", "shortcut_help": "Gyors segítség",
@ -283,6 +309,7 @@
"explore": "Felfedezés", "explore": "Felfedezés",
"favourites": "Kedvencek", "favourites": "Kedvencek",
"federated": "Összesített", "federated": "Összesített",
"hashtags": "Címkék",
"home": "Otthon", "home": "Otthon",
"list": "Lista", "list": "Lista",
"lists": "Listák", "lists": "Listák",
@ -314,7 +341,7 @@
"placeholder": { "placeholder": {
"content_warning": "Írja ide figyelmeztetését", "content_warning": "Írja ide figyelmeztetését",
"default_1": "Mi jár a fejedben?", "default_1": "Mi jár a fejedben?",
"reply_to_account": "Válasz: {0}", "reply_to_account": "Válasz erre: {0}",
"replying": "Válaszol" "replying": "Válaszol"
}, },
"polls": { "polls": {
@ -515,10 +542,12 @@
"hide_follower_count": "Követők/Követések számláló elrejtése", "hide_follower_count": "Követők/Követések számláló elrejtése",
"hide_news": "Hírek elrejtése", "hide_news": "Hírek elrejtése",
"hide_reply_count": "Visszajelzések számláló elrejtése", "hide_reply_count": "Visszajelzések számláló elrejtése",
"hide_tag_hover_card": "Lebegő kártya elrejtése",
"hide_translation": "Fordítás elrejtése", "hide_translation": "Fordítás elrejtése",
"hide_username_emojis": "Felhasználói emoji elrejtése", "hide_username_emojis": "Felhasználói emoji elrejtése",
"hide_username_emojis_description": "Elrejti a hangulatjeleket a felhasználónevek elől az idővonalakban. A hangulatjelek továbbra is láthatók lesznek a profiljukban.", "hide_username_emojis_description": "Elrejti a hangulatjeleket a felhasználónevek elől az idővonalakban. A hangulatjelek továbbra is láthatók lesznek a profiljukban.",
"label": "Preferenciák", "label": "Preferenciák",
"optimize_for_low_performance_device": "Alacsony teljesítményre optimalizálás",
"title": "Kísérleti képességek", "title": "Kísérleti képességek",
"use_star_favorite_icon": "Használja a csillag favikont", "use_star_favorite_icon": "Használja a csillag favikont",
"user_picker": "Felhasználók váltása", "user_picker": "Felhasználók váltása",
@ -555,6 +584,11 @@
"label": "Bejelentkezett felhasználók" "label": "Bejelentkezett felhasználók"
} }
}, },
"share_target": {
"description": "Az Elk konfigurálható úgy, hogy más alkalmazásokból is megoszthasson tartalmat, egyszerűen telepítse az Elket eszközére vagy számítógépére, és jelentkezzen be.",
"hint": "A tartalom megosztásához telepíteni kell az Elket, és be kell jelentkeznie.",
"title": "Oszd meg Elkkel"
},
"state": { "state": {
"attachments_exceed_server_limit": "A mellékletek száma meghaladta a bejegyzésenkénti korlátot.", "attachments_exceed_server_limit": "A mellékletek száma meghaladta a bejegyzésenkénti korlátot.",
"attachments_limit_error": "Túllépte a bejegyzésenkénti korlátot", "attachments_limit_error": "Túllépte a bejegyzésenkénti korlátot",

View file

@ -121,13 +121,13 @@
"block_account": { "block_account": {
"cancel": "Annulla", "cancel": "Annulla",
"confirm": "Blocca", "confirm": "Blocca",
"description": "Confermi di voler bloccare {0}?", "description": "Bloccare {0}?",
"title": "Blocca account" "title": "Blocca account"
}, },
"block_domain": { "block_domain": {
"cancel": "Annulla", "cancel": "Annulla",
"confirm": "Blocca", "confirm": "Blocca",
"description": "Confermi di voler bloccare {0}?", "description": "Bloccare {0}?",
"title": "Blocca dominio" "title": "Blocca dominio"
}, },
"common": { "common": {
@ -137,20 +137,20 @@
"delete_list": { "delete_list": {
"cancel": "Annulla", "cancel": "Annulla",
"confirm": "Elimina", "confirm": "Elimina",
"description": "Confermi di voler eliminare la lista \"{0}\"?", "description": "Eliminare la lista \"{0}\"?",
"title": "Elimina lista" "title": "Elimina lista"
}, },
"delete_posts": { "delete_posts": {
"cancel": "Annulla", "cancel": "Annulla",
"confirm": "Elimina", "confirm": "Elimina",
"description": "Confermi di voler eliminare questo post?", "description": "Eliminare questo post?",
"title": "Elimina post" "title": "Elimina post"
}, },
"mute_account": { "mute_account": {
"cancel": "Annulla", "cancel": "Annulla",
"confirm": "Silenzia", "confirm": "Silenzia",
"days": "giorni|giorno|giorni", "days": "giorni|giorno|giorni",
"description": "Confermi di voler silenziare {0}?", "description": "Silenziare {0}?",
"hours": "ore|ora|ore", "hours": "ore|ora|ore",
"minute": "minuti|minuto|minuti", "minute": "minuti|minuto|minuti",
"notifications": "Silenzia notifiche", "notifications": "Silenzia notifiche",
@ -160,13 +160,13 @@
"show_reblogs": { "show_reblogs": {
"cancel": "Annulla", "cancel": "Annulla",
"confirm": "Mostra", "confirm": "Mostra",
"description": "Confermi di voler mostrare i post potenziati da {0}?", "description": "Mostrare i post potenziati da {0}?",
"title": "Mostra potenziamenti" "title": "Mostra potenziamenti"
}, },
"unfollow": { "unfollow": {
"cancel": "Annulla", "cancel": "Annulla",
"confirm": "Smetti di seguire", "confirm": "Smetti di seguire",
"description": "Confermi di voler smettere di seguire?", "description": "Smettere di seguire?",
"title": "Smetti di seguire" "title": "Smetti di seguire"
} }
}, },
@ -306,6 +306,7 @@
"built_at": "Sviluppato {0}", "built_at": "Sviluppato {0}",
"compose": "Componi", "compose": "Componi",
"conversations": "Conversazioni", "conversations": "Conversazioni",
"docs": "Documentazione",
"explore": "Esplora", "explore": "Esplora",
"favourites": "Preferiti", "favourites": "Preferiti",
"federated": "Federata", "federated": "Federata",
@ -459,6 +460,7 @@
}, },
"language": { "language": {
"display_language": "Lingua interfaccia", "display_language": "Lingua interfaccia",
"how_to_contribute": "Come posso contribuire?",
"label": "Lingua", "label": "Lingua",
"post_language": "Lingua di pubblicazione", "post_language": "Lingua di pubblicazione",
"status": "Stato traduzione: {0}/{1} ({2}%)", "status": "Stato traduzione: {0}/{1} ({2}%)",

View file

@ -196,10 +196,10 @@
"title": "Produção de pré-visualização" "title": "Produção de pré-visualização"
}, },
"desc_highlight": "Espere alguns problemas e funcionalidades em falta.", "desc_highlight": "Espere alguns problemas e funcionalidades em falta.",
"desc_para1": "Obrigado pelo seu interesse em experimentar o Elk, a nossa aplicação web para o Mastodon, ainda em construção!", "desc_para1": "O Elk é uma ágil aplicação web para o Mastodon. Pode iniciar sessão com a sua conta Mastodon e utilizá-la para interagir com o fediverso.",
"desc_para2": "Estamos a trabalhar arduamente no seu desenvolvimento e melhoria ao longo do tempo.", "desc_para2": "O Elk é software de código aberto e estamos a aperfeiçoá-lo ativamente como um projeto comunitário. Junte-se a nós e vamos construí-lo juntos!",
"desc_para3": "Para ajudar a impulsionar o desenvolvimento, pode patrocinar a Equipa através do GitHub Sponsors. Esperamos que aprecie o Elk!", "desc_para3": "Para ajudar a impulsionar o desenvolvimento, pode patrocinar a Equipa através do GitHub Sponsors. Esperamos que aprecie o Elk!",
"desc_para4": "Elk é um software de código aberto. Se quiser ajudar a testar a aplicação, dando o seu feedback ou contributo,", "desc_para4": "Se quiser comunicar um erro, ajudar-nos a testar, dar feedback ou contribuir,",
"desc_para5": "pode encontrar-nos no GitHub", "desc_para5": "pode encontrar-nos no GitHub",
"desc_para6": "e participar.", "desc_para6": "e participar.",
"footer_team": "A Equipa do Elk", "footer_team": "A Equipa do Elk",
@ -306,6 +306,7 @@
"built_at": "Produzido {0}", "built_at": "Produzido {0}",
"compose": "Compor", "compose": "Compor",
"conversations": "Conversas", "conversations": "Conversas",
"docs": "Documentação",
"explore": "Explorar", "explore": "Explorar",
"favourites": "Favoritos", "favourites": "Favoritos",
"federated": "Federada", "federated": "Federada",
@ -459,6 +460,7 @@
}, },
"language": { "language": {
"display_language": "Idioma de Apresentação", "display_language": "Idioma de Apresentação",
"how_to_contribute": "Como contribuir?",
"label": "Idioma", "label": "Idioma",
"post_language": "Idioma de Publicação", "post_language": "Idioma de Publicação",
"status": "Estado da tradução: {0}/{1} ({2}%)", "status": "Estado da tradução: {0}/{1} ({2}%)",
@ -540,6 +542,7 @@
"hide_boost_count": "Esconder contagem de partilhas", "hide_boost_count": "Esconder contagem de partilhas",
"hide_favorite_count": "Esconder contagem de favoritos", "hide_favorite_count": "Esconder contagem de favoritos",
"hide_follower_count": "Esconder contagem de seguidores", "hide_follower_count": "Esconder contagem de seguidores",
"hide_gif_indi_on_posts": "Esconder indicador gif nas publicações",
"hide_news": "Esconder notícias", "hide_news": "Esconder notícias",
"hide_reply_count": "Esconder contagem de respostas", "hide_reply_count": "Esconder contagem de respostas",
"hide_tag_hover_card": "Esconder cartão flutuante de hashtag", "hide_tag_hover_card": "Esconder cartão flutuante de hashtag",
@ -591,7 +594,11 @@
}, },
"state": { "state": {
"attachments_exceed_server_limit": "O número de anexos excedeu o limite permitido por publicação.", "attachments_exceed_server_limit": "O número de anexos excedeu o limite permitido por publicação.",
"attachments_limit_audio_error": "Tamanho máximo para áudio excedido: {0}",
"attachments_limit_error": "Limite permitido por publicação excedido", "attachments_limit_error": "Limite permitido por publicação excedido",
"attachments_limit_image_error": "Tamanho máximo para imagem excedido: {0}",
"attachments_limit_unknown_error": "Tamanho máximo para ficheiro excedido: {0}",
"attachments_limit_video_error": "Tamanho máximo para video excedido: {0}",
"edited": "(Editado)", "edited": "(Editado)",
"editing": "Editando", "editing": "Editando",
"loading": "Carregando...", "loading": "Carregando...",
@ -612,6 +619,7 @@
"favourited_by": "Adicionada Aos Favoritos Por", "favourited_by": "Adicionada Aos Favoritos Por",
"filter_hidden_phrase": "Filtrada por", "filter_hidden_phrase": "Filtrada por",
"filter_show_anyway": "Mostrar mesmo assim", "filter_show_anyway": "Mostrar mesmo assim",
"gif": "GIF",
"img_alt": { "img_alt": {
"ALT": "ALT", "ALT": "ALT",
"desc": "Descrição", "desc": "Descrição",

View file

@ -162,7 +162,7 @@ export default defineNuxtConfig({
// our default translation server #76 // our default translation server #76
translateApi: '', translateApi: '',
// Use the instance where Elk has its Mastodon account as the default // Use the instance where Elk has its Mastodon account as the default
defaultServer: 'sector25.de', defaultServer: 'fedi.femby.page',
singleInstance: false, singleInstance: false,
}, },
storage: { storage: {
@ -197,17 +197,17 @@ export default defineNuxtConfig({
}, },
publicAssets: [ publicAssets: [
{ {
dir: '~/public/avatars', dir: resolve('./public/avatars'),
maxAge: 24 * 60 * 60 * 30, // 30 days maxAge: 24 * 60 * 60 * 30, // 30 days
baseURL: '/avatars', baseURL: '/avatars',
}, },
{ {
dir: '~/public/emojis', dir: resolve('./public/emojis'),
maxAge: 24 * 60 * 60 * 15, // 15 days, matching service worker maxAge: 24 * 60 * 60 * 15, // 15 days, matching service worker
baseURL: '/emojis', baseURL: '/emojis',
}, },
{ {
dir: '~/public/fonts', dir: resolve('./public/fonts'),
maxAge: 24 * 60 * 60 * 365, // 1 year (versioned) maxAge: 24 * 60 * 60 * 365, // 1 year (versioned)
baseURL: '/fonts', baseURL: '/fonts',
}, },

View file

@ -1,8 +1,8 @@
{ {
"name": "@elk-zone/elk", "name": "@elk-zone/elk",
"type": "module", "type": "module",
"version": "0.13.0", "version": "0.13.2",
"packageManager": "pnpm@8.15.3", "packageManager": "pnpm@8.15.5",
"license": "MIT", "license": "MIT",
"homepage": "https://elk.zone/", "homepage": "https://elk.zone/",
"main": "./nuxt.config.ts", "main": "./nuxt.config.ts",
@ -38,9 +38,9 @@
"@iconify/json": "^2.2.170", "@iconify/json": "^2.2.170",
"@iconify/utils": "^2.1.22", "@iconify/utils": "^2.1.22",
"@nuxt/devtools": "^1.0.8", "@nuxt/devtools": "^1.0.8",
"@nuxt/test-utils": "^3.11.0", "@nuxt/test-utils": "^3.12.0",
"@nuxtjs/color-mode": "^3.3.2", "@nuxtjs/color-mode": "^3.3.2",
"@nuxtjs/i18n": "^8.1.1", "@nuxtjs/i18n": "^8.2.0",
"@pinia/nuxt": "^0.5.1", "@pinia/nuxt": "^0.5.1",
"@tiptap/core": "2.2.4", "@tiptap/core": "2.2.4",
"@tiptap/extension-bold": "2.2.4", "@tiptap/extension-bold": "2.2.4",
@ -56,7 +56,7 @@
"@tiptap/starter-kit": "2.2.4", "@tiptap/starter-kit": "2.2.4",
"@tiptap/suggestion": "2.2.4", "@tiptap/suggestion": "2.2.4",
"@tiptap/vue-3": "2.2.4", "@tiptap/vue-3": "2.2.4",
"@unocss/nuxt": "^0.58.5", "@unocss/nuxt": "^0.58.9",
"@upstash/redis": "^1.27.1", "@upstash/redis": "^1.27.1",
"@vercel/kv": "^1.0.1", "@vercel/kv": "^1.0.1",
"@vue-macros/nuxt": "^1.6.0", "@vue-macros/nuxt": "^1.6.0",
@ -82,7 +82,7 @@
"iso-639-1": "^3.0.0", "iso-639-1": "^3.0.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"lru-cache": "^10.0.0", "lru-cache": "^10.0.0",
"masto": "^6.5.2", "masto": "^6.7.0",
"node-emoji": "^2.1.3", "node-emoji": "^2.1.3",
"nuxt-security": "^0.13.1", "nuxt-security": "^0.13.1",
"page-lifecycle": "^0.1.2", "page-lifecycle": "^0.1.2",
@ -94,14 +94,14 @@
"simple-git": "^3.19.1", "simple-git": "^3.19.1",
"slimeform": "^0.9.1", "slimeform": "^0.9.1",
"stale-dep": "^0.7.0", "stale-dep": "^0.7.0",
"std-env": "^3.3.3", "std-env": "^3.7.0",
"string-length": "^5.0.1", "string-length": "^5.0.1",
"tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log", "tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log",
"tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store", "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store",
"theme-vitesse": "^0.7.2", "theme-vitesse": "^0.7.2",
"tiny-decode": "^0.1.3", "tiny-decode": "^0.1.3",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"ufo": "^1.4.0", "ufo": "^1.5.3",
"ultrahtml": "^1.5.3", "ultrahtml": "^1.5.3",
"unimport": "^3.7.1", "unimport": "^3.7.1",
"vite-plugin-pwa": "^0.19.2", "vite-plugin-pwa": "^0.19.2",
@ -112,48 +112,47 @@
"ws": "^8.15.1" "ws": "^8.15.1"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^2.8.0", "@antfu/eslint-config": "^2.9.0",
"@antfu/ni": "^0.21.12", "@antfu/ni": "^0.21.12",
"@types/chroma-js": "^2.4.4", "@types/chroma-js": "^2.4.4",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@types/flat": "^5.0.5",
"@types/fnando__sparkline": "^0.3.7", "@types/fnando__sparkline": "^0.3.7",
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/prettier": "^2.7.3", "@types/prettier": "^3.0.0",
"@types/wicg-file-system-access": "^2020.9.8", "@types/wicg-file-system-access": "^2023.10.5",
"@types/ws": "^8.5.10", "@types/ws": "^8.5.10",
"@unlazy/nuxt": "^0.11.1", "@unlazy/nuxt": "^0.11.2",
"@unocss/eslint-config": "^0.58.5", "@unocss/eslint-config": "^0.58.9",
"@vue/test-utils": "^2.4.4", "@vue/test-utils": "2.4.5",
"bumpp": "^9.4.0", "bumpp": "^9.4.0",
"consola": "^3.2.3", "consola": "^3.2.3",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-format": "^0.1.0", "eslint-plugin-format": "^0.1.0",
"flat": "^5.0.2", "flat": "^6.0.1",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"lint-staged": "^14.0.1", "lint-staged": "^15.2.2",
"nuxt": "^3.10.3", "nuxt": "^3.11.2",
"prettier": "^3.0.3", "prettier": "^3.2.5",
"sharp": "^0.33.2", "sharp": "^0.33.3",
"sharp-ico": "^0.1.5", "sharp-ico": "^0.1.5",
"simple-git-hooks": "^2.10.0", "simple-git-hooks": "^2.11.1",
"tsx": "^4.7.1", "tsx": "^4.7.2",
"typescript": "^5.3.3", "typescript": "^5.4.4",
"vitest": "1.3.1", "vitest": "1.4.0",
"vue-tsc": "^1.8.27" "vue-tsc": "^2.0.10"
}, },
"pnpm": { "pnpm": {
"overrides": { "overrides": {
"unstorage": "^1.10.1" "unstorage": "^1.10.2"
}, },
"patchedDependencies": { "patchedDependencies": {
"nuxt-security@0.13.1": "patches/nuxt-security@0.13.1.patch" "nuxt-security@0.13.1": "patches/nuxt-security@0.13.1.patch"
} }
}, },
"resolutions": { "resolutions": {
"vitest": "1.3.1", "vitest": "1.4.0",
"vue": "^3.4.19" "vue": "^3.4.21"
}, },
"simple-git-hooks": { "simple-git-hooks": {
"pre-commit": "pnpm lint-staged" "pre-commit": "pnpm lint-staged"

View file

@ -1,18 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
const keys = useMagicKeys()
const { t } = useI18n() const { t } = useI18n()
useHydratedHead({ useHydratedHead({
title: () => t('nav.search'), title: () => t('nav.search'),
}) })
const search = ref<{ input?: HTMLInputElement }>() const search = ref<{ input?: HTMLInputElement }>()
watchEffect(() => { watchEffect(() => {
if (search.value?.input) if (search.value?.input)
search.value?.input?.focus() search.value?.input?.focus()
}) })
onActivated(() => onActivated(() => search.value?.input?.focus())
search.value?.input?.focus(),
)
onDeactivated(() => search.value?.input?.blur()) onDeactivated(() => search.value?.input?.blur())
watch(keys['/'], (v) => {
// focus on input when '/' is up to avoid '/' being typed
if (!v)
search.value?.input?.focus()
})
</script> </script>
<template> <template>

View file

@ -58,6 +58,13 @@ function handleShowCommit() {
@click="openPreviewHelp" @click="openPreviewHelp"
/> />
<SettingsItem
:text="$t('nav.docs')"
icon="i-ri:book-open-line"
to="https://docs.elk.zone/"
large target="_blank"
/>
<SettingsItem <SettingsItem
text="Mastodon" text="Mastodon"
icon="i-ri:mastodon-line" icon="i-ri:mastodon-line"
@ -117,7 +124,7 @@ function handleShowCommit() {
<SettingsItem <SettingsItem
v-for="team in elkTeamMembers" :key="team.github" v-for="team in elkTeamMembers" :key="team.github"
:text="team.display" :text="team.display"
:to="`https://github.com/sponsors/${team.github}`" :to="team.link"
external target="_blank" external target="_blank"
> >
<template #icon> <template #icon>

View file

@ -26,8 +26,18 @@ const status = computed(() => {
<h2 py2 font-bold text-xl flex="~ gap-1" items-center> <h2 py2 font-bold text-xl flex="~ gap-1" items-center>
{{ $t('settings.language.display_language') }} {{ $t('settings.language.display_language') }}
</h2> </h2>
<div>{{ status }}</div> <div>
{{ status }}
</div>
<SettingsLanguage select-settings /> <SettingsLanguage select-settings />
<NuxtLink
href="https://docs.elk.zone/guide/contributing"
target="_blank"
hover:underline text-primary inline-flex items-center gap-1
>
<span inline-block i-ri:information-line />
{{ $t('settings.language.how_to_contribute') }}
</NuxtLink>
</div> </div>
<div mt4> <div mt4>
<h2 font-bold text-xl flex="~ gap-1" items-center> <h2 font-bold text-xl flex="~ gap-1" items-center>

View file

@ -21,6 +21,12 @@ const userSettings = useUserSettings()
> >
{{ $t('settings.preferences.hide_alt_indi_on_posts') }} {{ $t('settings.preferences.hide_alt_indi_on_posts') }}
</SettingsToggleItem> </SettingsToggleItem>
<SettingsToggleItem
:checked="getPreferences(userSettings, 'hideGifIndicatorOnPosts')"
@click="togglePreferences('hideGifIndicatorOnPosts')"
>
{{ $t('settings.preferences.hide_gif_indi_on_posts') }}
</SettingsToggleItem>
<SettingsToggleItem <SettingsToggleItem
:checked="getPreferences(userSettings, 'hideAccountHoverCard')" :checked="getPreferences(userSettings, 'hideAccountHoverCard')"
@click="togglePreferences('hideAccountHoverCard')" @click="togglePreferences('hideAccountHoverCard')"

View file

@ -49,7 +49,7 @@ export default defineNuxtPlugin(({ $scrollToTop }) => {
whenever(logicAnd(isAuthenticated, notUsingInput, useMagicSequence(['g', 'i'])), () => navigateTo('/lists')) whenever(logicAnd(isAuthenticated, notUsingInput, useMagicSequence(['g', 'i'])), () => navigateTo('/lists'))
whenever(logicAnd(notUsingInput, useMagicSequence(['g', 's'])), () => navigateTo('/settings')) whenever(logicAnd(notUsingInput, useMagicSequence(['g', 's'])), () => navigateTo('/settings'))
whenever(logicAnd(isAuthenticated, notUsingInput, useMagicSequence(['g', 'p'])), () => navigateTo(`/${instanceDomain}/@${currentUser.value?.account.username}`)) whenever(logicAnd(isAuthenticated, notUsingInput, useMagicSequence(['g', 'p'])), () => navigateTo(`/${instanceDomain}/@${currentUser.value?.account.username}`))
whenever(logicAnd(notUsingInput, keys['/']), () => navigateTo('/search')) whenever(logicAnd(notUsingInput, computed(() => keys.current.size === 1), keys['/']), () => navigateTo('/search'))
const toggleFavouriteActiveStatus = () => { const toggleFavouriteActiveStatus = () => {
// TODO: find a better solution than clicking buttons... // TODO: find a better solution than clicking buttons...

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

View file

@ -9,9 +9,6 @@ const avatarsDir = resolve('./public/avatars/')
const sizes = [60, 100] const sizes = [60, 100]
async function download(url: string, fileName: string) { async function download(url: string, fileName: string) {
if (fs.existsSync(fileName))
return
console.log('downloading', fileName) console.log('downloading', fileName)
try { try {
const image = await ofetch(url, { responseType: 'arrayBuffer' }) const image = await ofetch(url, { responseType: 'arrayBuffer' })

View file

@ -1,5 +1,5 @@
import { Buffer } from 'node:buffer' import { Buffer } from 'node:buffer'
import flatten from 'flat' import { flatten, unflatten } from 'flat'
import { createResolver } from '@nuxt/kit' import { createResolver } from '@nuxt/kit'
import fs from 'fs-extra' import fs from 'fs-extra'
import { currentLocales } from '../config/i18n' import { currentLocales } from '../config/i18n'
@ -51,7 +51,7 @@ async function removeOutdatedTranslations() {
delete targetTranslations[key] delete targetTranslations[key]
} }
const unflattened = flatten.unflatten(targetTranslations) const unflattened = unflatten(targetTranslations)
await fs.writeFile( await fs.writeFile(
path, path,

View file

@ -1,5 +1,5 @@
import { Buffer } from 'node:buffer' import { Buffer } from 'node:buffer'
import flatten from 'flat' import { flatten } from 'flat'
import { createResolver } from '@nuxt/kit' import { createResolver } from '@nuxt/kit'
import fs from 'fs-extra' import fs from 'fs-extra'
import { countryLocaleVariants, currentLocales } from '../config/i18n' import { countryLocaleVariants, currentLocales } from '../config/i18n'

View file

@ -60,6 +60,8 @@ export function createNotificationOptions(
icon, icon,
lang: preferred_locale, lang: preferred_locale,
tag: notification_id, tag: notification_id,
// eslint-disable-next-line ts/prefer-ts-expect-error
// @ts-ignore error missing type, just ignore
timestamp: new Date().getTime(), timestamp: new Date().getTime(),
} }
@ -70,10 +72,13 @@ export function createNotificationOptions(
if (notification.account.avatar_static) if (notification.account.avatar_static)
notificationOptions.icon = notification.account.avatar_static notificationOptions.icon = notification.account.avatar_static
*/ */
if (notification.created_at) if (notification.created_at) {
// eslint-disable-next-line ts/prefer-ts-expect-error
// @ts-ignore error missing type, just ignore
notificationOptions.timestamp = new Date(notification.created_at).getTime() notificationOptions.timestamp = new Date(notification.created_at).getTime()
}
/* TODO: add spolier when actions available, checking also notification type /* TODO: add spoiler when actions available, checking also notification type
if (notification.status && (notification.status.spoilerText || notification.status.sensitive)) { if (notification.status && (notification.status.spoilerText || notification.status.sensitive)) {
if (notification.status.spoilerText) if (notification.status.spoilerText)
notificationOptions.body = notification.status.spoilerText notificationOptions.body = notification.status.spoilerText
@ -83,8 +88,11 @@ export function createNotificationOptions(
*/ */
if (notification.status) { if (notification.status) {
// notificationOptions.body = htmlToPlainText(notification.status.content) // notificationOptions.body = htmlToPlainText(notification.status.content)
if (notification.status.media_attachments && notification.status.media_attachments.length > 0 && notification.status.media_attachments[0].preview_url) if (notification.status.media_attachments && notification.status.media_attachments.length > 0 && notification.status.media_attachments[0].preview_url) {
// eslint-disable-next-line ts/prefer-ts-expect-error
// @ts-ignore error missing type, just ignore
notificationOptions.image = notification.status.media_attachments[0].preview_url notificationOptions.image = notification.status.media_attachments[0].preview_url
}
if (notification.type === 'favourite' || notification.type === 'reblog' || notification.type === 'mention') if (notification.type === 'favourite' || notification.type === 'reblog' || notification.type === 'mention')
notificationOptions.data.url = `${user.server}/@${user.account.username}/${notification.status.id}` notificationOptions.data.url = `${user.server}/@${user.account.username}/${notification.status.id}`

View file

@ -85,6 +85,9 @@ em-emoji-picker {
p:last-child { p:last-child {
--at-apply: mb-1; --at-apply: mb-1;
} }
pre {
--at-apply: whitespace-pre-wrap;
}
code { code {
--at-apply: bg-code text-code px1 py0.5 rounded text-0.875em leading-0.8em; --at-apply: bg-code text-code px1 py0.5 rounded text-0.875em leading-0.8em;
} }
@ -109,6 +112,9 @@ em-emoji-picker {
ul > li { ul > li {
--at-apply: pl-2; --at-apply: pl-2;
} }
blockquote {
--at-apply: border-primary border-l-4 border-solid pl-3 my-3 text-secondary;
}
.code-block { .code-block {
--at-apply: bg-code text-0.875em p3 mt-2 rounded overflow-auto --at-apply: bg-code text-0.875em p3 mt-2 rounded overflow-auto
leading-1.6em; leading-1.6em;