1
0

Merge branch 'main' into userquin/feat-track-scroll-position

This commit is contained in:
userquin 2023-02-15 16:48:44 +01:00
commit 369501efca
33 changed files with 667 additions and 116 deletions

45
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,45 @@
# Code Of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, political party, or sexual identity and orientation. Note, however, that religion, political party, or other ideological affiliation provide no exemptions for the behavior we outline as unacceptable in this Code of Conduct.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team by DM at [the Elk Discord](https://chat.elk.zone). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org

View File

@ -117,6 +117,10 @@ Elk uses [Vitest](https://vitest.dev). You can run the test suite with:
nr test
```
## 📲 PWA
You can consult the [PWA documentation](https://docs.elk.zone/docs/pwa) to learn more about the PWA capabilities on Elk, how to install Elk PWA in your desktop or mobile device and some hints about PWA stuff on Elk.
## 🦄 Stack
- [Vite](https://vitejs.dev/) - Next Generation Frontend Tooling
@ -129,7 +133,7 @@ nr test
- [Iconify](https://github.com/iconify/icon-sets#iconify-icon-sets-in-json-format) - Iconify icon sets in JSON format
- [Masto.js](https://neet.github.io/masto.js) - Mastodon API client in TypeScript
- [shiki](https://shiki.matsu.io/) - A beautiful Syntax Highlighter
- [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa) - Prompt for update and push notifications
- [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa) - Prompt for update, Web Push Notifications and Web Share Target API
## 👨‍💻 Contributors

View File

@ -11,7 +11,7 @@ defineProps<{
text-secondary-light
>
<slot name="prepend" />
<CommonTooltip :content="$t('account.bot')" :disabled="showLabel">
<CommonTooltip no-auto-focus :content="$t('account.bot')" :disabled="showLabel">
<div i-mdi:robot-outline />
</CommonTooltip>
<div v-if="showLabel">

View File

@ -1,10 +1,11 @@
<script setup lang="ts">
import { decode } from 'blurhash'
const { blurhash, src, srcset } = defineProps<{
const { blurhash, src, srcset, shouldLoadImage = true } = defineProps<{
blurhash?: string | null | undefined
src: string
srcset?: string
shouldLoadImage?: boolean
}>()
defineOptions({
@ -19,7 +20,7 @@ const placeholderSrc = $computed(() => {
return getDataUrlFromArr(pixels, 32, 32)
})
onMounted(() => {
function loadImage() {
const img = document.createElement('img')
img.onload = () => {
@ -34,6 +35,16 @@ onMounted(() => {
setTimeout(() => {
isLoaded.value = true
}, 3_000)
}
onMounted(() => {
if (shouldLoadImage)
loadImage()
})
watch(() => shouldLoadImage, () => {
if (shouldLoadImage)
loadImage()
})
</script>

View File

@ -1,6 +1,8 @@
<script setup lang="ts">
// only one icon can be lit up at the same time
const moreMenuVisible = ref(false)
const { notifications } = useNotifications()
</script>
<template>
@ -18,7 +20,12 @@ const moreMenuVisible = ref(false)
<div i-ri:search-line />
</NuxtLink>
<NuxtLink to="/notifications" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
<div i-ri:notification-4-line />
<div flex relative>
<div class="i-ri:notification-4-line" text-xl />
<div v-if="notifications" class="top-[-0.3rem] right-[-0.3rem]" absolute font-bold rounded-full h-4 w-4 text-xs bg-primary text-inverted flex items-center justify-center>
{{ notifications < 10 ? notifications : '•' }}
</div>
</div>
</NuxtLink>
<NuxtLink to="/conversations" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
<div i-ri:at-line />

View File

@ -25,7 +25,7 @@ const toggleApply = () => {
<template>
<div relative group>
<StatusAttachment :attachment="attachment" w-full />
<StatusAttachment :attachment="attachment" w-full is-preview />
<div absolute right-2 top-2>
<div
v-if="removable"
@ -63,7 +63,7 @@ const toggleApply = () => {
{{ $t('action.close') }}
</button>
</div>
<StatusAttachment :attachment="attachment" w-full />
<StatusAttachment :attachment="attachment" w-full is-preview />
</div>
</ModalDialog>
</div>

View File

@ -27,7 +27,7 @@ const emit = defineEmits<{
const { t } = useI18n()
const draftState = useDraft(draftKey, initial)
const { draft, isEmpty } = $(draftState)
const { draft } = $(draftState)
const {
isExceedingAttachmentLimit, isUploading, failedAttachments, isOverDropZone,
@ -48,8 +48,6 @@ const { editor } = useTiptap({
set: (newVal) => {
draft.params.status = newVal
draft.lastUpdated = Date.now()
if (isEmpty)
clearEmptyDrafts()
},
}),
placeholder: computed(() => placeholder ?? draft.params.inReplyToId ? t('placeholder.replying') : t('placeholder.default_1')),
@ -154,7 +152,7 @@ defineExpose({
<div id="state-editing" text-secondary self-center>
{{ $t('state.editing') }}
</div>
<StatusCard :status="draft.editingStatus" :actions="false" :hover="false" px-0 />
<StatusCard :status="draft.editingStatus" :actions="false" :hover="false" is-preview px-0 />
</div>
<div border="b dashed gray/40" />
</template>

View File

@ -66,12 +66,14 @@ const chooseIcon = (i: number, text: string) => {
</CommonDropdown>
<input
v-model="form.fieldsAttributes[i - 1].name"
type="text" placeholder="Label"
type="text" placeholder-text-secondary
:placeholder="$t('settings.profile.appearance.profile_metadata_label')"
input-base
>
<input
v-model="form.fieldsAttributes[i - 1].value"
type="text" placeholder="Content"
type="text" placeholder-text-secondary
:placeholder="$t('settings.profile.appearance.profile_metadata_value')"
input-base
>
</div>

View File

@ -1,8 +1,9 @@
<script setup lang="ts">
defineProps<{
const { disabled = false } = defineProps<{
icon?: string
text?: string
checked: boolean
disabled?: boolean
}>()
</script>
@ -10,10 +11,13 @@ defineProps<{
<button
exact-active-class="text-primary"
block w-full group focus:outline-none text-start
:disabled="disabled"
:class="disabled ? 'opacity-50 cursor-not-allowed' : ''"
>
<div
w-full flex w-fit px5 py3 md:gap2 gap4 items-center
transition-250 group-hover:bg-active
transition-250
:class="disabled ? '' : 'group-hover:bg-active'"
group-focus-visible:ring="2 current"
>
<div flex-1 flex items-center md:gap2 gap4>

View File

@ -53,7 +53,7 @@ function removeDisabledTranslation(code: string) {
{{ availableOption.nativeName }}
</option>
</select>
<button class="btn-text" @click="addDisabledTranslation">
<button class="btn-text shrink-0" @click="addDisabledTranslation">
{{ $t('settings.language.translations.add') }}
</button>
</div>

View File

@ -1,14 +1,17 @@
<script setup lang="ts">
import { clamp } from '@vueuse/core'
import type { mastodon } from 'masto'
import { decode } from 'blurhash'
const {
attachment,
fullSize = false,
isPreview = false,
} = defineProps<{
attachment: mastodon.v1.MediaAttachment
attachments?: mastodon.v1.MediaAttachment[]
fullSize?: boolean
isPreview?: boolean
}>()
const src = $computed(() => attachment.previewUrl || attachment.url || attachment.remoteUrl!)
@ -89,51 +92,109 @@ useIntersectionObserver(video, (entries) => {
}, { threshold: 0.75 })
const userSettings = useUserSettings()
const shouldLoadAttachment = ref(isPreview || !getPreferences(userSettings.value, 'enableDataSaving'))
function loadAttachment() {
shouldLoadAttachment.value = true
}
const blurHashSrc = $computed(() => {
if (!attachment.blurhash)
return ''
const pixels = decode(attachment.blurhash, 32, 32)
return getDataUrlFromArr(pixels, 32, 32)
})
let videoThumbnail = shouldLoadAttachment.value
? attachment.previewUrl
: blurHashSrc
watch(shouldLoadAttachment, () => {
videoThumbnail = shouldLoadAttachment
? attachment.previewUrl
: blurHashSrc
})
</script>
<template>
<div relative ma flex :gap="isAudio ? '2' : ''">
<template v-if="type === 'video'">
<video
ref="video"
preload="none"
:poster="attachment.previewUrl"
muted
loop
playsinline
controls
rounded-lg
object-cover
fullscreen:object-contain
:width="attachment.meta?.original?.width"
:height="attachment.meta?.original?.height"
:style="{
aspectRatio,
objectPosition,
}"
<button
type="button"
relative
@click="!shouldLoadAttachment ? loadAttachment() : null"
>
<source :src="attachment.url || attachment.previewUrl" type="video/mp4">
</video>
<video
ref="video"
preload="none"
:poster="videoThumbnail"
muted
loop
playsinline
:controls="shouldLoadAttachment"
rounded-lg
object-cover
fullscreen:object-contain
:width="attachment.meta?.original?.width"
:height="attachment.meta?.original?.height"
:style="{
aspectRatio,
objectPosition,
}"
:class="!shouldLoadAttachment ? 'brightness-60 hover:brightness-70 transition-filter' : ''"
>
<source :src="attachment.url || attachment.previewUrl" type="video/mp4">
</video>
<span
v-if="!shouldLoadAttachment"
class="status-attachment-load"
absolute
text-sm
text-white
flex flex-col justify-center items-center
gap-3 w-6 h-6
pointer-events-none
i-ri:video-download-line
/>
</button>
</template>
<template v-else-if="type === 'gifv'">
<video
ref="video"
preload="none"
:poster="attachment.previewUrl"
muted
loop
playsinline
rounded-lg
object-cover
:width="attachment.meta?.original?.width"
:height="attachment.meta?.original?.height"
:style="{
aspectRatio,
objectPosition,
}"
<button
type="button"
relative
@click="!shouldLoadAttachment ? loadAttachment() : null"
>
<source :src="attachment.url || attachment.previewUrl" type="video/mp4">
</video>
<video
ref="video"
preload="none"
:poster="videoThumbnail"
muted
loop
playsinline
rounded-lg
object-cover
:width="attachment.meta?.original?.width"
:height="attachment.meta?.original?.height"
:style="{
aspectRatio,
objectPosition,
}"
>
<source :src="attachment.url || attachment.previewUrl" type="video/mp4">
</video>
<span
v-if="!shouldLoadAttachment"
class="status-attachment-load"
absolute
text-sm
text-white
flex flex-col justify-center items-center
gap-3 w-6 h-6
pointer-events-none
i-ri:video-download-line
/>
</button>
</template>
<template v-else-if="type === 'audio'">
<audio controls h-15>
@ -149,7 +210,8 @@ const userSettings = useUserSettings()
h-full
w-full
aria-label="Open image preview dialog"
@click="openMediaPreview(attachments ? attachments : [attachment], attachments?.indexOf(attachment) || 0)"
relative
@click="!shouldLoadAttachment ? loadAttachment() : openMediaPreview(attachments ? attachments : [attachment], attachments?.indexOf(attachment) || 0)"
>
<CommonBlurhash
:blurhash="attachment.blurhash"
@ -163,10 +225,24 @@ const userSettings = useUserSettings()
aspectRatio,
objectPosition,
}"
:should-load-image="shouldLoadAttachment"
rounded-lg
h-full
w-full
object-cover
:draggable="shouldLoadAttachment"
:class="!shouldLoadAttachment ? 'brightness-60 hover:brightness-70 transition-filter' : ''"
/>
<span
v-if="!shouldLoadAttachment"
class="status-attachment-load"
absolute
text-sm
text-white
flex flex-col justify-center items-center
gap-3 w-6 h-6
pointer-events-none
i-ri:file-download-line
/>
</button>
</template>
@ -202,3 +278,11 @@ const userSettings = useUserSettings()
</div>
</div>
</template>
<style lang="postcss">
.status-attachment-load {
left: 50%;
top: 50%;
translate: -50% -50%;
}
</style>

View File

@ -8,6 +8,7 @@ const props = withDefaults(
context?: mastodon.v2.FilterContext
hover?: boolean
faded?: boolean
isPreview?: boolean
// If we know the prev and next status in the timeline, we can simplify the card
older?: mastodon.v1.Status
@ -177,7 +178,7 @@ const showReplyTo = $computed(() => !replyToMain && !directReply)
</div>
<!-- Content -->
<StatusContent :status="status" :newer="newer" :context="context" mb2 :class="{ 'mt-2 mb1': isDM }" />
<StatusContent :status="status" :newer="newer" :context="context" :is-preview="isPreview" mb2 :class="{ 'mt-2 mb1': isDM }" />
<StatusActions v-if="actions !== false" v-show="!userSettings.zenMode" :status="status" />
</div>
</div>

View File

@ -5,6 +5,7 @@ const { status, context } = defineProps<{
status: mastodon.v1.Status
newer?: mastodon.v1.Status
context?: mastodon.v2.FilterContext | 'details'
isPreview?: boolean
}>()
const isDM = $computed(() => status.visibility === 'direct')
@ -44,6 +45,7 @@ const hasSensitiveSpoilerOrMedia = $computed(() => status.sensitive && (!!status
<StatusMedia
v-if="status.mediaAttachments?.length"
:status="status"
:is-preview="isPreview"
/>
<StatusPreviewCard
v-if="status.card"

View File

@ -1,9 +1,10 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
const { status } = defineProps<{
const { status, isPreview = false } = defineProps<{
status: mastodon.v1.Status | mastodon.v1.StatusEdit
fullSize?: boolean
isPreview?: boolean
}>()
</script>
@ -16,6 +17,7 @@ const { status } = defineProps<{
:full-size="fullSize"
w-full
h-full
:is-preview="isPreview"
/>
</template>
</div>

View File

@ -28,6 +28,13 @@ const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {
video: 'i-ri:play-line',
rich: 'i-ri:profile-line',
}
const userSettings = useUserSettings()
const shouldLoadAttachment = ref(!getPreferences(userSettings.value, 'enableDataSaving'))
function loadAttachment() {
shouldLoadAttachment.value = true
}
</script>
<template>
@ -54,6 +61,7 @@ const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {
'w-full aspect-[1.91]': !isSquare,
'rounded-lg': root,
}"
relative
>
<CommonBlurhash
:blurhash="card.blurhash"
@ -61,8 +69,30 @@ const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {
:width="card.width"
:height="card.height"
:alt="alt"
:should-load-image="shouldLoadAttachment"
w-full h-full object-cover
:class="!shouldLoadAttachment ? 'brightness-60' : ''"
/>
<button
v-if="!shouldLoadAttachment"
type="button"
absolute
class="status-preview-card-load bg-black/64"
p-2
transition
rounded
hover:bg-black
cursor-pointer
@click.stop.prevent="!shouldLoadAttachment ? loadAttachment() : null"
>
<span
text-sm
text-white
flex flex-col justify-center items-center
gap-3 w-6 h-6
i-ri:file-download-line
/>
</button>
</div>
<div
v-else
@ -76,3 +106,11 @@ const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {
<StatusPreviewCardInfo :p="isSquare ? 'x-4' : '4'" :root="root" :card="card" :provider="providerName" />
</NuxtLink>
</template>
<style lang="postcss">
.status-preview-card-load {
left: 50%;
top: 50%;
translate: -50% -50%;
}
</style>

View File

@ -30,7 +30,7 @@ export function getDefaultDraft(options: Partial<Mutable<mastodon.v1.CreateStatu
params: {
status: status || '',
inReplyToId,
visibility: currentUser.value?.account.source.privacy || visibility || 'public',
visibility: visibility || 'public',
sensitive: sensitive ?? false,
spoilerText: spoilerText || '',
language: language || '', // auto inferred from current language on posting
@ -141,7 +141,7 @@ export function directMessageUser(account: mastodon.v1.Account) {
export function clearEmptyDrafts() {
for (const key in currentUserDrafts.value) {
if (builtinDraftKeys.includes(key) && !isEmptyDraft(currentUserDrafts.value[key]))
if (builtinDraftKeys.includes(key))
continue
if (!currentUserDrafts.value[key].params || isEmptyDraft(currentUserDrafts.value[key]))
delete currentUserDrafts.value[key]

View File

@ -18,6 +18,7 @@ export interface PreferencesSettings {
hideAccountHoverCard: boolean
grayscaleMode: boolean
enableAutoplay: boolean
enableDataSaving: boolean
enablePinchToZoom: boolean
experimentalVirtualScroller: boolean
experimentalGitHubCards: boolean
@ -77,6 +78,7 @@ export const DEFAULT__PREFERENCES_SETTINGS: PreferencesSettings = {
hideAccountHoverCard: false,
grayscaleMode: false,
enableAutoplay: true,
enableDataSaving: false,
enablePinchToZoom: false,
experimentalVirtualScroller: true,
experimentalGitHubCards: true,

View File

@ -33,7 +33,12 @@ export function usePreferences<T extends keyof PreferencesSettings>(name: T): Re
}
export function getPreferences<T extends keyof PreferencesSettings>(userSettings: UserSettings, name: T): PreferencesSettings[T] {
return userSettings?.preferences?.[name] ?? DEFAULT__PREFERENCES_SETTINGS[name]
const preference = userSettings?.preferences?.[name] ?? DEFAULT__PREFERENCES_SETTINGS[name]
if (name === 'enableAutoplay')
return getPreferences(userSettings, 'enableDataSaving') ? false : preference
return preference
}
export function togglePreferences(key: keyof PreferencesSettings) {

View File

@ -43,7 +43,10 @@ export function setupPageHeader() {
titleTemplate = `${titleTemplate.slice(0, 60)}...${titleTemplate.endsWith('"') ? '"' : ''}`
}
titleTemplate += ` | ${t('app_name')}`
if (titleTemplate.length)
titleTemplate += ' | '
titleTemplate += t('app_name')
if (buildInfo.env !== 'release')
titleTemplate += ` (${buildInfo.env})`

View File

@ -50,4 +50,4 @@ nr test
- [Iconify](https://github.com/iconify/icon-sets#iconify-icon-sets-in-json-format) - Iconify icon sets in JSON format
- [Masto.js](https://neet.github.io/masto.js) - Mastodon API client in TypeScript
- [shiki](https://shiki.matsu.io/) - A beautiful Syntax Highlighter
- [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa) - Prompt for update and push notifications
- [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa) - Prompt for update, Web Push Notifications and Web Share Target API

131
docs/content/80.pwa.md Normal file
View File

@ -0,0 +1,131 @@
# 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.
The main goal of the PWA is to provide a native app experience on mobile devices with Web Push Notifications and Web Share Target API capabilities.
Web Share Target will allow you to share content from other apps to Elk in mobile browsers.
## Mobile Support
Some mobile browsers will allow you to install the PWA as a native app, and some others will only allow you to add the PWA to your home screen.
If you're using a mobile browser that supports the PWA installation, you will see a banner at the bottom of the screen with the option to install the PWA.
If the browser also supports [beforeinstallprompt](https://web.dev/customize-install/) event, ElK will show a prompt (on the right aside on wide screens, or at top on small screens) to allow you to install the PWA directly.
If the browser doesn't support the PWA installation, check following entries:
### Safari iOS
Right now, you cannot use Web Push Notifications on Safari mobile browsers, since it doesn't support the Web Push API yet at operating system level.
You can check the status of the Push API on Safari mobile browsers on [this page](https://caniuse.com/notifications).
Visit also [this site](https://firt.dev/notes/pwa-ios/) to check the status of PWA Capabilities on Safari mobile browsers.
To install a Progressive Web App (PWA) on Safari, follow these steps:
- Tap the "Share" icon at the bottom of the screen.
- Scroll down and tap "Add to Home Screen".
- Customize the name of the app (if desired) and tap "Add".
- The PWA should now appear on your home screen.
### Firefox
To install a Progressive Web App (PWA) on Firefox, follow these steps:
- Look for the install button or icon, usually located in the address bar or in a prompt that appears on the screen.
- Click on the install button or icon and follow the prompts to complete the installation.
- If you don't see an install button or icon, you can look for the PWA in the Firefox menu. Click on the three horizontal lines in the top/bottom right corner of the browser window to open the menu, then click on "Install" option.
### Chromium based browsers
To install a Progressive Web App (PWA) on Chromium based browsers, such as Google Chrome, Microsoft Edge, or Brave, follow these steps:
- Click on the menu button (three vertical dots) at the top right corner of the screen.
- Select "Install Elk as app" or "Install Elk to Home screen".
- Customize the name of the app (if desired) and click "Install" or "Add".
- The PWA should now appear on your device's home screen or in your app drawer.
## PWA Configuration in Elk project
By default, Elk will enable the PWA integration, but can be disabled by setting `VITE_DEV_PWA` to `false` in your `.env` file.
You can find the configuration options for the PWA in the [config/pwa.ts](https://github.com/elk-zone/elk/blob/main/config/pwa.ts) module.
### PWA Web Manifest
Right now, there is no support for web manifest internationalization and theme color in any browser, we're using a custom module to generate web manifests for theme color and language variants.
Elk will generate 2 web manifests per locale, one for light theme and one for dark theme.
You can check web manifest generation on [modules/pwa/i18n.ts](https://github.com/elk-zone/elk/blob/main/modules/pwa/i18n.ts) module.
### PWA UI Components
Elk will provide a set of UI components to allow you to customize the PWA installation prompt on browsers with [beforeinstallprompt](https://web.dev/customize-install/) support.
You can find the PWA installation prompt in the [PwaInstallPrompt](https://github.com/elk-zone/elk/blob/main/components/pwa/PwaInstallPrompt.client.vue) component: will be shown on the right aside on wide screens, or at top on small screens.
Elk is using prompt for update strategy, so the PWA prompt for update will be shown only if there is a new version of Elk available.
On small screens, the PWA prompt for update will be shown as a button on the head, you can find it in [PwaBadge](https://github.com/elk-zone/elk/blob/main/components/pwa/PwaBadge.client.vue) component.
On wide screens, the PWA prompt for update will be shown as a prompt on the right aside, you can find it in [PwaPrompt](https://github.com/elk-zone/elk/blob/main/components/pwa/PwaPrompt.client.vue) component.
PWA prompt for update will take preference over PWA installation prompt, so if there is a new version of Elk available, the PWA prompt for update will be shown instead of the PWA installation prompt.
All previous UI components don't have any logic, all them will use the logic provided by the [PWA plugin](https://github.com/elk-zone/elk/blob/main/plugins/pwa.client.ts).
### Service Worker
You can find Elk custom service worker in [service-worker folder](https://github.com/elk-zone/elk/blob/main/service-worker), it provides:
- [Prompt for update strategy](https://github.com/elk-zone/elk/blob/main/service-worker/sw.ts#L14).
- [Web Push Notifications](https://github.com/elk-zone/elk/blob/main/service-worker/web-push-notifications.ts): [push notifications](https://github.com/elk-zone/elk/blob/main/service-worker/sw.ts#L105) and [push notification click](https://github.com/elk-zone/elk/blob/main/service-worker/sw.ts#L106).
- [Web Share Target API](https://github.com/elk-zone/elk/blob/main/service-worker/share-target.ts): [share target registration](https://github.com/elk-zone/elk/blob/main/service-worker/sw.ts#L107).
### Push Notifications Subscription Logic
Elk will allow you to send push notifications to your users, you can find the logic for Web Push Notifications subscription [composables/push-notifications folder](https://github.com/elk-zone/elk/blob/main/composables/push-notifications).
There is a limitation on browsers about registering multiple Web Push Notifications: you can only subscribe to one push service per browser per application: trying to register multiple push subscriptions will be treated as spam by the browser.
If you try to register multiple push subscriptions, the browser will throw an error, and you will need to unregister the previous push subscription before registering a new one.
### Debugging PWA in development
To debug the PWA in development, you will need to run `dev:pwa` or `dev:mocked:pwa` script.
Running one of previous scripts will start a development server with the PWA enabled: you can review the web manifests and debug the service worker in your browser, use any Chromium based browser, since we're registering the service worker using `type: 'module'`.
You can debug Web Push Notifications in desktop and mobile browser and Web Share Target in your mobile device, using the same URL as the development server.
Right now, we can only debug on Android Chrome mobile browsers:
- Web Push Notifications: you don't need to install de PWA, just enable the notifications in the browser on notifications page or web push notifications settings page.
- Web Share Target: you need to install the PWA, and then you can share content from other apps to Elk.
### Debugging PWA in mobile browsers
To debug the PWA service worker in your mobile browser, you will need to:
1) Enable development options in your Android device:
- Go to "Settings" on your device.
- Scroll down to "About phone" and tap it.
- Locate the "Build number" and tap it repeatedly (usually 7 times) until you see a message saying that you have enabled developer options.
- Go back to "Settings" and you should now see "Developer options" listed.
- Tap on "Developer options" and you can enable various settings such as USB debugging, mock locations, and more.
2) Connect your Android device to your computer using a USB cable.
3) Enable USB debugging in your Android device:
- Go to "Settings" on your device.
- Scroll down to "Developer options" and tap it.
- Enable "USB debugging".
- Confirm the prompt on your device to allow USB debugging.
- Open Chrome/Edge browser in your device.
4) Open Chrome on your computer and go to `chrome://inspect/#devices`.
- Elk application should be listed in the "Remote Target" section after a few seconds (navigate to any page).
- Enter `http://localhost:5314` in the open in a new tab input and click Open button.
- Click on the "Inspect" button to open the DevTools.
5) Remember to remove the service worker from your device browser using dev tools once you finish testing the service worker:
- Go to `Application > Storage`, you should check following checkboxes:
- Application: `[x]` Unregister service worker
- Storage: `[x]` Local and session storage `[x]` IndexedDB
- Cache: `[x]` Cache storage and `[x]` Application cache
- Click on Clear site data button
- Go to `Application > Service Workers` and check the current service worker is missing or has the status deleted or reduntant.

View File

@ -402,6 +402,8 @@
"notifications_settings": "Notifications",
"preferences": {
"enable_autoplay": "Enable Autoplay",
"enable_data_saving": "Enable data saving",
"enable_data_saving_description": "Save data by preventing attachments from automatically loading.",
"enable_pinch_to_zoom": "Enable pinch to zoom",
"github_cards": "GitHub Cards",
"grayscale_mode": "Grayscale mode",
@ -428,6 +430,8 @@
"label": "Appearance",
"profile_metadata": "Profile metadata",
"profile_metadata_desc": "You can have up to {0} items displayed as a table on your profile",
"profile_metadata_label": "Label",
"profile_metadata_value": "Content",
"title": "Edit profile"
},
"featured_tags": {

View File

@ -19,7 +19,7 @@
"follow_requested": "Solicitado",
"followers": "Seguidoras",
"followers_count": "{0} Seguidoras|{0} Seguidora|{0} Seguidoras",
"following": "Seguimentos",
"following": "Seguindo",
"following_count": "Seguindo a {0}",
"follows_you": "Séguete",
"go_to_profile": "Ir ao perfil",
@ -33,8 +33,9 @@
"pinned": "Fixada",
"posts": "Publicacións",
"posts_count": "{0} Publicacións|{0} Publicación|{0} Publicacións",
"profile_description": "Cabeceira do perfil de{0}",
"profile_description": "Cabeceira do perfil de {0}",
"profile_unavailable": "Perfil non dispoñible",
"request_follow": "Solicitar seguimento",
"unblock": "Desbloquear",
"unfollow": "Retirar seguimento",
"unmute": "Reactivar",
@ -68,6 +69,7 @@
"save": "Gardar",
"save_changes": "Gardar cambios",
"sign_in": "Acceder",
"sign_in_to": "Iniciar sesión en {0}",
"switch_account": "Cambiar de conta",
"vote": "Votar"
},
@ -116,6 +118,11 @@
"cancel": "Non",
"confirm": "Si"
},
"delete_list": {
"cancel": "Cancelar",
"confirm": "Eliminar",
"title": "Tes a certeza de querer eliminar a lista \"{0}\"?"
},
"delete_posts": {
"cancel": "Cancelar",
"confirm": "Eliminar",
@ -169,11 +176,28 @@
"desc_para4": "Elk é Código Aberto. Se queres axudar probándoo, aportando a túa opinión, ou colaborando,",
"desc_para5": "contacta con nós en GitHub",
"desc_para6": "e involúcrate.",
"footer_team": "O Equipo Elk",
"title": "Elk está de pre-estrea!"
},
"language": {
"search": "Buscar"
},
"list": {
"add_account": "Engadir conta á lista",
"cancel_edit": "Cancelar a edición",
"clear_error": "Limpar erro",
"create": "Crear",
"delete": "Elimina esta lista",
"delete_error": "Algo fallou ao querer eliminar a lista",
"edit": "Edita esta lista",
"edit_error": "Algo fallou ao querer actualizar a lista",
"error": "Houbo un fallo ao crear a lista",
"error_prefix": "Erro: ",
"list_title_placeholder": "Título da lista",
"modify_account": "Modificar listas coa conta",
"remove_account": "Retirar conta da lista",
"save": "Gardar cambios"
},
"menu": {
"block_account": "Bloquear {0}",
"block_domain": "Bloquear o dominio {0}",
@ -209,16 +233,19 @@
"blocked_domains": "Dominios bloqueados",
"blocked_users": "Usuarias bloqueadas",
"bookmarks": "Marcadores",
"built_at": "Versión {0}",
"built_at": "Publicada en {0}",
"compose": "Redactar",
"conversations": "Conversas",
"explore": "Explorar",
"favourites": "Favoritas",
"federated": "Federada",
"home": "Inicio",
"list": "Lista",
"lists": "Listas",
"local": "Local",
"muted_users": "Usuarias acaladas",
"notifications": "Notificacións",
"privacy": "Privacidade",
"profile": "Perfil",
"search": "Buscar",
"select_feature_flags": "Toggle Feature Flags",
@ -243,11 +270,13 @@
"content_warning": "Escribe aquí o aviso",
"default_1": "En que estás a pensar?",
"reply_to_account": "Responder a {0}",
"replying": "Respondendo",
"replying": "Responde aquí",
"the_thread": "a conversa"
},
"pwa": {
"dismiss": "Desbotar",
"install": "Instalar",
"install_title": "Instalar Elk",
"title": "Dispoñible nova versión de Elk!",
"update": "Actualizar",
"update_available_short": "Actualizar Elk",
@ -280,6 +309,7 @@
},
"settings": {
"about": {
"built_at": "Data versión",
"label": "Acerca de",
"meet_the_team": "Coñece ao equipo",
"sponsor_action": "Patrocínanos",
@ -306,7 +336,15 @@
},
"language": {
"display_language": "Idioma da interface",
"label": "Idioma"
"label": "Idioma",
"status": "Estado da tradución: {0}/{1} ({2}%)",
"translations": {
"add": "Engadir",
"choose_language": "Elixe idioma",
"heading": "Traducións",
"hide_specific": "Agochar determinadas traducións",
"remove": "Retirar"
}
},
"notifications": {
"label": "Notificacións",
@ -335,10 +373,13 @@
"save_settings": "Gardar axustes",
"subscription_error": {
"clear_error": "Limpar erro",
"invalid_vapid_key": "A chave pública VAPID non semella válida.",
"permission_denied": "Permiso non concedido: activa as notificacións no navegador.",
"repo_link": "Repositorio de Elk en GitHub",
"request_error": "Algo fallou ao solicitar a subscrición, inténtao outra vez e se o erro continúa, informa do problema no repositorio de Elk.",
"title": "Non se puido activar a subscrición a notificacións push",
"too_many_registrations": "Debido a limitacións do navegador, Elk non pode usar o servizo de notificacións push para múltiples contas en diferentes servidores. Podes subscribirte ás notificacións push con outra conta e intentalo outra vez."
"too_many_registrations": "Debido a limitacións do navegador, Elk non pode usar o servizo de notificacións push para múltiples contas en diferentes servidores. Podes subscribirte ás notificacións push con outra conta e intentalo outra vez.",
"vapid_not_supported": "O teu navegador ten soporte para Notificacións Web Push, pero non semella ter incluído o protocolo VAPID."
},
"title": "Axustes das Notificacións Push",
"undo_settings": "Desfacer cambios",
@ -355,22 +396,29 @@
"re_auth": "Semella que o teu servidor non ten soporte para notificacións Push. Inténtao pechando a sesión e volvendo a acceder, se esta mensaxe continúa aparecendo contacta coa administración do teu servidor."
}
},
"show_btn": "Ir aos axustes de notificacións"
"show_btn": "Ir aos axustes de notificacións",
"under_construction": "En desenvolvemento"
},
"notifications_settings": "Notificacións",
"preferences": {
"enable_autoplay": "Activa reprodución auto.",
"enable_autoplay": "Activar reprodución auto.",
"enable_pinch_to_zoom": "Activar belisco para aumentar",
"github_cards": "GitHub Cards",
"grayscale_mode": "Modo en escala de grises",
"hide_account_hover_card": "Non mostrar tarxetas emerxentes",
"hide_alt_indi_on_posts": "Agochar indicador ALT nas publicacións",
"hide_boost_count": "Agochar conta de promocións",
"hide_favorite_count": "Agochar conta de favoritos",
"hide_follower_count": "Agochar conta de seguimentos",
"hide_reply_count": "Agochar conta de respostas",
"hide_translation": "Agochar tradución",
"hide_username_emojis": "Agochar emojis nos nomes",
"hide_username_emojis_description": "Agocha nas cronoloxías os emojis nos nomes de usuaria. Emojis seguirán visibles na páxina de perfil da usuaria.",
"label": "Preferencias",
"title": "Características experimentais",
"user_picker": "Selector de usuarias",
"virtual_scroll": "Desprazamento virtual"
"user_picker": "Selector de conta",
"virtual_scroll": "Desprazamento virtual",
"wellbeing": "Benestar"
},
"profile": {
"appearance": {
@ -419,8 +467,10 @@
"filter_removed_phrase": "Eliminada polo filtro",
"filter_show_anyway": "Mostrar igualmente",
"img_alt": {
"ALT": "Texto Alt",
"desc": "Descrición",
"dismiss": "Desbotar"
"dismiss": "Desbotar",
"read": "Ler a descrición de {0}"
},
"poll": {
"count": "{0} votos|{0} voto|{0} votos",
@ -441,8 +491,10 @@
"edited": "editada o {0}"
},
"tab": {
"accounts": "Contas",
"for_you": "Para ti",
"hashtags": "Cancelos",
"list": "Lista",
"media": "Multimedia",
"news": "Novas",
"notifications_all": "Todo",
@ -506,8 +558,11 @@
"explore_links_intro": "Estes son os temas sobre os que están a conversar agora mesmo as persoas deste servidor e as dos outros servidores da rede descentralizada.",
"explore_posts_intro": "Estas publicacións deste e outros servidores da rede descentralizada están aumentando a súa popularidade.",
"explore_tags_intro": "Está aumentando a popularidade destes cancelos entre as persoas deste e outros servidores da rede descentralizada.",
"open_editor_tools": "Ferramentas de edición",
"publish_failed": "Close failed messages at the top of editor to republish posts",
"toggle_code_block": "Activar bloque de código"
"toggle_bold": "Activar grosa",
"toggle_code_block": "Activar bloque de código",
"toggle_italic": "Activar cursiva"
},
"user": {
"add_existing": "Engadir unha conta existente.",
@ -515,6 +570,7 @@
"sign_in_desc": "Inicia sesión para seguir perfís e cancelos, favorecer, compartir ou responder a mensaxes, ou interactuar coa túa conta noutro servidor.",
"sign_in_notice_title": "Vendo {0} datos públicos",
"sign_out_account": "Pechar sesión {0}",
"single_instance_sign_in_desc": "Inicia sesión para seguir perfís e cancelos, favorecer, compartir ou responder a mensaxes.",
"tip_no_account": "Se aínda non tes unha conta Mastodon, {0}.",
"tip_register_account": "elixe un servidor para crear unha"
},

View File

@ -16,7 +16,7 @@
"favourites": "Ulubione",
"follow": "Obserwuj",
"follow_back": "Przestań obserwować",
"follow_requested": "Wniosek",
"follow_requested": "Prośba",
"followers": "Obserwujący",
"followers_count": "{0} Obserwujących|{0} Obserwujący|{0} Obserwujących|{0} Obserwujących",
"following": "Obserwujesz",
@ -35,6 +35,7 @@
"posts_count": "{0} Wpisów|{0} Wpis|{0} Wpisy|{0} Wpisów",
"profile_description": "nagłówek profilu {0}",
"profile_unavailable": "Profil niedostępny",
"request_follow": "Prośba o śledzenie",
"unblock": "Odblokuj",
"unfollow": "Przestań obserwować",
"unmute": "Wyłącz wyciszenie",
@ -68,6 +69,7 @@
"save": "Zapisz",
"save_changes": "Zapisz zmiany",
"sign_in": "Zaloguj się",
"sign_in_to": "Zaloguj się do {0}",
"switch_account": "Przełącz konto",
"vote": "Zagłosuj"
},
@ -116,6 +118,11 @@
"cancel": "Nie",
"confirm": "Tak"
},
"delete_list": {
"cancel": "Anuluj",
"confirm": "Usuń",
"title": "Czy na pewno chcesz usunąć listę „{0}”?"
},
"delete_posts": {
"cancel": "Anuluj",
"confirm": "Usuń",
@ -140,6 +147,13 @@
"conversation": {
"with": " "
},
"custom_cards": {
"stackblitz": {
"lines": "Lines {0}",
"open": "Otwórz",
"snippet_from": "Snippet from {0}"
}
},
"error": {
"account_not_found": "Nie znaleziono konta {0}",
"explore-list-empty": "Nic nie jest w tej chwili popularne. Sprawdź później!",
@ -162,6 +176,7 @@
"desc_para4": "Elk jest Open Source. Jeśli chcesz pomóc w testowaniu, przekazać opinię lub wnieść swój wkład,",
"desc_para5": "skontaktuj się z nami na GitHub",
"desc_para6": "i zaangażuj się.",
"footer_team": "Zespół Elk",
"title": "Elk w wersji Preview!"
},
"language": {
@ -169,8 +184,19 @@
},
"list": {
"add_account": "Dodaj konto do listy",
"cancel_edit": "Anuluj edycję",
"clear_error": "Usuń błąd",
"create": "Utwórz",
"delete": "Usuń listę",
"delete_error": "Podczas usuwania listy wystąpił błąd",
"edit": "Edytuj listę",
"edit_error": "Podczas aktualizowania listy wystąpił błąd",
"error": "Podczas tworzenia listy wystąpił błąd",
"error_prefix": "Błąd:",
"list_title_placeholder": "Tytuł listy",
"modify_account": "Modyfikacja listy",
"remove_account": "Usuń konto z listy"
"remove_account": "Usuń konto z listy",
"save": "Zapisz zmiany"
},
"menu": {
"block_account": "Zablokuj {0}",
@ -283,6 +309,7 @@
},
"settings": {
"about": {
"built_at": "Kompilacja",
"label": "Informacje",
"meet_the_team": "Poznaj zespół",
"sponsor_action": "Wspomóż nas",
@ -310,6 +337,7 @@
"language": {
"display_language": "Język aplikacji",
"label": "Język",
"status": "Stan tłumaczenia: {0}/{1} ({2}%)",
"translations": {
"add": "Dodaj",
"choose_language": "Wybierz język",
@ -345,10 +373,13 @@
"save_settings": "Zapisz ustawienia",
"subscription_error": {
"clear_error": "Usuń błąd",
"invalid_vapid_key": "Wydaje się, że klucz publiczny VAPID jest nieprawidłowy.",
"permission_denied": "Brak uprawnień: włącz powiadomienia w przeglądarce.",
"repo_link": "Repozytorium Elk w Github",
"request_error": "Wystąpił błąd podczas żądania subskrypcji, spróbuj ponownie, a jeśli błąd będzie się powtarzał, zgłoś problem do repozytorium Elk.",
"title": "Nie można zasubskrybować powiadomień push",
"too_many_registrations": "Ze względu na ograniczenia przeglądarki Elk nie może korzystać z usługi powiadomień push dla wielu kont na różnych serwerach. Powinieneś anulować subskrypcję powiadomień push na innym koncie i spróbować ponownie."
"too_many_registrations": "Ze względu na ograniczenia przeglądarki Elk nie może korzystać z usługi powiadomień push dla wielu kont na różnych serwerach. Powinieneś anulować subskrypcję powiadomień push na innym koncie i spróbować ponownie.",
"vapid_not_supported": "Twoja przeglądarka obsługuje powiadomienia Web Push, ale wydaje się, że nie implementuje protokołu VAPID."
},
"title": "Ustawienia powiadomień push",
"undo_settings": "Cofnij zmiany",
@ -365,23 +396,29 @@
"re_auth": "Wygląda na to, że Twój serwer nie obsługuje powiadomień push. Spróbuj wylogować się i zalogować ponownie, jeśli ten komunikat nadal się pojawia, skontaktuj się z administratorem serwera."
}
},
"show_btn": "Przejdź do ustawień powiadomień"
"show_btn": "Przejdź do ustawień powiadomień",
"under_construction": "W budowie"
},
"notifications_settings": "Powiadomienia",
"preferences": {
"enable_autoplay": "Włącz autoodtwarzanie",
"enable_pinch_to_zoom": "Włącz powiększanie za pomocą gestów",
"github_cards": "GitHub Cards",
"grayscale_mode": "Tryb skali szarości",
"grayscale_mode": "Wpisy w odcieniach szarości",
"hide_account_hover_card": "Ukryj wizytówkę konta",
"hide_alt_indi_on_posts": "Ukryj wskaźnik ALT przy wpisach",
"hide_boost_count": "Ukryj liczbę podbić",
"hide_favorite_count": "Ukryj liczbę polubień",
"hide_follower_count": "Ukryj liczbę obserwujących",
"hide_reply_count": "Ukryj liczbę odpowiedzi",
"hide_translation": "Ukryj funkcję tłumaczenia",
"hide_username_emojis": "Ukryj emotikony w nazwie użytkownika",
"hide_username_emojis_description": "Ukrywa emotikony przed nazwami użytkowników na osi czasu. Emotikony będą nadal widoczne w ich profilach.",
"label": "Preferencje",
"title": "Funkcje eksperymentalne",
"user_picker": "User Picker",
"virtual_scroll": "Virtual Scrolling"
"virtual_scroll": "Virtual Scrolling",
"wellbeing": "Dla dobrego samopoczucia"
},
"profile": {
"appearance": {
@ -391,6 +428,8 @@
"label": "Wygląd",
"profile_metadata": "Metadane profilu",
"profile_metadata_desc": "Możesz mieć maksymalnie {0} elementy wyświetlane jako tabela w swoim profilu",
"profile_metadata_label": "Nazwa",
"profile_metadata_value": "Zawartość",
"title": "Edytuj profil"
},
"featured_tags": {
@ -430,8 +469,10 @@
"filter_removed_phrase": "Usunięto przez filtr",
"filter_show_anyway": "Pokaż mimo wszystko",
"img_alt": {
"ALT": "ALT",
"desc": "Opis",
"dismiss": "Zamknij"
"dismiss": "Zamknij",
"read": "Przeczytaj opis {0}"
},
"poll": {
"count": "{0} głosów|{0} głos|{0} głosy|{0} głosów",
@ -519,8 +560,11 @@
"explore_links_intro": "Te wiadomości obecnie są komentowane przez osoby z tego serwera i pozostałych w zdecentralizowanej sieci.",
"explore_posts_intro": "Te wpisy z tego i innych serwerów w zdecentralizowanej sieci zyskują teraz popularność na tym serwerze.",
"explore_tags_intro": "Te hasztagi zyskują obecnie na popularności wśród osób na tym i innych serwerach zdecentralizowanej sieci.",
"open_editor_tools": "Narzędzia edycji",
"publish_failed": "Zamknij komunikaty o błędzie u góry edytora, aby ponownie opublikować wpisy",
"toggle_code_block": "Przełączenie do trybu kodowania"
"toggle_bold": "Zmień na pogrubienie",
"toggle_code_block": "Przełączenie do trybu kodowania",
"toggle_italic": "Zmień na kursywę"
},
"user": {
"add_existing": "Dodaj istniejące konto",
@ -528,6 +572,7 @@
"sign_in_desc": "Zaloguj się, aby obserwować profile lub hasztagi, dodawać do ulubionych, udostępniać i odpowiadać na wpisy lub wchodzić w interakcje ze swojego konta na innym serwerze.",
"sign_in_notice_title": "Dane publiczne {0}",
"sign_out_account": "Wyloguj {0}",
"single_instance_sign_in_desc": "Zaloguj się, aby obserwować profile lub hashtagi, dodawać do ulubionych, udostępniać i odpowiadać na posty.",
"tip_no_account": "Jeśli nie masz jeszcze konta Mastodon, {0}.",
"tip_register_account": "wybierz swój serwer i zarejestruj się"
},

View File

@ -35,6 +35,7 @@
"posts_count": "{0} Publicações|{0} Publicação|{0} Publicações",
"profile_description": "Descrição de perfil de {0}",
"profile_unavailable": "Perfil indisponível",
"request_follow": "Pedir para seguir",
"unblock": "Desbloquear",
"unfollow": "Deixar de seguir",
"unmute": "Deixar de silenciar",
@ -68,6 +69,7 @@
"save": "Guardar",
"save_changes": "Guardar alterações",
"sign_in": "Entrar",
"sign_in_to": "Entrar em {0}",
"switch_account": "Mudar de conta",
"vote": "Votar"
},
@ -335,6 +337,7 @@
"language": {
"display_language": "Idioma de Apresentação",
"label": "Idioma",
"status": "Estado da tradução: {0}/{1} ({2}%)",
"translations": {
"add": "Adicionar",
"choose_language": "Selecionar idioma",
@ -409,7 +412,8 @@
"hide_follower_count": "Esconder contagem de seguidores",
"hide_reply_count": "Esconder contagem de respostas",
"hide_translation": "Esconder botão de tradução",
"hide_username_emojis": "Esconder emojis no nome de utilizador",
"hide_username_emojis": "Esconder emojis do nome de utilizador",
"hide_username_emojis_description": "Esconde os emojis do nome de utilizador nas cronologias. Os Emojis continuarão a ser visíveis nos seus perfis.",
"label": "Preferências",
"title": "Funcionalidades Experimentais",
"user_picker": "Selecionador de Utilizador",
@ -424,6 +428,8 @@
"label": "Aspeto",
"profile_metadata": "Metadados de perfil",
"profile_metadata_desc": "Pode ter até {0} itens expostos, em forma de tabela, no seu perfil",
"profile_metadata_label": "Rótulo",
"profile_metadata_value": "Conteúdo",
"title": "Editar perfil"
},
"featured_tags": {
@ -554,15 +560,19 @@
"explore_links_intro": "Estas notícias estão, neste momento, a ser faladas por pessoas neste e noutros servidores da rede descentralizada.",
"explore_posts_intro": "Estas publicações deste e de outros servidores na rede descentralizada estão, neste momento, a ganhar popularidade neste servidor.",
"explore_tags_intro": "Estes hashtags estão, neste momento, a ganhar popularidade entre as pessoas neste e noutros servidores da rede descentralizada.",
"open_editor_tools": "Ferramentas de edição",
"publish_failed": "Fechar mensagens de falha no topo do editor para republicar publicações",
"toggle_code_block": "Alternar bloco de código"
"toggle_bold": "Alternar negrito",
"toggle_code_block": "Alternar bloco de código",
"toggle_italic": "Alternar itálico"
},
"user": {
"add_existing": "Adicionar uma conta existente",
"server_address_label": "Endereço do Servidor Mastodon",
"sign_in_desc": "Entre, para seguir pessoas ou hashtags, adicionar aos favoritos, partilhar e responder a publicações, ou interagir a partir da sua conta de outro servidor.",
"sign_in_desc": "Inicie sessão, para seguir pessoas ou hashtags, adicionar aos favoritos, partilhar e responder a publicações, ou interagir a partir da sua conta de outro servidor.",
"sign_in_notice_title": "A visualizar os dados públicos de {0}",
"sign_out_account": "Desconectar {0}",
"single_instance_sign_in_desc": "Inicie sessão, para seguir pessoas ou hashtags, adicionar aos favoritos, partilhar e responder a publicações.",
"tip_no_account": "Se ainda não tem uma conta Mastodon, {0}.",
"tip_register_account": "escolha um servidor e inscreva-se"
},

View File

@ -28,12 +28,15 @@
"muted_users": "已靜音的使用者",
"muting": "已靜音",
"mutuals": "互相關注",
"notifications_on_post_disable": "當 {username} 發布時,停止通知我",
"notifications_on_post_enable": "當 {username} 發布時,通知我",
"notify_on_post": "{username} 發文時通知我",
"pinned": "置頂的貼文",
"posts": "貼文",
"posts_count": "{0} 則貼文",
"profile_description": "{0} 的個人資料封面",
"profile_unavailable": "個人資料不可見",
"request_follow": "要求追縱",
"unblock": "取消封鎖",
"unfollow": "取消追蹤",
"unmute": "取消靜音",
@ -47,6 +50,7 @@
"boost": "轉發",
"boost_count": "{0}",
"boosted": "已轉發",
"clear_publish_failed": "清除發布失敗的訊息",
"clear_upload_failed": "清除上傳失敗",
"close": "關閉",
"compose": "撰寫",
@ -59,6 +63,7 @@
"more": "更多",
"next": "下一個",
"prev": "上一個",
"previous": "之前的",
"publish": "發布",
"reply": "回覆",
"reply_count": "{0}",
@ -89,6 +94,11 @@
"toggle_zen_mode": "切換禪模式"
},
"common": {
"confirm_dialog": {
"cancel": "取消",
"confirm": "確認",
"title": "你確定嗎?"
},
"end_of_list": "清單到底了",
"error": "錯誤",
"in": "在",
@ -110,9 +120,10 @@
"confirm": "封鎖",
"title": "你確定要封鎖 {0} 域名嗎?"
},
"common": {
"cancel": "否",
"confirm": "是"
"delete_list": {
"cancel": "取消",
"confirm": "確認",
"title": "你確定要刪除 \"{0}\" 列表嗎?"
},
"delete_posts": {
"cancel": "取消",
@ -122,7 +133,7 @@
"mute_account": {
"cancel": "取消",
"confirm": "靜音",
"title": "你確定要靜音 {0}嗎?"
"title": "你確定要靜音 {0} 嗎?"
},
"show_reblogs": {
"cancel": "取消",
@ -138,15 +149,28 @@
"conversation": {
"with": "與"
},
"custom_cards": {
"stackblitz": {
"lines": "第 {0} 行",
"open": "開啟",
"snippet_from": "取自 {0} 的片段"
}
},
"error": {
"account_not_found": "未找到使用者 {0}",
"explore-list-empty": "目前沒有熱門話題,稍後再來看看吧!",
"file_size_cannot_exceed_n_mb": "檔案大小不能超過 {0}MB",
"file_size_cannot_exceed_n_mb": "檔案大小不能超過 {0} MB",
"sign_in_error": "無法連接伺服器",
"status_not_found": "未找到貼文",
"unsupported_file_format": "不支援的檔案格式"
},
"help": {
"build_preview": {
"desc1": "您當前正在查看來自開源社區、預覽版的鹿鳴 - {0}。",
"desc2": "可能包含末經審查或是惡意修改的內容。",
"desc3": "請不要使用真實的帳號登入。",
"title": "預覽部署"
},
"desc_highlight": "可能會在某些地方出現一些 bug 或缺少的功能。",
"desc_para1": "感謝你有興趣嘗試鹿鳴,一個我們正在積極開發的通用 Mastodon 用戶端。",
"desc_para2": "我們正在努力開發中,並隨著時間的推移不斷完善。",
@ -154,15 +178,33 @@
"desc_para4": "鹿鳴是開源的,如果你願意幫助測試、提供回饋或作出貢獻,",
"desc_para5": "在 GitHub 上聯繫我們",
"desc_para6": "來參與其中。",
"footer_team": "鹿鳴開發團隊",
"title": "預覽鹿鳴!"
},
"language": {
"search": "搜尋"
},
"list": {
"add_account": "在列表中添加帳號",
"cancel_edit": "取消編輯",
"clear_error": "清除錯誤",
"create": "建立",
"delete": "刪除這個列表",
"delete_error": "刪除列表時發生錯誤",
"edit": "編輯這個列表",
"edit_error": "更新列表時發生錯誤",
"error": "建立列表時發生錯誤",
"error_prefix": "錯誤:",
"list_title_placeholder": "列表標題",
"modify_account": "修改列表中的帳號",
"remove_account": "移除列表中的帳號",
"save": "保存變更"
},
"menu": {
"block_account": "封鎖 {0}",
"block_domain": "封鎖的域名 {0}",
"copy_link_to_post": "複製這篇貼文的連結",
"copy_original_link_to_post": "複製貼文的原網址",
"delete": "刪除",
"delete_and_redraft": "刪除並重新編輯",
"direct_message_account": "私訊 {0}",
@ -193,16 +235,19 @@
"blocked_domains": "已封鎖的域名",
"blocked_users": "已封鎖的使用者",
"bookmarks": "書籤",
"built_at": "於 {0}更新",
"built_at": "於 {0} 更新",
"compose": "撰寫",
"conversations": "私訊",
"explore": "探索",
"favourites": "喜歡",
"federated": "聯邦",
"home": "首頁",
"list": "列表",
"lists": "列表",
"local": "本站",
"muted_users": "已靜音的使用者",
"notifications": "通知",
"privacy": "隱私權政策",
"profile": "個人資料",
"search": "搜尋",
"select_feature_flags": "功能開關",
@ -232,6 +277,8 @@
},
"pwa": {
"dismiss": "忽略",
"install": "安裝",
"install_title": "安裝鹿鳴",
"title": "鹿鳴存在新的更新",
"update": "更新",
"update_available_short": "更新鹿鳴",
@ -264,6 +311,7 @@
},
"settings": {
"about": {
"built_at": "構建於",
"label": "關於",
"meet_the_team": "認識我們",
"sponsor_action": "贊助我們",
@ -271,7 +319,8 @@
"sponsors": "贊助",
"sponsors_body_1": "鹿鳴的誕生得感謝以下的慷慨贊助與幫助:",
"sponsors_body_2": "以及所有贊助鹿鳴成員的公司和個人,以及隊員們。",
"sponsors_body_3": "如果你喜歡鹿鳴,可以考慮贊助我們:"
"sponsors_body_3": "如果你喜歡鹿鳴,可以考慮贊助我們:",
"version": "版本"
},
"account_settings": {
"description": "在 Mastodon UI 中編輯你的帳號設定",
@ -289,7 +338,14 @@
},
"language": {
"display_language": "顯示語言",
"label": "語言"
"label": "語言",
"translations": {
"add": "添加",
"choose_language": "選擇語言",
"heading": "翻譯",
"hide_specific": "隱藏特定的翻譯",
"remove": "刪除"
}
},
"notifications": {
"label": "通知",
@ -318,10 +374,13 @@
"save_settings": "儲存設定變更",
"subscription_error": {
"clear_error": "清除錯誤",
"invalid_vapid_key": "VAPID 公鑰似乎無效。",
"permission_denied": "權限不足:請在你的瀏覽器中打開通知權限。",
"repo_link": "鹿鳴的 GitHub 儲存庫",
"request_error": "請求訂閱時發生了一個錯誤,請再次嘗試。如錯誤仍然存在,請到鹿鳴儲存庫中報告這一問題。",
"title": "無法訂閱推播通知。",
"too_many_registrations": "由於瀏覽器限制,鹿鳴無法為不同伺服器上的多個帳號使用推播通知服務。 你應該取消訂閱其他帳號的推送通知,然後重試。"
"too_many_registrations": "由於瀏覽器限制,鹿鳴無法為不同伺服器上的多個帳號使用推播通知服務。 你應該取消訂閱其他帳號的推送通知,然後重試。",
"vapid_not_supported": "您的瀏覽器支持 Web Push Notifications但似乎沒有實現 VAPIO 的協議。"
},
"title": "推播通知設定",
"undo_settings": "撤銷設定變更",
@ -338,18 +397,28 @@
"re_auth": "您的伺服器似乎不支援推播通知。嘗試退出使用者並重新登入。如果此消息仍然出現,請聯繫您伺服器的管理員。"
}
},
"show_btn": "前往通知設定"
"show_btn": "前往通知設定",
"under_construction": "建立中"
},
"notifications_settings": "通知",
"preferences": {
"enable_autoplay": "啟用自動播放功能",
"enable_pinch_to_zoom": "啟用雙指縮放功能",
"github_cards": "GitHub 卡片",
"grayscale_mode": "深色模式",
"hide_account_hover_card": "隱藏帳號浮動卡片",
"hide_alt_indi_on_posts": "隱藏貼文上的 alt 指示",
"hide_boost_count": "隱藏轉發數",
"hide_favorite_count": "隱藏收藏數",
"hide_follower_count": "隱藏粉絲數",
"hide_reply_count": "隱藏回覆數",
"hide_translation": "隱藏翻譯",
"hide_username_emojis": "隱藏使用者名稱上的表情符號",
"label": "偏好設定",
"title": "實驗功能",
"user_picker": "使用者選擇器",
"virtual_scroll": "虛擬滾動"
"virtual_scroll": "虛擬滾動",
"wellbeing": "福利"
},
"profile": {
"appearance": {
@ -385,6 +454,7 @@
"edited": "(已編輯)",
"editing": "編輯中",
"loading": "載入中...",
"publish_failed": "發布失敗",
"publishing": "發布中",
"upload_failed": "上傳失敗",
"uploading": "上傳中..."
@ -394,10 +464,13 @@
"edited": "在 {0} 編輯了",
"favourited_by": "被喜歡",
"filter_hidden_phrase": "篩選依據",
"filter_removed_phrase": "從篩選中移除",
"filter_show_anyway": "仍然顯示",
"img_alt": {
"ALT": "ALT",
"desc": "描述",
"dismiss": "關閉"
"dismiss": "關閉",
"read": "閱讀 {0} 的說明"
},
"poll": {
"count": "{0} 次投票",
@ -418,8 +491,10 @@
"edited": "在 {0} 編輯了"
},
"tab": {
"accounts": "帳號",
"for_you": "推薦追蹤",
"hashtags": "話題標籤",
"list": "列表",
"media": "媒體",
"news": "最新消息",
"notifications_all": "全部",
@ -483,6 +558,7 @@
"explore_links_intro": "這些新聞故事正被本站和分散式網路上其他站點的使用者談論。",
"explore_posts_intro": "來自本站和分散式網路上其他站點的這些嘟文正在本站引起關注。",
"explore_tags_intro": "這些標籤正在本站和分散式網路上其他站點的使用者中引起關注。",
"publish_failed": "請關閉編輯器上方的失敗訊息以重新發布貼文。",
"toggle_code_block": "切換程式碼區塊"
},
"user": {

View File

@ -1,7 +1,9 @@
import { mkdir, writeFile } from 'node:fs/promises'
import { defineNuxtModule } from '@nuxt/kit'
import type { VitePluginPWAAPI } from 'vite-plugin-pwa'
import { VitePWA } from 'vite-plugin-pwa'
import type { Plugin } from 'vite'
import { join } from 'pathe'
import type { VitePWANuxtOptions } from './types'
import { configurePWAOptions } from './config'
import { type LocalizedWebManifest, createI18n, pwaLocales } from './i18n'
@ -26,6 +28,13 @@ export default defineNuxtModule<VitePWANuxtOptions>({
nuxt.options.appConfig = nuxt.options.appConfig || {}
nuxt.options.appConfig.pwaEnabled = !options.disable
nuxt.options.nitro.publicAssets = nuxt.options.nitro.publicAssets || []
const manifestDir = join(nuxt.options.buildDir, 'manifests')
nuxt.options.nitro.publicAssets.push({
dir: manifestDir,
baseURL: '/',
maxAge: 0,
})
// TODO: combine with configurePWAOptions?
nuxt.hook('nitro:init', (nitro) => {
options.outDir = nitro.options.output.publicDir
@ -50,24 +59,19 @@ export default defineNuxtModule<VitePWANuxtOptions>({
throw new Error(`No webmanifest found for locale/theme ${entry}`)
return JSON.stringify(manifest)
}
viteInlineConfig.plugins.push({
name: 'elk:pwa:locales:build',
apply: 'build',
generateBundle(_, bundle) {
if (options.disable || !bundle)
return
Object.keys(webmanifests!).map(wm => [wm, `manifest-${wm}.webmanifest`]).forEach(([wm, fileName]) => {
bundle[fileName] = {
needsCodeReference: false,
type: 'asset',
name: undefined,
source: generateManifest(wm),
fileName,
}
})
},
})
if (isClient) {
viteInlineConfig.plugins.push({
name: 'elk:pwa:locales:build',
apply: 'build',
async writeBundle(_options, bundle) {
if (options.disable || !bundle)
return
await mkdir(manifestDir, { recursive: true })
for (const wm in webmanifests)
await writeFile(join(manifestDir, `manifest-${wm}.webmanifest`), generateManifest(wm))
},
})
}
viteInlineConfig.plugins.push({
name: 'elk:pwa:locales:dev',
apply: 'serve',

View File

@ -1,7 +1,7 @@
{
"name": "@elk-zone/elk",
"type": "module",
"version": "0.7.3",
"version": "0.7.4",
"packageManager": "pnpm@7.9.0",
"license": "MIT",
"homepage": "https://elk.zone/",

View File

@ -27,6 +27,7 @@ onReactivated(() => {
timeline-title-style
:content="account ? getDisplayName(account) : t('nav.profile')"
:show-emojis="!getPreferences(userSettings, 'hideUsernameEmojis')"
:markdown="false"
/>
</template>

View File

@ -33,10 +33,20 @@ const userSettings = useUserSettings()
</SettingsToggleItem>
<SettingsToggleItem
:checked="getPreferences(userSettings, 'enableAutoplay')"
:disabled="getPreferences(userSettings, 'enableDataSaving')"
@click="togglePreferences('enableAutoplay')"
>
{{ $t('settings.preferences.enable_autoplay') }}
</SettingsToggleItem>
<SettingsToggleItem
:checked="getPreferences(userSettings, 'enableDataSaving')"
@click="togglePreferences('enableDataSaving')"
>
{{ $t("settings.preferences.enable_data_saving") }}
<template #description>
{{ $t("settings.preferences.enable_data_saving_description") }}
</template>
</SettingsToggleItem>
<SettingsToggleItem
:checked="getPreferences(userSettings, 'enablePinchToZoom')"
@click="togglePreferences('enablePinchToZoom')"

View File

@ -1,6 +1,6 @@
import { sendRedirect } from 'h3'
const BOT_RE = /bot\b|index|spider|facebookexternalhit|crawl|wget|slurp|mediapartners-google/i
const BOT_RE = /bot\b|index|spider|facebookexternalhit|crawl|wget|slurp|mediapartners-google|whatsapp/i
export default defineNuxtPlugin(async (nuxtApp) => {
const route = useRoute()

View File

@ -2,10 +2,16 @@ import flatten from 'flat'
import { createResolver } from '@nuxt/kit'
import fs from 'fs-extra'
import { currentLocales } from '../config/i18n'
import vsCodeConfig from '../.vscode/settings.json'
import type { LocaleEntry } from '../docs/types'
import type { ElkTranslationStatus } from '~/types/translation-status'
const vsCodeConfig = JSON.parse(
await fs.readFile(
new URL('../.vscode/settings.json', import.meta.url),
'utf-8',
),
)
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])