chore: bump to eslint-config v2.8.0
(#2651)
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
This commit is contained in:
parent
62f70250d5
commit
9da77637b2
|
@ -1,15 +0,0 @@
|
||||||
*.css
|
|
||||||
*.png
|
|
||||||
*.ico
|
|
||||||
*.toml
|
|
||||||
*.patch
|
|
||||||
*.txt
|
|
||||||
Dockerfile
|
|
||||||
public/
|
|
||||||
public-dev/
|
|
||||||
public-staging/
|
|
||||||
https-dev-config/localhost.crt
|
|
||||||
https-dev-config/localhost.key
|
|
||||||
Dockerfile
|
|
||||||
elk-translation-status.json
|
|
||||||
docs/translation-status.json
|
|
19
.eslintrc
19
.eslintrc
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "@antfu",
|
|
||||||
"ignorePatterns": ["!pages/public"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["locales/**.json"],
|
|
||||||
"rules": {
|
|
||||||
"jsonc/sort-keys": "error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"vue/no-restricted-syntax":["error", {
|
|
||||||
"selector": "VElement[name='a']",
|
|
||||||
"message": "Use NuxtLink instead."
|
|
||||||
}],
|
|
||||||
"n/prefer-global/process": "off"
|
|
||||||
}
|
|
||||||
}
|
|
45
.vscode/settings.json
vendored
45
.vscode/settings.json
vendored
|
@ -5,10 +5,6 @@
|
||||||
"unmute",
|
"unmute",
|
||||||
"unstorage"
|
"unstorage"
|
||||||
],
|
],
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.fixAll.eslint": true
|
|
||||||
},
|
|
||||||
"editor.formatOnSave": false,
|
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.css": "postcss"
|
"*.css": "postcss"
|
||||||
},
|
},
|
||||||
|
@ -23,7 +19,44 @@
|
||||||
"i18n-ally.preferredDelimiter": "_",
|
"i18n-ally.preferredDelimiter": "_",
|
||||||
"i18n-ally.sortKeys": true,
|
"i18n-ally.sortKeys": true,
|
||||||
"i18n-ally.sourceLanguage": "en",
|
"i18n-ally.sourceLanguage": "en",
|
||||||
|
|
||||||
|
// Enable the ESlint flat config support
|
||||||
|
"eslint.experimental.useFlatConfig": true,
|
||||||
|
|
||||||
|
// Disable the default formatter, use eslint instead
|
||||||
"prettier.enable": false,
|
"prettier.enable": false,
|
||||||
"volar.completion.preferredTagNameCase": "pascal",
|
"editor.formatOnSave": false,
|
||||||
"volar.completion.preferredAttrNameCase": "kebab"
|
|
||||||
|
// Auto fix
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll": "explicit",
|
||||||
|
"source.organizeImports": "never"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||||
|
"eslint.rules.customizations": [
|
||||||
|
{ "rule": "style/*", "severity": "off" },
|
||||||
|
{ "rule": "*-indent", "severity": "off" },
|
||||||
|
{ "rule": "*-spacing", "severity": "off" },
|
||||||
|
{ "rule": "*-spaces", "severity": "off" },
|
||||||
|
{ "rule": "*-order", "severity": "off" },
|
||||||
|
{ "rule": "*-dangle", "severity": "off" },
|
||||||
|
{ "rule": "*-newline", "severity": "off" },
|
||||||
|
{ "rule": "*quotes", "severity": "off" },
|
||||||
|
{ "rule": "*semi", "severity": "off" }
|
||||||
|
],
|
||||||
|
|
||||||
|
// Enable eslint for all supported languages
|
||||||
|
"eslint.validate": [
|
||||||
|
"javascript",
|
||||||
|
"javascriptreact",
|
||||||
|
"typescript",
|
||||||
|
"typescriptreact",
|
||||||
|
"vue",
|
||||||
|
"html",
|
||||||
|
"markdown",
|
||||||
|
"json",
|
||||||
|
"jsonc",
|
||||||
|
"yaml"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ To develop and test the Elk package:
|
||||||
2. Ensure using the latest Node.js (16.x).
|
2. Ensure using the latest Node.js (16.x).
|
||||||
If you have [nvm](https://github.com/nvm-sh/nvm), you can run `nvm i` to install the required version.
|
If you have [nvm](https://github.com/nvm-sh/nvm), you can run `nvm i` to install the required version.
|
||||||
|
|
||||||
|
|
||||||
3. The package manager used to install and link dependencies must be [pnpm](https://pnpm.io/) v7. To use it you must first enable [Corepack](https://github.com/nodejs/corepack) by running `corepack enable`. (Note: on Linux in a standard Node 16+ environment, you should follow the instructions to install via Node's `corepack` rather than using the `curl` command)
|
3. The package manager used to install and link dependencies must be [pnpm](https://pnpm.io/) v7. To use it you must first enable [Corepack](https://github.com/nodejs/corepack) by running `corepack enable`. (Note: on Linux in a standard Node 16+ environment, you should follow the instructions to install via Node's `corepack` rather than using the `curl` command)
|
||||||
|
|
||||||
4. Check out a branch where you can work and commit your changes:
|
4. Check out a branch where you can work and commit your changes:
|
||||||
|
|
|
@ -52,7 +52,6 @@ One could put Elk behind popular reverse proxies with SSL Handling like Traefik,
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> The provided Dockerfile creates a container which will eventually run Elk as non-root user and create a persistent named Docker volume upon first start (if that volume does not yet exist). This volume is always created with root permission. Failing to change the permissions of ```/elk/data``` inside this volume to UID:GID 911 (as specified for Elk in the Dockerfile) will prevent Elk from storing it's config for user accounts. You either have to fix the permission in the created named volume, or mount a directory with the correct permission to ```/elk/data``` into the container.
|
> The provided Dockerfile creates a container which will eventually run Elk as non-root user and create a persistent named Docker volume upon first start (if that volume does not yet exist). This volume is always created with root permission. Failing to change the permissions of ```/elk/data``` inside this volume to UID:GID 911 (as specified for Elk in the Dockerfile) will prevent Elk from storing it's config for user accounts. You either have to fix the permission in the created named volume, or mount a directory with the correct permission to ```/elk/data``` into the container.
|
||||||
|
|
||||||
|
|
||||||
### Ecosystem
|
### Ecosystem
|
||||||
|
|
||||||
These are known deployments using Elk as an alternative Web client for Mastodon servers or as a base for other projects in the fediverse:
|
These are known deployments using Elk as an alternative Web client for Mastodon servers or as a base for other projects in the fediverse:
|
||||||
|
|
|
@ -43,7 +43,8 @@ watch(
|
||||||
}
|
}
|
||||||
|
|
||||||
account.value = undefined
|
account.value = undefined
|
||||||
}, { immediate: true, flush: 'post' },
|
},
|
||||||
|
{ immediate: true, flush: 'post' },
|
||||||
)
|
)
|
||||||
|
|
||||||
const userSettings = useUserSettings()
|
const userSettings = useUserSettings()
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { as = 'div', active } = defineProps<{ as: any; active: boolean }>()
|
const { as = 'div', active } = defineProps<{
|
||||||
|
as: any
|
||||||
|
active: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
const el = ref()
|
const el = ref()
|
||||||
|
|
||||||
watch(() => active, (active) => {
|
watch(() => active, (active) => {
|
||||||
|
|
|
@ -35,8 +35,8 @@ const showWarning = computed(() => {
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return isSupported
|
return isSupported
|
||||||
&& (!isSubscribed.value || !notificationPermission.value || notificationPermission.value === 'prompt')
|
&& (!isSubscribed.value || !notificationPermission.value || notificationPermission.value === 'prompt')
|
||||||
&& !(hiddenNotification.value[currentUser.value?.account?.acct ?? ''] === true)
|
&& !(hiddenNotification.value[currentUser.value?.account?.acct ?? ''])
|
||||||
})
|
})
|
||||||
|
|
||||||
async function saveSettings() {
|
async function saveSettings() {
|
||||||
|
|
|
@ -30,8 +30,14 @@ const draftState = useDraft(draftKey, initial)
|
||||||
const { draft } = draftState
|
const { draft } = draftState
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isExceedingAttachmentLimit, isUploading, failedAttachments, isOverDropZone,
|
isExceedingAttachmentLimit,
|
||||||
uploadAttachments, pickAttachments, setDescription, removeAttachment,
|
isUploading,
|
||||||
|
failedAttachments,
|
||||||
|
isOverDropZone,
|
||||||
|
uploadAttachments,
|
||||||
|
pickAttachments,
|
||||||
|
setDescription,
|
||||||
|
removeAttachment,
|
||||||
dropZoneRef,
|
dropZoneRef,
|
||||||
} = useUploadMediaAttachment(draft)
|
} = useUploadMediaAttachment(draft)
|
||||||
|
|
||||||
|
@ -68,7 +74,7 @@ function trimPollOptions() {
|
||||||
const trimmedOptions = draft.value.params.poll!.options.slice(0, indexLastNonEmpty + 1)
|
const trimmedOptions = draft.value.params.poll!.options.slice(0, indexLastNonEmpty + 1)
|
||||||
|
|
||||||
if (currentInstance.value?.configuration
|
if (currentInstance.value?.configuration
|
||||||
&& trimmedOptions.length >= currentInstance.value?.configuration?.polls.maxOptions)
|
&& trimmedOptions.length >= currentInstance.value?.configuration?.polls.maxOptions)
|
||||||
draft.value.params.poll!.options = trimmedOptions
|
draft.value.params.poll!.options = trimmedOptions
|
||||||
else
|
else
|
||||||
draft.value.params.poll!.options = [...trimmedOptions, '']
|
draft.value.params.poll!.options = [...trimmedOptions, '']
|
||||||
|
|
|
@ -18,12 +18,12 @@ useCommand({
|
||||||
scope: 'Settings',
|
scope: 'Settings',
|
||||||
|
|
||||||
name: () => props.text
|
name: () => props.text
|
||||||
?? (props.to
|
?? (props.to
|
||||||
? typeof props.to === 'string'
|
? typeof props.to === 'string'
|
||||||
? props.to
|
? props.to
|
||||||
: props.to.name
|
: props.to.name
|
||||||
: ''
|
: ''
|
||||||
),
|
),
|
||||||
description: () => props.description,
|
description: () => props.description,
|
||||||
icon: () => props.icon || '',
|
icon: () => props.icon || '',
|
||||||
visible: () => props.command && props.to,
|
visible: () => props.command && props.to,
|
||||||
|
|
|
@ -8,8 +8,7 @@ const dropdown = ref<any>()
|
||||||
|
|
||||||
const fieldIcons = computed(() =>
|
const fieldIcons = computed(() =>
|
||||||
Array.from({ length: maxAccountFieldCount.value }, (_, i) =>
|
Array.from({ length: maxAccountFieldCount.value }, (_, i) =>
|
||||||
getAccountFieldIcon(form.value.fieldsAttributes[i].name),
|
getAccountFieldIcon(form.value.fieldsAttributes[i].name)),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const fieldCount = computed(() => {
|
const fieldCount = computed(() => {
|
||||||
|
|
|
@ -42,7 +42,8 @@ watch(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
account.value = undefined
|
account.value = undefined
|
||||||
}, { immediate: true, flush: 'post' },
|
},
|
||||||
|
{ immediate: true, flush: 'post' },
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const props = defineProps<{ enabled?: boolean; filter?: boolean; isDM?: boolean; sensitiveNonSpoiler?: boolean }>()
|
const props = defineProps<{
|
||||||
|
enabled?: boolean
|
||||||
|
filter?: boolean
|
||||||
|
isDM?: boolean
|
||||||
|
sensitiveNonSpoiler?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
const expandSpoilers = computed(() => {
|
const expandSpoilers = computed(() => {
|
||||||
const expandCW = currentUser.value ? getExpandSpoilersByDefault(currentUser.value.account) : false
|
const expandCW = currentUser.value ? getExpandSpoilersByDefault(currentUser.value.account) : false
|
||||||
|
|
|
@ -170,7 +170,8 @@ export const useCommandRegistry = defineStore('command', () => {
|
||||||
const indexed = cmds.map((cmd, index) => ({ ...cmd, index }))
|
const indexed = cmds.map((cmd, index) => ({ ...cmd, index }))
|
||||||
|
|
||||||
const grouped = new Map<CommandScopeNames, CommandQueryResultItem[]>(
|
const grouped = new Map<CommandScopeNames, CommandQueryResultItem[]>(
|
||||||
scopes.map(scope => [scope, []]))
|
scopes.map(scope => [scope, []]),
|
||||||
|
)
|
||||||
for (const cmd of indexed) {
|
for (const cmd of indexed) {
|
||||||
const scope = cmd.scope ?? ''
|
const scope = cmd.scope ?? ''
|
||||||
grouped.get(scope)!.push({
|
grouped.get(scope)!.push({
|
||||||
|
|
|
@ -494,7 +494,10 @@ function _markdownProcess(value: string) {
|
||||||
|
|
||||||
let start = 0
|
let start = 0
|
||||||
while (true) {
|
while (true) {
|
||||||
let found: { match: RegExpMatchArray; replacer: (c: (string | Node)[]) => Node } | undefined
|
let found: {
|
||||||
|
match: RegExpMatchArray
|
||||||
|
replacer: (c: (string | Node)[]) => Node
|
||||||
|
} | undefined
|
||||||
|
|
||||||
for (const [re, replacer] of _markdownReplacements) {
|
for (const [re, replacer] of _markdownReplacements) {
|
||||||
re.lastIndex = start
|
re.lastIndex = start
|
||||||
|
|
|
@ -29,8 +29,10 @@ export function useHumanReadableNumber() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFormattedDateTime(value: MaybeRefOrGetter<string | number | Date | undefined | null>,
|
export function useFormattedDateTime(
|
||||||
options: Intl.DateTimeFormatOptions = { dateStyle: 'long', timeStyle: 'medium' }) {
|
value: MaybeRefOrGetter<string | number | Date | undefined | null>,
|
||||||
|
options: Intl.DateTimeFormatOptions = { dateStyle: 'long', timeStyle: 'medium' },
|
||||||
|
) {
|
||||||
const { locale } = useI18n()
|
const { locale } = useI18n()
|
||||||
const formatter = computed(() => Intl.DateTimeFormat(locale.value, options))
|
const formatter = computed(() => Intl.DateTimeFormat(locale.value, options))
|
||||||
return computed(() => {
|
return computed(() => {
|
||||||
|
|
|
@ -36,9 +36,11 @@ export function useMagicSequence(keys: string[]): ComputedRef<boolean> {
|
||||||
down = false
|
down = false
|
||||||
success.value = true
|
success.value = true
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
deep: true,
|
deep: true,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return computed(() => success.value)
|
return computed(() => success.value)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,23 +36,22 @@ export function usePublish(options: {
|
||||||
const { params, attachments } = draft.value
|
const { params, attachments } = draft.value
|
||||||
const firstEmptyInputIndex = params.poll?.options.findIndex(option => option.trim().length === 0)
|
const firstEmptyInputIndex = params.poll?.options.findIndex(option => option.trim().length === 0)
|
||||||
return isEmpty.value
|
return isEmpty.value
|
||||||
|| options.isUploading.value
|
|| options.isUploading.value
|
||||||
|| isSending.value
|
|| isSending.value
|
||||||
|| (attachments.length === 0 && !params.status)
|
|| (attachments.length === 0 && !params.status)
|
||||||
|| failedMessages.value.length > 0
|
|| failedMessages.value.length > 0
|
||||||
|| (attachments.length > 0 && params.poll !== null && params.poll !== undefined)
|
|| (attachments.length > 0 && params.poll !== null && params.poll !== undefined)
|
||||||
|| ((params.poll !== null && params.poll !== undefined)
|
|| ((params.poll !== null && params.poll !== undefined)
|
||||||
&& (
|
&& (
|
||||||
(firstEmptyInputIndex !== -1
|
(firstEmptyInputIndex !== -1
|
||||||
&& firstEmptyInputIndex !== params.poll.options.length - 1
|
&& firstEmptyInputIndex !== params.poll.options.length - 1
|
||||||
)
|
)
|
||||||
|| params.poll.options.findLastIndex(option => option.trim().length > 0) + 1 < 2
|
|| params.poll.options.findLastIndex(option => option.trim().length > 0) + 1 < 2
|
||||||
|| (new Set(params.poll.options).size !== params.poll.options.length)
|
|| (new Set(params.poll.options).size !== params.poll.options.length)
|
||||||
|| (currentInstance.value?.configuration?.polls.maxCharactersPerOption !== undefined
|
|| (currentInstance.value?.configuration?.polls.maxCharactersPerOption !== undefined
|
||||||
&& params.poll.options.find(option => option.length > currentInstance.value!.configuration!.polls.maxCharactersPerOption) !== undefined
|
&& params.poll.options.find(option => option.length > currentInstance.value!.configuration!.polls.maxCharactersPerOption) !== undefined
|
||||||
)
|
)
|
||||||
)
|
))
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(draft, () => {
|
watch(draft, () => {
|
||||||
|
@ -164,7 +163,7 @@ export function useUploadMediaAttachment(draft: Ref<Draft>) {
|
||||||
|
|
||||||
const maxPixels = computed(() => {
|
const maxPixels = computed(() => {
|
||||||
return currentInstance.value?.configuration?.mediaAttachments?.imageMatrixLimit
|
return currentInstance.value?.configuration?.mediaAttachments?.imageMatrixLimit
|
||||||
?? 4096 ** 2
|
?? 4096 ** 2
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadImage = (inputFile: Blob) => new Promise<HTMLImageElement>((resolve, reject) => {
|
const loadImage = (inputFile: Blob) => new Promise<HTMLImageElement>((resolve, reject) => {
|
||||||
|
|
|
@ -20,7 +20,7 @@ function getDefaultVisibility(currentVisibility: mastodon.v1.StatusVisibility) {
|
||||||
// the post more private than the replying to post
|
// the post more private than the replying to post
|
||||||
const preferredVisibility = currentUser.value?.account.source.privacy || 'public'
|
const preferredVisibility = currentUser.value?.account.source.privacy || 'public'
|
||||||
return ALL_VISIBILITY.indexOf(currentVisibility)
|
return ALL_VISIBILITY.indexOf(currentVisibility)
|
||||||
> ALL_VISIBILITY.indexOf(preferredVisibility)
|
> ALL_VISIBILITY.indexOf(preferredVisibility)
|
||||||
? currentVisibility
|
? currentVisibility
|
||||||
: preferredVisibility
|
: preferredVisibility
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,12 @@ export async function translateText(text: string, from: string | null | undefine
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
const translations = new WeakMap<mastodon.v1.Status | mastodon.v1.StatusEdit, { visible: boolean; text: string; success: boolean; error: string }>()
|
const translations = new WeakMap<mastodon.v1.Status | mastodon.v1.StatusEdit, {
|
||||||
|
visible: boolean
|
||||||
|
text: string
|
||||||
|
success: boolean
|
||||||
|
error: string
|
||||||
|
}>()
|
||||||
|
|
||||||
export function useTranslation(status: mastodon.v1.Status | mastodon.v1.StatusEdit, to: string) {
|
export function useTranslation(status: mastodon.v1.Status | mastodon.v1.StatusEdit, to: string) {
|
||||||
if (!translations.has(status))
|
if (!translations.has(status))
|
||||||
|
|
|
@ -6,10 +6,12 @@ import type {
|
||||||
} from '~/composables/push-notifications/types'
|
} from '~/composables/push-notifications/types'
|
||||||
import { PushSubscriptionError } from '~/composables/push-notifications/types'
|
import { PushSubscriptionError } from '~/composables/push-notifications/types'
|
||||||
|
|
||||||
export async function createPushSubscription(user: RequiredUserLogin,
|
export async function createPushSubscription(
|
||||||
|
user: RequiredUserLogin,
|
||||||
notificationData: CreatePushNotification,
|
notificationData: CreatePushNotification,
|
||||||
policy: mastodon.v1.WebPushSubscriptionPolicy = 'all',
|
policy: mastodon.v1.WebPushSubscriptionPolicy = 'all',
|
||||||
force = false): Promise<mastodon.v1.WebPushSubscription | undefined> {
|
force = false,
|
||||||
|
): Promise<mastodon.v1.WebPushSubscription | undefined> {
|
||||||
const { server: serverEndpoint, vapidKey } = user
|
const { server: serverEndpoint, vapidKey } = user
|
||||||
|
|
||||||
return await getRegistration()
|
return await getRegistration()
|
||||||
|
|
|
@ -87,7 +87,10 @@ export function usePushManager() {
|
||||||
|
|
||||||
currentUser.value.pushSubscription = await createPushSubscription(
|
currentUser.value.pushSubscription = await createPushSubscription(
|
||||||
{
|
{
|
||||||
pushSubscription, server, token, vapidKey,
|
pushSubscription,
|
||||||
|
server,
|
||||||
|
token,
|
||||||
|
vapidKey,
|
||||||
},
|
},
|
||||||
notificationData ?? {
|
notificationData ?? {
|
||||||
alerts: {
|
alerts: {
|
||||||
|
|
|
@ -5,7 +5,10 @@ const highlighter = ref<Highlighter>()
|
||||||
const registeredLang = ref(new Map<string, boolean>())
|
const registeredLang = ref(new Map<string, boolean>())
|
||||||
let shikiImport: Promise<void> | undefined
|
let shikiImport: Promise<void> | undefined
|
||||||
|
|
||||||
export function useHighlighter(lang: Lang): { promise?: Promise<void>; highlighter?: Highlighter } {
|
export function useHighlighter(lang: Lang): {
|
||||||
|
promise?: Promise<void>
|
||||||
|
highlighter?: Highlighter
|
||||||
|
} {
|
||||||
if (!shikiImport) {
|
if (!shikiImport) {
|
||||||
shikiImport = import('shiki')
|
shikiImport = import('shiki')
|
||||||
.then(async ({ getHighlighter }) => {
|
.then(async ({ getHighlighter }) => {
|
||||||
|
|
|
@ -16,7 +16,11 @@ declare module '@tiptap/core' {
|
||||||
/**
|
/**
|
||||||
* Insert a custom emoji.
|
* Insert a custom emoji.
|
||||||
*/
|
*/
|
||||||
insertCustomEmoji: (options: { src: string; alt?: string; title?: string }) => ReturnType
|
insertCustomEmoji: (options: {
|
||||||
|
src: string
|
||||||
|
alt?: string
|
||||||
|
title?: string
|
||||||
|
}) => ReturnType
|
||||||
/**
|
/**
|
||||||
* Insert a emoji.
|
* Insert a emoji.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -20,8 +20,10 @@ function wrapHandler<T extends (...args: any[]) => any>(handler: T): T {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function createEmojiRule<NR extends typeof nodeInputRule | typeof nodePasteRule>(nodeRule: NR,
|
function createEmojiRule<NR extends typeof nodeInputRule | typeof nodePasteRule>(
|
||||||
type: Parameters<NR>[0]['type']): ReturnType<NR>[] {
|
nodeRule: NR,
|
||||||
|
type: Parameters<NR>[0]['type'],
|
||||||
|
): ReturnType<NR>[] {
|
||||||
const rule = nodeRule({
|
const rule = nodeRule({
|
||||||
find: emojiRegEx as RegExp,
|
find: emojiRegEx as RegExp,
|
||||||
type,
|
type,
|
||||||
|
@ -86,10 +88,10 @@ export const TiptapPluginEmoji = Node.create({
|
||||||
find: InputRuleFinder
|
find: InputRuleFinder
|
||||||
type: NodeType
|
type: NodeType
|
||||||
getAttributes?:
|
getAttributes?:
|
||||||
| Record<string, any>
|
| Record<string, any>
|
||||||
| ((match: ExtendedRegExpMatchArray) => Record<string, any>)
|
| ((match: ExtendedRegExpMatchArray) => Record<string, any>)
|
||||||
| false
|
| false
|
||||||
| null
|
| null
|
||||||
}) {
|
}) {
|
||||||
return new InputRule({
|
return new InputRule({
|
||||||
find: config.find,
|
find: config.find,
|
||||||
|
|
|
@ -14,7 +14,6 @@ export default defineI18nConfig(() => {
|
||||||
missingWarn: true,
|
missingWarn: true,
|
||||||
datetimeFormats,
|
datetimeFormats,
|
||||||
numberFormats,
|
numberFormats,
|
||||||
// eslint-disable-next-line @typescript-eslint/comma-dangle
|
pluralRules,
|
||||||
pluralRules
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,10 +14,10 @@ css({
|
||||||
gap: '0.5rem',
|
gap: '0.5rem',
|
||||||
fontSize: '1.5rem',
|
fontSize: '1.5rem',
|
||||||
},
|
},
|
||||||
img: {
|
'img': {
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
aspectRatio: '1/1',
|
aspectRatio: '1/1',
|
||||||
height: '2.5rem'
|
height: '2.5rem',
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -61,8 +61,8 @@ async function copyToClipboard() {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText([
|
await navigator.clipboard.writeText([
|
||||||
`# ${localeTitle.value}`,
|
`# ${localeTitle.value}`,
|
||||||
(localeTab.value === 'missing' ? missingEntries.value : outdatedEntries.value).join('\n')].join('\n'),
|
(localeTab.value === 'missing' ? missingEntries.value : outdatedEntries.value).join('\n'),
|
||||||
)
|
].join('\n'))
|
||||||
copied.value = true
|
copied.value = true
|
||||||
setTimeout(() => copied.value = false, 750)
|
setTimeout(() => copied.value = false, 750)
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,6 @@ You can think of the service as something similar to Twitter, but with some key
|
||||||
|
|
||||||
For more details about Mastodon, see their [website](https://joinmastodon.org/), [blog](https://blog.joinmastodon.org), [docs](https://docs.joinmastodon.org), and [GitHub Repository](https://github.com/mastodon/mastodon).
|
For more details about Mastodon, see their [website](https://joinmastodon.org/), [blog](https://blog.joinmastodon.org), [docs](https://docs.joinmastodon.org), and [GitHub Repository](https://github.com/mastodon/mastodon).
|
||||||
|
|
||||||
|
|
||||||
## What is a Mastodon Client?
|
## What is a Mastodon Client?
|
||||||
|
|
||||||
A Mastodon Client is software that serves up the posts and activities from a Mastodon server using the [Mastodon API](https://docs.joinmastodon.org/api/).
|
A Mastodon Client is software that serves up the posts and activities from a Mastodon server using the [Mastodon API](https://docs.joinmastodon.org/api/).
|
||||||
|
@ -80,7 +79,6 @@ Desktop
|
||||||
[TheDesk](https://thedesk.top/en/), [Whalebird](https://whalebird.social/en), or [Sengi](https://nicolasconstant.github.io/sengi/)
|
[TheDesk](https://thedesk.top/en/), [Whalebird](https://whalebird.social/en), or [Sengi](https://nicolasconstant.github.io/sengi/)
|
||||||
::
|
::
|
||||||
|
|
||||||
|
|
||||||
All of these apps provide some combination of the features a typical Mastodon user expects for their account.
|
All of these apps provide some combination of the features a typical Mastodon user expects for their account.
|
||||||
|
|
||||||
### Browser-based Mastodon Clients
|
### Browser-based Mastodon Clients
|
||||||
|
|
|
@ -67,7 +67,6 @@ Options include:
|
||||||
Elk reorders timeline posts that are connected to each other to display them in a connected thread in the timelines.
|
Elk reorders timeline posts that are connected to each other to display them in a connected thread in the timelines.
|
||||||
This helps keep the conversation context for a post and its replies.
|
This helps keep the conversation context for a post and its replies.
|
||||||
|
|
||||||
|
|
||||||
## Improved notifications
|
## Improved notifications
|
||||||
|
|
||||||
Elk groups boosts and likes to the same post when they occur together in the notification history.
|
Elk groups boosts and likes to the same post when they occur together in the notification history.
|
||||||
|
@ -76,7 +75,6 @@ This greatly simplifies your notification history.
|
||||||
|
|
||||||
<!-- ## GitHub HTML cards -->
|
<!-- ## GitHub HTML cards -->
|
||||||
|
|
||||||
|
|
||||||
<!-- - markdown support
|
<!-- - markdown support
|
||||||
- GitHub HTML cards
|
- GitHub HTML cards
|
||||||
- and so on... -->
|
- and so on... -->
|
||||||
|
|
|
@ -35,4 +35,3 @@ When we update this privacy notice, the updated version will be indicated by a n
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
If you have questions or comments about this notice, you may raise an issue at https://github.com/elk-zone/elk.
|
If you have questions or comments about this notice, you may raise an issue at https://github.com/elk-zone/elk.
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
[build]
|
[build]
|
||||||
publish = "dist"
|
publish = "dist"
|
||||||
command = "pnpm generate"
|
command = "pnpm generate"
|
||||||
|
|
||||||
# Allow previewing docs
|
# Allow previewing docs
|
||||||
[[redirects]]
|
[[redirects]]
|
||||||
from = "/docs/*"
|
from = "/docs/*"
|
||||||
to = "/:splat"
|
to = "/:splat"
|
||||||
status = 200
|
status = 200
|
||||||
force = true
|
force = true
|
||||||
|
|
37
eslint.config.js
Normal file
37
eslint.config.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// @ts-check
|
||||||
|
import antfu from '@antfu/eslint-config'
|
||||||
|
|
||||||
|
export default await antfu(
|
||||||
|
{
|
||||||
|
unocss: false,
|
||||||
|
vue: {
|
||||||
|
overrides: {
|
||||||
|
'vue/no-restricted-syntax': ['error', {
|
||||||
|
selector: 'VElement[name=\'a\']',
|
||||||
|
message: 'Use NuxtLink instead.',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ignores: [
|
||||||
|
'public/**',
|
||||||
|
'public-dev/**',
|
||||||
|
'public-staging/**',
|
||||||
|
'https-dev-config/**',
|
||||||
|
'elk-translation-status.json',
|
||||||
|
'docs/translation-status.json',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
// TODO: migrate all process reference to `import.meta.env` and remove this rule
|
||||||
|
'node/prefer-global/process': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Sort local files
|
||||||
|
{
|
||||||
|
files: ['locales/**.json'],
|
||||||
|
rules: {
|
||||||
|
'jsonc/sort-keys': 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
|
@ -28,7 +28,8 @@ export default defineNuxtModule({
|
||||||
.map(async (l) => {
|
.map(async (l) => {
|
||||||
const exists = await isFile(resolver.resolve(`../node_modules/@emoji-mart/data/i18n/${l}.json`))
|
const exists = await isFile(resolver.resolve(`../node_modules/@emoji-mart/data/i18n/${l}.json`))
|
||||||
return [l, exists] as [code: string, exists: boolean]
|
return [l, exists] as [code: string, exists: boolean]
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
.then(l => l.filter(l => l[1]).map(l => l[0]))
|
.then(l => l.filter(l => l[1]).map(l => l[0]))
|
||||||
const switchStmt = locales.filter(l => l[1]).map((l) => {
|
const switchStmt = locales.filter(l => l[1]).map((l) => {
|
||||||
return `
|
return `
|
||||||
|
|
|
@ -10,8 +10,8 @@ export function configurePWAOptions(options: Partial<VitePWAOptions>, nuxt: Nuxt
|
||||||
|
|
||||||
let config: Partial<
|
let config: Partial<
|
||||||
import('workbox-build').BasePartial
|
import('workbox-build').BasePartial
|
||||||
& import('workbox-build').GlobPartial
|
& import('workbox-build').GlobPartial
|
||||||
& import('workbox-build').RequiredGlobDirectoryPartial
|
& import('workbox-build').RequiredGlobDirectoryPartial
|
||||||
>
|
>
|
||||||
|
|
||||||
if (options.strategies === 'injectManifest') {
|
if (options.strategies === 'injectManifest') {
|
||||||
|
|
|
@ -36,7 +36,8 @@ export default defineNuxtPlugin(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
needRefresh, updateServiceWorker,
|
needRefresh,
|
||||||
|
updateServiceWorker,
|
||||||
} = useRegisterSW({
|
} = useRegisterSW({
|
||||||
immediate: true,
|
immediate: true,
|
||||||
onRegisterError() {
|
onRegisterError() {
|
||||||
|
|
|
@ -13,7 +13,6 @@ export default defineNuxtPlugin(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('unhandledrejection', err =>
|
window.addEventListener('unhandledrejection', err =>
|
||||||
log.error(err.reason),
|
log.error(err.reason))
|
||||||
)
|
|
||||||
window.addEventListener('error', err => log.error(err.error), true)
|
window.addEventListener('error', err => log.error(err.error), true)
|
||||||
})
|
})
|
||||||
|
|
12
netlify.toml
12
netlify.toml
|
@ -1,10 +1,10 @@
|
||||||
[build]
|
[build]
|
||||||
publish = "dist"
|
publish = "dist"
|
||||||
command = "pnpm run build"
|
command = "pnpm run build"
|
||||||
|
|
||||||
# Redirect to Discord server
|
# Redirect to Discord server
|
||||||
[[redirects]]
|
[[redirects]]
|
||||||
from = "https://chat.elk.zone"
|
from = "https://chat.elk.zone"
|
||||||
to = "https://discord.gg/vAZSDU9J"
|
to = "https://discord.gg/vAZSDU9J"
|
||||||
status = 301
|
status = 301
|
||||||
force = true
|
force = true
|
||||||
|
|
|
@ -94,16 +94,42 @@ export default defineNuxtConfig({
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: [
|
include: [
|
||||||
'@tiptap/vue-3', 'string-length', 'vue-virtual-scroller', 'emoji-mart', 'iso-639-1',
|
'@tiptap/vue-3',
|
||||||
'@tiptap/extension-placeholder', '@tiptap/extension-document', '@tiptap/extension-paragraph',
|
'string-length',
|
||||||
'@tiptap/extension-text', '@tiptap/extension-mention', '@tiptap/extension-hard-break',
|
'vue-virtual-scroller',
|
||||||
'@tiptap/extension-bold', '@tiptap/extension-italic', '@tiptap/extension-code',
|
'emoji-mart',
|
||||||
'@tiptap/extension-history', 'prosemirror-state', 'browser-fs-access', 'blurhash',
|
'iso-639-1',
|
||||||
'@vueuse/integrations/useFocusTrap', '@tiptap/extension-code-block', 'prosemirror-highlight',
|
'@tiptap/extension-placeholder',
|
||||||
'@tiptap/core', 'tippy.js', 'prosemirror-highlight/shiki', '@fnando/sparkline',
|
'@tiptap/extension-document',
|
||||||
'@vueuse/gesture', 'github-reserved-names', 'file-saver', 'slimeform', 'vue-advanced-cropper',
|
'@tiptap/extension-paragraph',
|
||||||
'workbox-window', 'workbox-precaching', 'workbox-routing', 'workbox-cacheable-response',
|
'@tiptap/extension-text',
|
||||||
'workbox-strategies', 'workbox-expiration',
|
'@tiptap/extension-mention',
|
||||||
|
'@tiptap/extension-hard-break',
|
||||||
|
'@tiptap/extension-bold',
|
||||||
|
'@tiptap/extension-italic',
|
||||||
|
'@tiptap/extension-code',
|
||||||
|
'@tiptap/extension-history',
|
||||||
|
'prosemirror-state',
|
||||||
|
'browser-fs-access',
|
||||||
|
'blurhash',
|
||||||
|
'@vueuse/integrations/useFocusTrap',
|
||||||
|
'@tiptap/extension-code-block',
|
||||||
|
'prosemirror-highlight',
|
||||||
|
'@tiptap/core',
|
||||||
|
'tippy.js',
|
||||||
|
'prosemirror-highlight/shiki',
|
||||||
|
'@fnando/sparkline',
|
||||||
|
'@vueuse/gesture',
|
||||||
|
'github-reserved-names',
|
||||||
|
'file-saver',
|
||||||
|
'slimeform',
|
||||||
|
'vue-advanced-cropper',
|
||||||
|
'workbox-window',
|
||||||
|
'workbox-precaching',
|
||||||
|
'workbox-routing',
|
||||||
|
'workbox-cacheable-response',
|
||||||
|
'workbox-strategies',
|
||||||
|
'workbox-expiration',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -249,7 +275,8 @@ export default defineNuxtConfig({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
|
||||||
|
// eslint-disable-next-line ts/prefer-ts-expect-error
|
||||||
// @ts-ignore nuxt-security is conditional
|
// @ts-ignore nuxt-security is conditional
|
||||||
security: {
|
security: {
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -297,7 +324,7 @@ export default defineNuxtConfig({
|
||||||
})
|
})
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
// eslint-disable-next-line ts/no-namespace
|
||||||
namespace NodeJS {
|
namespace NodeJS {
|
||||||
interface Process {
|
interface Process {
|
||||||
mock?: Record<string, any>
|
mock?: Record<string, any>
|
||||||
|
|
|
@ -112,7 +112,7 @@
|
||||||
"ws": "^8.15.1"
|
"ws": "^8.15.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "^0.43.1",
|
"@antfu/eslint-config": "^2.8.0",
|
||||||
"@antfu/ni": "^0.21.12",
|
"@antfu/ni": "^0.21.12",
|
||||||
"@types/chroma-js": "^2.4.4",
|
"@types/chroma-js": "^2.4.4",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
|
@ -124,10 +124,12 @@
|
||||||
"@types/wicg-file-system-access": "^2020.9.8",
|
"@types/wicg-file-system-access": "^2020.9.8",
|
||||||
"@types/ws": "^8.5.10",
|
"@types/ws": "^8.5.10",
|
||||||
"@unlazy/nuxt": "^0.11.1",
|
"@unlazy/nuxt": "^0.11.1",
|
||||||
|
"@unocss/eslint-config": "^0.58.5",
|
||||||
"@vue/test-utils": "^2.4.4",
|
"@vue/test-utils": "^2.4.4",
|
||||||
"bumpp": "^9.4.0",
|
"bumpp": "^9.4.0",
|
||||||
"consola": "^3.2.3",
|
"consola": "^3.2.3",
|
||||||
"eslint": "^8.49.0",
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-plugin-format": "^0.1.0",
|
||||||
"flat": "^5.0.2",
|
"flat": "^5.0.2",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"lint-staged": "^14.0.1",
|
"lint-staged": "^14.0.1",
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
useHydratedHead({
|
useHydratedHead({
|
||||||
|
|
995
pnpm-lock.yaml
995
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -32,7 +32,10 @@ async function handleSharedTarget(event: FetchEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendShareTargetMessage(client: Client, data: FormData) {
|
async function sendShareTargetMessage(client: Client, data: FormData) {
|
||||||
const sharedData: { textParts: string[]; files: File[] } = {
|
const sharedData: {
|
||||||
|
textParts: string[]
|
||||||
|
files: File[]
|
||||||
|
} = {
|
||||||
textParts: [],
|
textParts: [],
|
||||||
files: [],
|
files: [],
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,8 +67,8 @@ if (import.meta.env.PROD) {
|
||||||
registerRoute(
|
registerRoute(
|
||||||
({ sameOrigin, request, url }) =>
|
({ sameOrigin, request, url }) =>
|
||||||
sameOrigin
|
sameOrigin
|
||||||
&& request.destination === 'image'
|
&& request.destination === 'image'
|
||||||
&& url.pathname.startsWith('/emojis/'),
|
&& url.pathname.startsWith('/emojis/'),
|
||||||
new StaleWhileRevalidate({
|
new StaleWhileRevalidate({
|
||||||
cacheName: 'elk-emojis',
|
cacheName: 'elk-emojis',
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
Loading…
Reference in a new issue