Merge branch 'develop' into feature/default-post-target-detect-from-path

This commit is contained in:
果物リン 2023-12-17 17:54:37 +09:00 committed by GitHub
commit f680469877
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1466 additions and 731 deletions

View File

@ -54,7 +54,7 @@ jobs:
- name: Copy API.json - name: Copy API.json
run: cp packages/backend/built/api.json ${{ matrix.api-json-name }} run: cp packages/backend/built/api.json ${{ matrix.api-json-name }}
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: api-artifact name: api-artifact
path: ${{ matrix.api-json-name }} path: ${{ matrix.api-json-name }}
@ -67,7 +67,7 @@ jobs:
PR_NUMBER: ${{ github.event.number }} PR_NUMBER: ${{ github.event.number }}
run: | run: |
echo "$PR_NUMBER" > ./pr_number echo "$PR_NUMBER" > ./pr_number
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: api-artifact name: api-artifact
path: pr_number path: pr_number

View File

@ -56,7 +56,7 @@ jobs:
- name: Echo full diff - name: Echo full diff
run: cat ./api-full.json.diff run: cat ./api-full.json.diff
- name: Upload full diff to Artifact - name: Upload full diff to Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: api-artifact name: api-artifact
path: | path: |

View File

@ -108,12 +108,12 @@ jobs:
wait-on: 'http://localhost:61812' wait-on: 'http://localhost:61812'
headed: true headed: true
browser: ${{ matrix.browser }} browser: ${{ matrix.browser }}
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v4
if: failure() if: failure()
with: with:
name: ${{ matrix.browser }}-cypress-screenshots name: ${{ matrix.browser }}-cypress-screenshots
path: cypress/screenshots path: cypress/screenshots
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v4
if: always() if: always()
with: with:
name: ${{ matrix.browser }}-cypress-videos name: ${{ matrix.browser }}-cypress-videos

View File

@ -16,6 +16,7 @@
## 2023.x.x (unreleased) ## 2023.x.x (unreleased)
### Note ### Note
- Node.js 20.10.0が最小要件になりました
- 絵文字ピッカーにピン留め表示する絵文字設定が「リアクション用」と「絵文字入力用」に分かれました。以前の設定は「リアクション用」として使用されます。 - 絵文字ピッカーにピン留め表示する絵文字設定が「リアクション用」と「絵文字入力用」に分かれました。以前の設定は「リアクション用」として使用されます。
**影響:** **影響:**
@ -36,8 +37,10 @@
### Client ### Client
- Feat: 今日誕生日のフォロー中のユーザーを一覧表示できるウィジェットを追加 - Feat: 今日誕生日のフォロー中のユーザーを一覧表示できるウィジェットを追加
- Feat: データセーバーでコードハイライトの読み込みを削減できるように - Feat: 画面に雪を降らせられるように
- Feat: MFMのアニメーション要素`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)に `delay` オプションを追加 - Enhance: MFMのアニメーション要素`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)に `delay` オプションを追加
- Enhance: センシティブと判断されたウェブサイトのサムネイルを非表示に
- ウェブサイトをセンシティブと判断する仕組みが動いていないため、summalyProxyを使用しないと機能しません。
- Enhance: 投稿フォームの絵文字ピッカーをリアクション時に使用するものと同じのを使用するように #12336 #12560 - Enhance: 投稿フォームの絵文字ピッカーをリアクション時に使用するものと同じのを使用するように #12336 #12560
- Enhance: リアクション用ピン留め絵文字と投稿時の絵文字入力用ピン留め絵文字を分けて設定できるように #12560 - Enhance: リアクション用ピン留め絵文字と投稿時の絵文字入力用ピン留め絵文字を分けて設定できるように #12560
- Enhance: 絵文字のオートコンプリート機能強化 #12364 - Enhance: 絵文字のオートコンプリート機能強化 #12364
@ -48,16 +51,19 @@
- Enhance: Shareページで投稿を完了すると、親ウィンドウ親フレームにpostMessageするように - Enhance: Shareページで投稿を完了すると、親ウィンドウ親フレームにpostMessageするように
- Enhance: チャンネル、クリップ、ページ、Play、ギャラリーにURLのコピーボタンを設置 #11305 - Enhance: チャンネル、クリップ、ページ、Play、ギャラリーにURLのコピーボタンを設置 #11305
- Enhance: ノートプレビューに「内容を隠す」が反映されるように - Enhance: ノートプレビューに「内容を隠す」が反映されるように
- Enhance: データセーバーでコードハイライトの読み込みを削減できるように
- Enhance: データセーバーの適用範囲を個別で設定できるように - Enhance: データセーバーの適用範囲を個別で設定できるように
- 従来のデータセーバーの設定はリセットされます - 従来のデータセーバーの設定はリセットされます
- Enhance: タイムライン上のタブからリスト、アンテナ、チャンネルの管理ページにジャンプできるように - Enhance: タイムライン上のタブからリスト、アンテナ、チャンネルの管理ページにジャンプできるように
- Enhance: ユーザー名、プロフィール、お知らせ、ページの編集画面でMFMや絵文字のオートコンプリートが使用できるように - Enhance: ユーザー名、プロフィール、お知らせ、ページの編集画面でMFMや絵文字のオートコンプリートが使用できるように
- Enhance: プロフィール、お知らせの編集画面でMFMのプレビューを表示できるように - Enhance: プロフィール、お知らせの編集画面でMFMのプレビューを表示できるように
- Feat: センシティブと判断されたウェブサイトのサムネイルをぼかすように
- ウェブサイトをセンシティブと判断する仕組みが動いていないため、summalyProxyを使用しないと機能しません。
- fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
- Enhance: 絵文字の詳細ページに記載される情報を追加 - Enhance: 絵文字の詳細ページに記載される情報を追加
- Enhance: コードブロックのハイライト機能を利用するには言語を明示的に指定させるように
- MFMでコードブロックを利用する際に意図しないハイライトが起こらないようになりました
- 逆に、MFMでコードハイライトを利用したい際は言語を明示的に指定する必要があります
(例: ` ```js ` → Javascript, ` ```ais ` → AiScript
- Fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
- Fix: コードエディタが正しく表示されない問題を修正 - Fix: コードエディタが正しく表示されない問題を修正
- Fix: プロフィールの「ファイル」にセンシティブな画像がある際のデザインを修正 - Fix: プロフィールの「ファイル」にセンシティブな画像がある際のデザインを修正
- Fix: 一度に大量の通知が入った際に通知音が音割れする問題を修正 - Fix: 一度に大量の通知が入った際に通知音が音割れする問題を修正
@ -67,6 +73,7 @@
- Fix: セキュリティ向上のためAiScriptの`Mk:apiExternal`を無効化 - Fix: セキュリティ向上のためAiScriptの`Mk:apiExternal`を無効化
- Fix: ノート中の絵文字をタップして「リアクションする」からリアクションした際にリアクションサウンドが鳴らない不具合を修正 - Fix: ノート中の絵文字をタップして「リアクションする」からリアクションした際にリアクションサウンドが鳴らない不具合を修正
- Fix: ノート中のリアクションの表示を微調整 #12650 - Fix: ノート中のリアクションの表示を微調整 #12650
- Fix: AiScriptの`readline`が不正な値を返すことがある問題を修正
### Server ### Server
- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように - Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように
@ -85,6 +92,7 @@
- Fix: 「みつける」が年越し時に壊れる問題を修正 - Fix: 「みつける」が年越し時に壊れる問題を修正
- Fix: アカウントをブロックした際に、自身のユーザーのページでノートが相手に表示される問題を修正 - Fix: アカウントをブロックした際に、自身のユーザーのページでノートが相手に表示される問題を修正
- Fix: モデレーションログがモデレーターは閲覧できないように修正 - Fix: モデレーションログがモデレーターは閲覧できないように修正
- Fix: HTTP Digestヘッダのアルゴリズム部分に大文字の"SHA-256"しか使えない
## 2023.11.1 ## 2023.11.1

6
locales/index.d.ts vendored
View File

@ -128,8 +128,8 @@ export interface Locale {
"pinnedEmojisForReactionSettingDescription": string; "pinnedEmojisForReactionSettingDescription": string;
"pinnedEmojisSettingDescription": string; "pinnedEmojisSettingDescription": string;
"emojiPickerDisplay": string; "emojiPickerDisplay": string;
"copyFromPinnedEmojisForReaction": string; "overwriteFromPinnedEmojisForReaction": string;
"copyFromPinnedEmojis": string; "overwriteFromPinnedEmojis": string;
"reactionSettingDescription2": string; "reactionSettingDescription2": string;
"rememberNoteVisibility": string; "rememberNoteVisibility": string;
"attachCancel": string; "attachCancel": string;
@ -1182,6 +1182,8 @@ export interface Locale {
"reloadRequiredToApplySettings": string; "reloadRequiredToApplySettings": string;
"remainingN": string; "remainingN": string;
"overwriteContentConfirm": string; "overwriteContentConfirm": string;
"seasonalScreenEffect": string;
"decorate": string;
"_announcement": { "_announcement": {
"forExistingUsers": string; "forExistingUsers": string;
"forExistingUsersDescription": string; "forExistingUsersDescription": string;

View File

@ -125,8 +125,8 @@ emojiPicker: "絵文字ピッカー"
pinnedEmojisForReactionSettingDescription: "リアクション時にピン留め表示する絵文字を設定できます" pinnedEmojisForReactionSettingDescription: "リアクション時にピン留め表示する絵文字を設定できます"
pinnedEmojisSettingDescription: "絵文字入力時にピン留め表示する絵文字を設定できます" pinnedEmojisSettingDescription: "絵文字入力時にピン留め表示する絵文字を設定できます"
emojiPickerDisplay: "ピッカーの表示" emojiPickerDisplay: "ピッカーの表示"
copyFromPinnedEmojisForReaction: "リアクション設定からコピーする" overwriteFromPinnedEmojisForReaction: "リアクション設定から上書きする"
copyFromPinnedEmojis: "絵文字設定からコピーする" overwriteFromPinnedEmojis: "全般設定から上書きする"
reactionSettingDescription2: "ドラッグして並び替え、クリックして削除、+を押して追加します。" reactionSettingDescription2: "ドラッグして並び替え、クリックして削除、+を押して追加します。"
rememberNoteVisibility: "公開範囲を記憶する" rememberNoteVisibility: "公開範囲を記憶する"
attachCancel: "添付取り消し" attachCancel: "添付取り消し"
@ -1179,6 +1179,8 @@ code: "コード"
reloadRequiredToApplySettings: "設定の反映にはリロードが必要です。" reloadRequiredToApplySettings: "設定の反映にはリロードが必要です。"
remainingN: "残り: {n}" remainingN: "残り: {n}"
overwriteContentConfirm: "現在の内容に上書きされますがよろしいですか?" overwriteContentConfirm: "現在の内容に上書きされますがよろしいですか?"
seasonalScreenEffect: "季節に応じた画面の演出"
decorate: "デコる"
_announcement: _announcement:
forExistingUsers: "既存ユーザーのみ" forExistingUsers: "既存ユーザーのみ"

View File

@ -1,12 +1,12 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2023.12.0-beta.4", "version": "2023.12.0-beta.5",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/misskey-dev/misskey.git" "url": "https://github.com/misskey-dev/misskey.git"
}, },
"packageManager": "pnpm@8.10.5", "packageManager": "pnpm@8.12.1",
"workspaces": [ "workspaces": [
"packages/frontend", "packages/frontend",
"packages/backend", "packages/backend",
@ -46,10 +46,10 @@
}, },
"dependencies": { "dependencies": {
"execa": "8.0.1", "execa": "8.0.1",
"cssnano": "6.0.1", "cssnano": "6.0.2",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"postcss": "8.4.32", "postcss": "8.4.32",
"terser": "5.24.0", "terser": "5.26.0",
"typescript": "5.3.3" "typescript": "5.3.3"
}, },
"devDependencies": { "devDependencies": {
@ -57,7 +57,7 @@
"@typescript-eslint/parser": "6.14.0", "@typescript-eslint/parser": "6.14.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "13.6.1", "cypress": "13.6.1",
"eslint": "8.55.0", "eslint": "8.56.0",
"start-server-and-test": "2.0.3", "start-server-and-test": "2.0.3",
"ncp": "2.0.0" "ncp": "2.0.0"
}, },

View File

@ -4,7 +4,7 @@
"private": true, "private": true,
"type": "module", "type": "module",
"engines": { "engines": {
"node": ">=18.16.0" "node": ">=20.10.0"
}, },
"scripts": { "scripts": {
"start": "node ./built/boot/entry.js", "start": "node ./built/boot/entry.js",
@ -90,7 +90,7 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"body-parser": "1.20.2", "body-parser": "1.20.2",
"bullmq": "4.15.3", "bullmq": "4.15.4",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"cbor": "9.0.1", "cbor": "9.0.1",
"chalk": "5.3.0", "chalk": "5.3.0",
@ -107,7 +107,7 @@
"file-type": "18.7.0", "file-type": "18.7.0",
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"form-data": "4.0.0", "form-data": "4.0.0",
"got": "13.0.0", "got": "14.0.0",
"happy-dom": "10.0.3", "happy-dom": "10.0.3",
"hpagent": "1.2.0", "hpagent": "1.2.0",
"http-link-header": "1.1.1", "http-link-header": "1.1.1",
@ -222,8 +222,8 @@
"@typescript-eslint/parser": "6.14.0", "@typescript-eslint/parser": "6.14.0",
"aws-sdk-client-mock": "3.0.0", "aws-sdk-client-mock": "3.0.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "8.55.0", "eslint": "8.56.0",
"eslint-plugin-import": "2.29.0", "eslint-plugin-import": "2.29.1",
"execa": "8.0.1", "execa": "8.0.1",
"jest": "29.7.0", "jest": "29.7.0",
"jest-mock": "29.7.0", "jest-mock": "29.7.0",

View File

@ -74,6 +74,14 @@ export const packedUserLiteSchema = {
format: 'url', format: 'url',
nullable: false, optional: false, nullable: false, optional: false,
}, },
offsetX: {
type: 'number',
nullable: false, optional: true,
},
offsetY: {
type: 'number',
nullable: false, optional: true,
},
}, },
}, },
}, },

View File

@ -138,7 +138,7 @@ export class ActivityPubServerService {
return; return;
} }
const algo = match[1]; const algo = match[1].toUpperCase();
const digestValue = match[2]; const digestValue = match[2];
if (algo !== 'SHA-256') { if (algo !== 'SHA-256') {

View File

@ -61,6 +61,9 @@ export class FileServerService {
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
fastify.addHook('onRequest', (request, reply, done) => { fastify.addHook('onRequest', (request, reply, done) => {
reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\''); reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\'');
if (process.env.NODE_ENV === 'development') {
reply.header('Access-Control-Allow-Origin', '*');
}
done(); done();
}); });

View File

@ -26,8 +26,8 @@
"@tabler/icons-webfont": "2.44.0", "@tabler/icons-webfont": "2.44.0",
"@vitejs/plugin-vue": "4.5.2", "@vitejs/plugin-vue": "4.5.2",
"@vue/compiler-sfc": "3.3.11", "@vue/compiler-sfc": "3.3.11",
"astring": "1.8.6",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6",
"astring": "1.8.6",
"broadcast-channel": "6.0.0", "broadcast-channel": "6.0.0",
"browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3", "browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
"buraha": "0.0.1", "buraha": "0.0.1",
@ -44,7 +44,7 @@
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"estree-walker": "3.0.3", "estree-walker": "3.0.3",
"eventemitter3": "5.0.1", "eventemitter3": "5.0.1",
"gsap": "3.12.3", "gsap": "3.12.4",
"idb-keyval": "6.2.1", "idb-keyval": "6.2.1",
"insert-text-at-cursor": "0.3.0", "insert-text-at-cursor": "0.3.0",
"is-file-animated": "1.0.2", "is-file-animated": "1.0.2",
@ -56,8 +56,8 @@
"punycode": "2.3.1", "punycode": "2.3.1",
"rollup": "4.9.0", "rollup": "4.9.0",
"sanitize-html": "2.11.0", "sanitize-html": "2.11.0",
"shiki": "0.14.6",
"sass": "1.69.5", "sass": "1.69.5",
"shiki": "0.14.7",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.159.0", "three": "0.159.0",
@ -69,29 +69,29 @@
"typescript": "5.3.3", "typescript": "5.3.3",
"uuid": "9.0.1", "uuid": "9.0.1",
"v-code-diff": "1.7.2", "v-code-diff": "1.7.2",
"vite": "5.0.8", "vite": "5.0.10",
"vue": "3.3.11", "vue": "3.3.11",
"vuedraggable": "next" "vuedraggable": "next"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-actions": "7.6.4", "@storybook/addon-actions": "7.6.5",
"@storybook/addon-essentials": "7.6.4", "@storybook/addon-essentials": "7.6.5",
"@storybook/addon-interactions": "7.6.4", "@storybook/addon-interactions": "7.6.5",
"@storybook/addon-links": "7.6.4", "@storybook/addon-links": "7.6.5",
"@storybook/addon-storysource": "7.6.4", "@storybook/addon-storysource": "7.6.5",
"@storybook/addons": "7.6.4", "@storybook/addons": "7.6.5",
"@storybook/blocks": "7.6.4", "@storybook/blocks": "7.6.5",
"@storybook/core-events": "7.6.4", "@storybook/core-events": "7.6.5",
"@storybook/jest": "0.2.3", "@storybook/jest": "0.2.3",
"@storybook/manager-api": "7.6.4", "@storybook/manager-api": "7.6.5",
"@storybook/preview-api": "7.6.4", "@storybook/preview-api": "7.6.5",
"@storybook/react": "7.6.4", "@storybook/react": "7.6.5",
"@storybook/react-vite": "7.6.4", "@storybook/react-vite": "7.6.5",
"@storybook/testing-library": "0.2.2", "@storybook/testing-library": "0.2.2",
"@storybook/theming": "7.6.4", "@storybook/theming": "7.6.5",
"@storybook/types": "7.6.4", "@storybook/types": "7.6.5",
"@storybook/vue3": "7.6.4", "@storybook/vue3": "7.6.5",
"@storybook/vue3-vite": "7.6.4", "@storybook/vue3-vite": "7.6.5",
"@testing-library/vue": "8.0.1", "@testing-library/vue": "8.0.1",
"@types/escape-regexp": "0.0.3", "@types/escape-regexp": "0.0.3",
"@types/estree": "1.0.5", "@types/estree": "1.0.5",
@ -111,11 +111,12 @@
"acorn": "8.11.2", "acorn": "8.11.2",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "13.6.1", "cypress": "13.6.1",
"eslint": "8.55.0", "eslint": "8.56.0",
"eslint-plugin-import": "2.29.0", "eslint-plugin-import": "2.29.1",
"eslint-plugin-vue": "9.19.2", "eslint-plugin-vue": "9.19.2",
"fast-glob": "3.3.2", "fast-glob": "3.3.2",
"happy-dom": "10.0.3", "happy-dom": "10.0.3",
"intersection-observer": "0.12.2",
"micromatch": "4.0.5", "micromatch": "4.0.5",
"msw": "1.3.2", "msw": "1.3.2",
"msw-storybook-addon": "1.10.0", "msw-storybook-addon": "1.10.0",
@ -124,7 +125,7 @@
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"start-server-and-test": "2.0.3", "start-server-and-test": "2.0.3",
"storybook": "7.6.4", "storybook": "7.6.5",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"summaly": "github:misskey-dev/summaly", "summaly": "github:misskey-dev/summaly",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",

View File

@ -12,19 +12,16 @@ import { version, ui, lang, updateLocale, locale } from '@/config.js';
import { applyTheme } from '@/scripts/theme.js'; import { applyTheme } from '@/scripts/theme.js';
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
import { i18n, updateI18n } from '@/i18n.js'; import { i18n, updateI18n } from '@/i18n.js';
import { confirm, alert, post, popup, toast } from '@/os.js';
import { $i, refreshAccount, login, updateAccount, signout } from '@/account.js'; import { $i, refreshAccount, login, updateAccount, signout } from '@/account.js';
import { defaultStore, ColdDeviceStorage } from '@/store.js'; import { defaultStore, ColdDeviceStorage } from '@/store.js';
import { fetchInstance, instance } from '@/instance.js'; import { fetchInstance, instance } from '@/instance.js';
import { deviceKind } from '@/scripts/device-kind.js'; import { deviceKind } from '@/scripts/device-kind.js';
import { reloadChannel } from '@/scripts/unison-reload.js'; import { reloadChannel } from '@/scripts/unison-reload.js';
import { reactionPicker } from '@/scripts/reaction-picker.js';
import { getUrlWithoutLoginId } from '@/scripts/login-id.js'; import { getUrlWithoutLoginId } from '@/scripts/login-id.js';
import { getAccountFromId } from '@/scripts/get-account-from-id.js'; import { getAccountFromId } from '@/scripts/get-account-from-id.js';
import { deckStore } from '@/ui/deck/deck-store.js'; import { deckStore } from '@/ui/deck/deck-store.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js'; import { fetchCustomEmojis } from '@/custom-emojis.js';
import { mainRouter } from '@/router.js';
export async function common(createVue: () => App<Element>) { export async function common(createVue: () => App<Element>) {
console.info(`Misskey v${version}`); console.info(`Misskey v${version}`);

View File

@ -20,6 +20,7 @@ import { mainRouter } from '@/router.js';
import { initializeSw } from '@/scripts/initialize-sw.js'; import { initializeSw } from '@/scripts/initialize-sw.js';
import { deckStore } from '@/ui/deck/deck-store.js'; import { deckStore } from '@/ui/deck/deck-store.js';
import { emojiPicker } from '@/scripts/emoji-picker.js'; import { emojiPicker } from '@/scripts/emoji-picker.js';
import { SnowfallEffect } from '@/scripts/snowfall-effect.js';
export async function mainBoot() { export async function mainBoot() {
const { isClientUpdated } = await common(() => createApp( const { isClientUpdated } = await common(() => createApp(
@ -75,6 +76,13 @@ export async function mainBoot() {
}, },
}; };
if (defaultStore.state.enableSeasonalScreenEffect) {
const month = new Date().getMonth() + 1;
if (month === 12 || month === 1) {
new SnowfallEffect().render();
}
}
if ($i) { if ($i) {
// only add post shortcuts if logged in // only add post shortcuts if logged in
hotkeys['p|n'] = post; hotkeys['p|n'] = post;

View File

@ -9,7 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkLoading v-if="!inline ?? true"/> <MkLoading v-if="!inline ?? true"/>
</template> </template>
<code v-if="inline" :class="$style.codeInlineRoot">{{ code }}</code> <code v-if="inline" :class="$style.codeInlineRoot">{{ code }}</code>
<XCode v-else-if="show" :code="code" :lang="lang"/> <XCode v-else-if="show && lang" :code="code" :lang="lang"/>
<pre v-else-if="show" :class="$style.codeBlockFallbackRoot"><code :class="$style.codeBlockFallbackCode">{{ code }}</code></pre>
<button v-else :class="$style.codePlaceholderRoot" @click="show = true"> <button v-else :class="$style.codePlaceholderRoot" @click="show = true">
<div :class="$style.codePlaceholderContainer"> <div :class="$style.codePlaceholderContainer">
<div><i class="ti ti-code"></i> {{ i18n.ts.code }}</div> <div><i class="ti ti-code"></i> {{ i18n.ts.code }}</div>
@ -47,6 +48,21 @@ const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'))
border-radius: .3em; border-radius: .3em;
} }
.codeBlockFallbackRoot {
display: block;
overflow-wrap: anywhere;
color: #D4D4D4;
background: #1E1E1E;
padding: 1em;
margin: .5em 0;
overflow: auto;
border-radius: 8px;
}
.codeBlockFallbackCode {
font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
}
.codePlaceholderRoot { .codePlaceholderRoot {
display: block; display: block;
width: 100%; width: 100%;

View File

@ -4,7 +4,9 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div :class="[$style.codeEditorRoot, { [$style.focused]: focused }]"> <div>
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
<div :class="[$style.codeEditorRoot, { [$style.focused]: focused }]">
<div :class="$style.codeEditorScroller"> <div :class="$style.codeEditorScroller">
<textarea <textarea
ref="inputEl" ref="inputEl"
@ -23,11 +25,17 @@ SPDX-License-Identifier: AGPL-3.0-only
></textarea> ></textarea>
<XCode :class="$style.codeEditorHighlighter" :codeEditor="true" :code="v" :lang="lang"/> <XCode :class="$style.codeEditorHighlighter" :codeEditor="true" :code="v" :lang="lang"/>
</div> </div>
</div>
<div :class="$style.caption"><slot name="caption"></slot></div>
<MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, toRefs, shallowRef, nextTick } from 'vue'; import { ref, watch, toRefs, shallowRef, nextTick } from 'vue';
import { debounce } from 'throttle-debounce';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
import XCode from '@/components/MkCode.core.vue'; import XCode from '@/components/MkCode.core.vue';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
@ -36,6 +44,8 @@ const props = withDefaults(defineProps<{
required?: boolean; required?: boolean;
readonly?: boolean; readonly?: boolean;
disabled?: boolean; disabled?: boolean;
debounce?: boolean;
manualSave?: boolean;
}>(), { }>(), {
lang: 'js', lang: 'js',
}); });
@ -54,6 +64,8 @@ const focused = ref(false);
const changed = ref(false); const changed = ref(false);
const inputEl = shallowRef<HTMLTextAreaElement>(); const inputEl = shallowRef<HTMLTextAreaElement>();
const focus = () => inputEl.value?.focus();
const onInput = (ev) => { const onInput = (ev) => {
v.value = ev.target?.value ?? v.value; v.value = ev.target?.value ?? v.value;
changed.value = true; changed.value = true;
@ -100,16 +112,48 @@ const updated = () => {
emit('update:modelValue', v.value); emit('update:modelValue', v.value);
}; };
const debouncedUpdated = debounce(1000, updated);
watch(modelValue, newValue => { watch(modelValue, newValue => {
v.value = newValue ?? ''; v.value = newValue ?? '';
}); });
watch(v, () => { watch(v, newValue => {
if (!props.manualSave) {
if (props.debounce) {
debouncedUpdated();
} else {
updated(); updated();
}
}
}); });
</script> </script>
<style lang="scss" module> <style lang="scss" module>
.label {
font-size: 0.85em;
padding: 0 0 8px 0;
user-select: none;
&:empty {
display: none;
}
}
.caption {
font-size: 0.85em;
padding: 8px 0 0 0;
color: var(--fgTransparentWeak);
&:empty {
display: none;
}
}
.save {
margin: 8px 0 0 0;
}
.codeEditorRoot { .codeEditorRoot {
min-width: 100%; min-width: 100%;
max-width: 100%; max-width: 100%;
@ -117,6 +161,7 @@ watch(v, () => {
overflow-y: hidden; overflow-y: hidden;
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
border-radius: 6px;
padding: 0; padding: 0;
color: var(--fg); color: var(--fg);
border: solid 1px var(--panel); border: solid 1px var(--panel);
@ -157,9 +202,10 @@ watch(v, () => {
caret-color: rgb(225, 228, 232); caret-color: rgb(225, 228, 232);
background-color: transparent; background-color: transparent;
border: 0; border: 0;
border-radius: 6px;
outline: 0; outline: 0;
min-width: calc(100% - 24px); min-width: calc(100% - 24px);
height: calc(100% - 24px); height: 100%;
padding: 12px; padding: 12px;
line-height: 1.5em; line-height: 1.5em;
font-size: 1em; font-size: 1em;

View File

@ -67,6 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo> <MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
<input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown"> <input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown">
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]"> <div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> <textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div> <div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
</div> </div>
@ -1038,6 +1039,16 @@ defineExpose({
} }
} }
.colorBar {
position: absolute;
top: 0px;
left: 12px;
width: 5px;
height: 100% ;
border-radius: 999px;
pointer-events: none;
}
.submitInner { .submitInner {
padding: 0 12px; padding: 0 12px;
line-height: 34px; line-height: 34px;
@ -1296,5 +1307,6 @@ defineExpose({
.headerRight { .headerRight {
gap: 0; gap: 0;
} }
} }
</style> </style>

View File

@ -12,16 +12,17 @@
<html> <html>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>misskey</title> <title>[DEV] Loading...</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta <meta
http-equiv="Content-Security-Policy" http-equiv="Content-Security-Policy"
content="default-src 'self'; content="default-src 'self';
worker-src 'self'; worker-src 'self';
script-src 'self'; script-src 'self' 'unsafe-eval';
style-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';
img-src 'self' data: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000; img-src 'self' data: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;" media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;"
/> />
<meta property="og:site_name" content="[DEV BUILD] Misskey" /> <meta property="og:site_name" content="[DEV BUILD] Misskey" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -15,9 +15,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts._play.summary }}</template> <template #label>{{ i18n.ts._play.summary }}</template>
</MkTextarea> </MkTextarea>
<MkButton primary @click="selectPreset">{{ i18n.ts.selectFromPresets }}<i class="ti ti-chevron-down"></i></MkButton> <MkButton primary @click="selectPreset">{{ i18n.ts.selectFromPresets }}<i class="ti ti-chevron-down"></i></MkButton>
<MkTextarea v-model="script" code tall spellcheck="false"> <MkCodeEditor v-model="script" lang="is">
<template #label>{{ i18n.ts._play.script }}</template> <template #label>{{ i18n.ts._play.script }}</template>
</MkTextarea> </MkCodeEditor>
<div class="_buttons"> <div class="_buttons">
<MkButton primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> <MkButton primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
<MkButton @click="show"><i class="ti ti-eye"></i> {{ i18n.ts.show }}</MkButton> <MkButton @click="show"><i class="ti ti-eye"></i> {{ i18n.ts.show }}</MkButton>
@ -40,6 +40,7 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
import MkCodeEditor from '@/components/MkCodeEditor.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
import { useRouter } from '@/router.js'; import { useRouter } from '@/router.js';

View File

@ -165,12 +165,8 @@ async function run() {
return new Promise(ok => { return new Promise(ok => {
os.inputText({ os.inputText({
title: q, title: q,
}).then(({ canceled, result: a }) => { }).then(({ result: a }) => {
if (canceled) { ok(a ?? '');
ok('');
} else {
ok(a);
}
}); });
}); });
}, },

View File

@ -26,9 +26,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkKeyValue> </MkKeyValue>
</FormSplit> </FormSplit>
<MkTextarea v-model="valueForEditor" tall code> <MkCodeEditor v-model="valueForEditor" lang="json5">
<template #label>{{ i18n.ts.value }} (JSON)</template> <template #label>{{ i18n.ts.value }} (JSON)</template>
</MkTextarea> </MkCodeEditor>
<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> <MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
@ -52,7 +52,7 @@ import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkKeyValue from '@/components/MkKeyValue.vue'; import MkKeyValue from '@/components/MkKeyValue.vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkCodeEditor from '@/components/MkCodeEditor.vue';
import FormSplit from '@/components/form/split.vue'; import FormSplit from '@/components/form/split.vue';
import FormInfo from '@/components/MkInfo.vue'; import FormInfo from '@/components/MkInfo.vue';

View File

@ -4,9 +4,12 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div v-if="!loading" class="_gaps"> <div>
<div v-if="!loading" class="_gaps">
<MkInfo>{{ i18n.t('_profile.avatarDecorationMax', { max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.t('remainingN', { n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo> <MkInfo>{{ i18n.t('_profile.avatarDecorationMax', { max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.t('remainingN', { n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration/>
<div v-if="$i.avatarDecorations.length > 0" v-panel :class="$style.current" class="_gaps_s"> <div v-if="$i.avatarDecorations.length > 0" v-panel :class="$style.current" class="_gaps_s">
<div>{{ i18n.ts.inUse }}</div> <div>{{ i18n.ts.inUse }}</div>
@ -34,21 +37,23 @@ SPDX-License-Identifier: AGPL-3.0-only
@click="openDecoration(avatarDecoration)" @click="openDecoration(avatarDecoration)"
/> />
</div> </div>
</div> </div>
<div v-else> <div v-else>
<MkLoading/> <MkLoading/>
</div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, defineAsyncComponent } from 'vue'; import { ref, defineAsyncComponent, computed } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XDecoration from './profile.avatar-decoration.decoration.vue'; import XDecoration from './avatar-decoration.decoration.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const loading = ref(true); const loading = ref(true);
const avatarDecorations = ref<Misskey.entities.GetAvatarDecorationsResponse>([]); const avatarDecorations = ref<Misskey.entities.GetAvatarDecorationsResponse>([]);
@ -59,7 +64,7 @@ os.api('get-avatar-decorations').then(_avatarDecorations => {
}); });
function openDecoration(avatarDecoration, index?: number) { function openDecoration(avatarDecoration, index?: number) {
os.popup(defineAsyncComponent(() => import('./profile.avatar-decoration.dialog.vue')), { os.popup(defineAsyncComponent(() => import('./avatar-decoration.dialog.vue')), {
decoration: avatarDecoration, decoration: avatarDecoration,
usingIndex: index, usingIndex: index,
}, { }, {
@ -115,9 +120,25 @@ function detachAllDecorations() {
$i.avatarDecorations = []; $i.avatarDecorations = [];
}); });
} }
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePageMetadata({
title: i18n.ts.avatarDecorations,
icon: 'ti ti-sparkles',
});
</script> </script>
<style lang="scss" module> <style lang="scss" module>
.avatar {
display: inline-block;
width: 72px;
height: 72px;
margin: 16px auto;
}
.current { .current {
padding: 16px; padding: 16px;
border-radius: var(--radius); border-radius: var(--radius);

View File

@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_m"> <div class="_gaps_m">
<FormInfo warn>{{ i18n.ts.customCssWarn }}</FormInfo> <FormInfo warn>{{ i18n.ts.customCssWarn }}</FormInfo>
<MkTextarea v-model="localCustomCss" manualSave tall code style="tab-size: 2;"> <MkCodeEditor v-model="localCustomCss" manualSave lang="css">
<template #label>CSS</template> <template #label>CSS</template>
</MkTextarea> </MkCodeEditor>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, computed } from 'vue'; import { ref, watch, computed } from 'vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkCodeEditor from '@/components/MkCodeEditor.vue';
import FormInfo from '@/components/MkInfo.vue'; import FormInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { unisonReload } from '@/scripts/unison-reload.js'; import { unisonReload } from '@/scripts/unison-reload.js';

View File

@ -40,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_buttons"> <div class="_buttons">
<MkButton inline @click="previewReaction"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton> <MkButton inline @click="previewReaction"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
<MkButton inline danger @click="setDefaultReaction"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton> <MkButton inline danger @click="setDefaultReaction"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
<MkButton inline danger @click="copyFromPinnedEmojis"><i class="ti ti-copy"></i> {{ i18n.ts.copyFromPinnedEmojis }}</MkButton> <MkButton inline danger @click="overwriteFromPinnedEmojis"><i class="ti ti-copy"></i> {{ i18n.ts.overwriteFromPinnedEmojis }}</MkButton>
</div> </div>
</div> </div>
</MkFolder> </MkFolder>
@ -80,7 +80,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_buttons"> <div class="_buttons">
<MkButton inline @click="previewEmoji"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton> <MkButton inline @click="previewEmoji"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
<MkButton inline danger @click="setDefaultEmoji"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton> <MkButton inline danger @click="setDefaultEmoji"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
<MkButton inline danger @click="copyFromPinnedEmojisForReaction"><i class="ti ti-copy"></i> {{ i18n.ts.copyFromPinnedEmojisForReaction }}</MkButton> <MkButton inline danger @click="overwriteFromPinnedEmojisForReaction"><i class="ti ti-copy"></i> {{ i18n.ts.overwriteFromPinnedEmojisForReaction }}</MkButton>
</div> </div>
</div> </div>
</MkFolder> </MkFolder>
@ -164,7 +164,7 @@ function previewEmoji(ev: MouseEvent) {
emojiPicker.show(getHTMLElement(ev)); emojiPicker.show(getHTMLElement(ev));
} }
async function copyFromPinnedEmojis() { async function overwriteFromPinnedEmojis() {
const { canceled } = await os.confirm({ const { canceled } = await os.confirm({
type: 'warning', type: 'warning',
text: i18n.ts.overwriteContentConfirm, text: i18n.ts.overwriteContentConfirm,
@ -177,7 +177,7 @@ async function copyFromPinnedEmojis() {
pinnedEmojisForReaction.value = [...pinnedEmojis.value]; pinnedEmojisForReaction.value = [...pinnedEmojis.value];
} }
async function copyFromPinnedEmojisForReaction() { async function overwriteFromPinnedEmojisForReaction() {
const { canceled } = await os.confirm({ const { canceled } = await os.confirm({
type: 'warning', type: 'warning',
text: i18n.ts.overwriteContentConfirm, text: i18n.ts.overwriteContentConfirm,

View File

@ -122,6 +122,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch> <MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch> <MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch> <MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
<MkSwitch v-model="enableSeasonalScreenEffect">{{ i18n.ts.seasonalScreenEffect }}</MkSwitch>
</div> </div>
<div> <div>
<MkRadios v-model="emojiStyle"> <MkRadios v-model="emojiStyle">
@ -289,6 +290,7 @@ const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificati
const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn')); const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline')); const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications')); const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect'));
watch(lang, () => { watch(lang, () => {
miLocalStorage.setItem('lang', lang.value as string); miLocalStorage.setItem('lang', lang.value as string);
@ -328,6 +330,7 @@ watch([
highlightSensitiveMedia, highlightSensitiveMedia,
keepScreenOn, keepScreenOn,
disableStreamingTimeline, disableStreamingTimeline,
enableSeasonalScreenEffect,
], async () => { ], async () => {
await reloadAsk(); await reloadAsk();
}); });

View File

@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_m"> <div class="_gaps_m">
<FormInfo warn>{{ i18n.ts._plugin.installWarn }}</FormInfo> <FormInfo warn>{{ i18n.ts._plugin.installWarn }}</FormInfo>
<MkTextarea v-model="code" tall code> <MkCodeEditor v-model="code" lang="is">
<template #label>{{ i18n.ts.code }}</template> <template #label>{{ i18n.ts.code }}</template>
</MkTextarea> </MkCodeEditor>
<div> <div>
<MkButton :disabled="code == null" primary inline @click="install"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton> <MkButton :disabled="code == null" primary inline @click="install"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, ref, computed } from 'vue'; import { nextTick, ref, computed } from 'vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkCodeEditor from '@/components/MkCodeEditor.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import FormInfo from '@/components/MkInfo.vue'; import FormInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';

View File

@ -5,12 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div class="_gaps_m"> <div class="_gaps_m">
<div :class="$style.avatarAndBanner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }"> <div class="_panel">
<div :class="$style.banner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
<MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
</div>
<div :class="$style.avatarContainer"> <div :class="$style.avatarContainer">
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration @click="changeAvatar"/> <MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration @click="changeAvatar"/>
<div class="_buttonsCenter">
<MkButton primary rounded @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton> <MkButton primary rounded @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
<MkButton primary rounded link to="/settings/avatar-decoration">{{ i18n.ts.decorate }} <i class="ti ti-sparkles"></i></MkButton>
</div>
</div> </div>
<MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
</div> </div>
<MkInput v-model="profile.name" :max="30" manualSave :mfmAutocomplete="['emoji']"> <MkInput v-model="profile.name" :max="30" manualSave :mfmAutocomplete="['emoji']">
@ -83,13 +88,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template> <template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
</FormSlot> </FormSlot>
<MkFolder>
<template #icon><i class="ti ti-sparkles"></i></template>
<template #label>{{ i18n.ts.avatarDecorations }}</template>
<XAvatarDecoration/>
</MkFolder>
<MkFolder> <MkFolder>
<template #label>{{ i18n.ts.advancedSettings }}</template> <template #label>{{ i18n.ts.advancedSettings }}</template>
@ -264,19 +262,19 @@ definePageMetadata({
</script> </script>
<style lang="scss" module> <style lang="scss" module>
.avatarAndBanner { .banner {
position: relative; position: relative;
height: 130px;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
border: solid 1px var(--divider); border-bottom: solid 1px var(--divider);
border-radius: 10px;
overflow: clip; overflow: clip;
} }
.avatarContainer { .avatarContainer {
display: inline-block; margin-top: -50px;
padding-bottom: 16px;
text-align: center; text-align: center;
padding: 16px;
} }
.avatar { .avatar {

View File

@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div class="_gaps_m"> <div class="_gaps_m">
<MkTextarea v-model="installThemeCode" code> <MkCodeEditor v-model="installThemeCode" lang="json5">
<template #label>{{ i18n.ts._theme.code }}</template> <template #label>{{ i18n.ts._theme.code }}</template>
</MkTextarea> </MkCodeEditor>
<div class="_buttons"> <div class="_buttons">
<MkButton :disabled="installThemeCode == null" inline @click="() => previewTheme(installThemeCode)"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton> <MkButton :disabled="installThemeCode == null" inline @click="() => previewTheme(installThemeCode)"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkCodeEditor from '@/components/MkCodeEditor.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { parseThemeCode, previewTheme, installTheme } from '@/scripts/install-theme.js'; import { parseThemeCode, previewTheme, installTheme } from '@/scripts/install-theme.js';
import * as os from '@/os.js'; import * as os from '@/os.js';

View File

@ -51,9 +51,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.editCode }}</template> <template #label>{{ i18n.ts.editCode }}</template>
<div class="_gaps_m"> <div class="_gaps_m">
<MkTextarea v-model="themeCode" tall code> <MkCodeEditor v-model="themeCode" lang="json5">
<template #label>{{ i18n.ts._theme.code }}</template> <template #label>{{ i18n.ts._theme.code }}</template>
</MkTextarea> </MkCodeEditor>
<MkButton primary @click="applyThemeCode">{{ i18n.ts.apply }}</MkButton> <MkButton primary @click="applyThemeCode">{{ i18n.ts.apply }}</MkButton>
</div> </div>
</MkFolder> </MkFolder>
@ -80,6 +80,7 @@ import { v4 as uuid } from 'uuid';
import JSON5 from 'json5'; import JSON5 from 'json5';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkCodeEditor from '@/components/MkCodeEditor.vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';

View File

@ -136,9 +136,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkLazy> </MkLazy>
</template> </template>
<div v-if="!disableNotes"> <div v-if="!disableNotes">
<div style="margin-bottom: 8px;">{{ i18n.ts.featured }}</div>
<MkLazy> <MkLazy>
<MkNotes :class="$style.tl" :noGap="true" :pagination="pagination"/> <XTimeline :user="user"/>
</MkLazy> </MkLazy>
</div> </div>
</div> </div>
@ -193,6 +192,7 @@ function calcAge(birthdate: string): number {
const XFiles = defineAsyncComponent(() => import('./index.files.vue')); const XFiles = defineAsyncComponent(() => import('./index.files.vue'));
const XActivity = defineAsyncComponent(() => import('./index.activity.vue')); const XActivity = defineAsyncComponent(() => import('./index.activity.vue'));
const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
user: Misskey.entities.UserDetailed; user: Misskey.entities.UserDetailed;
@ -219,14 +219,6 @@ watch(moderationNote, async () => {
await os.api('admin/update-user-note', { userId: props.user.id, text: moderationNote.value }); await os.api('admin/update-user-note', { userId: props.user.id, text: moderationNote.value });
}); });
const pagination = {
endpoint: 'users/featured-notes' as const,
limit: 10,
params: computed(() => ({
userId: props.user.id,
})),
};
const style = computed(() => { const style = computed(() => {
if (props.user.bannerUrl == null) return {}; if (props.user.bannerUrl == null) return {};
return { return {

View File

@ -4,18 +4,17 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<MkSpacer :contentMax="800" style="padding-top: 0"> <MkStickyContainer>
<MkStickyContainer>
<template #header> <template #header>
<MkTab v-model="include" :class="$style.tab"> <MkTab v-model="tab" :class="$style.tab">
<option value="featured">{{ i18n.ts.featured }}</option>
<option :value="null">{{ i18n.ts.notes }}</option> <option :value="null">{{ i18n.ts.notes }}</option>
<option value="all">{{ i18n.ts.all }}</option> <option value="all">{{ i18n.ts.all }}</option>
<option value="files">{{ i18n.ts.withFiles }}</option> <option value="files">{{ i18n.ts.withFiles }}</option>
</MkTab> </MkTab>
</template> </template>
<MkNotes :noGap="true" :pagination="pagination" :class="$style.tl"/> <MkNotes :noGap="true" :pagination="pagination" :class="$style.tl"/>
</MkStickyContainer> </MkStickyContainer>
</MkSpacer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -29,24 +28,29 @@ const props = defineProps<{
user: Misskey.entities.UserDetailed; user: Misskey.entities.UserDetailed;
}>(); }>();
const include = ref<string | null>('all'); const tab = ref<string | null>('all');
const pagination = { const pagination = computed(() => tab.value === 'featured' ? {
endpoint: 'users/featured-notes' as const,
limit: 10,
params: {
userId: props.user.id,
},
} : {
endpoint: 'users/notes' as const, endpoint: 'users/notes' as const,
limit: 10, limit: 10,
params: computed(() => ({ params: {
userId: props.user.id, userId: props.user.id,
withRenotes: include.value === 'all', withRenotes: tab.value === 'all',
withReplies: include.value === 'all', withReplies: tab.value === 'all',
withChannelNotes: include.value === 'all', withChannelNotes: tab.value === 'all',
withFiles: include.value === 'files', withFiles: tab.value === 'files',
})), },
}; });
</script> </script>
<style lang="scss" module> <style lang="scss" module>
.tab { .tab {
margin: calc(var(--margin) / 2) 0;
padding: calc(var(--margin) / 2) 0; padding: calc(var(--margin) / 2) 0;
background: var(--bg); background: var(--bg);
} }

View File

@ -9,7 +9,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<div> <div>
<div v-if="user"> <div v-if="user">
<XHome v-if="tab === 'home'" :user="user"/> <XHome v-if="tab === 'home'" :user="user"/>
<XTimeline v-else-if="tab === 'notes'" :user="user"/> <MkSpacer v-else-if="tab === 'notes'" :contentMax="800" style="padding-top: 0">
<XTimeline :user="user"/>
</MkSpacer>
<XActivity v-else-if="tab === 'activity'" :user="user"/> <XActivity v-else-if="tab === 'activity'" :user="user"/>
<XAchievements v-else-if="tab === 'achievements'" :user="user"/> <XAchievements v-else-if="tab === 'achievements'" :user="user"/>
<XReactions v-else-if="tab === 'reactions'" :user="user"/> <XReactions v-else-if="tab === 'reactions'" :user="user"/>

View File

@ -54,6 +54,10 @@ export const routes = [{
path: '/profile', path: '/profile',
name: 'profile', name: 'profile',
component: page(() => import('./pages/settings/profile.vue')), component: page(() => import('./pages/settings/profile.vue')),
}, {
path: '/avatar-decoration',
name: 'avatarDecoration',
component: page(() => import('./pages/settings/avatar-decoration.vue')),
}, { }, {
path: '/roles', path: '/roles',
name: 'roles', name: 'roles',

File diff suppressed because one or more lines are too long

View File

@ -412,6 +412,10 @@ export const defaultStore = markRaw(new Storage('base', {
code: false, code: false,
} as Record<string, boolean>, } as Record<string, boolean>,
}, },
enableSeasonalScreenEffect: {
where: 'device',
default: false,
},
sound_masterVolume: { sound_masterVolume: {
where: 'device', where: 'device',

View File

@ -3,13 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { describe, test, assert, afterEach } from 'vitest'; import { afterEach, assert, describe, test } from 'vitest';
import { render, cleanup, type RenderResult } from '@testing-library/vue'; import { cleanup, render, type RenderResult } from '@testing-library/vue';
import './init'; import './init';
import type * as Misskey from 'misskey-js'; import type * as Misskey from 'misskey-js';
import { directives } from '@/directives/index.js'; import { directives } from '@/directives/index.js';
import { components } from '@/components/index.js'; import { components } from '@/components/index.js';
import XHome from '@/pages/user/home.vue'; import XHome from '@/pages/user/home.vue';
import 'intersection-observer';
describe('XHome', () => { describe('XHome', () => {
const renderHome = (user: Partial<Misskey.entities.UserDetailed>): RenderResult => { const renderHome = (user: Partial<Misskey.entities.UserDetailed>): RenderResult => {

View File

@ -28,7 +28,7 @@
"@types/node": "20.10.4", "@types/node": "20.10.4",
"@typescript-eslint/eslint-plugin": "6.14.0", "@typescript-eslint/eslint-plugin": "6.14.0",
"@typescript-eslint/parser": "6.14.0", "@typescript-eslint/parser": "6.14.0",
"eslint": "8.55.0", "eslint": "8.56.0",
"jest": "29.7.0", "jest": "29.7.0",
"jest-fetch-mock": "3.0.3", "jest-fetch-mock": "3.0.3",
"jest-websocket-mock": "2.5.0", "jest-websocket-mock": "2.5.0",

View File

@ -16,8 +16,8 @@
"devDependencies": { "devDependencies": {
"@typescript-eslint/parser": "6.14.0", "@typescript-eslint/parser": "6.14.0",
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67", "@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
"eslint": "8.55.0", "eslint": "8.56.0",
"eslint-plugin-import": "2.29.0", "eslint-plugin-import": "2.29.1",
"nodemon": "3.0.2", "nodemon": "3.0.2",
"typescript": "5.3.3" "typescript": "5.3.3"
}, },

File diff suppressed because it is too large Load Diff