Merge branch 'develop'

This commit is contained in:
syuilo 2023-02-09 11:54:49 +09:00
commit a558767b7a
59 changed files with 830 additions and 740 deletions

View File

@ -8,6 +8,12 @@
You should also include the user name that made the change. You should also include the user name that made the change.
--> -->
## 13.5.3 (2023/02/09)
### Improvements
- Client: デッキにチャンネルカラムを追加
## 13.5.2 (2023/02/08) ## 13.5.2 (2023/02/08)
### Changes ### Changes

View File

@ -6,16 +6,13 @@ Also, the later tasks are more indefinite and are subject to change as developme
This is the phase we are at now. We need to make a high-maintenance environment that can withstand future development. This is the phase we are at now. We need to make a high-maintenance environment that can withstand future development.
- Make the number of type errors zero (backend) - Make the number of type errors zero (backend)
- Probably need to switch some libraries to others that make it difficult to reduce type errors
- e.g. koa to fastify https://github.com/misskey-dev/misskey/issues/7537
- Improve CI - Improve CI
- Fix tests - Fix tests
- mocha, jest, etc. do not support the combination of `TypeScript + ESM + Path alias`, and the tests currently do not work.
- Fix random test failures - https://github.com/misskey-dev/misskey/issues/7985 and https://github.com/misskey-dev/misskey/issues/7986 - Fix random test failures - https://github.com/misskey-dev/misskey/issues/7985 and https://github.com/misskey-dev/misskey/issues/7986
- Add more tests - Add more tests
- May need to implement a mechanism that allows for DI - ~~May need to implement a mechanism that allows for DI~~ → Done ✔️
- https://github.com/misskey-dev/misskey/pull/9085 - https://github.com/misskey-dev/misskey/pull/9085
- Measure coverage - ~~Measure coverage~~ → Done ✔️
- https://github.com/misskey-dev/misskey/pull/9081 - https://github.com/misskey-dev/misskey/pull/9081
- Improve documentation - Improve documentation
- Refactoring - Refactoring

View File

@ -1345,5 +1345,6 @@ _deck:
tl: "الخيط الزمني" tl: "الخيط الزمني"
antenna: "الهوائيات" antenna: "الهوائيات"
list: "القوائم" list: "القوائم"
channel: "القنوات"
mentions: "الإشارات" mentions: "الإشارات"
direct: "مباشرة" direct: "مباشرة"

View File

@ -1441,5 +1441,6 @@ _deck:
tl: "টাইমলাইন" tl: "টাইমলাইন"
antenna: "অ্যান্টেনা" antenna: "অ্যান্টেনা"
list: "লিস্ট" list: "লিস্ট"
channel: "চ্যানেলগুলি"
mentions: "উল্লেখসমূহ" mentions: "উল্লেখসমূহ"
direct: "ডাইরেক্ট নোটগুলি" direct: "ডাইরেক্ট নোটগুলি"

View File

@ -804,4 +804,5 @@ _deck:
tl: "Časová osa" tl: "Časová osa"
antenna: "Antény" antenna: "Antény"
list: "Seznamy" list: "Seznamy"
channel: "Kanály"
mentions: "Zmínění" mentions: "Zmínění"

View File

@ -1869,5 +1869,6 @@ _deck:
tl: "Chronik" tl: "Chronik"
antenna: "Antennen" antenna: "Antennen"
list: "Listen" list: "Listen"
channel: "Kanäle"
mentions: "Erwähnungen" mentions: "Erwähnungen"
direct: "Direktnachrichten" direct: "Direktnachrichten"

View File

@ -1869,5 +1869,6 @@ _deck:
tl: "Timeline" tl: "Timeline"
antenna: "Antennas" antenna: "Antennas"
list: "List" list: "List"
channel: "Channels"
mentions: "Mentions" mentions: "Mentions"
direct: "Direct notes" direct: "Direct notes"

View File

@ -1869,5 +1869,6 @@ _deck:
tl: "Linea de tiempo" tl: "Linea de tiempo"
antenna: "Antenas" antenna: "Antenas"
list: "Listas" list: "Listas"
channel: "Canal"
mentions: "Menciones" mentions: "Menciones"
direct: "Mensaje directo" direct: "Mensaje directo"

View File

@ -1541,5 +1541,6 @@ _deck:
tl: "Fil" tl: "Fil"
antenna: "Antennes" antenna: "Antennes"
list: "Listes" list: "Listes"
channel: "Canaux"
mentions: "Mentions" mentions: "Mentions"
direct: "Direct" direct: "Direct"

View File

@ -1673,5 +1673,6 @@ _deck:
tl: "Linimasa" tl: "Linimasa"
antenna: "Antena" antenna: "Antena"
list: "Daftar" list: "Daftar"
channel: "Kanal"
mentions: "Sebutan" mentions: "Sebutan"
direct: "Langsung" direct: "Langsung"

View File

@ -1869,5 +1869,6 @@ _deck:
tl: "Timeline" tl: "Timeline"
antenna: "Antenne" antenna: "Antenne"
list: "Liste" list: "Liste"
channel: "Canale"
mentions: "Menzioni" mentions: "Menzioni"
direct: "Diretta" direct: "Diretta"

View File

@ -129,6 +129,7 @@ unblockConfirm: "ブロック解除しますか?"
suspendConfirm: "凍結しますか?" suspendConfirm: "凍結しますか?"
unsuspendConfirm: "解凍しますか?" unsuspendConfirm: "解凍しますか?"
selectList: "リストを選択" selectList: "リストを選択"
selectChannel: "チャンネルを選択"
selectAntenna: "アンテナを選択" selectAntenna: "アンテナを選択"
selectWidget: "ウィジェットを選択" selectWidget: "ウィジェットを選択"
editWidgets: "ウィジェットを編集" editWidgets: "ウィジェットを編集"
@ -1922,5 +1923,6 @@ _deck:
tl: "タイムライン" tl: "タイムライン"
antenna: "アンテナ" antenna: "アンテナ"
list: "リスト" list: "リスト"
channel: "チャンネル"
mentions: "あなた宛て" mentions: "あなた宛て"
direct: "ダイレクト" direct: "ダイレクト"

View File

@ -1628,5 +1628,6 @@ _deck:
tl: "タイムライン" tl: "タイムライン"
antenna: "アンテナ" antenna: "アンテナ"
list: "リスト" list: "リスト"
channel: "チャンネル"
mentions: "あんた宛て" mentions: "あんた宛て"
direct: "ダイレクト" direct: "ダイレクト"

View File

@ -1866,5 +1866,6 @@ _deck:
tl: "타임라인" tl: "타임라인"
antenna: "안테나" antenna: "안테나"
list: "리스트" list: "리스트"
channel: "채널"
mentions: "받은 멘션" mentions: "받은 멘션"
direct: "다이렉트" direct: "다이렉트"

View File

@ -1438,5 +1438,6 @@ _deck:
tl: "Oś czasu" tl: "Oś czasu"
antenna: "Anteny" antenna: "Anteny"
list: "Listy" list: "Listy"
channel: "Kanały"
mentions: "Wspomnienia" mentions: "Wspomnienia"
direct: "Bezpośredni" direct: "Bezpośredni"

View File

@ -721,4 +721,5 @@ _deck:
tl: "Cronologie" tl: "Cronologie"
antenna: "Antene" antenna: "Antene"
list: "Liste" list: "Liste"
channel: "Canale"
mentions: "Mențiuni" mentions: "Mențiuni"

View File

@ -1845,5 +1845,6 @@ _deck:
tl: "Лента" tl: "Лента"
antenna: "Антенны" antenna: "Антенны"
list: "Списки" list: "Списки"
channel: "Каналы"
mentions: "Упоминания" mentions: "Упоминания"
direct: "Личное" direct: "Личное"

View File

@ -1545,5 +1545,6 @@ _deck:
tl: "Časová os" tl: "Časová os"
antenna: "Antény" antenna: "Antény"
list: "Zoznam" list: "Zoznam"
channel: "Kanály"
mentions: "Zmienky" mentions: "Zmienky"
direct: "Priame poznámky" direct: "Priame poznámky"

View File

@ -1869,5 +1869,6 @@ _deck:
tl: "ไทม์ไลน์" tl: "ไทม์ไลน์"
antenna: "เสาอากาศ" antenna: "เสาอากาศ"
list: "รายการ" list: "รายการ"
channel: "แชนแนล"
mentions: "พูดถึง" mentions: "พูดถึง"
direct: "ไดเร็ค" direct: "ไดเร็ค"

View File

@ -1689,5 +1689,6 @@ _deck:
tl: "Стрічка" tl: "Стрічка"
antenna: "Антени" antenna: "Антени"
list: "Списки" list: "Списки"
channel: "Канали"
mentions: "Згадки" mentions: "Згадки"
direct: "Особисте" direct: "Особисте"

View File

@ -1520,5 +1520,6 @@ _deck:
tl: "Bảng tin" tl: "Bảng tin"
antenna: "Trạm phát sóng" antenna: "Trạm phát sóng"
list: "Danh sách" list: "Danh sách"
channel: "Kênh"
mentions: "Lượt nhắc" mentions: "Lượt nhắc"
direct: "Nhắn riêng" direct: "Nhắn riêng"

View File

@ -1869,5 +1869,6 @@ _deck:
tl: "时间线" tl: "时间线"
antenna: "天线" antenna: "天线"
list: "列表" list: "列表"
channel: "频道"
mentions: "提及" mentions: "提及"
direct: "指定用户" direct: "指定用户"

View File

@ -1869,5 +1869,6 @@ _deck:
tl: "時間軸" tl: "時間軸"
antenna: "天線" antenna: "天線"
list: "清單" list: "清單"
channel: "頻道"
mentions: "提及" mentions: "提及"
direct: "指定使用者" direct: "指定使用者"

View File

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "13.5.2", "version": "13.5.3",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",
@ -54,8 +54,8 @@
"devDependencies": { "devDependencies": {
"@types/gulp": "4.0.10", "@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.1",
"@typescript-eslint/eslint-plugin": "5.50.0", "@typescript-eslint/eslint-plugin": "5.51.0",
"@typescript-eslint/parser": "5.50.0", "@typescript-eslint/parser": "5.51.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "12.5.1", "cypress": "12.5.1",
"eslint": "8.33.0", "eslint": "8.33.0",

View File

@ -23,9 +23,9 @@
"@tensorflow/tfjs-node": "4.2.0" "@tensorflow/tfjs-node": "4.2.0"
}, },
"dependencies": { "dependencies": {
"@bull-board/api": "4.11.0", "@bull-board/api": "4.11.1",
"@bull-board/fastify": "4.11.0", "@bull-board/fastify": "4.11.1",
"@bull-board/ui": "4.11.0", "@bull-board/ui": "4.11.1",
"@discordapp/twemoji": "14.0.2", "@discordapp/twemoji": "14.0.2",
"@fastify/accepts": "4.1.0", "@fastify/accepts": "4.1.0",
"@fastify/cookie": "8.3.0", "@fastify/cookie": "8.3.0",
@ -34,9 +34,9 @@
"@fastify/multipart": "7.4.0", "@fastify/multipart": "7.4.0",
"@fastify/static": "6.8.0", "@fastify/static": "6.8.0",
"@fastify/view": "7.4.1", "@fastify/view": "7.4.1",
"@nestjs/common": "9.3.1", "@nestjs/common": "9.3.7",
"@nestjs/core": "9.3.1", "@nestjs/core": "9.3.7",
"@nestjs/testing": "9.3.1", "@nestjs/testing": "9.3.7",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@sinonjs/fake-timers": "10.0.2", "@sinonjs/fake-timers": "10.0.2",
"accepts": "1.3.8", "accepts": "1.3.8",
@ -46,7 +46,7 @@
"aws-sdk": "2.1295.0", "aws-sdk": "2.1295.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.4", "blurhash": "2.0.4",
"bull": "4.10.2", "bull": "4.10.3",
"cacheable-lookup": "6.1.0", "cacheable-lookup": "6.1.0",
"cbor": "8.1.0", "cbor": "8.1.0",
"chalk": "5.2.0", "chalk": "5.2.0",
@ -90,7 +90,7 @@
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"pug": "3.0.2", "pug": "3.0.2",
"punycode": "2.3.0", "punycode": "2.3.0",
"pureimage": "0.3.15", "pureimage": "0.3.17",
"qrcode": "1.5.1", "qrcode": "1.5.1",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
@ -111,12 +111,12 @@
"stringz": "2.1.0", "stringz": "2.1.0",
"summaly": "2.7.0", "summaly": "2.7.0",
"systeminformation": "5.17.8", "systeminformation": "5.17.8",
"tinycolor2": "1.5.2", "tinycolor2": "1.6.0",
"tmp": "0.2.1", "tmp": "0.2.1",
"tsc-alias": "1.8.2", "tsc-alias": "1.8.2",
"tsconfig-paths": "4.1.2", "tsconfig-paths": "4.1.2",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typeorm": "0.3.11", "typeorm": "0.3.12",
"typescript": "4.9.5", "typescript": "4.9.5",
"ulid": "2.3.0", "ulid": "2.3.0",
"unzipper": "0.10.11", "unzipper": "0.10.11",
@ -128,10 +128,10 @@
"xev": "3.0.2" "xev": "3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "29.4.1", "@jest/globals": "29.4.2",
"@redocly/openapi-core": "1.0.0-beta.123", "@redocly/openapi-core": "1.0.0-beta.123",
"@swc/cli": "0.1.61", "@swc/cli": "0.1.61",
"@swc/core": "1.3.32", "@swc/core": "1.3.34",
"@swc/jest": "0.2.24", "@swc/jest": "0.2.24",
"@types/accepts": "1.3.5", "@types/accepts": "1.3.5",
"@types/archiver": "5.3.1", "@types/archiver": "5.3.1",
@ -145,11 +145,11 @@
"@types/ioredis": "4.28.10", "@types/ioredis": "4.28.10",
"@types/jest": "29.4.0", "@types/jest": "29.4.0",
"@types/js-yaml": "4.0.5", "@types/js-yaml": "4.0.5",
"@types/jsdom": "20.0.1", "@types/jsdom": "21.1.0",
"@types/jsonld": "1.5.8", "@types/jsonld": "1.5.8",
"@types/jsrsasign": "10.5.5", "@types/jsrsasign": "10.5.5",
"@types/mime-types": "2.1.1", "@types/mime-types": "2.1.1",
"@types/node": "18.11.18", "@types/node": "18.13.0",
"@types/node-fetch": "3.0.3", "@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.7", "@types/nodemailer": "6.4.7",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
@ -174,13 +174,13 @@
"@types/web-push": "3.3.2", "@types/web-push": "3.3.2",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.4", "@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.50.0", "@typescript-eslint/eslint-plugin": "5.51.0",
"@typescript-eslint/parser": "5.50.0", "@typescript-eslint/parser": "5.51.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "8.33.0", "eslint": "8.33.0",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.27.5",
"execa": "6.1.0", "execa": "6.1.0",
"jest": "29.4.1", "jest": "29.4.2",
"jest-mock": "29.4.1" "jest-mock": "29.4.2"
} }
} }

View File

@ -5,7 +5,7 @@ import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js';
const ACHIEVEMENT_TYPES = [ export const ACHIEVEMENT_TYPES = [
'notes1', 'notes1',
'notes10', 'notes10',
'notes100', 'notes100',

View File

@ -1,10 +1,10 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Brackets } from 'typeorm'; import { Brackets, ObjectLiteral } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository } from '@/models/index.js'; import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository } from '@/models/index.js';
import type { SelectQueryBuilder } from 'typeorm';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import type { SelectQueryBuilder } from 'typeorm';
@Injectable() @Injectable()
export class QueryService { export class QueryService {
@ -32,7 +32,7 @@ export class QueryService {
) { ) {
} }
public makePaginationQuery<T>(q: SelectQueryBuilder<T>, sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number): SelectQueryBuilder<T> { public makePaginationQuery<T extends ObjectLiteral>(q: SelectQueryBuilder<T>, sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number): SelectQueryBuilder<T> {
if (sinceId && untilId) { if (sinceId && untilId) {
q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId });
q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId });

View File

@ -5,7 +5,7 @@
* The getter will return a .bind version of the function * The getter will return a .bind version of the function
* and memoize the result against a symbol on the instance * and memoize the result against a symbol on the instance
*/ */
export function bindThis(target, key, descriptor) { export function bindThis(target: any, key: string, descriptor: any) {
let fn = descriptor.value; let fn = descriptor.value;
if (typeof fn !== 'function') { if (typeof fn !== 'function') {
@ -34,7 +34,7 @@ export function bindThis(target, key, descriptor) {
}); });
return boundFn; return boundFn;
}, },
set(value) { set(value: any) {
fn = value; fn = value;
}, },
}; };

View File

@ -45,7 +45,7 @@ export default class Logger {
} }
const time = dateFormat(new Date(), 'HH:mm:ss'); const time = dateFormat(new Date(), 'HH:mm:ss');
const worker = cluster.isPrimary ? '*' : cluster.worker.id; const worker = cluster.isPrimary ? '*' : cluster.worker!.id;
const l = const l =
level === 'error' ? important ? chalk.bgRed.white('ERR ') : chalk.red('ERR ') : level === 'error' ? important ? chalk.bgRed.white('ERR ') : chalk.red('ERR ') :
level === 'warning' ? chalk.yellow('WARN') : level === 'warning' ? chalk.yellow('WARN') :

View File

@ -51,7 +51,7 @@ export function genIdenticon(seed: string, stream: WriteStream): Promise<void> {
bg.addColorStop(0, bgColors[0]); bg.addColorStop(0, bgColors[0]);
bg.addColorStop(1, bgColors[1]); bg.addColorStop(1, bgColors[1]);
ctx.fillStyle = bg; ctx.fillStyle = bg as any;
ctx.beginPath(); ctx.beginPath();
ctx.fillRect(0, 0, size, size); ctx.fillRect(0, 0, size, size);

View File

@ -11,10 +11,9 @@ export class I18n<T extends Record<string, any>> {
// string にしているのは、ドット区切りでのパス指定を許可するため // string にしているのは、ドット区切りでのパス指定を許可するため
// なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも // なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
@bindThis
public t(key: string, args?: Record<string, any>): string { public t(key: string, args?: Record<string, any>): string {
try { try {
let str = key.split('.').reduce((o, i) => o[i], this.locale) as string; let str = key.split('.').reduce((o, i) => o[i], this.locale as any) as string;
if (args) { if (args) {
for (const [k, v] of Object.entries(args)) { for (const [k, v] of Object.entries(args)) {

View File

@ -1,3 +1,4 @@
import { IncomingMessage } from 'node:http';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import fastifyAccepts from '@fastify/accepts'; import fastifyAccepts from '@fastify/accepts';
import httpSignature from '@peertube/http-signature'; import httpSignature from '@peertube/http-signature';
@ -19,6 +20,7 @@ import { QueryService } from '@/core/QueryService.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { IActivity } from '@/core/activitypub/type.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
import type { FindOptionsWhere } from 'typeorm'; import type { FindOptionsWhere } from 'typeorm';
@ -97,7 +99,8 @@ export class ActivityPubServerService {
return; return;
} }
this.queueService.inbox(request.body, signature); // TODO: request.bodyのバリデーション
this.queueService.inbox(request.body as IActivity, signature);
reply.code(202); reply.code(202);
} }
@ -413,20 +416,21 @@ export class ActivityPubServerService {
@bindThis @bindThis
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
fastify.addConstraintStrategy({ // addConstraintStrategy の型定義がおかしいため
(fastify.addConstraintStrategy as any)({
name: 'apOrHtml', name: 'apOrHtml',
storage() { storage() {
const store = {}; const store = {} as any;
return { return {
get(key) { get(key: string) {
return store[key] ?? null; return store[key] ?? null;
}, },
set(key, value) { set(key: string, value: any) {
store[key] = value; store[key] = value;
}, },
}; };
}, },
deriveConstraint(request, ctx) { deriveConstraint(request: IncomingMessage) {
const accepted = accepts(request).type(['html', ACTIVITY_JSON, LD_JSON]); const accepted = accepts(request).type(['html', ACTIVITY_JSON, LD_JSON]);
const isAp = typeof accepted === 'string' && !accepted.match(/html/); const isAp = typeof accepted === 'string' && !accepted.match(/html/);
return isAp ? 'ap' : 'html'; return isAp ? 'ap' : 'html';
@ -536,6 +540,7 @@ export class ActivityPubServerService {
return (this.apRendererService.renderActivity(this.apRendererService.renderKey(user, keypair))); return (this.apRendererService.renderActivity(this.apRendererService.renderKey(user, keypair)));
} else { } else {
reply.code(400); reply.code(400);
return;
} }
}); });

View File

@ -166,6 +166,7 @@ export class ServerService {
return 'Verify succeeded!'; return 'Verify succeeded!';
} else { } else {
reply.code(404); reply.code(404);
return;
} }
}); });

View File

@ -34,7 +34,7 @@ export class RateLimiterService {
const min = (): void => { const min = (): void => {
const minIntervalLimiter = new Limiter({ const minIntervalLimiter = new Limiter({
id: `${actor}:${limitation.key}:min`, id: `${actor}:${limitation.key}:min`,
duration: limitation.minInterval * factor, duration: limitation.minInterval! * factor,
max: 1, max: 1,
db: this.redisClient, db: this.redisClient,
}); });
@ -62,8 +62,8 @@ export class RateLimiterService {
const max = (): void => { const max = (): void => {
const limiter = new Limiter({ const limiter = new Limiter({
id: `${actor}:${limitation.key}`, id: `${actor}:${limitation.key}`,
duration: limitation.duration * factor, duration: limitation.duration! * factor,
max: limitation.max / factor, max: limitation.max! / factor,
db: this.redisClient, db: this.redisClient,
}); });

View File

@ -10,9 +10,9 @@ import { getIpHash } from '@/misc/get-ip-hash.js';
import type { ILocalUser } from '@/models/entities/User.js'; import type { ILocalUser } from '@/models/entities/User.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js'; import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js';
import { bindThis } from '@/decorators.js';
import { RateLimiterService } from './RateLimiterService.js'; import { RateLimiterService } from './RateLimiterService.js';
import { SigninService } from './SigninService.js'; import { SigninService } from './SigninService.js';
import { bindThis } from '@/decorators.js';
import type { FastifyRequest, FastifyReply } from 'fastify'; import type { FastifyRequest, FastifyReply } from 'fastify';
@Injectable() @Injectable()
@ -131,7 +131,7 @@ export class SigninApiService {
createdAt: new Date(), createdAt: new Date(),
userId: user.id, userId: user.id,
ip: request.ip, ip: request.ip,
headers: request.headers, headers: request.headers as any,
success: false, success: false,
}); });

View File

@ -25,7 +25,7 @@ export class SigninService {
} }
@bindThis @bindThis
public signin(request: FastifyRequest, reply: FastifyReply, user: ILocalUser, redirect = false) { public signin(request: FastifyRequest, reply: FastifyReply, user: ILocalUser) {
setImmediate(async () => { setImmediate(async () => {
// Append signin history // Append signin history
const record = await this.signinsRepository.insert({ const record = await this.signinsRepository.insert({
@ -33,7 +33,7 @@ export class SigninService {
createdAt: new Date(), createdAt: new Date(),
userId: user.id, userId: user.id,
ip: request.ip, ip: request.ip,
headers: request.headers, headers: request.headers as any,
success: true, success: true,
}).then(x => this.signinsRepository.findOneByOrFail(x.identifiers[0])); }).then(x => this.signinsRepository.findOneByOrFail(x.identifiers[0]));
@ -41,25 +41,11 @@ export class SigninService {
this.globalEventService.publishMainStream(user.id, 'signin', await this.signinEntityService.pack(record)); this.globalEventService.publishMainStream(user.id, 'signin', await this.signinEntityService.pack(record));
}); });
if (redirect) { reply.code(200);
//#region Cookie return {
reply.setCookie('igi', user.token!, { id: user.id,
path: '/', i: user.token,
// SEE: https://github.com/koajs/koa/issues/974 };
// When using a SSL proxy it should be configured to add the "X-Forwarded-Proto: https" header
secure: this.config.url.startsWith('https'),
httpOnly: false,
});
//#endregion
reply.redirect(this.config.url);
} else {
reply.code(200);
return {
id: user.id,
i: user.token,
};
}
} }
} }

View File

@ -146,6 +146,7 @@ export class SignupApiService {
`To complete signup, please click this link: ${link}`); `To complete signup, please click this link: ${link}`);
reply.code(204); reply.code(204);
return;
} else { } else {
try { try {
const { account, secret } = await this.signupService.signup({ const { account, secret } = await this.signupService.signup({
@ -162,7 +163,7 @@ export class SignupApiService {
token: secret, token: secret,
}; };
} catch (err) { } catch (err) {
throw new FastifyReplyError(400, err); throw new FastifyReplyError(400, typeof err === 'string' ? err : (err as Error).toString());
} }
} }
} }
@ -195,7 +196,7 @@ export class SignupApiService {
return this.signinService.signin(request, reply, account as ILocalUser); return this.signinService.signin(request, reply, account as ILocalUser);
} catch (err) { } catch (err) {
throw new FastifyReplyError(400, err); throw new FastifyReplyError(400, typeof err === 'string' ? err : (err as Error).toString());
} }
} }
} }

View File

@ -20,7 +20,7 @@ export const paramDef = {
description: { type: 'string' }, description: { type: 'string' },
color: { type: 'string', nullable: true }, color: { type: 'string', nullable: true },
iconUrl: { type: 'string', nullable: true }, iconUrl: { type: 'string', nullable: true },
target: { type: 'string' }, target: { type: 'string', enum: ['manual', 'conditional'] },
condFormula: { type: 'object' }, condFormula: { type: 'object' },
isPublic: { type: 'boolean' }, isPublic: { type: 'boolean' },
isModerator: { type: 'boolean' }, isModerator: { type: 'boolean' },

View File

@ -28,7 +28,7 @@ export const paramDef = {
description: { type: 'string' }, description: { type: 'string' },
color: { type: 'string', nullable: true }, color: { type: 'string', nullable: true },
iconUrl: { type: 'string', nullable: true }, iconUrl: { type: 'string', nullable: true },
target: { type: 'string' }, target: { type: 'string', enum: ['manual', 'conditional'] },
condFormula: { type: 'object' }, condFormula: { type: 'object' },
isPublic: { type: 'boolean' }, isPublic: { type: 'boolean' },
isModerator: { type: 'boolean' }, isModerator: { type: 'boolean' },

View File

@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { getJsonSchema } from '@/core/chart/core.js'; import { getJsonSchema } from '@/core/chart/core.js';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import PerUserPvChart from '@/core/chart/charts/per-user-pv.js'; import PerUserPvChart from '@/core/chart/charts/per-user-pv.js';
import { schema } from '@/core/chart/charts/entities/per-user-notes.js'; import { schema } from '@/core/chart/charts/entities/per-user-pv.js';
export const meta = { export const meta = {
tags: ['charts', 'users'], tags: ['charts', 'users'],

View File

@ -27,7 +27,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
return { return {
params: Object.entries(ep.params.properties ?? {}).map(([k, v]) => ({ params: Object.entries(ep.params.properties ?? {}).map(([k, v]) => ({
name: k, name: k,
type: v.type.charAt(0).toUpperCase() + v.type.slice(1), type: v.type ? v.type.charAt(0).toUpperCase() + v.type.slice(1) : 'string',
})), })),
}; };
}); });

View File

@ -1,7 +1,7 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { AchievementService } from '@/core/AchievementService.js'; import { AchievementService, ACHIEVEMENT_TYPES } from '@/core/AchievementService.js';
export const meta = { export const meta = {
requireCredential: true, requireCredential: true,
@ -10,7 +10,7 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
name: { type: 'string' }, name: { type: 'string', enum: ACHIEVEMENT_TYPES },
}, },
required: ['name'], required: ['name'],
} as const; } as const;

View File

@ -155,7 +155,7 @@ export class ClientServerService {
}); });
serverAdapter.setBasePath(bullBoardPath); serverAdapter.setBasePath(bullBoardPath);
fastify.register(serverAdapter.registerPlugin(), { prefix: bullBoardPath }); (fastify.register as any)(serverAdapter.registerPlugin(), { prefix: bullBoardPath });
//#endregion //#endregion
fastify.register(fastifyView, { fastify.register(fastifyView, {
@ -372,6 +372,7 @@ export class ClientServerService {
return feed.atom1(); return feed.atom1();
} else { } else {
reply.code(404); reply.code(404);
return;
} }
}); });
@ -384,6 +385,7 @@ export class ClientServerService {
return feed.rss2(); return feed.rss2();
} else { } else {
reply.code(404); reply.code(404);
return;
} }
}); });
@ -396,6 +398,7 @@ export class ClientServerService {
return feed.json1(); return feed.json1();
} else { } else {
reply.code(404); reply.code(404);
return;
} }
}); });

View File

@ -12,7 +12,7 @@
"@rollup/plugin-json": "6.0.0", "@rollup/plugin-json": "6.0.0",
"@rollup/pluginutils": "5.0.2", "@rollup/pluginutils": "5.0.2",
"@syuilo/aiscript": "0.12.4", "@syuilo/aiscript": "0.12.4",
"@tabler/icons-webfont": "2.1.2", "@tabler/icons-webfont": "2.2.0",
"@vitejs/plugin-vue": "4.0.0", "@vitejs/plugin-vue": "4.0.0",
"@vue/compiler-sfc": "3.2.47", "@vue/compiler-sfc": "3.2.47",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
@ -23,7 +23,7 @@
"canvas-confetti": "1.6.0", "canvas-confetti": "1.6.0",
"chart.js": "4.2.0", "chart.js": "4.2.0",
"chartjs-adapter-date-fns": "3.0.0", "chartjs-adapter-date-fns": "3.0.0",
"chartjs-chart-matrix": "1.3.0", "chartjs-chart-matrix": "2.0.1",
"chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.0.0", "chartjs-plugin-zoom": "2.0.0",
"compare-versions": "5.0.1", "compare-versions": "5.0.1",
@ -44,7 +44,7 @@
"punycode": "2.3.0", "punycode": "2.3.0",
"querystring": "0.2.1", "querystring": "0.2.1",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"rollup": "3.12.1", "rollup": "3.14.0",
"s-age": "1.1.2", "s-age": "1.1.2",
"sanitize-html": "2.9.0", "sanitize-html": "2.9.0",
"sass": "1.58.0", "sass": "1.58.0",
@ -55,7 +55,7 @@
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.149.0", "three": "0.149.0",
"throttle-debounce": "5.0.0", "throttle-debounce": "5.0.0",
"tinycolor2": "1.5.2", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.2", "tsc-alias": "1.8.2",
"tsconfig-paths": "4.1.2", "tsconfig-paths": "4.1.2",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
@ -74,7 +74,7 @@
"@types/gulp": "4.0.10", "@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.1",
"@types/matter-js": "0.18.2", "@types/matter-js": "0.18.2",
"@types/node": "18.11.18", "@types/node": "18.13.0",
"@types/punycode": "2.1.0", "@types/punycode": "2.1.0",
"@types/sanitize-html": "2.8.0", "@types/sanitize-html": "2.8.0",
"@types/seedrandom": "3.0.4", "@types/seedrandom": "3.0.4",
@ -83,8 +83,8 @@
"@types/uuid": "9.0.0", "@types/uuid": "9.0.0",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.4", "@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.50.0", "@typescript-eslint/eslint-plugin": "5.51.0",
"@typescript-eslint/parser": "5.50.0", "@typescript-eslint/parser": "5.51.0",
"@vue/runtime-core": "3.2.47", "@vue/runtime-core": "3.2.47",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "12.5.1", "cypress": "12.5.1",

View File

@ -61,8 +61,6 @@ export async function signout() {
} catch (err) {} } catch (err) {}
//#endregion //#endregion
document.cookie = 'igi=; path=/';
if (accounts.length > 0) login(accounts[0].token); if (accounts.length > 0) login(accounts[0].token);
else unisonReload('/'); else unisonReload('/');
} }

View File

@ -18,7 +18,7 @@
</div> </div>
</Transition> </Transition>
<div class="container"> <div class="container">
<img ref="imgEl" :src="imgUrl" style="display: none;" @load="onImageLoad"> <img ref="imgEl" :src="imgUrl" style="display: none;" crossorigin="anonymous" @load="onImageLoad">
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,22 +1,23 @@
<template> <template>
<div v-if="hide" class="qjewsnkg" @click="hide = false"> <div v-if="hide" :class="$style.hidden" @click="hide = false">
<ImgWithBlurhash class="bg" :hash="image.blurhash" :title="image.comment" :alt="image.comment"/> <ImgWithBlurhash style="filter: brightness(0.5);" :hash="image.blurhash" :title="image.comment" :alt="image.comment"/>
<div class="text"> <div :class="$style.hiddenText">
<div class="wrapper"> <div :class="$style.hiddenTextWrapper">
<b style="display: block;"><i class="ti ti-alert-triangle"></i> {{ $ts.sensitive }}</b> <b style="display: block;"><i class="ti ti-alert-triangle"></i> {{ $ts.sensitive }}</b>
<span style="display: block;">{{ $ts.clickToShow }}</span> <span style="display: block;">{{ $ts.clickToShow }}</span>
</div> </div>
</div> </div>
</div> </div>
<div v-else class="gqnyydlz"> <div v-else :class="$style.visible">
<a <a
:class="$style.imageContainer"
:href="image.url" :href="image.url"
:title="image.name" :title="image.name"
> >
<ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment || image.name" :title="image.comment || image.name" :cover="false"/> <ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment || image.name" :title="image.comment || image.name" :cover="false"/>
<div v-if="image.type === 'image/gif'" class="gif">GIF</div> <div v-if="image.type === 'image/gif'" :class="$style.gif">GIF</div>
</a> </a>
<button v-tooltip="$ts.hide" class="_button hide" @click="hide = true"><i class="ti ti-eye-off"></i></button> <button v-tooltip="$ts.hide" :class="$style.hide" class="_button" @click="hide = true"><i class="ti ti-eye-off"></i></button>
</div> </div>
</template> </template>
@ -49,82 +50,77 @@ watch(() => props.image, () => {
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" module>
.qjewsnkg { .hidden {
position: relative; position: relative;
> .bg {
filter: brightness(0.5);
}
> .text {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
> .wrapper {
display: table-cell;
text-align: center;
font-size: 0.8em;
color: #fff;
}
}
} }
.gqnyydlz { .hiddenText {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
}
.hiddenTextWrapper {
display: table-cell;
text-align: center;
font-size: 0.8em;
color: #fff;
}
.visible {
position: relative; position: relative;
//box-shadow: 0 0 0 1px var(--divider) inset; //box-shadow: 0 0 0 1px var(--divider) inset;
background: var(--bg); background: var(--bg);
--c: rgb(0 0 0 / 2%);
background-image: linear-gradient(45deg, var(--c) 16.67%, var(--bg) 16.67%, var(--bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--bg) 66.67%, var(--bg) 100%);
background-size: 16px 16px;
}
> .hide { .hide {
display: block; display: block;
position: absolute; position: absolute;
border-radius: 6px; border-radius: 6px;
background-color: var(--accentedBg); background-color: var(--accentedBg);
-webkit-backdrop-filter: var(--blur, blur(15px)); -webkit-backdrop-filter: var(--blur, blur(15px));
backdrop-filter: var(--blur, blur(15px)); backdrop-filter: var(--blur, blur(15px));
color: var(--accent); color: var(--accent);
font-size: 0.8em; font-size: 0.8em;
padding: 6px 8px; padding: 6px 8px;
text-align: center; text-align: center;
top: 12px; top: 12px;
right: 12px; right: 12px;
}
> i { .imageContainer {
display: block; display: block;
} cursor: zoom-in;
} overflow: hidden;
width: 100%;
height: 100%;
background-position: center;
background-size: contain;
background-repeat: no-repeat;
}
> a { .gif {
display: block; background-color: var(--fg);
cursor: zoom-in; border-radius: 6px;
overflow: hidden; color: var(--accentLighten);
width: 100%; display: inline-block;
height: 100%; font-size: 14px;
background-position: center; font-weight: bold;
background-size: contain; left: 12px;
background-repeat: no-repeat; opacity: .5;
padding: 0 6px;
> .gif { text-align: center;
background-color: var(--fg); top: 12px;
border-radius: 6px; pointer-events: none;
color: var(--accentLighten);
display: inline-block;
font-size: 14px;
font-weight: bold;
left: 12px;
opacity: .5;
padding: 0 6px;
text-align: center;
top: 12px;
pointer-events: none;
}
}
} }
</style> </style>

View File

@ -215,7 +215,7 @@ useTooltip(reactionRef, (showing) => {
border-radius: 100%; border-radius: 100%;
background: var(--panel); background: var(--panel);
box-shadow: 0 0 0 3px var(--panel); box-shadow: 0 0 0 3px var(--panel);
font-size: 12px; font-size: 11px;
text-align: center; text-align: center;
color: #fff; color: #fff;

View File

@ -23,7 +23,7 @@
</div> </div>
</div> </div>
<XPostForm v-if="$i" :channel="channel" class="post-form _panel _margin" fixed/> <MkPostForm v-if="$i" :channel="channel" class="post-form _panel _margin" fixed/>
<XTimeline :key="channelId" class="_margin" src="channel" :channel="channelId" @before="before" @after="after"/> <XTimeline :key="channelId" class="_margin" src="channel" :channel="channelId" @before="before" @after="after"/>
</div> </div>
@ -34,7 +34,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, watch } from 'vue'; import { computed, inject, watch } from 'vue';
import MkContainer from '@/components/MkContainer.vue'; import MkContainer from '@/components/MkContainer.vue';
import XPostForm from '@/components/MkPostForm.vue'; import MkPostForm from '@/components/MkPostForm.vue';
import XTimeline from '@/components/MkTimeline.vue'; import XTimeline from '@/components/MkTimeline.vue';
import XChannelFollowButton from '@/components/MkChannelFollowButton.vue'; import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
import * as os from '@/os'; import * as os from '@/os';

View File

@ -29,7 +29,7 @@ import { noteVisibilities } from 'misskey-js';
import * as Acct from 'misskey-js/built/acct'; import * as Acct from 'misskey-js/built/acct';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import XPostForm from '@/components/MkPostForm.vue'; import MkPostForm from '@/components/MkPostForm.vue';
import * as os from '@/os'; import * as os from '@/os';
import { mainRouter } from '@/router'; import { mainRouter } from '@/router';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
@ -69,14 +69,14 @@ async function init() {
...(visibleAccts ? visibleAccts.split(',').map(Acct.parse) : []), ...(visibleAccts ? visibleAccts.split(',').map(Acct.parse) : []),
] ]
// TypeScript // TypeScript
.map(q => 'username' in q ? { username: q.username, host: q.host === null ? undefined : q.host } : q) .map(q => 'username' in q ? { username: q.username, host: q.host === null ? undefined : q.host } : q)
.map(q => os.api('users/show', q) .map(q => os.api('users/show', q)
.then(user => { .then(user => {
visibleUsers.push(user); visibleUsers.push(user);
}, () => { }, () => {
console.error(`Invalid user query: ${JSON.stringify(q)}`); console.error(`Invalid user query: ${JSON.stringify(q)}`);
}), }),
), ),
); );
} }
@ -120,13 +120,13 @@ async function init() {
if (fileIds) { if (fileIds) {
await Promise.all( await Promise.all(
fileIds.split(',') fileIds.split(',')
.map(fileId => os.api('drive/files/show', { fileId }) .map(fileId => os.api('drive/files/show', { fileId })
.then(file => { .then(file => {
files.push(file); files.push(file);
}, () => { }, () => {
console.error(`Failed to fetch a file ${fileId}`); console.error(`Failed to fetch a file ${fileId}`);
}), }),
), ),
); );
} }
//#endregion //#endregion

View File

@ -4,7 +4,7 @@
<MkSpacer :content-max="800"> <MkSpacer :content-max="800">
<div ref="rootEl" v-hotkey.global="keymap"> <div ref="rootEl" v-hotkey.global="keymap">
<XTutorial v-if="$i && $store.reactiveState.tutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/> <XTutorial v-if="$i && $store.reactiveState.tutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/>
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/> <MkPostForm v-if="$store.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> <div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
<div :class="$style.tl"> <div :class="$style.tl">
@ -24,7 +24,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, computed, watch } from 'vue'; import { defineAsyncComponent, computed, watch } from 'vue';
import XTimeline from '@/components/MkTimeline.vue'; import XTimeline from '@/components/MkTimeline.vue';
import XPostForm from '@/components/MkPostForm.vue'; import MkPostForm from '@/components/MkPostForm.vue';
import { scroll } from '@/scripts/scroll'; import { scroll } from '@/scripts/scroll';
import * as os from '@/os'; import * as os from '@/os';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';

View File

@ -127,13 +127,12 @@ hr {
} }
.ti { .ti {
vertical-align: -40%; vertical-align: -14%;
line-height: 1em; line-height: 1em;
&:before { &:before {
display: inline-block; display: inline-block;
font-size: 165%; font-size: 130%;
width: 0.74em;
} }
} }

View File

@ -146,6 +146,7 @@ const addColumn = async (ev) => {
'tl', 'tl',
'antenna', 'antenna',
'list', 'list',
'channel',
'mentions', 'mentions',
'direct', 'direct',
]; ];

View File

@ -0,0 +1,71 @@
<template>
<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<template #header>
<i class="ti ti-device-tv"></i><span style="margin-left: 8px;">{{ column.name }}</span>
</template>
<template v-if="column.channelId">
<div style="padding: 8px; text-align: center;">
<MkButton primary gradate rounded inline @click="post"><i class="ti ti-pencil"></i></MkButton>
</div>
<XTimeline ref="timeline" src="channel" :channel="column.channelId" @after="() => emit('loaded')"/>
</template>
</XColumn>
</template>
<script lang="ts" setup>
import { } from 'vue';
import XColumn from './column.vue';
import { updateColumn, Column } from './deck-store';
import XTimeline from '@/components/MkTimeline.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
const emit = defineEmits<{
(ev: 'loaded'): void;
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
}>();
let timeline = $shallowRef<InstanceType<typeof XTimeline>>();
if (props.column.channelId == null) {
setChannel();
}
async function setChannel() {
const channels = await os.api('channels/followed');
const { canceled, result: channel } = await os.select({
title: i18n.ts.selectChannel,
items: channels.map(x => ({
value: x, text: x.name,
})),
default: props.column.channelId,
});
if (canceled) return;
updateColumn(props.column.id, {
channelId: channel.id,
name: channel.name,
});
}
function post() {
os.post({
channel: {
id: props.column.channelId,
},
instant: true,
});
}
const menu = [{
icon: 'ti ti-pencil',
text: i18n.ts.selectChannel,
action: setChannel,
}];
</script>

View File

@ -6,6 +6,7 @@
<XNotificationsColumn v-else-if="column.type === 'notifications'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> <XNotificationsColumn v-else-if="column.type === 'notifications'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XTlColumn v-else-if="column.type === 'tl'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> <XTlColumn v-else-if="column.type === 'tl'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XListColumn v-else-if="column.type === 'list'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> <XListColumn v-else-if="column.type === 'list'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XChannelColumn v-else-if="column.type === 'channel'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XAntennaColumn v-else-if="column.type === 'antenna'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> <XAntennaColumn v-else-if="column.type === 'antenna'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XMentionsColumn v-else-if="column.type === 'mentions'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> <XMentionsColumn v-else-if="column.type === 'mentions'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XDirectColumn v-else-if="column.type === 'direct'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> <XDirectColumn v-else-if="column.type === 'direct'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
@ -17,6 +18,7 @@ import XMainColumn from './main-column.vue';
import XTlColumn from './tl-column.vue'; import XTlColumn from './tl-column.vue';
import XAntennaColumn from './antenna-column.vue'; import XAntennaColumn from './antenna-column.vue';
import XListColumn from './list-column.vue'; import XListColumn from './list-column.vue';
import XChannelColumn from './channel-column.vue';
import XNotificationsColumn from './notifications-column.vue'; import XNotificationsColumn from './notifications-column.vue';
import XWidgetsColumn from './widgets-column.vue'; import XWidgetsColumn from './widgets-column.vue';
import XMentionsColumn from './mentions-column.vue'; import XMentionsColumn from './mentions-column.vue';

View File

@ -14,7 +14,7 @@ type ColumnWidget = {
export type Column = { export type Column = {
id: string; id: string;
type: 'main' | 'widgets' | 'notifications' | 'tl' | 'antenna' | 'list' | 'mentions' | 'direct'; type: 'main' | 'widgets' | 'notifications' | 'tl' | 'antenna' | 'channel' | 'list' | 'mentions' | 'direct';
name: string | null; name: string | null;
width: number; width: number;
widgets?: ColumnWidget[]; widgets?: ColumnWidget[];
@ -22,6 +22,7 @@ export type Column = {
flexible?: boolean; flexible?: boolean;
antennaId?: string; antennaId?: string;
listId?: string; listId?: string;
channelId?: string;
includingTypes?: typeof notificationTypes[number][]; includingTypes?: typeof notificationTypes[number][];
tl?: 'home' | 'local' | 'social' | 'global'; tl?: 'home' | 'local' | 'social' | 'global';
}; };

View File

@ -1,12 +1,12 @@
<template> <template>
<XPostForm class="_panel mkw-postForm data-cy-mkw-postForm" :fixed="true" :autofocus="false"/> <MkPostForm class="_panel mkw-post-form data-cy-mkw-postForm" :fixed="true" :autofocus="false"/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { } from 'vue'; import { } from 'vue';
import { GetFormResultType } from '@/scripts/form';
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
import XPostForm from '@/components/MkPostForm.vue'; import { GetFormResultType } from '@/scripts/form';
import MkPostForm from '@/components/MkPostForm.vue';
const name = 'postForm'; const name = 'postForm';

View File

@ -12,8 +12,8 @@
"misskey-js": "0.0.15" "misskey-js": "0.0.15"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/parser": "5.50.0", "@typescript-eslint/parser": "5.51.0",
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.61", "@typescript/lib-webworker": "npm:@types/serviceworker@0.0.62",
"eslint": "8.33.0", "eslint": "8.33.0",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.27.5",
"typescript": "4.9.5" "typescript": "4.9.5"

File diff suppressed because it is too large Load Diff