Compare commits
66 Commits
2025.3.1-b
...
2025.3.2-a
Author | SHA1 | Date | |
---|---|---|---|
![]() |
15685be4cc | ||
![]() |
8508c4dadc | ||
![]() |
e594fb0037 | ||
![]() |
a369721791 | ||
![]() |
f8e244f48d | ||
![]() |
8410611512 | ||
![]() |
caab1ec7c3 | ||
![]() |
ffade9740e | ||
![]() |
b03bcf26cd | ||
![]() |
ddbc83b2e4 | ||
![]() |
d185785f20 | ||
![]() |
02d7fbefc4 | ||
![]() |
f7ea92c68c | ||
![]() |
e891d5c5d3 | ||
![]() |
57a6b630b7 | ||
![]() |
eda768a08c | ||
![]() |
1f345eb839 | ||
![]() |
1f2801af02 | ||
![]() |
a4ba096e2a | ||
![]() |
6841cdfa76 | ||
![]() |
794f360bc2 | ||
![]() |
f797765b1d | ||
![]() |
9dce512fbb | ||
![]() |
9e91f85370 | ||
![]() |
9998cb84e8 | ||
![]() |
5ed1101bbd | ||
![]() |
6c9153300d | ||
![]() |
7957ee5191 | ||
![]() |
b200743845 | ||
![]() |
08f7e7d9b3 | ||
![]() |
16ad6b3f6c | ||
![]() |
4df9083bf0 | ||
![]() |
6419af2179 | ||
![]() |
d9858b03c9 | ||
![]() |
88efc0a3be | ||
![]() |
ac21fa7194 | ||
![]() |
c76afce9a7 | ||
![]() |
8e3304344f | ||
![]() |
db5c127cdd | ||
![]() |
0402866b43 | ||
![]() |
6cefabc6b6 | ||
![]() |
c9c04d8391 | ||
![]() |
27e8805dcb | ||
![]() |
933abedc90 | ||
![]() |
69eee9f050 | ||
![]() |
2918fb2609 | ||
![]() |
fcd7fa62ba | ||
![]() |
be7e3b9a0c | ||
![]() |
06e7272ca1 | ||
![]() |
f35eb0f6d9 | ||
![]() |
bdb74539d4 | ||
![]() |
abc1e9168d | ||
![]() |
d30ddd4c2e | ||
![]() |
05cdc095c0 | ||
![]() |
7c1dc3d632 | ||
![]() |
c53349c3b4 | ||
![]() |
a710af54ed | ||
![]() |
ac07bb8d92 | ||
![]() |
698505030e | ||
![]() |
e16a14dcef | ||
![]() |
6d93725084 | ||
![]() |
cb9981d4eb | ||
![]() |
bee4db82bb | ||
![]() |
d7706ef1b5 | ||
![]() |
baf3f4a1d1 | ||
![]() |
c7a56c2c2b |
20
CHANGELOG.md
@@ -1,3 +1,22 @@
|
||||
## 2025.3.2
|
||||
|
||||
### General
|
||||
-
|
||||
|
||||
### Client
|
||||
- Feat: 設定の管理が強化されました
|
||||
- 自動でバックアップされるように
|
||||
- 任意の設定項目をデバイス間で同期できるように(実験的)
|
||||
- Enhance: プラグインの管理が強化されました
|
||||
- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
|
||||
- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
|
||||
- Enhance: テーマ設定画面のデザインを改善
|
||||
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
|
||||
|
||||
### Server
|
||||
- Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正
|
||||
|
||||
|
||||
## 2025.3.1
|
||||
|
||||
### General
|
||||
@@ -10,6 +29,7 @@
|
||||
|
||||
### Server
|
||||
- Fix: DBマイグレーション際にシステムアカウントのユーザーID判定が正しくない問題を修正
|
||||
- Fix: user.featured列が状況によってJSON文字列になっていたのを修正
|
||||
|
||||
|
||||
## 2025.3.0
|
||||
|
@@ -80,12 +80,13 @@ RUN apt-get update \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists
|
||||
|
||||
# add package.json to add pnpm
|
||||
COPY ./package.json ./package.json
|
||||
RUN node -e "console.log(JSON.parse(require('node:fs').readFileSync('./package.json')).packageManager)" | xargs npm install -g
|
||||
|
||||
USER misskey
|
||||
WORKDIR /misskey
|
||||
|
||||
# add package.json to add pnpm
|
||||
COPY --chown=misskey:misskey ./package.json ./package.json
|
||||
|
||||
COPY --chown=misskey:misskey --from=target-builder /misskey/node_modules ./node_modules
|
||||
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/node_modules ./packages/backend/node_modules
|
||||
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-js/node_modules ./packages/misskey-js/node_modules
|
||||
|
Before Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 317 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 238 KiB |
Before Width: | Height: | Size: 148 KiB |
178
locales/index.d.ts
vendored
@@ -5278,6 +5278,176 @@ export interface Locale extends ILocale {
|
||||
* アクセシビリティ
|
||||
*/
|
||||
"accessibility": string;
|
||||
/**
|
||||
* 設定のプロファイル
|
||||
*/
|
||||
"preferencesProfile": string;
|
||||
/**
|
||||
* 設定IDをコピー
|
||||
*/
|
||||
"copyPreferenceId": string;
|
||||
/**
|
||||
* 初期値に戻す
|
||||
*/
|
||||
"resetToDefaultValue": string;
|
||||
/**
|
||||
* アカウントで上書き
|
||||
*/
|
||||
"overrideByAccount": string;
|
||||
/**
|
||||
* 無題
|
||||
*/
|
||||
"untitled": string;
|
||||
/**
|
||||
* 名前はありません
|
||||
*/
|
||||
"noName": string;
|
||||
/**
|
||||
* スキップ
|
||||
*/
|
||||
"skip": string;
|
||||
/**
|
||||
* 復元
|
||||
*/
|
||||
"restore": string;
|
||||
/**
|
||||
* デバイス間で同期
|
||||
*/
|
||||
"syncBetweenDevices": string;
|
||||
/**
|
||||
* サーバーに設定値が存在します
|
||||
*/
|
||||
"preferenceSyncConflictTitle": string;
|
||||
/**
|
||||
* 同期が有効にされた設定項目は設定値をサーバーに保存しますが、この設定項目のサーバーに保存された設定値が見つかりました。どちらの設定値で上書きしますか?
|
||||
*/
|
||||
"preferenceSyncConflictText": string;
|
||||
/**
|
||||
* サーバーの設定値
|
||||
*/
|
||||
"preferenceSyncConflictChoiceServer": string;
|
||||
/**
|
||||
* デバイスの設定値
|
||||
*/
|
||||
"preferenceSyncConflictChoiceDevice": string;
|
||||
/**
|
||||
* 同期の有効化をキャンセル
|
||||
*/
|
||||
"preferenceSyncConflictChoiceCancel": string;
|
||||
"_settings": {
|
||||
/**
|
||||
* ドライブの管理と設定、使用量の確認、ファイルをアップロードする際の設定を行えます。
|
||||
*/
|
||||
"driveBanner": string;
|
||||
/**
|
||||
* プラグインを利用するとクライアントの機能を拡張することができます。プラグインのインストール、個別の設定と管理が行えます。
|
||||
*/
|
||||
"pluginBanner": string;
|
||||
/**
|
||||
* サーバーからの受信する通知の種類と範囲や、プッシュ通知の設定が行えます。
|
||||
*/
|
||||
"notificationsBanner": string;
|
||||
/**
|
||||
* API
|
||||
*/
|
||||
"api": string;
|
||||
/**
|
||||
* Webhook
|
||||
*/
|
||||
"webhook": string;
|
||||
/**
|
||||
* サービス連携
|
||||
*/
|
||||
"serviceConnection": string;
|
||||
/**
|
||||
* 外部のアプリ・サービスと連携するためのアクセストークンやWebhookの管理と設定が行えます。
|
||||
*/
|
||||
"serviceConnectionBanner": string;
|
||||
/**
|
||||
* アカウントのデータ
|
||||
*/
|
||||
"accountData": string;
|
||||
/**
|
||||
* アカウントのデータをエクスポート/インポートして管理できます。
|
||||
*/
|
||||
"accountDataBanner": string;
|
||||
/**
|
||||
* 非表示にするコンテンツの設定や、特定のユーザーからのアクションを制限する設定と管理を行えます。
|
||||
*/
|
||||
"muteAndBlockBanner": string;
|
||||
/**
|
||||
* クライアントの視覚や動作に関するパーソナライズを行い、より最適に使用できるように設定できます。
|
||||
*/
|
||||
"accessibilityBanner": string;
|
||||
/**
|
||||
* コンテンツの公開範囲、見つけやすさ、フォローの承認制などアカウントのプライバシーに関する設定を行えます。
|
||||
*/
|
||||
"privacyBanner": string;
|
||||
/**
|
||||
* パスワード、ログイン方法、認証アプリ、パスキーなどアカウントのセキュリティに関する設定を行えます。
|
||||
*/
|
||||
"securityBanner": string;
|
||||
/**
|
||||
* 好みに応じた、クライアントの全体的な動作の設定が行えます。
|
||||
*/
|
||||
"preferencesBanner": string;
|
||||
/**
|
||||
* 好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。
|
||||
*/
|
||||
"appearanceBanner": string;
|
||||
/**
|
||||
* クライアントで再生するサウンドの設定が行えます。
|
||||
*/
|
||||
"soundsBanner": string;
|
||||
};
|
||||
"_preferencesProfile": {
|
||||
/**
|
||||
* プロファイル名
|
||||
*/
|
||||
"profileName": string;
|
||||
/**
|
||||
* このデバイスを識別する名前を設定してください。
|
||||
*/
|
||||
"profileNameDescription": string;
|
||||
/**
|
||||
* 例: 「メインPC」、「スマホ」など
|
||||
*/
|
||||
"profileNameDescription2": string;
|
||||
};
|
||||
"_preferencesBackup": {
|
||||
/**
|
||||
* 自動バックアップ
|
||||
*/
|
||||
"autoBackup": string;
|
||||
/**
|
||||
* バックアップから復元
|
||||
*/
|
||||
"restoreFromBackup": string;
|
||||
/**
|
||||
* バックアップが見つかりませんでした
|
||||
*/
|
||||
"noBackupsFoundTitle": string;
|
||||
/**
|
||||
* 自動で作成されたバックアップは見つかりませんでしたが、バックアップファイルを手動で保存している場合、それをインポートして復元することはできます。
|
||||
*/
|
||||
"noBackupsFoundDescription": string;
|
||||
/**
|
||||
* 復元するバックアップを選択してください
|
||||
*/
|
||||
"selectBackupToRestore": string;
|
||||
/**
|
||||
* 自動バックアップを有効にするにはプロファイル名の設定が必要です。
|
||||
*/
|
||||
"youNeedToNameYourProfileToEnableAutoBackup": string;
|
||||
/**
|
||||
* このデバイスで設定の自動バックアップは有効になっていません。
|
||||
*/
|
||||
"autoPreferencesBackupIsNotEnabledForThisDevice": string;
|
||||
/**
|
||||
* 設定のバックアップが見つかりました
|
||||
*/
|
||||
"backupFound": string;
|
||||
};
|
||||
"_accountSettings": {
|
||||
/**
|
||||
* コンテンツの表示にログインを必須にする
|
||||
@@ -5315,6 +5485,10 @@ export interface Locale extends ILocale {
|
||||
* リモートサーバーに連合されたノートには効果が及ばない場合があります。
|
||||
*/
|
||||
"mayNotEffectForFederatedNotes": string;
|
||||
/**
|
||||
* これらの制限は簡易的なものです。リモートサーバーでの閲覧やモデレーション時など、一部のシチュエーションでは適用されない場合があります。
|
||||
*/
|
||||
"mayNotEffectSomeSituations": string;
|
||||
/**
|
||||
* 指定した時間を経過しているノート
|
||||
*/
|
||||
@@ -7662,6 +7836,10 @@ export interface Locale extends ILocale {
|
||||
* 標準のテーマ
|
||||
*/
|
||||
"builtinThemes": string;
|
||||
/**
|
||||
* サーバーのテーマ
|
||||
*/
|
||||
"instanceTheme": string;
|
||||
/**
|
||||
* そのテーマは既にインストールされています
|
||||
*/
|
||||
|
@@ -1315,6 +1315,53 @@ markAsSensitiveConfirm: "このメディアをセンシティブとして設定
|
||||
unmarkAsSensitiveConfirm: "このメディアのセンシティブ指定を解除しますか?"
|
||||
preferences: "環境設定"
|
||||
accessibility: "アクセシビリティ"
|
||||
preferencesProfile: "設定のプロファイル"
|
||||
copyPreferenceId: "設定IDをコピー"
|
||||
resetToDefaultValue: "初期値に戻す"
|
||||
overrideByAccount: "アカウントで上書き"
|
||||
untitled: "無題"
|
||||
noName: "名前はありません"
|
||||
skip: "スキップ"
|
||||
restore: "復元"
|
||||
syncBetweenDevices: "デバイス間で同期"
|
||||
preferenceSyncConflictTitle: "サーバーに設定値が存在します"
|
||||
preferenceSyncConflictText: "同期が有効にされた設定項目は設定値をサーバーに保存しますが、この設定項目のサーバーに保存された設定値が見つかりました。どちらの設定値で上書きしますか?"
|
||||
preferenceSyncConflictChoiceServer: "サーバーの設定値"
|
||||
preferenceSyncConflictChoiceDevice: "デバイスの設定値"
|
||||
preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル"
|
||||
|
||||
_settings:
|
||||
driveBanner: "ドライブの管理と設定、使用量の確認、ファイルをアップロードする際の設定を行えます。"
|
||||
pluginBanner: "プラグインを利用するとクライアントの機能を拡張することができます。プラグインのインストール、個別の設定と管理が行えます。"
|
||||
notificationsBanner: "サーバーからの受信する通知の種類と範囲や、プッシュ通知の設定が行えます。"
|
||||
api: "API"
|
||||
webhook: "Webhook"
|
||||
serviceConnection: "サービス連携"
|
||||
serviceConnectionBanner: "外部のアプリ・サービスと連携するためのアクセストークンやWebhookの管理と設定が行えます。"
|
||||
accountData: "アカウントのデータ"
|
||||
accountDataBanner: "アカウントのデータをエクスポート/インポートして管理できます。"
|
||||
muteAndBlockBanner: "非表示にするコンテンツの設定や、特定のユーザーからのアクションを制限する設定と管理を行えます。"
|
||||
accessibilityBanner: "クライアントの視覚や動作に関するパーソナライズを行い、より最適に使用できるように設定できます。"
|
||||
privacyBanner: "コンテンツの公開範囲、見つけやすさ、フォローの承認制などアカウントのプライバシーに関する設定を行えます。"
|
||||
securityBanner: "パスワード、ログイン方法、認証アプリ、パスキーなどアカウントのセキュリティに関する設定を行えます。"
|
||||
preferencesBanner: "好みに応じた、クライアントの全体的な動作の設定が行えます。"
|
||||
appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。"
|
||||
soundsBanner: "クライアントで再生するサウンドの設定が行えます。"
|
||||
|
||||
_preferencesProfile:
|
||||
profileName: "プロファイル名"
|
||||
profileNameDescription: "このデバイスを識別する名前を設定してください。"
|
||||
profileNameDescription2: "例: 「メインPC」、「スマホ」など"
|
||||
|
||||
_preferencesBackup:
|
||||
autoBackup: "自動バックアップ"
|
||||
restoreFromBackup: "バックアップから復元"
|
||||
noBackupsFoundTitle: "バックアップが見つかりませんでした"
|
||||
noBackupsFoundDescription: "自動で作成されたバックアップは見つかりませんでしたが、バックアップファイルを手動で保存している場合、それをインポートして復元することはできます。"
|
||||
selectBackupToRestore: "復元するバックアップを選択してください"
|
||||
youNeedToNameYourProfileToEnableAutoBackup: "自動バックアップを有効にするにはプロファイル名の設定が必要です。"
|
||||
autoPreferencesBackupIsNotEnabledForThisDevice: "このデバイスで設定の自動バックアップは有効になっていません。"
|
||||
backupFound: "設定のバックアップが見つかりました"
|
||||
|
||||
_accountSettings:
|
||||
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
|
||||
@@ -1326,6 +1373,7 @@ _accountSettings:
|
||||
makeNotesHiddenBefore: "過去のノートを非公開化する"
|
||||
makeNotesHiddenBeforeDescription: "この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートが自分のみ表示可能(非公開化)になります。無効に戻すと、ノートの公開状態も元に戻ります。"
|
||||
mayNotEffectForFederatedNotes: "リモートサーバーに連合されたノートには効果が及ばない場合があります。"
|
||||
mayNotEffectSomeSituations: "これらの制限は簡易的なものです。リモートサーバーでの閲覧やモデレーション時など、一部のシチュエーションでは適用されない場合があります。"
|
||||
notesHavePassedSpecifiedPeriod: "指定した時間を経過しているノート"
|
||||
notesOlderThanSpecifiedDateAndTime: "指定した日時より前のノート"
|
||||
|
||||
@@ -2007,6 +2055,7 @@ _theme:
|
||||
installed: "{name}をインストールしました"
|
||||
installedThemes: "インストールされたテーマ"
|
||||
builtinThemes: "標準のテーマ"
|
||||
instanceTheme: "サーバーのテーマ"
|
||||
alreadyInstalled: "そのテーマは既にインストールされています"
|
||||
invalid: "テーマの形式が間違っています"
|
||||
make: "テーマを作る"
|
||||
|
30
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2025.3.1-beta.0",
|
||||
"version": "2025.3.2-alpha.8",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -24,6 +24,7 @@
|
||||
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
||||
"build-storybook": "pnpm --filter frontend build-storybook",
|
||||
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
||||
"build-frontend-search-index": "pnpm --filter frontend build-search-index",
|
||||
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
||||
"start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||
"init": "pnpm migrate",
|
||||
@@ -47,31 +48,31 @@
|
||||
"cleanall": "pnpm clean-all"
|
||||
},
|
||||
"resolutions": {
|
||||
"chokidar": "3.6.0",
|
||||
"chokidar": "4.0.3",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssnano": "7.0.6",
|
||||
"execa": "8.0.1",
|
||||
"execa": "9.5.2",
|
||||
"fast-glob": "3.3.3",
|
||||
"ignore-walk": "6.0.5",
|
||||
"ignore-walk": "7.0.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.5.2",
|
||||
"tar": "6.2.1",
|
||||
"postcss": "8.5.3",
|
||||
"tar": "7.4.3",
|
||||
"terser": "5.39.0",
|
||||
"typescript": "5.7.3",
|
||||
"typescript": "5.8.2",
|
||||
"esbuild": "0.25.0",
|
||||
"glob": "11.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@misskey-dev/eslint-plugin": "2.1.0",
|
||||
"@types/node": "22.13.4",
|
||||
"@typescript-eslint/eslint-plugin": "8.24.0",
|
||||
"@typescript-eslint/parser": "8.24.0",
|
||||
"@types/node": "22.13.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.26.0",
|
||||
"@typescript-eslint/parser": "8.26.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "14.0.3",
|
||||
"eslint": "9.20.1",
|
||||
"globals": "15.15.0",
|
||||
"cypress": "14.1.0",
|
||||
"eslint": "9.22.0",
|
||||
"globals": "16.0.0",
|
||||
"ncp": "2.0.0",
|
||||
"pnpm": "10.6.1",
|
||||
"start-server-and-test": "2.0.10"
|
||||
@@ -82,6 +83,9 @@
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@aiscript-dev/aiscript-languageserver": "-"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"re2": "scripts/dependency-patches/re2.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class UserFeaturedFixup1741424411879 {
|
||||
name = 'UserFeaturedFixup1741424411879'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE OR REPLACE FUNCTION pg_temp.extract_ap_id(text) RETURNS text AS $$
|
||||
SELECT
|
||||
CASE
|
||||
WHEN $1 ~ '^https?://' THEN $1
|
||||
WHEN $1 LIKE '{%' THEN COALESCE(jsonb_extract_path_text($1::jsonb, 'id'), null)
|
||||
ELSE null
|
||||
END;
|
||||
$$ LANGUAGE sql IMMUTABLE;`);
|
||||
|
||||
// "host" is NOT NULL is not needed but just in case add it to prevent overwriting irreplaceable data
|
||||
await queryRunner.query(`UPDATE "user" SET "featured" = pg_temp.extract_ap_id("featured") WHERE "host" IS NOT NULL`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
// fixup migration, no down migration
|
||||
}
|
||||
}
|
@@ -268,7 +268,6 @@ export class FileInfoService {
|
||||
private async *asyncIterateFrames(cwd: string, command: FFmpeg.FfmpegCommand): AsyncGenerator<string, void> {
|
||||
const watcher = new FSWatcher({
|
||||
cwd,
|
||||
disableGlobbing: true,
|
||||
});
|
||||
let finished = false;
|
||||
command.once('end', () => {
|
||||
|
@@ -499,11 +499,28 @@ export class ApRendererService {
|
||||
this.userProfilesRepository.findOneByOrFail({ userId: user.id }),
|
||||
]);
|
||||
|
||||
const tryRewriteUrl = (maybeUrl: string) => {
|
||||
const urlSafeRegex = /^(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/;
|
||||
try {
|
||||
const match = maybeUrl.match(urlSafeRegex);
|
||||
if (!match) {
|
||||
return maybeUrl;
|
||||
}
|
||||
const urlPart = match[0];
|
||||
const urlPartParsed = new URL(urlPart);
|
||||
const restPart = maybeUrl.slice(match[0].length);
|
||||
|
||||
return `<a href="${urlPartParsed.href}" rel="me nofollow noopener" target="_blank">${urlPart}</a>${restPart}`;
|
||||
} catch (e) {
|
||||
return maybeUrl;
|
||||
}
|
||||
};
|
||||
|
||||
const attachment = profile.fields.map(field => ({
|
||||
type: 'PropertyValue',
|
||||
name: field.name,
|
||||
value: (field.value.startsWith('http://') || field.value.startsWith('https://'))
|
||||
? `<a href="${new URL(field.value).href}" rel="me nofollow noopener" target="_blank">${new URL(field.value).href}</a>`
|
||||
? tryRewriteUrl(field.value)
|
||||
: field.value,
|
||||
}));
|
||||
|
||||
|
@@ -560,7 +560,7 @@ export class ApPersonService implements OnModuleInit {
|
||||
inbox: person.inbox,
|
||||
sharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox ?? null,
|
||||
followersUri: person.followers ? getApId(person.followers) : undefined,
|
||||
featured: person.featured,
|
||||
featured: person.featured ? getApId(person.featured) : undefined,
|
||||
emojis: emojiNames,
|
||||
name: truncate(person.name, nameLength),
|
||||
tags,
|
||||
|
@@ -74,6 +74,10 @@ services:
|
||||
source: ../../../pnpm-workspace.yaml
|
||||
target: /misskey/pnpm-workspace.yaml
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: ../../../scripts/dependency-patches
|
||||
target: /misskey/scripts/dependency-patches
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: ./certificates/rootCA.crt
|
||||
target: /usr/local/share/ca-certificates/rootCA.crt
|
||||
|
@@ -70,6 +70,10 @@ services:
|
||||
source: ../../../pnpm-workspace.yaml
|
||||
target: /misskey/pnpm-workspace.yaml
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: ../../../scripts/dependency-patches
|
||||
target: /misskey/scripts/dependency-patches
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: ./certificates/rootCA.crt
|
||||
target: /usr/local/share/ca-certificates/rootCA.crt
|
||||
@@ -114,6 +118,10 @@ services:
|
||||
source: ../../../pnpm-workspace.yaml
|
||||
target: /misskey/pnpm-workspace.yaml
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: ../../../scripts/dependency-patches
|
||||
target: /misskey/scripts/dependency-patches
|
||||
read_only: true
|
||||
working_dir: /misskey
|
||||
command: >
|
||||
bash -c "
|
||||
|
@@ -53,7 +53,7 @@
|
||||
"acorn": "8.14.1",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-vue": "9.33.0",
|
||||
"eslint-plugin-vue": "10.0.0",
|
||||
"fast-glob": "3.3.3",
|
||||
"happy-dom": "17.3.0",
|
||||
"intersection-observer": "0.12.2",
|
||||
@@ -64,7 +64,7 @@
|
||||
"start-server-and-test": "2.0.10",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vue-component-type-helpers": "2.2.8",
|
||||
"vue-eslint-parser": "9.4.3",
|
||||
"vue-eslint-parser": "10.1.1",
|
||||
"vue-tsc": "2.2.8"
|
||||
}
|
||||
}
|
||||
|
@@ -25,10 +25,10 @@
|
||||
"@typescript-eslint/eslint-plugin": "8.26.0",
|
||||
"@typescript-eslint/parser": "8.26.0",
|
||||
"esbuild": "0.25.0",
|
||||
"eslint-plugin-vue": "9.33.0",
|
||||
"eslint-plugin-vue": "10.0.0",
|
||||
"nodemon": "3.1.9",
|
||||
"typescript": "5.8.2",
|
||||
"vue-eslint-parser": "9.4.3"
|
||||
"vue-eslint-parser": "10.1.1"
|
||||
},
|
||||
"files": [
|
||||
"js-built"
|
||||
|
@@ -21,7 +21,7 @@ let moduleInitialized = false;
|
||||
let unobserve = () => {};
|
||||
let misskeyOS = null;
|
||||
|
||||
function loadTheme(applyTheme: typeof import('../src/scripts/theme')['applyTheme']) {
|
||||
function loadTheme(applyTheme: typeof import('../src/theme')['applyTheme']) {
|
||||
unobserve();
|
||||
const theme = themes[document.documentElement.dataset.misskeyTheme];
|
||||
if (theme) {
|
||||
@@ -67,10 +67,10 @@ queueMicrotask(() => {
|
||||
import('../src/components'),
|
||||
import('../src/directives'),
|
||||
import('../src/widgets'),
|
||||
import('../src/scripts/theme'),
|
||||
import('../src/store'),
|
||||
import('../src/theme'),
|
||||
import('../src/preferences'),
|
||||
import('../src/os'),
|
||||
]).then(([{ default: components }, { default: directives }, { default: widgets }, { applyTheme }, { defaultStore }, os]) => {
|
||||
]).then(([{ default: components }, { default: directives }, { default: widgets }, { applyTheme }, { prefer }, os]) => {
|
||||
setup((app) => {
|
||||
moduleInitialized = true;
|
||||
if (app[appInitialized]) {
|
||||
@@ -83,7 +83,7 @@ queueMicrotask(() => {
|
||||
widgets(app);
|
||||
misskeyOS = os;
|
||||
if (isChromatic()) {
|
||||
defaultStore.set('animation', false);
|
||||
prefer.set('animation', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -104,9 +104,9 @@ const preview = {
|
||||
}
|
||||
}).catch(() => {})
|
||||
: Promise.resolve();
|
||||
const resetDefaultStorePromise = import('../src/store').then(({ defaultStore }) => {
|
||||
const resetDefaultStorePromise = import('../src/store').then(({ store }) => {
|
||||
// @ts-expect-error
|
||||
defaultStore.init();
|
||||
store.init();
|
||||
}).catch(() => {});
|
||||
Promise.all([resetIndexedDBPromise, resetDefaultStorePromise]).then(() => {
|
||||
initLocalStorage();
|
||||
|
2
packages/frontend/@types/theme.d.ts
vendored
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
declare module '@@/themes/*.json5' {
|
||||
import { Theme } from '@/scripts/theme.js';
|
||||
import { Theme } from '@/theme.js';
|
||||
|
||||
const theme: Theme;
|
||||
|
||||
|
BIN
packages/frontend/assets/bell_3d.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
packages/frontend/assets/cloud_3d.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
packages/frontend/assets/desktop_computer_3d.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
packages/frontend/assets/electric_plug_3d.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
packages/frontend/assets/gear_3d.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
packages/frontend/assets/link_3d.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
packages/frontend/assets/locked_with_key_3d.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
packages/frontend/assets/mens_room_3d.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
packages/frontend/assets/musical_note_3d.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
packages/frontend/assets/package_3d.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
packages/frontend/assets/prohibited_3d.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
packages/frontend/assets/speaker_high_volume_3d.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
packages/frontend/assets/unlocked_3d.png
Normal file
After Width: | Height: | Size: 30 KiB |
@@ -58,7 +58,7 @@ describe(normalizeClass.name, () => {
|
||||
|
||||
it('Composition API (standard)', () => {
|
||||
const ast = parse(`
|
||||
import { c as api, d as defaultStore, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc } from './app-!~{001}~.js';
|
||||
import { c as api, d as store, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc } from './app-!~{001}~.js';
|
||||
import { M as MkContainer } from './MkContainer-!~{03M}~.js';
|
||||
import { b as defineComponent, a as ref, e as onMounted, z as resolveComponent, g as openBlock, h as createBlock, i as withCtx, K as createTextVNode, E as toDisplayString, u as unref, l as createBaseVNode, q as normalizeClass, B as createCommentVNode, k as createElementBlock, F as Fragment, C as renderList, A as createVNode } from './vue-!~{002}~.js';
|
||||
import './photoswipe-!~{003}~.js';
|
||||
@@ -74,7 +74,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
||||
let fetching = ref(true);
|
||||
let images = ref([]);
|
||||
function thumbnail(image) {
|
||||
return defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
|
||||
return store.s.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
|
||||
}
|
||||
onMounted(() => {
|
||||
const image = [
|
||||
@@ -173,7 +173,7 @@ export { index_photos as default };
|
||||
`.slice(1), { ecmaVersion: 'latest', sourceType: 'module' });
|
||||
unwindCssModuleClassName(ast);
|
||||
expect(generate(ast)).toBe(`
|
||||
import {c as api, d as defaultStore, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc} from './app-!~{001}~.js';
|
||||
import {c as api, d as store, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc} from './app-!~{001}~.js';
|
||||
import {M as MkContainer} from './MkContainer-!~{03M}~.js';
|
||||
import {b as defineComponent, a as ref, e as onMounted, z as resolveComponent, g as openBlock, h as createBlock, i as withCtx, K as createTextVNode, E as toDisplayString, u as unref, l as createBaseVNode, q as normalizeClass, B as createCommentVNode, k as createElementBlock, F as Fragment, C as renderList, A as createVNode} from './vue-!~{002}~.js';
|
||||
import './photoswipe-!~{003}~.js';
|
||||
@@ -190,7 +190,7 @@ const index_photos = defineComponent({
|
||||
let fetching = ref(true);
|
||||
let images = ref([]);
|
||||
function thumbnail(image) {
|
||||
return defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
|
||||
return store.s.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
|
||||
}
|
||||
onMounted(() => {
|
||||
const image = ["image/jpeg", "image/webp", "image/avif", "image/png", "image/gif", "image/apng", "image/vnd.mozilla.apng"];
|
||||
@@ -268,7 +268,7 @@ export {index_photos as default};
|
||||
it('Composition API (with `useCssModule()`)', () => {
|
||||
const ast = parse(`
|
||||
import { a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup } from './!~{002}~.js';
|
||||
import { d as defaultStore, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc } from './app-!~{001}~.js';
|
||||
import { d as store, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc } from './app-!~{001}~.js';
|
||||
|
||||
function isDebuggerEnabled(id) {
|
||||
try {
|
||||
@@ -393,7 +393,7 @@ const _sfc_main = defineComponent({
|
||||
el.style.left = "";
|
||||
}
|
||||
return () => h(
|
||||
defaultStore.state.animation ? TransitionGroup : "div",
|
||||
prefer.s.animation ? TransitionGroup : "div",
|
||||
{
|
||||
class: {
|
||||
[$style["date-separated-list"]]: true,
|
||||
@@ -402,7 +402,7 @@ const _sfc_main = defineComponent({
|
||||
[$style["direction-down"]]: props.direction === "down",
|
||||
[$style["direction-up"]]: props.direction === "up"
|
||||
},
|
||||
...defaultStore.state.animation ? {
|
||||
...prefer.s.animation ? {
|
||||
name: "list",
|
||||
tag: "div",
|
||||
onBeforeLeave,
|
||||
@@ -441,7 +441,7 @@ export { MkDateSeparatedList as M };
|
||||
unwindCssModuleClassName(ast);
|
||||
expect(generate(ast)).toBe(`
|
||||
import {a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup} from './!~{002}~.js';
|
||||
import {d as defaultStore, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc} from './app-!~{001}~.js';
|
||||
import {d as store, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc} from './app-!~{001}~.js';
|
||||
function isDebuggerEnabled(id) {
|
||||
try {
|
||||
return localStorage.getItem(\`DEBUG_\${id}\`) !== null;
|
||||
@@ -555,7 +555,7 @@ const _sfc_main = defineComponent({
|
||||
el.style.top = "";
|
||||
el.style.left = "";
|
||||
}
|
||||
return () => h(defaultStore.state.animation ? TransitionGroup : "div", {
|
||||
return () => h(prefer.s.animation ? TransitionGroup : "div", {
|
||||
class: {
|
||||
[$style["date-separated-list"]]: true,
|
||||
[$style["date-separated-list-nogap"]]: props.noGap,
|
||||
@@ -563,7 +563,7 @@ const _sfc_main = defineComponent({
|
||||
[$style["direction-down"]]: props.direction === "down",
|
||||
[$style["direction-up"]]: props.direction === "up"
|
||||
},
|
||||
...defaultStore.state.animation ? {
|
||||
...prefer.s.animation ? {
|
||||
name: "list",
|
||||
tag: "div",
|
||||
onBeforeLeave,
|
||||
|
@@ -1213,22 +1213,37 @@ async function processVueFile(
|
||||
transformedCodeCache: Record<string, string>
|
||||
}> {
|
||||
const normalizedId = id.replace(/\\/g, '/'); // ファイルパスを正規化
|
||||
// すでにキャッシュに存在する場合は、そのまま返す
|
||||
if (transformedCodeCache[normalizedId] && transformedCodeCache[normalizedId].includes('markerId=')) {
|
||||
|
||||
// 開発モード時はコード内容に変更があれば常に再処理する
|
||||
// コード内容が同じ場合のみキャッシュを使用
|
||||
const isDevMode = process.env.NODE_ENV === 'development';
|
||||
|
||||
const s = new MagicString(code); // magic-string のインスタンスを作成
|
||||
|
||||
if (!isDevMode && transformedCodeCache[normalizedId] && transformedCodeCache[normalizedId].includes('markerId=')) {
|
||||
logger.info(`Using cached version for ${id}`);
|
||||
return {
|
||||
code: transformedCodeCache[normalizedId],
|
||||
map: null,
|
||||
map: s.generateMap({ source: id, includeContent: true }),
|
||||
transformedCodeCache
|
||||
};
|
||||
}
|
||||
|
||||
// すでに処理済みのファイルでコードに変更がない場合はキャッシュを返す
|
||||
if (transformedCodeCache[normalizedId] === code) {
|
||||
logger.info(`Code unchanged for ${id}, using cached version`);
|
||||
return {
|
||||
code: transformedCodeCache[normalizedId],
|
||||
map: s.generateMap({ source: id, includeContent: true }),
|
||||
transformedCodeCache
|
||||
};
|
||||
}
|
||||
|
||||
const s = new MagicString(code); // magic-string のインスタンスを作成
|
||||
const parsed = vueSfcParse(code, { filename: id });
|
||||
if (!parsed.descriptor.template) {
|
||||
return {
|
||||
code,
|
||||
map: null,
|
||||
map: s.generateMap({ source: id, includeContent: true }),
|
||||
transformedCodeCache
|
||||
};
|
||||
}
|
||||
@@ -1413,6 +1428,23 @@ async function processVueFile(
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateSearchIndex(options: Options, transformedCodeCache: Record<string, string> = {}) {
|
||||
const filePaths = options.targetFilePaths.reduce<string[]>((acc, filePathPattern) => {
|
||||
const matchedFiles = glob.sync(filePathPattern);
|
||||
return [...acc, ...matchedFiles];
|
||||
}, []);
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
const id = path.resolve(filePath); // 絶対パスに変換
|
||||
const code = fs.readFileSync(filePath, 'utf-8'); // ファイル内容を読み込む
|
||||
const { transformedCodeCache: newCache } = await processVueFile(code, id, options, transformedCodeCache); // processVueFile 関数を呼び出す
|
||||
transformedCodeCache = newCache; // キャッシュを更新
|
||||
}
|
||||
|
||||
await analyzeVueProps({ ...options, transformedCodeCache }); // 開発サーバー起動時にも analyzeVueProps を実行
|
||||
|
||||
return transformedCodeCache; // キャッシュを返す
|
||||
}
|
||||
|
||||
// Rollup プラグインとして export
|
||||
export default function pluginCreateSearchIndex(options: Options): Plugin {
|
||||
@@ -1430,19 +1462,7 @@ export default function pluginCreateSearchIndex(options: Options): Plugin {
|
||||
return;
|
||||
}
|
||||
|
||||
const filePaths = options.targetFilePaths.reduce<string[]>((acc, filePathPattern) => {
|
||||
const matchedFiles = glob.sync(filePathPattern);
|
||||
return [...acc, ...matchedFiles];
|
||||
}, []);
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
const id = path.resolve(filePath); // 絶対パスに変換
|
||||
const code = fs.readFileSync(filePath, 'utf-8'); // ファイル内容を読み込む
|
||||
const { transformedCodeCache: newCache } = await processVueFile(code, id, options, transformedCodeCache); // processVueFile 関数を呼び出す
|
||||
transformedCodeCache = newCache; // キャッシュを更新
|
||||
}
|
||||
|
||||
await analyzeVueProps({ ...options, transformedCodeCache }); // 開発サーバー起動時にも analyzeVueProps を実行
|
||||
transformedCodeCache = await generateSearchIndex(options, transformedCodeCache);
|
||||
},
|
||||
|
||||
async transform(code, id) {
|
||||
@@ -1466,16 +1486,21 @@ export default function pluginCreateSearchIndex(options: Options): Plugin {
|
||||
if (isMatch) break; // いずれかのパターンでマッチしたら、outer loop も抜ける
|
||||
}
|
||||
|
||||
|
||||
if (!isMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ファイルの内容が変更された場合は再処理を行う
|
||||
const normalizedId = id.replace(/\\/g, '/');
|
||||
const hasContentChanged = !transformedCodeCache[normalizedId] || transformedCodeCache[normalizedId] !== code;
|
||||
|
||||
const transformed = await processVueFile(code, id, options, transformedCodeCache);
|
||||
transformedCodeCache = transformed.transformedCodeCache; // キャッシュを更新
|
||||
if (isDevServer) {
|
||||
await analyzeVueProps({ ...options, transformedCodeCache }); // analyzeVueProps を呼び出す
|
||||
|
||||
if (isDevServer && hasContentChanged) {
|
||||
await analyzeVueProps({ ...options, transformedCodeCache }); // ファイルが変更されたときのみ分析を実行
|
||||
}
|
||||
|
||||
return transformed;
|
||||
},
|
||||
|
||||
|
@@ -5,6 +5,7 @@
|
||||
"scripts": {
|
||||
"watch": "vite",
|
||||
"build": "vite build",
|
||||
"build-search-index": "vite-node --config \"./vite-node.config.ts\" \"./scripts/generate-search-index.ts\"",
|
||||
"storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"",
|
||||
"build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
|
||||
"build-storybook": "pnpm build-storybook-pre && storybook build --webpack-stats-json storybook-static",
|
||||
@@ -75,7 +76,8 @@
|
||||
"v-code-diff": "1.13.1",
|
||||
"vite": "6.2.1",
|
||||
"vue": "3.5.13",
|
||||
"vuedraggable": "next"
|
||||
"vuedraggable": "next",
|
||||
"wanakana": "5.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@misskey-dev/summaly": "5.2.0",
|
||||
@@ -117,7 +119,7 @@
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "14.1.0",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-vue": "9.33.0",
|
||||
"eslint-plugin-vue": "10.0.0",
|
||||
"fast-glob": "3.3.3",
|
||||
"happy-dom": "17.3.0",
|
||||
"intersection-observer": "0.12.2",
|
||||
@@ -132,11 +134,12 @@
|
||||
"start-server-and-test": "2.0.10",
|
||||
"storybook": "8.6.4",
|
||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||
"vite-node": "3.0.8",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vitest": "3.0.8",
|
||||
"vitest-fetch-mock": "0.4.5",
|
||||
"vue-component-type-helpers": "2.2.8",
|
||||
"vue-eslint-parser": "9.4.3",
|
||||
"vue-eslint-parser": "10.1.1",
|
||||
"vue-tsc": "2.2.8"
|
||||
}
|
||||
}
|
||||
|
15
packages/frontend/scripts/generate-search-index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { searchIndexes } from '../vite.config.js';
|
||||
import { generateSearchIndex } from '../lib/vite-plugin-create-search-index.js';
|
||||
|
||||
async function main() {
|
||||
for (const searchIndex of searchIndexes) {
|
||||
await generateSearchIndex(searchIndex);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
@@ -8,15 +8,16 @@ import * as Misskey from 'misskey-js';
|
||||
import { apiUrl } from '@@/js/config.js';
|
||||
import type { MenuItem, MenuButton } from '@/types/menu.js';
|
||||
import { defaultMemoryStorage } from '@/memory-storage';
|
||||
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
|
||||
import { showSuspendedDialog } from '@/utility/show-suspended-dialog.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { del, get, set } from '@/scripts/idb-proxy.js';
|
||||
import { del, get, set } from '@/utility/idb-proxy.js';
|
||||
import { waiting, popup, popupMenu, success, alert } from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { unisonReload, reloadChannel } from '@/utility/unison-reload.js';
|
||||
|
||||
// TODO: 他のタブと永続化されたstateを同期
|
||||
// TODO: accountsはpreferences管理にする(tokenは別管理)
|
||||
|
||||
type Account = Misskey.entities.MeDetailed & { token: string };
|
||||
|
||||
|
@@ -8,7 +8,7 @@ import * as Misskey from 'misskey-js';
|
||||
import { url, lang } from '@@/js/config.js';
|
||||
import { assertStringAndIsIn } from './common.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { customEmojis } from '@/custom-emojis.js';
|
@@ -6,26 +6,29 @@
|
||||
import { computed, watch, version as vueVersion } from 'vue';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import { version, lang, updateLocale, locale } from '@@/js/config.js';
|
||||
import defaultLightTheme from '@@/themes/l-light.json5';
|
||||
import defaultDarkTheme from '@@/themes/d-green-lime.json5';
|
||||
import type { App } from 'vue';
|
||||
import widgets from '@/widgets/index.js';
|
||||
import directives from '@/directives/index.js';
|
||||
import components from '@/components/index.js';
|
||||
import { applyTheme } from '@/scripts/theme.js';
|
||||
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
|
||||
import { applyTheme } from '@/theme.js';
|
||||
import { isDeviceDarkmode } from '@/utility/is-device-darkmode.js';
|
||||
import { updateI18n, i18n } from '@/i18n.js';
|
||||
import { $i, refreshAccount, login } from '@/account.js';
|
||||
import { defaultStore, ColdDeviceStorage } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { fetchInstance, instance } from '@/instance.js';
|
||||
import { deviceKind, updateDeviceKind } from '@/scripts/device-kind.js';
|
||||
import { reloadChannel } from '@/scripts/unison-reload.js';
|
||||
import { getUrlWithoutLoginId } from '@/scripts/login-id.js';
|
||||
import { getAccountFromId } from '@/scripts/get-account-from-id.js';
|
||||
import { deviceKind, updateDeviceKind } from '@/utility/device-kind.js';
|
||||
import { reloadChannel } from '@/utility/unison-reload.js';
|
||||
import { getUrlWithoutLoginId } from '@/utility/login-id.js';
|
||||
import { getAccountFromId } from '@/utility/get-account-from-id.js';
|
||||
import { deckStore } from '@/ui/deck/deck-store.js';
|
||||
import { analytics, initAnalytics } from '@/analytics.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { fetchCustomEmojis } from '@/custom-emojis.js';
|
||||
import { setupRouter } from '@/router/main.js';
|
||||
import { createMainRouter } from '@/router/definition.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
export async function common(createVue: () => App<Element>) {
|
||||
console.info(`Misskey v${version}`);
|
||||
@@ -38,7 +41,7 @@ export async function common(createVue: () => App<Element>) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(window as any).$i = $i;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(window as any).$store = defaultStore;
|
||||
(window as any).$store = store;
|
||||
|
||||
window.addEventListener('error', event => {
|
||||
console.error(event);
|
||||
@@ -123,7 +126,7 @@ export async function common(createVue: () => App<Element>) {
|
||||
html.setAttribute('lang', lang);
|
||||
//#endregion
|
||||
|
||||
await defaultStore.ready;
|
||||
await store.ready;
|
||||
await deckStore.ready;
|
||||
|
||||
const fetchInstanceMetaPromise = fetchInstance();
|
||||
@@ -151,56 +154,63 @@ export async function common(createVue: () => App<Element>) {
|
||||
//#endregion
|
||||
|
||||
// NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため)
|
||||
watch(defaultStore.reactiveState.darkMode, (darkMode) => {
|
||||
applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme'));
|
||||
watch(store.r.darkMode, (darkMode) => {
|
||||
applyTheme(darkMode
|
||||
? (prefer.s.darkTheme ?? defaultDarkTheme)
|
||||
: (prefer.s.lightTheme ?? defaultLightTheme),
|
||||
);
|
||||
}, { immediate: miLocalStorage.getItem('theme') == null });
|
||||
|
||||
document.documentElement.dataset.colorScheme = defaultStore.state.darkMode ? 'dark' : 'light';
|
||||
document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light';
|
||||
|
||||
const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme'));
|
||||
const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme'));
|
||||
const darkTheme = prefer.model('darkTheme');
|
||||
const lightTheme = prefer.model('lightTheme');
|
||||
|
||||
watch(darkTheme, (theme) => {
|
||||
if (defaultStore.state.darkMode) {
|
||||
applyTheme(theme);
|
||||
if (store.s.darkMode) {
|
||||
applyTheme(theme ?? defaultDarkTheme);
|
||||
}
|
||||
});
|
||||
|
||||
watch(lightTheme, (theme) => {
|
||||
if (!defaultStore.state.darkMode) {
|
||||
applyTheme(theme);
|
||||
if (!store.s.darkMode) {
|
||||
applyTheme(theme ?? defaultLightTheme);
|
||||
}
|
||||
});
|
||||
|
||||
//#region Sync dark mode
|
||||
if (ColdDeviceStorage.get('syncDeviceDarkMode')) {
|
||||
defaultStore.set('darkMode', isDeviceDarkmode());
|
||||
if (prefer.s.syncDeviceDarkMode) {
|
||||
store.set('darkMode', isDeviceDarkmode());
|
||||
}
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => {
|
||||
if (ColdDeviceStorage.get('syncDeviceDarkMode')) {
|
||||
defaultStore.set('darkMode', mql.matches);
|
||||
if (prefer.s.syncDeviceDarkMode) {
|
||||
store.set('darkMode', mql.matches);
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
|
||||
if (prefer.s.darkTheme && store.s.darkMode) {
|
||||
if (miLocalStorage.getItem('themeId') !== prefer.s.darkTheme.id) applyTheme(prefer.s.darkTheme);
|
||||
} else if (prefer.s.lightTheme && !store.s.darkMode) {
|
||||
if (miLocalStorage.getItem('themeId') !== prefer.s.lightTheme.id) applyTheme(prefer.s.lightTheme);
|
||||
}
|
||||
|
||||
fetchInstanceMetaPromise.then(() => {
|
||||
if (defaultStore.state.themeInitial) {
|
||||
if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON.parse(instance.defaultLightTheme));
|
||||
if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON.parse(instance.defaultDarkTheme));
|
||||
defaultStore.set('themeInitial', false);
|
||||
}
|
||||
// TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア
|
||||
if (prefer.s.lightTheme == null && instance.defaultLightTheme != null) prefer.commit('lightTheme', JSON.parse(instance.defaultLightTheme));
|
||||
if (prefer.s.darkTheme == null && instance.defaultDarkTheme != null) prefer.commit('darkTheme', JSON.parse(instance.defaultDarkTheme));
|
||||
});
|
||||
|
||||
watch(defaultStore.reactiveState.overridedDeviceKind, (kind) => {
|
||||
watch(prefer.r.overridedDeviceKind, (kind) => {
|
||||
updateDeviceKind(kind);
|
||||
}, { immediate: true });
|
||||
|
||||
watch(defaultStore.reactiveState.useBlurEffectForModal, v => {
|
||||
watch(prefer.r.useBlurEffectForModal, v => {
|
||||
document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none');
|
||||
}, { immediate: true });
|
||||
|
||||
watch(defaultStore.reactiveState.useBlurEffect, v => {
|
||||
watch(prefer.r.useBlurEffect, v => {
|
||||
if (v) {
|
||||
document.documentElement.style.removeProperty('--MI-blur');
|
||||
} else {
|
||||
@@ -214,7 +224,7 @@ export async function common(createVue: () => App<Element>) {
|
||||
navigator.wakeLock.request('screen');
|
||||
}
|
||||
});
|
||||
if (defaultStore.state.keepScreenOn && 'wakeLock' in navigator) {
|
||||
if (prefer.s.keepScreenOn && 'wakeLock' in navigator) {
|
||||
navigator.wakeLock.request('screen')
|
||||
.then(onVisibilityChange)
|
||||
.catch(() => {
|
||||
|
@@ -5,26 +5,31 @@
|
||||
|
||||
import { createApp, defineAsyncComponent, markRaw } from 'vue';
|
||||
import { ui } from '@@/js/config.js';
|
||||
import { common } from './common.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { common } from './common.js';
|
||||
import type { Component } from 'vue';
|
||||
import type { Keymap } from '@/utility/hotkey.js';
|
||||
import type { DeckProfile } from '@/deck.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { alert, confirm, popup, post, toast } from '@/os.js';
|
||||
import { useStream } from '@/stream.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
import * as sound from '@/utility/sound.js';
|
||||
import { $i, signout, updateAccountPartial } from '@/account.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { ColdDeviceStorage, defaultStore } from '@/store.js';
|
||||
import { reactionPicker } from '@/scripts/reaction-picker.js';
|
||||
import { ColdDeviceStorage, store } from '@/store.js';
|
||||
import { reactionPicker } from '@/utility/reaction-picker.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js';
|
||||
import { initializeSw } from '@/scripts/initialize-sw.js';
|
||||
import { deckStore } from '@/ui/deck/deck-store.js';
|
||||
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
||||
import { claimAchievement, claimedAchievements } from '@/utility/achievements.js';
|
||||
import { initializeSw } from '@/utility/initialize-sw.js';
|
||||
import { emojiPicker } from '@/utility/emoji-picker.js';
|
||||
import { mainRouter } from '@/router/main.js';
|
||||
import { makeHotkey } from '@/scripts/hotkey.js';
|
||||
import type { Keymap } from '@/scripts/hotkey.js';
|
||||
import { makeHotkey } from '@/utility/hotkey.js';
|
||||
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { deckStore } from '@/ui/deck/deck-store.js';
|
||||
import { launchPlugins } from '@/plugin.js';
|
||||
|
||||
export async function mainBoot() {
|
||||
const { isClientUpdated } = await common(() => {
|
||||
@@ -34,7 +39,7 @@ export async function mainBoot() {
|
||||
if (!$i) uiStyle = 'visitor';
|
||||
|
||||
if (searchParams.has('zen')) uiStyle = 'zen';
|
||||
if (uiStyle === 'deck' && deckStore.state.useSimpleUiForNonRootPages && location.pathname !== '/') uiStyle = 'zen';
|
||||
if (uiStyle === 'deck' && prefer.s['deck.useSimpleUiForNonRootPages'] && location.pathname !== '/') uiStyle = 'zen';
|
||||
|
||||
if (searchParams.has('ui')) uiStyle = searchParams.get('ui');
|
||||
|
||||
@@ -73,9 +78,9 @@ export async function mainBoot() {
|
||||
|
||||
let reloadDialogShowing = false;
|
||||
stream.on('_disconnected_', async () => {
|
||||
if (defaultStore.state.serverDisconnectedBehavior === 'reload') {
|
||||
if (prefer.s.serverDisconnectedBehavior === 'reload') {
|
||||
location.reload();
|
||||
} else if (defaultStore.state.serverDisconnectedBehavior === 'dialog') {
|
||||
} else if (prefer.s.serverDisconnectedBehavior === 'dialog') {
|
||||
if (reloadDialogShowing) return;
|
||||
reloadDialogShowing = true;
|
||||
const { canceled } = await confirm({
|
||||
@@ -102,30 +107,24 @@ export async function mainBoot() {
|
||||
removeCustomEmojis(emojiData.emojis);
|
||||
});
|
||||
|
||||
for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) {
|
||||
import('@/plugin.js').then(async ({ install }) => {
|
||||
// Workaround for https://bugs.webkit.org/show_bug.cgi?id=242740
|
||||
await new Promise(r => setTimeout(r, 0));
|
||||
install(plugin);
|
||||
});
|
||||
}
|
||||
launchPlugins();
|
||||
|
||||
try {
|
||||
if (defaultStore.state.enableSeasonalScreenEffect) {
|
||||
if (prefer.s.enableSeasonalScreenEffect) {
|
||||
const month = new Date().getMonth() + 1;
|
||||
if (defaultStore.state.hemisphere === 'S') {
|
||||
if (prefer.s.hemisphere === 'S') {
|
||||
// ▼南半球
|
||||
if (month === 7 || month === 8) {
|
||||
const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
|
||||
const SnowfallEffect = (await import('@/utility/snowfall-effect.js')).SnowfallEffect;
|
||||
new SnowfallEffect({}).render();
|
||||
}
|
||||
} else {
|
||||
// ▼北半球
|
||||
if (month === 12 || month === 1) {
|
||||
const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
|
||||
const SnowfallEffect = (await import('@/utility/snowfall-effect.js')).SnowfallEffect;
|
||||
new SnowfallEffect({}).render();
|
||||
} else if (month === 3 || month === 4) {
|
||||
const SakuraEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
|
||||
const SakuraEffect = (await import('@/utility/snowfall-effect.js')).SnowfallEffect;
|
||||
new SakuraEffect({
|
||||
sakura: true,
|
||||
}).render();
|
||||
@@ -138,8 +137,120 @@ export async function mainBoot() {
|
||||
}
|
||||
|
||||
if ($i) {
|
||||
defaultStore.loaded.then(() => {
|
||||
if (defaultStore.state.accountSetupWizard !== -1) {
|
||||
store.loaded.then(async () => {
|
||||
// prefereces migration
|
||||
// TODO: そのうち消す
|
||||
if (store.s.menu.length > 0) {
|
||||
const themes = await misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' }).catch(() => []);
|
||||
if (themes.length > 0) {
|
||||
prefer.commit('themes', themes);
|
||||
}
|
||||
|
||||
const plugins = ColdDeviceStorage.get('plugins');
|
||||
prefer.commit('plugins', plugins.map(p => ({
|
||||
...p,
|
||||
installId: (p as any).id,
|
||||
id: undefined,
|
||||
})));
|
||||
|
||||
prefer.commit('deck.profile', deckStore.s.profile);
|
||||
misskeyApi('i/registry/keys', {
|
||||
scope: ['client', 'deck', 'profiles'],
|
||||
}).then(async keys => {
|
||||
const profiles: DeckProfile[] = [];
|
||||
for (const key of keys) {
|
||||
const deck = await misskeyApi('i/registry/get', {
|
||||
scope: ['client', 'deck', 'profiles'],
|
||||
key: key,
|
||||
});
|
||||
profiles.push({
|
||||
id: uuid(),
|
||||
name: key,
|
||||
columns: deck.columns,
|
||||
layout: deck.layout,
|
||||
});
|
||||
}
|
||||
prefer.commit('deck.profiles', profiles);
|
||||
});
|
||||
|
||||
prefer.commit('lightTheme', ColdDeviceStorage.get('lightTheme'));
|
||||
prefer.commit('darkTheme', ColdDeviceStorage.get('darkTheme'));
|
||||
prefer.commit('syncDeviceDarkMode', ColdDeviceStorage.get('syncDeviceDarkMode'));
|
||||
prefer.commit('overridedDeviceKind', store.s.overridedDeviceKind);
|
||||
prefer.commit('widgets', store.s.widgets);
|
||||
prefer.commit('keepCw', store.s.keepCw);
|
||||
prefer.commit('collapseRenotes', store.s.collapseRenotes);
|
||||
prefer.commit('rememberNoteVisibility', store.s.rememberNoteVisibility);
|
||||
prefer.commit('uploadFolder', store.s.uploadFolder);
|
||||
prefer.commit('keepOriginalUploading', store.s.keepOriginalUploading);
|
||||
prefer.commit('menu', store.s.menu);
|
||||
prefer.commit('statusbars', store.s.statusbars);
|
||||
prefer.commit('pinnedUserLists', store.s.pinnedUserLists);
|
||||
prefer.commit('serverDisconnectedBehavior', store.s.serverDisconnectedBehavior);
|
||||
prefer.commit('nsfw', store.s.nsfw);
|
||||
prefer.commit('highlightSensitiveMedia', store.s.highlightSensitiveMedia);
|
||||
prefer.commit('animation', store.s.animation);
|
||||
prefer.commit('animatedMfm', store.s.animatedMfm);
|
||||
prefer.commit('advancedMfm', store.s.advancedMfm);
|
||||
prefer.commit('showReactionsCount', store.s.showReactionsCount);
|
||||
prefer.commit('enableQuickAddMfmFunction', store.s.enableQuickAddMfmFunction);
|
||||
prefer.commit('loadRawImages', store.s.loadRawImages);
|
||||
prefer.commit('imageNewTab', store.s.imageNewTab);
|
||||
prefer.commit('disableShowingAnimatedImages', store.s.disableShowingAnimatedImages);
|
||||
prefer.commit('emojiStyle', store.s.emojiStyle);
|
||||
prefer.commit('menuStyle', store.s.menuStyle);
|
||||
prefer.commit('useBlurEffectForModal', store.s.useBlurEffectForModal);
|
||||
prefer.commit('useBlurEffect', store.s.useBlurEffect);
|
||||
prefer.commit('showFixedPostForm', store.s.showFixedPostForm);
|
||||
prefer.commit('showFixedPostFormInChannel', store.s.showFixedPostFormInChannel);
|
||||
prefer.commit('enableInfiniteScroll', store.s.enableInfiniteScroll);
|
||||
prefer.commit('useReactionPickerForContextMenu', store.s.useReactionPickerForContextMenu);
|
||||
prefer.commit('showGapBetweenNotesInTimeline', store.s.showGapBetweenNotesInTimeline);
|
||||
prefer.commit('instanceTicker', store.s.instanceTicker);
|
||||
prefer.commit('emojiPickerScale', store.s.emojiPickerScale);
|
||||
prefer.commit('emojiPickerWidth', store.s.emojiPickerWidth);
|
||||
prefer.commit('emojiPickerHeight', store.s.emojiPickerHeight);
|
||||
prefer.commit('emojiPickerStyle', store.s.emojiPickerStyle);
|
||||
prefer.commit('reportError', store.s.reportError);
|
||||
prefer.commit('squareAvatars', store.s.squareAvatars);
|
||||
prefer.commit('showAvatarDecorations', store.s.showAvatarDecorations);
|
||||
prefer.commit('numberOfPageCache', store.s.numberOfPageCache);
|
||||
prefer.commit('showNoteActionsOnlyHover', store.s.showNoteActionsOnlyHover);
|
||||
prefer.commit('showClipButtonInNoteFooter', store.s.showClipButtonInNoteFooter);
|
||||
prefer.commit('reactionsDisplaySize', store.s.reactionsDisplaySize);
|
||||
prefer.commit('limitWidthOfReaction', store.s.limitWidthOfReaction);
|
||||
prefer.commit('forceShowAds', store.s.forceShowAds);
|
||||
prefer.commit('aiChanMode', store.s.aiChanMode);
|
||||
prefer.commit('devMode', store.s.devMode);
|
||||
prefer.commit('mediaListWithOneImageAppearance', store.s.mediaListWithOneImageAppearance);
|
||||
prefer.commit('notificationPosition', store.s.notificationPosition);
|
||||
prefer.commit('notificationStackAxis', store.s.notificationStackAxis);
|
||||
prefer.commit('enableCondensedLine', store.s.enableCondensedLine);
|
||||
prefer.commit('keepScreenOn', store.s.keepScreenOn);
|
||||
prefer.commit('disableStreamingTimeline', store.s.disableStreamingTimeline);
|
||||
prefer.commit('useGroupedNotifications', store.s.useGroupedNotifications);
|
||||
prefer.commit('dataSaver', store.s.dataSaver);
|
||||
prefer.commit('enableSeasonalScreenEffect', store.s.enableSeasonalScreenEffect);
|
||||
prefer.commit('enableHorizontalSwipe', store.s.enableHorizontalSwipe);
|
||||
prefer.commit('useNativeUiForVideoAudioPlayer', store.s.useNativeUIForVideoAudioPlayer);
|
||||
prefer.commit('keepOriginalFilename', store.s.keepOriginalFilename);
|
||||
prefer.commit('alwaysConfirmFollow', store.s.alwaysConfirmFollow);
|
||||
prefer.commit('confirmWhenRevealingSensitiveMedia', store.s.confirmWhenRevealingSensitiveMedia);
|
||||
prefer.commit('contextMenu', store.s.contextMenu);
|
||||
prefer.commit('skipNoteRender', store.s.skipNoteRender);
|
||||
prefer.commit('showSoftWordMutedWord', store.s.showSoftWordMutedWord);
|
||||
prefer.commit('confirmOnReact', store.s.confirmOnReact);
|
||||
prefer.commit('sound.masterVolume', store.s.sound_masterVolume);
|
||||
prefer.commit('sound.notUseSound', store.s.sound_notUseSound);
|
||||
prefer.commit('sound.useSoundOnlyWhenActive', store.s.sound_useSoundOnlyWhenActive);
|
||||
prefer.commit('sound.on.note', store.s.sound_note as any);
|
||||
prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any);
|
||||
prefer.commit('sound.on.notification', store.s.sound_notification as any);
|
||||
prefer.commit('sound.on.reaction', store.s.sound_reaction as any);
|
||||
store.set('menu', []);
|
||||
}
|
||||
|
||||
if (store.s.accountSetupWizard !== -1) {
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
@@ -154,7 +265,7 @@ export async function mainBoot() {
|
||||
});
|
||||
}
|
||||
|
||||
function onAnnouncementCreated (ev: { announcement: Misskey.entities.Announcement }) {
|
||||
function onAnnouncementCreated(ev: { announcement: Misskey.entities.Announcement }) {
|
||||
const announcement = ev.announcement;
|
||||
if (announcement.display === 'dialog') {
|
||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkAnnouncementDialog.vue')), {
|
||||
@@ -412,7 +523,7 @@ export async function mainBoot() {
|
||||
post();
|
||||
},
|
||||
'd': () => {
|
||||
defaultStore.set('darkMode', !defaultStore.state.darkMode);
|
||||
store.set('darkMode', !store.s.darkMode);
|
||||
},
|
||||
's': () => {
|
||||
mainRouter.push('/search');
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
import { createApp, defineAsyncComponent } from 'vue';
|
||||
import { common } from './common.js';
|
||||
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
||||
import { emojiPicker } from '@/utility/emoji-picker.js';
|
||||
|
||||
export async function subBoot() {
|
||||
const { isClientUpdated } = await common(() => createApp(
|
||||
|
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { Cache } from '@/scripts/cache.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { Cache } from '@/utility/cache.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
||||
export const clipsCache = new Cache<Misskey.entities.Clip[]>(1000 * 60 * 30, () => misskeyApi('clips/list'));
|
||||
export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list'));
|
||||
|
@@ -90,7 +90,7 @@ import MkFolder from '@/components/MkFolder.vue';
|
||||
import RouterView from '@/components/global/RouterView.vue';
|
||||
import { useRouterFactory } from '@/router/supplier';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||
|
||||
const props = defineProps<{
|
||||
report: Misskey.entities.AdminAbuseUserReportsResponse[number];
|
||||
|
@@ -17,7 +17,7 @@ import * as Misskey from 'misskey-js';
|
||||
import MkMention from './MkMention.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { host as localHost } from '@@/js/config.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
||||
const user = ref<Misskey.entities.UserLite>();
|
||||
|
||||
|
@@ -9,7 +9,7 @@ import { HttpResponse, http } from 'msw';
|
||||
import { userDetailed } from '../../.storybook/fakes.js';
|
||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||
import MkAchievements from './MkAchievements.vue';
|
||||
import { ACHIEVEMENT_TYPES } from '@/scripts/achievements.js';
|
||||
import { ACHIEVEMENT_TYPES } from '@/utility/achievements.js';
|
||||
export const Empty = {
|
||||
render(args) {
|
||||
return {
|
||||
|
@@ -55,9 +55,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { ACHIEVEMENT_TYPES, ACHIEVEMENT_BADGES, claimAchievement } from '@/scripts/achievements.js';
|
||||
import { ACHIEVEMENT_TYPES, ACHIEVEMENT_BADGES, claimAchievement } from '@/utility/achievements.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
user: Misskey.entities.User;
|
||||
|
@@ -82,7 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { computed, onMounted, onBeforeUnmount, ref } from 'vue';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { globalEvents } from '@/events.js';
|
||||
import { defaultIdlingRenderScheduler } from '@/scripts/idle-render.js';
|
||||
import { defaultIdlingRenderScheduler } from '@/utility/idle-render.js';
|
||||
|
||||
// https://stackoverflow.com/questions/1878907/how-can-i-find-the-difference-between-two-angles
|
||||
const angleDiff = (a: number, b: number) => {
|
||||
|
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { onMounted, shallowRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import MkModal from '@/components/MkModal.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
@@ -59,10 +59,10 @@ import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { deepMerge } from '@/scripts/merge.js';
|
||||
import type { DeepPartial } from '@/scripts/merge.js';
|
||||
import { deepMerge } from '@/utility/merge.js';
|
||||
import type { DeepPartial } from '@/utility/merge.js';
|
||||
|
||||
type PartialAllowedAntenna = Omit<Misskey.entities.Antenna, 'id' | 'createdAt' | 'updatedAt'> & {
|
||||
id?: string;
|
||||
|
@@ -71,7 +71,7 @@ import MkInput from '@/components/MkInput.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import type { AsUiComponent, AsUiRoot, AsUiPostFormButton } from '@/scripts/aiscript/ui.js';
|
||||
import type { AsUiComponent, AsUiRoot, AsUiPostFormButton } from '@/aiscript/ui.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkPostForm from '@/components/MkPostForm.vue';
|
||||
|
||||
|
@@ -123,8 +123,8 @@ import MkButton from '@/components/MkButton.vue';
|
||||
import { $i, getAccounts, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/account.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
import { getProxiedImageUrl } from '@/scripts/media-proxy.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { getProxiedImageUrl } from '@/utility/media-proxy.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
||||
const props = defineProps<{
|
||||
name?: string;
|
||||
|
@@ -12,7 +12,7 @@ import { userDetailed } from '../../.storybook/fakes.js';
|
||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||
import MkAutocomplete from './MkAutocomplete.vue';
|
||||
import MkInput from './MkInput.vue';
|
||||
import { tick } from '@/scripts/test-utils.js';
|
||||
import { tick } from '@/utility/test-utils.js';
|
||||
const common = {
|
||||
render(args) {
|
||||
return {
|
||||
|
@@ -49,22 +49,23 @@ import sanitizeHtml from 'sanitize-html';
|
||||
import { emojilist, getEmojiName } from '@@/js/emojilist.js';
|
||||
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@@/js/emoji-base.js';
|
||||
import { MFM_TAGS, MFM_PARAMS } from '@@/js/const.js';
|
||||
import type { EmojiDef } from '@/scripts/search-emoji.js';
|
||||
import contains from '@/scripts/contains.js';
|
||||
import type { EmojiDef } from '@/utility/search-emoji.js';
|
||||
import contains from '@/utility/contains.js';
|
||||
import { acct } from '@/filters/user.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { store } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { customEmojis } from '@/custom-emojis.js';
|
||||
import { searchEmoji } from '@/scripts/search-emoji.js';
|
||||
import { searchEmoji } from '@/utility/search-emoji.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const lib = emojilist.filter(x => x.category !== 'flags');
|
||||
|
||||
const emojiDb = computed(() => {
|
||||
//#region Unicode Emoji
|
||||
const char2path = defaultStore.reactiveState.emojiStyle.value === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;
|
||||
const char2path = prefer.r.emojiStyle.value === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;
|
||||
|
||||
const unicodeEmojiDB: EmojiDef[] = lib.map(x => ({
|
||||
emoji: x.char,
|
||||
@@ -72,7 +73,7 @@ const emojiDb = computed(() => {
|
||||
url: char2path(x.char),
|
||||
}));
|
||||
|
||||
for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
|
||||
for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) {
|
||||
for (const [emoji, keywords] of Object.entries(index)) {
|
||||
for (const k of keywords) {
|
||||
unicodeEmojiDB.push({
|
||||
@@ -154,10 +155,10 @@ function complete(type: string, value: any) {
|
||||
emit('done', { type, value });
|
||||
emit('closed');
|
||||
if (type === 'emoji') {
|
||||
let recents = defaultStore.state.recentlyUsedEmojis;
|
||||
let recents = store.s.recentlyUsedEmojis;
|
||||
recents = recents.filter((emoji: any) => emoji !== value);
|
||||
recents.unshift(value);
|
||||
defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32));
|
||||
store.set('recentlyUsedEmojis', recents.splice(0, 32));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +238,7 @@ function exec() {
|
||||
} else if (props.type === 'emoji') {
|
||||
if (!props.q || props.q === '') {
|
||||
// 最近使った絵文字をサジェスト
|
||||
emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.value.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[];
|
||||
emojis.value = store.s.recentlyUsedEmojis.map(emoji => emojiDb.value.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[];
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
userIds: string[];
|
||||
|
@@ -220,28 +220,28 @@ function onMousedown(evt: MouseEvent): void {
|
||||
background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
|
||||
|
||||
&:not(:disabled):hover {
|
||||
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
|
||||
background: linear-gradient(90deg, hsl(from var(--MI_THEME-buttonGradateA) h s calc(l + 5)), hsl(from var(--MI_THEME-buttonGradateB) h s calc(l + 5)));
|
||||
}
|
||||
|
||||
&:not(:disabled):active {
|
||||
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
|
||||
background: linear-gradient(90deg, hsl(from var(--MI_THEME-buttonGradateA) h s calc(l + 5)), hsl(from var(--MI_THEME-buttonGradateB) h s calc(l + 5)));
|
||||
}
|
||||
}
|
||||
|
||||
&.danger {
|
||||
font-weight: bold;
|
||||
color: #ff2a2a;
|
||||
color: var(--MI_THEME-error);
|
||||
|
||||
&.primary {
|
||||
color: #fff;
|
||||
background: #ff2a2a;
|
||||
background: var(--MI_THEME-error);
|
||||
|
||||
&:not(:disabled):hover {
|
||||
background: #ff4242;
|
||||
background: hsl(from var(--MI_THEME-error) h s calc(l + 10));
|
||||
}
|
||||
|
||||
&:not(:disabled):active {
|
||||
background: #d42e2e;
|
||||
background: hsl(from var(--MI_THEME-error) h s calc(l - 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
|
||||
// APIs provided by Captcha services
|
||||
// see: https://docs.hcaptcha.com/configuration/#javascript-api
|
||||
@@ -154,7 +154,7 @@ async function requestRender() {
|
||||
|
||||
captchaWidgetId.value = captcha.value.render(elem, {
|
||||
sitekey: props.sitekey,
|
||||
theme: defaultStore.state.darkMode ? 'dark' : 'light',
|
||||
theme: store.s.darkMode ? 'dark' : 'light',
|
||||
callback: callback,
|
||||
'expired-callback': () => callback(undefined),
|
||||
'error-callback': () => callback(undefined),
|
||||
|
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
|
@@ -53,15 +53,15 @@ export type ChartSrc =
|
||||
import { onMounted, ref, shallowRef, watch } from 'vue';
|
||||
import { Chart } from 'chart.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
||||
import { chartVLine } from '@/scripts/chart-vline.js';
|
||||
import { alpha } from '@/scripts/color.js';
|
||||
import { misskeyApiGet } from '@/utility/misskey-api.js';
|
||||
import { store } from '@/store.js';
|
||||
import { useChartTooltip } from '@/utility/use-chart-tooltip.js';
|
||||
import { chartVLine } from '@/utility/chart-vline.js';
|
||||
import { alpha } from '@/utility/color.js';
|
||||
import date from '@/filters/date.js';
|
||||
import bytes from '@/filters/bytes.js';
|
||||
import { initChart } from '@/scripts/init-chart.js';
|
||||
import { chartLegend } from '@/scripts/chart-legend.js';
|
||||
import { initChart } from '@/utility/init-chart.js';
|
||||
import { chartLegend } from '@/utility/chart-legend.js';
|
||||
import MkChartLegend from '@/components/MkChartLegend.vue';
|
||||
|
||||
initChart();
|
||||
@@ -161,7 +161,7 @@ const render = () => {
|
||||
chartInstance.destroy();
|
||||
}
|
||||
|
||||
const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||
|
||||
const maxes = chartData.series.map((x, i) => Math.max(...x.data.map(d => d.y)));
|
||||
|
||||
|
@@ -23,9 +23,9 @@ import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import * as game from '@/scripts/clicker-game.js';
|
||||
import * as game from '@/utility/clicker-game.js';
|
||||
import number from '@/filters/number.js';
|
||||
import { claimAchievement } from '@/scripts/achievements.js';
|
||||
import { claimAchievement } from '@/utility/achievements.js';
|
||||
|
||||
const saveData = game.saveData;
|
||||
const cookies = computed(() => saveData.value?.cookies);
|
||||
|
@@ -12,8 +12,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { bundledLanguagesInfo } from 'shiki/langs';
|
||||
import type { BundledLanguage } from 'shiki/langs';
|
||||
import { getHighlighter, getTheme } from '@/scripts/code-highlighter.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { getHighlighter, getTheme } from '@/utility/code-highlighter.js';
|
||||
import { store } from '@/store.js';
|
||||
|
||||
const props = defineProps<{
|
||||
code: string;
|
||||
@@ -22,7 +22,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const highlighter = await getHighlighter();
|
||||
const darkMode = defaultStore.reactiveState.darkMode;
|
||||
const darkMode = store.r.darkMode;
|
||||
const codeLang = ref<BundledLanguage | 'aiscript'>('js');
|
||||
|
||||
const [lightThemeName, darkThemeName] = await Promise.all([
|
||||
@@ -74,10 +74,8 @@ watch(() => props.lang, (to) => {
|
||||
<style module lang="scss">
|
||||
.codeBlockRoot :global(.shiki) {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--MI_THEME-divider);
|
||||
font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
|
||||
|
||||
color: var(--shiki-fallback);
|
||||
|
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</button>
|
||||
<Suspense>
|
||||
<template #fallback>
|
||||
<MkLoading />
|
||||
<MkLoading/>
|
||||
</template>
|
||||
<XCode v-if="show && lang" :code="code" :lang="lang"/>
|
||||
<pre v-else-if="show" :class="$style.codeBlockFallbackRoot"><code :class="$style.codeBlockFallbackCode">{{ code }}</code></pre>
|
||||
@@ -28,9 +28,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { defineAsyncComponent, ref } from 'vue';
|
||||
import * as os from '@/os.js';
|
||||
import MkLoading from '@/components/global/MkLoading.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
code: string;
|
||||
@@ -42,7 +42,7 @@ const props = withDefaults(defineProps<{
|
||||
forceShow: false,
|
||||
});
|
||||
|
||||
const show = ref(props.forceShow === true ? true : !defaultStore.state.dataSaver.code);
|
||||
const show = ref(props.forceShow === true ? true : !prefer.s.dataSaver.code);
|
||||
|
||||
const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'));
|
||||
|
||||
|
@@ -19,10 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</header>
|
||||
<Transition
|
||||
:enterActiveClass="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''"
|
||||
:leaveActiveClass="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''"
|
||||
:enterFromClass="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''"
|
||||
:leaveToClass="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''"
|
||||
:enterActiveClass="prefer.s.animation ? $style.transition_toggle_enterActive : ''"
|
||||
:leaveActiveClass="prefer.s.animation ? $style.transition_toggle_leaveActive : ''"
|
||||
:enterFromClass="prefer.s.animation ? $style.transition_toggle_enterFrom : ''"
|
||||
:leaveToClass="prefer.s.animation ? $style.transition_toggle_leaveTo : ''"
|
||||
@enter="enter"
|
||||
@afterEnter="afterEnter"
|
||||
@leave="leave"
|
||||
@@ -40,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
|
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<Transition
|
||||
appear
|
||||
:enterActiveClass="defaultStore.state.animation ? $style.transition_fade_enterActive : ''"
|
||||
:leaveActiveClass="defaultStore.state.animation ? $style.transition_fade_leaveActive : ''"
|
||||
:enterFromClass="defaultStore.state.animation ? $style.transition_fade_enterFrom : ''"
|
||||
:leaveToClass="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''"
|
||||
:enterActiveClass="prefer.s.animation ? $style.transition_fade_enterActive : ''"
|
||||
:leaveActiveClass="prefer.s.animation ? $style.transition_fade_leaveActive : ''"
|
||||
:enterFromClass="prefer.s.animation ? $style.transition_fade_enterFrom : ''"
|
||||
:leaveToClass="prefer.s.animation ? $style.transition_fade_leaveTo : ''"
|
||||
>
|
||||
<div ref="rootEl" :class="$style.root" :style="{ zIndex }" @contextmenu.prevent.stop="() => {}">
|
||||
<MkMenu :items="items" :align="'left'" @close="emit('closed')"/>
|
||||
@@ -21,8 +21,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue';
|
||||
import MkMenu from './MkMenu.vue';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import contains from '@/scripts/contains.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import contains from '@/utility/contains.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
const props = defineProps<{
|
||||
|
@@ -35,13 +35,13 @@ import { onMounted, shallowRef, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import Cropper from 'cropperjs';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { apiUrl } from '@@/js/config.js';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { apiUrl } from '@@/js/config.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { getProxiedImageUrl } from '@/scripts/media-proxy.js';
|
||||
import { getProxiedImageUrl } from '@/utility/media-proxy.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'ok', cropped: Misskey.entities.DriveFile): void;
|
||||
@@ -81,8 +81,8 @@ const ok = async () => {
|
||||
formData.append('i', $i!.token);
|
||||
if (props.uploadFolder) {
|
||||
formData.append('folderId', props.uploadFolder);
|
||||
} else if (props.uploadFolder !== null && defaultStore.state.uploadFolder) {
|
||||
formData.append('folderId', defaultStore.state.uploadFolder);
|
||||
} else if (props.uploadFolder !== null && prefer.s.uploadFolder) {
|
||||
formData.append('folderId', prefer.s.uploadFolder);
|
||||
}
|
||||
|
||||
window.fetch(apiUrl + '/drive/files/create', {
|
||||
|
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { computed } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
|
||||
import { concat } from '@/scripts/array.js';
|
||||
import { concat } from '@/utility/array.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
|
||||
|
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts">
|
||||
import { defineComponent, h, TransitionGroup, useCssModule } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
import type { MisskeyEntity } from '@/types/date-separated-list.js';
|
||||
import MkAd from '@/components/global/MkAd.vue';
|
||||
import { isDebuggerEnabled, stackTraceInstances } from '@/debug.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import type { MisskeyEntity } from '@/types/date-separated-list.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -150,7 +150,7 @@ export default defineComponent({
|
||||
[$style['direction-up']]: props.direction === 'up',
|
||||
};
|
||||
|
||||
return () => defaultStore.state.animation ? h(TransitionGroup, {
|
||||
return () => prefer.s.animation ? h(TransitionGroup, {
|
||||
class: classes,
|
||||
name: 'list',
|
||||
tag: 'div',
|
||||
|
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { defaultIdlingRenderScheduler } from '@/scripts/idle-render.js';
|
||||
import { defaultIdlingRenderScheduler } from '@/utility/idle-render.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
showS?: boolean;
|
||||
|
@@ -45,8 +45,8 @@ import bytes from '@/filters/bytes.js';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { getDriveFileMenu } from '@/scripts/get-drive-file-menu.js';
|
||||
import { deviceKind } from '@/scripts/device-kind.js';
|
||||
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
|
||||
import { deviceKind } from '@/utility/device-kind.js';
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
|
||||
const router = useRouter();
|
||||
|
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template v-if="!hover"><i :class="$style.icon" class="ti ti-folder ti-fw"></i></template>
|
||||
{{ folder.name }}
|
||||
</p>
|
||||
<p v-if="defaultStore.state.uploadFolder == folder.id" :class="$style.upload">
|
||||
<p v-if="prefer.s.uploadFolder == folder.id" :class="$style.upload">
|
||||
{{ i18n.ts.uploadFolder }}
|
||||
</p>
|
||||
<button v-if="selectMode" class="_button" :class="$style.checkboxWrapper" @click.prevent.stop="checkboxClicked">
|
||||
@@ -38,11 +38,11 @@ import { computed, defineAsyncComponent, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { claimAchievement } from '@/scripts/achievements.js';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||
import { claimAchievement } from '@/utility/achievements.js';
|
||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
folder: Misskey.entities.DriveFolder;
|
||||
@@ -244,8 +244,8 @@ function deleteFolder() {
|
||||
misskeyApi('drive/folders/delete', {
|
||||
folderId: props.folder.id,
|
||||
}).then(() => {
|
||||
if (defaultStore.state.uploadFolder === props.folder.id) {
|
||||
defaultStore.set('uploadFolder', null);
|
||||
if (prefer.s.uploadFolder === props.folder.id) {
|
||||
prefer.commit('uploadFolder', null);
|
||||
}
|
||||
}).catch(err => {
|
||||
switch (err.id) {
|
||||
@@ -266,7 +266,7 @@ function deleteFolder() {
|
||||
}
|
||||
|
||||
function setAsUploadFolder() {
|
||||
defaultStore.set('uploadFolder', props.folder.id);
|
||||
prefer.commit('uploadFolder', props.folder.id);
|
||||
}
|
||||
|
||||
function onContextmenu(ev: MouseEvent) {
|
||||
@@ -295,7 +295,7 @@ function onContextmenu(ev: MouseEvent) {
|
||||
danger: true,
|
||||
action: deleteFolder,
|
||||
}];
|
||||
if (defaultStore.state.devMode) {
|
||||
if (prefer.s.devMode) {
|
||||
menu = menu.concat([{ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
text: i18n.ts.copyFolderId,
|
||||
|
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
const props = defineProps<{
|
||||
|
@@ -104,12 +104,12 @@ import XNavFolder from '@/components/MkDrive.navFolder.vue';
|
||||
import XFolder from '@/components/MkDrive.folder.vue';
|
||||
import XFile from '@/components/MkDrive.file.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { useStream } from '@/stream.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { uploadFile, uploads } from '@/scripts/upload.js';
|
||||
import { claimAchievement } from '@/scripts/achievements.js';
|
||||
import { uploadFile, uploads } from '@/utility/upload.js';
|
||||
import { claimAchievement } from '@/utility/achievements.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
initialFolder?: Misskey.entities.DriveFolder;
|
||||
@@ -142,7 +142,7 @@ const selectedFiles = ref<Misskey.entities.DriveFile[]>([]);
|
||||
const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]);
|
||||
const uploadings = uploads;
|
||||
const connection = useStream().useChannel('drive');
|
||||
const keepOriginal = ref<boolean>(defaultStore.state.keepOriginalUploading); // 外部渡しが多いので$refは使わないほうがよい
|
||||
const keepOriginal = ref<boolean>(prefer.s.keepOriginalUploading); // 外部渡しが多いので$refは使わないほうがよい
|
||||
|
||||
// ドロップされようとしているか
|
||||
const draghover = ref(false);
|
||||
@@ -716,7 +716,7 @@ function onContextmenu(ev: MouseEvent) {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (defaultStore.state.enableInfiniteScroll && loadMoreFiles.value) {
|
||||
if (prefer.s.enableInfiniteScroll && loadMoreFiles.value) {
|
||||
nextTick(() => {
|
||||
ilFilesObserver.observe(loadMoreFiles.value?.$el);
|
||||
});
|
||||
@@ -737,7 +737,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
if (defaultStore.state.enableInfiniteScroll) {
|
||||
if (prefer.s.enableInfiniteScroll) {
|
||||
nextTick(() => {
|
||||
ilFilesObserver.observe(loadMoreFiles.value?.$el);
|
||||
});
|
||||
|
@@ -105,8 +105,8 @@ import MkInfo from '@/components/MkInfo.vue';
|
||||
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||
import { normalizeEmbedParams, getEmbedCode } from '@/scripts/get-embed-code.js';
|
||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||
import { normalizeEmbedParams, getEmbedCode } from '@/utility/get-embed-code.js';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'ok'): void;
|
||||
|
@@ -131,17 +131,18 @@ import type {
|
||||
import XSection from '@/components/MkEmojiPicker.section.vue';
|
||||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { isTouchUsing } from '@/scripts/touch.js';
|
||||
import { deviceKind } from '@/scripts/device-kind.js';
|
||||
import { isTouchUsing } from '@/utility/touch.js';
|
||||
import { deviceKind } from '@/utility/device-kind.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
|
||||
import { checkReactionPermissions } from '@/utility/check-reaction-permissions.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
showPinned?: boolean;
|
||||
pinnedEmojis?: string[];
|
||||
pinnedEmojis?: string[];
|
||||
maxHeight?: number;
|
||||
asDrawer?: boolean;
|
||||
asWindow?: boolean;
|
||||
@@ -163,8 +164,9 @@ const {
|
||||
emojiPickerScale,
|
||||
emojiPickerWidth,
|
||||
emojiPickerHeight,
|
||||
recentlyUsedEmojis,
|
||||
} = defaultStore.reactiveState;
|
||||
} = prefer.r;
|
||||
|
||||
const recentlyUsedEmojis = store.r.recentlyUsedEmojis;
|
||||
|
||||
const recentlyUsedEmojisDef = computed(() => {
|
||||
return recentlyUsedEmojis.value.map(getDef);
|
||||
@@ -317,7 +319,7 @@ watch(q, () => {
|
||||
}
|
||||
if (matches.size >= max) return matches;
|
||||
|
||||
for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
|
||||
for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) {
|
||||
for (const emoji of emojis) {
|
||||
if (keywords.every(keyword => index[emoji.char].some(k => k.includes(keyword)))) {
|
||||
matches.add(emoji);
|
||||
@@ -334,7 +336,7 @@ watch(q, () => {
|
||||
}
|
||||
if (matches.size >= max) return matches;
|
||||
|
||||
for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
|
||||
for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) {
|
||||
for (const emoji of emojis) {
|
||||
if (index[emoji.char].some(k => k.startsWith(newQ))) {
|
||||
matches.add(emoji);
|
||||
@@ -351,7 +353,7 @@ watch(q, () => {
|
||||
}
|
||||
if (matches.size >= max) return matches;
|
||||
|
||||
for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
|
||||
for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) {
|
||||
for (const emoji of emojis) {
|
||||
if (index[emoji.char].some(k => k.includes(newQ))) {
|
||||
matches.add(emoji);
|
||||
@@ -413,7 +415,7 @@ function computeButtonTitle(ev: MouseEvent): void {
|
||||
|
||||
function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, ev?: MouseEvent) {
|
||||
const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined;
|
||||
if (el && defaultStore.state.animation) {
|
||||
if (el && prefer.s.animation) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const x = rect.left + (el.offsetWidth / 2);
|
||||
const y = rect.top + (el.offsetHeight / 2);
|
||||
@@ -427,10 +429,10 @@ function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef,
|
||||
|
||||
// 最近使った絵文字更新
|
||||
if (!pinned.value?.includes(key)) {
|
||||
let recents = defaultStore.state.recentlyUsedEmojis;
|
||||
let recents = store.s.recentlyUsedEmojis;
|
||||
recents = recents.filter((emoji) => emoji !== key);
|
||||
recents.unshift(key);
|
||||
defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32));
|
||||
store.set('recentlyUsedEmojis', recents.splice(0, 32));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
ref="modal"
|
||||
v-slot="{ type, maxHeight }"
|
||||
:zPriority="'middle'"
|
||||
:preferType="defaultStore.state.emojiPickerStyle"
|
||||
:preferType="prefer.s.emojiPickerStyle"
|
||||
:hasInteractionWithOtherFocusTrappedEls="true"
|
||||
:transparentBg="true"
|
||||
:manualShowing="manualShowing"
|
||||
@@ -40,16 +40,16 @@ import * as Misskey from 'misskey-js';
|
||||
import { shallowRef } from 'vue';
|
||||
import MkModal from '@/components/MkModal.vue';
|
||||
import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
manualShowing?: boolean | null;
|
||||
src?: HTMLElement;
|
||||
showPinned?: boolean;
|
||||
pinnedEmojis?: string[],
|
||||
pinnedEmojis?: string[],
|
||||
asReactionPicker?: boolean;
|
||||
targetNote?: Misskey.entities.Note;
|
||||
choseAndClose?: boolean;
|
||||
choseAndClose?: boolean;
|
||||
}>(), {
|
||||
manualShowing: null,
|
||||
showPinned: true,
|
||||
|
43
packages/frontend/src/components/MkFeatureBanner.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div v-panel :class="$style.root">
|
||||
<img :class="$style.img" :src="icon"/>
|
||||
<div :class="$style.text">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{
|
||||
icon: string;
|
||||
color: string;
|
||||
}>(), {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
.root {
|
||||
padding: 20px 24px;
|
||||
text-align: center;
|
||||
border-radius: var(--MI-radius);
|
||||
background: linear-gradient(180deg, color(from v-bind(color) srgb r g b / 0.1), color(from v-bind(color) srgb r g b / 0));
|
||||
}
|
||||
|
||||
.img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 40px;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-top: 12px;
|
||||
font-size: 85%;
|
||||
mix-blend-mode: luminosity;
|
||||
}
|
||||
</style>
|
@@ -14,10 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</button>
|
||||
</header>
|
||||
<Transition
|
||||
:enterActiveClass="defaultStore.state.animation ? $style.folderToggleEnterActive : ''"
|
||||
:leaveActiveClass="defaultStore.state.animation ? $style.folderToggleLeaveActive : ''"
|
||||
:enterFromClass="defaultStore.state.animation ? $style.folderToggleEnterFrom : ''"
|
||||
:leaveToClass="defaultStore.state.animation ? $style.folderToggleLeaveTo : ''"
|
||||
:enterActiveClass="prefer.s.animation ? $style.folderToggleEnterActive : ''"
|
||||
:leaveActiveClass="prefer.s.animation ? $style.folderToggleLeaveActive : ''"
|
||||
:enterFromClass="prefer.s.animation ? $style.folderToggleEnterFrom : ''"
|
||||
:leaveToClass="prefer.s.animation ? $style.folderToggleLeaveTo : ''"
|
||||
@enter="enter"
|
||||
@afterEnter="afterEnter"
|
||||
@leave="leave"
|
||||
@@ -33,8 +33,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, shallowRef, watch } from 'vue';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { getBgColor } from '@/scripts/get-bg-color.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { getBgColor } from '@/utility/get-bg-color.js';
|
||||
|
||||
const miLocalStoragePrefix = 'ui:folder:' as const;
|
||||
|
||||
|
@@ -27,10 +27,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : undefined, overflow: maxHeight ? `auto` : undefined }" :aria-hidden="!opened">
|
||||
<Transition
|
||||
:enterActiveClass="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''"
|
||||
:leaveActiveClass="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''"
|
||||
:enterFromClass="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''"
|
||||
:leaveToClass="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''"
|
||||
:enterActiveClass="prefer.s.animation ? $style.transition_toggle_enterActive : ''"
|
||||
:leaveActiveClass="prefer.s.animation ? $style.transition_toggle_leaveActive : ''"
|
||||
:enterFromClass="prefer.s.animation ? $style.transition_toggle_enterFrom : ''"
|
||||
:leaveToClass="prefer.s.animation ? $style.transition_toggle_leaveTo : ''"
|
||||
@enter="enter"
|
||||
@afterEnter="afterEnter"
|
||||
@leave="leave"
|
||||
@@ -57,8 +57,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, ref, shallowRef } from 'vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { getBgColor } from '@/scripts/get-bg-color.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { getBgColor } from '@/utility/get-bg-color.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
defaultOpen?: boolean;
|
||||
|
@@ -39,13 +39,14 @@ import { onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { host } from '@@/js/config.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { useStream } from '@/stream.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { claimAchievement } from '@/scripts/achievements.js';
|
||||
import { pleaseLogin } from '@/scripts/please-login.js';
|
||||
import { claimAchievement } from '@/utility/achievements.js';
|
||||
import { pleaseLogin } from '@/utility/please-login.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { store } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
user: Misskey.entities.UserDetailed,
|
||||
@@ -100,7 +101,7 @@ async function onClick() {
|
||||
userId: props.user.id,
|
||||
});
|
||||
} else {
|
||||
if (defaultStore.state.alwaysConfirmFollow) {
|
||||
if (prefer.s.alwaysConfirmFollow) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'question',
|
||||
text: i18n.tsx.followConfirm({ name: props.user.name || props.user.username }),
|
||||
@@ -120,11 +121,11 @@ async function onClick() {
|
||||
} else {
|
||||
await misskeyApi('following/create', {
|
||||
userId: props.user.id,
|
||||
withReplies: defaultStore.state.defaultWithReplies,
|
||||
withReplies: store.s.defaultWithReplies,
|
||||
});
|
||||
emit('update:user', {
|
||||
...props.user,
|
||||
withReplies: defaultStore.state.defaultWithReplies,
|
||||
withReplies: store.s.defaultWithReplies,
|
||||
});
|
||||
hasPendingFollowRequestFromYou.value = true;
|
||||
|
||||
|
@@ -15,8 +15,8 @@ import * as Misskey from 'misskey-js';
|
||||
import { computed, ref } from 'vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { selectFile } from '@/scripts/select-file.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { selectFile } from '@/utility/select-file.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
||||
const props = defineProps<{
|
||||
fileId?: string | null;
|
||||
|
@@ -80,7 +80,7 @@ import MkRange from './MkRange.vue';
|
||||
import MkButton from './MkButton.vue';
|
||||
import MkRadios from './MkRadios.vue';
|
||||
import XFile from './MkFormDialog.file.vue';
|
||||
import type { Form } from '@/scripts/form.js';
|
||||
import type { Form } from '@/utility/form.js';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
|
@@ -35,14 +35,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { computed, ref } from 'vue';
|
||||
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = defineProps<{
|
||||
post: Misskey.entities.GalleryPost;
|
||||
}>();
|
||||
|
||||
const hover = ref(false);
|
||||
const safe = computed(() => defaultStore.state.nsfw === 'ignore' || defaultStore.state.nsfw === 'respect' && !props.post.isSensitive);
|
||||
const safe = computed(() => prefer.s.nsfw === 'ignore' || prefer.s.nsfw === 'respect' && !props.post.isSensitive);
|
||||
const show = computed(() => safe.value || hover.value);
|
||||
|
||||
function enterHover(): void {
|
||||
|
@@ -16,11 +16,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { onMounted, nextTick, watch, shallowRef, ref } from 'vue';
|
||||
import { Chart } from 'chart.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
||||
import { alpha } from '@/scripts/color.js';
|
||||
import { initChart } from '@/scripts/init-chart.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { store } from '@/store.js';
|
||||
import { useChartTooltip } from '@/utility/use-chart-tooltip.js';
|
||||
import { alpha } from '@/utility/color.js';
|
||||
import { initChart } from '@/utility/init-chart.js';
|
||||
|
||||
initChart();
|
||||
|
||||
@@ -106,7 +106,7 @@ async function renderChart() {
|
||||
|
||||
await nextTick();
|
||||
|
||||
const color = defaultStore.state.darkMode ? '#b4e900' : '#86b300';
|
||||
const color = store.s.darkMode ? '#b4e900' : '#86b300';
|
||||
|
||||
// 視覚上の分かりやすさのため上から最も大きい3つの値の平均を最大値とする
|
||||
const max = values.slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3;
|
||||
|
@@ -28,12 +28,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { ref, shallowRef, computed, nextTick, watch } from 'vue';
|
||||
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { isHorizontalSwipeSwiping as isSwiping } from '@/scripts/touch.js';
|
||||
import { isHorizontalSwipeSwiping as isSwiping } from '@/utility/touch.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const rootEl = shallowRef<HTMLDivElement>();
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const tabModel = defineModel<string>('tab');
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -44,7 +43,7 @@ const emit = defineEmits<{
|
||||
(ev: 'swiped', newKey: string, direction: 'left' | 'right'): void;
|
||||
}>();
|
||||
|
||||
const shouldAnimate = computed(() => defaultStore.reactiveState.enableHorizontalSwipe.value || defaultStore.reactiveState.animation.value);
|
||||
const shouldAnimate = computed(() => prefer.r.enableHorizontalSwipe.value || prefer.r.animation.value);
|
||||
|
||||
// ▼ しきい値 ▼ //
|
||||
|
||||
@@ -72,7 +71,7 @@ const isSwipingForClass = ref(false);
|
||||
let swipeAborted = false;
|
||||
|
||||
function touchStart(event: TouchEvent) {
|
||||
if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
|
||||
if (!prefer.r.enableHorizontalSwipe.value) return;
|
||||
|
||||
if (event.touches.length !== 1) return;
|
||||
|
||||
@@ -83,7 +82,7 @@ function touchStart(event: TouchEvent) {
|
||||
}
|
||||
|
||||
function touchMove(event: TouchEvent) {
|
||||
if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
|
||||
if (!prefer.r.enableHorizontalSwipe.value) return;
|
||||
|
||||
if (event.touches.length !== 1) return;
|
||||
|
||||
@@ -134,7 +133,7 @@ function touchEnd(event: TouchEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
|
||||
if (!prefer.r.enableHorizontalSwipe.value) return;
|
||||
|
||||
if (event.touches.length !== 0) return;
|
||||
|
||||
|
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<div ref="root" :class="['chromatic-ignore', $style.root, { [$style.cover]: cover }]" :title="title ?? ''">
|
||||
<TransitionGroup
|
||||
:duration="defaultStore.state.animation && props.transition?.duration || undefined"
|
||||
:enterActiveClass="defaultStore.state.animation && props.transition?.enterActiveClass || undefined"
|
||||
:leaveActiveClass="defaultStore.state.animation && (props.transition?.leaveActiveClass ?? $style.transition_leaveActive) || undefined"
|
||||
:enterFromClass="defaultStore.state.animation && props.transition?.enterFromClass || undefined"
|
||||
:leaveToClass="defaultStore.state.animation && props.transition?.leaveToClass || undefined"
|
||||
:enterToClass="defaultStore.state.animation && props.transition?.enterToClass || undefined"
|
||||
:leaveFromClass="defaultStore.state.animation && props.transition?.leaveFromClass || undefined"
|
||||
:duration="prefer.s.animation && props.transition?.duration || undefined"
|
||||
:enterActiveClass="prefer.s.animation && props.transition?.enterActiveClass || undefined"
|
||||
:leaveActiveClass="prefer.s.animation && (props.transition?.leaveActiveClass ?? $style.transition_leaveActive) || undefined"
|
||||
:enterFromClass="prefer.s.animation && props.transition?.enterFromClass || undefined"
|
||||
:leaveToClass="prefer.s.animation && props.transition?.leaveToClass || undefined"
|
||||
:enterToClass="prefer.s.animation && props.transition?.enterToClass || undefined"
|
||||
:leaveFromClass="prefer.s.animation && props.transition?.leaveFromClass || undefined"
|
||||
>
|
||||
<canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined" tabindex="-1"/>
|
||||
<img v-show="!hide" key="img" ref="img" :height="imgHeight ?? undefined" :width="imgWidth ?? undefined" :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined" loading="eager" decoding="async" tabindex="-1"/>
|
||||
@@ -60,7 +60,7 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
|
||||
import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch, ref } from 'vue';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { render } from 'buraha';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
transition?: {
|
||||
|
@@ -50,8 +50,8 @@ import { debounce } from 'throttle-debounce';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { Autocomplete } from '@/scripts/autocomplete.js';
|
||||
import type { SuggestionType } from '@/scripts/autocomplete.js';
|
||||
import { Autocomplete } from '@/utility/autocomplete.js';
|
||||
import type { SuggestionType } from '@/utility/autocomplete.js';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string | number | null;
|
||||
|
@@ -18,8 +18,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkMiniChart from '@/components/MkMiniChart.vue';
|
||||
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
|
||||
import { misskeyApiGet } from '@/utility/misskey-api.js';
|
||||
import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
|
||||
|
||||
const props = defineProps<{
|
||||
instance: Misskey.entities.FederationInstance;
|
||||
|
@@ -88,10 +88,10 @@ import { onMounted, ref, computed, shallowRef } from 'vue';
|
||||
import { Chart } from 'chart.js';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkChart from '@/components/MkChart.vue';
|
||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
||||
import { useChartTooltip } from '@/utility/use-chart-tooltip.js';
|
||||
import { $i } from '@/account.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||
import { misskeyApiGet } from '@/utility/misskey-api.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkHeatmap from '@/components/MkHeatmap.vue';
|
||||
@@ -99,7 +99,7 @@ import type { HeatmapSource } from '@/components/MkHeatmap.vue';
|
||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||
import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
|
||||
import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue';
|
||||
import { initChart } from '@/scripts/init-chart.js';
|
||||
import { initChart } from '@/utility/init-chart.js';
|
||||
|
||||
initChart();
|
||||
|
||||
|
@@ -15,7 +15,7 @@ import { computed } from 'vue';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { instanceName as localInstanceName } from '@@/js/config.js';
|
||||
import { instance as localInstance } from '@/instance.js';
|
||||
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
|
||||
import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
|
||||
|
||||
const props = defineProps<{
|
||||
host: string | null;
|
||||
|
@@ -64,7 +64,7 @@ import { computed } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
|
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
|
@@ -30,8 +30,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { shallowRef } from 'vue';
|
||||
import MkModal from '@/components/MkModal.vue';
|
||||
import { navbarItemDef } from '@/navbar.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { deviceKind } from '@/scripts/device-kind.js';
|
||||
import { deviceKind } from '@/utility/device-kind.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
src?: HTMLElement;
|
||||
@@ -50,7 +50,7 @@ const preferedModalType = (deviceKind === 'desktop' && props.src != null) ? 'pop
|
||||
|
||||
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||
|
||||
const menu = defaultStore.state.menu;
|
||||
const menu = prefer.s.menu;
|
||||
|
||||
const items = Object.keys(navbarItemDef).filter(k => !menu.includes(k)).map(k => navbarItemDef[k]).filter(def => def.show == null ? true : def.show).map(def => ({
|
||||
type: def.to ? 'link' : 'button',
|
||||
|
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, ref } from 'vue';
|
||||
import { url as local } from '@@/js/config.js';
|
||||
import { useTooltip } from '@/scripts/use-tooltip.js';
|
||||
import { useTooltip } from '@/utility/use-tooltip.js';
|
||||
import * as os from '@/os.js';
|
||||
import { isEnabledUrlPreview } from '@/instance.js';
|
||||
import type { MkABehavior } from '@/components/global/MkA.vue';
|
||||
|
@@ -10,20 +10,20 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
tabindex="0"
|
||||
:class="[
|
||||
$style.audioContainer,
|
||||
(audio.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive,
|
||||
(audio.isSensitive && prefer.s.highlightSensitiveMedia) && $style.sensitive,
|
||||
]"
|
||||
@contextmenu.stop
|
||||
@keydown.stop
|
||||
>
|
||||
<button v-if="hide" :class="$style.hidden" @click="show">
|
||||
<div :class="$style.hiddenTextWrapper">
|
||||
<b v-if="audio.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.audio}${audio.size ? ' ' + bytes(audio.size) : ''})` : '' }}</b>
|
||||
<b v-else style="display: block;"><i class="ti ti-music"></i> {{ defaultStore.state.dataSaver.media && audio.size ? bytes(audio.size) : i18n.ts.audio }}</b>
|
||||
<b v-if="audio.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ prefer.s.dataSaver.media ? ` (${i18n.ts.audio}${audio.size ? ' ' + bytes(audio.size) : ''})` : '' }}</b>
|
||||
<b v-else style="display: block;"><i class="ti ti-music"></i> {{ prefer.s.dataSaver.media && audio.size ? bytes(audio.size) : i18n.ts.audio }}</b>
|
||||
<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div v-else-if="defaultStore.reactiveState.useNativeUIForVideoAudioPlayer.value" :class="$style.nativeAudioContainer">
|
||||
<div v-else-if="prefer.s.useNativeUiForVideoAudioPlayer" :class="$style.nativeAudioContainer">
|
||||
<audio
|
||||
ref="audioEl"
|
||||
preload="metadata"
|
||||
@@ -91,15 +91,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { shallowRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import type { Keymap } from '@/scripts/hotkey.js';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import type { Keymap } from '@/utility/hotkey.js';
|
||||
import { copyToClipboard } from '@/utility/copy-to-clipboard';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
import bytes from '@/filters/bytes.js';
|
||||
import { hms } from '@/filters/hms.js';
|
||||
import MkMediaRange from '@/components/MkMediaRange.vue';
|
||||
import { $i, iAmModerator } from '@/account.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = defineProps<{
|
||||
audio: Misskey.entities.DriveFile;
|
||||
@@ -155,10 +155,10 @@ const playerEl = shallowRef<HTMLDivElement>();
|
||||
const audioEl = shallowRef<HTMLAudioElement>();
|
||||
|
||||
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
||||
const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.audio.isSensitive && defaultStore.state.nsfw !== 'ignore'));
|
||||
const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.audio.isSensitive && prefer.s.nsfw !== 'ignore'));
|
||||
|
||||
async function show() {
|
||||
if (props.audio.isSensitive && defaultStore.state.confirmWhenRevealingSensitiveMedia) {
|
||||
if (props.audio.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'question',
|
||||
text: i18n.ts.sensitiveMediaRevealConfirm,
|
||||
@@ -240,7 +240,7 @@ function showMenu(ev: MouseEvent) {
|
||||
menu.push({ type: 'divider' }, ...details);
|
||||
}
|
||||
|
||||
if (defaultStore.state.devMode) {
|
||||
if (prefer.s.devMode) {
|
||||
menu.push({ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
text: i18n.ts.copyFileId,
|
||||
@@ -407,7 +407,7 @@ onDeactivated(() => {
|
||||
elapsedTimeMs.value = 0;
|
||||
durationMs.value = 0;
|
||||
bufferedEnd.value = 0;
|
||||
hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.audio.isSensitive && defaultStore.state.nsfw !== 'ignore');
|
||||
hide.value = (prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.audio.isSensitive && prefer.s.nsfw !== 'ignore');
|
||||
stopAudioElWatch();
|
||||
onceInit = false;
|
||||
if (mediaTickFrameId) {
|
||||
|
@@ -27,9 +27,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import * as os from '@/os.js';
|
||||
import MkMediaAudio from '@/components/MkMediaAudio.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = defineProps<{
|
||||
media: Misskey.entities.DriveFile;
|
||||
@@ -38,7 +38,7 @@ const props = defineProps<{
|
||||
const hide = ref(true);
|
||||
|
||||
async function show() {
|
||||
if (props.media.isSensitive && defaultStore.state.confirmWhenRevealingSensitiveMedia) {
|
||||
if (props.media.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'question',
|
||||
text: i18n.ts.sensitiveMediaRevealConfirm,
|
||||
|
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive]" @click="onclick">
|
||||
<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && prefer.s.highlightSensitiveMedia) && $style.sensitive]" @click="onclick">
|
||||
<component
|
||||
:is="disableImageLink ? 'div' : 'a'"
|
||||
v-bind="disableImageLink ? {
|
||||
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
>
|
||||
<ImgWithBlurhash
|
||||
:hash="image.blurhash"
|
||||
:src="(defaultStore.state.dataSaver.media && hide) ? null : url"
|
||||
:src="(prefer.s.dataSaver.media && hide) ? null : url"
|
||||
:forceBlurhash="hide"
|
||||
:cover="hide || cover"
|
||||
:alt="image.comment || image.name"
|
||||
@@ -32,8 +32,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template v-if="hide">
|
||||
<div :class="$style.hiddenText">
|
||||
<div :class="$style.hiddenTextWrapper">
|
||||
<b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
|
||||
<b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.dataSaver.media && image.size ? bytes(image.size) : i18n.ts.image }}</b>
|
||||
<b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ prefer.s.dataSaver.media ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
|
||||
<b v-else style="display: block;"><i class="ti ti-photo"></i> {{ prefer.s.dataSaver.media && image.size ? bytes(image.size) : i18n.ts.image }}</b>
|
||||
<span v-if="controls" style="display: block;">{{ i18n.ts.clickToShow }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,14 +54,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { watch, ref, computed } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard';
|
||||
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
|
||||
import { copyToClipboard } from '@/utility/copy-to-clipboard';
|
||||
import { getStaticImageUrl } from '@/utility/media-proxy.js';
|
||||
import bytes from '@/filters/bytes.js';
|
||||
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
import { $i, iAmModerator } from '@/account.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
image: Misskey.entities.DriveFile;
|
||||
@@ -77,9 +77,9 @@ const props = withDefaults(defineProps<{
|
||||
|
||||
const hide = ref(true);
|
||||
|
||||
const url = computed(() => (props.raw || defaultStore.state.loadRawImages)
|
||||
const url = computed(() => (props.raw || prefer.s.loadRawImages)
|
||||
? props.image.url
|
||||
: defaultStore.state.disableShowingAnimatedImages
|
||||
: prefer.s.disableShowingAnimatedImages
|
||||
? getStaticImageUrl(props.image.url)
|
||||
: props.image.thumbnailUrl,
|
||||
);
|
||||
@@ -91,7 +91,7 @@ async function onclick(ev: MouseEvent) {
|
||||
|
||||
if (hide.value) {
|
||||
ev.stopPropagation();
|
||||
if (props.image.isSensitive && defaultStore.state.confirmWhenRevealingSensitiveMedia) {
|
||||
if (props.image.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'question',
|
||||
text: i18n.ts.sensitiveMediaRevealConfirm,
|
||||
@@ -105,7 +105,7 @@ async function onclick(ev: MouseEvent) {
|
||||
|
||||
// Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする
|
||||
watch(() => props.image, () => {
|
||||
hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore');
|
||||
hide.value = (prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.image.isSensitive && prefer.s.nsfw !== 'ignore');
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
@@ -166,7 +166,7 @@ function showMenu(ev: MouseEvent) {
|
||||
menuItems.push({ type: 'divider' }, ...details);
|
||||
}
|
||||
|
||||
if (defaultStore.state.devMode) {
|
||||
if (prefer.s.devMode) {
|
||||
menuItems.push({ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
text: i18n.ts.copyFileId,
|
||||
|