kopia lustrzana https://github.com/elk-zone/elk
Merge branch 'elk-zone:main' into main
commit
c241aeeec5
|
@ -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@v5.0.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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@v5
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -45,7 +45,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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
const { as = 'div', active } = defineProps<{
|
||||
as: any
|
||||
as?: string
|
||||
active: boolean
|
||||
}>()
|
||||
|
||||
|
|
|
@ -30,21 +30,21 @@ 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 px5 py2 :class="{ 'xl:hidden': $route.name !== 'tag' }" class="native:xl:flex" border="b base">
|
||||
<div flex gap-3 items-center :overflow-hidden="!noOverflowHidden ? '' : false" py2 w-full>
|
||||
<NuxtLink
|
||||
v-if="backOnSmallScreen || back" flex="~ gap1" items-center btn-text p-0 xl:hidden
|
||||
<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"
|
||||
btn-text flex items-center ms="-3" p-3 xl:hidden
|
||||
:aria-label="$t('nav.back')"
|
||||
@click="$router.go(-1)"
|
||||
>
|
||||
<div i-ri:arrow-left-line class="rtl-flip" />
|
||||
</NuxtLink>
|
||||
<div :truncate="!noOverflowHidden ? '' : false" flex w-full data-tauri-drag-region class="native-mac:justify-start native-mac:text-center">
|
||||
<div text-lg i-ri:arrow-left-line class="rtl-flip" />
|
||||
</button>
|
||||
<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 />
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -33,17 +33,16 @@ router.afterEach(() => {
|
|||
{{ $t('app_name') }} <sup text-sm italic mt-1>{{ env === 'release' ? 'alpha' : env }}</sup>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
<div
|
||||
hidden xl:flex items-center me-8 mt-2 gap-1
|
||||
>
|
||||
<CommonTooltip :content="$t('nav.back')">
|
||||
<NuxtLink
|
||||
<div hidden xl:flex items-center me-6 mt-2 gap-1>
|
||||
<CommonTooltip :content="$t('nav.back')" :distance="0">
|
||||
<button
|
||||
type="button"
|
||||
:aria-label="$t('nav.back')"
|
||||
:class="{ 'pointer-events-none op0': !back || back === '/', 'xl:flex': $route.name !== 'tag' }"
|
||||
btn-text p-3 :class="{ 'pointer-events-none op0': !back || back === '/', 'xl:flex': $route.name !== 'tag' }"
|
||||
@click="$router.go(-1)"
|
||||
>
|
||||
<div text-xl i-ri:arrow-left-line class="rtl-flip" btn-text />
|
||||
</NuxtLink>
|
||||
<div text-xl i-ri:arrow-left-line class="rtl-flip" />
|
||||
</button>
|
||||
</CommonTooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -29,7 +29,7 @@ const emit = defineEmits<{
|
|||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { threadItems, threadIsActive, publishThread } = threadComposer ?? useThreadComposer(draftKey)
|
||||
const { threadItems, threadIsActive, publishThread, threadIsSending } = threadComposer ?? useThreadComposer(draftKey)
|
||||
|
||||
const draft = computed({
|
||||
get: () => threadItems.value[draftItemIndex],
|
||||
|
@ -237,9 +237,13 @@ function stopQuestionMarkPropagation(e: KeyboardEvent) {
|
|||
e.stopImmediatePropagation()
|
||||
}
|
||||
|
||||
const userSettings = useUserSettings()
|
||||
|
||||
const optimizeForLowPerformanceDevice = computed(() => getPreferences(userSettings.value, 'optimizeForLowPerformanceDevice'))
|
||||
|
||||
const languageDetectorInGlobalThis = 'LanguageDetector' in globalThis
|
||||
let supportsLanguageDetector = languageDetectorInGlobalThis && await (globalThis as any).LanguageDetector.availability() === 'available'
|
||||
let languageDetector: { detect: (arg0: string) => any }
|
||||
let supportsLanguageDetector = !optimizeForLowPerformanceDevice.value && languageDetectorInGlobalThis && await (globalThis as any).LanguageDetector.availability() === 'available'
|
||||
let languageDetector: { detect: (arg0: string, option: { signal: AbortSignal }) => any }
|
||||
// If the API is supported, but the model not loaded yet…
|
||||
if (languageDetectorInGlobalThis && !supportsLanguageDetector) {
|
||||
// …trigger the model download
|
||||
|
@ -255,26 +259,36 @@ function countLetters(text: string) {
|
|||
return letters.length
|
||||
}
|
||||
|
||||
async function detectLanguage() {
|
||||
let detectLanguageAbortController = new AbortController()
|
||||
|
||||
const detectLanguage = useDebounceFn(async () => {
|
||||
if (!supportsLanguageDetector) {
|
||||
return
|
||||
}
|
||||
if (!languageDetector) {
|
||||
// maybe we dont want to mess with this with abort....
|
||||
languageDetector = await (globalThis as any).LanguageDetector.create()
|
||||
}
|
||||
// we stop previously running language detection process
|
||||
detectLanguageAbortController.abort()
|
||||
detectLanguageAbortController = new AbortController()
|
||||
const text = htmlToText(editor.value?.getHTML() || '')
|
||||
if (!text || countLetters(text) <= 5) {
|
||||
draft.value.params.language = preferredLanguage.value
|
||||
return
|
||||
}
|
||||
try {
|
||||
const detectedLanguage = (await languageDetector.detect(text))[0].detectedLanguage
|
||||
const detectedLanguage = (await languageDetector.detect(text, { signal: detectLanguageAbortController.signal }))[0].detectedLanguage
|
||||
draft.value.params.language = detectedLanguage === 'und' ? preferredLanguage.value : detectedLanguage.substring(0, 2)
|
||||
}
|
||||
catch {
|
||||
catch (e) {
|
||||
// if error or abort we end up there
|
||||
if ((e as Error).name !== 'AbortError') {
|
||||
console.error(e)
|
||||
}
|
||||
draft.value.params.language = preferredLanguage.value
|
||||
}
|
||||
}
|
||||
}, 500)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -563,18 +577,18 @@ async function detectLanguage() {
|
|||
<button
|
||||
v-if="!threadIsActive || isFinalItemOfThread"
|
||||
btn-solid rounded-3 text-sm w-full flex="~ gap1" items-center md:w-fit class="publish-button"
|
||||
:aria-disabled="isPublishDisabled || isExceedingCharacterLimit" aria-describedby="publish-tooltip"
|
||||
:disabled="isPublishDisabled || isExceedingCharacterLimit"
|
||||
:aria-disabled="isPublishDisabled || isExceedingCharacterLimit || threadIsSending" aria-describedby="publish-tooltip"
|
||||
:disabled="isPublishDisabled || isExceedingCharacterLimit || threadIsSending"
|
||||
@click="publish"
|
||||
>
|
||||
<span v-if="isSending" block animate-spin preserve-3d>
|
||||
<span v-if="isSending || threadIsSending" block animate-spin preserve-3d>
|
||||
<div block i-ri:loader-2-fill />
|
||||
</span>
|
||||
<span v-if="failedMessages.length" block>
|
||||
<div block i-carbon:face-dizzy-filled />
|
||||
</span>
|
||||
<template v-if="threadIsActive">
|
||||
<span>{{ $t('action.publish_thread') }} </span>
|
||||
<span>{{ !threadIsSending ? $t('action.publish_thread') : $t('state.publishing') }} </span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span v-if="draft.editingStatus">{{ $t('action.save_changes') }}</span>
|
||||
|
|
|
@ -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: {},
|
||||
})
|
||||
|
||||
/**
|
||||
|
@ -104,11 +109,12 @@ export function parseMastodonHTML(
|
|||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/`/g, '`')
|
||||
.replace(/\*/g, '*')
|
||||
const classes = lang ? ` class="language-${lang}"` : ''
|
||||
return `><pre><code${classes}>${code}</code></pre>`
|
||||
})
|
||||
.replace(/`([^`\n]*)`/g, (_1, raw) => {
|
||||
return raw ? `<code>${htmlToText(raw).replace(/</g, '<').replace(/>/g, '>')}</code>` : ''
|
||||
return raw ? `<code>${htmlToText(raw).replace(/</g, '<').replace(/>/g, '>').replace(/\*/g, '*')}</code>` : ''
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -95,9 +95,9 @@ export async function toggleMuteAccount(relationship: mastodon.v1.Relationship,
|
|||
relationship!.muting = !relationship!.muting
|
||||
relationship = relationship!.muting
|
||||
? await client.value.v1.accounts.$select(account.id).mute({
|
||||
duration,
|
||||
notifications,
|
||||
})
|
||||
duration,
|
||||
notifications,
|
||||
})
|
||||
: await client.value.v1.accounts.$select(account.id).unmute()
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ export function useThreadComposer(draftKey: string, initial?: () => DraftItem) {
|
|||
*/
|
||||
const threadIsActive = computed<boolean>(() => draftItems.value.length > 1)
|
||||
|
||||
const threadIsSending = ref(false)
|
||||
|
||||
/**
|
||||
* Add an item to the thread
|
||||
*/
|
||||
|
@ -44,6 +46,7 @@ export function useThreadComposer(draftKey: string, initial?: () => DraftItem) {
|
|||
async function publishThread() {
|
||||
const allFailedMessages: Array<string> = []
|
||||
const isAReplyThread = Boolean(draftItems.value[0].params.inReplyToId)
|
||||
threadIsSending.value = true
|
||||
|
||||
let lastPublishedStatus: mastodon.v1.Status | null = null
|
||||
let amountPublished = 0
|
||||
|
@ -72,6 +75,7 @@ export function useThreadComposer(draftKey: string, initial?: () => DraftItem) {
|
|||
}
|
||||
// Remove all published items from the thread
|
||||
draftItems.value.splice(0, amountPublished)
|
||||
threadIsSending.value = false
|
||||
|
||||
// If we have errors, return them
|
||||
if (allFailedMessages.length > 0)
|
||||
|
@ -90,5 +94,6 @@ export function useThreadComposer(draftKey: string, initial?: () => DraftItem) {
|
|||
addThreadItem,
|
||||
removeThreadItem,
|
||||
publishThread,
|
||||
threadIsSending,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -10,6 +10,6 @@ catch (err) {
|
|||
|
||||
<template>
|
||||
<MainContent text-base grid gap-3 m3>
|
||||
<img rounded-3 :src="instance.thumbnail.url">
|
||||
<img v-if="instance !== undefined" rounded-3 :src="instance.thumbnail.url">
|
||||
</MainContent>
|
||||
</template>
|
||||
|
|
|
@ -80,7 +80,7 @@ const locales: LocaleObjectData[] = [
|
|||
file: 'en.json',
|
||||
name: 'English',
|
||||
},
|
||||
({
|
||||
{
|
||||
// @ts-expect-error ar used as placeholder
|
||||
code: 'ar',
|
||||
file: 'ar.json',
|
||||
|
@ -90,8 +90,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 +100,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,7 +110,7 @@ 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',
|
||||
|
|
|
@ -6,7 +6,7 @@ services:
|
|||
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.q. mkdir ./elk-storage; sudo chown 911:911 ./elk-storage
|
||||
# e.g., mkdir ./elk-storage; sudo chown 911:911 ./elk-storage
|
||||
- './elk-storage:/elk/data'
|
||||
ports:
|
||||
- 5314:5314
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@nuxt-themes/docus": "^1.15.1",
|
||||
"nuxt": "^3.17.6"
|
||||
"nuxt": "^3.18.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import type { Ref } from 'vue'
|
||||
import type { UnwrapNestedRefs } from 'vue'
|
||||
import type { Ref, UnwrapNestedRefs } from 'vue'
|
||||
|
||||
export interface PwaInjection {
|
||||
isInstalled: boolean
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
},
|
||||
})
|
|
@ -1 +0,0 @@
|
|||
export const env = useAppConfig().env
|
|
@ -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)
|
||||
})
|
|
@ -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')!
|
||||
}
|
||||
})
|
|
@ -1,2 +0,0 @@
|
|||
export const driver = undefined
|
||||
export const fsBase = ''
|
|
@ -1,29 +0,0 @@
|
|||
import { Store } from 'tauri-plugin-store-api'
|
||||
import { createStorage } from 'unstorage'
|
||||
|
||||
const store = new Store('.servers.dat')
|
||||
const storage = createStorage()
|
||||
storage.mount('servers', {
|
||||
getKeys() {
|
||||
return store.keys()
|
||||
},
|
||||
async removeItem(key: string) {
|
||||
await store.delete(key)
|
||||
},
|
||||
clear() {
|
||||
return store.clear()
|
||||
},
|
||||
hasItem(key: string) {
|
||||
return store.has(key)
|
||||
},
|
||||
setItem(key: string, value: any) {
|
||||
return store.set(key, value)
|
||||
},
|
||||
getItem(key: string) {
|
||||
return store.get(key)
|
||||
},
|
||||
})
|
||||
|
||||
export function useStorage() {
|
||||
return storage
|
||||
}
|
|
@ -40,7 +40,6 @@ export default defineNuxtConfig({
|
|||
'~~/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',
|
||||
],
|
||||
|
@ -62,6 +61,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 +71,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',
|
||||
],
|
||||
|
|
37
package.json
37
package.json
|
@ -2,7 +2,7 @@
|
|||
"name": "@elk-zone/elk",
|
||||
"type": "module",
|
||||
"version": "0.16.0",
|
||||
"packageManager": "pnpm@9.15.9",
|
||||
"packageManager": "pnpm@10.17.0",
|
||||
"license": "MIT",
|
||||
"homepage": "https://elk.zone/",
|
||||
"main": "./nuxt.config.ts",
|
||||
|
@ -39,7 +39,7 @@
|
|||
"@iconify/json": "^2.2.170",
|
||||
"@iconify/utils": "^2.1.22",
|
||||
"@nuxt/devtools": "^2.4.1",
|
||||
"@nuxt/test-utils": "^3.19.0",
|
||||
"@nuxt/test-utils": "^3.19.2",
|
||||
"@nuxtjs/color-mode": "^3.5.2",
|
||||
"@nuxtjs/i18n": "^9.5.4",
|
||||
"@pinia/nuxt": "^0.11.0",
|
||||
|
@ -68,7 +68,7 @@
|
|||
"@vueuse/motion": "2.2.6",
|
||||
"@vueuse/nuxt": "^13.2.0",
|
||||
"blurhash": "^2.0.5",
|
||||
"browser-fs-access": "^0.35.0",
|
||||
"browser-fs-access": "^0.38.0",
|
||||
"cheerio": "^1.0.0",
|
||||
"chroma-js": "^3.0.0",
|
||||
"emoji-mart": "^5.5.2",
|
||||
|
@ -88,7 +88,6 @@
|
|||
"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",
|
||||
"page-lifecycle": "^0.1.2",
|
||||
"pinia": "^3.0.2",
|
||||
|
@ -101,8 +100,6 @@
|
|||
"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",
|
||||
|
@ -117,8 +114,8 @@
|
|||
"ws": "^8.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^4.13.1",
|
||||
"@antfu/ni": "^24.4.0",
|
||||
"@antfu/eslint-config": "^5.4.1",
|
||||
"@antfu/ni": "^26.0.1",
|
||||
"@types/chroma-js": "^3.1.1",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/fnando__sparkline": "^0.3.7",
|
||||
|
@ -127,21 +124,21 @@
|
|||
"@types/wicg-file-system-access": "^2023.10.6",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@unlazy/nuxt": "^0.12.4",
|
||||
"@unocss/eslint-config": "^66.3.2",
|
||||
"@unocss/eslint-config": "^66.4.2",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"bumpp": "^10.2.0",
|
||||
"bumpp": "^10.2.3",
|
||||
"consola": "^3.4.2",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-format": "^1.0.1",
|
||||
"flat": "^6.0.1",
|
||||
"fs-extra": "^11.3.0",
|
||||
"lint-staged": "^15.5.2",
|
||||
"nuxt": "^3.17.6",
|
||||
"prettier": "^3.5.3",
|
||||
"sharp": "^0.34.2",
|
||||
"fs-extra": "^11.3.1",
|
||||
"lint-staged": "^16.1.6",
|
||||
"nuxt": "^3.18.1",
|
||||
"prettier": "^3.6.2",
|
||||
"sharp": "^0.34.3",
|
||||
"sharp-ico": "^0.1.5",
|
||||
"simple-git-hooks": "^2.13.0",
|
||||
"tsx": "^4.20.3",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"tsx": "^4.20.5",
|
||||
"typescript": "^5.4.4",
|
||||
"vitest": "3.2.4",
|
||||
"vue-tsc": "^2.1.6"
|
||||
|
@ -152,8 +149,8 @@
|
|||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"nuxt-component-meta": "0.12.0",
|
||||
"unstorage": "^1.16.0",
|
||||
"nuxt-component-meta": "0.14.0",
|
||||
"unstorage": "^1.17.1",
|
||||
"vitest": "3.2.4",
|
||||
"vue": "^3.5.4"
|
||||
},
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
declare module 'page-lifecycle/dist/lifecycle.mjs' {
|
||||
type PageLifecycleState = 'active' | 'passive' | 'hidden' | 'frozen' | 'terminated'
|
||||
type PageLifecycleState = 'active' | 'passive' | 'hidden' | 'frozen' | 'terminated'
|
||||
|
||||
interface PageLifecycleEvent extends Event {
|
||||
newState: PageLifecycleState
|
||||
oldState: PageLifecycleState
|
||||
}
|
||||
interface PageLifecycle extends EventTarget {
|
||||
get state(): PageLifecycleState
|
||||
get pageWasDiscarded(): boolean
|
||||
addUnsavedChanges: (id: symbol | any) => void
|
||||
removeUnsavedChanges: (id: symbol | any) => void
|
||||
addEventListener: (type: string, listener: (evt: PageLifecycleEvent) => void) => void
|
||||
}
|
||||
const lifecycle: PageLifecycle
|
||||
export default lifecycle
|
||||
interface PageLifecycleEvent extends Event {
|
||||
newState: PageLifecycleState
|
||||
oldState: PageLifecycleState
|
||||
}
|
||||
interface PageLifecycle extends EventTarget {
|
||||
get state(): PageLifecycleState
|
||||
get pageWasDiscarded(): boolean
|
||||
addUnsavedChanges: (id: symbol | any) => void
|
||||
removeUnsavedChanges: (id: symbol | any) => void
|
||||
addEventListener: (type: string, listener: (evt: PageLifecycleEvent) => void) => void
|
||||
}
|
||||
const lifecycle: PageLifecycle
|
||||
export default lifecycle
|
||||
}
|
||||
|
|
9611
pnpm-lock.yaml
9611
pnpm-lock.yaml
Plik diff jest za duży
Load Diff
|
@ -7,7 +7,7 @@ import { flatten } from 'flat'
|
|||
import { countryLocaleVariants, currentLocales } from '../config/i18n'
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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.',
|
||||
|
|
|
@ -60,7 +60,7 @@ async function fetchAppInfo(origin: string, server: string) {
|
|||
},
|
||||
body: {
|
||||
client_name: APP_NAME + (env !== 'release' ? ` (${env})` : ''),
|
||||
website: 'https://elk.zone',
|
||||
website: origin,
|
||||
redirect_uris: getRedirectURI(origin, server),
|
||||
scopes: 'read write follow push',
|
||||
},
|
||||
|
@ -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>()
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`content-rich > asterisk paris in code block 1`] = `"<p><pre class="code-block">1 * 2 * 3</pre></p>"`;
|
||||
|
||||
exports[`content-rich > asterisk paris in inline code 1`] = `
|
||||
"<p><code>1 * 2 * 3</code></p>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`content-rich > block with backticks 1`] = `"<p><pre class="code-block">[(\`number string) (\`tag string)]</pre></p>"`;
|
||||
|
||||
exports[`content-rich > block with injected html, with a known language 1`] = `
|
||||
|
|
|
@ -186,6 +186,16 @@ describe('content-rich', () => {
|
|||
`)
|
||||
expect(formatted).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it ('asterisk paris in inline code', async () => {
|
||||
const { formatted } = await render('<p>`1 * 2 * 3`</p>')
|
||||
expect(formatted).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it ('asterisk paris in code block', async () => {
|
||||
const { formatted } = await render('<p>```<br />1 * 2 * 3<br />```</p>')
|
||||
expect(formatted).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('editor', () => {
|
||||
|
|
|
@ -13,7 +13,7 @@ function status(id: string, filtered?: mastodon.v1.FilterContext): mastodon.v1.S
|
|||
|
||||
if (filtered) {
|
||||
fakeStatus.filtered
|
||||
= [
|
||||
= [
|
||||
{
|
||||
filter: {
|
||||
filterAction: 'hide',
|
||||
|
|
|
@ -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)'),
|
||||
],
|
||||
|
|
Ładowanie…
Reference in New Issue