mirror of
https://github.com/elk-zone/elk.git
synced 2024-11-21 08:55:24 +03:00
Merge branch 'main' into userquin/feat-add-pinch-to-zoom-setting
# Conflicts: # styles/global.css
This commit is contained in:
commit
d8960ff691
28 changed files with 124 additions and 429 deletions
|
@ -30,7 +30,7 @@ const emit = defineEmits<{
|
|||
</p>
|
||||
{{ $t('help.desc_para3') }}
|
||||
<p flex="~ gap-2 wrap" mxa>
|
||||
<template v-for="team of teams" :key="team.github">
|
||||
<template v-for="team of elkTeamMembers" :key="team.github">
|
||||
<NuxtLink :href="`https://github.com/sponsors/${team.github}`" target="_blank" external rounded-full transition duration-300 border="~ transparent" hover="scale-105 border-primary">
|
||||
<img :src="`/avatars/${team.github}-100x100.png`" :alt="team.display" rounded-full w-15 h-15 height="60" width="60">
|
||||
</NuxtLink>
|
||||
|
|
|
@ -34,7 +34,7 @@ const toggleApply = () => {
|
|||
text-white px2 py2 rounded-full cursor-pointer
|
||||
@click="$emit('remove')"
|
||||
>
|
||||
<div i-ri:close-line text-3 :class="[isHydrated && isSmallScreen ? 'text-6' : 'text-3']" />
|
||||
<div i-ri:close-line text-3 text-6 md:text-3 />
|
||||
</div>
|
||||
</div>
|
||||
<div absolute right-2 bottom-2>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
import ISO6391 from 'iso-639-1'
|
||||
import Fuse from 'fuse.js'
|
||||
|
||||
let { modelValue } = $defineModel<{
|
||||
|
@ -10,17 +9,7 @@ const { t } = useI18n()
|
|||
|
||||
const languageKeyword = $ref('')
|
||||
|
||||
const languageList: {
|
||||
code: string
|
||||
nativeName: string
|
||||
name: string
|
||||
}[] = ISO6391.getAllCodes().map(code => ({
|
||||
code,
|
||||
nativeName: ISO6391.getNativeName(code),
|
||||
name: ISO6391.getName(code),
|
||||
}))
|
||||
|
||||
const fuse = new Fuse(languageList, {
|
||||
const fuse = new Fuse(languagesNameList, {
|
||||
keys: ['code', 'nativeName', 'name'],
|
||||
shouldSort: true,
|
||||
})
|
||||
|
@ -28,7 +17,7 @@ const fuse = new Fuse(languageList, {
|
|||
const languages = $computed(() =>
|
||||
languageKeyword.trim()
|
||||
? fuse.search(languageKeyword).map(r => r.item)
|
||||
: [...languageList].sort(({ code: a }, { code: b }) => {
|
||||
: [...languagesNameList].sort(({ code: a }, { code: b }) => {
|
||||
return a === modelValue ? -1 : b === modelValue ? 1 : a.localeCompare(b)
|
||||
}),
|
||||
)
|
||||
|
@ -39,13 +28,15 @@ function chooseLanguage(language: string) {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<input
|
||||
v-model="languageKeyword"
|
||||
:placeholder="t('language.search')"
|
||||
p2 mb2 border-rounded w-full bg-transparent
|
||||
outline-none border="~ base"
|
||||
>
|
||||
<div relative of-x-hidden>
|
||||
<div p2>
|
||||
<input
|
||||
v-model="languageKeyword"
|
||||
:placeholder="t('language.search')"
|
||||
p2 border-rounded w-full bg-transparent
|
||||
outline-none border="~ base"
|
||||
>
|
||||
</div>
|
||||
<div max-h-40vh overflow-auto>
|
||||
<CommonDropdownItem
|
||||
v-for="{ code, nativeName, name } in languages"
|
||||
|
|
|
@ -6,7 +6,7 @@ import type { Draft } from '~/types'
|
|||
|
||||
const {
|
||||
draftKey,
|
||||
initial = getDefaultDraft() as never /* Bug of vue-core */,
|
||||
initial = getDefaultDraft,
|
||||
expanded = false,
|
||||
placeholder,
|
||||
dialogLabelledBy,
|
||||
|
@ -35,7 +35,7 @@ const {
|
|||
dropZoneRef,
|
||||
} = $(useUploadMediaAttachment($$(draft)))
|
||||
|
||||
let { shouldExpanded, isExpanded, isSending, isPublishDisabled, publishDraft, failedMessages } = $(usePublish(
|
||||
let { shouldExpanded, isExpanded, isSending, isPublishDisabled, publishDraft, failedMessages, preferredLanguage } = $(usePublish(
|
||||
{
|
||||
draftState,
|
||||
...$$({ expanded, isUploading, initialDraft: initial }),
|
||||
|
@ -62,6 +62,7 @@ const { editor } = useTiptap({
|
|||
},
|
||||
onPaste: handlePaste,
|
||||
})
|
||||
|
||||
const characterCount = $computed(() => {
|
||||
let length = stringLength(htmlToText(editor.value?.getHTML() || ''))
|
||||
|
||||
|
@ -76,6 +77,8 @@ const characterCount = $computed(() => {
|
|||
return length
|
||||
})
|
||||
|
||||
const postLanguageDisplay = $computed(() => languagesNameList.find(i => i.code === (draft.params.language || preferredLanguage))?.nativeName)
|
||||
|
||||
async function handlePaste(evt: ClipboardEvent) {
|
||||
const files = evt.clipboardData?.files
|
||||
if (!files || files.length === 0)
|
||||
|
@ -147,7 +150,7 @@ defineExpose({
|
|||
>
|
||||
<ContentMentionGroup v-if="draft.mentions?.length && shouldExpanded" replying>
|
||||
<button v-for="m, i of draft.mentions" :key="m" text-primary hover:color-red @click="draft.mentions?.splice(i, 1)">
|
||||
{{ acctToShortHandle(m) }}
|
||||
{{ accountToShortHandle(m) }}
|
||||
</button>
|
||||
</ContentMentionGroup>
|
||||
|
||||
|
@ -278,6 +281,20 @@ defineExpose({
|
|||
{{ characterCount ?? 0 }}<span text-secondary-light>/</span><span text-secondary-light>{{ characterLimit }}</span>
|
||||
</div>
|
||||
|
||||
<CommonTooltip placement="top" :content="$t('tooltip.change_language')">
|
||||
<CommonDropdown placement="bottom" auto-boundary-max-size>
|
||||
<button btn-action-icon :aria-label="$t('tooltip.change_language')" w-max mr1>
|
||||
<span v-if="postLanguageDisplay" text-secondary text-sm ml1>{{ postLanguageDisplay }}</span>
|
||||
<div v-else i-ri:translate-2 />
|
||||
<div i-ri:arrow-down-s-line text-sm text-secondary me--1 />
|
||||
</button>
|
||||
|
||||
<template #popper>
|
||||
<PublishLanguagePicker v-model="draft.params.language" min-w-80 />
|
||||
</template>
|
||||
</CommonDropdown>
|
||||
</CommonTooltip>
|
||||
|
||||
<CommonTooltip placement="top" :content="$t('tooltip.add_content_warning')">
|
||||
<button btn-action-icon :aria-label="$t('tooltip.add_content_warning')" @click="toggleSensitive">
|
||||
<div v-if="draft.params.sensitive" i-ri:alarm-warning-fill text-orange />
|
||||
|
@ -285,19 +302,6 @@ defineExpose({
|
|||
</button>
|
||||
</CommonTooltip>
|
||||
|
||||
<CommonTooltip placement="top" :content="$t('tooltip.change_language')">
|
||||
<CommonDropdown placement="bottom" auto-boundary-max-size>
|
||||
<button btn-action-icon :aria-label="$t('tooltip.change_language')" w-12 mr--1>
|
||||
<div i-ri:translate-2 />
|
||||
<div i-ri:arrow-down-s-line text-sm text-secondary me--1 />
|
||||
</button>
|
||||
|
||||
<template #popper>
|
||||
<PublishLanguagePicker v-model="draft.params.language" min-w-80 p3 />
|
||||
</template>
|
||||
</CommonDropdown>
|
||||
</CommonTooltip>
|
||||
|
||||
<PublishVisibilityPicker v-model="draft.params.visibility" :editing="!!draft.editingStatus">
|
||||
<template #default="{ visibility }">
|
||||
<button :disabled="!!draft.editingStatus" :aria-label="$t('tooltip.change_content_visibility')" btn-action-icon :class="{ 'w-12': !draft.editingStatus }">
|
||||
|
|
|
@ -5,7 +5,7 @@ const all = useUsers()
|
|||
const router = useRouter()
|
||||
|
||||
const clickUser = (user: UserLogin) => {
|
||||
if (user.account.id === currentUser.value?.account.id)
|
||||
if (user.account.acct === currentUser.value?.account.acct)
|
||||
router.push(getAccountRoute(user.account))
|
||||
else
|
||||
switchUser(user)
|
||||
|
@ -21,7 +21,7 @@ const clickUser = (user: UserLogin) => {
|
|||
flex rounded
|
||||
cursor-pointer
|
||||
aria-label="Switch user"
|
||||
:class="user.account.id === currentUser?.account.id ? '' : 'op25 grayscale'"
|
||||
:class="user.account.acct === currentUser?.account.acct ? '' : 'op25 grayscale'"
|
||||
hover="filter-none op100"
|
||||
@click="clickUser(user)"
|
||||
>
|
||||
|
|
|
@ -51,7 +51,7 @@ const clickUser = (user: UserLogin) => {
|
|||
:text="$t('user.sign_out_account', [getFullHandle(currentUser.account)])"
|
||||
icon="i-ri:logout-box-line rtl-flip"
|
||||
w-full
|
||||
@click="signout"
|
||||
@click="signOut"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@ export interface Team {
|
|||
mastodon: string
|
||||
}
|
||||
|
||||
export const teams: Team[] = [
|
||||
export const elkTeamMembers: Team[] = [
|
||||
{
|
||||
github: 'antfu',
|
||||
display: 'Anthony Fu',
|
||||
|
|
|
@ -349,7 +349,7 @@ export const provideGlobalCommands = () => {
|
|||
icon: 'i-ri:logout-box-line',
|
||||
|
||||
onActivate() {
|
||||
signout()
|
||||
signOut()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export function contentToVNode(
|
|||
return h(Fragment, (tree.children as Node[] || []).map(n => treeToVNode(n)))
|
||||
}
|
||||
|
||||
export function nodeToVNode(node: Node): VNode | string | null {
|
||||
function nodeToVNode(node: Node): VNode | string | null {
|
||||
if (node.type === TEXT_NODE)
|
||||
return node.value
|
||||
|
||||
|
|
11
composables/langugage.ts
Normal file
11
composables/langugage.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import ISO6391 from 'iso-639-1'
|
||||
|
||||
export const languagesNameList: {
|
||||
code: string
|
||||
nativeName: string
|
||||
name: string
|
||||
}[] = ISO6391.getAllCodes().map(code => ({
|
||||
code,
|
||||
nativeName: ISO6391.getNativeName(code),
|
||||
name: ISO6391.getName(code),
|
||||
}))
|
|
@ -7,14 +7,14 @@ export function getDisplayName(account: mastodon.v1.Account, options?: { rich?:
|
|||
return displayName.replace(/:([\w-]+?):/g, '')
|
||||
}
|
||||
|
||||
export function acctToShortHandle(acct: string) {
|
||||
export function accountToShortHandle(acct: string) {
|
||||
return `@${acct.includes('@') ? acct.split('@')[0] : acct}`
|
||||
}
|
||||
|
||||
export function getShortHandle({ acct }: mastodon.v1.Account) {
|
||||
if (!acct)
|
||||
return ''
|
||||
return acctToShortHandle(acct)
|
||||
return accountToShortHandle(acct)
|
||||
}
|
||||
|
||||
export function getServerName(account: mastodon.v1.Account) {
|
||||
|
|
|
@ -47,7 +47,7 @@ export function mastoLogin(masto: ElkMasto, user: Pick<UserLogin, 'server' | 'to
|
|||
setParams({
|
||||
streamingApiUrl: newInstance.urls.streamingApi,
|
||||
})
|
||||
instances.value[server] = newInstance
|
||||
instanceStorage.value[server] = newInstance
|
||||
})
|
||||
|
||||
return instance
|
||||
|
|
|
@ -4,15 +4,18 @@ import type { mastodon } from 'masto'
|
|||
import type { UseDraft } from './statusDrafts'
|
||||
import type { Draft } from '~~/types'
|
||||
|
||||
export const usePublish = (options: {
|
||||
export function usePublish(options: {
|
||||
draftState: UseDraft
|
||||
expanded: Ref<boolean>
|
||||
isUploading: Ref<boolean>
|
||||
initialDraft: Ref<() => Draft>
|
||||
}) => {
|
||||
}) {
|
||||
const { expanded, isUploading, initialDraft } = $(options)
|
||||
let { draft, isEmpty } = $(options.draftState)
|
||||
const { client } = $(useMasto())
|
||||
const settings = useUserSettings()
|
||||
|
||||
const preferredLanguage = $computed(() => (settings.value?.language || 'en').split('-')[0])
|
||||
|
||||
let isSending = $ref(false)
|
||||
const isExpanded = $ref(false)
|
||||
|
@ -31,6 +34,7 @@ export const usePublish = (options: {
|
|||
async function publishDraft() {
|
||||
if (isPublishDisabled)
|
||||
return
|
||||
|
||||
let content = htmlToText(draft.params.status || '')
|
||||
if (draft.mentions?.length)
|
||||
content = `${draft.mentions.map(i => `@${i}`).join(' ')} ${content}`
|
||||
|
@ -39,11 +43,12 @@ export const usePublish = (options: {
|
|||
...draft.params,
|
||||
status: content,
|
||||
mediaIds: draft.attachments.map(a => a.id),
|
||||
language: draft.params.language || preferredLanguage,
|
||||
...(isGlitchEdition.value ? { 'content-type': 'text/markdown' } : {}),
|
||||
} as mastodon.v1.CreateStatusParams
|
||||
|
||||
if (process.dev) {
|
||||
// eslint-disable-next-line no-console
|
||||
// eslint-disable-next-line no-console
|
||||
console.info({
|
||||
raw: draft.params.status,
|
||||
...payload,
|
||||
|
@ -60,6 +65,7 @@ export const usePublish = (options: {
|
|||
let status: mastodon.v1.Status
|
||||
if (!draft.editingStatus)
|
||||
status = await client.v1.statuses.create(payload)
|
||||
|
||||
else
|
||||
status = await client.v1.statuses.update(draft.editingStatus.id, payload)
|
||||
if (draft.params.inReplyToId)
|
||||
|
@ -84,14 +90,14 @@ export const usePublish = (options: {
|
|||
shouldExpanded,
|
||||
isPublishDisabled,
|
||||
failedMessages,
|
||||
|
||||
preferredLanguage,
|
||||
publishDraft,
|
||||
})
|
||||
}
|
||||
|
||||
export type MediaAttachmentUploadError = [filename: string, message: string]
|
||||
|
||||
export const useUploadMediaAttachment = (draftRef: Ref<Draft>) => {
|
||||
export function useUploadMediaAttachment(draftRef: Ref<Draft>) {
|
||||
const draft = $(draftRef)
|
||||
const { client } = $(useMasto())
|
||||
const { t } = useI18n()
|
||||
|
@ -117,7 +123,7 @@ export const useUploadMediaAttachment = (draftRef: Ref<Draft>) => {
|
|||
draft.attachments.push(attachment)
|
||||
}
|
||||
catch (e) {
|
||||
// TODO: add some human-readable error message, problem is that masto api will not return response code
|
||||
// TODO: add some human-readable error message, problem is that masto api will not return response code
|
||||
console.error(e)
|
||||
failedAttachments = [...failedAttachments, [file.name, (e as Error).message]]
|
||||
}
|
||||
|
@ -159,9 +165,10 @@ export const useUploadMediaAttachment = (draftRef: Ref<Draft>) => {
|
|||
return $$({
|
||||
isUploading,
|
||||
isExceedingAttachmentLimit,
|
||||
isOverDropZone,
|
||||
|
||||
failedAttachments,
|
||||
dropZoneRef,
|
||||
isOverDropZone,
|
||||
|
||||
uploadAttachments,
|
||||
pickAttachments,
|
||||
|
|
|
@ -33,7 +33,7 @@ export function getDefaultDraft(options: Partial<Mutable<mastodon.v1.CreateStatu
|
|||
visibility: visibility || 'public',
|
||||
sensitive: sensitive ?? false,
|
||||
spoilerText: spoilerText || '',
|
||||
language: language || getDefaultLanguage(),
|
||||
language: language || '', // auto inferred from current language on posting
|
||||
},
|
||||
mentions,
|
||||
lastUpdated: Date.now(),
|
||||
|
@ -52,16 +52,6 @@ export async function getDraftFromStatus(status: mastodon.v1.Status): Promise<Dr
|
|||
})
|
||||
}
|
||||
|
||||
function getDefaultLanguage() {
|
||||
const userSettings = useUserSettings()
|
||||
const defaultLanguage = userSettings.value.language
|
||||
|
||||
if (defaultLanguage)
|
||||
return defaultLanguage.split('-')[0]
|
||||
|
||||
return 'en'
|
||||
}
|
||||
|
||||
function getAccountsToMention(status: mastodon.v1.Status) {
|
||||
const userId = currentUser.value?.account.id
|
||||
const accountsToMention = new Set<string>()
|
||||
|
|
|
@ -2,6 +2,4 @@ import { breakpointsTailwind } from '@vueuse/core'
|
|||
|
||||
export const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
|
||||
export const isSmallScreen = breakpoints.smallerOrEqual('md')
|
||||
export const isMediumScreen = breakpoints.smallerOrEqual('lg')
|
||||
export const isMediumOrLargeScreen = breakpoints.between('sm', 'xl')
|
||||
|
|
|
@ -7,7 +7,6 @@ import type { UserLogin } from '~/types'
|
|||
import type { Overwrite } from '~/types/utils'
|
||||
import {
|
||||
DEFAULT_POST_CHARS_LIMIT,
|
||||
STORAGE_KEY_CURRENT_USER,
|
||||
STORAGE_KEY_CURRENT_USER_HANDLE,
|
||||
STORAGE_KEY_NODES,
|
||||
STORAGE_KEY_NOTIFICATION,
|
||||
|
@ -44,20 +43,20 @@ const initializeUsers = async (): Promise<Ref<UserLogin[]> | RemovableRef<UserLo
|
|||
}
|
||||
|
||||
const users = await initializeUsers()
|
||||
export const instances = useLocalStorage<Record<string, mastodon.v1.Instance>>(STORAGE_KEY_SERVERS, mock ? mock.server : {}, { deep: true })
|
||||
export const nodes = useLocalStorage<Record<string, any>>(STORAGE_KEY_NODES, {}, { deep: true })
|
||||
const currentUserId = useLocalStorage<string>(STORAGE_KEY_CURRENT_USER, mock ? mock.user.account.id : '')
|
||||
const nodes = useLocalStorage<Record<string, any>>(STORAGE_KEY_NODES, {}, { deep: true })
|
||||
const currentUserHandle = useLocalStorage<string>(STORAGE_KEY_CURRENT_USER_HANDLE, mock ? mock.user.account.id : '')
|
||||
export const instanceStorage = useLocalStorage<Record<string, mastodon.v1.Instance>>(STORAGE_KEY_SERVERS, mock ? mock.server : {}, { deep: true })
|
||||
|
||||
export type ElkInstance = Partial<mastodon.v1.Instance> & {
|
||||
uri: string
|
||||
/** support GoToSocial */
|
||||
accountDomain?: string | null
|
||||
}
|
||||
export const getInstanceCache = (server: string): mastodon.v1.Instance | undefined => instances.value[server]
|
||||
export const getInstanceCache = (server: string): mastodon.v1.Instance | undefined => instanceStorage.value[server]
|
||||
|
||||
export const currentUser = computed<UserLogin | undefined>(() => {
|
||||
if (currentUserId.value) {
|
||||
const user = users.value.find(user => user.account?.id === currentUserId.value)
|
||||
if (currentUserHandle.value) {
|
||||
const user = users.value.find(user => user.account?.acct === currentUserHandle.value)
|
||||
if (user)
|
||||
return user
|
||||
}
|
||||
|
@ -66,7 +65,7 @@ export const currentUser = computed<UserLogin | undefined>(() => {
|
|||
})
|
||||
|
||||
const publicInstance = ref<ElkInstance | null>(null)
|
||||
export const currentInstance = computed<null | ElkInstance>(() => currentUser.value ? instances.value[currentUser.value.server] ?? null : publicInstance.value)
|
||||
export const currentInstance = computed<null | ElkInstance>(() => currentUser.value ? instanceStorage.value[currentUser.value.server] ?? null : publicInstance.value)
|
||||
|
||||
export function getInstanceDomain(instance: ElkInstance) {
|
||||
return instance.accountDomain || withoutProtocol(instance.uri)
|
||||
|
@ -84,12 +83,12 @@ if (process.client) {
|
|||
const windowReload = () => {
|
||||
document.visibilityState === 'visible' && window.location.reload()
|
||||
}
|
||||
watch(currentUserId, async (id, oldId) => {
|
||||
watch(currentUserHandle, async (handle, oldHandle) => {
|
||||
// when sign in or switch account
|
||||
if (id) {
|
||||
if (id === currentUser.value?.account?.id) {
|
||||
if (handle) {
|
||||
if (handle === currentUser.value?.account?.acct) {
|
||||
// when sign in, the other tab will not have the user, idb is not reactive
|
||||
const newUser = users.value.find(user => user.account?.id === id)
|
||||
const newUser = users.value.find(user => user.account?.acct === handle)
|
||||
// if the user is there, then we are switching account
|
||||
if (newUser) {
|
||||
// check if the change is on current tab: if so, don't reload
|
||||
|
@ -101,19 +100,13 @@ if (process.client) {
|
|||
window.addEventListener('visibilitychange', windowReload, { capture: true })
|
||||
}
|
||||
// when sign out
|
||||
else if (oldId) {
|
||||
const oldUser = users.value.find(user => user.account?.id === oldId)
|
||||
else if (oldHandle) {
|
||||
const oldUser = users.value.find(user => user.account?.acct === oldHandle)
|
||||
// when sign out, the other tab will not have the user, idb is not reactive
|
||||
if (oldUser)
|
||||
window.addEventListener('visibilitychange', windowReload, { capture: true })
|
||||
}
|
||||
}, { immediate: true, flush: 'post' })
|
||||
|
||||
// for injected script to read
|
||||
const currentUserHandle = computed(() => currentUser.value?.account.acct || '')
|
||||
watchEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEY_CURRENT_USER_HANDLE, currentUserHandle.value)
|
||||
})
|
||||
}
|
||||
|
||||
export const useUsers = () => users
|
||||
|
@ -144,7 +137,7 @@ export async function loginTo(masto: ElkMasto, user: Overwrite<UserLogin, { acco
|
|||
|
||||
const account = getUser()?.account
|
||||
if (account)
|
||||
currentUserId.value = account.id
|
||||
currentUserHandle.value = account.acct
|
||||
|
||||
const [me, pushSubscription] = await Promise.all([
|
||||
fetchAccountInfo(client, user.server),
|
||||
|
@ -168,7 +161,7 @@ export async function loginTo(masto: ElkMasto, user: Overwrite<UserLogin, { acco
|
|||
})
|
||||
}
|
||||
|
||||
currentUserId.value = me.id
|
||||
currentUserHandle.value = me.acct
|
||||
}
|
||||
|
||||
export async function fetchAccountInfo(client: mastodon.Client, server: string) {
|
||||
|
@ -238,7 +231,7 @@ export async function switchUser(user: UserLogin) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function signout() {
|
||||
export async function signOut() {
|
||||
// TODO: confirm
|
||||
if (!currentUser.value)
|
||||
return
|
||||
|
@ -253,21 +246,21 @@ export async function signout() {
|
|||
// Clear stale data
|
||||
clearUserLocalStorage()
|
||||
if (!users.value.some((u, i) => u.server === currentUser.value!.server && i !== index))
|
||||
delete instances.value[currentUser.value.server]
|
||||
delete instanceStorage.value[currentUser.value.server]
|
||||
|
||||
await removePushNotifications(currentUser.value)
|
||||
|
||||
await removePushNotificationData(currentUser.value)
|
||||
|
||||
currentUserId.value = ''
|
||||
currentUserHandle.value = ''
|
||||
// Remove the current user from the users
|
||||
users.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// Set currentUserId to next user if available
|
||||
currentUserId.value = users.value[0]?.account?.id
|
||||
currentUserHandle.value = users.value[0]?.account?.acct
|
||||
|
||||
if (!currentUserId.value)
|
||||
if (!currentUserHandle.value)
|
||||
await useRouter().push('/')
|
||||
|
||||
loginTo(masto, currentUser.value)
|
||||
|
|
|
@ -7,7 +7,6 @@ export const STORAGE_KEY_DRAFTS = 'elk-drafts'
|
|||
export const STORAGE_KEY_USERS = 'elk-users'
|
||||
export const STORAGE_KEY_SERVERS = 'elk-servers'
|
||||
export const STORAGE_KEY_NODES = 'elk-nodes'
|
||||
export const STORAGE_KEY_CURRENT_USER = 'elk-current-user'
|
||||
export const STORAGE_KEY_CURRENT_USER_HANDLE = 'elk-current-user-handle'
|
||||
export const STORAGE_KEY_NOTIFY_TAB = 'elk-notify-tab'
|
||||
export const STORAGE_KEY_FIRST_VISIT = 'elk-first-visit'
|
||||
|
|
|
@ -22,6 +22,7 @@ export default defineNuxtModule({
|
|||
...nuxt.options.alias,
|
||||
'unstorage/drivers/fs': 'unenv/runtime/mock/proxy',
|
||||
'unstorage/drivers/cloudflare-kv-http': 'unenv/runtime/mock/proxy',
|
||||
'#storage-config': resolve('./runtime/storage-config'),
|
||||
'node:events': 'unenv/runtime/node/events/index',
|
||||
'#build-info': resolve('./runtime/build-info'),
|
||||
}
|
||||
|
|
2
modules/tauri/runtime/storage-config.ts
Normal file
2
modules/tauri/runtime/storage-config.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export const driver = undefined
|
||||
export const fsBase = ''
|
|
@ -1,4 +1,4 @@
|
|||
import { createResolver } from '@nuxt/kit'
|
||||
import { createResolver, useNuxt } from '@nuxt/kit'
|
||||
import Inspect from 'vite-plugin-inspect'
|
||||
import { isCI, isDevelopment, isWindows } from 'std-env'
|
||||
import { isPreview } from './config/env'
|
||||
|
@ -86,6 +86,11 @@ export default defineNuxtConfig({
|
|||
'postcss-nested': {},
|
||||
},
|
||||
},
|
||||
appConfig: {
|
||||
storage: {
|
||||
driver: process.env.NUXT_STORAGE_DRIVER ?? (isCI ? 'cloudflare' : 'fs'),
|
||||
},
|
||||
},
|
||||
runtimeConfig: {
|
||||
adminKey: '',
|
||||
cloudflare: {
|
||||
|
@ -102,8 +107,7 @@ export default defineNuxtConfig({
|
|||
defaultServer: 'm.webtoo.ls',
|
||||
},
|
||||
storage: {
|
||||
driver: isCI ? 'cloudflare' : 'fs',
|
||||
fsBase: 'node_modules/.cache/servers',
|
||||
fsBase: 'node_modules/.cache/app',
|
||||
},
|
||||
},
|
||||
routeRules: {
|
||||
|
@ -126,6 +130,13 @@ export default defineNuxtConfig({
|
|||
ignore: ['/settings'],
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
'nitro:config': function (config) {
|
||||
const nuxt = useNuxt()
|
||||
config.virtual = config.virtual || {}
|
||||
config.virtual['#storage-config'] = `export const driver = ${JSON.stringify(nuxt.options.appConfig.storage.driver)}`
|
||||
},
|
||||
},
|
||||
app: {
|
||||
keepalive: true,
|
||||
head: {
|
||||
|
|
|
@ -118,7 +118,7 @@
|
|||
"unplugin-vue-inspector": "^0.0.2",
|
||||
"vite-plugin-inspect": "^0.7.14",
|
||||
"vite-plugin-pwa": "^0.14.1",
|
||||
"vitest": "^0.28.1",
|
||||
"vitest": "^0.28.3",
|
||||
"vue-tsc": "^1.0.24",
|
||||
"workbox-build": "^6.5.4",
|
||||
"workbox-window": "^6.5.4"
|
||||
|
|
|
@ -115,7 +115,7 @@ const handleShowCommit = () => {
|
|||
</p>
|
||||
|
||||
<SettingsItem
|
||||
v-for="team in teams" :key="team.github"
|
||||
v-for="team in elkTeamMembers" :key="team.github"
|
||||
:text="team.display"
|
||||
:to="`https://github.com/sponsors/${team.github}`"
|
||||
external target="_blank"
|
||||
|
|
|
@ -119,7 +119,7 @@ importers:
|
|||
unplugin-vue-inspector: ^0.0.2
|
||||
vite-plugin-inspect: ^0.7.14
|
||||
vite-plugin-pwa: ^0.14.1
|
||||
vitest: ^0.28.1
|
||||
vitest: ^0.28.3
|
||||
vue-advanced-cropper: ^2.8.8
|
||||
vue-tsc: ^1.0.24
|
||||
vue-virtual-scroller: 2.0.0-beta.7
|
||||
|
@ -216,7 +216,7 @@ importers:
|
|||
unplugin-auto-import: 0.13.0_@vueuse+core@9.11.1
|
||||
unplugin-vue-inspector: 0.0.2
|
||||
vite-plugin-inspect: 0.7.14
|
||||
vite-plugin-pwa: 0.14.1_tz3vz2xt4jvid2diblkpydcyn4
|
||||
vite-plugin-pwa: 0.14.1
|
||||
vitest: 0.28.3_jsdom@21.1.0
|
||||
vue-tsc: 1.0.24_typescript@4.9.4
|
||||
workbox-build: 6.5.4
|
||||
|
@ -12949,12 +12949,10 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/vite-plugin-pwa/0.14.1_tz3vz2xt4jvid2diblkpydcyn4:
|
||||
/vite-plugin-pwa/0.14.1:
|
||||
resolution: {integrity: sha512-5zx7yhQ8RTLwV71+GA9YsQQ63ALKG8XXIMqRJDdZkR8ZYftFcRgnzM7wOWmQZ/DATspyhPih5wCdcZnAIsM+mA==}
|
||||
peerDependencies:
|
||||
vite: ^3.1.0 || ^4.0.0
|
||||
workbox-build: ^6.5.4
|
||||
workbox-window: ^6.5.4
|
||||
dependencies:
|
||||
'@rollup/plugin-replace': 5.0.2_rollup@3.10.1
|
||||
debug: 4.3.4
|
||||
|
@ -12964,6 +12962,7 @@ packages:
|
|||
workbox-build: 6.5.4
|
||||
workbox-window: 6.5.4
|
||||
transitivePeerDependencies:
|
||||
- '@types/babel__core'
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { join, resolve } from 'pathe'
|
||||
import fs from 'fs-extra'
|
||||
import { $fetch } from 'ohmyfetch'
|
||||
import { teams } from '../composables/about'
|
||||
import { elkTeamMembers } from '../composables/about'
|
||||
|
||||
const avatarsDir = resolve('./public/avatars/')
|
||||
|
||||
|
@ -24,7 +24,7 @@ async function download(url: string, fileName: string) {
|
|||
async function fetchAvatars() {
|
||||
await fs.ensureDir(avatarsDir)
|
||||
|
||||
await Promise.all(teams.reduce((acc, { github }) => {
|
||||
await Promise.all(elkTeamMembers.reduce((acc, { github }) => {
|
||||
acc.push(...sizes.map(s => download(`https://github.com/${github}.png?size=${s}`, join(avatarsDir, `${github}-${s}x${s}.png`))))
|
||||
return acc
|
||||
}, [] as Promise<void>[]))
|
||||
|
|
|
@ -14,29 +14,31 @@ import cached from './cache-driver'
|
|||
|
||||
// @ts-expect-error virtual import
|
||||
import { env } from '#build-info'
|
||||
// @ts-expect-error virtual import
|
||||
import { driver } from '#storage-config'
|
||||
|
||||
import type { AppInfo } from '~/types'
|
||||
import { APP_NAME } from '~/constants'
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const fs = _fs as typeof import('unstorage/dist/drivers/fs')['default']
|
||||
const kv = _kv as typeof import('unstorage/dist/drivers/cloudflare-kv-http')['default']
|
||||
const memory = _memory as typeof import('unstorage/dist/drivers/memory')['default']
|
||||
|
||||
const storage = useStorage() as Storage
|
||||
|
||||
if (config.storage.driver === 'fs') {
|
||||
if (driver === 'fs') {
|
||||
const config = useRuntimeConfig()
|
||||
storage.mount('servers', fs({ base: config.storage.fsBase }))
|
||||
}
|
||||
else if (config.storage.driver === 'cloudflare') {
|
||||
else if (driver === 'cloudflare') {
|
||||
const config = useRuntimeConfig()
|
||||
storage.mount('servers', cached(kv({
|
||||
accountId: config.cloudflare.accountId,
|
||||
namespaceId: config.cloudflare.namespaceId,
|
||||
apiToken: config.cloudflare.apiToken,
|
||||
})))
|
||||
}
|
||||
else if (config.storage.driver === 'memory') {
|
||||
else if (driver === 'memory') {
|
||||
storage.mount('servers', memory())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
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`] = `
|
||||
"<pre>
|
||||
<code class=\\"language-js\\">
|
||||
<a href="javascript:alert(1)">click me</a>
|
||||
</code>
|
||||
</pre>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`content-rich > block with injected html, with an unknown language 1`] = `
|
||||
"<pre>
|
||||
<code class=\\"language-xyzzy\\">
|
||||
<a href="javascript:alert(1)">click me</a>
|
||||
</code>
|
||||
</pre>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`content-rich > block with injected html, without language 1`] = `
|
||||
"<pre>
|
||||
<code>
|
||||
<a href="javascript:alert(1)">click me</a>
|
||||
</code>
|
||||
</pre>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`content-rich > code frame 1`] = `
|
||||
"<p>Testing code block</p><p></p><p><pre class=\\"code-block\\">import { useMouse, usePreferredDark } from '@vueuse/core'
|
||||
// tracks mouse position
|
||||
const { x, y } = useMouse()
|
||||
// is the user prefers dark theme
|
||||
const isDark = usePreferredDark()</pre></p>"
|
||||
`;
|
||||
|
||||
exports[`content-rich > code frame 2 1`] = `
|
||||
"<p>
|
||||
<span class=\\"h-card\\"
|
||||
><a
|
||||
class=\\"u-url mention\\"
|
||||
rel=\\"nofollow noopener noreferrer\\"
|
||||
to=\\"/webtoo.ls/@antfu\\"
|
||||
></a
|
||||
></span>
|
||||
Testing<br />
|
||||
<pre class=\\"code-block\\">const a = hello</pre>
|
||||
</p>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`content-rich > code frame empty 1`] = `"<p><pre class=\\"code-block\\"></pre><br></p>"`;
|
||||
|
||||
exports[`content-rich > code frame no lang 1`] = `"<p><pre class=\\"code-block\\">hello world</pre><br>no lang</p>"`;
|
||||
|
||||
exports[`content-rich > custom emoji 1`] = `
|
||||
"Daniel Roe
|
||||
<picture alt=\\":nuxt:\\" class=\\"custom-emoji\\" data-emoji-id=\\"nuxt\\"
|
||||
><source
|
||||
srcset=\\"
|
||||
https://media.webtoo.ls/custom_emojis/images/000/000/366/original/73330dfc9dda4078.png
|
||||
\\"
|
||||
media=\\"(prefers-reduced-motion: reduce)\\" />
|
||||
<img
|
||||
src=\\"https://media.webtoo.ls/custom_emojis/images/000/000/366/original/73330dfc9dda4078.png\\"
|
||||
alt=\\":nuxt:\\"
|
||||
/></picture>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`content-rich > empty 1`] = `""`;
|
||||
|
||||
exports[`content-rich > group mention > html 1`] = `
|
||||
"<p>
|
||||
<span class=\\"h-card\\"
|
||||
><a
|
||||
class=\\"u-url mention\\"
|
||||
rel=\\"nofollow noopener noreferrer\\"
|
||||
to=\\"//@pilipinas@lemmy.ml\\"
|
||||
></a
|
||||
></span>
|
||||
</p>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`content-rich > handles formatting from servers 1`] = `
|
||||
"<h1>Fedi HTML Support Survey</h1>
|
||||
<p>Does the following formatting come through accurately for you?</p>
|
||||
<p></p>
|
||||
<ul>
|
||||
<li>This is an indented bulleted list (not just asterisks).</li>
|
||||
<li><strong>This line is bold.</strong></li>
|
||||
<li><em>This line is italic.</em></li>
|
||||
</ul>
|
||||
<ol>
|
||||
<li>This list...</li>
|
||||
<li>...is numbered and indented</li>
|
||||
</ol>
|
||||
<h1>This line is larger.</h1>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`content-rich > handles html within code blocks 1`] = `
|
||||
"<p>
|
||||
HTML block code:<br />
|
||||
<pre class=\\"code-block\\">
|
||||
<span class="icon--noto icon--noto--1st-place-medal"></span>
|
||||
<span class="icon--noto icon--noto--2nd-place-medal-medal"></span></pre
|
||||
>
|
||||
</p>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`content-rich > inline code with link 1`] = `
|
||||
"<p>
|
||||
Inline code with link:
|
||||
<code
|
||||
>https://api.iconify.design/noto.css?icons=1st-place-medal,2nd-place-medal</code
|
||||
>
|
||||
</p>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`content-rich > link + mention 1`] = `
|
||||
"<p>
|
||||
Happy
|
||||
<img
|
||||
src=\\"/emojis/twemoji/1f917.svg\\"
|
||||
alt=\\"🤗\\"
|
||||
class=\\"iconify-emoji iconify-emoji--twemoji\\"
|
||||
/>
|
||||
we’re now using
|
||||
<span class=\\"h-card\\"
|
||||
><a
|
||||
class=\\"u-url mention\\"
|
||||
rel=\\"nofollow noopener noreferrer\\"
|
||||
to=\\"/webtoo.ls/@vitest\\"
|
||||
></a
|
||||
></span>
|
||||
(migrated from chai+mocha)
|
||||
<a
|
||||
href=\\"https://github.com/ayoayco/astro-reactive-library/pull/203\\"
|
||||
rel=\\"nofollow noopener noreferrer\\"
|
||||
target=\\"_blank\\"
|
||||
><span class=\\"invisible\\">https://</span
|
||||
><span class=\\"ellipsis\\">github.com/ayoayco/astro-react</span
|
||||
><span class=\\"invisible\\">ive-library/pull/203</span></a
|
||||
>
|
||||
</p>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`content-rich > plain text 1`] = `
|
||||
"hello there
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`editor > transform mentions 1`] = `
|
||||
"
|
||||
@elk Hello"
|
||||
`;
|
|
@ -1,144 +0,0 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`html-parse > code frame > html 1`] = `
|
||||
"<p>Testing code block</p><p></p><p><pre><code class=\\"language-ts\\">import { useMouse, usePreferredDark } from '@vueuse/core'
|
||||
// tracks mouse position
|
||||
const { x, y } = useMouse()
|
||||
// is the user prefers dark theme
|
||||
const isDark = usePreferredDark()</code></pre></p>"
|
||||
`;
|
||||
|
||||
exports[`html-parse > code frame > text 1`] = `
|
||||
"Testing code block
|
||||
|
||||
|
||||
\`\`\`ts
|
||||
import { useMouse, usePreferredDark } from '@vueuse/core'
|
||||
// tracks mouse position
|
||||
const { x, y } = useMouse()
|
||||
// is the user prefers dark theme
|
||||
const isDark = usePreferredDark()
|
||||
\`\`\`"
|
||||
`;
|
||||
|
||||
exports[`html-parse > code frame 2 > html 1`] = `
|
||||
"<p>
|
||||
<span class=\\"h-card\\"
|
||||
><a
|
||||
href=\\"https://webtoo.ls/@antfu\\"
|
||||
class=\\"u-url mention\\"
|
||||
rel=\\"nofollow noopener noreferrer\\"
|
||||
target=\\"_blank\\"
|
||||
>@<span>antfu</span></a
|
||||
></span
|
||||
>
|
||||
Testing<br />
|
||||
<pre><code class=\\"language-ts\\">const a = hello</code></pre>
|
||||
</p>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`html-parse > code frame 2 > text 1`] = `
|
||||
"@antfu Testing
|
||||
|
||||
\`\`\`ts
|
||||
const a = hello
|
||||
\`\`\`"
|
||||
`;
|
||||
|
||||
exports[`html-parse > custom emoji > html 1`] = `
|
||||
"Daniel Roe
|
||||
<picture alt=\\":nuxt:\\" class=\\"custom-emoji\\" data-emoji-id=\\"nuxt\\"
|
||||
><source
|
||||
srcset=\\"
|
||||
https://media.webtoo.ls/custom_emojis/images/000/000/366/original/73330dfc9dda4078.png
|
||||
\\"
|
||||
media=\\"(prefers-reduced-motion: reduce)\\" />
|
||||
<img
|
||||
src=\\"https://media.webtoo.ls/custom_emojis/images/000/000/366/original/73330dfc9dda4078.png\\"
|
||||
alt=\\":nuxt:\\"
|
||||
/></picture>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`html-parse > custom emoji > text 1`] = `"Daniel Roe :nuxt:"`;
|
||||
|
||||
exports[`html-parse > emojis > html 1`] = `
|
||||
"<img
|
||||
src=\\"/emojis/twemoji/1f1eb-1f1f7.svg\\"
|
||||
alt=\\"🇫🇷\\"
|
||||
class=\\"iconify-emoji iconify-emoji--twemoji\\"
|
||||
/>
|
||||
<img
|
||||
src=\\"/emojis/twemoji/1f468-200d-1f469-200d-1f466.svg\\"
|
||||
alt=\\"👨👩👦\\"
|
||||
class=\\"iconify-emoji iconify-emoji--twemoji\\"
|
||||
/>
|
||||
<img
|
||||
src=\\"/emojis/twemoji/1f469-200d-1f692.svg\\"
|
||||
alt=\\"👩🚒\\"
|
||||
class=\\"iconify-emoji iconify-emoji--twemoji\\"
|
||||
/><img
|
||||
src=\\"/emojis/twemoji/1f9d1-1f3fd-200d-1f680.svg\\"
|
||||
alt=\\"🧑🏽🚀\\"
|
||||
class=\\"iconify-emoji iconify-emoji--twemoji\\"
|
||||
/>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`html-parse > emojis > text 1`] = `"🇫🇷 👨👩👦 👩🚒🧑🏽🚀"`;
|
||||
|
||||
exports[`html-parse > empty > html 1`] = `""`;
|
||||
|
||||
exports[`html-parse > empty > text 1`] = `""`;
|
||||
|
||||
exports[`html-parse > html entities > html 1`] = `
|
||||
"<p>Hello <World />.</p>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`html-parse > html entities > text 1`] = `"Hello <World />."`;
|
||||
|
||||
exports[`html-parse > inline markdown > html 1`] = `"<p>text <code>code</code> <b>bold</b> <em>italic</em> <del>del</del></p><p></p><p><pre><code class=\\"language-js\\">code block</code></pre></p>"`;
|
||||
|
||||
exports[`html-parse > inline markdown > text 1`] = `
|
||||
"text \`code\` **bold** *italic* ~~del~~
|
||||
|
||||
|
||||
\`\`\`js
|
||||
code block
|
||||
\`\`\`"
|
||||
`;
|
||||
|
||||
exports[`html-parse > link + mention > html 1`] = `
|
||||
"<p>
|
||||
Happy
|
||||
<img
|
||||
src=\\"/emojis/twemoji/1f917.svg\\"
|
||||
alt=\\"🤗\\"
|
||||
class=\\"iconify-emoji iconify-emoji--twemoji\\"
|
||||
/>
|
||||
we’re now using
|
||||
<span class=\\"h-card\\"
|
||||
><a
|
||||
href=\\"https://webtoo.ls/@vitest\\"
|
||||
class=\\"u-url mention\\"
|
||||
rel=\\"nofollow noopener noreferrer\\"
|
||||
target=\\"_blank\\"
|
||||
>@<span>vitest</span></a
|
||||
></span
|
||||
>
|
||||
(migrated from chai+mocha)
|
||||
<a
|
||||
href=\\"https://github.com/ayoayco/astro-reactive-library/pull/203\\"
|
||||
rel=\\"nofollow noopener noreferrer\\"
|
||||
target=\\"_blank\\"
|
||||
><span class=\\"invisible\\">https://</span
|
||||
><span class=\\"ellipsis\\">github.com/ayoayco/astro-react</span
|
||||
><span class=\\"invisible\\">ive-library/pull/203</span></a
|
||||
>
|
||||
</p>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`html-parse > link + mention > text 1`] = `"Happy 🤗 we’re now using @vitest (migrated from chai+mocha) https://github.com/ayoayco/astro-reactive-library/pull/203"`;
|
|
@ -287,15 +287,10 @@ vi.mock('shiki-es', async (importOriginal) => {
|
|||
}
|
||||
})
|
||||
|
||||
vi.mock('~/components/content/ContentMentionGroup.vue', async () => {
|
||||
const { defineComponent, h } = await import('vue')
|
||||
return {
|
||||
default: defineComponent({
|
||||
setup(props, { slots }) {
|
||||
return () => h('mention-group', null, { default: () => slots?.default?.() })
|
||||
},
|
||||
}),
|
||||
}
|
||||
mockComponent('ContentMentionGroup', {
|
||||
setup(props, { slots }) {
|
||||
return () => h('mention-group', null, { default: () => slots?.default?.() })
|
||||
},
|
||||
})
|
||||
|
||||
mockComponent('AccountHoverWrapper', {
|
||||
|
|
Loading…
Reference in a new issue