Merge branch 'develop' into develop
This commit is contained in:
commit
baf162c337
|
@ -8,7 +8,7 @@
|
|||
"version": "8.9.2"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "20.5.1"
|
||||
"version": "20.10.0"
|
||||
}
|
||||
},
|
||||
"forwardPorts": [3000],
|
||||
|
|
150
.github/workflows/get-api-diff.yml
vendored
150
.github/workflows/get-api-diff.yml
vendored
|
@ -6,37 +6,33 @@ on:
|
|||
branches:
|
||||
- master
|
||||
- develop
|
||||
paths:
|
||||
- packages/backend/**
|
||||
- .github/workflows/get-api-diff.yml
|
||||
|
||||
jobs:
|
||||
get-base:
|
||||
get-from-misskey:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.5.1]
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:13
|
||||
ports:
|
||||
- 5432:5432
|
||||
env:
|
||||
POSTGRES_DB: misskey
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
POSTGRES_USER: example-misskey-user
|
||||
POSTGRESS_PASS: example-misskey-pass
|
||||
redis:
|
||||
image: redis:7
|
||||
ports:
|
||||
- 6379:6379
|
||||
node-version: [20.10.0]
|
||||
api-json-name: [api-base.json, api-head.json]
|
||||
include:
|
||||
- api-json-name: api-base.json
|
||||
repo-name: ${{ github.event.pull_request.base.repo.full_name }}
|
||||
ref: ${{ github.base_ref }}
|
||||
- api-json-name: api-head.json
|
||||
repo-name: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
repository: ${{ github.event.pull_request.base.repo.full_name }}
|
||||
ref: ${{ github.base_ref }}
|
||||
repository: ${{ matrix.repo-name }}
|
||||
ref: ${{ matrix.ref }}
|
||||
submodules: true
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
@ -56,121 +52,15 @@ jobs:
|
|||
run: cp .config/example.yml .config/default.yml
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
- name : Migrate
|
||||
run: pnpm migrate
|
||||
- name: Launch misskey
|
||||
run: |
|
||||
screen -S misskey -dm pnpm run dev
|
||||
sleep 30s
|
||||
- name: Wait for Misskey to be ready
|
||||
run: |
|
||||
MAX_RETRIES=12
|
||||
RETRY_DELAY=5
|
||||
count=0
|
||||
until $(curl --output /dev/null --silent --head --fail http://localhost:3000) || [[ $count -eq $MAX_RETRIES ]]; do
|
||||
printf '.'
|
||||
sleep $RETRY_DELAY
|
||||
count=$((count + 1))
|
||||
done
|
||||
|
||||
if [[ $count -eq $MAX_RETRIES ]]; then
|
||||
echo "Failed to connect to Misskey after $MAX_RETRIES attempts."
|
||||
exit 1
|
||||
fi
|
||||
- id: fetch
|
||||
name: Get api.json from Misskey
|
||||
run: |
|
||||
RESULT=$(curl --retry 5 --retry-delay 5 --retry-max-time 60 http://localhost:3000/api.json)
|
||||
echo $RESULT > api-base.json
|
||||
- name: Generate API JSON
|
||||
run: pnpm --filter backend generate-api-json
|
||||
- name: Copy API.json
|
||||
run: cp packages/backend/built/api.json ${{ matrix.api-json-name }}
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: api-artifact
|
||||
path: api-base.json
|
||||
- name: Kill Misskey Job
|
||||
run: screen -S misskey -X quit
|
||||
|
||||
get-head:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.5.1]
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:13
|
||||
ports:
|
||||
- 5432:5432
|
||||
env:
|
||||
POSTGRES_DB: misskey
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
POSTGRES_USER: example-misskey-user
|
||||
POSTGRESS_PASS: example-misskey-pass
|
||||
redis:
|
||||
image: redis:7
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
ref: ${{ github.head_ref }}
|
||||
submodules: true
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4.0.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
- run: corepack enable
|
||||
- run: pnpm i --frozen-lockfile
|
||||
- name: Check pnpm-lock.yaml
|
||||
run: git diff --exit-code pnpm-lock.yaml
|
||||
- name: Copy Configure
|
||||
run: cp .config/example.yml .config/default.yml
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
- name : Migrate
|
||||
run: pnpm migrate
|
||||
- name: Launch misskey
|
||||
run: |
|
||||
screen -S misskey -dm pnpm run dev
|
||||
sleep 30s
|
||||
- name: Wait for Misskey to be ready
|
||||
run: |
|
||||
MAX_RETRIES=12
|
||||
RETRY_DELAY=5
|
||||
count=0
|
||||
until $(curl --output /dev/null --silent --head --fail http://localhost:3000) || [[ $count -eq $MAX_RETRIES ]]; do
|
||||
printf '.'
|
||||
sleep $RETRY_DELAY
|
||||
count=$((count + 1))
|
||||
done
|
||||
|
||||
if [[ $count -eq $MAX_RETRIES ]]; then
|
||||
echo "Failed to connect to Misskey after $MAX_RETRIES attempts."
|
||||
exit 1
|
||||
fi
|
||||
- id: fetch
|
||||
name: Get api.json from Misskey
|
||||
run: |
|
||||
RESULT=$(curl --retry 5 --retry-delay 5 --retry-max-time 60 http://localhost:3000/api.json)
|
||||
echo $RESULT > api-head.json
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: api-artifact
|
||||
path: api-head.json
|
||||
- name: Kill Misskey Job
|
||||
run: screen -S misskey -X quit
|
||||
path: ${{ matrix.api-json-name }}
|
||||
|
||||
save-pr-number:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
2
.github/workflows/test-backend.yml
vendored
2
.github/workflows/test-backend.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.5.1]
|
||||
node-version: [20.10.0]
|
||||
|
||||
services:
|
||||
postgres:
|
||||
|
|
4
.github/workflows/test-frontend.yml
vendored
4
.github/workflows/test-frontend.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.5.1]
|
||||
node-version: [20.10.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
|
@ -51,7 +51,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [20.5.1]
|
||||
node-version: [20.10.0]
|
||||
browser: [chrome]
|
||||
|
||||
services:
|
||||
|
|
2
.github/workflows/test-misskey-js.yml
vendored
2
.github/workflows/test-misskey-js.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.5.1]
|
||||
node-version: [20.10.0]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
|
|
2
.github/workflows/test-production.yml
vendored
2
.github/workflows/test-production.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.5.1]
|
||||
node-version: [20.10.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
|
|
|
@ -1 +1 @@
|
|||
20.5.1
|
||||
20.10.0
|
||||
|
|
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -17,12 +17,27 @@
|
|||
### General
|
||||
- Feat: メールアドレスの認証にverifymail.ioを使えるように (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/971ba07a44550f68d2ba31c62066db2d43a0caed)
|
||||
- Feat: モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能を追加 (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/e0eb5a752f6e5616d6312bb7c9790302f9dbff83)
|
||||
- Feat: TL上からノートが見えなくなるワードミュートであるハードミュートを追加
|
||||
- Fix: MFM `$[unixtime ]` に不正な値を入力した際に発生する各種エラーを修正
|
||||
|
||||
### Client
|
||||
- Enhance: 絵文字のオートコンプリート機能強化 #12364
|
||||
- Enhance: ユーザーのRawデータを表示するページが復活
|
||||
- Enhance: リアクション選択時に音を鳴らせるように
|
||||
- fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
|
||||
- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
|
||||
- Fix: コードエディタが正しく表示されない問題を修正
|
||||
- Fix: プロフィールの「ファイル」にセンシティブな画像がある際のデザインを修正
|
||||
- Fix: 一度に大量の通知が入った際に通知音が音割れする問題を修正
|
||||
|
||||
### Server
|
||||
-
|
||||
- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように
|
||||
- Fix: 時間経過により無効化されたアンテナを再有効化したとき、サーバ再起動までその状況が反映されないのを修正 #12303
|
||||
- Fix: ロールタイムラインが保存されない問題を修正
|
||||
- Fix: api.jsonの生成ロジックを改善 #12402
|
||||
- Fix: 招待コードが使い回せる問題を修正
|
||||
- Fix: 特定の条件下でチャンネルやユーザーのノート一覧に最新のノートが表示されなくなる問題を修正
|
||||
- Fix: 何もノートしていないユーザーのフィードにアクセスするとエラーになる問題を修正
|
||||
|
||||
## 2023.11.1
|
||||
|
||||
|
@ -30,6 +45,7 @@
|
|||
- Feat: 管理者がコントロールパネルからメールアドレスの照会を行えるようになりました
|
||||
- Enhance: ローカリゼーションの更新
|
||||
- Enhance: 依存関係の更新
|
||||
- Enhance: json-schema(OpenAPIの戻り値として使用されるスキーマ定義)を出来る限り最新化 #12311
|
||||
|
||||
### Client
|
||||
- Enhance: MFMでルビを振れるように
|
||||
|
@ -38,6 +54,7 @@
|
|||
- 例: `$[unixtime 1701356400]`
|
||||
- Enhance: プラグインでエラーが発生した場合のハンドリングを強化
|
||||
- Enhance: 細かなUIのブラッシュアップ
|
||||
- Enhance: サウンド設定に「サウンドを出力しない」と「Misskeyがアクティブな時のみサウンドを出力する」を追加
|
||||
- Fix: 効果音が再生されるとデバイスで再生している動画や音声が停止する問題を修正 #12339
|
||||
- Fix: デッキに表示されたチャンネルの表示先チャンネルを切り替えた際、即座に反映されない問題を修正 #12236
|
||||
- Fix: プラグインでノートの表示を書き換えられない問題を修正
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# syntax = docker/dockerfile:1.4
|
||||
|
||||
ARG NODE_VERSION=20.5.1-bullseye
|
||||
ARG NODE_VERSION=20.10.0-bullseye
|
||||
|
||||
# build assets & compile TypeScript
|
||||
|
||||
|
|
7
locales/index.d.ts
vendored
7
locales/index.d.ts
vendored
|
@ -547,6 +547,8 @@ export interface Locale {
|
|||
"popout": string;
|
||||
"volume": string;
|
||||
"masterVolume": string;
|
||||
"notUseSound": string;
|
||||
"useSoundOnlyWhenActive": string;
|
||||
"details": string;
|
||||
"chooseEmoji": string;
|
||||
"unableToProcess": string;
|
||||
|
@ -642,6 +644,7 @@ export interface Locale {
|
|||
"smtpSecureInfo": string;
|
||||
"testEmail": string;
|
||||
"wordMute": string;
|
||||
"hardWordMute": string;
|
||||
"regexpError": string;
|
||||
"regexpErrorDescription": string;
|
||||
"instanceMute": string;
|
||||
|
@ -1039,6 +1042,7 @@ export interface Locale {
|
|||
"enableChartsForFederatedInstances": string;
|
||||
"showClipButtonInNoteFooter": string;
|
||||
"reactionsDisplaySize": string;
|
||||
"limitWidthOfReaction": string;
|
||||
"noteIdOrUrl": string;
|
||||
"video": string;
|
||||
"videos": string;
|
||||
|
@ -1641,7 +1645,9 @@ export interface Locale {
|
|||
"assignTarget": string;
|
||||
"descriptionOfAssignTarget": string;
|
||||
"manual": string;
|
||||
"manualRoles": string;
|
||||
"conditional": string;
|
||||
"conditionalRoles": string;
|
||||
"condition": string;
|
||||
"isConditionalRole": string;
|
||||
"isPublic": string;
|
||||
|
@ -1939,6 +1945,7 @@ export interface Locale {
|
|||
"notification": string;
|
||||
"antenna": string;
|
||||
"channel": string;
|
||||
"reaction": string;
|
||||
};
|
||||
"_ago": {
|
||||
"future": string;
|
||||
|
|
|
@ -544,6 +544,8 @@ showInPage: "ページで表示"
|
|||
popout: "ポップアウト"
|
||||
volume: "音量"
|
||||
masterVolume: "マスター音量"
|
||||
notUseSound: "サウンドを出力しない"
|
||||
useSoundOnlyWhenActive: "Misskeyがアクティブな時のみサウンドを出力する"
|
||||
details: "詳細"
|
||||
chooseEmoji: "絵文字を選択"
|
||||
unableToProcess: "操作を完了できません"
|
||||
|
@ -639,6 +641,7 @@ smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する"
|
|||
smtpSecureInfo: "STARTTLS使用時はオフにします。"
|
||||
testEmail: "配信テスト"
|
||||
wordMute: "ワードミュート"
|
||||
hardWordMute: "ハードワードミュート"
|
||||
regexpError: "正規表現エラー"
|
||||
regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:"
|
||||
instanceMute: "サーバーミュート"
|
||||
|
@ -1036,6 +1039,7 @@ enableChartsForRemoteUser: "リモートユーザーのチャートを生成"
|
|||
enableChartsForFederatedInstances: "リモートサーバーのチャートを生成"
|
||||
showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
|
||||
reactionsDisplaySize: "リアクションの表示サイズ"
|
||||
limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小して表示する"
|
||||
noteIdOrUrl: "ノートIDまたはURL"
|
||||
video: "動画"
|
||||
videos: "動画"
|
||||
|
@ -1551,7 +1555,9 @@ _role:
|
|||
assignTarget: "アサイン"
|
||||
descriptionOfAssignTarget: "<b>マニュアル</b>は誰がこのロールに含まれるかを手動で管理します。\n<b>コンディショナル</b>は条件を設定し、それに合致するユーザーが自動で含まれるようになります。"
|
||||
manual: "マニュアル"
|
||||
manualRoles: "マニュアルロール"
|
||||
conditional: "コンディショナル"
|
||||
conditionalRoles: "コンディショナルロール"
|
||||
condition: "条件"
|
||||
isConditionalRole: "これはコンディショナルロールです。"
|
||||
isPublic: "公開ロール"
|
||||
|
@ -1844,6 +1850,7 @@ _sfx:
|
|||
notification: "通知"
|
||||
antenna: "アンテナ受信"
|
||||
channel: "チャンネル通知"
|
||||
reaction: "リアクション選択時"
|
||||
|
||||
_ago:
|
||||
future: "未来"
|
||||
|
@ -1855,7 +1862,7 @@ _ago:
|
|||
weeksAgo: "{n}週間前"
|
||||
monthsAgo: "{n}ヶ月前"
|
||||
yearsAgo: "{n}年前"
|
||||
invalid: "ありません"
|
||||
invalid: "日時の解析に失敗"
|
||||
|
||||
_timeIn:
|
||||
seconds: "{n}秒後"
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.31",
|
||||
"terser": "5.24.0",
|
||||
"typescript": "5.2.2"
|
||||
"typescript": "5.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "6.11.0",
|
||||
|
|
8
packages/backend/generate_api_json.js
Normal file
8
packages/backend/generate_api_json.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { loadConfig } from './built/config.js'
|
||||
import { genOpenapiSpec } from './built/server/api/openapi/gen-spec.js'
|
||||
import { writeFileSync } from "node:fs";
|
||||
|
||||
const config = loadConfig();
|
||||
const spec = genOpenapiSpec(config);
|
||||
|
||||
writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');
|
11
packages/backend/migration/1700383825690-hard-mute.js
Normal file
11
packages/backend/migration/1700383825690-hard-mute.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export class HardMute1700383825690 {
|
||||
name = 'HardMute1700383825690'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "hardMutedWords" jsonb NOT NULL DEFAULT '[]'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "hardMutedWords"`);
|
||||
}
|
||||
}
|
|
@ -23,7 +23,8 @@
|
|||
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit",
|
||||
"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
|
||||
"test": "pnpm jest",
|
||||
"test-and-coverage": "pnpm jest-and-coverage"
|
||||
"test-and-coverage": "pnpm jest-and-coverage",
|
||||
"generate-api-json": "node ./generate_api_json.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-android-arm64": "1.3.11",
|
||||
|
@ -165,7 +166,7 @@
|
|||
"tsconfig-paths": "4.2.0",
|
||||
"twemoji-parser": "14.0.0",
|
||||
"typeorm": "0.3.17",
|
||||
"typescript": "5.2.2",
|
||||
"typescript": "5.3.2",
|
||||
"ulid": "2.3.0",
|
||||
"vary": "1.1.2",
|
||||
"web-push": "3.6.6",
|
||||
|
|
|
@ -16,7 +16,7 @@ import type { AntennasRepository, UserListMembershipsRepository } from '@/models
|
|||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
|
@ -39,7 +39,7 @@ export class AntennaService implements OnApplicationShutdown {
|
|||
|
||||
private utilityService: UtilityService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
) {
|
||||
this.antennasFetched = false;
|
||||
this.antennas = [];
|
||||
|
@ -60,11 +60,21 @@ export class AntennaService implements OnApplicationShutdown {
|
|||
lastUsedAt: new Date(body.lastUsedAt),
|
||||
});
|
||||
break;
|
||||
case 'antennaUpdated':
|
||||
this.antennas[this.antennas.findIndex(a => a.id === body.id)] = {
|
||||
...body,
|
||||
lastUsedAt: new Date(body.lastUsedAt),
|
||||
};
|
||||
case 'antennaUpdated': {
|
||||
const idx = this.antennas.findIndex(a => a.id === body.id);
|
||||
if (idx >= 0) {
|
||||
this.antennas[idx] = {
|
||||
...body,
|
||||
lastUsedAt: new Date(body.lastUsedAt),
|
||||
};
|
||||
} else {
|
||||
// サーバ起動時にactiveじゃなかった場合、リストに持っていないので追加する必要あり
|
||||
this.antennas.push({
|
||||
...body,
|
||||
lastUsedAt: new Date(body.lastUsedAt),
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'antennaDeleted':
|
||||
this.antennas = this.antennas.filter(a => a.id !== body.id);
|
||||
|
@ -84,7 +94,7 @@ export class AntennaService implements OnApplicationShutdown {
|
|||
const redisPipeline = this.redisForTimelines.pipeline();
|
||||
|
||||
for (const antenna of matchedAntennas) {
|
||||
this.funoutTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline);
|
||||
this.fanoutTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline);
|
||||
this.globalEventService.publishAntennaStream(antenna.id, 'note', note);
|
||||
}
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ import { FileInfoService } from './FileInfoService.js';
|
|||
import { SearchService } from './SearchService.js';
|
||||
import { ClipService } from './ClipService.js';
|
||||
import { FeaturedService } from './FeaturedService.js';
|
||||
import { FunoutTimelineService } from './FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from './FanoutTimelineService.js';
|
||||
import { ChannelFollowingService } from './ChannelFollowingService.js';
|
||||
import { RegistryApiService } from './RegistryApiService.js';
|
||||
import { ChartLoggerService } from './chart/ChartLoggerService.js';
|
||||
|
@ -194,7 +194,7 @@ const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: Fi
|
|||
const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService };
|
||||
const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService };
|
||||
const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService };
|
||||
const $FunoutTimelineService: Provider = { provide: 'FunoutTimelineService', useExisting: FunoutTimelineService };
|
||||
const $FanoutTimelineService: Provider = { provide: 'FanoutTimelineService', useExisting: FanoutTimelineService };
|
||||
const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
|
||||
const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
|
||||
|
||||
|
@ -330,7 +330,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
SearchService,
|
||||
ClipService,
|
||||
FeaturedService,
|
||||
FunoutTimelineService,
|
||||
FanoutTimelineService,
|
||||
ChannelFollowingService,
|
||||
RegistryApiService,
|
||||
ChartLoggerService,
|
||||
|
@ -459,7 +459,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$SearchService,
|
||||
$ClipService,
|
||||
$FeaturedService,
|
||||
$FunoutTimelineService,
|
||||
$FanoutTimelineService,
|
||||
$ChannelFollowingService,
|
||||
$RegistryApiService,
|
||||
$ChartLoggerService,
|
||||
|
@ -589,7 +589,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
SearchService,
|
||||
ClipService,
|
||||
FeaturedService,
|
||||
FunoutTimelineService,
|
||||
FanoutTimelineService,
|
||||
ChannelFollowingService,
|
||||
RegistryApiService,
|
||||
FederationChart,
|
||||
|
@ -717,7 +717,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$SearchService,
|
||||
$ClipService,
|
||||
$FeaturedService,
|
||||
$FunoutTimelineService,
|
||||
$FanoutTimelineService,
|
||||
$ChannelFollowingService,
|
||||
$RegistryApiService,
|
||||
$FederationChart,
|
||||
|
|
|
@ -10,7 +10,7 @@ import { bindThis } from '@/decorators.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
|
||||
@Injectable()
|
||||
export class FunoutTimelineService {
|
||||
export class FanoutTimelineService {
|
||||
constructor(
|
||||
@Inject(DI.redisForTimelines)
|
||||
private redisForTimelines: Redis.Redis,
|
|
@ -5,11 +5,12 @@
|
|||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import type { MiNote, MiUser } from '@/models/_.js';
|
||||
import type { MiGalleryPost, MiNote, MiUser } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
const GLOBAL_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと
|
||||
export const GALLERY_POSTS_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと
|
||||
const PER_USER_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 7; // 1週間ごと
|
||||
const HASHTAG_RANKING_WINDOW = 1000 * 60 * 60; // 1時間ごと
|
||||
|
||||
|
@ -79,6 +80,11 @@ export class FeaturedService {
|
|||
return this.updateRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, noteId, score);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public updateGalleryPostsRanking(galleryPostId: MiGalleryPost['id'], score = 1): Promise<void> {
|
||||
return this.updateRankingOf('featuredGalleryPostsRanking', GALLERY_POSTS_RANKING_WINDOW, galleryPostId, score);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public updateInChannelNotesRanking(channelId: MiNote['channelId'], noteId: MiNote['id'], score = 1): Promise<void> {
|
||||
return this.updateRankingOf(`featuredInChannelNotesRanking:${channelId}`, GLOBAL_NOTES_RANKING_WINDOW, noteId, score);
|
||||
|
@ -99,6 +105,11 @@ export class FeaturedService {
|
|||
return this.getRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, threshold);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getGalleryPostsRanking(threshold: number): Promise<MiGalleryPost['id'][]> {
|
||||
return this.getRankingOf('featuredGalleryPostsRanking', GALLERY_POSTS_RANKING_WINDOW, threshold);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getInChannelNotesRanking(channelId: MiNote['channelId'], threshold: number): Promise<MiNote['id'][]> {
|
||||
return this.getRankingOf(`featuredInChannelNotesRanking:${channelId}`, GLOBAL_NOTES_RANKING_WINDOW, threshold);
|
||||
|
|
|
@ -250,6 +250,12 @@ export class MfmService {
|
|||
}
|
||||
}
|
||||
|
||||
function fnDefault(node: mfm.MfmFn) {
|
||||
const el = doc.createElement('i');
|
||||
appendChildren(node.children, el);
|
||||
return el;
|
||||
}
|
||||
|
||||
const handlers: { [K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => any } = {
|
||||
bold: (node) => {
|
||||
const el = doc.createElement('b');
|
||||
|
@ -276,17 +282,68 @@ export class MfmService {
|
|||
},
|
||||
|
||||
fn: (node) => {
|
||||
if (node.props.name === 'unixtime') {
|
||||
const text = node.children[0]!.type === 'text' ? node.children[0].props.text : '';
|
||||
const date = new Date(parseInt(text, 10) * 1000);
|
||||
const el = doc.createElement('time');
|
||||
el.setAttribute('datetime', date.toISOString());
|
||||
el.textContent = date.toISOString();
|
||||
return el;
|
||||
} else {
|
||||
const el = doc.createElement('i');
|
||||
appendChildren(node.children, el);
|
||||
return el;
|
||||
switch (node.props.name) {
|
||||
case 'unixtime': {
|
||||
const text = node.children[0].type === 'text' ? node.children[0].props.text : '';
|
||||
try {
|
||||
const date = new Date(parseInt(text, 10) * 1000);
|
||||
const el = doc.createElement('time');
|
||||
el.setAttribute('datetime', date.toISOString());
|
||||
el.textContent = date.toISOString();
|
||||
return el;
|
||||
} catch (err) {
|
||||
return fnDefault(node);
|
||||
}
|
||||
}
|
||||
|
||||
case 'ruby': {
|
||||
if (node.children.length === 1) {
|
||||
const child = node.children[0];
|
||||
const text = child.type === 'text' ? child.props.text : '';
|
||||
const rubyEl = doc.createElement('ruby');
|
||||
const rtEl = doc.createElement('rt');
|
||||
|
||||
// ruby未対応のHTMLサニタイザーを通したときにルビが「劉備(りゅうび)」となるようにする
|
||||
const rpStartEl = doc.createElement('rp');
|
||||
rpStartEl.appendChild(doc.createTextNode('('));
|
||||
const rpEndEl = doc.createElement('rp');
|
||||
rpEndEl.appendChild(doc.createTextNode(')'));
|
||||
|
||||
rubyEl.appendChild(doc.createTextNode(text.split(' ')[0]));
|
||||
rtEl.appendChild(doc.createTextNode(text.split(' ')[1]));
|
||||
rubyEl.appendChild(rpStartEl);
|
||||
rubyEl.appendChild(rtEl);
|
||||
rubyEl.appendChild(rpEndEl);
|
||||
return rubyEl;
|
||||
} else {
|
||||
const rt = node.children.at(-1);
|
||||
|
||||
if (!rt) {
|
||||
return fnDefault(node);
|
||||
}
|
||||
|
||||
const text = rt.type === 'text' ? rt.props.text : '';
|
||||
const rubyEl = doc.createElement('ruby');
|
||||
const rtEl = doc.createElement('rt');
|
||||
|
||||
// ruby未対応のHTMLサニタイザーを通したときにルビが「劉備(りゅうび)」となるようにする
|
||||
const rpStartEl = doc.createElement('rp');
|
||||
rpStartEl.appendChild(doc.createTextNode('('));
|
||||
const rpEndEl = doc.createElement('rp');
|
||||
rpEndEl.appendChild(doc.createTextNode(')'));
|
||||
|
||||
appendChildren(node.children.slice(0, node.children.length - 1), rubyEl);
|
||||
rtEl.appendChild(doc.createTextNode(text.trim()));
|
||||
rubyEl.appendChild(rpStartEl);
|
||||
rubyEl.appendChild(rtEl);
|
||||
rubyEl.appendChild(rpEndEl);
|
||||
return rubyEl;
|
||||
}
|
||||
}
|
||||
|
||||
default: {
|
||||
return fnDefault(node);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ import { RoleService } from '@/core/RoleService.js';
|
|||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { SearchService } from '@/core/SearchService.js';
|
||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||
|
||||
|
@ -194,7 +194,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
private idService: IdService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private queueService: QueueService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private noteReadService: NoteReadService,
|
||||
private notificationService: NotificationService,
|
||||
private relayService: RelayService,
|
||||
|
@ -843,9 +843,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
const r = this.redisForTimelines.pipeline();
|
||||
|
||||
if (note.channelId) {
|
||||
this.funoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
|
||||
this.fanoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
|
||||
|
||||
this.funoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
||||
this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
||||
|
||||
const channelFollowings = await this.channelFollowingsRepository.find({
|
||||
where: {
|
||||
|
@ -855,9 +855,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
});
|
||||
|
||||
for (const channelFollowing of channelFollowings) {
|
||||
this.funoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.funoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -895,9 +895,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
if (!following.withReplies) continue;
|
||||
}
|
||||
|
||||
this.funoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.funoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -913,36 +913,36 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
if (!userListMembership.withReplies) continue;
|
||||
}
|
||||
|
||||
this.funoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r);
|
||||
this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.funoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r);
|
||||
this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r);
|
||||
}
|
||||
}
|
||||
|
||||
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL
|
||||
this.funoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.funoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
}
|
||||
}
|
||||
|
||||
// 自分自身以外への返信
|
||||
if (note.replyId && note.replyUserId !== note.userId) {
|
||||
this.funoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
||||
this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
||||
|
||||
if (note.visibility === 'public' && note.userHost == null) {
|
||||
this.funoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
|
||||
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
|
||||
}
|
||||
} else {
|
||||
this.funoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
||||
this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.funoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r);
|
||||
this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r);
|
||||
}
|
||||
|
||||
if (note.visibility === 'public' && note.userHost == null) {
|
||||
this.funoutTimelineService.push('localTimeline', note.id, 1000, r);
|
||||
this.fanoutTimelineService.push('localTimeline', note.id, 1000, r);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.funoutTimelineService.push('localTimelineWithFiles', note.id, 500, r);
|
||||
this.fanoutTimelineService.push('localTimelineWithFiles', note.id, 500, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import { IdService } from '@/core/IdService.js';
|
|||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
export type RolePolicies = {
|
||||
|
@ -87,6 +87,9 @@ export class RoleService implements OnApplicationShutdown {
|
|||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.redisForTimelines)
|
||||
private redisForTimelines: Redis.Redis,
|
||||
|
||||
@Inject(DI.redisForSub)
|
||||
private redisForSub: Redis.Redis,
|
||||
|
||||
|
@ -105,7 +108,7 @@ export class RoleService implements OnApplicationShutdown {
|
|||
private globalEventService: GlobalEventService,
|
||||
private idService: IdService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
) {
|
||||
//this.onMessage = this.onMessage.bind(this);
|
||||
|
||||
|
@ -476,10 +479,10 @@ export class RoleService implements OnApplicationShutdown {
|
|||
public async addNoteToRoleTimeline(note: Packed<'Note'>): Promise<void> {
|
||||
const roles = await this.getUserRoles(note.userId);
|
||||
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
const redisPipeline = this.redisForTimelines.pipeline();
|
||||
|
||||
for (const role of roles) {
|
||||
this.funoutTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline);
|
||||
this.fanoutTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline);
|
||||
this.globalEventService.publishRoleTimelineStream(role.id, 'note', note);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import { CacheService } from '@/core/CacheService.js';
|
|||
import type { Config } from '@/config.js';
|
||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import Logger from '../logger.js';
|
||||
|
||||
const logger = new Logger('following/create');
|
||||
|
@ -84,7 +84,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
private webhookService: WebhookService,
|
||||
private apRendererService: ApRendererService,
|
||||
private accountMoveService: AccountMoveService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private perUserFollowingChart: PerUserFollowingChart,
|
||||
private instanceChart: InstanceChart,
|
||||
) {
|
||||
|
@ -305,7 +305,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
}
|
||||
});
|
||||
|
||||
this.funoutTimelineService.purge(`homeTimeline:${follower.id}`);
|
||||
this.fanoutTimelineService.purge(`homeTimeline:${follower.id}`);
|
||||
}
|
||||
|
||||
// Publish followed event
|
||||
|
@ -374,7 +374,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
}
|
||||
});
|
||||
|
||||
this.funoutTimelineService.purge(`homeTimeline:${follower.id}`);
|
||||
this.fanoutTimelineService.purge(`homeTimeline:${follower.id}`);
|
||||
}
|
||||
|
||||
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
|
||||
|
|
|
@ -198,12 +198,14 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
});
|
||||
} else if (notification.type === 'renote:grouped') {
|
||||
const users = await Promise.all(notification.userIds.map(userId => {
|
||||
const user = hint?.packedUsers != null
|
||||
? hint.packedUsers.get(userId)
|
||||
: this.userEntityService.pack(userId!, { id: meId }, {
|
||||
detail: false,
|
||||
});
|
||||
return user;
|
||||
const packedUser = hint?.packedUsers != null ? hint.packedUsers.get(userId) : null;
|
||||
if (packedUser) {
|
||||
return packedUser;
|
||||
}
|
||||
|
||||
return this.userEntityService.pack(userId, { id: meId }, {
|
||||
detail: false,
|
||||
});
|
||||
}));
|
||||
return await awaitAll({
|
||||
id: notification.id,
|
||||
|
|
|
@ -473,6 +473,7 @@ export class UserEntityService implements OnModuleInit {
|
|||
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
|
||||
unreadNotificationsCount: notificationsInfo?.unreadCount,
|
||||
mutedWords: profile!.mutedWords,
|
||||
hardMutedWords: profile!.hardMutedWords,
|
||||
mutedInstances: profile!.mutedInstances,
|
||||
mutingNotificationTypes: [], // 後方互換性のため
|
||||
notificationRecieveConfig: profile!.notificationRecieveConfig,
|
||||
|
|
|
@ -215,7 +215,12 @@ export class MiUserProfile {
|
|||
@Column('jsonb', {
|
||||
default: [],
|
||||
})
|
||||
public mutedWords: string[][];
|
||||
public mutedWords: (string[] | string)[];
|
||||
|
||||
@Column('jsonb', {
|
||||
default: [],
|
||||
})
|
||||
public hardMutedWords: (string[] | string)[];
|
||||
|
||||
@Column('jsonb', {
|
||||
default: [],
|
||||
|
|
|
@ -42,11 +42,15 @@ export const packedAnnouncementSchema = {
|
|||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
forYou: {
|
||||
needConfirmationToRead: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
needConfirmationToRead: {
|
||||
silence: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
forYou: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
|
|
|
@ -19,7 +19,7 @@ export const packedChannelSchema = {
|
|||
},
|
||||
lastNotedAt: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
nullable: true, optional: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
name: {
|
||||
|
@ -28,38 +28,18 @@ export const packedChannelSchema = {
|
|||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
bannerUrl: {
|
||||
type: 'string',
|
||||
format: 'url',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
isArchived: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
notesCount: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
usersCount: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
isFollowing: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
isFavorited: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
format: 'id',
|
||||
},
|
||||
bannerUrl: {
|
||||
type: 'string',
|
||||
format: 'url',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
pinnedNoteIds: {
|
||||
type: 'array',
|
||||
nullable: false, optional: false,
|
||||
|
@ -72,6 +52,18 @@ export const packedChannelSchema = {
|
|||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isArchived: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
usersCount: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
notesCount: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
isSensitive: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
@ -80,5 +72,22 @@ export const packedChannelSchema = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isFollowing: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
isFavorited: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
pinnedNotes: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'Note',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
@ -44,13 +44,13 @@ export const packedClipSchema = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isFavorited: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
favoritedCount: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isFavorited: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
@ -74,7 +74,7 @@ export const packedDriveFileSchema = {
|
|||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: false, nullable: false,
|
||||
format: 'url',
|
||||
},
|
||||
thumbnailUrl: {
|
||||
|
|
|
@ -21,6 +21,12 @@ export const packedDriveFolderSchema = {
|
|||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
parentId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
format: 'id',
|
||||
example: 'xxxxxxxxxx',
|
||||
},
|
||||
foldersCount: {
|
||||
type: 'number',
|
||||
optional: true, nullable: false,
|
||||
|
@ -29,12 +35,6 @@ export const packedDriveFolderSchema = {
|
|||
type: 'number',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
parentId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
format: 'id',
|
||||
example: 'xxxxxxxxxx',
|
||||
},
|
||||
parent: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
|
|
|
@ -79,6 +79,10 @@ export const packedFederationInstanceSchema = {
|
|||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
isSilenced: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
iconUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
|
@ -93,11 +97,6 @@ export const packedFederationInstanceSchema = {
|
|||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
isSilenced: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
infoUpdatedAt: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
|
|
|
@ -22,6 +22,16 @@ export const packedFlashSchema = {
|
|||
optional: false, nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
|
@ -34,16 +44,6 @@ export const packedFlashSchema = {
|
|||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
likedCount: {
|
||||
type: 'number',
|
||||
optional: false, nullable: true,
|
||||
|
|
|
@ -22,16 +22,16 @@ export const packedFollowingSchema = {
|
|||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
followee: {
|
||||
type: 'object',
|
||||
optional: true, nullable: false,
|
||||
ref: 'UserDetailed',
|
||||
},
|
||||
followerId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
followee: {
|
||||
type: 'object',
|
||||
optional: true, nullable: false,
|
||||
ref: 'UserDetailed',
|
||||
},
|
||||
follower: {
|
||||
type: 'object',
|
||||
optional: true, nullable: false,
|
||||
|
|
|
@ -22,14 +22,6 @@ export const packedGalleryPostSchema = {
|
|||
optional: false, nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
|
@ -40,6 +32,14 @@ export const packedGalleryPostSchema = {
|
|||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
fileIds: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
|
@ -70,5 +70,13 @@ export const packedGalleryPostSchema = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
likedCount: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isLiked: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
@ -127,22 +127,26 @@ export const packedNoteSchema = {
|
|||
channel: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
isSensitive: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
color: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isSensitive: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
allowRenoteToExternal: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -42,13 +42,9 @@ export const packedNotificationSchema = {
|
|||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
choice: {
|
||||
type: 'number',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
invitation: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
achievement: {
|
||||
type: 'string',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
|
@ -81,14 +77,14 @@ export const packedNotificationSchema = {
|
|||
required: ['user', 'reaction'],
|
||||
},
|
||||
},
|
||||
},
|
||||
users: {
|
||||
type: 'array',
|
||||
optional: true, nullable: true,
|
||||
items: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
users: {
|
||||
type: 'array',
|
||||
optional: true, nullable: true,
|
||||
items: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
@ -22,6 +22,32 @@ export const packedPageSchema = {
|
|||
optional: false, nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
content: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
variables: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
|
@ -34,23 +60,47 @@ export const packedPageSchema = {
|
|||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
content: {
|
||||
type: 'array',
|
||||
hideTitleWhenPinned: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
variables: {
|
||||
type: 'array',
|
||||
alignCenter: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userId: {
|
||||
font: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
ref: 'UserLite',
|
||||
script: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
eyeCatchingImageId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
eyeCatchingImage: {
|
||||
type: 'object',
|
||||
optional: false, nullable: true,
|
||||
ref: 'DriveFile',
|
||||
},
|
||||
attachedFiles: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'DriveFile',
|
||||
},
|
||||
},
|
||||
likedCount: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isLiked: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
@ -49,11 +49,6 @@ export const packedUserLiteSchema = {
|
|||
nullable: false, optional: false,
|
||||
format: 'id',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
format: 'url',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
angle: {
|
||||
type: 'number',
|
||||
nullable: false, optional: true,
|
||||
|
@ -62,19 +57,14 @@ export const packedUserLiteSchema = {
|
|||
type: 'boolean',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
format: 'url',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
isAdmin: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: true,
|
||||
default: false,
|
||||
},
|
||||
isModerator: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: true,
|
||||
default: false,
|
||||
},
|
||||
isBot: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: true,
|
||||
|
@ -83,12 +73,67 @@ export const packedUserLiteSchema = {
|
|||
type: 'boolean',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
instance: {
|
||||
type: 'object',
|
||||
nullable: false, optional: true,
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
softwareName: {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
softwareVersion: {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
iconUrl: {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
faviconUrl: {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
themeColor: {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
emojis: {
|
||||
type: 'object',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
onlineStatus: {
|
||||
type: 'string',
|
||||
format: 'url',
|
||||
nullable: true, optional: false,
|
||||
nullable: false, optional: false,
|
||||
enum: ['unknown', 'online', 'active', 'offline'],
|
||||
},
|
||||
badgeRoles: {
|
||||
type: 'array',
|
||||
nullable: false, optional: true,
|
||||
items: {
|
||||
type: 'object',
|
||||
nullable: false, optional: false,
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
iconUrl: {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
displayOrder: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
@ -105,21 +150,18 @@ export const packedUserDetailedNotMeOnlySchema = {
|
|||
format: 'uri',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
movedToUri: {
|
||||
movedTo: {
|
||||
type: 'string',
|
||||
format: 'uri',
|
||||
nullable: true,
|
||||
optional: false,
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
alsoKnownAs: {
|
||||
type: 'array',
|
||||
nullable: true,
|
||||
optional: false,
|
||||
nullable: true, optional: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
format: 'id',
|
||||
nullable: false,
|
||||
optional: false,
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
|
@ -249,6 +291,11 @@ export const packedUserDetailedNotMeOnlySchema = {
|
|||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
ffVisibility: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
enum: ['public', 'followers', 'private'],
|
||||
},
|
||||
twoFactorEnabled: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
|
@ -264,6 +311,57 @@ export const packedUserDetailedNotMeOnlySchema = {
|
|||
nullable: false, optional: false,
|
||||
default: false,
|
||||
},
|
||||
roles: {
|
||||
type: 'array',
|
||||
nullable: false, optional: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
nullable: false, optional: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
format: 'id',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
color: {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
iconUrl: {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
isModerator: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
isAdministrator: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
displayOrder: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
memo: {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
moderationNote: {
|
||||
type: 'string',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
//#region relations
|
||||
isFollowing: {
|
||||
type: 'boolean',
|
||||
|
@ -297,10 +395,6 @@ export const packedUserDetailedNotMeOnlySchema = {
|
|||
type: 'boolean',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
memo: {
|
||||
type: 'string',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
notify: {
|
||||
type: 'string',
|
||||
nullable: false, optional: true,
|
||||
|
@ -326,29 +420,37 @@ export const packedMeDetailedOnlySchema = {
|
|||
nullable: true, optional: false,
|
||||
format: 'id',
|
||||
},
|
||||
injectFeaturedNote: {
|
||||
isModerator: {
|
||||
type: 'boolean',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
isAdmin: {
|
||||
type: 'boolean',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
injectFeaturedNote: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
receiveAnnouncementEmail: {
|
||||
type: 'boolean',
|
||||
nullable: true, optional: false,
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
alwaysMarkNsfw: {
|
||||
type: 'boolean',
|
||||
nullable: true, optional: false,
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
autoSensitive: {
|
||||
type: 'boolean',
|
||||
nullable: true, optional: false,
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
carefulBot: {
|
||||
type: 'boolean',
|
||||
nullable: true, optional: false,
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
autoAcceptFollowed: {
|
||||
type: 'boolean',
|
||||
nullable: true, optional: false,
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
noCrawle: {
|
||||
type: 'boolean',
|
||||
|
@ -387,10 +489,23 @@ export const packedMeDetailedOnlySchema = {
|
|||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
unreadAnnouncements: {
|
||||
type: 'array',
|
||||
nullable: false, optional: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
nullable: false, optional: false,
|
||||
ref: 'Announcement',
|
||||
},
|
||||
},
|
||||
hasUnreadAntenna: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
hasUnreadChannel: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
hasUnreadNotification: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
|
@ -415,6 +530,18 @@ export const packedMeDetailedOnlySchema = {
|
|||
},
|
||||
},
|
||||
},
|
||||
hardMutedWords: {
|
||||
type: 'array',
|
||||
nullable: false, optional: false,
|
||||
items: {
|
||||
type: 'array',
|
||||
nullable: false, optional: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
mutedInstances: {
|
||||
type: 'array',
|
||||
nullable: true, optional: false,
|
||||
|
@ -429,12 +556,132 @@ export const packedMeDetailedOnlySchema = {
|
|||
},
|
||||
emailNotificationTypes: {
|
||||
type: 'array',
|
||||
nullable: true, optional: false,
|
||||
nullable: false, optional: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
achievements: {
|
||||
type: 'array',
|
||||
nullable: false, optional: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
nullable: false, optional: false,
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
unlockedAt: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
loggedInDays: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
policies: {
|
||||
type: 'object',
|
||||
nullable: false, optional: false,
|
||||
properties: {
|
||||
gtlAvailable: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
ltlAvailable: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canPublicNote: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canInvite: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
inviteLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
inviteLimitCycle: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
inviteExpirationTime: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canManageCustomEmojis: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canManageAvatarDecorations: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canSearchNotes: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canUseTranslator: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
canHideAds: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
driveCapacityMb: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
alwaysMarkNsfw: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
pinLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
antennaLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
wordMuteLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
webhookLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
clipLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
noteEachClipsLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
userListLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
userEachUserListsLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
rateLimitFactor: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
//#region secrets
|
||||
email: {
|
||||
type: 'string',
|
||||
|
@ -511,5 +758,13 @@ export const packedUserSchema = {
|
|||
type: 'object',
|
||||
ref: 'UserDetailed',
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
ref: 'UserDetailedNotMe',
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
ref: 'MeDetailed',
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
|
|
@ -126,7 +126,7 @@ export class SignupApiService {
|
|||
code: invitationCode,
|
||||
});
|
||||
|
||||
if (ticket == null) {
|
||||
if (ticket == null || ticket.usedById != null) {
|
||||
reply.code(400);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ export const paramDef = {
|
|||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
publishing: { type: 'boolean', default: false },
|
||||
publishing: { type: 'boolean', default: null, nullable: true },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
@ -37,8 +37,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId);
|
||||
if (ps.publishing) {
|
||||
if (ps.publishing === true) {
|
||||
query.andWhere('ad.expiresAt > :now', { now: new Date() }).andWhere('ad.startsAt <= :now', { now: new Date() });
|
||||
} else if (ps.publishing === false) {
|
||||
query.andWhere('ad.expiresAt <= :now', { now: new Date() }).orWhere('ad.startsAt > :now', { now: new Date() });
|
||||
}
|
||||
const ads = await query.limit(ps.limit).getMany();
|
||||
|
||||
|
|
|
@ -267,6 +267,14 @@ export const meta = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
enableVerifymailApi: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
verifymailAuthKey: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
enableChartsForRemoteUser: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
@ -421,6 +429,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
deeplIsPro: instance.deeplIsPro,
|
||||
enableIpLogging: instance.enableIpLogging,
|
||||
enableActiveEmailValidation: instance.enableActiveEmailValidation,
|
||||
enableVerifymailApi: instance.enableVerifymailApi,
|
||||
verifymailAuthKey: instance.verifymailAuthKey,
|
||||
enableChartsForRemoteUser: instance.enableChartsForRemoteUser,
|
||||
enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances,
|
||||
enableServerMachineStats: instance.enableServerMachineStats,
|
||||
|
|
|
@ -12,7 +12,8 @@ import { NoteReadService } from '@/core/NoteReadService.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
@ -70,7 +71,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private noteEntityService: NoteEntityService,
|
||||
private queryService: QueryService,
|
||||
private noteReadService: NoteReadService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
|
@ -85,12 +87,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.noSuchAntenna);
|
||||
}
|
||||
|
||||
this.antennasRepository.update(antenna.id, {
|
||||
isActive: true,
|
||||
lastUsedAt: new Date(),
|
||||
});
|
||||
// falseだった場合はアンテナの配信先が増えたことを通知したい
|
||||
const needPublishEvent = !antenna.isActive;
|
||||
|
||||
let noteIds = await this.funoutTimelineService.get(`antennaTimeline:${antenna.id}`, untilId, sinceId);
|
||||
antenna.isActive = true;
|
||||
antenna.lastUsedAt = new Date();
|
||||
this.antennasRepository.update(antenna.id, antenna);
|
||||
|
||||
if (needPublishEvent) {
|
||||
this.globalEventService.publishInternalEvent('antennaUpdated', antenna);
|
||||
}
|
||||
|
||||
let noteIds = await this.fanoutTimelineService.get(`antennaTimeline:${antenna.id}`, untilId, sinceId);
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
|
|
|
@ -12,9 +12,10 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
@ -69,15 +70,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private idService: IdService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
private queryService: QueryService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private cacheService: CacheService,
|
||||
private activeUsersChart: ActiveUsersChart,
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||
const isRangeSpecified = untilId != null && sinceId != null;
|
||||
|
||||
const serverSettings = await this.metaService.fetch();
|
||||
|
||||
const channel = await this.channelsRepository.findOneBy({
|
||||
id: ps.channelId,
|
||||
});
|
||||
|
@ -88,14 +92,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
if (me) this.activeUsersChart.read(me);
|
||||
|
||||
if (isRangeSpecified || sinceId == null) {
|
||||
if (serverSettings.enableFanoutTimeline && (isRangeSpecified || sinceId == null)) {
|
||||
const [
|
||||
userIdsWhoMeMuting,
|
||||
] = me ? await Promise.all([
|
||||
this.cacheService.userMutingsCache.fetch(me.id),
|
||||
]) : [new Set<string>()];
|
||||
|
||||
let noteIds = await this.funoutTimelineService.get(`channelTimeline:${channel.id}`, untilId, sinceId);
|
||||
let noteIds = await this.fanoutTimelineService.get(`channelTimeline:${channel.id}`, untilId, sinceId);
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
|
||||
if (noteIds.length > 0) {
|
||||
|
|
|
@ -16,12 +16,9 @@ export const meta = {
|
|||
requireCredential: false,
|
||||
|
||||
res: {
|
||||
oneOf: [{
|
||||
type: 'object',
|
||||
ref: 'FederationInstance',
|
||||
}, {
|
||||
type: 'null',
|
||||
}],
|
||||
type: 'object',
|
||||
optional: false, nullable: true,
|
||||
ref: 'FederationInstance',
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
|||
import type { GalleryPostsRepository } from '@/models/_.js';
|
||||
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['gallery'],
|
||||
|
@ -27,25 +28,49 @@ export const meta = {
|
|||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
private galleryPostsRankingCache: string[] = [];
|
||||
private galleryPostsRankingCacheLastFetchedAt = 0;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.galleryPostsRepository)
|
||||
private galleryPostsRepository: GalleryPostsRepository,
|
||||
|
||||
private galleryPostEntityService: GalleryPostEntityService,
|
||||
private featuredService: FeaturedService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.galleryPostsRepository.createQueryBuilder('post')
|
||||
.andWhere('post.createdAt > :date', { date: new Date(Date.now() - (1000 * 60 * 60 * 24 * 3)) })
|
||||
.andWhere('post.likedCount > 0')
|
||||
.orderBy('post.likedCount', 'DESC');
|
||||
let postIds: string[];
|
||||
if (this.galleryPostsRankingCacheLastFetchedAt !== 0 && (Date.now() - this.galleryPostsRankingCacheLastFetchedAt < 1000 * 60 * 30)) {
|
||||
postIds = this.galleryPostsRankingCache;
|
||||
} else {
|
||||
postIds = await this.featuredService.getGalleryPostsRanking(100);
|
||||
this.galleryPostsRankingCache = postIds;
|
||||
this.galleryPostsRankingCacheLastFetchedAt = Date.now();
|
||||
}
|
||||
|
||||
const posts = await query.limit(10).getMany();
|
||||
postIds.sort((a, b) => a > b ? -1 : 1);
|
||||
if (ps.untilId) {
|
||||
postIds = postIds.filter(id => id < ps.untilId!);
|
||||
}
|
||||
postIds = postIds.slice(0, ps.limit);
|
||||
|
||||
if (postIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const query = this.galleryPostsRepository.createQueryBuilder('post')
|
||||
.where('post.id IN (:...postIds)', { postIds: postIds });
|
||||
|
||||
const posts = await query.getMany();
|
||||
|
||||
return await this.galleryPostEntityService.packMany(posts, me);
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { GalleryLikesRepository, GalleryPostsRepository } from '@/models/_.js';
|
||||
import { FeaturedService, GALLERY_POSTS_RANKING_WINDOW } from '@/core/FeaturedService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
@ -57,6 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
@Inject(DI.galleryLikesRepository)
|
||||
private galleryLikesRepository: GalleryLikesRepository,
|
||||
|
||||
private featuredService: FeaturedService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
|
@ -88,6 +90,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
userId: me.id,
|
||||
});
|
||||
|
||||
// ランキング更新
|
||||
if (Date.now() - this.idService.parse(post.id).date.getTime() < GALLERY_POSTS_RANKING_WINDOW) {
|
||||
await this.featuredService.updateGalleryPostsRanking(post.id, 1);
|
||||
}
|
||||
|
||||
this.galleryPostsRepository.increment({ id: post.id }, 'likedCount', 1);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { GalleryPostsRepository, GalleryLikesRepository } from '@/models/_.js';
|
||||
import { FeaturedService, GALLERY_POSTS_RANKING_WINDOW } from '@/core/FeaturedService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
|
@ -49,6 +51,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
@Inject(DI.galleryLikesRepository)
|
||||
private galleryLikesRepository: GalleryLikesRepository,
|
||||
|
||||
private featuredService: FeaturedService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const post = await this.galleryPostsRepository.findOneBy({ id: ps.postId });
|
||||
|
@ -68,6 +73,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
// Delete like
|
||||
await this.galleryLikesRepository.delete(exist.id);
|
||||
|
||||
// ランキング更新
|
||||
if (Date.now() - this.idService.parse(post.id).date.getTime() < GALLERY_POSTS_RANKING_WINDOW) {
|
||||
await this.featuredService.updateGalleryPostsRanking(post.id, -1);
|
||||
}
|
||||
|
||||
this.galleryPostsRepository.decrement({ id: post.id }, 'likedCount', 1);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -123,6 +123,11 @@ export const meta = {
|
|||
},
|
||||
} as const;
|
||||
|
||||
const muteWords = { type: 'array', items: { oneOf: [
|
||||
{ type: 'array', items: { type: 'string' } },
|
||||
{ type: 'string' }
|
||||
] } } as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
@ -171,7 +176,8 @@ export const paramDef = {
|
|||
autoSensitive: { type: 'boolean' },
|
||||
ffVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
|
||||
pinnedPageId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
mutedWords: { type: 'array' },
|
||||
mutedWords: muteWords,
|
||||
hardMutedWords: muteWords,
|
||||
mutedInstances: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
|
@ -234,16 +240,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (ps.location !== undefined) profileUpdates.location = ps.location;
|
||||
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
|
||||
if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility;
|
||||
if (ps.mutedWords !== undefined) {
|
||||
|
||||
function checkMuteWordCount(mutedWords: (string[] | string)[], limit: number) {
|
||||
// TODO: ちゃんと数える
|
||||
const length = JSON.stringify(ps.mutedWords).length;
|
||||
if (length > (await this.roleService.getUserPolicies(user.id)).wordMuteLimit) {
|
||||
const length = JSON.stringify(mutedWords).length;
|
||||
if (length > limit) {
|
||||
throw new ApiError(meta.errors.tooManyMutedWords);
|
||||
}
|
||||
}
|
||||
|
||||
// validate regular expression syntax
|
||||
ps.mutedWords.filter(x => !Array.isArray(x)).forEach(x => {
|
||||
const regexp = x.match(/^\/(.+)\/(.*)$/);
|
||||
function validateMuteWordRegex(mutedWords: (string[] | string)[]) {
|
||||
for (const mutedWord of mutedWords) {
|
||||
if (typeof mutedWord !== "string") continue;
|
||||
|
||||
const regexp = mutedWord.match(/^\/(.+)\/(.*)$/);
|
||||
if (!regexp) throw new ApiError(meta.errors.invalidRegexp);
|
||||
|
||||
try {
|
||||
|
@ -251,11 +261,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
} catch (err) {
|
||||
throw new ApiError(meta.errors.invalidRegexp);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (ps.mutedWords !== undefined) {
|
||||
checkMuteWordCount(ps.mutedWords, (await this.roleService.getUserPolicies(user.id)).wordMuteLimit);
|
||||
validateMuteWordRegex(ps.mutedWords);
|
||||
|
||||
profileUpdates.mutedWords = ps.mutedWords;
|
||||
profileUpdates.enableWordMute = ps.mutedWords.length > 0;
|
||||
}
|
||||
if (ps.hardMutedWords !== undefined) {
|
||||
checkMuteWordCount(ps.hardMutedWords, (await this.roleService.getUserPolicies(user.id)).wordMuteLimit);
|
||||
validateMuteWordRegex(ps.hardMutedWords);
|
||||
profileUpdates.hardMutedWords = ps.hardMutedWords;
|
||||
}
|
||||
if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances;
|
||||
if (ps.notificationRecieveConfig !== undefined) profileUpdates.notificationRecieveConfig = ps.notificationRecieveConfig;
|
||||
if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked;
|
||||
|
|
|
@ -262,7 +262,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (renote.channelId && renote.channelId !== ps.channelId) {
|
||||
// チャンネルのノートに対しリノート要求がきたとき、チャンネル外へのリノート可否をチェック
|
||||
// リノートのユースケースのうち、チャンネル内→チャンネル外は少数だと考えられるため、JOINはせず必要な時に都度取得する
|
||||
const renoteChannel = await this.channelsRepository.findOneById(renote.channelId);
|
||||
const renoteChannel = await this.channelsRepository.findOneBy({ id: renote.channelId });
|
||||
if (renoteChannel == null) {
|
||||
// リノートしたいノートが書き込まれているチャンネルが無い
|
||||
throw new ApiError(meta.errors.noSuchChannel);
|
||||
|
|
|
@ -64,16 +64,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
}
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
noteIds.sort((a, b) => a > b ? -1 : 1);
|
||||
if (ps.untilId) {
|
||||
noteIds = noteIds.filter(id => id < ps.untilId!);
|
||||
}
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const query = this.notesRepository.createQueryBuilder('note')
|
||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
|
|
|
@ -14,7 +14,7 @@ import { RoleService } from '@/core/RoleService.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
|
@ -77,7 +77,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private activeUsersChart: ActiveUsersChart,
|
||||
private idService: IdService,
|
||||
private cacheService: CacheService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private queryService: QueryService,
|
||||
private userFollowingService: UserFollowingService,
|
||||
private metaService: MetaService,
|
||||
|
@ -120,20 +120,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
let shouldFallbackToDb = false;
|
||||
|
||||
if (ps.withFiles) {
|
||||
const [htlNoteIds, ltlNoteIds] = await this.funoutTimelineService.getMulti([
|
||||
const [htlNoteIds, ltlNoteIds] = await this.fanoutTimelineService.getMulti([
|
||||
`homeTimelineWithFiles:${me.id}`,
|
||||
'localTimelineWithFiles',
|
||||
], untilId, sinceId);
|
||||
noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
|
||||
} else if (ps.withReplies) {
|
||||
const [htlNoteIds, ltlNoteIds, ltlReplyNoteIds] = await this.funoutTimelineService.getMulti([
|
||||
const [htlNoteIds, ltlNoteIds, ltlReplyNoteIds] = await this.fanoutTimelineService.getMulti([
|
||||
`homeTimeline:${me.id}`,
|
||||
'localTimeline',
|
||||
'localTimelineWithReplies',
|
||||
], untilId, sinceId);
|
||||
noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds, ...ltlReplyNoteIds]));
|
||||
} else {
|
||||
const [htlNoteIds, ltlNoteIds] = await this.funoutTimelineService.getMulti([
|
||||
const [htlNoteIds, ltlNoteIds] = await this.fanoutTimelineService.getMulti([
|
||||
`homeTimeline:${me.id}`,
|
||||
'localTimeline',
|
||||
], untilId, sinceId);
|
||||
|
|
|
@ -14,7 +14,7 @@ import { RoleService } from '@/core/RoleService.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
|
@ -69,7 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private activeUsersChart: ActiveUsersChart,
|
||||
private idService: IdService,
|
||||
private cacheService: CacheService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private queryService: QueryService,
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
|
@ -107,9 +107,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
let noteIds: string[];
|
||||
|
||||
if (ps.withFiles) {
|
||||
noteIds = await this.funoutTimelineService.get('localTimelineWithFiles', untilId, sinceId);
|
||||
noteIds = await this.fanoutTimelineService.get('localTimelineWithFiles', untilId, sinceId);
|
||||
} else {
|
||||
const [nonReplyNoteIds, replyNoteIds] = await this.funoutTimelineService.getMulti([
|
||||
const [nonReplyNoteIds, replyNoteIds] = await this.fanoutTimelineService.getMulti([
|
||||
'localTimeline',
|
||||
'localTimelineWithReplies',
|
||||
], untilId, sinceId);
|
||||
|
|
|
@ -14,7 +14,7 @@ import { DI } from '@/di-symbols.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
|
@ -65,7 +65,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private activeUsersChart: ActiveUsersChart,
|
||||
private idService: IdService,
|
||||
private cacheService: CacheService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private userFollowingService: UserFollowingService,
|
||||
private queryService: QueryService,
|
||||
private metaService: MetaService,
|
||||
|
@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
this.cacheService.userBlockedCache.fetch(me.id),
|
||||
]);
|
||||
|
||||
let noteIds = await this.funoutTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId);
|
||||
let noteIds = await this.fanoutTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId);
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
|
||||
let redisTimeline: MiNote[] = [];
|
||||
|
|
|
@ -13,7 +13,7 @@ import { DI } from '@/di-symbols.js';
|
|||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { MiLocalUser } from '@/models/User.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
|
@ -81,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private activeUsersChart: ActiveUsersChart,
|
||||
private cacheService: CacheService,
|
||||
private idService: IdService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private queryService: QueryService,
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
|
@ -123,7 +123,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
this.cacheService.userBlockedCache.fetch(me.id),
|
||||
]);
|
||||
|
||||
let noteIds = await this.funoutTimelineService.get(ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, untilId, sinceId);
|
||||
let noteIds = await this.fanoutTimelineService.get(ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, untilId, sinceId);
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
|
||||
let redisTimeline: MiNote[] = [];
|
||||
|
|
|
@ -11,7 +11,7 @@ import { QueryService } from '@/core/QueryService.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
@ -66,7 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private idService: IdService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
private queryService: QueryService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
|
@ -84,7 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
return [];
|
||||
}
|
||||
|
||||
let noteIds = await this.funoutTimelineService.get(`roleTimeline:${role.id}`, untilId, sinceId);
|
||||
let noteIds = await this.fanoutTimelineService.get(`roleTimeline:${role.id}`, untilId, sinceId);
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
|
|
|
@ -14,7 +14,8 @@ import { CacheService } from '@/core/CacheService.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
@ -70,7 +71,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private queryService: QueryService,
|
||||
private cacheService: CacheService,
|
||||
private idService: IdService,
|
||||
private funoutTimelineService: FunoutTimelineService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
|
@ -78,7 +80,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
const isRangeSpecified = untilId != null && sinceId != null;
|
||||
const isSelf = me && (me.id === ps.userId);
|
||||
|
||||
if (isRangeSpecified || sinceId == null) {
|
||||
const serverSettings = await this.metaService.fetch();
|
||||
|
||||
if (serverSettings.enableFanoutTimeline && (isRangeSpecified || sinceId == null)) {
|
||||
const [
|
||||
userIdsWhoMeMuting,
|
||||
] = me ? await Promise.all([
|
||||
|
@ -86,9 +90,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
]) : [new Set<string>()];
|
||||
|
||||
const [noteIdsRes, repliesNoteIdsRes, channelNoteIdsRes] = await Promise.all([
|
||||
this.funoutTimelineService.get(ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, untilId, sinceId),
|
||||
ps.withReplies ? this.funoutTimelineService.get(`userTimelineWithReplies:${ps.userId}`, untilId, sinceId) : Promise.resolve([]),
|
||||
ps.withChannelNotes ? this.funoutTimelineService.get(`userTimelineWithChannel:${ps.userId}`, untilId, sinceId) : Promise.resolve([]),
|
||||
this.fanoutTimelineService.get(ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, untilId, sinceId),
|
||||
ps.withReplies ? this.fanoutTimelineService.get(`userTimelineWithReplies:${ps.userId}`, untilId, sinceId) : Promise.resolve([]),
|
||||
ps.withChannelNotes ? this.fanoutTimelineService.get(`userTimelineWithChannel:${ps.userId}`, untilId, sinceId) : Promise.resolve([]),
|
||||
]);
|
||||
|
||||
let noteIds = Array.from(new Set([
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import type { Config } from '@/config.js';
|
||||
import endpoints from '../endpoints.js';
|
||||
import endpoints, { IEndpoint } from '../endpoints.js';
|
||||
import { errors as basicErrors } from './errors.js';
|
||||
import { schemas, convertSchemaToOpenApiSchema } from './schemas.js';
|
||||
|
||||
|
@ -33,16 +33,17 @@ export function genOpenapiSpec(config: Config) {
|
|||
schemas: schemas,
|
||||
|
||||
securitySchemes: {
|
||||
ApiKeyAuth: {
|
||||
type: 'apiKey',
|
||||
in: 'body',
|
||||
name: 'i',
|
||||
bearerAuth: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
|
||||
// 書き換えたりするのでディープコピーしておく。そのまま編集するとメモリ上の値が汚れて次回以降の出力に影響する
|
||||
const copiedEndpoints = JSON.parse(JSON.stringify(endpoints)) as IEndpoint[];
|
||||
for (const endpoint of copiedEndpoints.filter(ep => !ep.meta.secure)) {
|
||||
const errors = {} as any;
|
||||
|
||||
if (endpoint.meta.errors) {
|
||||
|
@ -79,6 +80,13 @@ export function genOpenapiSpec(config: Config) {
|
|||
schema.required = [...schema.required ?? [], 'file'];
|
||||
}
|
||||
|
||||
if (schema.required && schema.required.length <= 0) {
|
||||
// 空配列は許可されない
|
||||
schema.required = undefined;
|
||||
}
|
||||
|
||||
const hasBody = (schema.type === 'object' && schema.properties && Object.keys(schema.properties).length >= 1);
|
||||
|
||||
const info = {
|
||||
operationId: endpoint.name,
|
||||
summary: endpoint.name,
|
||||
|
@ -92,17 +100,19 @@ export function genOpenapiSpec(config: Config) {
|
|||
} : {}),
|
||||
...(endpoint.meta.requireCredential ? {
|
||||
security: [{
|
||||
ApiKeyAuth: [],
|
||||
bearerAuth: [],
|
||||
}],
|
||||
} : {}),
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
[requestType]: {
|
||||
schema,
|
||||
...(hasBody ? {
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
[requestType]: {
|
||||
schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} : {}),
|
||||
responses: {
|
||||
...(endpoint.meta.res ? {
|
||||
'200': {
|
||||
|
@ -118,6 +128,11 @@ export function genOpenapiSpec(config: Config) {
|
|||
description: 'OK (without any results)',
|
||||
},
|
||||
}),
|
||||
...(endpoint.meta.res?.optional === true || endpoint.meta.res?.nullable === true ? {
|
||||
'204': {
|
||||
description: 'OK (without any results)',
|
||||
},
|
||||
} : {}),
|
||||
'400': {
|
||||
description: 'Client error',
|
||||
content: {
|
||||
|
@ -190,6 +205,7 @@ export function genOpenapiSpec(config: Config) {
|
|||
};
|
||||
|
||||
spec.paths['/' + endpoint.name] = {
|
||||
...(endpoint.meta.allowGet ? { get: info } : {}),
|
||||
post: info,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,10 +7,16 @@ import type { Schema } from '@/misc/json-schema.js';
|
|||
import { refs } from '@/misc/json-schema.js';
|
||||
|
||||
export function convertSchemaToOpenApiSchema(schema: Schema) {
|
||||
const res: any = schema;
|
||||
// optional, refはスキーマ定義に含まれないので分離しておく
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { optional, ref, ...res }: any = schema;
|
||||
|
||||
if (schema.type === 'object' && schema.properties) {
|
||||
res.required = Object.entries(schema.properties).filter(([k, v]) => !v.optional).map(([k]) => k);
|
||||
const required = Object.entries(schema.properties).filter(([k, v]) => !v.optional).map(([k]) => k);
|
||||
if (required.length > 0) {
|
||||
// 空配列は許可されない
|
||||
res.required = required;
|
||||
}
|
||||
|
||||
for (const k of Object.keys(schema.properties)) {
|
||||
res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k]);
|
||||
|
|
|
@ -58,7 +58,7 @@ export class FeedService {
|
|||
const feed = new Feed({
|
||||
id: author.link,
|
||||
title: `${author.name} (@${user.username}@${this.config.host})`,
|
||||
updated: this.idService.parse(notes[0].id).date,
|
||||
updated: notes.length !== 0 ? this.idService.parse(notes[0].id).date : undefined,
|
||||
generator: 'Misskey',
|
||||
description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,
|
||||
link: author.link,
|
||||
|
|
|
@ -93,7 +93,7 @@ describe('Webリソース', () => {
|
|||
});
|
||||
aliceChannel = await channel(alice, {});
|
||||
|
||||
bob = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
}, 1000 * 60 * 2);
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -152,6 +152,11 @@ describe('Webリソース', () => {
|
|||
type,
|
||||
}));
|
||||
|
||||
test('がGETできる。(ノートが存在しない場合でも。)', async () => await ok({
|
||||
path: path(bob.username),
|
||||
type,
|
||||
}));
|
||||
|
||||
test('は存在しないユーザーはGETできない。', async () => await notOk({
|
||||
path: path('nonexisting'),
|
||||
status: 404,
|
||||
|
|
|
@ -168,6 +168,7 @@ describe('ユーザー', () => {
|
|||
hasPendingReceivedFollowRequest: user.hasPendingReceivedFollowRequest,
|
||||
unreadAnnouncements: user.unreadAnnouncements,
|
||||
mutedWords: user.mutedWords,
|
||||
hardMutedWords: user.hardMutedWords,
|
||||
mutedInstances: user.mutedInstances,
|
||||
mutingNotificationTypes: user.mutingNotificationTypes,
|
||||
notificationRecieveConfig: user.notificationRecieveConfig,
|
||||
|
|
BIN
packages/frontend/assets/sounds/syuilo/bubble1.mp3
Normal file
BIN
packages/frontend/assets/sounds/syuilo/bubble1.mp3
Normal file
Binary file not shown.
BIN
packages/frontend/assets/sounds/syuilo/bubble2.mp3
Normal file
BIN
packages/frontend/assets/sounds/syuilo/bubble2.mp3
Normal file
Binary file not shown.
|
@ -69,7 +69,7 @@
|
|||
"tsc-alias": "1.8.8",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"twemoji-parser": "14.0.0",
|
||||
"typescript": "5.2.2",
|
||||
"typescript": "5.3.2",
|
||||
"uuid": "9.0.1",
|
||||
"v-code-diff": "1.7.2",
|
||||
"vanilla-tilt": "1.8.1",
|
||||
|
|
|
@ -204,12 +204,16 @@ export async function common(createVue: () => App<Element>) {
|
|||
|
||||
if (defaultStore.state.keepScreenOn) {
|
||||
if ('wakeLock' in navigator) {
|
||||
navigator.wakeLock.request('screen');
|
||||
|
||||
document.addEventListener('visibilitychange', async () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
navigator.wakeLock.request('screen');
|
||||
}
|
||||
navigator.wakeLock.request('screen')
|
||||
.then(() => {
|
||||
document.addEventListener('visibilitychange', async () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
navigator.wakeLock.request('screen');
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// If Permission fails on an AppleDevice such as Safari
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<Mfm :text="report.comment"/>
|
||||
</div>
|
||||
<hr/>
|
||||
<div>{{ i18n.ts.reporter }}: <MkAcct :user="report.reporter"/></div>
|
||||
<div>{{ i18n.ts.reporter }}: <MkA :to="`/admin/user/${report.reporter.id}`" class="_link">@{{ report.reporter.username }}</MkA></div>
|
||||
<div v-if="report.assignee">
|
||||
{{ i18n.ts.moderator }}:
|
||||
<MkAcct :user="report.assignee"/>
|
||||
|
|
|
@ -242,29 +242,7 @@ function exec() {
|
|||
return;
|
||||
}
|
||||
|
||||
const matched: EmojiDef[] = [];
|
||||
const max = 30;
|
||||
|
||||
emojiDb.value.some(x => {
|
||||
if (x.name.startsWith(props.q ?? '') && !x.aliasOf && !matched.some(y => y.emoji === x.emoji)) matched.push(x);
|
||||
return matched.length === max;
|
||||
});
|
||||
|
||||
if (matched.length < max) {
|
||||
emojiDb.value.some(x => {
|
||||
if (x.name.startsWith(props.q ?? '') && !matched.some(y => y.emoji === x.emoji)) matched.push(x);
|
||||
return matched.length === max;
|
||||
});
|
||||
}
|
||||
|
||||
if (matched.length < max) {
|
||||
emojiDb.value.some(x => {
|
||||
if (x.name.includes(props.q ?? '') && !matched.some(y => y.emoji === x.emoji)) matched.push(x);
|
||||
return matched.length === max;
|
||||
});
|
||||
}
|
||||
|
||||
emojis.value = matched;
|
||||
emojis.value = emojiAutoComplete(props.q, emojiDb.value);
|
||||
} else if (props.type === 'mfmTag') {
|
||||
if (!props.q || props.q === '') {
|
||||
mfmTags.value = MFM_TAGS;
|
||||
|
@ -275,6 +253,78 @@ function exec() {
|
|||
}
|
||||
}
|
||||
|
||||
type EmojiScore = { emoji: EmojiDef, score: number };
|
||||
|
||||
function emojiAutoComplete(query: string | null, emojiDb: EmojiDef[], max = 30): EmojiDef[] {
|
||||
if (!query) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const matched = new Map<string, EmojiScore>();
|
||||
|
||||
// 前方一致(エイリアスなし)
|
||||
emojiDb.some(x => {
|
||||
if (x.name.startsWith(query) && !x.aliasOf) {
|
||||
matched.set(x.name, { emoji: x, score: query.length + 1 });
|
||||
}
|
||||
return matched.size === max;
|
||||
});
|
||||
|
||||
// 前方一致(エイリアス込み)
|
||||
if (matched.size < max) {
|
||||
emojiDb.some(x => {
|
||||
if (x.name.startsWith(query) && !matched.has(x.aliasOf ?? x.name)) {
|
||||
matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length });
|
||||
}
|
||||
return matched.size === max;
|
||||
});
|
||||
}
|
||||
|
||||
// 部分一致(エイリアス込み)
|
||||
if (matched.size < max) {
|
||||
emojiDb.some(x => {
|
||||
if (x.name.includes(query) && !matched.has(x.aliasOf ?? x.name)) {
|
||||
matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length - 1 });
|
||||
}
|
||||
return matched.size === max;
|
||||
});
|
||||
}
|
||||
|
||||
// 簡易あいまい検索(3文字以上)
|
||||
if (matched.size < max && query.length > 3) {
|
||||
const queryChars = [...query];
|
||||
const hitEmojis = new Map<string, EmojiScore>();
|
||||
|
||||
for (const x of emojiDb) {
|
||||
// 文字列の位置を進めながら、クエリの文字を順番に探す
|
||||
|
||||
let pos = 0;
|
||||
let hit = 0;
|
||||
for (const c of queryChars) {
|
||||
pos = x.name.indexOf(c, pos);
|
||||
if (pos <= -1) break;
|
||||
hit++;
|
||||
}
|
||||
|
||||
// 半分以上の文字が含まれていればヒットとする
|
||||
if (hit > Math.ceil(queryChars.length / 2) && hit - 2 > (matched.get(x.aliasOf ?? x.name)?.score ?? 0)) {
|
||||
hitEmojis.set(x.aliasOf ?? x.name, { emoji: x, score: hit - 2 });
|
||||
}
|
||||
}
|
||||
|
||||
// ヒットしたものを全部追加すると雑多になるので、先頭の6件程度だけにしておく(6件=オートコンプリートのポップアップのサイズ分)
|
||||
[...hitEmojis.values()]
|
||||
.sort((x, y) => y.score - x.score)
|
||||
.slice(0, 6)
|
||||
.forEach(it => matched.set(it.emoji.name, it));
|
||||
}
|
||||
|
||||
return [...matched.values()]
|
||||
.sort((x, y) => y.score - x.score)
|
||||
.slice(0, max)
|
||||
.map(it => it.emoji);
|
||||
}
|
||||
|
||||
function onMousedown(event: Event) {
|
||||
if (!contains(rootEl.value, event.target) && (rootEl.value !== event.target)) props.close();
|
||||
}
|
||||
|
|
|
@ -139,6 +139,10 @@ watch(v, () => {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.textarea, .codeEditorHighlighter {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -154,6 +158,8 @@ watch(v, () => {
|
|||
background-color: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
min-width: calc(100% - 24px);
|
||||
height: calc(100% - 24px);
|
||||
padding: 12px;
|
||||
line-height: 1.5em;
|
||||
font-size: 1em;
|
||||
|
|
|
@ -37,6 +37,7 @@ import * as Misskey from 'misskey-js';
|
|||
import bytes from '@/filters/bytes.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import hasAudio from '@/scripts/media-has-audio.js';
|
||||
|
||||
const props = defineProps<{
|
||||
video: Misskey.entities.DriveFile;
|
||||
|
@ -49,6 +50,12 @@ const videoEl = shallowRef<HTMLVideoElement>();
|
|||
watch(videoEl, () => {
|
||||
if (videoEl.value) {
|
||||
videoEl.value.volume = 0.3;
|
||||
hasAudio(videoEl.value).then(had => {
|
||||
if (!had) {
|
||||
videoEl.value.loop = videoEl.value.muted = true;
|
||||
videoEl.value.play();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<div
|
||||
v-if="!muted"
|
||||
v-if="!hardMuted && !muted"
|
||||
v-show="!isDeleted"
|
||||
ref="el"
|
||||
v-hotkey="keymap"
|
||||
|
@ -133,7 +133,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div v-else :class="$style.muted" @click="muted = false">
|
||||
<div v-else-if="!hardMuted" :class="$style.muted" @click="muted = false">
|
||||
<I18n :src="i18n.ts.userSaysSomething" tag="small">
|
||||
<template #name>
|
||||
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
|
||||
|
@ -163,6 +163,7 @@ import { focusPrev, focusNext } from '@/scripts/focus.js';
|
|||
import { checkWordMute } from '@/scripts/check-word-mute.js';
|
||||
import { userPage } from '@/filters/user.js';
|
||||
import * as os from '@/os.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
import { defaultStore, noteViewInterruptors } from '@/store.js';
|
||||
import { reactionPicker } from '@/scripts/reaction-picker.js';
|
||||
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
|
||||
|
@ -183,6 +184,7 @@ const props = withDefaults(defineProps<{
|
|||
note: Misskey.entities.Note;
|
||||
pinned?: boolean;
|
||||
mock?: boolean;
|
||||
withHardMute?: boolean;
|
||||
}>(), {
|
||||
mock: false,
|
||||
});
|
||||
|
@ -239,13 +241,23 @@ const urls = $computed(() => parsed ? extractUrlFromMfm(parsed) : null);
|
|||
const isLong = shouldCollapsed(appearNote, urls ?? []);
|
||||
const collapsed = ref(appearNote.cw == null && isLong);
|
||||
const isDeleted = ref(false);
|
||||
const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false);
|
||||
const muted = ref(checkMute(appearNote, $i?.mutedWords));
|
||||
const hardMuted = ref(props.withHardMute && checkMute(appearNote, $i?.hardMutedWords));
|
||||
const translation = ref<any>(null);
|
||||
const translating = ref(false);
|
||||
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
|
||||
const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || (appearNote.visibility === 'followers' && appearNote.userId === $i.id));
|
||||
let renoteCollapsed = $ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.userId || $i.id === appearNote.userId)) || (appearNote.myReaction != null)));
|
||||
|
||||
function checkMute(note: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null): boolean {
|
||||
if (mutedWords == null) return false;
|
||||
|
||||
if (checkWordMute(note, $i, mutedWords)) return true;
|
||||
if (note.reply && checkWordMute(note.reply, $i, mutedWords)) return true;
|
||||
if (note.renote && checkWordMute(note.renote, $i, mutedWords)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const keymap = {
|
||||
'r': () => reply(true),
|
||||
'e|a|plus': () => react(true),
|
||||
|
@ -325,6 +337,8 @@ function react(viaKeyboard = false): void {
|
|||
pleaseLogin();
|
||||
showMovedDialog();
|
||||
if (appearNote.reactionAcceptance === 'likeOnly') {
|
||||
sound.play('reaction');
|
||||
|
||||
if (props.mock) {
|
||||
return;
|
||||
}
|
||||
|
@ -343,6 +357,8 @@ function react(viaKeyboard = false): void {
|
|||
} else {
|
||||
blur();
|
||||
reactionPicker.show(reactButton.value, reaction => {
|
||||
sound.play('reaction');
|
||||
|
||||
if (props.mock) {
|
||||
emit('reaction', reaction);
|
||||
return;
|
||||
|
|
|
@ -210,6 +210,7 @@ import { checkWordMute } from '@/scripts/check-word-mute.js';
|
|||
import { userPage } from '@/filters/user.js';
|
||||
import { notePage } from '@/filters/note.js';
|
||||
import * as os from '@/os.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
import { defaultStore, noteViewInterruptors } from '@/store.js';
|
||||
import { reactionPicker } from '@/scripts/reaction-picker.js';
|
||||
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
|
||||
|
@ -369,6 +370,8 @@ function react(viaKeyboard = false): void {
|
|||
pleaseLogin();
|
||||
showMovedDialog();
|
||||
if (appearNote.reactionAcceptance === 'likeOnly') {
|
||||
sound.play('reaction');
|
||||
|
||||
os.api('notes/reactions/create', {
|
||||
noteId: appearNote.id,
|
||||
reaction: '❤️',
|
||||
|
@ -383,6 +386,8 @@ function react(viaKeyboard = false): void {
|
|||
} else {
|
||||
blur();
|
||||
reactionPicker.show(reactButton.value, reaction => {
|
||||
sound.play('reaction');
|
||||
|
||||
os.api('notes/reactions/create', {
|
||||
noteId: appearNote.id,
|
||||
reaction: reaction,
|
||||
|
|
|
@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:ad="true"
|
||||
:class="$style.notes"
|
||||
>
|
||||
<MkNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note"/>
|
||||
<MkNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note" :withHardMute="true"/>
|
||||
</MkDateSeparatedList>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template #default="{ items: notifications }">
|
||||
<MkDateSeparatedList v-slot="{ item: notification }" :class="$style.list" :items="notifications" :noGap="true">
|
||||
<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note"/>
|
||||
<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note" :withHardMute="true"/>
|
||||
<XNotification v-else :key="notification.id" :notification="notification" :withTime="true" :full="true" class="_panel"/>
|
||||
</MkDateSeparatedList>
|
||||
</template>
|
||||
|
|
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]"
|
||||
@click="toggleReaction()"
|
||||
>
|
||||
<MkReactionIcon :class="$style.icon" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
|
||||
<MkReactionIcon :class="defaultStore.state.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
|
||||
<span :class="$style.count">{{ count }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
@ -28,6 +28,7 @@ import MkReactionEffect from '@/components/MkReactionEffect.vue';
|
|||
import { claimAchievement } from '@/scripts/achievements.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
|
||||
const props = defineProps<{
|
||||
reaction: string;
|
||||
|
@ -59,6 +60,10 @@ async function toggleReaction() {
|
|||
});
|
||||
if (confirm.canceled) return;
|
||||
|
||||
if (oldReaction !== props.reaction) {
|
||||
sound.play('reaction');
|
||||
}
|
||||
|
||||
if (mock) {
|
||||
emit('reactionToggled', props.reaction, (props.count - 1));
|
||||
return;
|
||||
|
@ -75,6 +80,8 @@ async function toggleReaction() {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
sound.play('reaction');
|
||||
|
||||
if (mock) {
|
||||
emit('reactionToggled', props.reaction, (props.count + 1));
|
||||
return;
|
||||
|
@ -188,7 +195,7 @@ if (!mock) {
|
|||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
.limitWidth {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<iframe
|
||||
ref="tweet"
|
||||
allow="fullscreen;web-share"
|
||||
sandbox="allow-popups allow-scripts allow-same-origin"
|
||||
sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin"
|
||||
scrolling="no"
|
||||
:style="{ position: 'relative', width: '100%', height: `${tweetHeight}px`, border: 0 }"
|
||||
:src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"
|
||||
|
|
|
@ -28,12 +28,25 @@ const props = withDefaults(defineProps<{
|
|||
mode: 'relative',
|
||||
});
|
||||
|
||||
const _time = props.time == null ? NaN :
|
||||
typeof props.time === 'number' ? props.time :
|
||||
(props.time instanceof Date ? props.time : new Date(props.time)).getTime();
|
||||
function getDateSafe(n: Date | string | number) {
|
||||
try {
|
||||
if (n instanceof Date) {
|
||||
return n;
|
||||
}
|
||||
return new Date(n);
|
||||
} catch (err) {
|
||||
return {
|
||||
getTime: () => NaN,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line vue/no-setup-props-destructure
|
||||
const _time = props.time == null ? NaN : getDateSafe(props.time).getTime();
|
||||
const invalid = Number.isNaN(_time);
|
||||
const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid;
|
||||
|
||||
// eslint-disable-next-line vue/no-setup-props-destructure
|
||||
let now = $ref((props.origin ?? new Date()).getTime());
|
||||
const ago = $computed(() => (now - _time) / 1000/*ms*/);
|
||||
|
||||
|
|
|
@ -65,9 +65,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<img src="https://avatars.githubusercontent.com/u/67428053?v=4" :class="$style.contributorAvatar">
|
||||
<span :class="$style.contributorUsername">@kakkokari-gtyih</span>
|
||||
</a>
|
||||
<a href="https://github.com/taichanNE30" target="_blank" :class="$style.contributor">
|
||||
<a href="https://github.com/tai-cha" target="_blank" :class="$style.contributor">
|
||||
<img src="https://avatars.githubusercontent.com/u/40626578?v=4" :class="$style.contributorAvatar">
|
||||
<span :class="$style.contributorUsername">@taichanNE30</span>
|
||||
<span :class="$style.contributorUsername">@tai-cha</span>
|
||||
</a>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
|
|
@ -9,12 +9,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<XHeader :actions="headerActions" :tabs="headerTabs"/>
|
||||
</template>
|
||||
<MkSpacer :contentMax="900">
|
||||
<MkSwitch :modelValue="publishing" @update:modelValue="onChangePublishing">
|
||||
{{ i18n.ts.publishing }}
|
||||
</MkSwitch>
|
||||
<MkSelect v-model="filterType" :class="$style.input" @update:modelValue="filterItems">
|
||||
<template #label>{{ i18n.ts.state }}</template>
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="publishing">{{ i18n.ts.publishing }}</option>
|
||||
<option value="expired">{{ i18n.ts.expired }}</option>
|
||||
</MkSelect>
|
||||
<div>
|
||||
<div v-for="ad in ads" class="_panel _gaps_m" :class="$style.ad">
|
||||
<MkAd v-if="ad.url" :specify="ad"/>
|
||||
<MkAd v-if="ad.url" :key="ad.id" :specify="ad"/>
|
||||
<MkInput v-model="ad.url" type="url">
|
||||
<template #label>URL</template>
|
||||
</MkInput>
|
||||
|
@ -82,14 +85,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import XHeader from './_header_.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import FormSplit from '@/components/form/split.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
@ -101,24 +104,34 @@ let ads: any[] = $ref([]);
|
|||
const localTime = new Date();
|
||||
const localTimeDiff = localTime.getTimezoneOffset() * 60 * 1000;
|
||||
const daysOfWeek: string[] = [i18n.ts._weekday.sunday, i18n.ts._weekday.monday, i18n.ts._weekday.tuesday, i18n.ts._weekday.wednesday, i18n.ts._weekday.thursday, i18n.ts._weekday.friday, i18n.ts._weekday.saturday];
|
||||
let publishing = false;
|
||||
const filterType = ref('all');
|
||||
let publishing: boolean | null = null;
|
||||
|
||||
os.api('admin/ad/list', { publishing: publishing }).then(adsResponse => {
|
||||
ads = adsResponse.map(r => {
|
||||
const exdate = new Date(r.expiresAt);
|
||||
const stdate = new Date(r.startsAt);
|
||||
exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
|
||||
stdate.setMilliseconds(stdate.getMilliseconds() - localTimeDiff);
|
||||
return {
|
||||
...r,
|
||||
expiresAt: exdate.toISOString().slice(0, 16),
|
||||
startsAt: stdate.toISOString().slice(0, 16),
|
||||
};
|
||||
});
|
||||
if (adsResponse != null) {
|
||||
ads = adsResponse.map(r => {
|
||||
const exdate = new Date(r.expiresAt);
|
||||
const stdate = new Date(r.startsAt);
|
||||
exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
|
||||
stdate.setMilliseconds(stdate.getMilliseconds() - localTimeDiff);
|
||||
return {
|
||||
...r,
|
||||
expiresAt: exdate.toISOString().slice(0, 16),
|
||||
startsAt: stdate.toISOString().slice(0, 16),
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const onChangePublishing = (v) => {
|
||||
publishing = v;
|
||||
const filterItems = (v) => {
|
||||
if (v === 'publishing') {
|
||||
publishing = true;
|
||||
} else if (v === 'expired') {
|
||||
publishing = false;
|
||||
} else {
|
||||
publishing = null;
|
||||
}
|
||||
|
||||
refresh();
|
||||
};
|
||||
|
||||
|
@ -197,6 +210,7 @@ function save(ad) {
|
|||
|
||||
function more() {
|
||||
os.api('admin/ad/list', { untilId: ads.reduce((acc, ad) => ad.id != null ? ad : acc).id, publishing: publishing }).then(adsResponse => {
|
||||
if (adsResponse == null) return;
|
||||
ads = ads.concat(adsResponse.map(r => {
|
||||
const exdate = new Date(r.expiresAt);
|
||||
const stdate = new Date(r.startsAt);
|
||||
|
@ -213,6 +227,7 @@ function more() {
|
|||
|
||||
function refresh() {
|
||||
os.api('admin/ad/list', { publishing: publishing }).then(adsResponse => {
|
||||
if (adsResponse == null) return;
|
||||
ads = adsResponse.map(r => {
|
||||
const exdate = new Date(r.expiresAt);
|
||||
const stdate = new Date(r.startsAt);
|
||||
|
@ -252,4 +267,7 @@ definePageMetadata({
|
|||
margin-bottom: var(--margin);
|
||||
}
|
||||
}
|
||||
.input {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -198,13 +198,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkButton primary rounded @click="create"><i class="ti ti-plus"></i> {{ i18n.ts._role.new }}</MkButton>
|
||||
<div class="_gaps_s">
|
||||
<MkFoldableSection>
|
||||
<template #header>Manual roles</template>
|
||||
<template #header>{{ i18n.ts._role.manualRoles }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkRolePreview v-for="role in roles.filter(x => x.target === 'manual')" :key="role.id" :role="role" :forModeration="true"/>
|
||||
</div>
|
||||
</MkFoldableSection>
|
||||
<MkFoldableSection>
|
||||
<template #header>Conditional roles</template>
|
||||
<template #header>{{ i18n.ts._role.conditionalRoles }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkRolePreview v-for="role in roles.filter(x => x.target === 'conditional')" :key="role.id" :role="role" :forModeration="true"/>
|
||||
</div>
|
||||
|
|
|
@ -56,6 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<option value="medium">{{ i18n.ts.medium }}</option>
|
||||
<option value="large">{{ i18n.ts.large }}</option>
|
||||
</MkRadios>
|
||||
<MkSwitch v-model="limitWidthOfReaction">{{ i18n.ts.limitWidthOfReaction }}</MkSwitch>
|
||||
</div>
|
||||
|
||||
<MkSelect v-model="instanceTicker">
|
||||
|
@ -226,6 +227,7 @@ const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serve
|
|||
const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNoteActionsOnlyHover'));
|
||||
const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showClipButtonInNoteFooter'));
|
||||
const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize'));
|
||||
const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthOfReaction'));
|
||||
const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
|
||||
const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
|
||||
const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal'));
|
||||
|
@ -290,6 +292,7 @@ watch([
|
|||
overridedDeviceKind,
|
||||
mediaListWithOneImageAppearance,
|
||||
reactionsDisplaySize,
|
||||
limitWidthOfReaction,
|
||||
highlightSensitiveMedia,
|
||||
keepScreenOn,
|
||||
disableStreamingTimeline,
|
||||
|
|
|
@ -9,7 +9,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #icon><i class="ti ti-message-off"></i></template>
|
||||
<template #label>{{ i18n.ts.wordMute }}</template>
|
||||
|
||||
<XWordMute/>
|
||||
<XWordMute :muted="$i!.mutedWords" @save="saveMutedWords"/>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-message-off"></i></template>
|
||||
<template #label>{{ i18n.ts.hardWordMute }}</template>
|
||||
|
||||
<XWordMute :muted="$i!.hardMutedWords" @save="saveHardMutedWords"/>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
|
@ -129,6 +136,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
|||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
import { $i } from '@/account.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
|
||||
const renoteMutingPagination = {
|
||||
|
@ -207,6 +215,14 @@ async function toggleBlockItem(item) {
|
|||
}
|
||||
}
|
||||
|
||||
async function saveMutedWords(mutedWords: (string | string[])[]) {
|
||||
await os.api('i/update', { mutedWords });
|
||||
}
|
||||
|
||||
async function saveHardMutedWords(hardMutedWords: (string | string[])[]) {
|
||||
await os.api('i/update', { hardMutedWords });
|
||||
}
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
const headerTabs = $computed(() => []);
|
||||
|
|
|
@ -18,16 +18,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkTab from '@/components/MkTab.vue';
|
||||
import * as os from '@/os.js';
|
||||
import number from '@/filters/number.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
|
||||
const props = defineProps<{
|
||||
muted: (string[] | string)[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'save', value: (string[] | string)[]): void;
|
||||
}>();
|
||||
|
||||
const render = (mutedWords) => mutedWords.map(x => {
|
||||
if (Array.isArray(x)) {
|
||||
|
@ -37,8 +38,7 @@ const render = (mutedWords) => mutedWords.map(x => {
|
|||
}
|
||||
}).join('\n');
|
||||
|
||||
const tab = ref('soft');
|
||||
const mutedWords = ref(render($i!.mutedWords));
|
||||
const mutedWords = ref(render(props.muted));
|
||||
const changed = ref(false);
|
||||
|
||||
watch(mutedWords, () => {
|
||||
|
@ -85,9 +85,7 @@ async function save() {
|
|||
return;
|
||||
}
|
||||
|
||||
await os.api('i/update', {
|
||||
mutedWords: parsed,
|
||||
});
|
||||
emit('save', parsed);
|
||||
|
||||
changed.value = false;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="notUseSound">
|
||||
<template #label>{{ i18n.ts.notUseSound }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="useSoundOnlyWhenActive">
|
||||
<template #label>{{ i18n.ts.useSoundOnlyWhenActive }}</template>
|
||||
</MkSwitch>
|
||||
<MkRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :textConverter="(v) => `${Math.floor(v * 100)}%`">
|
||||
<template #label>{{ i18n.ts.masterVolume }}</template>
|
||||
</MkRange>
|
||||
|
@ -35,10 +41,13 @@ import MkFolder from '@/components/MkFolder.vue';
|
|||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
|
||||
const notUseSound = computed(defaultStore.makeGetterSetter('sound_notUseSound'));
|
||||
const useSoundOnlyWhenActive = computed(defaultStore.makeGetterSetter('sound_useSoundOnlyWhenActive'));
|
||||
const masterVolume = computed(defaultStore.makeGetterSetter('sound_masterVolume'));
|
||||
|
||||
const soundsKeys = ['note', 'noteMy', 'notification', 'antenna', 'channel'] as const;
|
||||
const soundsKeys = ['note', 'noteMy', 'notification', 'antenna', 'channel', 'reaction'] as const;
|
||||
|
||||
const sounds = ref<Record<typeof soundsKeys[number], Ref<any>>>({
|
||||
note: defaultStore.reactiveState.sound_note,
|
||||
|
@ -46,6 +55,7 @@ const sounds = ref<Record<typeof soundsKeys[number], Ref<any>>>({
|
|||
notification: defaultStore.reactiveState.sound_notification,
|
||||
antenna: defaultStore.reactiveState.sound_antenna,
|
||||
channel: defaultStore.reactiveState.sound_channel,
|
||||
reaction: defaultStore.reactiveState.sound_reaction,
|
||||
});
|
||||
|
||||
async function updated(type: keyof typeof sounds.value, sound) {
|
||||
|
|
|
@ -11,10 +11,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkLoading v-if="fetching"/>
|
||||
<div v-if="!fetching && files.length > 0" :class="$style.stream">
|
||||
<template v-for="file in files" :key="file.note.id + file.file.id">
|
||||
<div v-if="file.file.isSensitive && !showingFiles.includes(file.file.id)" :class="$style.sensitive" @click="showingFiles.push(file.file.id)">
|
||||
<div>
|
||||
<div><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}</div>
|
||||
<div>{{ i18n.ts.clickToShow }}</div>
|
||||
<div v-if="file.file.isSensitive && !showingFiles.includes(file.file.id)" :class="$style.img" @click="showingFiles.push(file.file.id)">
|
||||
<!-- TODO: 画像以外のファイルに対応 -->
|
||||
<ImgWithBlurhash :class="$style.sensitiveImg" :hash="file.file.blurhash" :src="thumbnail(file.file)" :title="file.file.name" :forceBlurhash="true"/>
|
||||
<div :class="$style.sensitive">
|
||||
<div>
|
||||
<div><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}</div>
|
||||
<div>{{ i18n.ts.clickToShow }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MkA v-else :class="$style.img" :to="notePage(file.note)">
|
||||
|
@ -88,6 +92,7 @@ onMounted(() => {
|
|||
}
|
||||
|
||||
.img {
|
||||
position: relative;
|
||||
height: 128px;
|
||||
border-radius: 6px;
|
||||
overflow: clip;
|
||||
|
@ -99,8 +104,24 @@ onMounted(() => {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.sensitiveImg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
filter: brightness(0.7);
|
||||
}
|
||||
.sensitive {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 0.8em;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -18,6 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<XPages v-else-if="tab === 'pages'" :user="user"/>
|
||||
<XFlashs v-else-if="tab === 'flashs'" :user="user"/>
|
||||
<XGallery v-else-if="tab === 'gallery'" :user="user"/>
|
||||
<XRaw v-else-if="tab === 'raw'" :user="user"/>
|
||||
</div>
|
||||
<MkError v-else-if="error" @retry="fetchUser()"/>
|
||||
<MkLoading v-else/>
|
||||
|
@ -44,6 +45,7 @@ const XLists = defineAsyncComponent(() => import('./lists.vue'));
|
|||
const XPages = defineAsyncComponent(() => import('./pages.vue'));
|
||||
const XFlashs = defineAsyncComponent(() => import('./flashs.vue'));
|
||||
const XGallery = defineAsyncComponent(() => import('./gallery.vue'));
|
||||
const XRaw = defineAsyncComponent(() => import('./raw.vue'));
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
acct: string;
|
||||
|
@ -112,6 +114,10 @@ const headerTabs = $computed(() => user ? [{
|
|||
key: 'gallery',
|
||||
title: i18n.ts.gallery,
|
||||
icon: 'ti ti-icons',
|
||||
}, {
|
||||
key: 'raw',
|
||||
title: 'Raw',
|
||||
icon: 'ti ti-code',
|
||||
}] : []);
|
||||
|
||||
definePageMetadata(computed(() => user ? {
|
||||
|
|
130
packages/frontend/src/pages/user/raw.vue
Normal file
130
packages/frontend/src/pages/user/raw.vue
Normal file
|
@ -0,0 +1,130 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkSpacer :contentMax="600" :marginMin="16" :marginMax="32">
|
||||
<div class="_gaps_m">
|
||||
<div :class="$style.userMInfoRoot">
|
||||
<MkAvatar :class="$style.userMInfoAvatar" :user="user" indicator link preview/>
|
||||
<div :class="$style.userMInfoMetaRoot">
|
||||
<span :class="$style.userMInfoMetaName"><MkUserName :class="$style.userMInfoMetaName" :user="user"/></span>
|
||||
<span :class="$style.userMInfoMetaSub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
|
||||
<span :class="$style.userMInfoMetaState">
|
||||
<span v-if="suspended" :class="$style.suspended">Suspended</span>
|
||||
<span v-if="silenced" :class="$style.silenced">Silenced</span>
|
||||
<span v-if="moderator" :class="$style.moderator">Moderator</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; gap: 1em;">
|
||||
<MkKeyValue :copy="user.id" oneline>
|
||||
<template #key>ID</template>
|
||||
<template #value><span class="_monospace">{{ user.id }}</span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline>
|
||||
<template #key>{{ i18n.ts.createdAt }}</template>
|
||||
<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
|
||||
<FormSection>
|
||||
<template #label>Raw</template>
|
||||
<MkObjectView tall :value="user"></MkObjectView>
|
||||
</FormSection>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { acct } from '@/filters/user.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkObjectView from '@/components/MkObjectView.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
user: Misskey.entities.User;
|
||||
}>();
|
||||
|
||||
const moderator = computed(() => props.user.isModerator ?? false);
|
||||
const silenced = computed(() => props.user.isSilenced ?? false);
|
||||
const suspended = computed(() => props.user.isSuspended ?? false);
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.userMInfoRoot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.userMInfoAvatar {
|
||||
display: block;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.userMInfoMetaRoot {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.userMInfoMetaName {
|
||||
display: block;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.userMInfoMetaSub {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: 85%;
|
||||
opacity: 0.7;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.userMInfoMetaState {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 4px;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> .suspended,
|
||||
> .silenced,
|
||||
> .moderator {
|
||||
display: inline-block;
|
||||
border: solid 1px;
|
||||
border-radius: 6px;
|
||||
padding: 2px 6px;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
> .suspended {
|
||||
color: var(--error);
|
||||
border-color: var(--error);
|
||||
}
|
||||
|
||||
> .silenced {
|
||||
color: var(--warn);
|
||||
border-color: var(--warn);
|
||||
}
|
||||
|
||||
> .moderator {
|
||||
color: var(--success);
|
||||
border-color: var(--success);
|
||||
}
|
||||
}
|
||||
</style>
|
9
packages/frontend/src/scripts/media-has-audio.ts
Normal file
9
packages/frontend/src/scripts/media-has-audio.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export default async function hasAudio(media: HTMLMediaElement) {
|
||||
const cloned = media.cloneNode() as HTMLMediaElement;
|
||||
cloned.muted = (cloned as typeof cloned & Partial<HTMLVideoElement>).playsInline = true;
|
||||
cloned.play();
|
||||
await new Promise((resolve) => cloned.addEventListener('playing', resolve));
|
||||
const result = !!(cloned as any).audioTracks?.length || (cloned as any).mozHasAudio || !!(cloned as any).webkitAudioDecodedByteCount;
|
||||
cloned.remove();
|
||||
return result;
|
||||
}
|
|
@ -5,8 +5,9 @@
|
|||
|
||||
import { defaultStore } from '@/store.js';
|
||||
|
||||
const ctx = new AudioContext();
|
||||
let ctx: AudioContext;
|
||||
const cache = new Map<string, AudioBuffer>();
|
||||
let canPlay = true;
|
||||
|
||||
export const soundsTypes = [
|
||||
null,
|
||||
|
@ -38,6 +39,8 @@ export const soundsTypes = [
|
|||
'syuilo/waon',
|
||||
'syuilo/popo',
|
||||
'syuilo/triple',
|
||||
'syuilo/bubble1',
|
||||
'syuilo/bubble2',
|
||||
'syuilo/poi1',
|
||||
'syuilo/poi2',
|
||||
'syuilo/pirori',
|
||||
|
@ -61,7 +64,10 @@ export const soundsTypes = [
|
|||
'noizenecio/kick_gaba7',
|
||||
] as const;
|
||||
|
||||
export async function getAudio(file: string, useCache = true) {
|
||||
export async function loadAudio(file: string, useCache = true) {
|
||||
if (ctx == null) {
|
||||
ctx = new AudioContext();
|
||||
}
|
||||
if (useCache && cache.has(file)) {
|
||||
return cache.get(file)!;
|
||||
}
|
||||
|
@ -77,30 +83,52 @@ export async function getAudio(file: string, useCache = true) {
|
|||
return audioBuffer;
|
||||
}
|
||||
|
||||
export function setVolume(audio: HTMLAudioElement, volume: number): HTMLAudioElement {
|
||||
const masterVolume = defaultStore.state.sound_masterVolume;
|
||||
audio.volume = masterVolume - ((1 - volume) * masterVolume);
|
||||
return audio;
|
||||
}
|
||||
|
||||
export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notification') {
|
||||
export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notification' | 'reaction') {
|
||||
const sound = defaultStore.state[`sound_${type}`];
|
||||
if (_DEV_) console.log('play', type, sound);
|
||||
if (sound.type == null) return;
|
||||
playFile(sound.type, sound.volume);
|
||||
if (sound.type == null || !canPlay) return;
|
||||
|
||||
canPlay = false;
|
||||
playFile(sound.type, sound.volume).then(() => {
|
||||
// ごく短時間に音が重複しないように
|
||||
setTimeout(() => {
|
||||
canPlay = true;
|
||||
}, 25);
|
||||
});
|
||||
}
|
||||
|
||||
export async function playFile(file: string, volume: number) {
|
||||
const buffer = await loadAudio(file);
|
||||
createSourceNode(buffer, volume)?.start();
|
||||
}
|
||||
|
||||
export function createSourceNode(buffer: AudioBuffer, volume: number) : AudioBufferSourceNode | null {
|
||||
const masterVolume = defaultStore.state.sound_masterVolume;
|
||||
if (masterVolume === 0 || volume === 0) {
|
||||
return;
|
||||
if (isMute() || masterVolume === 0 || volume === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const gainNode = ctx.createGain();
|
||||
gainNode.gain.value = masterVolume * volume;
|
||||
|
||||
const soundSource = ctx.createBufferSource();
|
||||
soundSource.buffer = await getAudio(file);
|
||||
soundSource.buffer = buffer;
|
||||
soundSource.connect(gainNode).connect(ctx.destination);
|
||||
soundSource.start();
|
||||
|
||||
return soundSource;
|
||||
}
|
||||
|
||||
export function isMute(): boolean {
|
||||
if (defaultStore.state.sound_notUseSound) {
|
||||
// サウンドを出力しない
|
||||
return true;
|
||||
}
|
||||
|
||||
// noinspection RedundantIfStatementJS
|
||||
if (defaultStore.state.sound_useSoundOnlyWhenActive && document.visibilityState === 'hidden') {
|
||||
// ブラウザがアクティブな時のみサウンドを出力する
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -330,6 +330,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||
where: 'device',
|
||||
default: 'medium' as 'small' | 'medium' | 'large',
|
||||
},
|
||||
limitWidthOfReaction: {
|
||||
where: 'device',
|
||||
default: true,
|
||||
},
|
||||
forceShowAds: {
|
||||
where: 'device',
|
||||
default: false,
|
||||
|
@ -387,6 +391,14 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||
where: 'device',
|
||||
default: 0.3,
|
||||
},
|
||||
sound_notUseSound: {
|
||||
where: 'device',
|
||||
default: false,
|
||||
},
|
||||
sound_useSoundOnlyWhenActive: {
|
||||
where: 'device',
|
||||
default: false,
|
||||
},
|
||||
sound_note: {
|
||||
where: 'device',
|
||||
default: { type: 'syuilo/n-aec', volume: 1 },
|
||||
|
@ -407,6 +419,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||
where: 'device',
|
||||
default: { type: 'syuilo/square-pico', volume: 1 },
|
||||
},
|
||||
sound_reaction: {
|
||||
where: 'device',
|
||||
default: { type: 'syuilo/bubble2', volume: 1 },
|
||||
},
|
||||
}));
|
||||
|
||||
// TODO: 他のタブと永続化されたstateを同期
|
||||
|
|
|
@ -58,6 +58,7 @@ import { useStream } from '@/stream.js';
|
|||
import number from '@/filters/number.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
import { deepClone } from '@/scripts/clone.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
|
||||
const name = 'jobQueue';
|
||||
|
||||
|
@ -99,7 +100,12 @@ const current = reactive({
|
|||
},
|
||||
});
|
||||
const prev = reactive({} as typeof current);
|
||||
const jammedSound = sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1);
|
||||
let jammedAudioBuffer: AudioBuffer | null = $ref(null);
|
||||
let jammedSoundNodePlaying: boolean = $ref(false);
|
||||
|
||||
if (defaultStore.state.sound_masterVolume) {
|
||||
sound.loadAudio('syuilo/queue-jammed').then(buf => jammedAudioBuffer = buf);
|
||||
}
|
||||
|
||||
for (const domain of ['inbox', 'deliver']) {
|
||||
prev[domain] = deepClone(current[domain]);
|
||||
|
@ -113,8 +119,13 @@ const onStats = (stats) => {
|
|||
current[domain].waiting = stats[domain].waiting;
|
||||
current[domain].delayed = stats[domain].delayed;
|
||||
|
||||
if (current[domain].waiting > 0 && widgetProps.sound && jammedSound.paused) {
|
||||
jammedSound.play();
|
||||
if (current[domain].waiting > 0 && widgetProps.sound && jammedAudioBuffer && !jammedSoundNodePlaying) {
|
||||
const soundNode = sound.createSourceNode(jammedAudioBuffer, 1);
|
||||
if (soundNode) {
|
||||
jammedSoundNodePlaying = true;
|
||||
soundNode.onended = () => jammedSoundNodePlaying = false;
|
||||
soundNode.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -25,3 +25,21 @@ vi.mock('@/store.js', () => {
|
|||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Add mocks for Web Audio API
|
||||
const AudioNodeMock = vi.fn(() => ({
|
||||
connect: vi.fn(() => ({ connect: vi.fn() })),
|
||||
start: vi.fn(),
|
||||
}));
|
||||
|
||||
const GainNodeMock = vi.fn(() => ({
|
||||
gain: vi.fn(),
|
||||
}));
|
||||
|
||||
const AudioContextMock = vi.fn(() => ({
|
||||
createBufferSource: vi.fn(() => new AudioNodeMock()),
|
||||
createGain: vi.fn(() => new GainNodeMock()),
|
||||
decodeAudioData: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.stubGlobal('AudioContext', AudioContextMock);
|
||||
|
|
|
@ -150,7 +150,7 @@ describe('MkUrlPreview', () => {
|
|||
});
|
||||
assert.exists(iframe, 'iframe should exist');
|
||||
assert.strictEqual(iframe?.getAttribute('allow'), 'fullscreen;web-share');
|
||||
assert.strictEqual(iframe?.getAttribute('sandbox'), 'allow-popups allow-scripts allow-same-origin');
|
||||
assert.strictEqual(iframe?.getAttribute('sandbox'), 'allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin');
|
||||
});
|
||||
|
||||
test('Loading a post in iframe', async () => {
|
||||
|
@ -159,6 +159,6 @@ describe('MkUrlPreview', () => {
|
|||
});
|
||||
assert.exists(iframe, 'iframe should exist');
|
||||
assert.strictEqual(iframe?.getAttribute('allow'), 'fullscreen;web-share');
|
||||
assert.strictEqual(iframe?.getAttribute('sandbox'), 'allow-popups allow-scripts allow-same-origin');
|
||||
assert.strictEqual(iframe?.getAttribute('sandbox'), 'allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1565,7 +1565,8 @@ export type Endpoints = {
|
|||
injectFeaturedNote?: boolean;
|
||||
receiveAnnouncementEmail?: boolean;
|
||||
alwaysMarkNsfw?: boolean;
|
||||
mutedWords?: string[][];
|
||||
mutedWords?: (string[] | string)[];
|
||||
hardMutedWords?: (string[] | string)[];
|
||||
notificationRecieveConfig?: any;
|
||||
emailNotificationTypes?: string[];
|
||||
alsoKnownAs?: string[];
|
||||
|
@ -2516,7 +2517,8 @@ type MeDetailed = UserDetailed & {
|
|||
integrations: Record<string, any>;
|
||||
isDeleted: boolean;
|
||||
isExplorable: boolean;
|
||||
mutedWords: string[][];
|
||||
mutedWords: (string[] | string)[];
|
||||
hardMutedWords: (string[] | string)[];
|
||||
notificationRecieveConfig: {
|
||||
[notificationType in typeof notificationTypes_2[number]]?: {
|
||||
type: 'all';
|
||||
|
@ -2685,10 +2687,16 @@ type ModerationLog = {
|
|||
} | {
|
||||
type: 'resolveAbuseReport';
|
||||
info: ModerationLogPayloads['resolveAbuseReport'];
|
||||
} | {
|
||||
type: 'unsetUserAvatar';
|
||||
info: ModerationLogPayloads['unsetUserAvatar'];
|
||||
} | {
|
||||
type: 'unsetUserBanner';
|
||||
info: ModerationLogPayloads['unsetUserBanner'];
|
||||
});
|
||||
|
||||
// @public (undocumented)
|
||||
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration"];
|
||||
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner"];
|
||||
|
||||
// @public (undocumented)
|
||||
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];
|
||||
|
@ -3046,10 +3054,10 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
|
|||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:632:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||
// src/entities.ts:116:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
|
||||
// src/entities.ts:627:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:20:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:635:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||
// src/entities.ts:117:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
|
||||
// src/entities.ts:628:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
"jest-websocket-mock": "2.5.0",
|
||||
"mock-socket": "9.3.1",
|
||||
"tsd": "0.29.0",
|
||||
"typescript": "5.2.2"
|
||||
"typescript": "5.3.2"
|
||||
},
|
||||
"files": [
|
||||
"built"
|
||||
|
|
|
@ -432,7 +432,8 @@ export type Endpoints = {
|
|||
injectFeaturedNote?: boolean;
|
||||
receiveAnnouncementEmail?: boolean;
|
||||
alwaysMarkNsfw?: boolean;
|
||||
mutedWords?: string[][];
|
||||
mutedWords?: (string[] | string)[];
|
||||
hardMutedWords?: (string[] | string)[];
|
||||
notificationRecieveConfig?: any;
|
||||
emailNotificationTypes?: string[];
|
||||
alsoKnownAs?: string[];
|
||||
|
|
|
@ -112,7 +112,8 @@ export type MeDetailed = UserDetailed & {
|
|||
integrations: Record<string, any>;
|
||||
isDeleted: boolean;
|
||||
isExplorable: boolean;
|
||||
mutedWords: string[][];
|
||||
mutedWords: (string[] | string)[];
|
||||
hardMutedWords: (string[] | string)[];
|
||||
notificationRecieveConfig: {
|
||||
[notificationType in typeof notificationTypes[number]]?: {
|
||||
type: 'all';
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
|
||||
"eslint": "8.53.0",
|
||||
"eslint-plugin-import": "2.29.0",
|
||||
"typescript": "5.2.2"
|
||||
"typescript": "5.3.2"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
|
172
pnpm-lock.yaml
172
pnpm-lock.yaml
|
@ -28,8 +28,8 @@ importers:
|
|||
specifier: 5.24.0
|
||||
version: 5.24.0
|
||||
typescript:
|
||||
specifier: 5.2.2
|
||||
version: 5.2.2
|
||||
specifier: 5.3.2
|
||||
version: 5.3.2
|
||||
optionalDependencies:
|
||||
'@tensorflow/tfjs-core':
|
||||
specifier: 4.4.0
|
||||
|
@ -37,10 +37,10 @@ importers:
|
|||
devDependencies:
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: 6.11.0
|
||||
version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.2.2)
|
||||
version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.3.2)
|
||||
'@typescript-eslint/parser':
|
||||
specifier: 6.11.0
|
||||
version: 6.11.0(eslint@8.53.0)(typescript@5.2.2)
|
||||
version: 6.11.0(eslint@8.53.0)(typescript@5.3.2)
|
||||
cross-env:
|
||||
specifier: 7.0.3
|
||||
version: 7.0.3
|
||||
|
@ -381,8 +381,8 @@ importers:
|
|||
specifier: 0.3.17
|
||||
version: 0.3.17(ioredis@5.3.2)(pg@8.11.3)
|
||||
typescript:
|
||||
specifier: 5.2.2
|
||||
version: 5.2.2
|
||||
specifier: 5.3.2
|
||||
version: 5.3.2
|
||||
ulid:
|
||||
specifier: 2.3.0
|
||||
version: 2.3.0
|
||||
|
@ -615,10 +615,10 @@ importers:
|
|||
version: 8.5.9
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: 6.11.0
|
||||
version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.2.2)
|
||||
version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.3.2)
|
||||
'@typescript-eslint/parser':
|
||||
specifier: 6.11.0
|
||||
version: 6.11.0(eslint@8.53.0)(typescript@5.2.2)
|
||||
version: 6.11.0(eslint@8.53.0)(typescript@5.3.2)
|
||||
aws-sdk-client-mock:
|
||||
specifier: 3.0.0
|
||||
version: 3.0.0
|
||||
|
@ -806,8 +806,8 @@ importers:
|
|||
specifier: 14.0.0
|
||||
version: 14.0.0
|
||||
typescript:
|
||||
specifier: 5.2.2
|
||||
version: 5.2.2
|
||||
specifier: 5.3.2
|
||||
version: 5.3.2
|
||||
uuid:
|
||||
specifier: 9.0.1
|
||||
version: 9.0.1
|
||||
|
@ -822,7 +822,7 @@ importers:
|
|||
version: 4.5.0(@types/node@20.9.1)(sass@1.69.5)(terser@5.24.0)
|
||||
vue:
|
||||
specifier: 3.3.8
|
||||
version: 3.3.8(typescript@5.2.2)
|
||||
version: 3.3.8(typescript@5.3.2)
|
||||
vuedraggable:
|
||||
specifier: next
|
||||
version: 4.1.0(vue@3.3.8)
|
||||
|
@ -862,10 +862,10 @@ importers:
|
|||
version: 7.5.3
|
||||
'@storybook/react':
|
||||
specifier: 7.5.3
|
||||
version: 7.5.3(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)
|
||||
version: 7.5.3(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.2)
|
||||
'@storybook/react-vite':
|
||||
specifier: 7.5.3
|
||||
version: 7.5.3(react-dom@18.2.0)(react@18.2.0)(rollup@4.4.1)(typescript@5.2.2)(vite@4.5.0)
|
||||
version: 7.5.3(react-dom@18.2.0)(react@18.2.0)(rollup@4.4.1)(typescript@5.3.2)(vite@4.5.0)
|
||||
'@storybook/testing-library':
|
||||
specifier: 0.2.2
|
||||
version: 0.2.2
|
||||
|
@ -880,7 +880,7 @@ importers:
|
|||
version: 7.5.3(@vue/compiler-core@3.3.8)(vue@3.3.8)
|
||||
'@storybook/vue3-vite':
|
||||
specifier: 7.5.3
|
||||
version: 7.5.3(@vue/compiler-core@3.3.8)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(vite@4.5.0)(vue@3.3.8)
|
||||
version: 7.5.3(@vue/compiler-core@3.3.8)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.2)(vite@4.5.0)(vue@3.3.8)
|
||||
'@testing-library/vue':
|
||||
specifier: 8.0.0
|
||||
version: 8.0.0(@vue/compiler-sfc@3.3.8)(vue@3.3.8)
|
||||
|
@ -922,10 +922,10 @@ importers:
|
|||
version: 8.5.9
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: 6.11.0
|
||||
version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.2.2)
|
||||
version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.3.2)
|
||||
'@typescript-eslint/parser':
|
||||
specifier: 6.11.0
|
||||
version: 6.11.0(eslint@8.53.0)(typescript@5.2.2)
|
||||
version: 6.11.0(eslint@8.53.0)(typescript@5.3.2)
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 0.34.6
|
||||
version: 0.34.6(vitest@0.34.6)
|
||||
|
@ -961,7 +961,7 @@ importers:
|
|||
version: 4.0.5
|
||||
msw:
|
||||
specifier: 1.3.2
|
||||
version: 1.3.2(typescript@5.2.2)
|
||||
version: 1.3.2(typescript@5.3.2)
|
||||
msw-storybook-addon:
|
||||
specifier: 1.10.0
|
||||
version: 1.10.0(msw@1.3.2)
|
||||
|
@ -1003,7 +1003,7 @@ importers:
|
|||
version: 9.3.2(eslint@8.53.0)
|
||||
vue-tsc:
|
||||
specifier: 1.8.22
|
||||
version: 1.8.22(typescript@5.2.2)
|
||||
version: 1.8.22(typescript@5.3.2)
|
||||
|
||||
packages/misskey-js:
|
||||
dependencies:
|
||||
|
@ -1034,10 +1034,10 @@ importers:
|
|||
version: 20.9.1
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: 6.11.0
|
||||
version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.2.2)
|
||||
version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.3.2)
|
||||
'@typescript-eslint/parser':
|
||||
specifier: 6.11.0
|
||||
version: 6.11.0(eslint@8.53.0)(typescript@5.2.2)
|
||||
version: 6.11.0(eslint@8.53.0)(typescript@5.3.2)
|
||||
eslint:
|
||||
specifier: 8.53.0
|
||||
version: 8.53.0
|
||||
|
@ -1057,8 +1057,8 @@ importers:
|
|||
specifier: 0.29.0
|
||||
version: 0.29.0
|
||||
typescript:
|
||||
specifier: 5.2.2
|
||||
version: 5.2.2
|
||||
specifier: 5.3.2
|
||||
version: 5.3.2
|
||||
|
||||
packages/sw:
|
||||
dependencies:
|
||||
|
@ -1074,7 +1074,7 @@ importers:
|
|||
devDependencies:
|
||||
'@typescript-eslint/parser':
|
||||
specifier: 6.11.0
|
||||
version: 6.11.0(eslint@8.53.0)(typescript@5.2.2)
|
||||
version: 6.11.0(eslint@8.53.0)(typescript@5.3.2)
|
||||
'@typescript/lib-webworker':
|
||||
specifier: npm:@types/serviceworker@0.0.67
|
||||
version: /@types/serviceworker@0.0.67
|
||||
|
@ -1085,8 +1085,8 @@ importers:
|
|||
specifier: 2.29.0
|
||||
version: 2.29.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)
|
||||
typescript:
|
||||
specifier: 5.2.2
|
||||
version: 5.2.2
|
||||
specifier: 5.3.2
|
||||
version: 5.3.2
|
||||
|
||||
packages:
|
||||
|
||||
|
@ -4254,7 +4254,7 @@ packages:
|
|||
chalk: 4.1.2
|
||||
dev: true
|
||||
|
||||
/@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.2.2)(vite@4.5.0):
|
||||
/@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.3.2)(vite@4.5.0):
|
||||
resolution: {integrity: sha512-2D6y7fNvFmsLmRt6UCOFJPvFoPMJGT0Uh1Wg0RaigUp7kdQPs6yYn8Dmx6GZkOH/NW0yMTwRz/p0SRMMRo50vA==}
|
||||
peerDependencies:
|
||||
typescript: '>= 4.3.x'
|
||||
|
@ -4266,8 +4266,8 @@ packages:
|
|||
glob: 7.2.3
|
||||
glob-promise: 4.2.2(glob@7.2.3)
|
||||
magic-string: 0.27.0
|
||||
react-docgen-typescript: 2.2.2(typescript@5.2.2)
|
||||
typescript: 5.2.2
|
||||
react-docgen-typescript: 2.2.2(typescript@5.3.2)
|
||||
typescript: 5.3.2
|
||||
vite: 4.5.0(@types/node@20.9.1)(sass@1.69.5)(terser@5.24.0)
|
||||
dev: true
|
||||
|
||||
|
@ -6348,7 +6348,7 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@storybook/builder-vite@7.5.3(typescript@5.2.2)(vite@4.5.0):
|
||||
/@storybook/builder-vite@7.5.3(typescript@5.3.2)(vite@4.5.0):
|
||||
resolution: {integrity: sha512-c104V3O75OCVnfZj0Jr70V09g0KSbPGvQK2Zh31omXGvakG8XrhWolYxkmjOcForJmAqsXnKs/nw3F75Gp853g==}
|
||||
peerDependencies:
|
||||
'@preact/preset-vite': '*'
|
||||
|
@ -6379,7 +6379,7 @@ packages:
|
|||
fs-extra: 11.1.1
|
||||
magic-string: 0.30.5
|
||||
rollup: 3.29.4
|
||||
typescript: 5.2.2
|
||||
typescript: 5.3.2
|
||||
vite: 4.5.0(@types/node@20.9.1)(sass@1.69.5)(terser@5.24.0)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
@ -6750,7 +6750,7 @@ packages:
|
|||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: true
|
||||
|
||||
/@storybook/react-vite@7.5.3(react-dom@18.2.0)(react@18.2.0)(rollup@4.4.1)(typescript@5.2.2)(vite@4.5.0):
|
||||
/@storybook/react-vite@7.5.3(react-dom@18.2.0)(react@18.2.0)(rollup@4.4.1)(typescript@5.3.2)(vite@4.5.0):
|
||||
resolution: {integrity: sha512-ArPyHgiPbT5YvcyK4xK/DfqBOpn4R4/EP3kfIGhx8QKJyOtxPEYFdkLIZ5xu3KnPX7/z7GT+4a6Rb+8sk9gliA==}
|
||||
engines: {node: '>=16'}
|
||||
peerDependencies:
|
||||
|
@ -6758,10 +6758,10 @@ packages:
|
|||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
vite: ^3.0.0 || ^4.0.0 || ^5.0.0
|
||||
dependencies:
|
||||
'@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.2.2)(vite@4.5.0)
|
||||
'@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.3.2)(vite@4.5.0)
|
||||
'@rollup/pluginutils': 5.0.5(rollup@4.4.1)
|
||||
'@storybook/builder-vite': 7.5.3(typescript@5.2.2)(vite@4.5.0)
|
||||
'@storybook/react': 7.5.3(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)
|
||||
'@storybook/builder-vite': 7.5.3(typescript@5.3.2)(vite@4.5.0)
|
||||
'@storybook/react': 7.5.3(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.2)
|
||||
'@vitejs/plugin-react': 3.1.0(vite@4.5.0)
|
||||
magic-string: 0.30.5
|
||||
react: 18.2.0
|
||||
|
@ -6777,7 +6777,7 @@ packages:
|
|||
- vite-plugin-glimmerx
|
||||
dev: true
|
||||
|
||||
/@storybook/react@7.5.3(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2):
|
||||
/@storybook/react@7.5.3(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-dZILdM36xMFDjdmmy421G5X+sOIncB2qF3IPTooniG1i1Z6v/dVNo57ovdID9lDTNa+AWr2fLB9hANiISMqmjQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
peerDependencies:
|
||||
|
@ -6810,7 +6810,7 @@ packages:
|
|||
react-element-to-jsx-string: 15.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
ts-dedent: 2.2.0
|
||||
type-fest: 2.19.0
|
||||
typescript: 5.2.2
|
||||
typescript: 5.3.2
|
||||
util-deprecate: 1.0.2
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
@ -6892,7 +6892,7 @@ packages:
|
|||
file-system-cache: 2.3.0
|
||||
dev: true
|
||||
|
||||
/@storybook/vue3-vite@7.5.3(@vue/compiler-core@3.3.8)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(vite@4.5.0)(vue@3.3.8):
|
||||
/@storybook/vue3-vite@7.5.3(@vue/compiler-core@3.3.8)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.2)(vite@4.5.0)(vue@3.3.8):
|
||||
resolution: {integrity: sha512-gkNwDDn2AKthAtaoPrHb0+2gi33UluxpfSq/M5COoMEVFphj6y/jyDa+OEYlceXgnD8g2xvX4/yv2TbTNDzmcQ==}
|
||||
engines: {node: ^14.18 || >=16}
|
||||
peerDependencies:
|
||||
|
@ -6900,7 +6900,7 @@ packages:
|
|||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
vite: ^3.0.0 || ^4.0.0 || ^5.0.0
|
||||
dependencies:
|
||||
'@storybook/builder-vite': 7.5.3(typescript@5.2.2)(vite@4.5.0)
|
||||
'@storybook/builder-vite': 7.5.3(typescript@5.3.2)(vite@4.5.0)
|
||||
'@storybook/core-server': 7.5.3
|
||||
'@storybook/vue3': 7.5.3(@vue/compiler-core@3.3.8)(vue@3.3.8)
|
||||
'@vitejs/plugin-vue': 4.5.0(vite@4.5.0)(vue@3.3.8)
|
||||
|
@ -6937,7 +6937,7 @@ packages:
|
|||
lodash: 4.17.21
|
||||
ts-dedent: 2.2.0
|
||||
type-fest: 2.19.0
|
||||
vue: 3.3.8(typescript@5.2.2)
|
||||
vue: 3.3.8(typescript@5.3.2)
|
||||
vue-component-type-helpers: 1.8.22
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
@ -7432,7 +7432,7 @@ packages:
|
|||
'@testing-library/dom': 9.3.3
|
||||
'@vue/compiler-sfc': 3.3.8
|
||||
'@vue/test-utils': 2.4.1(vue@3.3.8)
|
||||
vue: 3.3.8(typescript@5.2.2)
|
||||
vue: 3.3.8(typescript@5.3.2)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/server-renderer'
|
||||
dev: true
|
||||
|
@ -8073,7 +8073,7 @@ packages:
|
|||
dev: true
|
||||
optional: true
|
||||
|
||||
/@typescript-eslint/eslint-plugin@6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.2.2):
|
||||
/@typescript-eslint/eslint-plugin@6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
|
@ -8085,10 +8085,10 @@ packages:
|
|||
optional: true
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.6.2
|
||||
'@typescript-eslint/parser': 6.11.0(eslint@8.53.0)(typescript@5.2.2)
|
||||
'@typescript-eslint/parser': 6.11.0(eslint@8.53.0)(typescript@5.3.2)
|
||||
'@typescript-eslint/scope-manager': 6.11.0
|
||||
'@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.2.2)
|
||||
'@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.2.2)
|
||||
'@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.3.2)
|
||||
'@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.2)
|
||||
'@typescript-eslint/visitor-keys': 6.11.0
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
eslint: 8.53.0
|
||||
|
@ -8096,13 +8096,13 @@ packages:
|
|||
ignore: 5.2.4
|
||||
natural-compare: 1.4.0
|
||||
semver: 7.5.4
|
||||
ts-api-utils: 1.0.1(typescript@5.2.2)
|
||||
typescript: 5.2.2
|
||||
ts-api-utils: 1.0.1(typescript@5.3.2)
|
||||
typescript: 5.3.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/parser@6.11.0(eslint@8.53.0)(typescript@5.2.2):
|
||||
/@typescript-eslint/parser@6.11.0(eslint@8.53.0)(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
|
@ -8114,11 +8114,11 @@ packages:
|
|||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 6.11.0
|
||||
'@typescript-eslint/types': 6.11.0
|
||||
'@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2)
|
||||
'@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.2)
|
||||
'@typescript-eslint/visitor-keys': 6.11.0
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
eslint: 8.53.0
|
||||
typescript: 5.2.2
|
||||
typescript: 5.3.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -8131,7 +8131,7 @@ packages:
|
|||
'@typescript-eslint/visitor-keys': 6.11.0
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/type-utils@6.11.0(eslint@8.53.0)(typescript@5.2.2):
|
||||
/@typescript-eslint/type-utils@6.11.0(eslint@8.53.0)(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
|
@ -8141,12 +8141,12 @@ packages:
|
|||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2)
|
||||
'@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.2.2)
|
||||
'@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.2)
|
||||
'@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.2)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
eslint: 8.53.0
|
||||
ts-api-utils: 1.0.1(typescript@5.2.2)
|
||||
typescript: 5.2.2
|
||||
ts-api-utils: 1.0.1(typescript@5.3.2)
|
||||
typescript: 5.3.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -8156,7 +8156,7 @@ packages:
|
|||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/typescript-estree@6.11.0(typescript@5.2.2):
|
||||
/@typescript-eslint/typescript-estree@6.11.0(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
|
@ -8171,13 +8171,13 @@ packages:
|
|||
globby: 11.1.0
|
||||
is-glob: 4.0.3
|
||||
semver: 7.5.4
|
||||
ts-api-utils: 1.0.1(typescript@5.2.2)
|
||||
typescript: 5.2.2
|
||||
ts-api-utils: 1.0.1(typescript@5.3.2)
|
||||
typescript: 5.3.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/utils@6.11.0(eslint@8.53.0)(typescript@5.2.2):
|
||||
/@typescript-eslint/utils@6.11.0(eslint@8.53.0)(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
|
@ -8188,7 +8188,7 @@ packages:
|
|||
'@types/semver': 7.5.5
|
||||
'@typescript-eslint/scope-manager': 6.11.0
|
||||
'@typescript-eslint/types': 6.11.0
|
||||
'@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2)
|
||||
'@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.2)
|
||||
eslint: 8.53.0
|
||||
semver: 7.5.4
|
||||
transitivePeerDependencies:
|
||||
|
@ -8232,7 +8232,7 @@ packages:
|
|||
vue: ^3.2.25
|
||||
dependencies:
|
||||
vite: 4.5.0(@types/node@20.9.1)(sass@1.69.5)(terser@5.24.0)
|
||||
vue: 3.3.8(typescript@5.2.2)
|
||||
vue: 3.3.8(typescript@5.3.2)
|
||||
|
||||
/@vitest/coverage-v8@0.34.6(vitest@0.34.6):
|
||||
resolution: {integrity: sha512-fivy/OK2d/EsJFoEoxHFEnNGTg+MmdZBAVK9Ka4qhXR2K3J0DS08vcGVwzDtXSuUMabLv4KtPcpSKkcMXFDViw==}
|
||||
|
@ -8327,7 +8327,7 @@ packages:
|
|||
ast-kit: 0.11.2(rollup@4.4.1)
|
||||
local-pkg: 0.5.0
|
||||
magic-string-ast: 0.3.0
|
||||
vue: 3.3.8(typescript@5.2.2)
|
||||
vue: 3.3.8(typescript@5.3.2)
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
dev: false
|
||||
|
@ -8344,7 +8344,7 @@ packages:
|
|||
'@vue/shared': 3.3.8
|
||||
magic-string: 0.30.5
|
||||
unplugin: 1.5.1
|
||||
vue: 3.3.8(typescript@5.2.2)
|
||||
vue: 3.3.8(typescript@5.3.2)
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
dev: false
|
||||
|
@ -8415,7 +8415,7 @@ packages:
|
|||
'@vue/compiler-dom': 3.3.8
|
||||
'@vue/shared': 3.3.8
|
||||
|
||||
/@vue/language-core@1.8.22(typescript@5.2.2):
|
||||
/@vue/language-core@1.8.22(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-bsMoJzCrXZqGsxawtUea1cLjUT9dZnDsy5TuZ+l1fxRMzUGQUG9+Ypq4w//CqpWmrx7nIAJpw2JVF/t258miRw==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
|
@ -8430,7 +8430,7 @@ packages:
|
|||
computeds: 0.0.1
|
||||
minimatch: 9.0.3
|
||||
muggle-string: 0.3.1
|
||||
typescript: 5.2.2
|
||||
typescript: 5.3.2
|
||||
vue-template-compiler: 2.7.14
|
||||
dev: true
|
||||
|
||||
|
@ -8468,7 +8468,7 @@ packages:
|
|||
dependencies:
|
||||
'@vue/compiler-ssr': 3.3.8
|
||||
'@vue/shared': 3.3.8
|
||||
vue: 3.3.8(typescript@5.2.2)
|
||||
vue: 3.3.8(typescript@5.3.2)
|
||||
|
||||
/@vue/shared@3.3.6:
|
||||
resolution: {integrity: sha512-Xno5pEqg8SVhomD0kTSmfh30ZEmV/+jZtyh39q6QflrjdJCXah5lrnOLi9KB6a5k5aAHXMXjoMnxlzUkCNfWLQ==}
|
||||
|
@ -8491,7 +8491,7 @@ packages:
|
|||
optional: true
|
||||
dependencies:
|
||||
js-beautify: 1.14.9
|
||||
vue: 3.3.8(typescript@5.2.2)
|
||||
vue: 3.3.8(typescript@5.3.2)
|
||||
vue-component-type-helpers: 1.8.4
|
||||
dev: true
|
||||
|
||||
|
@ -11158,7 +11158,7 @@ packages:
|
|||
eslint-import-resolver-webpack:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 6.11.0(eslint@8.53.0)(typescript@5.2.2)
|
||||
'@typescript-eslint/parser': 6.11.0(eslint@8.53.0)(typescript@5.3.2)
|
||||
debug: 3.2.7(supports-color@5.5.0)
|
||||
eslint: 8.53.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
|
@ -11176,7 +11176,7 @@ packages:
|
|||
'@typescript-eslint/parser':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 6.11.0(eslint@8.53.0)(typescript@5.2.2)
|
||||
'@typescript-eslint/parser': 6.11.0(eslint@8.53.0)(typescript@5.3.2)
|
||||
array-includes: 3.1.7
|
||||
array.prototype.findlastindex: 1.2.3
|
||||
array.prototype.flat: 1.3.2
|
||||
|
@ -14852,10 +14852,10 @@ packages:
|
|||
msw: '>=0.35.0 <2.0.0'
|
||||
dependencies:
|
||||
is-node-process: 1.2.0
|
||||
msw: 1.3.2(typescript@5.2.2)
|
||||
msw: 1.3.2(typescript@5.3.2)
|
||||
dev: true
|
||||
|
||||
/msw@1.3.2(typescript@5.2.2):
|
||||
/msw@1.3.2(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-wKLhFPR+NitYTkQl5047pia0reNGgf0P6a1eTnA5aNlripmiz0sabMvvHcicE8kQ3/gZcI0YiPFWmYfowfm3lA==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
@ -14884,7 +14884,7 @@ packages:
|
|||
path-to-regexp: 6.2.1
|
||||
strict-event-emitter: 0.4.6
|
||||
type-fest: 2.19.0
|
||||
typescript: 5.2.2
|
||||
typescript: 5.3.2
|
||||
yargs: 17.6.2
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
@ -16773,12 +16773,12 @@ packages:
|
|||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: true
|
||||
|
||||
/react-docgen-typescript@2.2.2(typescript@5.2.2):
|
||||
/react-docgen-typescript@2.2.2(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==}
|
||||
peerDependencies:
|
||||
typescript: '>= 4.3.x'
|
||||
dependencies:
|
||||
typescript: 5.2.2
|
||||
typescript: 5.3.2
|
||||
dev: true
|
||||
|
||||
/react-docgen@6.0.4:
|
||||
|
@ -18627,13 +18627,13 @@ packages:
|
|||
escape-string-regexp: 5.0.0
|
||||
dev: false
|
||||
|
||||
/ts-api-utils@1.0.1(typescript@5.2.2):
|
||||
/ts-api-utils@1.0.1(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==}
|
||||
engines: {node: '>=16.13.0'}
|
||||
peerDependencies:
|
||||
typescript: '>=4.2.0'
|
||||
dependencies:
|
||||
typescript: 5.2.2
|
||||
typescript: 5.3.2
|
||||
dev: true
|
||||
|
||||
/ts-dedent@2.2.0:
|
||||
|
@ -18897,8 +18897,8 @@ packages:
|
|||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/typescript@5.2.2:
|
||||
resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==}
|
||||
/typescript@5.3.2:
|
||||
resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
|
@ -19186,7 +19186,7 @@ packages:
|
|||
diff: 5.1.0
|
||||
diff-match-patch: 1.0.5
|
||||
highlight.js: 11.8.0
|
||||
vue: 3.3.8(typescript@5.2.2)
|
||||
vue: 3.3.8(typescript@5.3.2)
|
||||
vue-demi: 0.13.11(vue@3.3.8)
|
||||
dev: false
|
||||
|
||||
|
@ -19400,7 +19400,7 @@ packages:
|
|||
'@vue/composition-api':
|
||||
optional: true
|
||||
dependencies:
|
||||
vue: 3.3.8(typescript@5.2.2)
|
||||
vue: 3.3.8(typescript@5.3.2)
|
||||
dev: false
|
||||
|
||||
/vue-docgen-api@4.64.1(vue@3.3.8):
|
||||
|
@ -19444,7 +19444,7 @@ packages:
|
|||
peerDependencies:
|
||||
vue: '>=2'
|
||||
dependencies:
|
||||
vue: 3.3.8(typescript@5.2.2)
|
||||
vue: 3.3.8(typescript@5.3.2)
|
||||
dev: true
|
||||
|
||||
/vue-template-compiler@2.7.14:
|
||||
|
@ -19454,19 +19454,19 @@ packages:
|
|||
he: 1.2.0
|
||||
dev: true
|
||||
|
||||
/vue-tsc@1.8.22(typescript@5.2.2):
|
||||
/vue-tsc@1.8.22(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-j9P4kHtW6eEE08aS5McFZE/ivmipXy0JzrnTgbomfABMaVKx37kNBw//irL3+LlE3kOo63XpnRigyPC3w7+z+A==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
dependencies:
|
||||
'@volar/typescript': 1.10.7
|
||||
'@vue/language-core': 1.8.22(typescript@5.2.2)
|
||||
'@vue/language-core': 1.8.22(typescript@5.3.2)
|
||||
semver: 7.5.4
|
||||
typescript: 5.2.2
|
||||
typescript: 5.3.2
|
||||
dev: true
|
||||
|
||||
/vue@3.3.8(typescript@5.2.2):
|
||||
/vue@3.3.8(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
|
@ -19479,7 +19479,7 @@ packages:
|
|||
'@vue/runtime-dom': 3.3.8
|
||||
'@vue/server-renderer': 3.3.8(vue@3.3.8)
|
||||
'@vue/shared': 3.3.8
|
||||
typescript: 5.2.2
|
||||
typescript: 5.3.2
|
||||
|
||||
/vuedraggable@4.1.0(vue@3.3.8):
|
||||
resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
|
||||
|
@ -19487,7 +19487,7 @@ packages:
|
|||
vue: ^3.0.1
|
||||
dependencies:
|
||||
sortablejs: 1.14.0
|
||||
vue: 3.3.8(typescript@5.2.2)
|
||||
vue: 3.3.8(typescript@5.3.2)
|
||||
dev: false
|
||||
|
||||
/w3c-xmlserializer@4.0.0:
|
||||
|
|
Loading…
Reference in a new issue