chore: bump to eslint-config v2.8.0 (#2651)

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
This commit is contained in:
Joaquín Sánchez 2024-03-05 15:48:58 +01:00 committed by GitHub
parent 62f70250d5
commit 9da77637b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 896 additions and 511 deletions

View file

@ -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

View file

@ -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
View file

@ -5,10 +5,6 @@
"unmute",
"unstorage"
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.formatOnSave": false,
"files.associations": {
"*.css": "postcss"
},
@ -23,7 +19,44 @@
"i18n-ally.preferredDelimiter": "_",
"i18n-ally.sortKeys": true,
"i18n-ally.sourceLanguage": "en",
// Enable the ESlint flat config support
"eslint.experimental.useFlatConfig": true,
// Disable the default formatter, use eslint instead
"prettier.enable": false,
"volar.completion.preferredTagNameCase": "pascal",
"volar.completion.preferredAttrNameCase": "kebab"
"editor.formatOnSave": false,
// 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"
]
}

View file

@ -21,7 +21,6 @@ To develop and test the Elk package:
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.
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:

View file

@ -52,7 +52,6 @@ One could put Elk behind popular reverse proxies with SSL Handling like Traefik,
> [!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.
### 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:

View file

@ -43,7 +43,8 @@ watch(
}
account.value = undefined
}, { immediate: true, flush: 'post' },
},
{ immediate: true, flush: 'post' },
)
const userSettings = useUserSettings()

View file

@ -1,5 +1,9 @@
<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()
watch(() => active, (active) => {

View file

@ -35,8 +35,8 @@ const showWarning = computed(() => {
return false
return isSupported
&& (!isSubscribed.value || !notificationPermission.value || notificationPermission.value === 'prompt')
&& !(hiddenNotification.value[currentUser.value?.account?.acct ?? ''] === true)
&& (!isSubscribed.value || !notificationPermission.value || notificationPermission.value === 'prompt')
&& !(hiddenNotification.value[currentUser.value?.account?.acct ?? ''])
})
async function saveSettings() {

View file

@ -30,8 +30,14 @@ const draftState = useDraft(draftKey, initial)
const { draft } = draftState
const {
isExceedingAttachmentLimit, isUploading, failedAttachments, isOverDropZone,
uploadAttachments, pickAttachments, setDescription, removeAttachment,
isExceedingAttachmentLimit,
isUploading,
failedAttachments,
isOverDropZone,
uploadAttachments,
pickAttachments,
setDescription,
removeAttachment,
dropZoneRef,
} = useUploadMediaAttachment(draft)
@ -68,7 +74,7 @@ function trimPollOptions() {
const trimmedOptions = draft.value.params.poll!.options.slice(0, indexLastNonEmpty + 1)
if (currentInstance.value?.configuration
&& trimmedOptions.length >= currentInstance.value?.configuration?.polls.maxOptions)
&& trimmedOptions.length >= currentInstance.value?.configuration?.polls.maxOptions)
draft.value.params.poll!.options = trimmedOptions
else
draft.value.params.poll!.options = [...trimmedOptions, '']

View file

@ -18,12 +18,12 @@ useCommand({
scope: 'Settings',
name: () => props.text
?? (props.to
? typeof props.to === 'string'
? props.to
: props.to.name
: ''
),
?? (props.to
? typeof props.to === 'string'
? props.to
: props.to.name
: ''
),
description: () => props.description,
icon: () => props.icon || '',
visible: () => props.command && props.to,

View file

@ -8,8 +8,7 @@ const dropdown = ref<any>()
const fieldIcons = computed(() =>
Array.from({ length: maxAccountFieldCount.value }, (_, i) =>
getAccountFieldIcon(form.value.fieldsAttributes[i].name),
),
getAccountFieldIcon(form.value.fieldsAttributes[i].name)),
)
const fieldCount = computed(() => {

View file

@ -42,7 +42,8 @@ watch(
return
}
account.value = undefined
}, { immediate: true, flush: 'post' },
},
{ immediate: true, flush: 'post' },
)
</script>

View file

@ -1,5 +1,10 @@
<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 expandCW = currentUser.value ? getExpandSpoilersByDefault(currentUser.value.account) : false

View file

@ -170,7 +170,8 @@ export const useCommandRegistry = defineStore('command', () => {
const indexed = cmds.map((cmd, index) => ({ ...cmd, index }))
const grouped = new Map<CommandScopeNames, CommandQueryResultItem[]>(
scopes.map(scope => [scope, []]))
scopes.map(scope => [scope, []]),
)
for (const cmd of indexed) {
const scope = cmd.scope ?? ''
grouped.get(scope)!.push({

View file

@ -494,7 +494,10 @@ function _markdownProcess(value: string) {
let start = 0
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) {
re.lastIndex = start

View file

@ -29,8 +29,10 @@ export function useHumanReadableNumber() {
}
}
export function useFormattedDateTime(value: MaybeRefOrGetter<string | number | Date | undefined | null>,
options: Intl.DateTimeFormatOptions = { dateStyle: 'long', timeStyle: 'medium' }) {
export function useFormattedDateTime(
value: MaybeRefOrGetter<string | number | Date | undefined | null>,
options: Intl.DateTimeFormatOptions = { dateStyle: 'long', timeStyle: 'medium' },
) {
const { locale } = useI18n()
const formatter = computed(() => Intl.DateTimeFormat(locale.value, options))
return computed(() => {

View file

@ -36,9 +36,11 @@ export function useMagicSequence(keys: string[]): ComputedRef<boolean> {
down = false
success.value = true
}
}, {
},
{
deep: true,
})
},
)
return computed(() => success.value)
}

View file

@ -36,23 +36,22 @@ export function usePublish(options: {
const { params, attachments } = draft.value
const firstEmptyInputIndex = params.poll?.options.findIndex(option => option.trim().length === 0)
return isEmpty.value
|| options.isUploading.value
|| isSending.value
|| (attachments.length === 0 && !params.status)
|| failedMessages.value.length > 0
|| (attachments.length > 0 && params.poll !== null && params.poll !== undefined)
|| ((params.poll !== null && params.poll !== undefined)
&& (
(firstEmptyInputIndex !== -1
&& firstEmptyInputIndex !== params.poll.options.length - 1
)
|| params.poll.options.findLastIndex(option => option.trim().length > 0) + 1 < 2
|| (new Set(params.poll.options).size !== params.poll.options.length)
|| (currentInstance.value?.configuration?.polls.maxCharactersPerOption !== undefined
&& params.poll.options.find(option => option.length > currentInstance.value!.configuration!.polls.maxCharactersPerOption) !== undefined
)
)
)
|| options.isUploading.value
|| isSending.value
|| (attachments.length === 0 && !params.status)
|| failedMessages.value.length > 0
|| (attachments.length > 0 && params.poll !== null && params.poll !== undefined)
|| ((params.poll !== null && params.poll !== undefined)
&& (
(firstEmptyInputIndex !== -1
&& firstEmptyInputIndex !== params.poll.options.length - 1
)
|| params.poll.options.findLastIndex(option => option.trim().length > 0) + 1 < 2
|| (new Set(params.poll.options).size !== params.poll.options.length)
|| (currentInstance.value?.configuration?.polls.maxCharactersPerOption !== undefined
&& params.poll.options.find(option => option.length > currentInstance.value!.configuration!.polls.maxCharactersPerOption) !== undefined
)
))
})
watch(draft, () => {
@ -164,7 +163,7 @@ export function useUploadMediaAttachment(draft: Ref<Draft>) {
const maxPixels = computed(() => {
return currentInstance.value?.configuration?.mediaAttachments?.imageMatrixLimit
?? 4096 ** 2
?? 4096 ** 2
})
const loadImage = (inputFile: Blob) => new Promise<HTMLImageElement>((resolve, reject) => {

View file

@ -20,7 +20,7 @@ function getDefaultVisibility(currentVisibility: mastodon.v1.StatusVisibility) {
// the post more private than the replying to post
const preferredVisibility = currentUser.value?.account.source.privacy || 'public'
return ALL_VISIBILITY.indexOf(currentVisibility)
> ALL_VISIBILITY.indexOf(preferredVisibility)
> ALL_VISIBILITY.indexOf(preferredVisibility)
? currentVisibility
: preferredVisibility
}

View file

@ -95,7 +95,12 @@ export async function translateText(text: string, from: string | null | undefine
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) {
if (!translations.has(status))

View file

@ -6,10 +6,12 @@ import type {
} 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,
policy: mastodon.v1.WebPushSubscriptionPolicy = 'all',
force = false): Promise<mastodon.v1.WebPushSubscription | undefined> {
force = false,
): Promise<mastodon.v1.WebPushSubscription | undefined> {
const { server: serverEndpoint, vapidKey } = user
return await getRegistration()

View file

@ -87,7 +87,10 @@ export function usePushManager() {
currentUser.value.pushSubscription = await createPushSubscription(
{
pushSubscription, server, token, vapidKey,
pushSubscription,
server,
token,
vapidKey,
},
notificationData ?? {
alerts: {

View file

@ -5,7 +5,10 @@ const highlighter = ref<Highlighter>()
const registeredLang = ref(new Map<string, boolean>())
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) {
shikiImport = import('shiki')
.then(async ({ getHighlighter }) => {

View file

@ -16,7 +16,11 @@ declare module '@tiptap/core' {
/**
* 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.
*/

View file

@ -20,8 +20,10 @@ function wrapHandler<T extends (...args: any[]) => any>(handler: T): T {
})
}
function createEmojiRule<NR extends typeof nodeInputRule | typeof nodePasteRule>(nodeRule: NR,
type: Parameters<NR>[0]['type']): ReturnType<NR>[] {
function createEmojiRule<NR extends typeof nodeInputRule | typeof nodePasteRule>(
nodeRule: NR,
type: Parameters<NR>[0]['type'],
): ReturnType<NR>[] {
const rule = nodeRule({
find: emojiRegEx as RegExp,
type,
@ -86,10 +88,10 @@ export const TiptapPluginEmoji = Node.create({
find: InputRuleFinder
type: NodeType
getAttributes?:
| Record<string, any>
| ((match: ExtendedRegExpMatchArray) => Record<string, any>)
| false
| null
| Record<string, any>
| ((match: ExtendedRegExpMatchArray) => Record<string, any>)
| false
| null
}) {
return new InputRule({
find: config.find,

View file

@ -14,7 +14,6 @@ export default defineI18nConfig(() => {
missingWarn: true,
datetimeFormats,
numberFormats,
// eslint-disable-next-line @typescript-eslint/comma-dangle
pluralRules
pluralRules,
}
})

View file

@ -14,10 +14,10 @@ css({
gap: '0.5rem',
fontSize: '1.5rem',
},
img: {
'img': {
flexShrink: 0,
aspectRatio: '1/1',
height: '2.5rem'
}
height: '2.5rem',
},
})
</style>

View file

@ -61,8 +61,8 @@ async function copyToClipboard() {
try {
await navigator.clipboard.writeText([
`# ${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
setTimeout(() => copied.value = false, 750)
}

View file

@ -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).
## 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/).
@ -80,7 +79,6 @@ Desktop
[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.
### Browser-based Mastodon Clients

View file

@ -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.
This helps keep the conversation context for a post and its replies.
## Improved notifications
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 -->
<!-- - markdown support
- GitHub HTML cards
- and so on... -->

View file

@ -35,4 +35,3 @@ When we update this privacy notice, the updated version will be indicated by a n
## Contact
If you have questions or comments about this notice, you may raise an issue at https://github.com/elk-zone/elk.

View file

@ -1,10 +1,10 @@
[build]
publish = "dist"
command = "pnpm generate"
publish = "dist"
command = "pnpm generate"
# Allow previewing docs
[[redirects]]
from = "/docs/*"
to = "/:splat"
status = 200
force = true
from = "/docs/*"
to = "/:splat"
status = 200
force = true

37
eslint.config.js Normal file
View 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',
},
},
)

View file

@ -28,7 +28,8 @@ export default defineNuxtModule({
.map(async (l) => {
const exists = await isFile(resolver.resolve(`../node_modules/@emoji-mart/data/i18n/${l}.json`))
return [l, exists] as [code: string, exists: boolean]
}))
}),
)
.then(l => l.filter(l => l[1]).map(l => l[0]))
const switchStmt = locales.filter(l => l[1]).map((l) => {
return `

View file

@ -10,8 +10,8 @@ export function configurePWAOptions(options: Partial<VitePWAOptions>, nuxt: Nuxt
let config: Partial<
import('workbox-build').BasePartial
& import('workbox-build').GlobPartial
& import('workbox-build').RequiredGlobDirectoryPartial
& import('workbox-build').GlobPartial
& import('workbox-build').RequiredGlobDirectoryPartial
>
if (options.strategies === 'injectManifest') {

View file

@ -36,7 +36,8 @@ export default defineNuxtPlugin(() => {
}
const {
needRefresh, updateServiceWorker,
needRefresh,
updateServiceWorker,
} = useRegisterSW({
immediate: true,
onRegisterError() {

View file

@ -13,7 +13,6 @@ export default defineNuxtPlugin(() => {
}
window.addEventListener('unhandledrejection', err =>
log.error(err.reason),
)
log.error(err.reason))
window.addEventListener('error', err => log.error(err.error), true)
})

View file

@ -1,10 +1,10 @@
[build]
publish = "dist"
command = "pnpm run build"
publish = "dist"
command = "pnpm run build"
# Redirect to Discord server
[[redirects]]
from = "https://chat.elk.zone"
to = "https://discord.gg/vAZSDU9J"
status = 301
force = true
from = "https://chat.elk.zone"
to = "https://discord.gg/vAZSDU9J"
status = 301
force = true

View file

@ -94,16 +94,42 @@ export default defineNuxtConfig({
},
optimizeDeps: {
include: [
'@tiptap/vue-3', 'string-length', 'vue-virtual-scroller', 'emoji-mart', 'iso-639-1',
'@tiptap/extension-placeholder', '@tiptap/extension-document', '@tiptap/extension-paragraph',
'@tiptap/extension-text', '@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',
'@tiptap/vue-3',
'string-length',
'vue-virtual-scroller',
'emoji-mart',
'iso-639-1',
'@tiptap/extension-placeholder',
'@tiptap/extension-document',
'@tiptap/extension-paragraph',
'@tiptap/extension-text',
'@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
security: {
headers: {
@ -297,7 +324,7 @@ export default defineNuxtConfig({
})
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
// eslint-disable-next-line ts/no-namespace
namespace NodeJS {
interface Process {
mock?: Record<string, any>

View file

@ -112,7 +112,7 @@
"ws": "^8.15.1"
},
"devDependencies": {
"@antfu/eslint-config": "^0.43.1",
"@antfu/eslint-config": "^2.8.0",
"@antfu/ni": "^0.21.12",
"@types/chroma-js": "^2.4.4",
"@types/file-saver": "^2.0.7",
@ -124,10 +124,12 @@
"@types/wicg-file-system-access": "^2020.9.8",
"@types/ws": "^8.5.10",
"@unlazy/nuxt": "^0.11.1",
"@unocss/eslint-config": "^0.58.5",
"@vue/test-utils": "^2.4.4",
"bumpp": "^9.4.0",
"consola": "^3.2.3",
"eslint": "^8.49.0",
"eslint": "^8.57.0",
"eslint-plugin-format": "^0.1.0",
"flat": "^5.0.2",
"fs-extra": "^11.2.0",
"lint-staged": "^14.0.1",

View file

@ -1,5 +1,4 @@
<script setup lang="ts">
const { t } = useI18n()
useHydratedHead({

File diff suppressed because it is too large Load diff

View file

@ -32,7 +32,10 @@ async function handleSharedTarget(event: FetchEvent) {
}
async function sendShareTargetMessage(client: Client, data: FormData) {
const sharedData: { textParts: string[]; files: File[] } = {
const sharedData: {
textParts: string[]
files: File[]
} = {
textParts: [],
files: [],
}

View file

@ -67,8 +67,8 @@ if (import.meta.env.PROD) {
registerRoute(
({ sameOrigin, request, url }) =>
sameOrigin
&& request.destination === 'image'
&& url.pathname.startsWith('/emojis/'),
&& request.destination === 'image'
&& url.pathname.startsWith('/emojis/'),
new StaleWhileRevalidate({
cacheName: 'elk-emojis',
plugins: [