Merge branch 'develop' into report

This commit is contained in:
まっちゃてぃー。 2024-01-02 20:13:54 +09:00 committed by GitHub
commit 8f73d4b567
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 704 additions and 428 deletions

View file

@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class SupportTrueMailApi1703658526000 {
name = 'SupportTrueMailApi1703658526000'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "truemailInstance" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "truemailAuthKey" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "enableTruemailApi" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTruemailApi"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailInstance"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailAuthKey"`);
}
}

View file

@ -74,6 +74,8 @@
"@fastify/multipart": "8.0.0",
"@fastify/static": "6.12.0",
"@fastify/view": "8.2.0",
"@misskey-dev/sharp-read-bmp": "^1.1.1",
"@misskey-dev/summaly": "^5.0.3",
"@nestjs/common": "10.2.10",
"@nestjs/core": "10.2.10",
"@nestjs/testing": "10.2.10",
@ -157,11 +159,9 @@
"sanitize-html": "2.11.0",
"secure-json-parse": "2.7.0",
"sharp": "0.32.6",
"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"summaly": "github:misskey-dev/summaly",
"systeminformation": "5.21.20",
"tinycolor2": "1.6.0",
"tmp": "0.2.1",
@ -177,6 +177,7 @@
},
"devDependencies": {
"@jest/globals": "29.7.0",
"@misskey-dev/eslint-plugin": "^1.0.0",
"@simplewebauthn/typescript-types": "8.3.4",
"@swc/jest": "0.2.29",
"@types/accepts": "1.3.7",

View file

@ -7,7 +7,7 @@ import { randomUUID } from 'node:crypto';
import * as fs from 'node:fs';
import { Inject, Injectable } from '@nestjs/common';
import sharp from 'sharp';
import { sharpBmp } from 'sharp-read-bmp';
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
import { IsNull } from 'typeorm';
import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3';
import { DI } from '@/di-symbols.js';

View file

@ -156,7 +156,7 @@ export class EmailService {
@bindThis
public async validateEmailForAccount(emailAddress: string): Promise<{
available: boolean;
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned';
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist';
}> {
const meta = await this.metaService.fetch();
@ -173,6 +173,8 @@ export class EmailService {
if (meta.enableActiveEmailValidation) {
if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) {
validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey);
} else if (meta.enableTruemailApi && meta.truemailInstance && meta.truemailAuthKey != null) {
validated = await this.trueMail(meta.truemailInstance, emailAddress, meta.truemailAuthKey);
} else {
validated = await validateEmail({
email: emailAddress,
@ -201,6 +203,8 @@ export class EmailService {
validated.reason === 'disposable' ? 'disposable' :
validated.reason === 'mx' ? 'mx' :
validated.reason === 'smtp' ? 'smtp' :
validated.reason === 'network' ? 'network' :
validated.reason === 'blacklist' ? 'blacklist' :
null,
};
}
@ -265,4 +269,67 @@ export class EmailService {
reason: null,
};
}
private async trueMail<T>(truemailInstance: string, emailAddress: string, truemailAuthKey: string): Promise<{
valid: boolean;
reason: 'used' | 'format' | 'blacklist' | 'mx' | 'smtp' | 'network' | T | null;
}> {
const endpoint = truemailInstance + '?email=' + emailAddress;
try {
const res = await this.httpRequestService.send(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: truemailAuthKey
},
});
const json = (await res.json()) as {
email: string;
success: boolean;
errors?: {
list_match?: string;
regex?: string;
mx?: string;
smtp?: string;
} | null;
};
if (json.email === undefined || (json.email !== undefined && json.errors?.regex)) {
return {
valid: false,
reason: 'format',
};
}
if (json.errors?.smtp) {
return {
valid: false,
reason: 'smtp',
};
}
if (json.errors?.mx) {
return {
valid: false,
reason: 'mx',
};
}
if (!json.success) {
return {
valid: false,
reason: json.errors?.list_match as T || 'blacklist',
};
}
return {
valid: true,
reason: null,
};
} catch (error) {
return {
valid: false,
reason: 'network',
};
}
}
}

View file

@ -71,8 +71,11 @@ export default class Logger {
let log = `${l} ${worker}\t[${contexts.join(' ')}]\t${m}`;
if (envOption.withLogTime) log = chalk.gray(time) + ' ' + log;
console.log(important ? chalk.bold(log) : log);
if (level === 'error' && data) console.log(data);
const args: unknown[] = [important ? chalk.bold(log) : log];
if (data != null) {
args.push(data);
}
console.log(...args);
}
@bindThis

View file

@ -457,6 +457,23 @@ export class MiMeta {
})
public verifymailAuthKey: string | null;
@Column('boolean', {
default: false,
})
public enableTruemailApi: boolean;
@Column('varchar', {
length: 1024,
nullable: true,
})
public truemailInstance: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public truemailAuthKey: string | null;
@Column('boolean', {
default: true,
})

View file

@ -9,7 +9,7 @@ import { dirname } from 'node:path';
import { Inject, Injectable } from '@nestjs/common';
import rename from 'rename';
import sharp from 'sharp';
import { sharpBmp } from 'sharp-read-bmp';
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
import type { Config } from '@/config.js';
import type { MiDriveFile, DriveFilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';

View file

@ -284,6 +284,18 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
enableTruemailApi: {
type: 'boolean',
optional: false, nullable: false,
},
truemailInstance: {
type: 'string',
optional: false, nullable: true,
},
truemailAuthKey: {
type: 'string',
optional: false, nullable: true,
},
enableChartsForRemoteUser: {
type: 'boolean',
optional: false, nullable: false,
@ -524,6 +536,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
enableActiveEmailValidation: instance.enableActiveEmailValidation,
enableVerifymailApi: instance.enableVerifymailApi,
verifymailAuthKey: instance.verifymailAuthKey,
enableTruemailApi: instance.enableTruemailApi,
truemailInstance: instance.truemailInstance,
truemailAuthKey: instance.truemailAuthKey,
enableChartsForRemoteUser: instance.enableChartsForRemoteUser,
enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances,
enableServerMachineStats: instance.enableServerMachineStats,

View file

@ -116,6 +116,9 @@ export const paramDef = {
enableActiveEmailValidation: { type: 'boolean' },
enableVerifymailApi: { type: 'boolean' },
verifymailAuthKey: { type: 'string', nullable: true },
enableTruemailApi: { type: 'boolean' },
truemailInstance: { type: 'string', nullable: true },
truemailAuthKey: { type: 'string', nullable: true },
enableChartsForRemoteUser: { type: 'boolean' },
enableChartsForFederatedInstances: { type: 'boolean' },
enableServerMachineStats: { type: 'boolean' },
@ -474,6 +477,26 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.verifymailAuthKey = ps.verifymailAuthKey;
}
}
if (ps.enableTruemailApi !== undefined) {
set.enableTruemailApi = ps.enableTruemailApi;
}
if (ps.truemailInstance !== undefined) {
if (ps.truemailInstance === '') {
set.truemailInstance = null;
} else {
set.truemailInstance = ps.truemailInstance;
}
}
if (ps.truemailAuthKey !== undefined) {
if (ps.truemailAuthKey === '') {
set.truemailAuthKey = null;
} else {
set.truemailAuthKey = ps.truemailAuthKey;
}
}
if (ps.enableChartsForRemoteUser !== undefined) {
set.enableChartsForRemoteUser = ps.enableChartsForRemoteUser;

View file

@ -4,7 +4,7 @@
*/
import { Inject, Injectable } from '@nestjs/common';
import { summaly } from 'summaly';
import { summaly } from '@misskey-dev/summaly';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { MetaService } from '@/core/MetaService.js';

View file

@ -19,18 +19,18 @@
"dependencies": {
"@discordapp/twemoji": "15.0.2",
"@github/webauthn-json": "2.1.1",
"@misskey-dev/browser-image-resizer": "2.2.1-misskey.10",
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "5.0.5",
"@rollup/pluginutils": "5.1.0",
"@syuilo/aiscript": "0.16.0",
"@tabler/icons-webfont": "2.44.0",
"@twemoji/parser": "15.0.0",
"@vitejs/plugin-vue": "4.5.2",
"@vue/compiler-sfc": "3.3.12",
"@vitejs/plugin-vue": "5.0.2",
"@vue/compiler-sfc": "3.4.3",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6",
"astring": "1.8.6",
"broadcast-channel": "7.0.0",
"browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
"buraha": "0.0.1",
"canvas-confetti": "1.6.1",
"chart.js": "4.4.1",
@ -45,7 +45,6 @@
"escape-regexp": "0.0.1",
"estree-walker": "3.0.3",
"eventemitter3": "5.0.1",
"gsap": "3.12.4",
"idb-keyval": "6.2.1",
"insert-text-at-cursor": "0.3.0",
"is-file-animated": "1.0.2",
@ -70,10 +69,12 @@
"uuid": "9.0.1",
"v-code-diff": "1.7.2",
"vite": "5.0.10",
"vue": "3.3.12",
"vue": "3.4.3",
"vuedraggable": "next"
},
"devDependencies": {
"@misskey-dev/eslint-plugin": "^1.0.0",
"@misskey-dev/summaly": "^5.0.3",
"@storybook/addon-actions": "7.6.5",
"@storybook/addon-essentials": "7.6.5",
"@storybook/addon-interactions": "7.6.5",
@ -107,7 +108,7 @@
"@typescript-eslint/eslint-plugin": "6.14.0",
"@typescript-eslint/parser": "6.14.0",
"@vitest/coverage-v8": "0.34.6",
"@vue/runtime-core": "3.3.12",
"@vue/runtime-core": "3.4.3",
"acorn": "8.11.2",
"cross-env": "7.0.3",
"cypress": "13.6.1",
@ -127,11 +128,10 @@
"start-server-and-test": "2.0.3",
"storybook": "7.6.5",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"summaly": "github:misskey-dev/summaly",
"vite-plugin-turbosnap": "1.0.3",
"vitest": "0.34.6",
"vitest-fetch-mock": "0.2.2",
"vue-eslint-parser": "9.3.2",
"vue-tsc": "1.8.25"
"vue-tsc": "1.8.27"
}
}

View file

@ -131,6 +131,10 @@ function onMousedown(evt: MouseEvent): void {
box-sizing: border-box;
transition: background 0.1s ease;
&:hover {
text-decoration: none;
}
&:not(:disabled):hover {
background: var(--buttonHoverBg);
}

View file

@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.codeEditorScroller">
<textarea
ref="inputEl"
v-model="vModel"
v-model="v"
:class="[$style.textarea]"
:disabled="disabled"
:required="required"
@ -58,7 +58,6 @@ const emit = defineEmits<{
}>();
const { modelValue } = toRefs(props);
const vModel = ref<string>(modelValue.value ?? '');
const v = ref<string>(modelValue.value ?? '');
const focused = ref(false);
const changed = ref(false);
@ -79,15 +78,14 @@ const onKeydown = (ev: KeyboardEvent) => {
if (ev.code === 'Enter') {
const pos = inputEl.value?.selectionStart ?? 0;
const posEnd = inputEl.value?.selectionEnd ?? vModel.value.length;
const posEnd = inputEl.value?.selectionEnd ?? v.value.length;
if (pos === posEnd) {
const lines = vModel.value.slice(0, pos).split('\n');
const lines = v.value.slice(0, pos).split('\n');
const currentLine = lines[lines.length - 1];
const currentLineSpaces = currentLine.match(/^\s+/);
const posDelta = currentLineSpaces ? currentLineSpaces[0].length : 0;
ev.preventDefault();
vModel.value = vModel.value.slice(0, pos) + '\n' + (currentLineSpaces ? currentLineSpaces[0] : '') + vModel.value.slice(pos);
v.value = vModel.value;
v.value = v.value.slice(0, pos) + '\n' + (currentLineSpaces ? currentLineSpaces[0] : '') + v.value.slice(pos);
nextTick(() => {
inputEl.value?.setSelectionRange(pos + 1 + posDelta, pos + 1 + posDelta);
});
@ -97,9 +95,8 @@ const onKeydown = (ev: KeyboardEvent) => {
if (ev.key === 'Tab') {
const pos = inputEl.value?.selectionStart ?? 0;
const posEnd = inputEl.value?.selectionEnd ?? vModel.value.length;
vModel.value = vModel.value.slice(0, pos) + '\t' + vModel.value.slice(posEnd);
v.value = vModel.value;
const posEnd = inputEl.value?.selectionEnd ?? v.value.length;
v.value = v.value.slice(0, pos) + '\t' + v.value.slice(posEnd);
nextTick(() => {
inputEl.value?.setSelectionRange(pos + 1, pos + 1);
});

View file

@ -9,7 +9,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { reactive, watch } from 'vue';
import gsap from 'gsap';
import number from '@/filters/number.js';
const props = defineProps<{
@ -20,8 +19,24 @@ const tweened = reactive({
number: 0,
});
watch(() => props.value, (n) => {
gsap.to(tweened, { duration: 1, number: Number(n) || 0 });
watch(() => props.value, (to, from) => {
// requestAnimationFrame500msfromto1
let start: number | null = null;
function step(timestamp: number) {
if (start === null) {
start = timestamp;
}
const elapsed = timestamp - start;
tweened.number = (from ?? 0) + (to - (from ?? 0)) * elapsed / 500;
if (elapsed < 500) {
window.requestAnimationFrame(step);
} else {
tweened.number = to;
}
}
window.requestAnimationFrame(step);
}, {
immediate: true,
});

View file

@ -752,7 +752,17 @@ async function post(ev?: MouseEvent) {
if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') {
const hashtags_ = hashtags.value.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_;
if (!postData.text) {
postData.text = hashtags_;
} else {
const postTextLines = postData.text.split('\n');
if (postTextLines[postTextLines.length - 1].trim() === '') {
postTextLines[postTextLines.length - 1] += hashtags_;
} else {
postTextLines[postTextLines.length - 1] += ' ' + hashtags_;
}
postData.text = postTextLines.join('\n');
}
}
// plugin

View file

@ -84,7 +84,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent, onUnmounted, ref } from 'vue';
import type { summaly } from 'summaly';
import type { summaly } from '@misskey-dev/summaly';
import { url as local } from '@/config.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';

View file

@ -80,6 +80,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #prefix><i class="ti ti-key"></i></template>
<template #label>Verifymail.io API Auth Key</template>
</MkInput>
<MkSwitch v-model="enableTruemailApi" @update:modelValue="save">
<template #label>Use TrueMail API</template>
</MkSwitch>
<MkInput v-model="truemailInstance" @update:modelValue="save">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>TrueMail API Instance</template>
</MkInput>
<MkInput v-model="truemailAuthKey" @update:modelValue="save">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>TrueMail API Auth Key</template>
</MkInput>
</div>
</MkFolder>
@ -153,6 +164,9 @@ const enableIpLogging = ref<boolean>(false);
const enableActiveEmailValidation = ref<boolean>(false);
const enableVerifymailApi = ref<boolean>(false);
const verifymailAuthKey = ref<string | null>(null);
const enableTruemailApi = ref<boolean>(false);
const truemailInstance = ref<string | null>(null);
const truemailAuthKey = ref<string | null>(null);
const bannedEmailDomains = ref<string>('');
async function init() {
@ -194,6 +208,9 @@ function save() {
enableActiveEmailValidation: enableActiveEmailValidation.value,
enableVerifymailApi: enableVerifymailApi.value,
verifymailAuthKey: verifymailAuthKey.value,
enableTruemailApi: enableTruemailApi.value,
truemailInstance: truemailInstance.value,
truemailAuthKey: truemailAuthKey.value,
bannedEmailDomains: bannedEmailDomains.value.split('\n'),
}).then(() => {
fetchInstance();

View file

@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-else-if="tab === 'search'">
<div class="_gaps">
<div>
<MkInput v-model="searchQuery">
<MkInput v-model="searchQuery" @enter="search()">
<template #prefix><i class="ti ti-search"></i></template>
</MkInput>
<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>

View file

@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSpacer :contentMax="700">
<div v-if="tab === 'search'">
<div class="_gaps">
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
<template #prefix><i class="ti ti-search"></i></template>
</MkInput>
<MkRadios v-model="searchType" @update:modelValue="search()">

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps">
<div class="_gaps">
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
<template #prefix><i class="ti ti-search"></i></template>
</MkInput>
<MkFolder>

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps">
<div class="_gaps">
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
<template #prefix><i class="ti ti-search"></i></template>
</MkInput>
<MkRadios v-model="searchOrigin" @update:modelValue="search()">

View file

@ -168,7 +168,7 @@ export class Storage<T extends StateDef> {
this.reactiveState[key].value = this.state[key] = rawValue;
return this.addIdbSetJob(async () => {
if (_DEV_) console.log(`set ${key} start`);
if (_DEV_) console.log(`set ${String(key)} start`);
switch (this.def[key].where) {
case 'device': {
this.pizzaxChannel.postMessage({
@ -207,7 +207,7 @@ export class Storage<T extends StateDef> {
break;
}
}
if (_DEV_) console.log(`set ${key} complete`);
if (_DEV_) console.log(`set ${String(key)} complete`);
});
}

View file

@ -5,7 +5,7 @@
import { reactive, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { readAndCompressImage } from 'browser-image-resizer';
import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
import { getCompressionConfig } from './upload/compress-config.js';
import { defaultStore } from '@/store.js';
import { apiUrl } from '@/config.js';

View file

@ -5,7 +5,7 @@
import isAnimated from 'is-file-animated';
import { isWebpSupported } from './isWebpSupported.js';
import type { BrowserImageResizerConfig } from 'browser-image-resizer';
import type { BrowserImageResizerConfigWithConvertedOutput } from '@misskey-dev/browser-image-resizer';
const compressTypeMap = {
'image/jpeg': { quality: 0.90, mimeType: 'image/webp' },
@ -21,7 +21,7 @@ const compressTypeMapFallback = {
'image/svg+xml': { quality: 1, mimeType: 'image/png' },
} as const;
export async function getCompressionConfig(file: File): Promise<BrowserImageResizerConfig | undefined> {
export async function getCompressionConfig(file: File): Promise<BrowserImageResizerConfigWithConvertedOutput | undefined> {
const imgConfig = (isWebpSupported() ? compressTypeMap : compressTypeMapFallback)[file.type];
if (!imgConfig || await isAnimated(file)) {
return;

View file

@ -6,7 +6,7 @@
import { describe, test, assert, afterEach } from 'vitest';
import { render, cleanup, type RenderResult } from '@testing-library/vue';
import './init';
import type { summaly } from 'summaly';
import type { summaly } from '@misskey-dev/summaly';
import { components } from '@/components/index.js';
import { directives } from '@/directives/index.js';
import MkUrlPreview from '@/components/MkUrlPreview.vue';

View file

@ -8,15 +8,16 @@
},
"devDependencies": {
"@apidevtools/swagger-parser": "10.1.0",
"@misskey-dev/eslint-plugin": "^1.0.0",
"@types/node": "20.9.1",
"@typescript-eslint/eslint-plugin": "6.11.0",
"@typescript-eslint/parser": "6.11.0",
"eslint": "8.53.0",
"typescript": "5.3.3",
"tsx": "4.4.0",
"ts-case-convert": "2.0.2",
"openapi-types": "12.1.3",
"openapi-typescript": "6.7.1"
"openapi-typescript": "6.7.1",
"ts-case-convert": "2.0.2",
"tsx": "4.4.0",
"typescript": "5.3.3"
},
"files": [
"built"

View file

@ -23,6 +23,7 @@
},
"devDependencies": {
"@microsoft/api-extractor": "7.38.5",
"@misskey-dev/eslint-plugin": "^1.0.0",
"@swc/jest": "0.2.29",
"@types/jest": "29.5.11",
"@types/node": "20.10.5",

View file

@ -1,118 +1,7 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
'import'
],
ignorePatterns: ['**/.eslintrc.cjs'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended',
'plugin:import/typescript'
'plugin:@misskey-dev/recommended',
],
rules: {
'indent': ['warn', 'tab', {
'SwitchCase': 1,
'MemberExpression': 1,
'flatTernaryExpressions': true,
'ArrayExpression': 'first',
'ObjectExpression': 'first',
}],
'eol-last': ['error', 'always'],
'semi': ['error', 'always'],
'semi-spacing': ['error', { 'before': false, 'after': true }],
'quotes': ['warn', 'single'],
'comma-dangle': ['warn', 'always-multiline'],
'comma-spacing': ['error', { 'before': false, 'after': true }],
'array-bracket-spacing': ['error', 'never'],
'keyword-spacing': ['error', {
'before': true,
'after': true,
}],
'key-spacing': ['error', {
'beforeColon': false,
'afterColon': true,
}],
'arrow-spacing': ['error', {
'before': true,
'after': true,
}],
'brace-style': ['error', '1tbs', {
'allowSingleLine': true,
}],
'padded-blocks': ['error', 'never'],
/* TODO: path alias使warn
'no-restricted-imports': ['warn', {
'patterns': [
]
}],
*/
'eqeqeq': ['error', 'always', { 'null': 'ignore' }],
'no-multi-spaces': ['error'],
'no-var': ['error'],
'prefer-arrow-callback': ['error'],
'no-throw-literal': ['error'],
'no-param-reassign': ['warn'],
'no-constant-condition': ['warn'],
'no-empty-pattern': ['warn'],
'no-async-promise-executor': ['off'],
'no-useless-escape': ['off'],
'no-multiple-empty-lines': ['error', { 'max': 1 }],
'no-control-regex': ['warn'],
'no-empty': ['warn'],
'no-inner-declarations': ['off'],
'no-sparse-arrays': ['off'],
'nonblock-statement-body-position': ['error', 'beside'],
'object-curly-spacing': ['error', 'always'],
'space-infix-ops': ['error'],
'space-before-blocks': ['error', 'always'],
'padding-line-between-statements': [
'error',
{ 'blankLine': 'always', 'prev': 'function', 'next': '*' },
{ 'blankLine': 'always', 'prev': '*', 'next': 'function' },
],
"lines-between-class-members": "off",
/* typescript-eslint enforce
'@typescript-eslint/lines-between-class-members': ['error', {
enforce: [{
blankLine: 'always',
prev: 'method',
next: '*',
}]
}],
*/
'@typescript-eslint/func-call-spacing': ['error', 'never'],
'@typescript-eslint/no-explicit-any': ['warn'],
'@typescript-eslint/no-unused-vars': ['warn'],
'@typescript-eslint/no-unnecessary-condition': ['warn'],
'@typescript-eslint/no-var-requires': ['warn'],
'@typescript-eslint/no-inferrable-types': ['warn'],
'@typescript-eslint/no-empty-function': ['off'],
'@typescript-eslint/no-non-null-assertion': ['warn'],
'@typescript-eslint/explicit-function-return-type': ['off'],
'@typescript-eslint/no-misused-promises': ['error', {
'checksVoidReturn': false,
}],
'@typescript-eslint/consistent-type-imports': 'off',
'@typescript-eslint/prefer-nullish-coalescing': [
'warn',
],
'@typescript-eslint/naming-convention': [
'error',
{
"selector": "typeLike",
"format": ["PascalCase"]
},
{
"selector": "typeParameter",
"format": []
}
],
'import/no-unresolved': ['off'],
'import/no-default-export': ['warn'],
'import/order': ['warn', {
'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
}]
},
};

View file

@ -14,6 +14,7 @@
"misskey-js": "workspace:*"
},
"devDependencies": {
"@misskey-dev/eslint-plugin": "^1.0.0",
"@typescript-eslint/parser": "6.14.0",
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
"eslint": "8.56.0",