Merge branch 'develop' into fix-signout
This commit is contained in:
commit
40ea7ec8c9
|
|
@ -1,4 +1,4 @@
|
||||||
## Unreleased
|
## 2026.1.0
|
||||||
|
|
||||||
### Note
|
### Note
|
||||||
- `users/following` の `birthday` プロパティは非推奨になりました。代わりに `users/get-following-birthday-users` をご利用ください。
|
- `users/following` の `birthday` プロパティは非推奨になりました。代わりに `users/get-following-birthday-users` をご利用ください。
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
- Enhance: 「もうすぐ誕生日のユーザー」ウィジェットで、誕生日が至近のユーザーも表示できるように
|
- Enhance: 「もうすぐ誕生日のユーザー」ウィジェットで、誕生日が至近のユーザーも表示できるように
|
||||||
(Cherry-picked from https://github.com/MisskeyIO/misskey)
|
(Cherry-picked from https://github.com/MisskeyIO/misskey)
|
||||||
- 「今日誕生日のユーザー」は「もうすぐ誕生日のユーザー」に名称変更されました
|
- 「今日誕生日のユーザー」は「もうすぐ誕生日のユーザー」に名称変更されました
|
||||||
|
- 依存関係の更新
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Enhance: ドライブのファイル一覧で自動でもっと見るを利用可能に
|
- Enhance: ドライブのファイル一覧で自動でもっと見るを利用可能に
|
||||||
|
|
@ -14,9 +15,12 @@
|
||||||
- Enhance: ウィジェットの設定項目のラベルの多言語対応
|
- Enhance: ウィジェットの設定項目のラベルの多言語対応
|
||||||
- Enhance: アカウント管理ページで、全てのアカウントから一括でログアウトできるように
|
- Enhance: アカウント管理ページで、全てのアカウントから一括でログアウトできるように
|
||||||
- Fix: ドライブクリーナーでファイルを削除しても画面に反映されない問題を修正 #16061
|
- Fix: ドライブクリーナーでファイルを削除しても画面に反映されない問題を修正 #16061
|
||||||
|
- Fix: 非ログイン時にログインを求めるダイアログが表示された後にダイアログのぼかしが解除されず操作不能になることがある問題を修正
|
||||||
- Fix: ドライブのソートが「登録日(昇順)」の場合に正しく動作しない問題を修正
|
- Fix: ドライブのソートが「登録日(昇順)」の場合に正しく動作しない問題を修正
|
||||||
- Fix: ログアウトボタンを押下するとすべてのアカウントからログアウトする問題を修正
|
- Fix: ログアウトボタンを押下するとすべてのアカウントからログアウトする問題を修正
|
||||||
- Fix: アカウント管理ページで、アカウントの追加・削除を行ってもリストに反映されない問題を修正
|
- Fix: アカウント管理ページで、アカウントの追加・削除を行ってもリストに反映されない問題を修正
|
||||||
|
- Fix: 管理画面でアーカイブ済のお知らせを表示した際にアクティブなお知らせが多い旨の警告が出る問題を修正
|
||||||
|
- Fix: ファイルタブのセンシティブメディアを開く際に確認ダイアログを出す設定が適用されない問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Enhance: OAuthのクライアント情報取得(Client Information Discovery)において、IndieWeb Living Standard 11 July 2024で定義されているJSONドキュメント形式に対応しました
|
- Enhance: OAuthのクライアント情報取得(Client Information Discovery)において、IndieWeb Living Standard 11 July 2024で定義されているJSONドキュメント形式に対応しました
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.12.2",
|
"version": "2026.1.0-alpha.0",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
||||||
|
|
@ -41,20 +41,20 @@
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@swc/core-android-arm64": "1.3.11",
|
"@swc/core-android-arm64": "1.3.11",
|
||||||
"@swc/core-darwin-arm64": "1.15.3",
|
"@swc/core-darwin-arm64": "1.15.7",
|
||||||
"@swc/core-darwin-x64": "1.15.3",
|
"@swc/core-darwin-x64": "1.15.7",
|
||||||
"@swc/core-freebsd-x64": "1.3.11",
|
"@swc/core-freebsd-x64": "1.3.11",
|
||||||
"@swc/core-linux-arm-gnueabihf": "1.15.3",
|
"@swc/core-linux-arm-gnueabihf": "1.15.7",
|
||||||
"@swc/core-linux-arm64-gnu": "1.15.3",
|
"@swc/core-linux-arm64-gnu": "1.15.7",
|
||||||
"@swc/core-linux-arm64-musl": "1.15.3",
|
"@swc/core-linux-arm64-musl": "1.15.7",
|
||||||
"@swc/core-linux-x64-gnu": "1.15.3",
|
"@swc/core-linux-x64-gnu": "1.15.7",
|
||||||
"@swc/core-linux-x64-musl": "1.15.3",
|
"@swc/core-linux-x64-musl": "1.15.7",
|
||||||
"@swc/core-win32-arm64-msvc": "1.15.3",
|
"@swc/core-win32-arm64-msvc": "1.15.7",
|
||||||
"@swc/core-win32-ia32-msvc": "1.15.3",
|
"@swc/core-win32-ia32-msvc": "1.15.7",
|
||||||
"@swc/core-win32-x64-msvc": "1.15.3",
|
"@swc/core-win32-x64-msvc": "1.15.7",
|
||||||
"@tensorflow/tfjs": "4.22.0",
|
"@tensorflow/tfjs": "4.22.0",
|
||||||
"@tensorflow/tfjs-node": "4.22.0",
|
"@tensorflow/tfjs-node": "4.22.0",
|
||||||
"bufferutil": "4.0.9",
|
"bufferutil": "4.1.0",
|
||||||
"slacc-android-arm-eabi": "0.0.10",
|
"slacc-android-arm-eabi": "0.0.10",
|
||||||
"slacc-android-arm64": "0.0.10",
|
"slacc-android-arm64": "0.0.10",
|
||||||
"slacc-darwin-arm64": "0.0.10",
|
"slacc-darwin-arm64": "0.0.10",
|
||||||
|
|
@ -68,11 +68,11 @@
|
||||||
"slacc-linux-x64-musl": "0.0.10",
|
"slacc-linux-x64-musl": "0.0.10",
|
||||||
"slacc-win32-arm64-msvc": "0.0.10",
|
"slacc-win32-arm64-msvc": "0.0.10",
|
||||||
"slacc-win32-x64-msvc": "0.0.10",
|
"slacc-win32-x64-msvc": "0.0.10",
|
||||||
"utf-8-validate": "6.0.5"
|
"utf-8-validate": "6.0.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.948.0",
|
"@aws-sdk/client-s3": "3.958.0",
|
||||||
"@aws-sdk/lib-storage": "3.948.0",
|
"@aws-sdk/lib-storage": "3.958.0",
|
||||||
"@discordapp/twemoji": "16.0.1",
|
"@discordapp/twemoji": "16.0.1",
|
||||||
"@fastify/accepts": "5.0.4",
|
"@fastify/accepts": "5.0.4",
|
||||||
"@fastify/cors": "11.2.0",
|
"@fastify/cors": "11.2.0",
|
||||||
|
|
@ -83,18 +83,18 @@
|
||||||
"@kitajs/html": "4.2.11",
|
"@kitajs/html": "4.2.11",
|
||||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||||
"@misskey-dev/summaly": "5.2.5",
|
"@misskey-dev/summaly": "5.2.5",
|
||||||
"@napi-rs/canvas": "0.1.84",
|
"@napi-rs/canvas": "0.1.87",
|
||||||
"@nestjs/common": "11.1.9",
|
"@nestjs/common": "11.1.10",
|
||||||
"@nestjs/core": "11.1.9",
|
"@nestjs/core": "11.1.10",
|
||||||
"@nestjs/testing": "11.1.9",
|
"@nestjs/testing": "11.1.10",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@sentry/node": "10.29.0",
|
"@sentry/node": "10.32.1",
|
||||||
"@sentry/profiling-node": "10.29.0",
|
"@sentry/profiling-node": "10.32.1",
|
||||||
"@simplewebauthn/server": "13.2.2",
|
"@simplewebauthn/server": "13.2.2",
|
||||||
"@sinonjs/fake-timers": "15.0.0",
|
"@sinonjs/fake-timers": "15.1.0",
|
||||||
"@smithy/node-http-handler": "4.4.5",
|
"@smithy/node-http-handler": "4.4.7",
|
||||||
"@swc/cli": "0.7.9",
|
"@swc/cli": "0.7.9",
|
||||||
"@swc/core": "1.15.3",
|
"@swc/core": "1.15.7",
|
||||||
"@twemoji/parser": "16.0.0",
|
"@twemoji/parser": "16.0.0",
|
||||||
"@types/redis-info": "3.0.3",
|
"@types/redis-info": "3.0.3",
|
||||||
"accepts": "1.3.8",
|
"accepts": "1.3.8",
|
||||||
|
|
@ -104,7 +104,7 @@
|
||||||
"bcryptjs": "3.0.3",
|
"bcryptjs": "3.0.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"body-parser": "2.2.1",
|
"body-parser": "2.2.1",
|
||||||
"bullmq": "5.65.1",
|
"bullmq": "5.66.3",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"chalk": "5.6.2",
|
"chalk": "5.6.2",
|
||||||
"chalk-template": "1.1.2",
|
"chalk-template": "1.1.2",
|
||||||
|
|
@ -116,7 +116,7 @@
|
||||||
"fastify": "5.6.2",
|
"fastify": "5.6.2",
|
||||||
"fastify-raw-body": "5.0.0",
|
"fastify-raw-body": "5.0.0",
|
||||||
"feed": "5.1.0",
|
"feed": "5.1.0",
|
||||||
"file-type": "21.1.1",
|
"file-type": "21.2.0",
|
||||||
"fluent-ffmpeg": "2.1.3",
|
"fluent-ffmpeg": "2.1.3",
|
||||||
"form-data": "4.0.5",
|
"form-data": "4.0.5",
|
||||||
"got": "14.6.5",
|
"got": "14.6.5",
|
||||||
|
|
@ -140,7 +140,7 @@
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"node-html-parser": "7.0.1",
|
"node-html-parser": "7.0.1",
|
||||||
"nodemailer": "7.0.11",
|
"nodemailer": "7.0.12",
|
||||||
"nsfwjs": "4.2.0",
|
"nsfwjs": "4.2.0",
|
||||||
"oauth2orize": "1.12.0",
|
"oauth2orize": "1.12.0",
|
||||||
"oauth2orize-pkce": "0.1.2",
|
"oauth2orize-pkce": "0.1.2",
|
||||||
|
|
@ -153,7 +153,7 @@
|
||||||
"qrcode": "1.5.4",
|
"qrcode": "1.5.4",
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"ratelimiter": "3.4.1",
|
"ratelimiter": "3.4.1",
|
||||||
"re2": "1.22.3",
|
"re2": "1.23.0",
|
||||||
"redis-info": "3.1.0",
|
"redis-info": "3.1.0",
|
||||||
"reflect-metadata": "0.2.2",
|
"reflect-metadata": "0.2.2",
|
||||||
"rename": "1.0.4",
|
"rename": "1.0.4",
|
||||||
|
|
@ -166,7 +166,7 @@
|
||||||
"slacc": "0.0.10",
|
"slacc": "0.0.10",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"systeminformation": "5.27.14",
|
"systeminformation": "5.28.1",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tmp": "0.2.5",
|
"tmp": "0.2.5",
|
||||||
"tsc-alias": "1.8.16",
|
"tsc-alias": "1.8.16",
|
||||||
|
|
@ -180,8 +180,8 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "29.7.0",
|
"@jest/globals": "29.7.0",
|
||||||
"@kitajs/ts-html-plugin": "4.1.3",
|
"@kitajs/ts-html-plugin": "4.1.3",
|
||||||
"@nestjs/platform-express": "11.1.9",
|
"@nestjs/platform-express": "11.1.10",
|
||||||
"@sentry/vue": "10.29.0",
|
"@sentry/vue": "10.32.1",
|
||||||
"@simplewebauthn/types": "12.0.0",
|
"@simplewebauthn/types": "12.0.0",
|
||||||
"@swc/jest": "0.2.39",
|
"@swc/jest": "0.2.39",
|
||||||
"@types/accepts": "1.3.7",
|
"@types/accepts": "1.3.7",
|
||||||
|
|
@ -195,11 +195,11 @@
|
||||||
"@types/jsonld": "1.5.15",
|
"@types/jsonld": "1.5.15",
|
||||||
"@types/mime-types": "3.0.1",
|
"@types/mime-types": "3.0.1",
|
||||||
"@types/ms": "2.1.0",
|
"@types/ms": "2.1.0",
|
||||||
"@types/node": "24.10.2",
|
"@types/node": "24.10.4",
|
||||||
"@types/nodemailer": "7.0.4",
|
"@types/nodemailer": "7.0.4",
|
||||||
"@types/oauth2orize": "1.11.5",
|
"@types/oauth2orize": "1.11.5",
|
||||||
"@types/oauth2orize-pkce": "0.1.2",
|
"@types/oauth2orize-pkce": "0.1.2",
|
||||||
"@types/pg": "8.15.6",
|
"@types/pg": "8.16.0",
|
||||||
"@types/qrcode": "1.5.6",
|
"@types/qrcode": "1.5.6",
|
||||||
"@types/random-seed": "0.3.5",
|
"@types/random-seed": "0.3.5",
|
||||||
"@types/ratelimiter": "3.4.6",
|
"@types/ratelimiter": "3.4.6",
|
||||||
|
|
@ -214,8 +214,8 @@
|
||||||
"@types/vary": "1.1.3",
|
"@types/vary": "1.1.3",
|
||||||
"@types/web-push": "3.6.4",
|
"@types/web-push": "3.6.4",
|
||||||
"@types/ws": "8.18.1",
|
"@types/ws": "8.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.49.0",
|
"@typescript-eslint/eslint-plugin": "8.50.1",
|
||||||
"@typescript-eslint/parser": "8.49.0",
|
"@typescript-eslint/parser": "8.50.1",
|
||||||
"aws-sdk-client-mock": "4.1.0",
|
"aws-sdk-client-mock": "4.1.0",
|
||||||
"cbor": "10.0.11",
|
"cbor": "10.0.11",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
|
|
@ -229,6 +229,6 @@
|
||||||
"pid-port": "2.0.0",
|
"pid-port": "2.0.0",
|
||||||
"simple-oauth2": "5.1.0",
|
"simple-oauth2": "5.1.0",
|
||||||
"supertest": "7.1.4",
|
"supertest": "7.1.4",
|
||||||
"vite": "7.2.7"
|
"vite": "7.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,13 @@ function onFollowChange(user: Misskey.entities.UserDetailed) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onClick() {
|
async function onClick() {
|
||||||
pleaseLogin({ openOnRemote: { type: 'web', path: `/@${props.user.username}@${props.user.host ?? host}` } });
|
const isLoggedIn = await pleaseLogin({
|
||||||
|
openOnRemote: {
|
||||||
|
type: 'web',
|
||||||
|
path: `/@${props.user.username}@${props.user.host ?? host}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
wait.value = true;
|
wait.value = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ import { hms } from '@/filters/hms.js';
|
||||||
import MkMediaRange from '@/components/MkMediaRange.vue';
|
import MkMediaRange from '@/components/MkMediaRange.vue';
|
||||||
import { $i, iAmModerator } from '@/i.js';
|
import { $i, iAmModerator } from '@/i.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
import { canRevealFile, shouldHideFileByDefault } from '@/utility/sensitive-file.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
audio: Misskey.entities.DriveFile;
|
audio: Misskey.entities.DriveFile;
|
||||||
|
|
@ -154,16 +155,11 @@ function hasFocus() {
|
||||||
const playerEl = useTemplateRef('playerEl');
|
const playerEl = useTemplateRef('playerEl');
|
||||||
const audioEl = useTemplateRef('audioEl');
|
const audioEl = useTemplateRef('audioEl');
|
||||||
|
|
||||||
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
const hide = ref(shouldHideFileByDefault(props.audio));
|
||||||
const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.audio.isSensitive && prefer.s.nsfw !== 'ignore'));
|
|
||||||
|
|
||||||
async function reveal() {
|
async function reveal() {
|
||||||
if (props.audio.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
if (!(await canRevealFile(props.audio))) {
|
||||||
const { canceled } = await os.confirm({
|
return;
|
||||||
type: 'question',
|
|
||||||
text: i18n.ts.sensitiveMediaRevealConfirm,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hide.value = false;
|
hide.value = false;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<MkMediaAudio v-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media"/>
|
<MkMediaAudio v-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media"/>
|
||||||
<div v-else-if="media.isSensitive && hide" :class="$style.sensitive" @click="reveal">
|
<div v-else-if="hide" :class="$style.sensitive" @click="reveal">
|
||||||
<span style="font-size: 1.6em;"><i class="ti ti-alert-triangle"></i></span>
|
<span style="font-size: 1.6em;"><i class="ti ti-alert-triangle"></i></span>
|
||||||
<b>{{ i18n.ts.sensitive }}</b>
|
<b>{{ i18n.ts.sensitive }}</b>
|
||||||
<span>{{ i18n.ts.clickToShow }}</span>
|
<span>{{ i18n.ts.clickToShow }}</span>
|
||||||
|
|
@ -27,23 +27,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import * as os from '@/os.js';
|
|
||||||
import MkMediaAudio from '@/components/MkMediaAudio.vue';
|
import MkMediaAudio from '@/components/MkMediaAudio.vue';
|
||||||
import { prefer } from '@/preferences.js';
|
import { shouldHideFileByDefault, canRevealFile } from '@/utility/sensitive-file.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
media: Misskey.entities.DriveFile;
|
media: Misskey.entities.DriveFile;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const hide = ref(true);
|
const hide = ref(shouldHideFileByDefault(props.media));
|
||||||
|
|
||||||
async function reveal() {
|
async function reveal() {
|
||||||
if (props.media.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
if (!(await canRevealFile(props.media))) {
|
||||||
const { canceled } = await os.confirm({
|
return;
|
||||||
type: 'question',
|
|
||||||
text: i18n.ts.sensitiveMediaRevealConfirm,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hide.value = false;
|
hide.value = false;
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { $i, iAmModerator } from '@/i.js';
|
import { $i, iAmModerator } from '@/i.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
import { shouldHideFileByDefault, canRevealFile } from '@/utility/sensitive-file.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
image: Misskey.entities.DriveFile;
|
image: Misskey.entities.DriveFile;
|
||||||
|
|
@ -106,12 +107,8 @@ async function reveal(ev: MouseEvent) {
|
||||||
|
|
||||||
if (hide.value) {
|
if (hide.value) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
if (props.image.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
if (!(await canRevealFile(props.image))) {
|
||||||
const { canceled } = await os.confirm({
|
return;
|
||||||
type: 'question',
|
|
||||||
text: i18n.ts.sensitiveMediaRevealConfirm,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hide.value = false;
|
hide.value = false;
|
||||||
|
|
@ -119,8 +116,8 @@ async function reveal(ev: MouseEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする
|
// Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする
|
||||||
watch(() => props.image, () => {
|
watch(() => props.image, (newImage) => {
|
||||||
hide.value = (prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.image.isSensitive && prefer.s.nsfw !== 'ignore');
|
hide.value = shouldHideFileByDefault(newImage);
|
||||||
}, {
|
}, {
|
||||||
deep: true,
|
deep: true,
|
||||||
immediate: true,
|
immediate: true,
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ import hasAudio from '@/utility/media-has-audio.js';
|
||||||
import MkMediaRange from '@/components/MkMediaRange.vue';
|
import MkMediaRange from '@/components/MkMediaRange.vue';
|
||||||
import { $i, iAmModerator } from '@/i.js';
|
import { $i, iAmModerator } from '@/i.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
import { shouldHideFileByDefault, canRevealFile } from '@/utility/sensitive-file.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
video: Misskey.entities.DriveFile;
|
video: Misskey.entities.DriveFile;
|
||||||
|
|
@ -176,15 +177,11 @@ function hasFocus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
||||||
const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.video.isSensitive && prefer.s.nsfw !== 'ignore'));
|
const hide = ref(shouldHideFileByDefault(props.video));
|
||||||
|
|
||||||
async function reveal() {
|
async function reveal() {
|
||||||
if (props.video.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
if (!(await canRevealFile(props.video))) {
|
||||||
const { canceled } = await os.confirm({
|
return;
|
||||||
type: 'question',
|
|
||||||
text: i18n.ts.sensitiveMediaRevealConfirm,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hide.value = false;
|
hide.value = false;
|
||||||
|
|
|
||||||
|
|
@ -468,8 +468,12 @@ if (!props.mock) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renote() {
|
async function renote() {
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
if (props.mock) return;
|
||||||
|
|
||||||
|
const isLoggedIn = await pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
|
|
||||||
const { menu } = getRenoteMenu({ note: note, renoteButton, mock: props.mock });
|
const { menu } = getRenoteMenu({ note: note, renoteButton, mock: props.mock });
|
||||||
|
|
@ -478,11 +482,12 @@ function renote() {
|
||||||
subscribeManuallyToNoteCapture();
|
subscribeManuallyToNoteCapture();
|
||||||
}
|
}
|
||||||
|
|
||||||
function reply(): void {
|
async function reply() {
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
if (props.mock) return;
|
||||||
if (props.mock) {
|
|
||||||
return;
|
const isLoggedIn = await pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
}
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
os.post({
|
os.post({
|
||||||
reply: appearNote,
|
reply: appearNote,
|
||||||
channel: appearNote.channel,
|
channel: appearNote.channel,
|
||||||
|
|
@ -491,8 +496,10 @@ function reply(): void {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function react(): void {
|
async function react() {
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
const isLoggedIn = await pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
if (appearNote.reactionAcceptance === 'likeOnly') {
|
if (appearNote.reactionAcceptance === 'likeOnly') {
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
|
|
@ -621,10 +628,12 @@ async function clip(): Promise<void> {
|
||||||
os.popupMenu(await getNoteClipMenu({ note: note, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
os.popupMenu(await getNoteClipMenu({ note: note, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showRenoteMenu(): void {
|
async function showRenoteMenu() {
|
||||||
if (props.mock) {
|
if (props.mock) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const isLoggedIn = await pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
function getUnrenote(): MenuItem {
|
function getUnrenote(): MenuItem {
|
||||||
return {
|
return {
|
||||||
|
|
@ -649,7 +658,6 @@ function showRenoteMenu(): void {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isMyRenote) {
|
if (isMyRenote) {
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
|
||||||
os.popupMenu([
|
os.popupMenu([
|
||||||
renoteDetailsMenu,
|
renoteDetailsMenu,
|
||||||
getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote),
|
getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote),
|
||||||
|
|
|
||||||
|
|
@ -448,8 +448,10 @@ if (appearNote.reactionAcceptance === 'likeOnly') {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renote() {
|
async function renote() {
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
const isLoggedIn = await pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
|
|
||||||
const { menu } = getRenoteMenu({ note: note, renoteButton });
|
const { menu } = getRenoteMenu({ note: note, renoteButton });
|
||||||
|
|
@ -459,8 +461,10 @@ function renote() {
|
||||||
subscribeManuallyToNoteCapture();
|
subscribeManuallyToNoteCapture();
|
||||||
}
|
}
|
||||||
|
|
||||||
function reply(): void {
|
async function reply() {
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
const isLoggedIn = await pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
os.post({
|
os.post({
|
||||||
reply: appearNote,
|
reply: appearNote,
|
||||||
|
|
@ -470,8 +474,10 @@ function reply(): void {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function react(): void {
|
async function react() {
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
const isLoggedIn = await pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
if (appearNote.reactionAcceptance === 'likeOnly') {
|
if (appearNote.reactionAcceptance === 'likeOnly') {
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
|
|
@ -569,9 +575,12 @@ async function clip(): Promise<void> {
|
||||||
os.popupMenu(await getNoteClipMenu({ note: note }), clipButton.value).then(focus);
|
os.popupMenu(await getNoteClipMenu({ note: note }), clipButton.value).then(focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showRenoteMenu(): void {
|
async function showRenoteMenu() {
|
||||||
if (!isMyRenote) return;
|
if (!isMyRenote) return;
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
|
||||||
|
const isLoggedIn = await pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
os.popupMenu([{
|
os.popupMenu([{
|
||||||
text: i18n.ts.unrenote,
|
text: i18n.ts.unrenote,
|
||||||
icon: 'ti ti-trash',
|
icon: 'ti ti-trash',
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<template v-for="file in note.files">
|
<template v-for="file in note.files">
|
||||||
<div
|
<div
|
||||||
v-if="(((
|
v-if="isHiding(file)"
|
||||||
(prefer.s.nsfw === 'force' || file.isSensitive) &&
|
|
||||||
prefer.s.nsfw !== 'ignore'
|
|
||||||
) || (prefer.s.dataSaver.media && file.type.startsWith('image/'))) &&
|
|
||||||
!showingFiles.has(file.id)
|
|
||||||
)"
|
|
||||||
:class="[$style.filePreview, { [$style.square]: square }]"
|
:class="[$style.filePreview, { [$style.square]: square }]"
|
||||||
@click="showingFiles.add(file.id)"
|
@click="reveal(file)"
|
||||||
>
|
>
|
||||||
<MkDriveFileThumbnail
|
<MkDriveFileThumbnail
|
||||||
:file="file"
|
:file="file"
|
||||||
|
|
@ -49,6 +44,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import { notePage } from '@/filters/note.js';
|
import { notePage } from '@/filters/note.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
import { shouldHideFileByDefault, canRevealFile } from '@/utility/sensitive-file.js';
|
||||||
import bytes from '@/filters/bytes.js';
|
import bytes from '@/filters/bytes.js';
|
||||||
|
|
||||||
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
|
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
|
||||||
|
|
@ -59,6 +55,24 @@ defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const showingFiles = ref<Set<string>>(new Set());
|
const showingFiles = ref<Set<string>>(new Set());
|
||||||
|
|
||||||
|
function isHiding(file: Misskey.entities.DriveFile) {
|
||||||
|
if (shouldHideFileByDefault(file) && !showingFiles.value.has(file.id)) {
|
||||||
|
if (!file.isSensitive && !file.type.startsWith('image/')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reveal(file: Misskey.entities.DriveFile) {
|
||||||
|
if (!(await canRevealFile(file))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showingFiles.value.add(file.id);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,8 @@ const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||||
const vote = async (id: number) => {
|
const vote = async (id: number) => {
|
||||||
if (props.readOnly || closed.value || isVoted.value) return;
|
if (props.readOnly || closed.value || isVoted.value) return;
|
||||||
|
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
const isLoggedIn = await pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
|
|
|
||||||
|
|
@ -329,8 +329,8 @@ const canSaveAsServerDraft = computed((): boolean => {
|
||||||
return canPost.value && (textLength.value > 0 || files.value.length > 0 || poll.value != null);
|
return canPost.value && (textLength.value > 0 || files.value.length > 0 || poll.value != null);
|
||||||
});
|
});
|
||||||
|
|
||||||
const withHashtags = computed(store.makeGetterSetter('postFormWithHashtags'));
|
const withHashtags = store.model('postFormWithHashtags');
|
||||||
const hashtags = computed(store.makeGetterSetter('postFormHashtags'));
|
const hashtags = store.model('postFormHashtags');
|
||||||
|
|
||||||
watch(text, () => {
|
watch(text, () => {
|
||||||
checkMissingMention();
|
checkMissingMention();
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
// TODO: Misskeyのドメイン知識があるのでutilityなどに移動する
|
// TODO: Misskeyのドメイン知識があるのでutilityなどに移動する
|
||||||
|
|
||||||
import { onUnmounted, ref, watch } from 'vue';
|
import { customRef, ref, watch, onScopeDispose } from 'vue';
|
||||||
import { BroadcastChannel } from 'broadcast-channel';
|
import { BroadcastChannel } from 'broadcast-channel';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
|
|
@ -235,44 +235,43 @@ export class Pizzax<T extends StateDef> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 特定のキーの、簡易的なgetter/setterを作ります
|
* 特定のキーの、簡易的なcomputed refを作ります
|
||||||
* 主にvue上で設定コントロールのmodelとして使う用
|
* 主にvue上で設定コントロールのmodelとして使う用
|
||||||
*/
|
*/
|
||||||
// TODO: 廃止
|
public model<K extends keyof T, R = T[K]['default']>(
|
||||||
public makeGetterSetter<K extends keyof T, R = T[K]['default']>(
|
key: K,
|
||||||
|
): Ref<R>;
|
||||||
|
public model<K extends keyof T, R extends Exclude<any, T[K]['default']>>(
|
||||||
|
key: K,
|
||||||
|
getter: (v: T[K]['default']) => R,
|
||||||
|
setter: (v: R) => T[K]['default'],
|
||||||
|
): Ref<R>;
|
||||||
|
|
||||||
|
public model<K extends keyof T, R>(
|
||||||
key: K,
|
key: K,
|
||||||
getter?: (v: T[K]['default']) => R,
|
getter?: (v: T[K]['default']) => R,
|
||||||
setter?: (v: R) => T[K]['default'],
|
setter?: (v: R) => T[K]['default'],
|
||||||
): {
|
): Ref<R> {
|
||||||
get: () => R;
|
return customRef<R>((track, trigger) => {
|
||||||
set: (value: R) => void;
|
const watchStop = watch(this.r[key], () => {
|
||||||
} {
|
trigger();
|
||||||
const valueRef = ref(this.s[key]);
|
});
|
||||||
|
|
||||||
const stop = watch(this.r[key], val => {
|
onScopeDispose(() => {
|
||||||
valueRef.value = val;
|
watchStop();
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
return {
|
||||||
|
get: () => {
|
||||||
|
track();
|
||||||
|
return (getter != null ? getter(this.s[key]) : this.s[key]) as R;
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
const val = setter != null ? setter(value) : value;
|
||||||
|
this.set(key, val as T[K]['default']);
|
||||||
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE: vueコンポーネント内で呼ばれない限りは、onUnmounted は無意味なのでメモリリークする
|
|
||||||
onUnmounted(() => {
|
|
||||||
stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: VueのcustomRef使うと良い感じになるかも
|
|
||||||
return {
|
|
||||||
get: () => {
|
|
||||||
if (getter) {
|
|
||||||
return getter(valueRef.value);
|
|
||||||
} else {
|
|
||||||
return valueRef.value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set: (value) => {
|
|
||||||
const val = setter ? setter(value) : value;
|
|
||||||
this.set(key, val);
|
|
||||||
valueRef.value = val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// localStorage => indexedDBのマイグレーション
|
// localStorage => indexedDBのマイグレーション
|
||||||
|
|
|
||||||
|
|
@ -709,8 +709,8 @@ export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise<void> {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function post(props: PostFormProps = {}): Promise<void> {
|
export async function post(props: PostFormProps = {}): Promise<void> {
|
||||||
pleaseLogin({
|
const isLoggedIn = await pleaseLogin({
|
||||||
openOnRemote: (props.initialText || props.initialNote ? {
|
openOnRemote: (props.initialText || props.initialNote ? {
|
||||||
type: 'share',
|
type: 'share',
|
||||||
params: {
|
params: {
|
||||||
|
|
@ -720,6 +720,7 @@ export function post(props: PostFormProps = {}): Promise<void> {
|
||||||
},
|
},
|
||||||
} : undefined),
|
} : undefined),
|
||||||
});
|
});
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 900px;">
|
<div class="_spacer" style="--MI_SPACER-w: 900px;">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkInfo>{{ i18n.ts._announcement.shouldNotBeUsedToPresentPermanentInfo }}</MkInfo>
|
<MkInfo>{{ i18n.ts._announcement.shouldNotBeUsedToPresentPermanentInfo }}</MkInfo>
|
||||||
<MkInfo v-if="announcements.length > 5" warn>{{ i18n.ts._announcement.tooManyActiveAnnouncementDescription }}</MkInfo>
|
<MkInfo v-if="announcementsStatus === 'active' && announcements.length > 5" warn>{{ i18n.ts._announcement.tooManyActiveAnnouncementDescription }}</MkInfo>
|
||||||
|
|
||||||
<MkSelect v-model="announcementsStatus" :items="announcementsStatusDef">
|
<MkSelect v-model="announcementsStatus" :items="announcementsStatusDef">
|
||||||
<template #label>{{ i18n.ts.filter }}</template>
|
<template #label>{{ i18n.ts.filter }}</template>
|
||||||
|
|
|
||||||
|
|
@ -151,9 +151,11 @@ function shareWithNote() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function like() {
|
async function like() {
|
||||||
if (!flash.value) return;
|
if (!flash.value) return;
|
||||||
pleaseLogin();
|
|
||||||
|
const isLoggedIn = await pleaseLogin();
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
os.apiWithDialog('flash/like', {
|
os.apiWithDialog('flash/like', {
|
||||||
flashId: flash.value.id,
|
flashId: flash.value.id,
|
||||||
|
|
@ -165,7 +167,9 @@ function like() {
|
||||||
|
|
||||||
async function unlike() {
|
async function unlike() {
|
||||||
if (!flash.value) return;
|
if (!flash.value) return;
|
||||||
pleaseLogin();
|
|
||||||
|
const isLoggedIn = await pleaseLogin();
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,8 @@ async function matchHeatbeat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function matchUser() {
|
async function matchUser() {
|
||||||
pleaseLogin();
|
const isLoggedIn = await pleaseLogin();
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
const user = await os.selectUser({ includeSelf: false, localOnly: true });
|
const user = await os.selectUser({ includeSelf: false, localOnly: true });
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
|
|
@ -207,8 +208,9 @@ async function matchUser() {
|
||||||
matchHeatbeat();
|
matchHeatbeat();
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchAny(ev: MouseEvent) {
|
async function matchAny(ev: MouseEvent) {
|
||||||
pleaseLogin();
|
const isLoggedIn = await pleaseLogin();
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
|
||||||
os.popupMenu([{
|
os.popupMenu([{
|
||||||
text: i18n.ts._reversi.allowIrregularRules,
|
text: i18n.ts._reversi.allowIrregularRules,
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ const items = ref(prefer.s.menu.map(x => ({
|
||||||
})));
|
})));
|
||||||
const itemTypeValues = computed(() => items.value.map(x => x.type));
|
const itemTypeValues = computed(() => items.value.map(x => x.type));
|
||||||
|
|
||||||
const menuDisplay = computed(store.makeGetterSetter('menuDisplay'));
|
const menuDisplay = store.model('menuDisplay');
|
||||||
const showNavbarSubButtons = prefer.model('showNavbarSubButtons');
|
const showNavbarSubButtons = prefer.model('showNavbarSubButtons');
|
||||||
|
|
||||||
async function addItem() {
|
async function addItem() {
|
||||||
|
|
|
||||||
|
|
@ -855,7 +855,7 @@ const $i = ensureSignin();
|
||||||
|
|
||||||
const lang = ref(miLocalStorage.getItem('lang'));
|
const lang = ref(miLocalStorage.getItem('lang'));
|
||||||
const dataSaver = ref(prefer.s.dataSaver);
|
const dataSaver = ref(prefer.s.dataSaver);
|
||||||
const realtimeMode = computed(store.makeGetterSetter('realtimeMode'));
|
const realtimeMode = store.model('realtimeMode');
|
||||||
|
|
||||||
const overridedDeviceKind = prefer.model('overridedDeviceKind');
|
const overridedDeviceKind = prefer.model('overridedDeviceKind');
|
||||||
const pollingInterval = prefer.model('pollingInterval');
|
const pollingInterval = prefer.model('pollingInterval');
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ const $i = ensureSignin();
|
||||||
|
|
||||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
||||||
|
|
||||||
const reactionAcceptance = computed(store.makeGetterSetter('reactionAcceptance'));
|
const reactionAcceptance = store.model('reactionAcceptance');
|
||||||
|
|
||||||
function assertVaildLang(lang: string | null): lang is keyof typeof langmap {
|
function assertVaildLang(lang: string | null): lang is keyof typeof langmap {
|
||||||
return lang != null && lang in langmap;
|
return lang != null && lang in langmap;
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { computed, onUnmounted, ref, watch } from 'vue';
|
import { customRef, ref, watch, onScopeDispose } from 'vue';
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { host, version } from '@@/js/config.js';
|
import { host, version } from '@@/js/config.js';
|
||||||
import { PREF_DEF } from './def.js';
|
import { PREF_DEF } from './def.js';
|
||||||
import type { Ref, WritableComputedRef } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import { genId } from '@/utility/id.js';
|
import { genId } from '@/utility/id.js';
|
||||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||||
|
|
@ -301,36 +301,39 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
|
||||||
* 特定のキーの、簡易的なcomputed refを作ります
|
* 特定のキーの、簡易的なcomputed refを作ります
|
||||||
* 主にvue上で設定コントロールのmodelとして使う用
|
* 主にvue上で設定コントロールのmodelとして使う用
|
||||||
*/
|
*/
|
||||||
public model<K extends keyof PREF, V extends ValueOf<K> = ValueOf<K>>(
|
public model<K extends keyof PREF, V = ValueOf<K>>(
|
||||||
|
key: K,
|
||||||
|
): Ref<V>;
|
||||||
|
public model<K extends keyof PREF, V extends Exclude<any, ValueOf<K>>>(
|
||||||
|
key: K,
|
||||||
|
getter: (v: ValueOf<K>) => V,
|
||||||
|
setter: (v: V) => ValueOf<K>,
|
||||||
|
): Ref<V>;
|
||||||
|
|
||||||
|
public model<K extends keyof PREF, V>(
|
||||||
key: K,
|
key: K,
|
||||||
getter?: (v: ValueOf<K>) => V,
|
getter?: (v: ValueOf<K>) => V,
|
||||||
setter?: (v: V) => ValueOf<K>,
|
setter?: (v: V) => ValueOf<K>,
|
||||||
): WritableComputedRef<V> {
|
): Ref<V> {
|
||||||
const valueRef = ref(this.s[key]);
|
return customRef<V>((track, trigger) => {
|
||||||
|
const watchStop = watch(this.r[key], () => {
|
||||||
|
trigger();
|
||||||
|
});
|
||||||
|
|
||||||
const stop = watch(this.r[key], val => {
|
onScopeDispose(() => {
|
||||||
valueRef.value = val;
|
watchStop();
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
// NOTE: vueコンポーネント内で呼ばれない限りは、onUnmounted は無意味なのでメモリリークする
|
return {
|
||||||
onUnmounted(() => {
|
get: () => {
|
||||||
stop();
|
track();
|
||||||
});
|
return (getter != null ? getter(this.s[key]) : this.s[key]) as V;
|
||||||
|
},
|
||||||
// TODO: VueのcustomRef使うと良い感じになるかも
|
set: (value) => {
|
||||||
return computed({
|
const val = setter != null ? setter(value) : value;
|
||||||
get: () => {
|
this.commit(key, val as ValueOf<K>);
|
||||||
if (getter) {
|
},
|
||||||
return getter(valueRef.value);
|
};
|
||||||
} else {
|
|
||||||
return valueRef.value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set: (value) => {
|
|
||||||
const val = setter ? setter(value) : value;
|
|
||||||
this.commit(key, val);
|
|
||||||
valueRef.value = val;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
const settingsWindowed = ref(window.innerWidth > WINDOW_THRESHOLD);
|
const settingsWindowed = ref(window.innerWidth > WINDOW_THRESHOLD);
|
||||||
const menu = ref(prefer.s.menu);
|
const menu = ref(prefer.s.menu);
|
||||||
// const menuDisplay = computed(store.makeGetterSetter('menuDisplay'));
|
// const menuDisplay = store.model('menuDisplay');
|
||||||
const otherNavItemIndicated = computed<boolean>(() => {
|
const otherNavItemIndicated = computed<boolean>(() => {
|
||||||
for (const def in navbarItemDef) {
|
for (const def in navbarItemDef) {
|
||||||
if (menu.value.includes(def)) continue;
|
if (menu.value.includes(def)) continue;
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@ export async function pleaseLogin(opts: {
|
||||||
path?: string;
|
path?: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
openOnRemote?: OpenOnRemoteOptions;
|
openOnRemote?: OpenOnRemoteOptions;
|
||||||
} = {}) {
|
} = {}): Promise<boolean> {
|
||||||
if ($i) return;
|
if ($i != null) return true;
|
||||||
|
|
||||||
let _openOnRemote: OpenOnRemoteOptions | undefined = undefined;
|
let _openOnRemote: OpenOnRemoteOptions | undefined = undefined;
|
||||||
|
|
||||||
|
|
@ -71,5 +71,5 @@ export async function pleaseLogin(opts: {
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
});
|
});
|
||||||
|
|
||||||
throw new Error('signin required');
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import { prefer } from '@/preferences.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
export function shouldHideFileByDefault(file: Misskey.entities.DriveFile): boolean {
|
||||||
|
if (prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.isSensitive && prefer.s.nsfw !== 'ignore') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function canRevealFile(file: Misskey.entities.DriveFile): Promise<boolean> {
|
||||||
|
if (file.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'question',
|
||||||
|
text: i18n.ts.sensitiveMediaRevealConfirm,
|
||||||
|
});
|
||||||
|
if (canceled) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2025.12.2",
|
"version": "2026.1.0-alpha.0",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
|
|
||||||
2583
pnpm-lock.yaml
2583
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue