mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-07 06:26:01 +02:00
Compare commits
3 Commits
maxfilesiz
...
ptr-sfx
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fd6d65042 | ||
|
|
c25f49ebb1 | ||
|
|
453c6d4180 |
@@ -215,6 +215,20 @@ proxyBypassHosts:
|
||||
# Media Proxy
|
||||
#mediaProxy: https://example.com/proxy
|
||||
|
||||
# Proxy remote files (default: true)
|
||||
proxyRemoteFiles: true
|
||||
|
||||
# Sign to ActivityPub GET request (default: true)
|
||||
signToActivityPubGet: true
|
||||
|
||||
allowedPrivateNetworks: [
|
||||
'127.0.0.1/32'
|
||||
]
|
||||
|
||||
# Disable automatic redirect for ActivityPub object lookup. (default: false)
|
||||
# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation.
|
||||
# However it will make it impossible for other instances to lookup third-party user and notes through your URL.
|
||||
#disallowExternalApRedirect: true
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
||||
@@ -227,6 +227,12 @@ proxyBypassHosts:
|
||||
# Media Proxy
|
||||
#mediaProxy: https://example.com/proxy
|
||||
|
||||
# Proxy remote files (default: true)
|
||||
proxyRemoteFiles: true
|
||||
|
||||
# Sign to ActivityPub GET request (default: true)
|
||||
signToActivityPubGet: true
|
||||
|
||||
# For security reasons, uploading attachments from the intranet is prohibited,
|
||||
# but exceptions can be made from the following settings. Default value is "undefined".
|
||||
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
|
||||
@@ -234,6 +240,14 @@ proxyBypassHosts:
|
||||
# '127.0.0.1/32'
|
||||
#]
|
||||
|
||||
# Disable automatic redirect for ActivityPub object lookup. (default: false)
|
||||
# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation.
|
||||
# However it will make it impossible for other instances to lookup third-party user and notes through your URL.
|
||||
#disallowExternalApRedirect: true
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
||||
# Log settings
|
||||
# logging:
|
||||
# sql:
|
||||
|
||||
@@ -319,12 +319,19 @@ proxyBypassHosts:
|
||||
# * Perform image compression (on a different server resource than the main process)
|
||||
#mediaProxy: https://example.com/proxy
|
||||
|
||||
# Proxy remote files (default: true)
|
||||
# Proxy remote files by this instance or mediaProxy to prevent remote files from running in remote domains.
|
||||
proxyRemoteFiles: true
|
||||
|
||||
# Movie Thumbnail Generation URL
|
||||
# There is no reference implementation.
|
||||
# For example, Misskey will point to the following URL:
|
||||
# https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4
|
||||
#videoThumbnailGenerator: https://example.com
|
||||
|
||||
# Sign to ActivityPub GET request (default: true)
|
||||
signToActivityPubGet: true
|
||||
|
||||
# For security reasons, uploading attachments from the intranet is prohibited,
|
||||
# but exceptions can be made from the following settings. Default value is "undefined".
|
||||
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
|
||||
@@ -332,6 +339,14 @@ proxyBypassHosts:
|
||||
# '127.0.0.1/32'
|
||||
#]
|
||||
|
||||
# Disable automatic redirect for ActivityPub object lookup. (default: false)
|
||||
# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation.
|
||||
# However it will make it impossible for other instances to lookup third-party user and notes through your URL.
|
||||
#disallowExternalApRedirect: true
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
||||
# PID File of master process
|
||||
#pidFile: /tmp/misskey.pid
|
||||
|
||||
|
||||
@@ -202,6 +202,15 @@ proxyBypassHosts:
|
||||
# Media Proxy
|
||||
#mediaProxy: https://example.com/proxy
|
||||
|
||||
# Proxy remote files (default: true)
|
||||
proxyRemoteFiles: true
|
||||
|
||||
# Sign to ActivityPub GET request (default: true)
|
||||
signToActivityPubGet: true
|
||||
|
||||
allowedPrivateNetworks: [
|
||||
'127.0.0.1/32'
|
||||
]
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
||||
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,14 +1,5 @@
|
||||
## 2025.5.1
|
||||
|
||||
### Note
|
||||
- 設定ファイルの以下の項目がコントロールパネルから設定するようになりました
|
||||
- signToActivityPubGet
|
||||
- proxyRemoteFiles
|
||||
- disallowExternalApRedirect
|
||||
- 許可しないかどうかではなく、許可するかどうかの設定(allowExternalApRedirect)になりました
|
||||
- 設定ファイルの maxFileSize が削除されました
|
||||
- リクエストの最大ペイロードサイズは、nginxやCDNなどのMisskeyの前段で制限をするか、ロール設定で制限してください。
|
||||
|
||||
### General
|
||||
- Feat: 非ログインでサーバーを閲覧された際に、サーバー内のコンテンツを非公開にすることができるようになりました
|
||||
- モデレーションが行き届きにくい不適切なリモートコンテンツなどが、自サーバー経由で図らずもインターネットに公開されてしまうことによるトラブル防止などに役立ちます
|
||||
@@ -23,8 +14,6 @@
|
||||
- 何らの理由によりWebsocket接続が行えない環境でも快適に利用可能です
|
||||
- 従来のWebsocket接続を行うモードはリアルタイムモードとして再定義されました
|
||||
- チャットなど、一部の機能は引き続き設定に関わらずWebsocket接続が行われます
|
||||
- Feat: 絵文字をミュート可能にする機能
|
||||
- 絵文字(ユニコードの絵文字・カスタム絵文字)毎にミュートし、不可視化することができるようになりました
|
||||
- Enhance: メモリ使用量を軽減しました
|
||||
- Enhance: 画像の高品質なプレースホルダを無効化してパフォーマンスを向上させるオプションを追加
|
||||
- Enhance: 招待されているが参加していないルームを開いたときに、招待を承認するかどうか尋ねるように
|
||||
|
||||
@@ -221,6 +221,12 @@ id: "aidx"
|
||||
# Media Proxy
|
||||
#mediaProxy: https://example.com/proxy
|
||||
|
||||
# Sign to ActivityPub GET request (default: true)
|
||||
signToActivityPubGet: true
|
||||
|
||||
#allowedPrivateNetworks: [
|
||||
# '127.0.0.1/32'
|
||||
#]
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
||||
76
locales/index.d.ts
vendored
76
locales/index.d.ts
vendored
@@ -707,7 +707,7 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"cacheRemoteFiles": string;
|
||||
/**
|
||||
* この設定を有効にすると、リモートファイルをこのサーバーのストレージにキャッシュするようになります。画像の表示が高速になりますが、サーバーのストレージを多く消費します。リモートユーザーがどれほどキャッシュを保持するかは、ロールによるドライブ容量制限によって決定されます。この制限を超えた場合、古いファイルからキャッシュが削除されリンクになります。この設定が無効の場合、リモートのファイルを最初からリンクとして保持します。
|
||||
* この設定を有効にすると、リモートファイルをこのサーバーのストレージにキャッシュするようになります。画像の表示が高速になりますが、サーバーのストレージを多く消費します。リモートユーザーがどれほどキャッシュを保持するかは、ロールによるドライブ容量制限によって決定されます。この制限を超えた場合、古いファイルからキャッシュが削除されリンクになります。この設定が無効の場合、リモートのファイルを最初からリンクとして保持しますが、画像のサムネイル生成やユーザーのプライバシー保護のために、default.ymlでproxyRemoteFilesをtrueにすることをお勧めします。
|
||||
*/
|
||||
"cacheRemoteFilesDescription": string;
|
||||
/**
|
||||
@@ -3186,10 +3186,6 @@ export interface Locale extends ILocale {
|
||||
* 反映には再起動が必要です。
|
||||
*/
|
||||
"needReloadToApply": string;
|
||||
/**
|
||||
* 反映にはサーバーの再起動が必要です。
|
||||
*/
|
||||
"needToRestartServerToApply": string;
|
||||
/**
|
||||
* タイトルバーを表示する
|
||||
*/
|
||||
@@ -5429,22 +5425,6 @@ export interface Locale extends ILocale {
|
||||
* オフにする
|
||||
*/
|
||||
"turnItOff": string;
|
||||
/**
|
||||
* 絵文字ミュート
|
||||
*/
|
||||
"emojiMute": string;
|
||||
/**
|
||||
* 絵文字ミュート解除
|
||||
*/
|
||||
"emojiUnmute": string;
|
||||
/**
|
||||
* {x}をミュート
|
||||
*/
|
||||
"muteX": ParameterizedString<"x">;
|
||||
/**
|
||||
* {x}のミュートを解除
|
||||
*/
|
||||
"unmuteX": ParameterizedString<"x">;
|
||||
"_chat": {
|
||||
/**
|
||||
* まだメッセージはありません
|
||||
@@ -6456,30 +6436,6 @@ export interface Locale extends ILocale {
|
||||
* このサーバーを利用するのが自分だけの場合、このモードを有効にすることで動作が最適化されます。
|
||||
*/
|
||||
"singleUserMode_description": string;
|
||||
/**
|
||||
* GETリクエストに署名する
|
||||
*/
|
||||
"signToActivityPubGet": string;
|
||||
/**
|
||||
* 通常は有効にしてください。連合の通信に関する問題がある場合に、無効にすると改善することがありますが、逆にサーバーによっては通信が不可になることがあります。
|
||||
*/
|
||||
"signToActivityPubGet_description": string;
|
||||
/**
|
||||
* リモートファイルをプロキシする
|
||||
*/
|
||||
"proxyRemoteFiles": string;
|
||||
/**
|
||||
* 有効にすると、リモートのファイルをプロキシして提供します。画像のサムネイル生成やユーザーのプライバシー保護に役立ちます。
|
||||
*/
|
||||
"proxyRemoteFiles_description": string;
|
||||
/**
|
||||
* ActivityPub経由の照会にリダイレクトを許可する
|
||||
*/
|
||||
"allowExternalApRedirect": string;
|
||||
/**
|
||||
* 有効にすると、他のサーバーがこのサーバーを通して第三者のコンテンツを照会することが可能になりますが、コンテンツのなりすましが発生する可能性があります。
|
||||
*/
|
||||
"allowExternalApRedirect_description": string;
|
||||
/**
|
||||
* 非利用者に対するユーザー作成コンテンツの公開範囲
|
||||
*/
|
||||
@@ -11898,36 +11854,6 @@ export interface Locale extends ILocale {
|
||||
"text3": string;
|
||||
};
|
||||
};
|
||||
"_clientPerformanceIssueTip": {
|
||||
/**
|
||||
* バッテリー消費が多いと感じたら
|
||||
*/
|
||||
"title": string;
|
||||
/**
|
||||
* アドブロッカーを無効にしてください
|
||||
*/
|
||||
"makeSureDisabledAdBlocker": string;
|
||||
/**
|
||||
* アドブロッカーはパフォーマンスに影響を及ぼすことがあります。OSの機能やブラウザの機能・アドオンなどでアドブロッカーが有効になっていないか確認してください。
|
||||
*/
|
||||
"makeSureDisabledAdBlocker_description": string;
|
||||
/**
|
||||
* カスタムCSSを無効にしてください
|
||||
*/
|
||||
"makeSureDisabledCustomCss": string;
|
||||
/**
|
||||
* スタイルを上書きするとパフォーマンスに影響を及ぼすことがあります。カスタムCSSや、スタイルを上書きする拡張機能が有効になっていないか確認してください。
|
||||
*/
|
||||
"makeSureDisabledCustomCss_description": string;
|
||||
/**
|
||||
* 拡張機能を無効にしてください
|
||||
*/
|
||||
"makeSureDisabledAddons": string;
|
||||
/**
|
||||
* 一部の拡張機能はクライアントの動作に干渉しパフォーマンスに影響を及ぼすことがあります。ブラウザの拡張機能を無効にして改善するか確認してください。
|
||||
*/
|
||||
"makeSureDisabledAddons_description": string;
|
||||
};
|
||||
}
|
||||
declare const locales: {
|
||||
[lang: string]: Locale;
|
||||
|
||||
@@ -172,7 +172,7 @@ emojiUrl: "絵文字画像URL"
|
||||
addEmoji: "絵文字を追加"
|
||||
settingGuide: "おすすめ設定"
|
||||
cacheRemoteFiles: "リモートのファイルをキャッシュする"
|
||||
cacheRemoteFilesDescription: "この設定を有効にすると、リモートファイルをこのサーバーのストレージにキャッシュするようになります。画像の表示が高速になりますが、サーバーのストレージを多く消費します。リモートユーザーがどれほどキャッシュを保持するかは、ロールによるドライブ容量制限によって決定されます。この制限を超えた場合、古いファイルからキャッシュが削除されリンクになります。この設定が無効の場合、リモートのファイルを最初からリンクとして保持します。"
|
||||
cacheRemoteFilesDescription: "この設定を有効にすると、リモートファイルをこのサーバーのストレージにキャッシュするようになります。画像の表示が高速になりますが、サーバーのストレージを多く消費します。リモートユーザーがどれほどキャッシュを保持するかは、ロールによるドライブ容量制限によって決定されます。この制限を超えた場合、古いファイルからキャッシュが削除されリンクになります。この設定が無効の場合、リモートのファイルを最初からリンクとして保持しますが、画像のサムネイル生成やユーザーのプライバシー保護のために、default.ymlでproxyRemoteFilesをtrueにすることをお勧めします。"
|
||||
youCanCleanRemoteFilesCache: "ファイル管理の🗑️ボタンで全てのキャッシュを削除できます。"
|
||||
cacheRemoteSensitiveFiles: "リモートのセンシティブなファイルをキャッシュする"
|
||||
cacheRemoteSensitiveFilesDescription: "この設定を無効にすると、リモートのセンシティブなファイルはキャッシュせず直リンクするようになります。"
|
||||
@@ -792,7 +792,6 @@ wide: "広い"
|
||||
narrow: "狭い"
|
||||
reloadToApplySetting: "設定はページリロード後に反映されます。"
|
||||
needReloadToApply: "反映には再起動が必要です。"
|
||||
needToRestartServerToApply: "反映にはサーバーの再起動が必要です。"
|
||||
showTitlebar: "タイトルバーを表示する"
|
||||
clearCache: "キャッシュをクリア"
|
||||
onlineUsersCount: "{n}人がオンライン"
|
||||
@@ -1352,10 +1351,6 @@ advice: "アドバイス"
|
||||
realtimeMode: "リアルタイムモード"
|
||||
turnItOn: "オンにする"
|
||||
turnItOff: "オフにする"
|
||||
emojiMute: "絵文字ミュート"
|
||||
emojiUnmute: "絵文字ミュート解除"
|
||||
muteX: "{x}をミュート"
|
||||
unmuteX: "{x}のミュートを解除"
|
||||
|
||||
_chat:
|
||||
noMessagesYet: "まだメッセージはありません"
|
||||
@@ -1640,12 +1635,6 @@ _serverSettings:
|
||||
deliverSuspendedSoftwareDescription: "脆弱性などの理由で、サーバーのソフトウェアの名前及びバージョンの範囲を指定して配信を停止できます。このバージョン情報はサーバーが提供したものであり、信頼性は保証されません。バージョン指定には semver の範囲指定が使用できますが、>= 2024.3.1 と指定すると 2024.3.1-custom.0 のようなカスタムバージョンが含まれないため、>= 2024.3.1-0 のように prerelease の指定を行うことを推奨します。"
|
||||
singleUserMode: "お一人様モード"
|
||||
singleUserMode_description: "このサーバーを利用するのが自分だけの場合、このモードを有効にすることで動作が最適化されます。"
|
||||
signToActivityPubGet: "GETリクエストに署名する"
|
||||
signToActivityPubGet_description: "通常は有効にしてください。連合の通信に関する問題がある場合に、無効にすると改善することがありますが、逆にサーバーによっては通信が不可になることがあります。"
|
||||
proxyRemoteFiles: "リモートファイルをプロキシする"
|
||||
proxyRemoteFiles_description: "有効にすると、リモートのファイルをプロキシして提供します。画像のサムネイル生成やユーザーのプライバシー保護に役立ちます。"
|
||||
allowExternalApRedirect: "ActivityPub経由の照会にリダイレクトを許可する"
|
||||
allowExternalApRedirect_description: "有効にすると、他のサーバーがこのサーバーを通して第三者のコンテンツを照会することが可能になりますが、コンテンツのなりすましが発生する可能性があります。"
|
||||
userGeneratedContentsVisibilityForVisitor: "非利用者に対するユーザー作成コンテンツの公開範囲"
|
||||
userGeneratedContentsVisibilityForVisitor_description: "モデレーションが行き届きにくい不適切なリモートコンテンツなどが、自サーバー経由で図らずもインターネットに公開されてしまうことによるトラブル防止などに役立ちます。"
|
||||
userGeneratedContentsVisibilityForVisitor_description2: "サーバーで受信したリモートのコンテンツを含め、サーバー内の全てのコンテンツを無条件でインターネットに公開することはリスクが伴います。特に、分散型の特性を知らない閲覧者にとっては、リモートのコンテンツであってもサーバー内で作成されたコンテンツであると誤って認識してしまう可能性があるため、注意が必要です。"
|
||||
@@ -3181,12 +3170,3 @@ _serverSetupWizard:
|
||||
text1: "Misskeyは有志によって開発されている無料のソフトウェアです。"
|
||||
text2: "今後も開発を続けられるように、よろしければぜひカンパをお願いいたします。"
|
||||
text3: "支援者向け特典もあります!"
|
||||
|
||||
_clientPerformanceIssueTip:
|
||||
title: "バッテリー消費が多いと感じたら"
|
||||
makeSureDisabledAdBlocker: "アドブロッカーを無効にしてください"
|
||||
makeSureDisabledAdBlocker_description: "アドブロッカーはパフォーマンスに影響を及ぼすことがあります。OSの機能やブラウザの機能・アドオンなどでアドブロッカーが有効になっていないか確認してください。"
|
||||
makeSureDisabledCustomCss: "カスタムCSSを無効にしてください"
|
||||
makeSureDisabledCustomCss_description: "スタイルを上書きするとパフォーマンスに影響を及ぼすことがあります。カスタムCSSや、スタイルを上書きする拡張機能が有効になっていないか確認してください。"
|
||||
makeSureDisabledAddons: "拡張機能を無効にしてください"
|
||||
makeSureDisabledAddons_description: "一部の拡張機能はクライアントの動作に干渉しパフォーマンスに影響を及ぼすことがあります。ブラウザの拡張機能を無効にして改善するか確認してください。"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2025.5.1-alpha.2",
|
||||
"version": "2025.5.1-alpha.1",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import {loadConfig} from "./js/migration-config.js";
|
||||
|
||||
export class MigrateSomeConfigFileSettingsToMeta1746949539915 {
|
||||
name = 'MigrateSomeConfigFileSettingsToMeta1746949539915'
|
||||
|
||||
async up(queryRunner) {
|
||||
const config = loadConfig();
|
||||
// $1 cannot be used in ALTER TABLE queries
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "proxyRemoteFiles" boolean NOT NULL DEFAULT ${config.proxyRemoteFiles}`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "signToActivityPubGet" boolean NOT NULL DEFAULT ${config.signToActivityPubGet}`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "allowExternalApRedirect" boolean NOT NULL DEFAULT ${!config.disallowExternalApRedirect}`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "allowExternalApRedirect"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "signToActivityPubGet"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "proxyRemoteFiles"`);
|
||||
}
|
||||
}
|
||||
@@ -3,29 +3,6 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { path as configYamlPath } from '../../built/config.js';
|
||||
import * as yaml from 'js-yaml';
|
||||
import fs from "node:fs";
|
||||
|
||||
export function isConcurrentIndexMigrationEnabled() {
|
||||
return process.env.MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY === '1';
|
||||
}
|
||||
|
||||
let loadedConfigCache = undefined;
|
||||
|
||||
function loadConfigInternal() {
|
||||
const config = yaml.load(fs.readFileSync(configYamlPath, 'utf-8'));
|
||||
|
||||
return {
|
||||
disallowExternalApRedirect: Boolean(config.disallowExternalApRedirect ?? false),
|
||||
proxyRemoteFiles: Boolean(config.proxyRemoteFiles ?? false),
|
||||
signToActivityPubGet: Boolean(config.signToActivityPubGet ?? true),
|
||||
}
|
||||
}
|
||||
|
||||
export function loadConfig() {
|
||||
if (loadedConfigCache === undefined) {
|
||||
loadedConfigCache = loadConfigInternal();
|
||||
}
|
||||
return loadedConfigCache;
|
||||
}
|
||||
|
||||
@@ -79,6 +79,9 @@ type Source = {
|
||||
proxyBypassHosts?: string[];
|
||||
|
||||
allowedPrivateNetworks?: string[];
|
||||
disallowExternalApRedirect?: boolean;
|
||||
|
||||
maxFileSize?: number;
|
||||
|
||||
clusterLimit?: number;
|
||||
|
||||
@@ -97,8 +100,11 @@ type Source = {
|
||||
inboxJobMaxAttempts?: number;
|
||||
|
||||
mediaProxy?: string;
|
||||
proxyRemoteFiles?: boolean;
|
||||
videoThumbnailGenerator?: string;
|
||||
|
||||
signToActivityPubGet?: boolean;
|
||||
|
||||
perChannelMaxNoteCacheCount?: number;
|
||||
perUserNotificationsMaxCount?: number;
|
||||
deactivateAntennaThreshold?: number;
|
||||
@@ -150,6 +156,8 @@ export type Config = {
|
||||
proxySmtp: string | undefined;
|
||||
proxyBypassHosts: string[] | undefined;
|
||||
allowedPrivateNetworks: string[] | undefined;
|
||||
disallowExternalApRedirect: boolean;
|
||||
maxFileSize: number;
|
||||
clusterLimit: number | undefined;
|
||||
id: string;
|
||||
outgoingAddress: string | undefined;
|
||||
@@ -162,6 +170,8 @@ export type Config = {
|
||||
relationshipJobPerSec: number | undefined;
|
||||
deliverJobMaxAttempts: number | undefined;
|
||||
inboxJobMaxAttempts: number | undefined;
|
||||
proxyRemoteFiles: boolean | undefined;
|
||||
signToActivityPubGet: boolean | undefined;
|
||||
logging?: {
|
||||
sql?: {
|
||||
disableQueryTruncation?: boolean,
|
||||
@@ -219,7 +229,7 @@ const dir = `${_dirname}/../../../.config`;
|
||||
/**
|
||||
* Path of configuration file
|
||||
*/
|
||||
export const path = process.env.MISSKEY_CONFIG_YML
|
||||
const path = process.env.MISSKEY_CONFIG_YML
|
||||
? resolve(dir, process.env.MISSKEY_CONFIG_YML)
|
||||
: process.env.NODE_ENV === 'test'
|
||||
? resolve(dir, 'test.yml')
|
||||
@@ -290,6 +300,8 @@ export function loadConfig(): Config {
|
||||
proxySmtp: config.proxySmtp,
|
||||
proxyBypassHosts: config.proxyBypassHosts,
|
||||
allowedPrivateNetworks: config.allowedPrivateNetworks,
|
||||
disallowExternalApRedirect: config.disallowExternalApRedirect ?? false,
|
||||
maxFileSize: config.maxFileSize ?? 262144000,
|
||||
clusterLimit: config.clusterLimit,
|
||||
outgoingAddress: config.outgoingAddress,
|
||||
outgoingAddressFamily: config.outgoingAddressFamily,
|
||||
@@ -301,6 +313,8 @@ export function loadConfig(): Config {
|
||||
relationshipJobPerSec: config.relationshipJobPerSec,
|
||||
deliverJobMaxAttempts: config.deliverJobMaxAttempts,
|
||||
inboxJobMaxAttempts: config.inboxJobMaxAttempts,
|
||||
proxyRemoteFiles: config.proxyRemoteFiles,
|
||||
signToActivityPubGet: config.signToActivityPubGet ?? true,
|
||||
mediaProxy: externalMediaProxy ?? internalMediaProxy,
|
||||
externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy,
|
||||
videoThumbnailGenerator: config.videoThumbnailGenerator ?
|
||||
|
||||
@@ -41,7 +41,7 @@ export class DownloadService {
|
||||
|
||||
const timeout = 30 * 1000;
|
||||
const operationTimeout = 60 * 1000;
|
||||
const maxSize = 1024 * 1024 * 1024; // 1GB
|
||||
const maxSize = this.config.maxFileSize;
|
||||
|
||||
const urlObj = new URL(url);
|
||||
let filename = urlObj.pathname.split('/').pop() ?? 'untitled';
|
||||
|
||||
@@ -104,7 +104,7 @@ export class Resolver {
|
||||
throw new IdentifiableError('09d79f9e-64f1-4316-9cfa-e75c4d091574', 'Instance is blocked');
|
||||
}
|
||||
|
||||
if (this.meta.signToActivityPubGet && !this.user) {
|
||||
if (this.config.signToActivityPubGet && !this.user) {
|
||||
this.user = await this.systemAccountService.fetch('actor');
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { DriveFilesRepository, MiMeta } from '@/models/_.js';
|
||||
import type { DriveFilesRepository } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
@@ -34,9 +34,6 @@ export class DriveFileEntityService {
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.meta)
|
||||
private meta: MiMeta,
|
||||
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
@@ -98,7 +95,7 @@ export class DriveFileEntityService {
|
||||
return this.getProxiedUrl(file.uri, 'static');
|
||||
}
|
||||
|
||||
if (file.uri != null && file.isLink && this.meta.proxyRemoteFiles) {
|
||||
if (file.uri != null && file.isLink && this.config.proxyRemoteFiles) {
|
||||
// リモートかつ期限切れはローカルプロキシを試みる
|
||||
// 従来は/files/${thumbnailAccessKey}にアクセスしていたが、
|
||||
// /filesはメディアプロキシにリダイレクトするようにしたため直接メディアプロキシを指定する
|
||||
@@ -118,7 +115,7 @@ export class DriveFileEntityService {
|
||||
}
|
||||
|
||||
// リモートかつ期限切れはローカルプロキシを試みる
|
||||
if (file.uri != null && file.isLink && this.meta.proxyRemoteFiles) {
|
||||
if (file.uri != null && file.isLink && this.config.proxyRemoteFiles) {
|
||||
const key = file.webpublicAccessKey;
|
||||
|
||||
if (key && !key.match('/')) { // 古いものはここにオブジェクトストレージキーが入ってるので除外
|
||||
|
||||
@@ -131,6 +131,7 @@ export class MetaEntityService {
|
||||
mediaProxy: this.config.mediaProxy,
|
||||
enableUrlPreview: instance.urlPreviewEnabled,
|
||||
noteSearchableScope: (this.config.meilisearch == null || this.config.meilisearch.scope !== 'local') ? 'global' : 'local',
|
||||
maxFileSize: this.config.maxFileSize,
|
||||
federation: this.meta.federation,
|
||||
};
|
||||
|
||||
|
||||
@@ -680,21 +680,6 @@ export class MiMeta {
|
||||
default: false,
|
||||
})
|
||||
public singleUserMode: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public proxyRemoteFiles: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public signToActivityPubGet: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public allowExternalApRedirect: boolean;
|
||||
}
|
||||
|
||||
export type SoftwareSuspension = {
|
||||
|
||||
@@ -293,6 +293,10 @@ export const packedMetaLiteSchema = {
|
||||
optional: false, nullable: false,
|
||||
default: 'local',
|
||||
},
|
||||
maxFileSize: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
federation: {
|
||||
type: 'string',
|
||||
enum: ['all', 'specified', 'none'],
|
||||
|
||||
@@ -108,7 +108,7 @@ export class ServerService implements OnApplicationShutdown {
|
||||
// this will break lookup that involve copying a URL from a third-party server, like trying to lookup http://charlie.example.com/@alice@alice.com
|
||||
//
|
||||
// this is not required by standard but protect us from peers that did not validate final URL.
|
||||
if (!this.meta.allowExternalApRedirect) {
|
||||
if (this.config.disallowExternalApRedirect) {
|
||||
const maybeApLookupRegex = /application\/activity\+json|application\/ld\+json.+activitystreams/i;
|
||||
fastify.addHook('onSend', (request, reply, _, done) => {
|
||||
const location = reply.getHeader('location');
|
||||
@@ -133,8 +133,8 @@ export class ServerService implements OnApplicationShutdown {
|
||||
reply.header('content-type', 'text/plain; charset=utf-8');
|
||||
reply.header('link', `<${encodeURI(location)}>; rel="canonical"`);
|
||||
done(null, [
|
||||
'Refusing to relay remote ActivityPub object lookup.',
|
||||
'',
|
||||
"Refusing to relay remote ActivityPub object lookup.",
|
||||
"",
|
||||
`Please remove 'application/activity+json' and 'application/ld+json' from the Accept header or fetch using the authoritative URL at ${location}.`,
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
@@ -51,6 +51,7 @@ export class ApiServerService {
|
||||
|
||||
fastify.register(multipart, {
|
||||
limits: {
|
||||
fileSize: this.config.maxFileSize,
|
||||
files: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -555,18 +555,6 @@ export const meta = {
|
||||
enum: ['all', 'local', 'none'],
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
proxyRemoteFiles: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
signToActivityPubGet: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
allowExternalApRedirect: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -714,9 +702,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
deliverSuspendedSoftware: instance.deliverSuspendedSoftware,
|
||||
singleUserMode: instance.singleUserMode,
|
||||
ugcVisibilityForVisitor: instance.ugcVisibilityForVisitor,
|
||||
proxyRemoteFiles: instance.proxyRemoteFiles,
|
||||
signToActivityPubGet: instance.signToActivityPubGet,
|
||||
allowExternalApRedirect: instance.allowExternalApRedirect,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -201,9 +201,6 @@ export const paramDef = {
|
||||
type: 'string',
|
||||
enum: ['all', 'local', 'none'],
|
||||
},
|
||||
proxyRemoteFiles: { type: 'boolean' },
|
||||
signToActivityPubGet: { type: 'boolean' },
|
||||
allowExternalApRedirect: { type: 'boolean' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -706,18 +703,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
set.ugcVisibilityForVisitor = ps.ugcVisibilityForVisitor;
|
||||
}
|
||||
|
||||
if (ps.proxyRemoteFiles !== undefined) {
|
||||
set.proxyRemoteFiles = ps.proxyRemoteFiles;
|
||||
}
|
||||
|
||||
if (ps.signToActivityPubGet !== undefined) {
|
||||
set.signToActivityPubGet = ps.signToActivityPubGet;
|
||||
}
|
||||
|
||||
if (ps.allowExternalApRedirect !== undefined) {
|
||||
set.allowExternalApRedirect = ps.allowExternalApRedirect;
|
||||
}
|
||||
|
||||
const before = await this.metaService.fetch(true);
|
||||
|
||||
await this.metaService.update(set);
|
||||
|
||||
@@ -17,6 +17,8 @@ proxyBypassHosts:
|
||||
- www.recaptcha.net
|
||||
- hcaptcha.com
|
||||
- challenges.cloudflare.com
|
||||
proxyRemoteFiles: true
|
||||
signToActivityPubGet: true
|
||||
allowedPrivateNetworks:
|
||||
- 127.0.0.1/32
|
||||
- 172.20.0.0/16
|
||||
|
||||
BIN
packages/frontend/assets/sounds/syuilo/pa.mp3
Normal file
BIN
packages/frontend/assets/sounds/syuilo/pa.mp3
Normal file
Binary file not shown.
BIN
packages/frontend/assets/sounds/syuilo/pa2.mp3
Normal file
BIN
packages/frontend/assets/sounds/syuilo/pa2.mp3
Normal file
Binary file not shown.
BIN
packages/frontend/assets/sounds/syuilo/sho.mp3
Normal file
BIN
packages/frontend/assets/sounds/syuilo/sho.mp3
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB |
@@ -27,6 +27,7 @@ import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
|
||||
import { getScrollContainer } from '@@/js/scroll.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { isHorizontalSwipeSwiping } from '@/utility/touch.js';
|
||||
import * as sound from '@/utility/sound.js';
|
||||
|
||||
const SCROLL_STOP = 10;
|
||||
const MAX_PULL_DISTANCE = Infinity;
|
||||
@@ -200,7 +201,13 @@ function moving(event: MouseEvent | TouchEvent) {
|
||||
const moveHeight = moveScreenY - startScreenY!;
|
||||
pullDistance.value = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE);
|
||||
|
||||
let before = isPulledEnough.value;
|
||||
isPulledEnough.value = pullDistance.value >= FIRE_THRESHOLD;
|
||||
if (before === false && isPulledEnough.value === true) {
|
||||
sound.playUrl('/client-assets/sounds/syuilo/sho.mp3', {
|
||||
volume: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,6 +216,9 @@ function moving(event: MouseEvent | TouchEvent) {
|
||||
* タイムアウトがないのでこれを最終的に実行しないと出たままになる
|
||||
*/
|
||||
function refreshFinished() {
|
||||
sound.playUrl('/client-assets/sounds/syuilo/pa2.mp3', {
|
||||
volume: 1,
|
||||
});
|
||||
closeContent().then(() => {
|
||||
isPulling.value = false;
|
||||
isRefreshing.value = false;
|
||||
|
||||
@@ -22,7 +22,6 @@ import { computed, inject, onMounted, useTemplateRef, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { getUnicodeEmoji } from '@@/js/emojilist.js';
|
||||
import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
|
||||
import type { MenuItem } from '@/types/menu';
|
||||
import XDetails from '@/components/MkReactionsViewer.details.vue';
|
||||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||
import * as os from '@/os.js';
|
||||
@@ -37,7 +36,6 @@ import { customEmojisMap } from '@/custom-emojis.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { DI } from '@/di.js';
|
||||
import { noteEvents } from '@/composables/use-note-capture.js';
|
||||
import { mute as muteEmoji, unmute as unmuteEmoji, checkMuted as isEmojiMuted } from '@/utility/emoji-mute.js';
|
||||
|
||||
const props = defineProps<{
|
||||
noteId: Misskey.entities.Note['id'];
|
||||
@@ -65,7 +63,6 @@ const canToggle = computed(() => {
|
||||
return !props.reaction.match(/@\w/) && $i && emoji.value;
|
||||
});
|
||||
const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));
|
||||
const isLocalCustomEmoji = props.reaction[0] === ':' && props.reaction.includes('@.');
|
||||
|
||||
async function toggleReaction() {
|
||||
if (!canToggle.value) return;
|
||||
@@ -142,55 +139,21 @@ async function toggleReaction() {
|
||||
}
|
||||
|
||||
async function menu(ev) {
|
||||
let menuItems: MenuItem[] = [];
|
||||
if (!canGetInfo.value) return;
|
||||
|
||||
if (canGetInfo.value) {
|
||||
menuItems.push({
|
||||
text: i18n.ts.info,
|
||||
icon: 'ti ti-info-circle',
|
||||
action: async () => {
|
||||
const { dispose } = os.popup(MkCustomEmojiDetailedDialog, {
|
||||
emoji: await misskeyApiGet('emoji', {
|
||||
name: props.reaction.replace(/:/g, '').replace(/@\./, ''),
|
||||
}),
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (isEmojiMuted(props.reaction).value) {
|
||||
menuItems.push({
|
||||
text: i18n.ts.emojiUnmute,
|
||||
icon: 'ti ti-mood-smile',
|
||||
action: () => {
|
||||
os.confirm({
|
||||
type: 'question',
|
||||
title: i18n.tsx.unmuteX({ x: isLocalCustomEmoji ? `:${emojiName.value}:` : props.reaction }),
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
unmuteEmoji(props.reaction);
|
||||
});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
menuItems.push({
|
||||
text: i18n.ts.emojiMute,
|
||||
icon: 'ti ti-mood-off',
|
||||
action: () => {
|
||||
os.confirm({
|
||||
type: 'question',
|
||||
title: i18n.tsx.muteX({ x: isLocalCustomEmoji ? `:${emojiName.value}:` : props.reaction }),
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
muteEmoji(props.reaction);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
|
||||
os.popupMenu([{
|
||||
text: i18n.ts.info,
|
||||
icon: 'ti ti-info-circle',
|
||||
action: async () => {
|
||||
const { dispose } = os.popup(MkCustomEmojiDetailedDialog, {
|
||||
emoji: await misskeyApiGet('emoji', {
|
||||
name: props.reaction.replace(/:/g, '').replace(/@\./, ''),
|
||||
}),
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
},
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
function anime() {
|
||||
|
||||
@@ -5,16 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<img
|
||||
v-if="shouldMute"
|
||||
:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
|
||||
src="/client-assets/unknown.png"
|
||||
:title="alt"
|
||||
draggable="false"
|
||||
style="-webkit-user-drag: none;"
|
||||
@click="onClick"
|
||||
/>
|
||||
<img
|
||||
v-else-if="errored && fallbackToImage"
|
||||
v-if="errored && fallbackToImage"
|
||||
:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
|
||||
src="/client-assets/dummy.png"
|
||||
:title="alt"
|
||||
@@ -49,7 +40,6 @@ import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialo
|
||||
import { $i } from '@/i.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { DI } from '@/di.js';
|
||||
import { makeEmojiMuteKey, mute as muteEmoji, unmute as unmuteEmoji, checkMuted as checkEmojiMuted } from '@/utility/emoji-mute';
|
||||
|
||||
const props = defineProps<{
|
||||
name: string;
|
||||
@@ -61,16 +51,12 @@ const props = defineProps<{
|
||||
menu?: boolean;
|
||||
menuReaction?: boolean;
|
||||
fallbackToImage?: boolean;
|
||||
ignoreMuted?: boolean;
|
||||
}>();
|
||||
|
||||
const react = inject(DI.mfmEmojiReactCallback);
|
||||
|
||||
const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substring(1, props.name.length - 1) : props.name).replace('@.', ''));
|
||||
const isLocal = computed(() => !props.host && (customEmojiName.value.endsWith('@.') || !customEmojiName.value.includes('@')));
|
||||
const emojiCodeToMute = makeEmojiMuteKey(props);
|
||||
const isMuted = checkEmojiMuted(emojiCodeToMute);
|
||||
const shouldMute = computed(() => !props.ignoreMuted && isMuted.value);
|
||||
|
||||
const rawUrl = computed(() => {
|
||||
if (props.url) {
|
||||
@@ -109,18 +95,14 @@ function onClick(ev: MouseEvent) {
|
||||
menuItems.push({
|
||||
type: 'label',
|
||||
text: `:${props.name}:`,
|
||||
}, {
|
||||
text: i18n.ts.copy,
|
||||
icon: 'ti ti-copy',
|
||||
action: () => {
|
||||
copyToClipboard(`:${props.name}:`);
|
||||
},
|
||||
});
|
||||
|
||||
if (isLocal.value) {
|
||||
menuItems.push({
|
||||
text: i18n.ts.copy,
|
||||
icon: 'ti ti-copy',
|
||||
action: () => {
|
||||
copyToClipboard(`:${props.name}:`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (props.menuReaction && react) {
|
||||
menuItems.push({
|
||||
text: i18n.ts.doReaction,
|
||||
@@ -131,43 +113,21 @@ function onClick(ev: MouseEvent) {
|
||||
});
|
||||
}
|
||||
|
||||
if (isLocal.value) {
|
||||
menuItems.push({
|
||||
type: 'divider',
|
||||
}, {
|
||||
text: i18n.ts.info,
|
||||
icon: 'ti ti-info-circle',
|
||||
action: async () => {
|
||||
const { dispose } = os.popup(MkCustomEmojiDetailedDialog, {
|
||||
emoji: await misskeyApiGet('emoji', {
|
||||
name: customEmojiName.value,
|
||||
}),
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
menuItems.push({
|
||||
text: i18n.ts.info,
|
||||
icon: 'ti ti-info-circle',
|
||||
action: async () => {
|
||||
const { dispose } = os.popup(MkCustomEmojiDetailedDialog, {
|
||||
emoji: await misskeyApiGet('emoji', {
|
||||
name: customEmojiName.value,
|
||||
}),
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (isMuted.value) {
|
||||
menuItems.push({
|
||||
text: i18n.ts.emojiUnmute,
|
||||
icon: 'ti ti-mood-smile',
|
||||
action: async () => {
|
||||
await unmute();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
menuItems.push({
|
||||
text: i18n.ts.emojiMute,
|
||||
icon: 'ti ti-mood-off',
|
||||
action: async () => {
|
||||
await mute();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (($i?.isModerator ?? $i?.isAdmin) && isLocal.value) {
|
||||
if ($i?.isModerator ?? $i?.isAdmin) {
|
||||
menuItems.push({
|
||||
text: i18n.ts.edit,
|
||||
icon: 'ti ti-pencil',
|
||||
@@ -192,36 +152,6 @@ async function edit(name: string) {
|
||||
});
|
||||
}
|
||||
|
||||
function mute() {
|
||||
const titleEmojiName = isLocal.value
|
||||
? `:${customEmojiName.value}:`
|
||||
: emojiCodeToMute;
|
||||
os.confirm({
|
||||
type: 'question',
|
||||
title: i18n.tsx.muteX({ x: titleEmojiName }),
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
muteEmoji(emojiCodeToMute);
|
||||
});
|
||||
}
|
||||
|
||||
function unmute() {
|
||||
const titleEmojiName = isLocal.value
|
||||
? `:${customEmojiName.value}:`
|
||||
: emojiCodeToMute;
|
||||
os.confirm({
|
||||
type: 'question',
|
||||
title: i18n.tsx.unmuteX({ x: titleEmojiName }),
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
unmuteEmoji(emojiCodeToMute);
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
||||
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<img v-if="shouldMute" :class="$style.root" src="/client-assets/unknown.png" :alt="props.emoji" decoding="async" @pointerenter="computeTitle" @click="onClick"/>
|
||||
<img v-else-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle" @click="onClick"/>
|
||||
<img v-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle" @click="onClick"/>
|
||||
<span v-else :alt="props.emoji" @pointerenter="computeTitle" @click="onClick">{{ colorizedNativeEmoji }}</span>
|
||||
</template>
|
||||
|
||||
@@ -19,13 +18,11 @@ import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { DI } from '@/di.js';
|
||||
import { mute as muteEmoji, unmute as unmuteEmoji, checkMuted as checkMutedEmoji } from '@/utility/emoji-mute.js';
|
||||
|
||||
const props = defineProps<{
|
||||
emoji: string;
|
||||
menu?: boolean;
|
||||
menuReaction?: boolean;
|
||||
ignoreMuted?: boolean;
|
||||
}>();
|
||||
|
||||
const react = inject(DI.mfmEmojiReactCallback, null);
|
||||
@@ -35,38 +32,12 @@ const char2path = prefer.s.emojiStyle === 'twemoji' ? char2twemojiFilePath : cha
|
||||
const useOsNativeEmojis = computed(() => prefer.s.emojiStyle === 'native');
|
||||
const url = computed(() => char2path(props.emoji));
|
||||
const colorizedNativeEmoji = computed(() => colorizeEmoji(props.emoji));
|
||||
const isMuted = checkMutedEmoji(props.emoji);
|
||||
const shouldMute = computed(() => isMuted.value && !props.ignoreMuted);
|
||||
|
||||
// Searching from an array with 2000 items for every emoji felt like too energy-consuming, so I decided to do it lazily on pointerenter
|
||||
function computeTitle(event: PointerEvent): void {
|
||||
(event.target as HTMLElement).title = getEmojiName(props.emoji);
|
||||
}
|
||||
|
||||
function mute() {
|
||||
os.confirm({
|
||||
type: 'question',
|
||||
title: i18n.tsx.muteX({ x: props.emoji }),
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
muteEmoji(props.emoji);
|
||||
});
|
||||
}
|
||||
|
||||
function unmute() {
|
||||
os.confirm({
|
||||
type: 'question',
|
||||
title: i18n.tsx.unmuteX({ x: props.emoji }),
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
unmuteEmoji(props.emoji);
|
||||
});
|
||||
}
|
||||
|
||||
function onClick(ev: MouseEvent) {
|
||||
if (props.menu) {
|
||||
const menuItems: MenuItem[] = [];
|
||||
@@ -92,22 +63,6 @@ function onClick(ev: MouseEvent) {
|
||||
});
|
||||
}
|
||||
|
||||
menuItems.push({
|
||||
type: 'divider',
|
||||
}, isMuted.value ? {
|
||||
text: i18n.ts.emojiUnmute,
|
||||
icon: 'ti ti-mood-smile',
|
||||
action: () => {
|
||||
unmute();
|
||||
},
|
||||
} : {
|
||||
text: i18n.ts.emojiMute,
|
||||
icon: 'ti ti-mood-off',
|
||||
action: () => {
|
||||
mute();
|
||||
},
|
||||
});
|
||||
|
||||
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,8 +435,6 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
|
||||
normal: props.plain,
|
||||
host: props.author.host,
|
||||
useOriginalSize: scale >= 2.5,
|
||||
menu: props.enableEmojiMenu,
|
||||
menuReaction: false,
|
||||
})];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,15 +35,10 @@ export type PagingCtx<E extends keyof Misskey.Endpoints = keyof Misskey.Endpoint
|
||||
|
||||
baseId?: MisskeyEntity['id'];
|
||||
direction?: 'newer' | 'older';
|
||||
|
||||
// 一部のAPIはさらに遡れる場合でもパフォーマンス上の理由でlimit以下の結果を返す場合があり、その場合はsafe、それ以外はlimitにすることを推奨
|
||||
canFetchDetection?: 'safe' | 'limit';
|
||||
};
|
||||
|
||||
export function usePagination<Endpoint extends keyof Misskey.Endpoints, T = Misskey.Endpoints[Endpoint]['res'] extends (infer I)[] ? I : never>(props: {
|
||||
ctx: PagingCtx<Endpoint>;
|
||||
autoInit?: boolean;
|
||||
autoReInit?: boolean;
|
||||
useShallowRef?: boolean;
|
||||
}) {
|
||||
const items = props.useShallowRef ? shallowRef<T[]>([]) : ref<T[]>([]);
|
||||
@@ -54,9 +49,8 @@ export function usePagination<Endpoint extends keyof Misskey.Endpoints, T = Miss
|
||||
const canFetchOlder = ref(false);
|
||||
const error = ref(false);
|
||||
|
||||
if (props.autoReInit !== false) {
|
||||
watch(() => [props.ctx.endpoint, props.ctx.params], init, { deep: true });
|
||||
}
|
||||
// パラメータに何らかの変更があった際、再読込したい(チャンネル等のIDが変わったなど)
|
||||
watch(() => [props.ctx.endpoint, props.ctx.params], init, { deep: true });
|
||||
|
||||
function getNewestId(): string | null | undefined {
|
||||
// 様々な要因により並び順は保証されないのでソートが必要
|
||||
@@ -98,20 +92,12 @@ export function usePagination<Endpoint extends keyof Misskey.Endpoints, T = Miss
|
||||
if (i === 3) item._shouldInsertAd_ = true;
|
||||
}
|
||||
|
||||
pushItems(res);
|
||||
|
||||
if (props.ctx.canFetchDetection === 'limit') {
|
||||
if (res.length < FIRST_FETCH_LIMIT) {
|
||||
canFetchOlder.value = false;
|
||||
} else {
|
||||
canFetchOlder.value = true;
|
||||
}
|
||||
} else if (props.ctx.canFetchDetection === 'safe' || props.ctx.canFetchDetection == null) {
|
||||
if (res.length === 0 || props.ctx.noPaging) {
|
||||
canFetchOlder.value = false;
|
||||
} else {
|
||||
canFetchOlder.value = true;
|
||||
}
|
||||
if (res.length === 0 || props.ctx.noPaging) {
|
||||
pushItems(res);
|
||||
canFetchOlder.value = false;
|
||||
} else {
|
||||
pushItems(res);
|
||||
canFetchOlder.value = true;
|
||||
}
|
||||
|
||||
error.value = false;
|
||||
@@ -144,22 +130,15 @@ export function usePagination<Endpoint extends keyof Misskey.Endpoints, T = Miss
|
||||
if (i === 10) item._shouldInsertAd_ = true;
|
||||
}
|
||||
|
||||
pushItems(res);
|
||||
|
||||
if (props.ctx.canFetchDetection === 'limit') {
|
||||
if (res.length < FIRST_FETCH_LIMIT) {
|
||||
canFetchOlder.value = false;
|
||||
} else {
|
||||
canFetchOlder.value = true;
|
||||
}
|
||||
} else if (props.ctx.canFetchDetection === 'safe' || props.ctx.canFetchDetection == null) {
|
||||
if (res.length === 0) {
|
||||
canFetchOlder.value = false;
|
||||
} else {
|
||||
canFetchOlder.value = true;
|
||||
}
|
||||
if (res.length === 0) {
|
||||
canFetchOlder.value = false;
|
||||
fetchingOlder.value = false;
|
||||
} else {
|
||||
pushItems(res);
|
||||
canFetchOlder.value = true;
|
||||
fetchingOlder.value = false;
|
||||
}
|
||||
}).finally(() => {
|
||||
}, err => {
|
||||
fetchingOlder.value = false;
|
||||
});
|
||||
}
|
||||
@@ -253,11 +232,9 @@ export function usePagination<Endpoint extends keyof Misskey.Endpoints, T = Miss
|
||||
}
|
||||
}
|
||||
|
||||
if (props.autoInit !== false) {
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
}
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
return {
|
||||
items: items as DeepReadonly<ShallowRef<T[]>>,
|
||||
|
||||
@@ -86,6 +86,28 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkTextarea>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-cloud"></i></template>
|
||||
<template #label>{{ i18n.ts.files }}</template>
|
||||
<template v-if="filesForm.modified.value" #footer>
|
||||
<MkFormFooter :form="filesForm"/>
|
||||
</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="filesForm.state.cacheRemoteFiles">
|
||||
<template #label>{{ i18n.ts.cacheRemoteFiles }}<span v-if="filesForm.modifiedStates.cacheRemoteFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}{{ i18n.ts.youCanCleanRemoteFilesCache }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<template v-if="filesForm.state.cacheRemoteFiles">
|
||||
<MkSwitch v-model="filesForm.state.cacheRemoteSensitiveFiles">
|
||||
<template #label>{{ i18n.ts.cacheRemoteSensitiveFiles }}<span v-if="filesForm.modifiedStates.cacheRemoteSensitiveFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</template>
|
||||
</MkSwitch>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-world-cog"></i></template>
|
||||
<template #label>ServiceWorker</template>
|
||||
@@ -233,36 +255,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkSwitch v-model="federationForm.state.signToActivityPubGet">
|
||||
<template #label>{{ i18n.ts._serverSettings.signToActivityPubGet }}<span v-if="federationForm.modifiedStates.signToActivityPubGet" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._serverSettings.signToActivityPubGet_description }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="federationForm.state.proxyRemoteFiles">
|
||||
<template #label>{{ i18n.ts._serverSettings.proxyRemoteFiles }}<span v-if="federationForm.modifiedStates.proxyRemoteFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts._serverSettings.proxyRemoteFiles_description }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="federationForm.state.allowExternalApRedirect">
|
||||
<template #label>{{ i18n.ts._serverSettings.allowExternalApRedirect }}<span v-if="federationForm.modifiedStates.allowExternalApRedirect" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>
|
||||
<div>{{ i18n.ts._serverSettings.allowExternalApRedirect_description }}</div>
|
||||
<div>{{ i18n.ts.needToRestartServerToApply }}</div>
|
||||
</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="federationForm.state.cacheRemoteFiles">
|
||||
<template #label>{{ i18n.ts.cacheRemoteFiles }}<span v-if="federationForm.modifiedStates.cacheRemoteFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}{{ i18n.ts.youCanCleanRemoteFilesCache }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<template v-if="federationForm.state.cacheRemoteFiles">
|
||||
<MkSwitch v-model="federationForm.state.cacheRemoteSensitiveFiles">
|
||||
<template #label>{{ i18n.ts.cacheRemoteSensitiveFiles }}<span v-if="federationForm.modifiedStates.cacheRemoteSensitiveFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||
<template #caption>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</template>
|
||||
</MkSwitch>
|
||||
</template>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
@@ -346,6 +338,17 @@ const pinnedUsersForm = useForm({
|
||||
fetchInstance(true);
|
||||
});
|
||||
|
||||
const filesForm = useForm({
|
||||
cacheRemoteFiles: meta.cacheRemoteFiles,
|
||||
cacheRemoteSensitiveFiles: meta.cacheRemoteSensitiveFiles,
|
||||
}, async (state) => {
|
||||
await os.apiWithDialog('admin/update-meta', {
|
||||
cacheRemoteFiles: state.cacheRemoteFiles,
|
||||
cacheRemoteSensitiveFiles: state.cacheRemoteSensitiveFiles,
|
||||
});
|
||||
fetchInstance(true);
|
||||
});
|
||||
|
||||
const serviceWorkerForm = useForm({
|
||||
enableServiceWorker: meta.enableServiceWorker,
|
||||
swPublicKey: meta.swPublickey ?? '',
|
||||
@@ -391,21 +394,11 @@ const federationForm = useForm({
|
||||
federation: meta.federation,
|
||||
federationHosts: meta.federationHosts.join('\n'),
|
||||
deliverSuspendedSoftware: meta.deliverSuspendedSoftware,
|
||||
signToActivityPubGet: meta.signToActivityPubGet,
|
||||
proxyRemoteFiles: meta.proxyRemoteFiles,
|
||||
allowExternalApRedirect: meta.allowExternalApRedirect,
|
||||
cacheRemoteFiles: meta.cacheRemoteFiles,
|
||||
cacheRemoteSensitiveFiles: meta.cacheRemoteSensitiveFiles,
|
||||
}, async (state) => {
|
||||
await os.apiWithDialog('admin/update-meta', {
|
||||
federation: state.federation,
|
||||
federationHosts: state.federationHosts.split('\n'),
|
||||
deliverSuspendedSoftware: state.deliverSuspendedSoftware,
|
||||
signToActivityPubGet: state.signToActivityPubGet,
|
||||
proxyRemoteFiles: state.proxyRemoteFiles,
|
||||
allowExternalApRedirect: state.allowExternalApRedirect,
|
||||
cacheRemoteFiles: state.cacheRemoteFiles,
|
||||
cacheRemoteSensitiveFiles: state.cacheRemoteSensitiveFiles,
|
||||
});
|
||||
fetchInstance(true);
|
||||
});
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :class="$style.emojis">
|
||||
<div v-for="emoji in emojis" :key="`emojiMute-${emoji}`" :class="$style.emoji" @click="onEmojiClick($event, emoji)">
|
||||
<MkCustomEmoji
|
||||
v-if="emoji.startsWith(':')"
|
||||
:name="customEmojiName(emoji)"
|
||||
:host="customEmojiHost(emoji)"
|
||||
:normal="true"
|
||||
:menu="false"
|
||||
:menuReaction="false"
|
||||
:ignoreMuted="true"
|
||||
/>
|
||||
<MkEmoji
|
||||
v-else
|
||||
:emoji="emoji"
|
||||
:menu="false"
|
||||
:menuReaction="false"
|
||||
:ignoreMuted="true"
|
||||
></MkEmoji>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkButton primary inline @click="add"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { MenuItem } from '@/types/menu';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import {
|
||||
mute as muteEmoji,
|
||||
unmute as unmuteEmoji,
|
||||
extractCustomEmojiName as customEmojiName,
|
||||
extractCustomEmojiHost as customEmojiHost,
|
||||
} from '@/utility/emoji-mute.js';
|
||||
|
||||
const emojis = prefer.model('mutingEmojis');
|
||||
|
||||
function getHTMLElement(ev: MouseEvent): HTMLElement {
|
||||
const target = ev.currentTarget ?? ev.target;
|
||||
return target as HTMLElement;
|
||||
}
|
||||
|
||||
function add(ev: MouseEvent) {
|
||||
os.pickEmoji(getHTMLElement(ev), { showPinned: false }).then((emoji) => {
|
||||
if (emoji) {
|
||||
muteEmoji(emoji);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onEmojiClick(ev: MouseEvent, emoji: string) {
|
||||
const menuItems : MenuItem[] = [{
|
||||
type: 'label',
|
||||
text: emoji,
|
||||
}, {
|
||||
text: i18n.ts.emojiUnmute,
|
||||
icon: 'ti ti-mood-off',
|
||||
action: () => unmute(emoji),
|
||||
}];
|
||||
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
function unmute(emoji: string) {
|
||||
os.confirm({
|
||||
type: 'question',
|
||||
title: i18n.tsx.unmuteX({ x: emoji }),
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
unmuteEmoji(emoji);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style module>
|
||||
.emojis {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji {
|
||||
display: inline-flex;
|
||||
height: 42px;
|
||||
padding: 0 6px;
|
||||
font-size: 1.5em;
|
||||
border-radius: 6px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--MI_THEME-buttonBg);
|
||||
}
|
||||
</style>
|
||||
@@ -49,20 +49,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker
|
||||
:label="i18n.ts.emojiMute"
|
||||
:keywords="['emoji', 'mute', 'hide']"
|
||||
>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-mood-off"></i></template>
|
||||
<template #label>{{ i18n.ts.emojiMute }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<XEmojiMute/>
|
||||
</div>
|
||||
</mkfolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker
|
||||
:label="i18n.ts.instanceMute"
|
||||
:keywords="['note', 'server', 'instance', 'host', 'federation', 'mute', 'hide']"
|
||||
@@ -177,7 +163,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import XEmojiMute from './mute-block.emoji-mute.vue';
|
||||
import XInstanceMute from './mute-block.instance-mute.vue';
|
||||
import XWordMute from './mute-block.word-mute.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
|
||||
@@ -601,24 +601,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<MkInfo>
|
||||
<div class="_gaps_s">
|
||||
<div>{{ i18n.ts._clientPerformanceIssueTip.title }}</div>
|
||||
<div>
|
||||
<div><b>{{ i18n.ts._clientPerformanceIssueTip.makeSureDisabledAdBlocker }}</b></div>
|
||||
<div>{{ i18n.ts._clientPerformanceIssueTip.makeSureDisabledAdBlocker_description }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div><b>{{ i18n.ts._clientPerformanceIssueTip.makeSureDisabledCustomCss }}</b></div>
|
||||
<div>{{ i18n.ts._clientPerformanceIssueTip.makeSureDisabledCustomCss_description }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div><b>{{ i18n.ts._clientPerformanceIssueTip.makeSureDisabledAddons }}</b></div>
|
||||
<div>{{ i18n.ts._clientPerformanceIssueTip.makeSureDisabledAddons_description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</MkInfo>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
@@ -345,9 +345,6 @@ export const PREF_DEF = {
|
||||
plugins: {
|
||||
default: [] as Plugin[],
|
||||
},
|
||||
mutingEmojis: {
|
||||
default: [] as string[],
|
||||
},
|
||||
|
||||
'sound.masterVolume': {
|
||||
default: 0.3,
|
||||
|
||||
@@ -407,7 +407,6 @@ function menuEdit() {
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
height: var(--top-height);
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.instance {
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { computed } from 'vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
// custom絵文字の情報からキーを作成する
|
||||
export function makeEmojiMuteKey(props: { name: string; host?: string | null }) {
|
||||
return props.name.startsWith(':') ? props.name : `:${props.name}${props.host ? `@${props.host}` : ''}:`;
|
||||
}
|
||||
|
||||
export function extractCustomEmojiName (name:string) {
|
||||
return (name[0] === ':' ? name.substring(1, name.length - 1) : name).replace('@.', '').split('@')[0];
|
||||
}
|
||||
|
||||
export function extractCustomEmojiHost (name:string) {
|
||||
// nameは:emojiName@host:の形式
|
||||
// 取り出したい部分はhostなので、@以降を取り出す
|
||||
const index = name.indexOf('@');
|
||||
if (index === -1) {
|
||||
return null;
|
||||
}
|
||||
const host = name.substring(index + 1, name.length - 1);
|
||||
if (host === '' || host === '.') {
|
||||
return null;
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
export function mute(emoji: string) {
|
||||
const isCustomEmoji = emoji.startsWith(':') && emoji.endsWith(':');
|
||||
const emojiMuteKey = isCustomEmoji ?
|
||||
makeEmojiMuteKey({ name: extractCustomEmojiName(emoji), host: extractCustomEmojiHost(emoji) }) :
|
||||
emoji;
|
||||
const mutedEmojis = prefer.r.mutingEmojis.value;
|
||||
if (!mutedEmojis.includes(emojiMuteKey)) {
|
||||
return prefer.commit('mutingEmojis', [...mutedEmojis, emojiMuteKey]);
|
||||
}
|
||||
throw new Error('Emoji is already muted', { cause: `${emojiMuteKey} is Already Muted` });
|
||||
}
|
||||
|
||||
export function unmute(emoji:string) {
|
||||
const isCustomEmoji = emoji.startsWith(':') && emoji.endsWith(':');
|
||||
const emojiMuteKey = isCustomEmoji ?
|
||||
makeEmojiMuteKey({ name: extractCustomEmojiName(emoji), host: extractCustomEmojiHost(emoji) }) :
|
||||
emoji;
|
||||
const mutedEmojis = prefer.r.mutingEmojis.value;
|
||||
console.log('unmute', emoji, emojiMuteKey);
|
||||
console.log('mutedEmojis', mutedEmojis);
|
||||
prefer.commit('mutingEmojis', mutedEmojis.filter((e) => e !== emojiMuteKey));
|
||||
}
|
||||
|
||||
export function checkMuted(emoji: string) {
|
||||
const isCustomEmoji = emoji.startsWith(':') && emoji.endsWith(':');
|
||||
const emojiMuteKey = isCustomEmoji ?
|
||||
makeEmojiMuteKey({ name: extractCustomEmojiName(emoji), host: extractCustomEmojiHost(emoji) }) :
|
||||
emoji;
|
||||
return computed(() => prefer.r.mutingEmojis.value.includes(emojiMuteKey));
|
||||
}
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
import { vi } from 'vitest';
|
||||
import createFetchMock from 'vitest-fetch-mock';
|
||||
import type { Ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const fetchMocker = createFetchMock(vi);
|
||||
fetchMocker.enableMocks();
|
||||
@@ -29,24 +27,13 @@ export const preferState: Record<string, unknown> = {
|
||||
code: false,
|
||||
},
|
||||
|
||||
mutingEmojis: [],
|
||||
};
|
||||
|
||||
export let preferReactive: Record<string, Ref<unknown>> = {};
|
||||
|
||||
for (const key in preferState) {
|
||||
if (preferState[key] !== undefined) {
|
||||
preferReactive[key] = ref(preferState[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: store somehow becomes undefined in vitest?
|
||||
vi.mock('@/preferences.js', () => {
|
||||
|
||||
return {
|
||||
prefer: {
|
||||
s: preferState,
|
||||
r: preferReactive,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "misskey-js",
|
||||
"version": "2025.5.1-alpha.2",
|
||||
"version": "2025.5.1-alpha.1",
|
||||
"description": "Misskey SDK for JavaScript",
|
||||
"license": "MIT",
|
||||
"main": "./built/index.js",
|
||||
|
||||
@@ -8785,9 +8785,6 @@ export type operations = {
|
||||
singleUserMode: boolean;
|
||||
/** @enum {string} */
|
||||
ugcVisibilityForVisitor: 'all' | 'local' | 'none';
|
||||
proxyRemoteFiles: boolean;
|
||||
signToActivityPubGet: boolean;
|
||||
allowExternalApRedirect: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -11461,9 +11458,6 @@ export type operations = {
|
||||
singleUserMode?: boolean;
|
||||
/** @enum {string} */
|
||||
ugcVisibilityForVisitor?: 'all' | 'local' | 'none';
|
||||
proxyRemoteFiles?: boolean;
|
||||
signToActivityPubGet?: boolean;
|
||||
allowExternalApRedirect?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user