Merge branch 'main' of github.com:ayoayco/elk
Some checks are pending
ci / ci (push) Waiting to run
build & push docker container / docker (push) Waiting to run
ci / check-provenance (push) Waiting to run

This commit is contained in:
Ayo Ayco 2025-11-21 15:27:14 +01:00
commit 3f8a381c09
78 changed files with 10778 additions and 10178 deletions

View file

@ -17,12 +17,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
# workaround for npm registry key change
# ref. `pnpm@10.1.0` / `pnpm@9.15.4` cannot be installed due to key id mismatch · Issue #612 · nodejs/corepack
# - https://github.com/nodejs/corepack/issues/612#issuecomment-2629496091
- run: npm i -g corepack@latest && corepack enable
- uses: actions/setup-node@v4.4.0
- uses: actions/setup-node@v6.0.0
with:
node-version-file: .nvmrc

View file

@ -16,7 +16,7 @@ jobs:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Docker meta
id: metal
uses: docker/metadata-action@v5

22
.github/workflows/provenance.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: ci
on:
push:
branches:
- main
pull_request:
branches:
- main
permissions:
contents: read
jobs:
check-provenance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check provenance downgrades
uses: danielroe/provenance-action@41bcc969e579d9e29af08ba44fcbfdf95cee6e6c # v0.1.1
with:
fail-on-provenance-change: true

View file

@ -12,12 +12,12 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc

View file

@ -19,6 +19,6 @@ jobs:
name: Semantic Pull Request
steps:
- name: Validate PR title
uses: amannn/action-semantic-pull-request@v5.5.3
uses: amannn/action-semantic-pull-request@v6.1.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View file

@ -4,6 +4,7 @@ dist
.output
.pnpm-store
.nuxt
.data
.env
.DS_Store
.idea/

4
.npmrc
View file

@ -1,4 +0,0 @@
shamefully-hoist=true
shell-emulator=true
ignore-workspace-root-check=true
package-manager-strict=false

2
.nvmrc
View file

@ -1 +1 @@
22
lts/*

View file

@ -17,13 +17,12 @@ RUN apk add git --no-cache
# Prepare build deps ( ignore postinstall scripts for now )
COPY package.json ./
COPY .npmrc ./
COPY pnpm-lock.yaml ./
COPY patches ./patches
RUN pnpm i --frozen-lockfile --ignore-scripts
# Copy all source files
COPY . ./
RUN pnpm nuxt prepare
# Run full install with every postinstall script ( This needs project file )
RUN pnpm i --frozen-lockfile

View file

@ -34,7 +34,7 @@ One could put Elk behind popular reverse proxies with SSL Handling like Traefik,
1. got into new source dir: ```cd elk```
1. create local storage directory for settings: ```mkdir elk-storage```
1. adjust permissions of storage dir: ```sudo chown 911:911 ./elk-storage```
1. start container: ```docker-compose up --build -d```
1. start container: ```docker compose up --build -d```
> [!NOTE]
> The provided Dockerfile creates a container which will eventually run Elk as non-root user and create a persistent named Docker volume upon first start (if that volume does not yet exist). This volume is always created with root permission. Failing to change the permissions of ```/elk/data``` inside this volume to UID:GID 911 (as specified for Elk in the Dockerfile) will prevent Elk from storing it's config for user accounts. You either have to fix the permission in the created named volume, or mount a directory with the correct permission to ```/elk/data``` into the container.

19
app/augments.d.ts vendored Normal file
View file

@ -0,0 +1,19 @@
export {}
declare module '#app' {
interface PageMeta {
wideLayout?: boolean
}
interface RuntimeNuxtHooks {
'elk-logo:click': () => void
}
}
declare global {
namespace NodeJS {
interface Process {
mock?: Record<string, any>
}
}
}

View file

@ -30,12 +30,11 @@ const containerClass = computed(() => {
sticky top-0 z-20
pt="[env(safe-area-inset-top,0)]"
bg="[rgba(var(--rgb-bg-base),0.7)]"
class="native:lg:w-[calc(100vw-5rem)] native:xl:w-[calc(135%+(100vw-1200px)/2)]"
:class="{
'backdrop-blur': !getPreferences(userSettings, 'optimizeForLowPerformanceDevice'),
}"
>
<div flex justify-between gap-2 min-h-53px px5 py1 :class="{ 'xl:hidden': $route.name !== 'tag' }" class="native:xl:flex" border="b base">
<div flex justify-between gap-2 min-h-53px px5 py1 :class="{ 'xl:hidden': $route.name !== 'tag' }" border="b base">
<div flex gap-2 items-center :overflow-hidden="!noOverflowHidden ? '' : false" w-full>
<button
v-if="backOnSmallScreen || back"
@ -45,7 +44,7 @@ const containerClass = computed(() => {
>
<div text-lg i-ri:arrow-left-line class="rtl-flip" />
</button>
<div :truncate="!noOverflowHidden ? '' : false" flex w-full data-tauri-drag-region class="native-mac:justify-start native-mac:text-center">
<div :truncate="!noOverflowHidden ? '' : false" flex w-full class="native-mac:justify-start native-mac:text-center">
<slot name="title" />
</div>
<div sm:hidden h-7 w-1px />

View file

@ -13,9 +13,9 @@ watchEffect(() => {
}
const duration
= days.value * 24 * 60 * 60
+ hours.value * 60 * 60
+ minutes.value * 60
= days.value * 24 * 60 * 60
+ hours.value * 60 * 60
+ minutes.value * 60
if (duration <= 0) {
isValid.value = false

View file

@ -82,7 +82,7 @@ function handleFavouritedBoostedByClose() {
>
<ModalMediaPreview v-if="isMediaPreviewOpen" @close="closeMediaPreview()" />
</ModalDialog>
<ModalDialog v-model="isEditHistoryDialogOpen" max-w-125>
<ModalDialog v-model="isEditHistoryDialogOpen" :focus-first-element="false" max-w-125>
<StatusEditPreview v-if="statusEdit" :edit="statusEdit" />
</ModalDialog>
<ModalDialog v-model="isCommandPanelOpen" max-w-fit flex>

View file

@ -10,6 +10,7 @@ const {
closeByMask = true,
useVIf = true,
keepAlive = false,
focusFirstElement = true,
} = defineProps<{
// level of depth
zIndex?: number
@ -21,6 +22,8 @@ const {
keepAlive?: boolean
// The aria-labelledby id for the dialog.
dialogLabelledBy?: string
// Whether to focus on the first element when the modal opens.
focusFirstElement?: boolean
}>()
const emit = defineEmits<{
@ -45,6 +48,7 @@ const { activate } = useFocusTrap(elDialogRoot, {
escapeDeactivates: true,
preventScroll: true,
returnFocusOnDeactivate: true,
initialFocus: focusFirstElement ? undefined : false,
})
defineExpose({

View file

@ -18,7 +18,7 @@ router.afterEach(() => {
</script>
<template>
<div flex justify-between sticky top-0 bg-base z-1 py-4 native:py-7 data-tauri-drag-region>
<div flex justify-between sticky top-0 bg-base z-1 py-4>
<NuxtLink
flex items-end gap-3
py2 px-5

View file

@ -122,9 +122,9 @@ export const useCommandRegistry = defineStore('command', () => {
const fuse = (lastScope === scope && lastFuse)
? lastFuse
: new Fuse(cmds, {
keys: ['scope', 'name', 'description'],
includeScore: true,
})
keys: ['scope', 'name', 'description'],
includeScore: true,
})
lastScope = scope
lastFuse = fuse

View file

@ -67,6 +67,11 @@ const sanitizer = sanitize({
li: {
value: keep,
},
// Hollo supports <ruby> tags
// https://github.com/fedify-dev/hollo/blob/80e7184aa805f579be8712ff9231be655343c661/src/xss.ts#L92-L94
ruby: {},
rp: {},
rt: {},
})
/**

View file

@ -17,6 +17,7 @@ export const shikiParser: Parser = (options) => {
return promise ?? []
if (!parser)
// @ts-expect-error will be fixed when shiki upgrades
parser = createParser(highlighter)
return parser(options)

View file

@ -16,9 +16,9 @@ const instance = instanceStorage.value[currentServer.value]
</script>
<template>
<div h-full :data-mode="isHydrated && isGrayscale ? 'grayscale' : ''" data-tauri-drag-region>
<main flex w-full mxa lg:max-w-80rem class="native:grid native:sm:grid-cols-[auto_1fr] native:lg:grid-cols-[auto_minmax(600px,2fr)_1fr]">
<aside class="native:w-auto w-1/8 md:w-1/6 lg:w-1/5 xl:w-1/4 zen-hide" hidden sm:flex justify-end xl:me-4 native:me-0 relative>
<div h-full :data-mode="isHydrated && isGrayscale ? 'grayscale' : ''">
<main flex w-full mxa lg:max-w-80rem>
<aside class="w-1/8 md:w-1/6 lg:w-1/5 xl:w-1/4 zen-hide" hidden sm:flex justify-end xl:me-4 relative>
<div sticky top-0 w-20 xl:w-100 h-100dvh flex="~ col" lt-xl-items-center>
<slot name="left">
<div flex="~ col" overflow-y-auto justify-between h-full max-w-full overflow-x-hidden>
@ -60,7 +60,7 @@ const instance = instanceStorage.value[currentServer.value]
<NavBottom v-if="isHydrated" sm:hidden />
</div>
</div>
<aside v-if="isHydrated && !wideLayout" class="hidden lg:w-1/5 xl:w-1/4 sm:none xl:block native:w-full zen-hide">
<aside v-if="isHydrated && !wideLayout" class="hidden lg:w-1/5 xl:w-1/4 sm:none xl:block zen-hide">
<div sticky top-0 h-100dvh flex="~ col" gap-2 py3 ms-2>
<slot name="right">
<SearchWidget mt-4 mx-1 hidden xl:block />

View file

@ -19,11 +19,11 @@ export default defineNuxtPlugin(async (nuxt) => {
if (!supportLanguages.includes(lang.value))
userSettings.value.language = getDefaultLanguage(supportLanguages)
if (lang.value !== i18n.locale)
if (lang.value !== i18n.locale.value)
await setLocale(userSettings.value.language as Locale)
watch([lang, isHydrated], () => {
if (isHydrated.value && lang.value !== i18n.locale)
if (isHydrated.value && lang.value !== i18n.locale.value)
setLocale(lang.value)
}, { immediate: true })
}

View file

@ -5,7 +5,7 @@ export { version } from '../package.json'
/**
* Environment variable `PULL_REQUEST` provided by Netlify.
* @see {@link https://docs.netlify.com/configure-builds/environment-variables/#git-metadata}
* @see {@link https://docs.netlify.com/build/configure-builds/environment-variables/#git-metadata}
*
* Whether triggered by a GitHub PR
*/
@ -13,7 +13,7 @@ export const isPR = process.env.PULL_REQUEST === 'true'
/**
* Environment variable `BRANCH` provided by Netlify.
* @see {@link https://docs.netlify.com/configure-builds/environment-variables/#git-metadata}
* @see {@link https://docs.netlify.com/build/configure-builds/environment-variables/#git-metadata}
*
* Git branch
*/
@ -21,7 +21,7 @@ export const gitBranch = process.env.BRANCH
/**
* Environment variable `CONTEXT` provided by Netlify.
* @see {@link https://docs.netlify.com/configure-builds/environment-variables/#build-metadata}
* @see {@link https://docs.netlify.com/build/configure-builds/environment-variables/#build-metadata}
*
* Whether triggered by PR, `deploy-preview` or `dev`.
*/

View file

@ -75,13 +75,11 @@ 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: 'العربية',
@ -90,8 +88,8 @@ const locales: LocaleObjectData[] = [
const name = new Intl.PluralRules('ar-EG').select(choice)
return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name]
},
} satisfies LocaleObjectData),
({
} satisfies LocaleObjectData,
{
code: 'ckb',
file: 'ckb.json',
name: 'کوردیی ناوەندی',
@ -100,8 +98,8 @@ const locales: LocaleObjectData[] = [
const name = new Intl.PluralRules('ckb').select(choice)
return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name]
},
} satisfies LocaleObjectData),
({
} satisfies LocaleObjectData,
{
code: 'fa-IR',
file: 'fa-IR.json',
name: 'فارسی',
@ -110,9 +108,8 @@ const locales: LocaleObjectData[] = [
const name = new Intl.PluralRules('fa-IR').select(choice)
return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name]
},
} satisfies LocaleObjectData),
} satisfies LocaleObjectData,
{
// @ts-expect-error ca used as placeholder
code: 'ca',
file: 'ca.json',
name: 'Català',
@ -153,7 +150,6 @@ const locales: LocaleObjectData[] = [
name: 'Nederlands',
},
{
// @ts-expect-error es used as placeholder
code: 'es',
file: 'es.json',
name: 'Español',
@ -207,7 +203,6 @@ const locales: LocaleObjectData[] = [
},
},
{
// @ts-expect-error pt used as placeholder
code: 'pt',
file: 'pt.json',
name: 'Português',
@ -242,6 +237,11 @@ const locales: LocaleObjectData[] = [
file: 'it-IT.json',
name: 'Italiano',
},
{
code: 'sv',
file: 'sv.json',
name: 'Svenska',
},
{
code: 'th-TH',
file: 'th-TH.json',
@ -283,7 +283,7 @@ function buildLocales() {
acc.push(data)
}
return acc
}, <LocaleObjectData[]>[])
}, [] as LocaleObjectData[])
return useLocales.sort((a, b) => a.code.localeCompare(b.code))
}
@ -313,7 +313,7 @@ export const datetimeFormats = Object.values(currentLocales).reduce((acc, data)
}
return acc
}, <DateTimeFormats>{})
}, {} as DateTimeFormats)
export const numberFormats = Object.values(currentLocales).reduce((acc, data) => {
const numberFormats = data.numberFormats
@ -345,7 +345,7 @@ export const numberFormats = Object.values(currentLocales).reduce((acc, data) =>
}
return acc
}, <NumberFormats>{})
}, {} as NumberFormats)
export const pluralRules = Object.values(currentLocales).reduce((acc, data) => {
const pluralRule = data.pluralRule
@ -355,4 +355,4 @@ export const pluralRules = Object.values(currentLocales).reduce((acc, data) => {
}
return acc
}, <PluralizationRules>{})
}, {} as PluralizationRules)

View file

@ -6,5 +6,10 @@ services:
build:
context: .
dockerfile: Dockerfile
volumes:
# make sure this directory has the same ownership as the elk user from the Dockerfile
# otherwise Elk will not be able to store configs for accounts
# e.g., mkdir ./elk-storage; sudo chown 911:911 ./elk-storage
- './elk-storage:/elk/data'
ports:
- 5314:5314

View file

@ -1,35 +1,41 @@
export default defineAppConfig({
docus: {
seo: {
title: 'Elk',
description: 'A nimble Mastodon web client.',
image: 'https://docs.elk.zone/elk-screenshot.png',
socials: {
// twitter: 'elk_zone',
github: 'elk-zone/elk',
mastodon: {
label: 'Mastodon',
icon: 'IconMastodon',
href: 'https://elk.zone/@elk@webtoo.ls',
},
description: 'A nimble Mastodon web client with modern features and elegant design.',
},
header: {
title: 'Elk',
logo: {
alt: 'Elk',
light: '/logo.svg',
dark: '/logo.svg',
},
aside: {
level: 0,
exclude: [],
},
header: {
logo: true,
showLinkIcon: true,
exclude: [],
},
footer: {
iconLinks: [
},
socials: {
github: 'https://github.com/elk-zone/elk',
mastodon: 'https://elk.zone/@elk@webtoo.ls',
},
github: {
url: 'https://github.com/elk-zone/elk',
branch: 'main',
rootDir: 'docs',
},
toc: {
title: 'On this page',
bottom: {
title: 'Community',
links: [
{
href: 'https://nuxt.com',
icon: 'IconNuxtLabs',
icon: 'i-ph-shooting-star-duotone',
label: 'Star on GitHub',
to: 'https://github.com/elk-zone/elk',
target: '_blank',
},
{
href: 'https://m.webtoo.ls/@elk',
icon: 'IconMastodon',
icon: 'i-simple-icons-mastodon',
label: 'Follow on Mastodon',
to: 'https://elk.zone/@elk@webtoo.ls',
target: '_blank',
},
],
},

View file

@ -1,5 +0,0 @@
<template>
<AppLayout>
<NuxtPage />
</AppLayout>
</template>

View file

@ -0,0 +1,4 @@
/* Elk brand colors for light and dark modes */
:root {
--ui-primary: #f0943c;
}

View file

@ -1,9 +1,9 @@
<script setup lang="ts">
import type { TranslationStatus } from '../../types'
import type { TranslationStatus } from '../../../types'
const localesStatuses: TranslationStatus = await import('../../translation-status.json').then(m => m.default)
const localesStatuses: TranslationStatus = await import('../../../translation-status.json').then(m => m.default)
const totalReference = localesStatuses.en.total
const totalReference = localesStatuses.en!.total
type Tab = 'missing' | 'outdated'
@ -32,7 +32,7 @@ const missingEntries = computed<string[]>(() => {
if (hidden.value || !currentLocale.value || localeTab.value !== 'missing')
return []
return localesStatuses[locale.value].missing
return localesStatuses[locale.value]!.missing
})
const outdatedEntries = computed<string[]>(() => {

View file

@ -1,36 +0,0 @@
---
title: Elk
navigation: false
layout: page
---
::block-hero
---
cta:
- Read more
- /guide
secondary:
- Try it out →
- https://elk.zone
---
#title
Elk
#description
An in-progress, nimble Mastodon web client
#support
![Screenshot of Elk](/screenshot.png)
#extra
::list
- markdown support
- code blocks
- reordering and connecting posts in timelines
- multi account
- GitHub HTML cards
- and more...
::
::

View file

@ -1,3 +1,8 @@
---
title: Introduction
description: Get started with Elk, the nimble Mastodon web client.
---
# Introduction
## What is Elk?
@ -16,7 +21,7 @@ Elk provides some features not available through the standard Mastodon web app i
You can use Elk right in your browser.
On a mobile device, you can install the app to your home screen right from your browser for easy access.
(This is called a Progressive Web App, or [PWA](../80.pwa.md).)
(This is called a Progressive Web App, or [PWA](../pwa.md).)
Want to try it out?
Visit https://elk.zone, type in your Mastodon server address, then log in.
@ -50,7 +55,7 @@ Using a client, you can
- View, add, or participate in polls
- Follow, unfollow, mute, and block accounts
::alert{type="info"}
::callout{type="info"}
**Note:** Not all clients provide all features.
::

View file

@ -1,6 +1,11 @@
---
title: Features
description: Discover the features that make Elk a delightful Mastodon client.
---
# Features
::alert{type=warning}
::callout{type=warning}
🚧 This section is a work in progress. 🚧
::
@ -23,7 +28,7 @@ Elk renders basic Markdown-like text markup in post texts as the expected HTML.
- Use one asterisk (`*`) before and after a word or phrase to *italicize** the text.
- Surround the text with two asterisks (`**word**`) to **bold** it.
::alert{type="warning"}
::callout{type="warning"}
Many apps do not support Markdown in posts.
Mastodon itself does not support Markdown in posts.

View file

@ -1,3 +1,8 @@
---
title: Contributing
description: Learn how to contribute to Elk and help build the future of Mastodon clients.
---
# Contributing
We're really excited that you're interested in contributing to Elk! Before submitting your contribution, please read through the following guide.

View file

@ -1,3 +1,8 @@
---
title: Sponsoring
description: Support the development of Elk by sponsoring the team.
---
# Sponsoring
If you're enjoying the app, consider sponsoring our team:

View file

@ -1,4 +1,7 @@
# Netlify and Cloudflare
---
title: Netlify and Cloudflare
description: Deploy your own Elk instance.
---
Want to host Elk for your Mastodon instance? You came to the right place!

View file

@ -1,3 +1,8 @@
---
title: Progressive Web App
description: Learn about Elk's PWA capabilities and how to install it on your device.
---
# PWA
Elk provides a PWA (Progressive Web App) that can be installed on your desktop/device. This allows you to use Elk as a native app on your device, and it will work offline.

View file

@ -1,3 +1,8 @@
---
title: Privacy Policy
description: Elk's privacy policy and data handling practices.
---
# Privacy
> Last updated January 27, 2023
@ -8,7 +13,7 @@ This privacy notice for Elk describes how we handle your information when you:
- Download and use our mobile or desktop application (Elk)
::alert{type=warning}
::callout{type="warning"}
Elk is [open source](https://github.com/elk-zone/elk) and other websites that link to this privacy notice may not be affiliated with Elk or bound by this policy.
::

158
docs/content/index.md Executable file
View file

@ -0,0 +1,158 @@
---
seo:
title: "Elk - A Nimble Mastodon Web Client"
description: A nimble Mastodon web client that provides a fresh and intuitive social media experience with modern features and elegant design.
ogImage: https://docs.elk.zone/elk-screenshot.png
---
::u-page-hero
---
orientation: horizontal
---
:::div{.hidden.lg:flex.items-center.justify-center}
![Elk Screenshot](/screenshot.png){.rounded-lg.shadow-lg}
:::
#title
A Nimble [Mastodon Web Client]{.text-primary}
#description
Experience Mastodon like never before. Elk brings you a fresh, intuitive interface with modern features that make social networking delightful.
#links
:::u-button
---
icon: i-ph-rocket-launch-duotone
size: xl
to: /guide
---
Read the docs
:::
:::u-button
---
icon: i-ph-arrow-square-out-duotone
size: xl
variant: outline
to: https://elk.zone
target: _blank
---
Try it live
:::
::
::u-page-section
#title
Everything you need for the perfect [Mastodon experience]{.text-primary}
#features
:::u-page-card
---
spotlight: true
icon: i-ph-markdown-logo-duotone
to: /guide/features
---
#title
Rich Content Support
#description
Full Markdown support with syntax highlighting, emoji reactions, and rich media previews that bring your posts to life.
:::
:::u-page-card
---
spotlight: true
icon: i-ic-twotone-view-timeline
to: /guide/features
---
#title
Smart Timeline Management
#description
Reorder and connect posts in your timeline with intelligent grouping and enhanced notification management.
:::
:::u-page-card
---
spotlight: true
icon: i-ph-users-duotone
to: /guide/features
---
#title
Multi-Account Support
#description
Seamlessly manage multiple Mastodon accounts with quick switching and unified notifications.
:::
:::u-page-card
---
spotlight: true
icon: i-ph-github-logo-duotone
to: /guide/features
---
#title
GitHub Integration
#description
Beautiful HTML cards for GitHub links with repository previews and rich metadata display.
:::
:::u-page-card
---
spotlight: true
icon: i-ph-device-mobile-duotone
to: /pwa
---
#title
Progressive Web App
#description
Install Elk on any device for a native-like experience with offline support and push notifications.
:::
:::u-page-card
---
spotlight: true
icon: i-ph-heart-duotone
to: /guide/contributing
---
#title
Open Source & Community Driven
#description
Built with love by the community. Contribute to the future of federated social media.
:::
::
::u-page-section
---
orientation: horizontal
---
#title
Ready to dive in?
#description
Join thousands of users who have already discovered a better way to experience Mastodon. Get started in minutes with our comprehensive documentation.
#links
:::u-button
---
icon: i-ph-book-open-duotone
size: xl
to: /guide
---
Read the docs
:::
:::u-button
---
icon: i-ph-github-logo-duotone
size: xl
variant: outline
to: https://github.com/elk-zone/elk
target: _blank
---
View on GitHub
:::
::

View file

@ -1,5 +1,17 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
extends: '@nuxt-themes/docus',
extends: ['docus'],
css: ['~/assets/css/main.css'],
site: {
name: 'Elk',
url: 'https://docs.elk.zone',
},
llms: {
domain: 'https://docs.elk.zone',
},
vite: {
optimizeDeps: {

View file

@ -3,16 +3,14 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "nuxi dev",
"build": "nuxi build",
"generate": "nuxi generate",
"preview": "nuxi preview"
"dev": "nuxt dev",
"build": "nuxt build",
"generate": "nuxt generate",
"preview": "nuxt preview"
},
"dependencies": {
"theme-colors": "^0.1.0"
},
"devDependencies": {
"@nuxt-themes/docus": "^1.15.1",
"nuxt": "^3.18.1"
"better-sqlite3": "^12.4.1",
"docus": "^4.0.0",
"nuxt": "^4.1.2"
}
}

View file

@ -1,17 +0,0 @@
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 },
})

View file

@ -7,6 +7,8 @@
"route_loaded": "Page {0} chargée"
},
"account": {
"authorize": "Autoriser l'abonnement",
"authorized": "Vous avez autorisé la demande",
"avatar_description": "Avatar de {0}",
"blocked_by": "Ce compte vous a bloqué",
"blocked_domains": "Domaines bloqués",
@ -25,6 +27,7 @@
"follows_you": "@:account.follow_back",
"go_to_profile": "Aller à son profil",
"joined": "a rejoint",
"lock": "Verrouiller",
"moved_title": "a indiqué que son nouveau compte est désormais :",
"muted_users": "Comptes masqués",
"muting": "Masqué·e",
@ -37,7 +40,10 @@
"profile_description": "En-tête du profil de {0}",
"profile_personal_note": "Note personnelle",
"profile_unavailable": "Profil non accessible",
"reject": "Rejeter l'abonnement",
"rejected": "Vous avez rejeté la demande",
"request_follow": "Demander à suivre",
"requested": "{0} a demandé à vous suivre",
"unblock": "Débloquer",
"unfollow": "Ne plus suivre",
"unmute": "Réafficher",
@ -52,6 +58,7 @@
"boost": "Partager",
"boost_count": "{0}",
"boosted": "Partagé",
"clear": "Effacer",
"clear_publish_failed": "Effacer les erreurs de publication",
"clear_save_failed": "Effacer les erreurs de sauvegarde",
"clear_upload_failed": "Effacer les erreurs de téléversement de fichier",
@ -66,8 +73,10 @@
"favourited": "J'aime",
"more": "Plus",
"next": "Suivant",
"open_image_preview_dialog": "Ouvrir le dialogue d'aperçu de l'image",
"prev": "Précédent",
"publish": "Publier",
"publish_thread": "Publier le fil",
"reply": "Répondre",
"reply_count": "{0}",
"reset": "Réinitialiser",
@ -115,12 +124,14 @@
"block_account": {
"cancel": "Annuler",
"confirm": "Bloquer",
"description": "Voulez-vous vraiment bloquer {0} ?"
"description": "Voulez-vous vraiment bloquer {0} ?",
"title": "Bloquer le compte"
},
"block_domain": {
"cancel": "Annuler",
"confirm": "Bloquer",
"description": "Voulez-vous vraiment bloquer {0} ?"
"description": "Voulez-vous vraiment bloquer {0} ?",
"title": "Bloquer le domaine"
},
"common": {
"cancel": "Non",
@ -129,27 +140,37 @@
"delete_list": {
"cancel": "Annuler",
"confirm": "Supprimer",
"description": "Voulez-vous vraiment supprimer la liste \"{0}\" ?"
"description": "Voulez-vous vraiment supprimer la liste \"{0}\" ?",
"title": "Supprimer la liste"
},
"delete_posts": {
"cancel": "Annuler",
"confirm": "Supprimer",
"description": "Voulez-vous vraiment supprimer ce message ?"
"description": "Voulez-vous vraiment supprimer ce message ?",
"title": "Supprimer le message"
},
"mute_account": {
"cancel": "Annuler",
"confirm": "Mettre en sourdine",
"description": "Voulez-vous vraiment mettre en sourdine {0} ?"
"days": "jour|jour|jour",
"description": "Voulez-vous vraiment mettre en sourdine {0} ?",
"hours": "heures|heures|heures",
"minute": "minutes|minutes|minutes",
"notifications": "Mettre en sourdine les notifications",
"specify_duration": "Spécifier la durée de la mise en sourdine",
"title": "Mettre en sourdine le compte"
},
"show_reblogs": {
"cancel": "Annuler",
"confirm": "Afficher",
"description": "Voulez-vous vraiment afficher les partages de {0} ?"
"description": "Voulez-vous vraiment afficher les partages de {0} ?",
"title": "Afficher les partages"
},
"unfollow": {
"cancel": "Annuler",
"confirm": "Se désabonner",
"description": "Voulez-vous vraiment vous désabonner ?"
"description": "Voulez-vous vraiment vous désabonner ?",
"title": "Se désabonner"
}
},
"conversation": {
@ -202,9 +223,12 @@
"error": "Il y a eu une erreur lors de la création de la liste",
"error_prefix": "Erreur :",
"list_title_placeholder": "Nom de la liste",
"manage": "Gérer les listes",
"modify_account": "Modifier les listes de ce compte",
"remove_account": "Supprimer ce compte de listes",
"save": "Enregistrer les changements"
"save": "Enregistrer les changements",
"search_following_desc": "Chercher des personnes que vous suivez",
"search_following_placeholder": "Chercher parmi les personnes que vous suivez"
},
"magic_keys": {
"dialog_header": "Raccourcis clavier",
@ -214,14 +238,26 @@
"command_mode": "Mode commande",
"compose": "Composer",
"favourite": "J'aime",
"search": "Rechercher",
"show_new_items": "Afficher les nouveaux éléments",
"title": "Actions"
},
"media": {
"title": "Média"
},
"navigation": {
"go_to_bookmarks": "Signets",
"go_to_conversations": "Conversations",
"go_to_explore": "Explorer",
"go_to_favourites": "Favoris",
"go_to_federated": "Fédérés",
"go_to_home": "Accueil",
"go_to_lists": "Listes",
"go_to_local": "Local",
"go_to_notifications": "Notifications",
"go_to_profile": "Profil",
"go_to_search": "Rechercher",
"go_to_settings": "Paramètres",
"next_status": "Message suivant",
"previous_status": "Message précédent",
"shortcut_help": "Aide sur les raccourcis",
@ -276,13 +312,16 @@
"built_at": "Dernière compilation {0}",
"compose": "Composer",
"conversations": "Conversations",
"docs": "Documentation",
"explore": "Explorer",
"favourites": "Aimés",
"federated": "Fédérés",
"hashtags": "Hashtags",
"home": "Accueil",
"list": "Liste",
"lists": "Listes",
"local": "Local",
"more_menu": "Plus d'options",
"muted_users": "Comptes masqués",
"notifications": "Notifications",
"privacy": "Données privées",
@ -297,10 +336,12 @@
"zen_mode": "Mode Zen"
},
"notification": {
"and": "et",
"favourited_post": "a aimé votre message",
"followed_you": "vous suit",
"followed_you_count": "{0} personnes vous suivent|{0} personne vous suit|{0} personnes vous suivent",
"missing_type": "MISSING notification.type:",
"others": "{0} personnes|{0} personne|{0} personnes",
"reblogged_post": "a relayé votre message",
"reported": "{0} a signalé {1}",
"request_to_follow": "vous demande de le suivre",
@ -417,6 +458,8 @@
"label": "Paramètres de compte"
},
"interface": {
"bottom_nav": "Navigation inférieure",
"bottom_nav_instructions": "Choisissez jusqu'à cinq boutons de navigation inférieure favoris. Doit inclure le bouton \"Plus d'options\".",
"color_mode": "Couleur de thème",
"dark_mode": "Mode sombre",
"default": " (par défaut)",
@ -428,6 +471,7 @@
},
"language": {
"display_language": "Langue d'affichage",
"how_to_contribute": "Comment contribuer ?",
"label": "Langue",
"post_language": "Langue de publication",
"status": "État de la traduction : {0}/{1} ({2} %)",
@ -495,6 +539,8 @@
},
"notifications_settings": "Notifications",
"preferences": {
"embedded_media": "Lecteur multimédia intégré",
"embedded_media_description": "Affichez un lecteur intégré au lieu de la carte d'aperçu normale lors de l'expansion des liens de streaming de supports partagés.",
"enable_autoplay": "Activer la lecture automatique",
"enable_data_saving": "Activer l'économie de données",
"enable_data_saving_description": "Economise les données en évitant le chargement automatique des médias.",
@ -507,13 +553,16 @@
"hide_boost_count": "Masquer les compteurs de partages",
"hide_favorite_count": "Masquer les compteurs de favoris",
"hide_follower_count": "Masquer les compteurs d'abonné·e·s",
"hide_gif_indi_on_posts": "Masquer l'indicateur de gif sur les messages",
"hide_news": "Masquer les actualités",
"hide_reply_count": "Masquer les compteurs de réponses",
"hide_translation": "Masquer traduction",
"hide_username_emojis": "Masquer les emojis sur le nom d'utilisateur",
"hide_username_emojis_description": "Masque les emojis des noms d'utilisateur dans la timeline. \nLes emojis seront toujours visibles sur leurs profils.",
"label": "Préférences",
"optimize_for_low_performance_device": "Optimiser pour un dispositif à faible performance",
"title": "Fonctionnalités expérimentales",
"unmute_videos": "Son de vidéo par défaut",
"use_star_favorite_icon": "Utiliser l'icône de l'étoile en favoris",
"user_picker": "User Picker",
"user_picker_description": "Affiche tous les avatars des comptes connectés en bas à gauche afin que vous puissiez basculer rapidement entre eux.",
@ -556,7 +605,11 @@
},
"state": {
"attachments_exceed_server_limit": "Le nombre de pièces jointes a dépassé la limite par message.",
"attachments_limit_audio_error": "Taille maximum d'audio dépassée : {0}",
"attachments_limit_error": "Limite par publication dépassée",
"attachments_limit_image_error": "Taille maximum d'image dépassée : {0}",
"attachments_limit_unknown_error": "Taille maximum de fichier dépassée : {0}",
"attachments_limit_video_error": "Taille maximum de vidéo dépassée : {0}",
"edited": "(Édité)",
"editing": "Édition",
"loading": "Chargement...",
@ -573,15 +626,18 @@
},
"boosted_by": "Partagé par",
"edited": "Edité {0}",
"embedded_warning": "Lire ceci peut révéler votre adresse IP à d'autres.",
"favourited_by": "Aimé par",
"filter_hidden_phrase": "Filtré par",
"filter_show_anyway": "Montrer coûte que coûte",
"gif": "GIF",
"img_alt": {
"ALT": "ALT",
"desc": "Description",
"dismiss": "Fermer",
"read": "Lire la description de {0}"
},
"pinned": "Messages épinglés",
"poll": {
"count": "{0} votes",
"ends": "se clôt {0}",
@ -663,6 +719,7 @@
"year_past": "il y a 0 année|l'année dernière|il y a {n} années"
},
"timeline": {
"no_posts": "Pas de messages ici !",
"show_new_items": "Voir le nouveau message|Voir les {v} nouveaux messages",
"view_older_posts": "Les messages plus anciens d'autres instances peuvent ne pas être affichés."
},
@ -675,6 +732,7 @@
"add_emojis": "Ajouter des émoticônes",
"add_media": "Ajouter des images, une vidéo ou un fichier audio",
"add_publishable_content": "Ajouter du contenu à publier",
"add_thread_item": "Ajouter un message au fil",
"change_content_visibility": "Ajuster la confidentialité du message",
"change_language": "Changer la langue",
"emoji": "Emoji",
@ -684,6 +742,8 @@
"open_editor_tools": "Outils d'édition",
"pick_an_icon": "Choisir une icône",
"publish_failed": "Fermez les messages ayant échoué en haut de l'éditeur pour republier les messages",
"remove_thread_item": "Supprimer le message du fil",
"start_thread": "Commencer un fil",
"toggle_bold": "Appliquer/retirer le gras",
"toggle_code_block": "Ajouter un bloc de code",
"toggle_italic": "Appliquer/retirer l'italique"

View file

@ -27,6 +27,7 @@
"follows_you": "Mengikutimu",
"go_to_profile": "Buka profil",
"joined": "Bergabung",
"lock": "Kunci",
"moved_title": "telah mengindikasikan bahwa akun baru mereka sekarang:",
"muted_users": "Pengguna dibisukan",
"muting": "Bisukan",
@ -57,6 +58,7 @@
"boost": "Dukung",
"boost_count": "{0}",
"boosted": "Didukung",
"clear": "Bersihkan",
"clear_publish_failed": "Hapus error publikasi",
"clear_save_failed": "Hapus kesalahan penyimpanan",
"clear_upload_failed": "Hapus kesalahan pengunggahan file",
@ -71,8 +73,10 @@
"favourited": "Difavoritkan",
"more": "Selengkapnya",
"next": "Selanjutnya",
"open_image_preview_dialog": "Buka dialog pratinjau gambar",
"prev": "Sebelumnya",
"publish": "Terbitkan",
"publish_thread": "Terbitkan thread",
"reply": "Balas",
"reply_count": "{0}",
"reset": "Atur ulang",
@ -120,12 +124,14 @@
"block_account": {
"cancel": "Batalkan",
"confirm": "Blokir",
"description": "Yakin ingin memblokir {0}?"
"description": "Yakin ingin memblokir {0}?",
"title": "Blokir akun"
},
"block_domain": {
"cancel": "Batalkan",
"confirm": "Blokir",
"description": "Yakin ingin memblokir {0}?"
"description": "Yakin ingin memblokir {0}?",
"title": "Blokir domain"
},
"common": {
"cancel": "Tidak",
@ -134,27 +140,37 @@
"delete_list": {
"cancel": "Batalkan",
"confirm": "Hapus",
"description": "Apakah Anda yakin ingin menghapus daftar \"{0}\"?"
"description": "Apakah Anda yakin ingin menghapus daftar \"{0}\"?",
"title": "Hapus daftar"
},
"delete_posts": {
"cancel": "Batalkan",
"confirm": "Hapus",
"description": "Yakin ingin menghapus postingan ini?"
"description": "Yakin ingin menghapus postingan ini?",
"title": "Hapus postingan"
},
"mute_account": {
"cancel": "Batalkan",
"confirm": "Bisukan",
"description": "Yakin ingin membisukan {0}?"
"days": "hari|hari|hari",
"description": "Yakin ingin membisukan {0}?",
"hours": "jam|jam|jam",
"minute": "menit|menit|menit",
"notifications": "Bisukan notifikasi",
"specify_duration": "Tentukan durasi bisu",
"title": "Bisukan akun"
},
"show_reblogs": {
"cancel": "Batalkan",
"confirm": "Tampilkan",
"description": "Yakin ingin menampilkan dukungan dari {0}?"
"description": "Yakin ingin menampilkan dukungan dari {0}?",
"title": "Tampilkan dukungan"
},
"unfollow": {
"cancel": "Batalkan",
"confirm": "Berhenti mengikuti",
"description": "Anda yakin ingin berhenti mengikuti?"
"description": "Anda yakin ingin berhenti mengikuti?",
"title": "Berhenti mengikuti"
}
},
"conversation": {
@ -207,9 +223,12 @@
"error": "Terjadi kesalahan saat membuat daftar",
"error_prefix": "Kesalahan: ",
"list_title_placeholder": "Judul daftar",
"manage": "Kelola daftar",
"modify_account": "Ubah daftar dengan akun",
"remove_account": "Hapus akun dari daftar",
"save": "Simpan perubahan"
"save": "Simpan perubahan",
"search_following_desc": "Cari orang yang Anda ikuti",
"search_following_placeholder": "Cari di antara orang yang Anda ikuti"
},
"magic_keys": {
"dialog_header": "Pintasan keyboard",
@ -220,14 +239,25 @@
"compose": "Menyusun",
"favourite": "Favorit",
"search": "Cari",
"show_new_items": "Tampilkan item baru",
"title": "Tindakan"
},
"media": {
"title": "Media"
},
"navigation": {
"go_to_bookmarks": "Markah",
"go_to_conversations": "Percakapan",
"go_to_explore": "Jelajahi",
"go_to_favourites": "Favorit",
"go_to_federated": "Federasi",
"go_to_home": "Beranda",
"go_to_lists": "Daftar",
"go_to_local": "Lokal",
"go_to_notifications": "Notifikasi",
"go_to_profile": "Profil",
"go_to_search": "Cari",
"go_to_settings": "Pengaturan",
"next_status": "Posting selanjutnya",
"previous_status": "Posting sebelumnya",
"shortcut_help": "Bantuan jalan pintas",
@ -282,13 +312,16 @@
"built_at": "Diperbarui {0}",
"compose": "Menyusun",
"conversations": "Percakapan",
"docs": "Dokumentasi",
"explore": "Jelajahi",
"favourites": "Favorit",
"federated": "Federasi",
"hashtags": "Tagar",
"home": "Beranda",
"list": "Daftar",
"lists": "Daftar",
"local": "Lokal",
"more_menu": "Menu lainnya",
"muted_users": "Pengguna dibisukan",
"notifications": "Notifikasi",
"privacy": "Privasi",
@ -303,10 +336,12 @@
"zen_mode": "Mode Zen"
},
"notification": {
"and": "dan",
"favourited_post": "memfavoritkan pos Anda",
"followed_you": "mengikutimu",
"followed_you_count": "{0} orang mengikuti Anda|{0} orang mengikuti Anda|{0} orang mengikuti Anda",
"missing_type": "NOTIFIKASI HILANG.jenis:",
"others": "{0} lainnya|{0} lainnya|{0} lainnya",
"reblogged_post": "meningkatkan postingan Anda",
"reported": "{0} melaporkan {1}",
"request_to_follow": "meminta untuk mengikuti Anda",
@ -423,6 +458,8 @@
"label": "Pengaturan akun"
},
"interface": {
"bottom_nav": "Navigasi Bawah",
"bottom_nav_instructions": "Pilih tombol navigasi favorit Anda hingga lima untuk navigasi bawah. Harus menyertakan tombol \"Menu Lainnya\".",
"color_mode": "Tema",
"dark_mode": "Gelap",
"default": "(bawaan)",
@ -434,6 +471,7 @@
},
"language": {
"display_language": "Bahasa tampilan",
"how_to_contribute": "Bagaimana cara berkontribusi?",
"label": "Bahasa",
"post_language": "Bahasa Postingan",
"status": "Status terjemahan: {0}/{1} ({2}%)",
@ -515,13 +553,17 @@
"hide_boost_count": "Sembunyikan jumlah dukungan",
"hide_favorite_count": "Sembunyikan jumlah favorit",
"hide_follower_count": "Sembunyikan jumlah pengikut",
"hide_gif_indi_on_posts": "Sembunyikan indikator gif pada postingan",
"hide_news": "Sembunyikan berita",
"hide_reply_count": "Sembunyikan jumlah balasan",
"hide_tag_hover_card": "Sembunyikan tag kartu hover",
"hide_translation": "Sembunyikan terjemahan",
"hide_username_emojis": "Sembunyikan emoji di nama pengguna",
"hide_username_emojis_description": "Sembunyikan emoji dari nama pengguna dalam timeline. Emoji tetap akan terlihat di profil mereka.",
"label": "Preferensi",
"optimize_for_low_performance_device": "Optimalkan untuk perangkat berkinerja rendah",
"title": "Fitur Eksperimental",
"unmute_videos": "Suara video aktif secara default",
"use_star_favorite_icon": "Gunakan ikon bintang favorit",
"user_picker": "Pemilih Pengguna",
"user_picker_description": "Menampilkan semua avatar akun yang dicatat di kiri bawah sehingga Anda dapat beralih antar akun dengan cepat.",
@ -564,7 +606,11 @@
},
"state": {
"attachments_exceed_server_limit": "Jumlah lampiran melebihi batas per posting.",
"attachments_limit_audio_error": "Ukuran maksimal audio terlampaui: {0}",
"attachments_limit_error": "Batas per posting terlampaui",
"attachments_limit_image_error": "Ukuran maksimal gambar terlampaui: {0}",
"attachments_limit_unknown_error": "Ukuran file maksimal terlampaui: {0}",
"attachments_limit_video_error": "Ukuran maksimal video terlampaui: {0}",
"edited": "(Disunting)",
"editing": "Menyunting",
"loading": "Memuat...",
@ -585,24 +631,27 @@
"favourited_by": "Difavoritkan Oleh",
"filter_hidden_phrase": "Difilter oleh",
"filter_show_anyway": "Tetap tampilkan",
"gif": "GIF",
"img_alt": {
"ALT": "ALT",
"desc": "Keterangan",
"dismiss": "Batalkan",
"read": "Baca deskripsi {0}"
},
"pinned": "Postingan disematkan",
"poll": {
"count": "{0} suara|{0} suara|{0} suara",
"ends": "berakhir {0}",
"finished": "selesai {0}"
"finished": "selesai {0}",
"update": "Perbarui jajak pendapat"
},
"replying_to": "Membalas ke {0}",
"show_full_thread": "Tampilkan utas Penuh",
"show_full_thread": "Tampilkan thread Penuh",
"someone": "seseorang",
"spoiler_media_hidden": "Media disembunyikan",
"spoiler_show_less": "Tampilkan lebih sedikit",
"spoiler_show_more": "Menampilkan lebih banyak",
"thread": "Utas",
"thread": "Thread",
"try_original_site": "Coba situs asli"
},
"status_history": {
@ -671,6 +720,7 @@
"year_past": "0 tahun lalu|tahun lalu|{n} tahun lalu"
},
"timeline": {
"no_posts": "Tidak ada postingan di sini!",
"show_new_items": "Tampilkan {v} item baru|Tampilkan {v} item baru|Tampilkan {v} item baru",
"view_older_posts": "Posting lama dari contoh lain mungkin tidak ditampilkan."
},
@ -683,6 +733,7 @@
"add_emojis": "Tambahkan emoji",
"add_media": "Tambahkan gambar, video atau file audio",
"add_publishable_content": "Tambahkan konten untuk dipublikasikan",
"add_thread_item": "Tambahkan item ke thread",
"change_content_visibility": "Ubah visibilitas konten",
"change_language": "Ubah bahasa",
"emoji": "Emoji",
@ -692,6 +743,8 @@
"open_editor_tools": "Alat Editor",
"pick_an_icon": "Pilih ikon",
"publish_failed": "Tutup pesan yang gagal di bagian atas editor untuk mempublikasikan ulang postingan",
"remove_thread_item": "Hapus item dari thread",
"start_thread": "Mulai thread",
"toggle_bold": "Ubah tebal",
"toggle_code_block": "Tulis sebagai blok kode",
"toggle_italic": "Alihkan miring"

772
locales/sv.json Normal file
View file

@ -0,0 +1,772 @@
{
"a11y": {
"loading_page": "Laddar sidan, vänligen vänta",
"loading_titled_page": "Laddar sida {0}, vänligen vänta",
"locale_changed": "Språk ändrades till {0}",
"locale_changing": "Byter språk, vänligen vänta",
"route_loaded": "Sidan {0} laddades"
},
"account": {
"authorize": "Tillåt att följa",
"authorized": "Du har tillåtit förfrågan",
"avatar_description": "{0}'s profilbild",
"blocked_by": "Du är blockerad för denna användare.",
"blocked_domains": "Blockerade domäner",
"blocked_users": "Blockerade användare",
"blocking": "Blockerad",
"bot": "BOT",
"copy_account_name": "Kopiera kontonamn",
"favourites": "Favoriter",
"follow": "Följ",
"follow_back": "Följ tillbaka",
"follow_requested": "Begärd",
"followers": "Följare",
"followers_count": "{0} Följare|{0} Följare|{0} Följare",
"following": "Följer",
"following_count": "{0} Följer",
"follows_you": "Följer dig",
"go_to_profile": "Gå till profil",
"joined": "Gick med",
"lock": "Lås",
"moved_title": "har angett att deras konto är nu:",
"muted_users": "Tystade användare",
"muting": "Tystad",
"mutuals": "Ömsesidiga",
"notifications_on_post_disable": "Sluta notifiera mig när {username} gör inlägg",
"notifications_on_post_enable": "Notifiera mig när {username} gör inlägg",
"pinned": "Fäst",
"posts": "Inlägg",
"posts_count": "{0} Inlägg|{0} Inlägg|{0} Inlägg",
"profile_description": "{0}s profilrubrik",
"profile_personal_note": "Personlig anteckning",
"profile_unavailable": "Profil ej tillgänlig",
"reject": "Neka följare",
"rejected": "Du har nekat begäran",
"request_follow": "Begär att följa",
"requested": "{0} har begärt att följa dig",
"unblock": "Avblockera",
"unfollow": "Avfölj",
"unmute": "Avtysta",
"view_other_followers": "Följare från andra instanser kanske inte visas.",
"view_other_following": "Följande från andra instanser kanske inte visas.",
"withdraw_follow_request": "Återkalla följförfrågan"
},
"action": {
"apply": "Tillämpa",
"bookmark": "Bokmärk",
"bookmarked": "Bokmärkt",
"boost": "Boosta",
"boost_count": "{0}",
"boosted": "Boostad",
"clear": "Rensa",
"clear_publish_failed": "Rensa publiceringsfel",
"clear_save_failed": "Rensa sparfel",
"clear_upload_failed": "Rensa filuppladdningsfel",
"close": "Stäng",
"compose": "Komponera",
"confirm": "Bekräfta",
"done": "Klar",
"edit": "Redigera",
"enter_app": "Öppna appen",
"favourite": "Favoritmarkera",
"favourite_count": "{0}",
"favourited": "Favoritmarkerad",
"more": "Mer",
"next": "Nästa",
"open_image_preview_dialog": "Öppna bildförhandsgranskning",
"prev": "Föregående",
"publish": "Publicera",
"publish_thread": "Publicera tråd",
"reply": "Svara",
"reply_count": "{0}",
"reset": "Återställ",
"save": "Spara",
"save_changes": "Spara ändringar",
"sign_in": "Logga in",
"sign_in_to": "Logga in på {0}",
"switch_account": "Växla konto",
"vote": "Rösta"
},
"app_desc_short": "En smidig Mastodon-webbklient",
"app_logo": "Elk-logga",
"app_name": "Elk",
"attachment": {
"edit_title": "Beskrivning",
"remove_label": "Ta bort bilaga"
},
"command": {
"activate": "Aktivera",
"complete": "Färdigställ",
"compose_desc": "Skriv ett nytt inlägg",
"n_people_in_the_past_n_days": "{0} personer de senaste {1} dagarna",
"select_lang": "Välj språk",
"sign_in_desc": "Lägg till ett befintligt konto",
"switch_account": "Växla till {0}",
"switch_account_desc": "Växla till ett annat konto",
"toggle_dark_mode": "Växla mörkt läge",
"toggle_zen_mode": "Växla zen-läge"
},
"common": {
"end_of_list": "Slut på listan",
"error": "FEL",
"fetching": "Hämtar...",
"in": "i",
"no_bookmarks": "Inga bokmärkta inlägg än",
"no_favourites": "Inga favoritmarkerade inlägg än",
"not_found": "404 Ej hittad",
"offline_desc": "Verkar som att du är offline. Vänligen kontrollera din nätverksanslutning."
},
"compose": {
"draft_title": "Utkast {0}",
"drafts": "Utkast ({v})"
},
"confirm": {
"block_account": {
"cancel": "Avbryt",
"confirm": "Blockera",
"description": "Är du säker på att du vill blockera {0}?",
"title": "Blockera konto"
},
"block_domain": {
"cancel": "Avbryt",
"confirm": "Blockera",
"description": "Är du säker på att du vill blockera {0}?",
"title": "Blockera domän"
},
"common": {
"cancel": "Nej",
"confirm": "Ja"
},
"delete_list": {
"cancel": "Avbryt",
"confirm": "Radera",
"description": "Är du säker på att du vill radera \"{0}\"-listan?",
"title": "Radera lista"
},
"delete_posts": {
"cancel": "Avbryt",
"confirm": "Radera",
"description": "Är du säker på att du vill radera detta inlägg",
"title": "Radera inlägg"
},
"mute_account": {
"cancel": "Avbryt",
"confirm": "Tysta",
"days": "dagar|dag|dagar",
"description": "Är du säker på att du vill tysta {0}?",
"hours": "timmar|timme|timmar",
"minute": "minuter|minut|minuter",
"notifications": "Tysta notiser",
"specify_duration": "Specifiera tyst-varaktighet",
"title": "Tysta konto"
},
"show_reblogs": {
"cancel": "Avbryt",
"confirm": "Visa",
"description": "Är du säker på att du vill visa boosts från {0}?",
"title": "Visa boosts"
},
"unfollow": {
"cancel": "Avbryt",
"confirm": "Avfölj",
"description": "Är du säker på att du vill avfölja {0}?",
"title": "Avfölj"
}
},
"conversation": {
"with": "med"
},
"custom_cards": {
"stackblitz": {
"lines": "Rader {0}",
"open": "Öppna",
"snippet_from": "Urklipp från {0}"
}
},
"error": {
"account_not_found": "Konto {0} hittades ej",
"explore_list_empty": "Inget trendar just nu. Kolla igen senare!",
"file_size_cannot_exceed_n_mb": "Filstorleken kan ej överstiga {0}MB",
"sign_in_error": "Kan ej ansluta till servern.",
"status_not_found": "Inlägget hittades ej",
"unsupported_file_format": "Filformatet stödjs ej"
},
"help": {
"build_preview": {
"desc1": "Du använder just nu en förhandsversion av Elk från communityt - {0}.",
"desc2": "Den kan innehålla ogranskade eller till och med illvilliga ändringar.",
"desc3": "Logga inte in med ditt riktiga konto.",
"title": "Förhandsversion"
},
"desc_highlight": "Förvänta dig buggar här och där och funktioner som saknas.",
"desc_para1": "Elk är en smidig Mastodon-webbklient. Du kan logga in med ditt Mastodon-konto och använda det för att interagera med fediversumet.",
"desc_para2": "Elk är Open Source och vi förbättrar det aktivt som ett community-projekt. Gå med och låt oss bygga det tillsammans!",
"desc_para3": "För att accelerera utvecklingen så kan du sponsra teamet genom GitHub Sponsors. Vi hoppas du gillar Elk!",
"desc_para4": "Om du vill göra en buggrapport, hjälpa oss med testning, ge oss feedback eller bidra,",
"desc_para5": "nå ut till oss på GitHub",
"desc_para6": "och involvera dig.",
"footer_team": "Elk-teamet",
"title": "Välkommen till Elk!"
},
"language": {
"search": "Sök"
},
"list": {
"add_account": "Lägg till konto i listan",
"cancel_edit": "Avbryt redigering",
"clear_error": "Rensa fel",
"create": "Skapa",
"delete": "Radera denna lista",
"delete_error": "Ett fel uppstod när listan skulle raderas",
"edit": "Redigera denna lista",
"edit_error": "Ett fel uppstod när listan skulle uppdateras",
"error": "Ett fel uppstod när listan skulle skapas",
"error_prefix": "Fel: ",
"list_title_placeholder": "Listnamn",
"manage": "Hantera listor",
"modify_account": "Modifiera listor med konto",
"remove_account": "Radera konto från lista",
"save": "Spara ändringar",
"search_following_desc": "Sök efter personer du följer",
"search_following_placeholder": "Sök bland personer du följer"
},
"magic_keys": {
"dialog_header": "Kortkommandon",
"groups": {
"actions": {
"boost": "Boosta",
"command_mode": "Kommandoläge",
"compose": "Komponera",
"favourite": "Favoritmarkera",
"search": "Sök",
"show_new_items": "Visa nya objekt",
"title": "Åtgärder"
},
"media": {
"title": "Media"
},
"navigation": {
"go_to_bookmarks": "Bokmärken",
"go_to_conversations": "Konversationer",
"go_to_explore": "Upptäck",
"go_to_favourites": "Favoriter",
"go_to_federated": "Federerat",
"go_to_home": "Hem",
"go_to_lists": "Listor",
"go_to_local": "Lokalt",
"go_to_notifications": "Notiser",
"go_to_profile": "Profil",
"go_to_search": "Sök",
"go_to_settings": "Inställningar",
"next_status": "Nästa inlägg",
"previous_status": "Föregående inlägg",
"shortcut_help": "Kortkommandon",
"title": "Navigering"
}
},
"sequence_then": "then"
},
"menu": {
"add_personal_note": "Lägg till personlig anteckning för {0}",
"block_account": "Blockera {0}",
"block_domain": "Blockera domän {0}",
"copy_link_to_post": "Kopiera länk till detta inlägg",
"copy_original_link_to_post": "Kopiera originallänk till detta inlägg",
"delete": "Radera",
"delete_and_redraft": "Radera & gör om",
"direct_message_account": "Skicka meddelande till {0}",
"edit": "Redigera",
"hide_reblogs": "Göm boosts från {0}",
"mention_account": "Omnämn {0}",
"mute_account": "Tysta {0}",
"mute_conversation": "Tysta detta inlägg",
"open_in_original_site": "Öppna på originalsidan",
"pin_on_profile": "Fäst i profil",
"remove_personal_note": "Ta bort personlig anteckning från {0}",
"report_account": "Anmäl {0}",
"share_account": "Dela {0}",
"share_post": "Dela detta inlägg",
"show_favourited_and_boosted_by": "Visa vem som favoritmarkerade och boostade",
"show_reblogs": "Visa boosts från {0}",
"show_untranslated": "Visa oöversatta",
"toggle_theme": {
"dark": "Växla mörkt läge",
"light": "Växla ljust läge"
},
"translate_post": "Översätt inlägg",
"unblock_account": "Avblockera {0}",
"unblock_domain": "Avblockera domain {0}",
"unfollow_account": "Avfölj {0}",
"unmute_account": "Avtysta {0}",
"unmute_conversation": "Avtysta detta inlägg",
"unpin_on_profile": "Ångra fäst i profil"
},
"modals": {
"aria_label_close": "Stäng"
},
"nav": {
"back": "Gå tillbaka",
"blocked_domains": "Blockerade domäner",
"blocked_users": "Blockerade användare",
"bookmarks": "Bokmärken",
"built_at": "Byggd {0}",
"compose": "Komponera",
"conversations": "Konversationer",
"docs": "Dokumentation",
"explore": "Upptäck",
"favourites": "Favoriter",
"federated": "Federerat",
"hashtags": "Hashtaggar",
"home": "Hem",
"list": "Lista",
"lists": "Listor",
"local": "Lokalt",
"more_menu": "Mer",
"muted_users": "Tystade användare",
"notifications": "Notiser",
"privacy": "Integritet",
"profile": "Profil",
"search": "Sök",
"select_feature_flags": "Växla funktionsflaggor",
"select_font_size": "Teckenstorlek",
"select_language": "Språk för gränssnittet",
"settings": "Inställningar",
"show_intro": "Visa introduktion",
"toggle_theme": "Växla tema",
"zen_mode": "Zen-läge"
},
"notification": {
"and": "och",
"favourited_post": "favoritmarkerade ditt inlägg",
"followed_you": "följde dig",
"followed_you_count": "{0} följde dig|{0} följde dig|{0} följde dig",
"missing_type": "SAKNAS notification.type:",
"others": "{0} andra|{0} andra|{0} andra",
"reblogged_post": "boostade ditt inlägg",
"reported": "{0} anmälde {1}",
"request_to_follow": "begärde att följa dig",
"signed_up": "registrerade sig",
"update_status": "uppdaterade deras inlägg"
},
"placeholder": {
"content_warning": "Skriv din varning här",
"default_1": "Vad tänker du på?",
"reply_to_account": "Svara till {0}",
"replying": "Svarar"
},
"polls": {
"allow_multiple": "Tillåt flera val",
"cancel": "Avbryt",
"create": "Skapa omröstning",
"disallow_multiple": "Tillåt ej flera val",
"expiration": "Omröstning upphör",
"hide_votes": "Dölj totala röster till slutet",
"option_placeholder": "Omröstningsval {current}/{max}",
"remove_option": "Ta bort val",
"settings": "Omröstningsval",
"show_votes": "Visa alltid totalt antal röster"
},
"pwa": {
"dismiss": "Avvisa",
"install": "Installera",
"install_title": "Installera Elk",
"screenshots": {
"dark": "Skärmavbild av Elk i mörkt läge",
"light": "Skärmavbild av Elk i ljust läge"
},
"title": "Ny Elk-uppdatering tillgänglig!",
"update": "Uppdatera",
"update_available_short": "Uppdatera Elk",
"webmanifest": {
"canary": {
"description": "En smidig Mastodon-webbklient (canary)",
"name": "Elk (canary)",
"short_name": "Elk (canary)"
},
"dev": {
"description": "En smidig Mastodon-webbklient (dev)",
"name": "Elk (dev)",
"short_name": "Elk (dev)"
},
"preview": {
"description": "En smidig Mastodon-webbklient (preview)",
"name": "Elk (preview)",
"short_name": "Elk (preview)"
},
"release": {
"description": "En smidig Mastodon-webbklient",
"name": "Elk",
"short_name": "Elk"
}
}
},
"report": {
"additional_comments": "Övriga kommentarer",
"another_server": "Användaren du anmäler är från en annan server",
"anything_else": "Är det något annat vi borde veta om?",
"block_desc": "Du kommer inte att se inlägg från den här användaren i fortsättningen. De kommer inte kunna se dina inlägg eller följa dig. De kommer kunna se att de är blockerade.",
"dontlike": "Jag gillar det inte",
"dontlike_desc": "Det är inget jag vill se",
"forward": "Ja, vidarebefordra denna anmälan till {0}",
"forward_question": "Vill du även skicka en anonymiserad kopia av denna anmälan till servern?",
"further_actions": {
"limit": {
"description": "Här är dina alternativ för att kontrollera vad du ser:",
"title": "Vill du inte se det här?"
},
"report": {
"description": "Medan vi granskar detta, så kan du ta följande åtgärder:",
"title": "Tack för din anmälan, vi kommer ta en titt på detta."
}
},
"limiting": "Begränsar {0}",
"mute_desc": "Du kommer inte att se inlägg från den här användaren i fortsättningen. De kommer fortfarande kunna följa dig och se dina inlägg. De kommer inte veta att de är tystade.",
"other": "Det är något annat",
"other_desc": "Problemet passar inte i någon av kategorierna",
"reporting": "Anmäler {0}",
"select_many": "Välj allt som stämmer in:",
"select_one": "Välj den bästa matchningen:",
"select_posts": "Finns det några inlägg som styrker denna anmälan?",
"select_posts_other": "Finns det några andra inlägg som styrker denna anmälan?",
"spam": "Det är skräppost",
"spam_desc": "Skadliga länkar, klicklockeri eller upprepande svar",
"submit": "Skicka in anmälan",
"unfollow_desc": "Du kommer inte att se inlägg från den här användaren i ditt flöde i fortsättningen. Du kan dock komma att se inlägg från den här användaren på andra ställen.",
"violation": "Det bryter mot en eller flera av serverreglerna",
"whats_wrong_account": "Berätta vad som är fel med det här kontot",
"whats_wrong_post": "Berätta vad som är fel med det här inlägget"
},
"search": {
"search_desc": "Sök efter personer & hashtaggar",
"search_empty": "Kunde inte hitta något med dessa söktermer"
},
"settings": {
"about": {
"built_at": "Byggd",
"label": "Om",
"meet_the_team": "Möt teamet",
"sponsor_action": "Sponsra oss",
"sponsor_action_desc": "Sponsra teamet som utvecklar Elk",
"sponsors": "Sponsorer",
"sponsors_body_1": "Elk möjliggörs tack vare det generösa stödet och hjälpen från:",
"sponsors_body_2": "Samt alla företag och individer som stödjer Elk-teamet och dess medlemmar.",
"sponsors_body_3": "Om du tycker om appen, överväg då att sponsra oss:",
"version": "Version"
},
"account_settings": {
"description": "Ändra dina kontoinställningar i Mastodon-gränssnittet",
"label": "Kontoinställningar"
},
"interface": {
"bottom_nav": "Nedre navigeringsfält",
"bottom_nav_instructions": "Välj upp till fem stycken favoritknappar för det nedre navigeringsfältet. Måste innehålla \"Mer\"-knappen",
"color_mode": "Färgläge",
"dark_mode": "Mörkt",
"default": " (standard)",
"font_size": "Teckenstorlek",
"label": "Gränssnitt",
"light_mode": "Ljust",
"system_mode": "System",
"theme_color": "Färgtema"
},
"language": {
"display_language": "Språk för gränssnittet",
"how_to_contribute": "Hur kan du bidra?",
"label": "Språk",
"post_language": "Inläggsspråk",
"status": "Översättningsstatus: {0}/{1} ({2}%)",
"translations": {
"add": "Lägg till",
"choose_language": "Välj språk",
"heading": "Översättningar",
"hide_specific": "Dölj specifika översättningar",
"remove": "Ta bort"
}
},
"notifications": {
"label": "Notiser",
"notifications": {
"label": "Inställningar för notiser"
},
"push_notifications": {
"alerts": {
"favourite": "Favoriter",
"follow": "Nya följare",
"mention": "Omnämnanden",
"poll": "Omröstningar",
"reblog": "Boostar",
"title": "Vilka notiser vill jag ta emot?"
},
"description": "Ta emot notiser även när du inte använder Elk.",
"instructions": "Glöm inte att spara dina ändringar med @:settings.notifications.push_notifications.save_settings -knappen!",
"label": "Inställningar för push-notiser",
"policy": {
"all": "Från vem som helst",
"followed": "Från personer jag följer",
"follower": "Från personer som följer mig",
"none": "Från ingen",
"title": "Vem kan jag ta emot notiser från?"
},
"save_settings": "Spara inställningar",
"subscription_error": {
"clear_error": "Rensa fel",
"error_hint": "Du kan konsultera en lista med vanliga frågor för att lösa problem: {0}.",
"invalid_vapid_key": "Den allmänna nyckeln för VAPID verkar vara ogiltig.",
"permission_denied": "Åtkomst nekad: aktivera notiser i din webbläsare.",
"repo_link": "Elks förvar på GitHub",
"request_error": "Ett fel inträffade när prenumerationen förfrågades. Prova igen, och om felet återstår, vänligen anmäl felet till Elks förvar på Github.",
"title": "Kunde inte prenumerera på push-notiser",
"too_many_registrations": "På grund av begränsningar i webbläsaren, kan Elk inte använda tjänsten för push-notiser för flera konton på olika servrar. Du bör avprenumerera från push-notiser på ett annat konto och försök igen.",
"vapid_not_supported": "Din webbläsare stödjer Web Push Notifications, men verkar inte ha implementerat VAPID-protokollet."
},
"title": "Inställningar för push-notiser",
"undo_settings": "Ångra ändringar",
"unsubscribe": "Avaktivera push-notiser",
"unsupported": "Din webbläsare stödjer inte push-notiser.",
"warning": {
"enable_close": "Stäng",
"enable_description": "För att ta emot notiser när Elk inte är öppet, aktivera push-notiser. Du kan kontrollera precis vilka typer av interaktioner som ska generera push-notiser via \"@:settings.notifications.show_btn{'\"'} -knappen ovan när det är aktiverat.",
"enable_description_desktop": "För att ta emot notiser när Elk inte är öppet, aktivera push-notiser. Du kan kontrollera precis vilka typer av interaktioner som ska generera push-notiser via \"Inställningar > Notiser > Inställningar för push-notiser\" när det är aktiverat.",
"enable_description_mobile": "Du kan även komma åt inställningarna genom att använda navigationsmenyn \"Inställningar > Notiser > Inställningar för push-notiser\".",
"enable_description_settings": "För att ta emot notiser när Elk inte är öppet, aktivera push-notiser. Du kan kontrollera precis vilka typer av interaktioner som ska generera push-notiser på denna sida när du aktiverat dem.",
"enable_desktop": "Aktivera push-notiser",
"enable_title": "Missa aldrig något",
"re_auth": "Det ser ut som att din server inte stödjer push-notiser. Prova att logga ut och logga in igen, och om detta meddelande fortfarande dyker upp så kontakta din serveradministratör."
}
},
"show_btn": "Gå till inställningar för notiser",
"under_construction": "Under uppbyggnad"
},
"notifications_settings": "Notiser",
"preferences": {
"embedded_media": "Inbäddad mediaspelare",
"embedded_media_description": "Visa en inbäddad mediaspelare istället för den vanliga förhandsvisningskortet när delade medialänkar öppnas.",
"enable_autoplay": "Aktivera automatisk uppspelning",
"enable_data_saving": "Aktivera datasparläge",
"enable_data_saving_description": "Spara på din dataförbrukning genom att förhindra att bilagor laddas automatiskt.",
"enable_pinch_to_zoom": "Aktivera nyp för att zooma",
"github_cards": "GitHub-kort",
"github_cards_description": "När ett inlägg har en GitHub-länk, visas ett HTML-kort med hjälp av social graph-metadata istället för bilden.",
"grayscale_mode": "Gråskaleläge",
"hide_account_hover_card": "Dölj konto-hoverkort",
"hide_alt_indi_on_posts": "Dölj alt-indikator på inlägg",
"hide_boost_count": "Dölj boost-räknare",
"hide_favorite_count": "Dölj favoriträknare",
"hide_follower_count": "Dölj följer/följarräknare",
"hide_gif_indi_on_posts": "Dölj gif-indikator på inlägg",
"hide_news": "Dölj nyheter",
"hide_reply_count": "Dölj svarsräknare",
"hide_tag_hover_card": "Dölj tagg-hoverkort",
"hide_translation": "Dölj översättningar",
"hide_username_emojis": "Dölj användarnamn-emojis",
"hide_username_emojis_description": "Döljer emojis på användarnamn i tidslinjer. Emojis kommer fortfarande vara synliga i deras profiler.",
"label": "Inställningar",
"optimize_for_low_performance_device": "Optimera för enhet med låg prestanda",
"title": "Experimentella funktioner",
"unmute_videos": "Videoljud på som standard",
"use_star_favorite_icon": "Använd stjärna som favoritikon",
"user_picker": "Användarväljare",
"user_picker_description": "Visar alla profilbilder för inloggade konton i det nedre vänstra hörnet så att du snabbt kan växla mellan dem.",
"virtual_scroll": "Virtuell skrollning",
"virtual_scroll_description": "Använder en virtuell lista i tidslinjer så att ett större antal objekt kan renderas följsamt.",
"wellbeing": "Välbefinnande",
"zen_mode": "Zen-läge",
"zen_mode_description": "Döljer flikar så länge muspekaren inte är över dem. Döljer även vissa element från tidslinjen."
},
"profile": {
"appearance": {
"bio": "Biografi",
"description": "Ändra profilbild, användarnamn, profil, etc.",
"display_name": "Visningsnamn",
"label": "Utseende",
"profile_metadata": "Profil-metadata",
"profile_metadata_desc": "Du kan ha upp till {0} objekt för visning som en tabell på din profil",
"profile_metadata_label": "Etikett",
"profile_metadata_value": "Innehåll",
"title": "Redigera profil"
},
"featured_tags": {
"description": "Folk kan bläddra bland dina offentliga inlägg under dessa hashtaggar.",
"label": "Utvalda hashtaggar",
"under_construction": "Under uppbyggnad"
},
"label": "Profil"
},
"select_a_settings": "Välj en inställning",
"users": {
"export": "Exportera användarnycklar",
"import": "Importera användarnycklar",
"label": "Inloggade användare"
}
},
"share_target": {
"description": "Elk kan konfigureras så att du kan dela innehåll från andra applikationer. Installera Elk på din enhet eller dator och logga in.",
"hint": "För att kunna dela innehåll med Elk så måste Elk vara installerat och du måste vara inloggad.",
"title": "Dela med Elk"
},
"state": {
"attachments_exceed_server_limit": "Antalet bilagor överstiger gränsen per inlägg.",
"attachments_limit_audio_error": "Maximal ljudfilsstorlek har överskridits: {0}",
"attachments_limit_error": "Gränsen per inlägg har överstigits",
"attachments_limit_image_error": "Maximal bildstorlek har överskridits: {0}",
"attachments_limit_unknown_error": "Maximal filstorlek har överskridits: {0}",
"attachments_limit_video_error": "Maximal videostorlek har överskridits: {0}",
"edited": "(Redigerad)",
"editing": "Redigerar",
"loading": "Laddar...",
"publish_failed": "Publicering misslyckades",
"publishing": "Publicerar",
"save_failed": "Sparningen misslyckades",
"upload_failed": "Uppladdning misslyckades",
"uploading": "Laddar upp..."
},
"status": {
"account": {
"suspended_message": "Kontot för detta inlägg har stängts av.",
"suspended_show": "Visa innehållet ändå?"
},
"boosted_by": "Boostad av",
"edited": "Redigerad {0}",
"embedded_warning": "Om du spelar detta så kan det avslöja din IP-adress för andra.",
"favourited_by": "Favoritmarkerad av",
"filter_hidden_phrase": "Filtrerad av",
"filter_show_anyway": "Visa ändå",
"gif": "GIF",
"img_alt": {
"ALT": "ALT",
"desc": "Beskrivning",
"dismiss": "Stäng",
"read": "Läs {0} beskrivning"
},
"pinned": "Fästa inlägg",
"poll": {
"count": "{0} röster|{0} röst|{0} röster",
"ends": "slutar {0}",
"finished": "avslutad {0}",
"update": "Uppdatera omröstning"
},
"replying_to": "Svarar till {0}",
"show_full_thread": "Visa hela tråden",
"someone": "någon",
"spoiler_media_hidden": "Media dold",
"spoiler_show_less": "Visa mindre",
"spoiler_show_more": "Visa mer",
"thread": "Tråd",
"try_original_site": "Försök på originalsidan"
},
"status_history": {
"created": "skapades {0}",
"edited": "redigerades {0}"
},
"tab": {
"accounts": "Konton",
"for_you": "För dig",
"hashtags": "Hashtaggar",
"list": "Lista",
"media": "Media",
"news": "Nyheter",
"notifications_admin": {
"report": "Anmäl",
"sign_up": "Registrera"
},
"notifications_all": "Alla",
"notifications_favourite": "Favoritmarkering",
"notifications_follow": "Följ",
"notifications_follow_request": "Följförfrågan",
"notifications_mention": "Omnämnanden",
"notifications_more_tooltip": "Filtrera notiser efter typ",
"notifications_poll": "Omröstning",
"notifications_reblog": "Boost",
"notifications_status": "Status",
"notifications_update": "Uppdatering",
"posts": "Inlägg",
"posts_with_replies": "Inlägg & svar"
},
"tag": {
"follow": "Följ",
"follow_label": "Följ {0}-taggen",
"unfollow": "Avfölj",
"unfollow_label": "Avfölj {0}-taggen"
},
"time_ago_options": {
"day_future": "om 0 dagar|imorgon|om {n} dagar",
"day_past": "0 dagar sedan|igår|{n} dagar sedan",
"hour_future": "om 0 timmar|om 1 timme|om {n} timmar",
"hour_past": "0 timmar sedan|1 timme sedan|{n} timmar sedan",
"just_now": "just nu",
"minute_future": "om 0 minuter|om 1 minut|om {n} minuter",
"minute_past": "0 minuter sedan|1 minut sedan|{n} minuter sedan",
"month_future": "om 0 månader|nästa månad|om {n} månader",
"month_past": "0 månader sedan|förra månaden|{n} månader sedan",
"second_future": "just nu|om {n} sekund|om {n} sekunder",
"second_past": "just nu|{n} sekund sedan|{n} sekunder sedan",
"short_day_future": "om {n}d",
"short_day_past": "{n}d",
"short_hour_future": "om {n}h",
"short_hour_past": "{n}h",
"short_minute_future": "om {n}min",
"short_minute_past": "{n}min",
"short_month_future": "om {n}mån",
"short_month_past": "{n}mån",
"short_second_future": "om {n}s",
"short_second_past": "{n}s",
"short_week_future": "om {n}v",
"short_week_past": "{n}v",
"short_year_future": "om {n}år",
"short_year_past": "{n}år",
"week_future": "om 0 veckor|nästa vecka|om {n} veckor",
"week_past": "0 veckor sedan|förra veckan|{n} veckor sedan",
"year_future": "om 0 år|nästa år|om {n} år",
"year_past": "0 år sedan|förra året|{n} år sedan"
},
"timeline": {
"no_posts": "Inga inlägg här!",
"show_new_items": "Visa {v} nya inlägg|Visa {v} nytt inlägg|Visa {v} nya inlägg",
"view_older_posts": "Äldre inlägg från andra instanser kanske inte visas."
},
"title": {
"federated_timeline": "Federerad tidslinje",
"local_timeline": "Lokal tidslinje"
},
"tooltip": {
"add_content_warning": "Lägg till innehållsvarning",
"add_emojis": "Lägg till emojis",
"add_media": "Lägg till bilder, video eller en ljudfil",
"add_publishable_content": "Lägg till innehåll för publicering",
"add_thread_item": "Lägg till inlägg i tråden",
"change_content_visibility": "Ändra innehållets synlighet",
"change_language": "Byt språk",
"emoji": "Emoji",
"explore_links_intro": "Dessa nyheter pratas det om just nu på denna och andra servrar på det decentraliserade nätverket.",
"explore_posts_intro": "Dessa inlägg från denna och andra servrar på det decentraliserade nätverket har fått fäste på denna server just nu.",
"explore_tags_intro": "Dessa hashtaggar har fått fäste bland folk på denna och andra servrar på det decentraliserade nätverket",
"open_editor_tools": "Redigeringsverktyg",
"pick_an_icon": "Välj en ikon",
"publish_failed": "Stäng misslyckade meddelanden på toppen av redigeringsverktyget för att åter publicera inläggen",
"remove_thread_item": "Ta bort inlägg från tråden",
"start_thread": "Starta tråd",
"toggle_bold": "Fet",
"toggle_code_block": "Kodblock",
"toggle_italic": "Kursiv"
},
"user": {
"add_existing": "Lägg till ett befintligt konto",
"server_address_label": "Mastodon-serveradress",
"sign_in_desc": "Logga in för att följa profiler eller hashtaggar samt favoritmarkera, dela och svara på inlägg, eller interagera från ditt konto på en annan server.",
"sign_in_notice_title": "Visar {0} offentliga data",
"sign_out_account": "Logga ut {0}",
"single_instance_sign_in_desc": "Logga in för att följa profiler eller hashtaggar, favoritmarkera, dela och svara på inlägg",
"tip_no_account": "Om du inte har ett Mastodon-konto än, {0}.",
"tip_register_account": "välj server och registrera ett"
},
"visibility": {
"direct": "Privat omnämnande",
"direct_desc": "Synligt endast för omnämnda användare",
"private": "Endast följare",
"private_desc": "Synligt endast för följare",
"public": "Offentlig",
"public_desc": "Synligt för alla",
"unlisted": "Offentlig (begränsad)",
"unlisted_desc": "Synligt för alla, men bortvald från upptäcktsfunktioner"
}
}

View file

@ -1,5 +1,5 @@
import type { BuildInfo } from '#shared/types'
import { createResolver, defineNuxtModule } from '@nuxt/kit'
import type { BuildInfo } from '../shared/types'
import { createResolver, defineNuxtModule } from 'nuxt/kit'
import { isCI } from 'std-env'
import { getEnv, version } from '../config/env'

View file

@ -1,5 +1,5 @@
import { lstat } from 'node:fs'
import { createResolver, defineNuxtModule } from '@nuxt/kit'
import { createResolver, defineNuxtModule } from 'nuxt/kit'
import { currentLocales } from '../config/i18n'
const virtual = 'virtual:emoji-mart-lang-importer'

View file

@ -1,5 +1,5 @@
import { addVitePlugin, defineNuxtModule } from '@nuxt/kit'
import MagicString from 'magic-string'
import { addVitePlugin, defineNuxtModule } from 'nuxt/kit'
export default defineNuxtModule({
meta: {

View file

@ -1,7 +1,7 @@
import type { ManifestOptions } from 'vite-plugin-pwa'
import { Buffer } from 'node:buffer'
import { readFile } from 'node:fs/promises'
import { createResolver } from '@nuxt/kit'
import { createResolver } from 'nuxt/kit'
import { THEME_COLORS } from '../../app/constants/index'
import { getEnv } from '../../config/env'
import { currentLocales } from '../../config/i18n'

View file

@ -6,7 +6,7 @@ import type { VitePWANuxtOptions } from './types'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
import { addPlugin, createResolver, defineNuxtModule } from 'nuxt/kit'
import { join, resolve } from 'pathe'
import { VitePWA } from 'vite-plugin-pwa'
import { configurePWAOptions } from './config'

View file

@ -1,68 +0,0 @@
import { rm } from 'node:fs/promises'
import { addImports, addImportsSources, addPlugin, createResolver, defineNuxtModule, useNuxt } from '@nuxt/kit'
import { resolveModulePath } from 'exsolve'
const mockProxy = resolveModulePath('mocked-exports/proxy', { from: import.meta.url })
export default defineNuxtModule({
meta: {
name: 'tauri',
},
setup() {
const nuxt = useNuxt()
const { resolve } = createResolver(import.meta.url)
if (!process.env.TAURI_PLATFORM)
return
if (nuxt.options.dev)
nuxt.options.ssr = false
nuxt.options.pwa.disable = true
nuxt.options.sourcemap.client = false
nuxt.options.alias = {
...nuxt.options.alias,
'unstorage/drivers/fs': mockProxy,
'unstorage/drivers/cloudflare-kv-http': mockProxy,
'#storage-config': resolve('./runtime/storage-config'),
'node:events': 'unenv/runtime/node/events/index',
'#build-info': resolve('./runtime/build-info'),
}
nuxt.hook('vite:extend', ({ config }) => {
config.build!.target = ['chrome100', 'safari15']
config.envPrefix = [...config.envPrefix || [], 'VITE_', 'TAURI_']
})
// prevent creation of server routes
nuxt.hook('nitro:config', (config) => {
config.srcDir = './_nonexistent'
config.scanDirs = []
})
addImportsSources({
from: 'h3',
imports: ['defineEventHandler', 'getQuery', 'getRouterParams', 'readBody', 'sendRedirect'] as Array<keyof typeof import('h3')>,
})
nuxt.options.imports.dirs = nuxt.options.imports.dirs || []
nuxt.options.imports.dirs.push(resolve('../../server/utils'))
addImports({ name: 'useStorage', from: resolve('./runtime/storage') })
addPlugin(resolve('./runtime/logging.client'))
addPlugin(resolve('./runtime/nitro.client'))
// cleanup files copied from the public folder that we don't need
nuxt.hook('close', async () => {
await rm('.output/public/_redirects')
await rm('.output/public/apple-touch-icon.png')
await rm('.output/public/elk-og.png')
await rm('.output/public/favicon.ico')
await rm('.output/public/pwa-192x192.png')
await rm('.output/public/pwa-512x512.png')
await rm('.output/public/robots.txt')
})
},
})

View file

@ -1 +0,0 @@
export const env = useAppConfig().env

View file

@ -1,18 +0,0 @@
import * as log from 'tauri-plugin-log-api'
// When running inside Tauri, catch all logs from 3rd party packages and direct them to the unified logging stream
export default defineNuxtPlugin(() => {
// eslint-disable-next-line no-global-assign
console = {
...console,
trace: log.trace,
debug: log.debug,
log: log.info,
warn: log.warn,
error: log.error,
}
window.addEventListener('unhandledrejection', err =>
log.error(err.reason))
window.addEventListener('error', err => log.error(err.error), true)
})

View file

@ -1,73 +0,0 @@
import type { FetchResponse } from 'ofetch'
import {
createApp,
createRouter,
defineLazyEventHandler,
toNodeListener,
} from 'h3'
import { fetchNodeRequestHandler } from 'node-mock-http'
import { createFetch } from 'ofetch'
const handlers = [
{
route: '/api/:server/oauth',
handler: defineLazyEventHandler(() => import('~~/server/api/[server]/oauth/[origin]').then(r => r.default || r)),
},
{
route: '/api/:server/login',
handler: defineLazyEventHandler(() => import('~~/server/api/[server]/login').then(r => r.default || r)),
},
{
route: '/api/list-servers',
handler: defineLazyEventHandler(() => import('~~/server/api/list-servers').then(r => r.default || r)),
},
]
// @ts-expect-error undeclared global window property
window.__NUXT__.config = {
// @ts-expect-error undeclared global window property
...window.__NUXT__.config,
storage: {},
}
export default defineNuxtPlugin(async () => {
const config = useRuntimeConfig()
const h3App = createApp({
debug: import.meta.dev,
// TODO: add global error handler
// onError: (err, event) => {
// console.log({ err, event })
// },
})
const router = createRouter()
for (const h of handlers)
router.use(h.route, h.handler)
// @ts-expect-error TODO: fix
h3App.use(config.app.baseURL, router)
const nodeHandler = toNodeListener(h3App)
const localFetch: typeof fetch = async (input, init) => {
if (!input.toString().startsWith('/')) {
return globalThis.fetch(input.toString(), init)
}
return await fetchNodeRequestHandler(nodeHandler, input.toString(), init)
}
// @ts-expect-error error types are subtly different here in a future nitro version
globalThis.$fetch = createFetch({
fetch: localFetch,
Headers,
defaults: { baseURL: config.app.baseURL },
})
const route = useRoute()
if (route.path.startsWith('/api')) {
const result = (await ($fetch.raw as any)(route.fullPath)) as FetchResponse<unknown>
if (result.headers.get('location'))
location.href = result.headers.get('location')!
}
})

View file

@ -1,2 +0,0 @@
export const driver = undefined
export const fsBase = ''

View file

@ -1,6 +1,6 @@
import type { BuildInfo } from './shared/types'
import { createResolver, useNuxt } from '@nuxt/kit'
import { resolveModulePath } from 'exsolve'
import { createResolver, useNuxt } from 'nuxt/kit'
import { isCI, isDevelopment, isWindows } from 'std-env'
import { isPreview } from './config/env'
import { currentLocales } from './config/i18n'
@ -11,12 +11,10 @@ const { resolve } = createResolver(import.meta.url)
const mockProxy = resolveModulePath('mocked-exports/proxy', { from: import.meta.url })
export default defineNuxtConfig({
compatibilityDate: '2024-09-11',
future: {
compatibilityVersion: 4,
},
compatibilityDate: '2025-07-11',
typescript: {
tsConfig: {
include: ['../tests/nuxt'],
exclude: ['../service-worker'],
compilerOptions: {
// TODO: enable this once we fix the issues
@ -37,12 +35,6 @@ export default defineNuxtConfig({
'@unlazy/nuxt',
'@nuxt/test-utils/module',
...(isDevelopment || isWindows) ? [] : ['nuxt-security'],
'~~/modules/emoji-mart-translation',
'~~/modules/purge-comments',
'~~/modules/build-env',
'~~/modules/tauri/index',
'~~/modules/pwa/index', // change to '@vite-pwa/nuxt' once released and remove pwa module
'stale-dep/nuxt',
],
vue: {
propsDestructure: true,
@ -62,6 +54,9 @@ export default defineNuxtConfig({
experimental: {
payloadExtraction: false,
renderJsonPayloads: true,
// Temporary workaround to avoid hash mismatch issue
// ref. https://github.com/elk-zone/elk/issues/3385#issuecomment-3335167005
entryImportMap: false,
},
css: [
'@unocss/reset/tailwind.css',
@ -69,9 +64,7 @@ export default defineNuxtConfig({
'~/styles/default-theme.css',
'~/styles/vars.css',
'~/styles/global.css',
...process.env.TAURI_PLATFORM === 'macos'
? []
: ['~/styles/scrollbars.css'],
'~/styles/scrollbars.css',
'~/styles/tiptap.css',
'~/styles/dropdown.css',
],
@ -318,48 +311,19 @@ export default defineNuxtConfig({
colorMode: { classSuffix: '' },
i18n: {
locales: currentLocales,
lazy: true,
strategy: 'no_prefix',
detectBrowserLanguage: false,
// 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: {
packageManager: 'pnpm',
},
unlazy: {
ssr: false,
},
})
declare global {
// eslint-disable-next-line ts/no-namespace
namespace NodeJS {
interface Process {
mock?: Record<string, any>
}
}
}
declare module '#app' {
interface PageMeta {
wideLayout?: boolean
}
interface RuntimeNuxtHooks {
'elk-logo:click': () => void
}
}
declare module '@nuxt/schema' {
interface AppConfig {
storage: any

View file

@ -1,52 +1,58 @@
{
"name": "@elk-zone/elk",
"type": "module",
"version": "0.16.0",
"packageManager": "pnpm@9.15.9",
"version": "0.17.0",
"packageManager": "pnpm@10.23.0",
"license": "MIT",
"homepage": "https://elk.zone/",
"main": "./nuxt.config.ts",
"scripts": {
"build": "nuxi build",
"dev": "nuxi dev --port 5314",
"dev:pwa": "VITE_DEV_PWA=true nuxi dev --port 5314",
"dev:mocked": "nuxi dev --port 5314 --dotenv .env.mock",
"dev:mocked:pwa": "VITE_DEV_PWA=true nuxi dev --port 5314 --dotenv .env.mock",
"dev:mocked:pwa:ssl": "VITE_DEV_PWA=true nuxi dev --port 5314 --https --ssl-cert ./https-dev-config/localhost.crt --ssl-key ./https-dev-config/localhost.key --dotenv .env.mock",
"build": "nuxt build",
"dev": "nuxt dev --port 5314",
"dev:pwa": "VITE_DEV_PWA=true nuxt dev --port 5314",
"dev:mocked": "nuxt dev --port 5314 --dotenv .env.mock",
"dev:mocked:pwa": "VITE_DEV_PWA=true nuxt dev --port 5314 --dotenv .env.mock",
"dev:mocked:pwa:ssl": "VITE_DEV_PWA=true nuxt dev --port 5314 --https --ssl-cert ./https-dev-config/localhost.crt --ssl-key ./https-dev-config/localhost.key --dotenv .env.mock",
"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",
"test:unit": "stale-dep && vitest",
"test:unit:ci": "stale-dep && vitest run",
"test:typecheck": "stale-dep && vue-tsc --noEmit && vue-tsc --noEmit --project service-worker/tsconfig.json",
"test": "nr test:unit",
"test:ci": "nr test:unit:ci",
"update:team:avatars": "tsx scripts/avatars.ts",
"cleanup-translations": "tsx scripts/cleanup-translations.ts",
"prepare-translation-status": "tsx scripts/prepare-translation-status.ts",
"generate-pwa-icons": "tsx scripts/generate-pwa-icons.ts",
"postinstall": "ignore-dependency-scripts \"stale-dep -u && simple-git-hooks && nuxi prepare && nr prepare-translation-status\"",
"release": "stale-dep && bumpp && tsx scripts/release.ts"
"typecheck": "nuxt typecheck",
"prepare": "node scripts/prepare-translation-status.ts && node scripts/prepare.ts",
"generate": "nuxt generate",
"test:unit": "vitest",
"test:unit:ci": "vitest run",
"test:typecheck": "vue-tsc -b --noEmit",
"test": "vitest",
"test:ci": "vitest run",
"update:team:avatars": "node scripts/avatars.ts",
"cleanup-translations": "node scripts/cleanup-translations.ts",
"prepare-translation-status": "node scripts/prepare-translation-status.ts",
"generate-pwa-icons": "node scripts/generate-pwa-icons.ts",
"postinstall": "simple-git-hooks && nuxt prepare",
"release": "bumpp && node scripts/release.ts"
},
"dependencies": {
"@emoji-mart/data": "^1.1.2",
"@fnando/sparkline": "^0.3.10",
"@iconify-emoji/twemoji": "^1.0.2",
"@iconify/json": "^2.2.170",
"@iconify/utils": "^2.1.22",
"@nuxt/devtools": "^2.4.1",
"@nuxt/test-utils": "^3.19.0",
"@iconify/utils": "^3.0.0",
"@intlify/core-base": "^11.1.12",
"@intlify/message-compiler": "^11.1.12",
"@intlify/shared": "^11.1.12",
"@nuxt/devtools": "^2.6.5",
"@nuxt/test-utils": "^3.19.2",
"@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/i18n": "^9.5.4",
"@pinia/nuxt": "^0.11.0",
"@nuxtjs/i18n": "^10.1.0",
"@pinia/nuxt": "^0.11.2",
"@tiptap/core": "2.2.4",
"@tiptap/extension-bold": "2.2.4",
"@tiptap/extension-character-count": "2.2.4",
"@tiptap/extension-code": "2.2.4",
"@tiptap/extension-code-block": "2.2.4",
"@tiptap/extension-document": "2.2.4",
"@tiptap/extension-hard-break": "2.2.4",
"@tiptap/extension-history": "2.2.4",
"@tiptap/extension-italic": "2.2.4",
"@tiptap/extension-mention": "2.2.4",
@ -57,16 +63,19 @@
"@tiptap/starter-kit": "2.2.4",
"@tiptap/suggestion": "2.2.4",
"@tiptap/vue-3": "2.2.4",
"@unocss/nuxt": "^66.1.2",
"@unhead/schema": "^2.0.17",
"@unlazy/nuxt": "^0.12.4",
"@unocss/nuxt": "^66.5.2",
"@upstash/redis": "^1.27.1",
"@vercel/kv": "^3.0.0",
"@vue-macros/nuxt": "^1.11.12",
"@vue-macros/nuxt": "^1.13.5",
"@vueuse/core": "^12.0.0",
"@vueuse/gesture": "^2.0.0",
"@vueuse/integrations": "^12.0.0",
"@vueuse/math": "^12.0.0",
"@vueuse/motion": "2.2.6",
"@vueuse/nuxt": "^13.2.0",
"@vueuse/nuxt": "^13.9.0",
"@vueuse/shared": "^13.9.0",
"blurhash": "^2.0.5",
"browser-fs-access": "^0.38.0",
"cheerio": "^1.0.0",
@ -79,47 +88,53 @@
"form-data": "^4.0.0",
"fuse.js": "^7.0.0",
"github-reserved-names": "^2.0.4",
"happy-dom": "^16.0.0",
"happy-dom": "^20.0.0",
"idb-keyval": "^6.2.1",
"ignore-dependency-scripts": "^1.0.1",
"ioredis": "^5.7.0",
"iso-639-1": "^3.0.0",
"js-yaml": "^4.1.0",
"lru-cache": "^11.0.0",
"magic-string": "^0.30.19",
"masto": "^6.10.4",
"mocked-exports": "^0.1.1",
"node-emoji": "^2.1.3",
"node-mock-http": "^1.0.0",
"nuxt-security": "^2.2.0",
"nuxt": "^4.1.2",
"nuxt-security": "^2.4.0",
"page-lifecycle": "^0.1.2",
"pinia": "^3.0.2",
"pathe": "^2.0.3",
"pinia": "^3.0.3",
"postcss-nested": "^7.0.0",
"prosemirror-highlight": "^0.13.0",
"prosemirror-state": "^1.4.3",
"rollup-plugin-node-polyfills": "^0.2.1",
"shiki": "^1.22.2",
"simple-git": "^3.19.1",
"slimeform": "^0.10.0",
"stale-dep": "^0.8.0",
"std-env": "^3.7.0",
"string-length": "^5.0.1",
"tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log",
"tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store",
"theme-vitesse": "^0.8.0",
"tiny-decode": "^0.1.3",
"tippy.js": "^6.3.7",
"ufo": "^1.5.3",
"ultrahtml": "^1.5.3",
"unimport": "^3.10.0",
"unstorage": "^1.17.1",
"vite": "^7.1.7",
"vite-plugin-pwa": "^0.21.0",
"vue": "^3.5.4",
"vue-advanced-cropper": "^2.8.9",
"vue-i18n": "^11.1.12",
"vue-virtual-scroller": "2.0.0-beta.8",
"workbox-build": "^7.1.1",
"workbox-cacheable-response": "^7.1.0",
"workbox-expiration": "^7.1.0",
"workbox-precaching": "^7.1.0",
"workbox-routing": "^7.1.0",
"workbox-strategies": "^7.1.0",
"workbox-window": "^7.1.0",
"ws": "^8.15.1"
},
"devDependencies": {
"@antfu/eslint-config": "^5.1.0",
"@antfu/ni": "^24.4.0",
"@antfu/eslint-config": "^6.2.0",
"@types/chroma-js": "^3.1.1",
"@types/file-saver": "^2.0.7",
"@types/fnando__sparkline": "^0.3.7",
@ -127,39 +142,31 @@
"@types/js-yaml": "^4.0.9",
"@types/wicg-file-system-access": "^2023.10.6",
"@types/ws": "^8.18.1",
"@unlazy/nuxt": "^0.12.4",
"@unocss/eslint-config": "^66.4.1",
"@unocss/eslint-config": "^66.4.2",
"@vue/test-utils": "2.4.6",
"bumpp": "^10.2.2",
"bumpp": "^10.2.3",
"consola": "^3.4.2",
"eslint": "^9.32.0",
"eslint": "^9.39.1",
"eslint-plugin-format": "^1.0.1",
"flat": "^6.0.1",
"fs-extra": "^11.3.0",
"lint-staged": "^15.5.2",
"nuxt": "^3.18.1",
"fs-extra": "^11.3.1",
"lint-staged": "^16.1.6",
"prettier": "^3.6.2",
"sharp": "^0.34.3",
"sharp-ico": "^0.1.5",
"simple-git-hooks": "^2.13.1",
"tsx": "^4.20.3",
"typescript": "^5.4.4",
"vitest": "3.2.4",
"vitest": "4.0.9",
"vue-tsc": "^2.1.6"
},
"pnpm": {
"patchedDependencies": {
"pinceau": "patches/pinceau.patch"
}
},
"resolutions": {
"nuxt-component-meta": "0.13.0",
"unstorage": "^1.16.1",
"vitest": "3.2.4",
"nuxt-component-meta": "0.14.2",
"unstorage": "^1.17.3",
"vitest": "4.0.9",
"vue": "^3.5.4"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
"pre-commit": "npx lint-staged"
},
"lint-staged": {
"*": "eslint --fix"

View file

View file

@ -1,13 +0,0 @@
diff --git a/dist/index.d.ts b/dist/index.d.ts
index 612f1c7908c2e973870be08c6fba1515e6e2b9ca..445a3b0574c5388b624d537fc17cbf4a08973ded 100644
--- a/dist/index.d.ts
+++ b/dist/index.d.ts
@@ -115,7 +115,7 @@ interface ModuleHooks {
interface ModuleOptions extends PinceauOptions {
}
-declare module '@vue/runtime-core' {
+declare module 'vue' {
interface ComponentCustomProperties {
$dt: DtFunction;
$pinceau: ComputedRef<string>;

File diff suppressed because it is too large Load diff

View file

@ -1,2 +1,21 @@
packages:
- docs
ignoreWorkspaceRootCheck: true
ignoredBuiltDependencies:
- '@parcel/watcher'
- '@tailwindcss/oxide'
- esbuild
- vue-demi
onlyBuiltDependencies:
- better-sqlite3
- sharp
- simple-git-hooks
packageManagerStrict: false
shellEmulator: true
verifyDepsBeforeRun: install

View file

@ -2,7 +2,7 @@ import { writeFile } from 'node:fs/promises'
import fs from 'fs-extra'
import { ofetch } from 'ofetch'
import { join, resolve } from 'pathe'
import { elkTeamMembers } from '../app/composables/about'
import { elkTeamMembers } from '../app/composables/about.ts'
const avatarsDir = resolve('./public/avatars/')

View file

@ -1,8 +1,8 @@
import { Buffer } from 'node:buffer'
import { readFile, writeFile } from 'node:fs/promises'
import { createResolver } from '@nuxt/kit'
import { flatten, unflatten } from 'flat'
import { currentLocales } from '../config/i18n'
import { createResolver } from 'nuxt/kit'
import { currentLocales } from '../config/i18n.ts'
const resolver = createResolver(import.meta.url)

View file

@ -1,4 +1,4 @@
import type { ThemeColors } from '~/composables/settings'
import type { ThemeColors } from '../app/composables/settings'
import chroma from 'chroma-js'
// #cc7d24 -> hcl(67.14,62.19,59.56)

View file

@ -1,13 +1,13 @@
import type { ElkTranslationStatus } from '#shared/types/translation-status'
import type { LocaleEntry } from '../docs/types'
import type { ElkTranslationStatus } from '../shared/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 { countryLocaleVariants, currentLocales } from '../config/i18n'
import { createResolver } from 'nuxt/kit'
import { countryLocaleVariants, currentLocales } from '../config/i18n.ts'
export const localeData: [code: string, file: string[], title: string][]
= currentLocales.map((l: any) => [l.code, l.files ? l.files : [l.file!], l.name ?? l.code])
= currentLocales.map((l: any) => [l.code, l.files ? l.files : [l.file!], l.name ?? l.code])
function merge(src: Record<string, any>, dst: Record<string, any>) {
for (const key in src) {

View file

@ -1,7 +1,7 @@
import process from 'node:process'
import fs from 'fs-extra'
import { emojiPrefix, iconifyEmojiPackage } from '../config/emojis'
import { colorsMap } from './generate-themes'
import { emojiPrefix, iconifyEmojiPackage } from '../config/emojis.ts'
import { colorsMap } from './generate-themes.ts'
const dereference = process.platform === 'win32' ? true : undefined

View file

@ -1,6 +1,6 @@
import { stringifyQuery } from 'ufo'
import { defaultUserAgent } from '~~/server/utils/shared'
import { defaultUserAgent, invalidateApp } from '~~/server/utils/shared'
export default defineEventHandler(async (event) => {
let { server, origin } = getRouterParams(event)
@ -43,7 +43,51 @@ export default defineEventHandler(async (event) => {
const url = `/signin/callback?${stringifyQuery({ server, token: result.access_token, vapid_key: app.vapid_key })}`
await sendRedirect(event, url, 302)
}
catch {
catch (error: any) {
// Check for invalid client error (OAuth app deleted)
if (error?.data?.error === 'invalid_client'
|| (error?.statusCode === 401 && error?.data?.error_description?.includes('Client authentication failed'))) {
// Invalidate cached app and retry once
await invalidateApp(origin, server)
try {
const newApp = await getApp(origin, server)
if (!newApp) {
throw createError({
statusCode: 400,
statusMessage: `Failed to re-register app for server: ${server}`,
})
}
const retryResult: any = await $fetch(`https://${server}/oauth/token`, {
method: 'POST',
headers: {
'user-agent': defaultUserAgent,
},
body: {
client_id: newApp.client_id,
client_secret: newApp.client_secret,
redirect_uri: getRedirectURI(origin, server),
grant_type: 'authorization_code',
code,
scope: 'read write follow push',
},
retry: 1,
})
const url = `/signin/callback?${stringifyQuery({ server, token: retryResult.access_token, vapid_key: newApp.vapid_key })}`
await sendRedirect(event, url, 302)
return
}
catch {
throw createError({
statusCode: 400,
statusMessage: 'OAuth application recovery failed. Please try again.',
})
}
}
// Other errors (network, invalid code, etc.)
throw createError({
statusCode: 400,
statusMessage: 'Could not complete log in.',

View file

@ -111,6 +111,12 @@ export async function deleteApp(server: string) {
await storage.removeItem(key)
}
export async function invalidateApp(origin: string, server: string) {
const host = origin.replace(/^https?:\/\//, '').replace(/\W/g, '-').replace(/\?.*$/, '')
const key = `servers:v4:${server}:${host}.json`.toLowerCase()
await storage.removeItem(key)
}
export async function listServers() {
const keys = await storage.getKeys('servers:v4:')
const servers = new Set<string>()

View file

@ -1,5 +1,6 @@
{
"extends": "../tsconfig.json",
"extends": "../.nuxt/tsconfig.app.json",
"noEmit": true,
"compilerOptions": {
"lib": ["ESNext", "WebWorker", "DOM.Iterable"],
"types": ["vite/client"]

View file

@ -0,0 +1 @@
{"root":["./elk-sw.ts","./notification.ts","./share-target.ts","./types.ts","./web-push-notifications.ts"],"version":"5.9.2"}

View file

@ -1,6 +1,6 @@
/// <reference lib="WebWorker" />
/// <reference types="vite/client" />
import type { PushPayload } from '~~/service-worker/types'
import type { PushPayload } from './types'
import { ELK_PAGE_LIFECYCLE_FROZEN } from '../app/constants'
import {
closeDatabaseConnections,

View file

@ -1,5 +1,5 @@
import type { RouteLocationRaw } from '#vue-router'
import type { mastodon } from 'masto'
import type { RouteLocationRaw } from 'vue-router'
import type { MarkNonNullable, Mutable } from './utils'
export interface AppInfo {
@ -43,8 +43,6 @@ export interface GroupedLikeNotifications {
export type NotificationSlot = GroupedNotifications | GroupedLikeNotifications | mastodon.v1.Notification
export type TranslateFn = ReturnType<typeof useI18n>['t']
export interface DraftItem {
editingStatus?: mastodon.v1.Status
initialText?: string

View file

@ -13,7 +13,7 @@ function status(id: string, filtered?: mastodon.v1.FilterContext): mastodon.v1.S
if (filtered) {
fakeStatus.filtered
= [
= [
{
filter: {
filterAction: 'hide',

View file

@ -1,3 +1,20 @@
{
"extends": "./.nuxt/tsconfig.json"
"references": [
{
"path": "./.nuxt/tsconfig.app.json"
},
{
"path": "./.nuxt/tsconfig.server.json"
},
{
"path": "./.nuxt/tsconfig.shared.json"
},
{
"path": "./.nuxt/tsconfig.node.json"
},
{
"path": "./service-worker/tsconfig.json"
}
],
"files": []
}

View file

@ -1,5 +1,3 @@
import type { Variant } from 'unocss'
import process from 'node:process'
import { variantParentMatcher } from '@unocss/preset-mini/utils'
import {
@ -108,29 +106,14 @@ export default defineConfig({
},
},
variants: [
...(process.env.TAURI_PLATFORM
? <Variant<any>[]>[(matcher) => {
if (!matcher.startsWith('native:'))
return
return {
matcher: matcher.slice(7),
layer: 'native',
}
}]
: []),
...(process.env.TAURI_PLATFORM !== 'macos'
? <Variant<any>[]>[
(matcher) => {
if (!matcher.startsWith('native-mac:'))
return
return {
matcher: matcher.slice(11),
layer: 'native-mac',
}
},
]
: []
),
(matcher) => {
if (!matcher.startsWith('native-mac:'))
return
return {
matcher: matcher.slice(11),
layer: 'native-mac',
}
},
variantParentMatcher('fullscreen', '@media (display-mode: fullscreen)'),
variantParentMatcher('coarse-pointer', '@media (pointer: coarse)'),
],

View file

@ -1,22 +1,30 @@
import { defineVitestConfig } from '@nuxt/test-utils/config'
import { defineVitestProject } from '@nuxt/test-utils/config'
import { isCI } from 'std-env'
import { defineConfig } from 'vitest/config'
export default defineVitestConfig({
export default defineConfig({
define: {
'process.test': 'true',
},
test: {
reporters: isCI ? ['default', 'hanging-process'] : ['default'],
setupFiles: [
'./tests/setup.ts',
],
environmentOptions: {
nuxt: {
mock: {
indexedDb: true,
intersectionObserver: true,
projects: [
await defineVitestProject({
test: {
name: 'nuxt',
setupFiles: [
'./tests/setup.ts',
],
environmentOptions: {
nuxt: {
mock: {
indexedDb: true,
intersectionObserver: true,
},
},
},
},
},
},
}),
],
},
})