diff --git a/.config/example.yml b/.config/example.yml index c179395966f5..b996a83fb50a 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -30,6 +30,10 @@ url: https://example.tld/ # The port that your Misskey server should listen on. port: 3000 +# You can also use UNIX domain socket. +# socket: /path/to/misskey.sock +# chmodSocket: '777' + # ┌──────────────────────────┐ #───┘ PostgreSQL configuration └──────────────────────────────── @@ -78,6 +82,8 @@ redis: #pass: example-pass #prefix: example-prefix #db: 1 + # You can specify more ioredis options... + #username: example-username #redisForPubsub: # host: localhost @@ -86,6 +92,8 @@ redis: # #pass: example-pass # #prefix: example-prefix # #db: 1 +# # You can specify more ioredis options... +# #username: example-username #redisForJobQueue: # host: localhost @@ -94,6 +102,8 @@ redis: # #pass: example-pass # #prefix: example-prefix # #db: 1 +# # You can specify more ioredis options... +# #username: example-username # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── @@ -104,6 +114,7 @@ redis: # apiKey: '' # ssl: true # index: '' +# scope: local # ┌───────────────┐ #───┘ ID generation └─────────────────────────────────────────── diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a47804ab07a0..0664ecd110a4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "features": { "ghcr.io/devcontainers-contrib/features/pnpm:2": {}, "ghcr.io/devcontainers/features/node:1": { - "version": "18.16.0" + "version": "20.3.1" } }, "forwardPorts": [3000], diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 8f8c5a13ab72..2809cd2ca469 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.8' services: app: - build: + build: context: . dockerfile: Dockerfile diff --git a/.editorconfig b/.editorconfig index a6f988f8d7c7..def7baa1a8f1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,6 +6,10 @@ indent_size = 2 charset = utf-8 insert_final_newline = true end_of_line = lf +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false [*.{yml,yaml}] indent_style = space diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.md b/.github/ISSUE_TEMPLATE/01_bug-report.md index 25e7fc8b201e..b889d96eb39c 100644 --- a/.github/ISSUE_TEMPLATE/01_bug-report.md +++ b/.github/ISSUE_TEMPLATE/01_bug-report.md @@ -54,7 +54,7 @@ Please include errors from the developer console and/or server log files if you * Installation Method or Hosting Service: * Misskey: 13.x.x -* Node: 18.x.x +* Node: 20.x.x * PostgreSQL: 15.x.x * Redis: 7.x.x * OS and Architecture: diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index 6cb1b34997ed..1aea8b54595e 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -37,7 +37,7 @@ jobs: with: version: 8 run_install: false - - name: Use Node.js 18.x + - name: Use Node.js 20.x uses: actions/setup-node@v3.6.0 with: node-version-file: '.node-version' diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index d7be15bd4f09..96e64c322e46 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.x] services: postgres: diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index 4ea4ba462873..eef68aa0d166 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.x] steps: - uses: actions/checkout@v3.3.0 @@ -51,7 +51,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x] + node-version: [20.x] browser: [chrome] services: diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml index b15e704c7fa3..213657ce1f44 100644 --- a/.github/workflows/test-misskey-js.yml +++ b/.github/workflows/test-misskey-js.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml index 5243a837778b..8429465b5b41 100644 --- a/.github/workflows/test-production.yml +++ b/.github/workflows/test-production.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.x] steps: - uses: actions/checkout@v3.3.0 diff --git a/.gitignore b/.gitignore index 537232d37fc4..a66e527db059 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,6 @@ temp *.blend3 *.blend4 *.blend5 + +# VSCode addon +.favorites.json diff --git a/.node-version b/.node-version index 6d80269a4f04..dd0fe95cce55 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -18.16.0 +20.3.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 45dc0e3c905c..438436f01a86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,70 @@ --> +## 13.x.x (unreleased) + +### General +- 招待機能を改善しました + * 過去に発行した招待コードを確認できるようになりました + * ロールごとに招待コードの発行数制限と制限対象期間、有効期限を設定できるようになりました + * 招待コードを作成したユーザーと使用したユーザーを確認できるようになりました +- ユーザーにロールが期限付きでアサインされている場合、その期限をユーザーのモデレーションページで確認できるようになりました +- identicon生成を無効にしてパフォーマンスを向上させることができるようになりました +- サーバーのマシン情報の公開を無効にしてパフォーマンスを向上させることができるようになりました + +### Client +- deck UIのカラムのメニューからアンテナとリストの編集画面を開けるように +- ドライブファイルのメニューで画像をクロップできるように +- 画像を動画と同様に簡単に隠せるように +- Enhance: ノートの埋め込みが複数画像と動画を表示されるように +- オリジナル画像を保持せずにアップロードする場合webpでアップロードされるように(Safari以外) +- 見たことのあるRenoteを省略して表示をオンのときに自分のnoteのrenoteを省略するように +- フォルダーやファイルに対しても開発者モード使用時、IDをコピーできるように +- 引用対象を「もっと見る」で展開した場合、「閉じる」で畳めるように +- プロフィールURLをコピーできるボタンを追加 #11190 +- `CURRENT_URL`で現在表示中のURLを取得できるように(AiScript) +- ユーザーのContextMenuに「アンテナに追加」ボタンを追加 +- フォローやお気に入り登録をしていないチャンネルを開く時は概要ページを開くように +- 画面ビューワをタップした場合、マウスクリックと同様に画像ビューワを閉じるように +- オフライン時の画面にリロードボタンを追加 +- Renote時に公開範囲のデフォルト設定が適用されるように +- Deckで非ルートページにアクセスした際に簡易UIで表示しない設定を追加 +- ロール設定画面でロールIDを確認できるように +- コンテキストメニュー表示時のパフォーマンスを改善 +- フォロー/フォロワー非公開時の表示を改善 +- 本文にMFMが含まれている場合に自動でたたまれる機能が、返信先や引用RNにも適用されるように + - position は対象外になりました +- AiScriptを0.15.0に更新 +- Fix: サーバーメトリクスが90度傾いている +- Fix: 非ログイン時にクレデンシャルが必要なページに行くとエラーが出る問題を修正 +- Fix: sparkle内にリンクを入れるとクリック不能になる問題の修正 +- Fix: ZenUIでポップアップの表示位置がおかしい問題を修正 +- Fix: ページ遷移でスクロール位置が保持されない問題を修正 +- Fix: フォルダーのページネーションが機能しない #11180 +- Fix: 長い文章を投稿する際、プレビューが画面からはみ出る問題を修正 +- Fix: システムフォント設定が正しく反映されない問題を修正 +- Fix: アンケート終了時のプッシュ通知が正しく表示されない問題を修正 +- Fix: MasterVolumeが0の時だけでなく各通知音の音量設定が0のときも、HTMLAudioElement.playが実行されないように変更 + +### Server +- JSON.parse の回数を削減することで、ストリーミングのパフォーマンスを向上しました +- nsfwjs のモデルロードを排他することで、重複ロードによってメモリ使用量が増加しないように +- 連合の配送ジョブのパフォーマンスを向上(ロック機構の見直し、Redisキャッシュの活用) +- featuredノートのsignedGet回数を減らしました +- ActivityPubの署名用鍵長を2048bitに変更しパフォーマンスを向上(新規アカウントのみ) +- リモートサーバーのセンシティブなファイルのキャッシュだけを無効化できるオプションを追加 +- MeilisearchにIndexするノートの範囲を設定できるように +- Export notes with file detail +- Add unix socket support +- 設定ファイルでioredisの全てのオプションを指定可能に +- Fix: エクスポートしたカスタム絵文字のzipが大きいと読み込めない問題を修正 +- Fix: リモートサーバーに無意味なActivityPubの配信を行うことがあるのを修正 +- Fix: Remove Meilisearch index when notes are deleted +- Fix: 非英語環境でのPostgreSQLのエラーハンドリングを修正 +- Fix: インスタンスのアイコンがbase64の場合の挙動を修正 +- Fix: ローカルの `Person` を指す `acct` URI を解析するときのバグを修正しました +- Fix: 無効化されたアンテナが再度有効化されないことがある問題を修正 + ## 13.13.2 ### General diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6b3804f84f9..62bc11cd996b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -106,7 +106,7 @@ If your language is not listed in Crowdin, please open an issue. ![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg) ## Development -During development, it is useful to use the +During development, it is useful to use the ``` pnpm dev @@ -150,7 +150,7 @@ Prepare DB/Redis for testing. ``` docker compose -f packages/backend/test/docker-compose.yml up ``` -Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`. +Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`. Run all test. ``` @@ -214,30 +214,13 @@ Misskey uses [Storybook](https://storybook.js.org/) for UI development. ### Setup & Run -#### Universal - -##### Setup - -```bash -pnpm --filter misskey-js build -pnpm --filter frontend tsc -p .storybook && (node packages/frontend/.storybook/preload-locale.js & node packages/frontend/.storybook/preload-theme.js) -``` - -##### Run - -```bash -node packages/frontend/.storybook/generate.js && pnpm --filter frontend storybook dev -``` - -#### macOS & Linux - -##### Setup +#### Setup ```bash pnpm --filter misskey-js build ``` -##### Run +#### Run ```bash pnpm --filter frontend storybook-dev diff --git a/Dockerfile b/Dockerfile index fb389659bc93..5431c28aad96 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.4 -ARG NODE_VERSION=18.16.0-bullseye +ARG NODE_VERSION=20.3.1-bullseye # build assets & compile TypeScript diff --git a/README.md b/README.md index 2aae4bb865ba..ab4388c2ebd2 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Misskey logo - + **🌎 **[Misskey](https://misskey-hub.net/)** is an open source, decentralized social media platform that's free forever! 🚀** - + --- @@ -21,7 +21,7 @@ become a patron - + --- [![codecov](https://codecov.io/gh/misskey-dev/misskey/branch/develop/graph/badge.svg?token=R6IQZ3QJOL)](https://codecov.io/gh/misskey-dev/misskey) diff --git a/assets/title_float.svg b/assets/title_float.svg index 43205ac1c482..ed1749e321fb 100644 --- a/assets/title_float.svg +++ b/assets/title_float.svg @@ -23,13 +23,13 @@ diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue index 2e4f93e8489b..8e61c70484c7 100644 --- a/packages/frontend/src/components/MkLink.vue +++ b/packages/frontend/src/components/MkLink.vue @@ -1,6 +1,6 @@ @@ -113,6 +114,21 @@ function showMenu(ev: MouseEvent) { align-items: center; } +.hide { + display: block; + position: absolute; + border-radius: 6px; + background-color: var(--fg); + color: var(--accentLighten); + font-size: 12px; + opacity: .5; + padding: 5px 8px; + text-align: center; + cursor: pointer; + top: 12px; + right: 12px; +} + .hiddenTextWrapper { display: table-cell; text-align: center; @@ -137,8 +153,8 @@ function showMenu(ev: MouseEvent) { backdrop-filter: var(--blur, blur(15px)); color: #fff; font-size: 0.8em; - width: 32px; - height: 32px; + width: 28px; + height: 28px; text-align: center; bottom: 10px; right: 10px; diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index a0a2450054b1..be0aed652498 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -113,8 +113,10 @@ onMounted(() => { right: 0, }, imageClickAction: 'close', - tapAction: 'toggle-controls', + tapAction: 'close', bgOpacity: 1, + showAnimationDuration: 100, + hideAnimationDuration: 100, pswpModule: PhotoSwipe, }); diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 40bae90b5e49..dc5807b2dd3e 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -17,8 +17,8 @@ controls @contextmenu.stop > - diff --git a/packages/frontend/src/components/MkMiniChart.vue b/packages/frontend/src/components/MkMiniChart.vue index 89050e10f039..e884455709d0 100644 --- a/packages/frontend/src/components/MkMiniChart.vue +++ b/packages/frontend/src/components/MkMiniChart.vue @@ -59,8 +59,8 @@ function draw(): void { polygonPoints = `0,${ viewBoxY } ${ polylinePoints } ${ viewBoxX },${ viewBoxY }`; - headX = _polylinePoints[_polylinePoints.length - 1][0]; - headY = _polylinePoints[_polylinePoints.length - 1][1]; + headX = _polylinePoints.at(-1)![0]; + headY = _polylinePoints.at(-1)![1]; } watch(() => props.src, draw, { immediate: true }); diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 7c9ddadbf8e3..deeae6e94036 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -165,6 +165,7 @@ import { getNoteSummary } from '@/scripts/get-note-summary'; import { MenuItem } from '@/types/menu'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { showMovedDialog } from '@/scripts/show-moved-dialog'; +import { shouldCollapsed } from '@/scripts/collapsed'; const props = defineProps<{ note: misskey.entities.Note; @@ -204,17 +205,7 @@ let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note const isMyRenote = $i && ($i.id === note.userId); const showContent = ref(false); const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null; -const isLong = (appearNote.cw == null && appearNote.text != null && ( - (appearNote.text.includes('$[x2')) || - (appearNote.text.includes('$[x3')) || - (appearNote.text.includes('$[x4')) || - (appearNote.text.includes('$[scale')) || - (appearNote.text.includes('$[position')) || - (appearNote.text.split('\n').length > 9) || - (appearNote.text.length > 500) || - (appearNote.files.length >= 5) || - (urls && urls.length >= 4) -)); +const isLong = shouldCollapsed(appearNote); const collapsed = ref(appearNote.cw == null && isLong); const isDeleted = ref(false); const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords)); @@ -222,7 +213,7 @@ const translation = ref(null); const translating = ref(false); const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance); const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id); -let renoteCollapsed = $ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.userId)) || (appearNote.myReaction != null))); +let renoteCollapsed = $ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.userId || $i.id === appearNote.userId)) || (appearNote.myReaction != null))); const keymap = { 'r': () => reply(true), @@ -259,6 +250,17 @@ useTooltip(renoteButton, async (showing) => { }, {}, 'closed'); }); +type Visibility = 'public' | 'home' | 'followers' | 'specified'; + +// defaultStore.state.visibilityがstringなためstringも受け付けている +function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility { + if (a === 'specified' || b === 'specified') return 'specified'; + if (a === 'followers' || b === 'followers') return 'followers'; + if (a === 'home' || b === 'home') return 'home'; + // if (a === 'public' || b === 'public') + return 'public'; +} + function renote(viaKeyboard = false) { pleaseLogin(); showMovedDialog(); @@ -309,7 +311,12 @@ function renote(viaKeyboard = false) { os.popup(MkRippleEffect, { x, y }, {}, 'end'); } + const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility; + const localOnly = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly; + os.api('notes/create', { + localOnly, + visibility: smallerVisibility(appearNote.visibility, configuredVisibility), renoteId: appearNote.id, }).then(() => { os.toast(i18n.ts.renoted); diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index a65039277b8e..1f8a36b8de56 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -293,7 +293,7 @@ function renote(viaKeyboard = false) { const y = rect.top + (el.offsetHeight / 2); os.popup(MkRippleEffect, { x, y }, {}, 'end'); } - + os.api('notes/create', { renoteId: appearNote.id, }).then(() => { diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index 709b5a52dfe5..6e35ad42418d 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -17,25 +17,27 @@ -
+
+ + diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index aabebb3abf75..69d495d86f7d 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -6,7 +6,7 @@ :class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.large]: defaultStore.state.largeNoteReactions }]" @click="toggleReaction()" > - + {{ count }} diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue index 9f56189f3efc..276bd6f98433 100644 --- a/packages/frontend/src/components/MkRetentionLineChart.vue +++ b/packages/frontend/src/components/MkRetentionLineChart.vue @@ -90,6 +90,7 @@ onMounted(async () => { ticks: { callback: (value, index, values) => value + '%', }, + min: 0, }, }, interaction: { diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue index b6ffba6cc708..de5195ab4fde 100644 --- a/packages/frontend/src/components/MkSignupDialog.rules.vue +++ b/packages/frontend/src/components/MkSignupDialog.rules.vue @@ -9,7 +9,10 @@ {{ i18n.ts.invitationRequiredToRegister }}
-
{{ i18n.ts.pleaseConfirmBelowBeforeSignup }}
+
+
{{ i18n.ts.pleaseConfirmBelowBeforeSignup }}
+
{{ i18n.ts.beSureToReadThisAsItIsImportant }}
+
@@ -19,7 +22,7 @@
  • - {{ i18n.ts.agree }} + {{ i18n.ts.agree }}
    @@ -28,7 +31,7 @@ {{ i18n.ts.termsOfService }} - {{ i18n.ts.agree }} + {{ i18n.ts.agree }} @@ -37,7 +40,7 @@ {{ i18n.ts.basicNotesBeforeCreateAccount }} - {{ i18n.ts.agree }} + {{ i18n.ts.agree }}
    {{ i18n.ts.pleaseAgreeAllToContinue }}
    @@ -52,13 +55,14 @@ diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index 3a050889c8d6..3a032a116754 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -15,9 +15,12 @@ {{ i18n.ts.poll }} - + @@ -28,16 +31,15 @@ import MkMediaList from '@/components/MkMediaList.vue'; import MkPoll from '@/components/MkPoll.vue'; import { i18n } from '@/i18n'; import { $i } from '@/account'; +import { shouldCollapsed } from '@/scripts/collapsed'; const props = defineProps<{ note: misskey.entities.Note; }>(); -const collapsed = $ref( - props.note.cw == null && props.note.text != null && ( - (props.note.text.split('\n').length > 9) || - (props.note.text.length > 500) - )); +const isLong = shouldCollapsed(props.note); + +const collapsed = $ref(isLong); diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index 72b70416d9b9..0bc9b0316081 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -46,7 +46,7 @@ defineProps<{ margin: 0 0 8px 0; font-size: 0.9em; } - + > .items { > .item { display: flex; diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index fcad5b8064f8..f7b1b7dfff17 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -32,7 +32,7 @@
    - +
    @@ -52,19 +52,21 @@
    -
    - - {{ i18n.ts.expandTweet }} - -
    -
    - - {{ i18n.ts.enablePlayer }} - - - {{ i18n.ts.openInWindow }} - -
    +
    @@ -85,9 +87,11 @@ const props = withDefaults(defineProps<{ url: string; detail?: boolean; compact?: boolean; + showActions?: boolean; }>(), { detail: false, compact: false, + showActions: true, }); const MOBILE_THRESHOLD = 500; diff --git a/packages/frontend/src/components/MkUrlPreviewPopup.vue b/packages/frontend/src/components/MkUrlPreviewPopup.vue index 36a9e2f73fbe..d360169c8246 100644 --- a/packages/frontend/src/components/MkUrlPreviewPopup.vue +++ b/packages/frontend/src/components/MkUrlPreviewPopup.vue @@ -1,7 +1,7 @@ diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue index 172b51751115..5e538cc52860 100644 --- a/packages/frontend/src/components/MkUserInfo.vue +++ b/packages/frontend/src/components/MkUserInfo.vue @@ -15,13 +15,13 @@
    -

    {{ i18n.ts.notes }}

    {{ user.notesCount }} +

    {{ i18n.ts.notes }}

    {{ number(user.notesCount) }}
    -
    -

    {{ i18n.ts.following }}

    {{ user.followingCount }} +
    +

    {{ i18n.ts.following }}

    {{ number(user.followingCount) }}
    -
    -

    {{ i18n.ts.followers }}

    {{ user.followersCount }} +
    +

    {{ i18n.ts.followers }}

    {{ number(user.followersCount) }}
    @@ -31,9 +31,11 @@ diff --git a/packages/frontend/src/components/global/i18n.ts b/packages/frontend/src/components/global/i18n.ts index 2708b759aa84..6706d08f2f9f 100644 --- a/packages/frontend/src/components/global/i18n.ts +++ b/packages/frontend/src/components/global/i18n.ts @@ -11,13 +11,13 @@ export default function(props: { src: string; tag?: string; textTag?: string; }, parsed.push(str); break; } else { - if (nextBracketOpen > 0) parsed.push(str.substr(0, nextBracketOpen)); + if (nextBracketOpen > 0) parsed.push(str.substring(0, nextBracketOpen)); parsed.push({ arg: str.substring(nextBracketOpen + 1, nextBracketClose), }); } - str = str.substr(nextBracketClose + 1); + str = str.substring(nextBracketClose + 1); } return h(props.tag ?? 'span', parsed.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : slots[x.arg]())); diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts index ad7fa372e97c..1d883c038ea6 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend/src/const.ts @@ -57,6 +57,9 @@ export const ROLE_POLICIES = [ 'ltlAvailable', 'canPublicNote', 'canInvite', + 'inviteLimit', + 'inviteLimitCycle', + 'inviteExpirationTime', 'canManageCustomEmojis', 'canSearchNotes', 'canHideAds', diff --git a/packages/frontend/src/directives/adaptive-bg.ts b/packages/frontend/src/directives/adaptive-bg.ts index 313aad79964c..83bcd7089d5d 100644 --- a/packages/frontend/src/directives/adaptive-bg.ts +++ b/packages/frontend/src/directives/adaptive-bg.ts @@ -10,7 +10,7 @@ export default { return el.parentElement ? getBgColor(el.parentElement) : 'transparent'; } }; - + const parentBg = getBgColor(src.parentElement); const myBg = window.getComputedStyle(src).backgroundColor; diff --git a/packages/frontend/src/directives/adaptive-border.ts b/packages/frontend/src/directives/adaptive-border.ts index 619c9f0b6d50..5bd04024beef 100644 --- a/packages/frontend/src/directives/adaptive-border.ts +++ b/packages/frontend/src/directives/adaptive-border.ts @@ -10,7 +10,7 @@ export default { return el.parentElement ? getBgColor(el.parentElement) : 'transparent'; } }; - + const parentBg = getBgColor(src.parentElement); const myBg = window.getComputedStyle(src).backgroundColor; diff --git a/packages/frontend/src/directives/panel.ts b/packages/frontend/src/directives/panel.ts index d31dc41ed416..8727183d3652 100644 --- a/packages/frontend/src/directives/panel.ts +++ b/packages/frontend/src/directives/panel.ts @@ -10,7 +10,7 @@ export default { return el.parentElement ? getBgColor(el.parentElement) : 'transparent'; } }; - + const parentBg = getBgColor(src.parentElement); const myBg = getComputedStyle(document.documentElement).getPropertyValue('--panel'); diff --git a/packages/frontend/src/filters/date.ts b/packages/frontend/src/filters/date.ts index 706b7d60cc12..9bc9bfe8abb7 100644 --- a/packages/frontend/src/filters/date.ts +++ b/packages/frontend/src/filters/date.ts @@ -1,4 +1,4 @@ import { dateTimeFormat } from '@/scripts/intl-const'; -export default (d: Date | number | undefined) => dateTimeFormat.format(d); +export default (d: Date | number | undefined) => dateTimeFormat.format(d); export const dateString = (d: string) => dateTimeFormat.format(new Date(d)); diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts index ca4f21f79b06..f9d04f7950af 100644 --- a/packages/frontend/src/local-storage.ts +++ b/packages/frontend/src/local-storage.ts @@ -14,7 +14,7 @@ type Keys = 'wallpaper' | 'theme' | 'colorScheme' | - 'useSystemFont' | + 'useSystemFont' | 'fontSize' | 'ui' | 'ui_temp' | diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/nirax.ts index 68977ed796fa..3a03444de231 100644 --- a/packages/frontend/src/nirax.ts +++ b/packages/frontend/src/nirax.ts @@ -1,8 +1,7 @@ // NIRAX --- A lightweight router import { EventEmitter } from 'eventemitter3'; -import { Component, shallowRef, ShallowRef } from 'vue'; -import { pleaseLogin } from '@/scripts/please-login'; +import { Component, onMounted, shallowRef, ShallowRef } from 'vue'; import { safeURIDecode } from '@/scripts/safe-uri-decode'; type RouteDef = { @@ -23,7 +22,7 @@ type ParsedPath = (string | { optional?: boolean; })[]; -export type Resolved = { route: RouteDef; props: Map; child?: Resolved; }; +export type Resolved = { route: RouteDef; props: Map; child?: Resolved; }; function parsePath(path: string): ParsedPath { const res = [] as ParsedPath; @@ -75,15 +74,19 @@ export class Router extends EventEmitter<{ public currentRef: ShallowRef = shallowRef(); public currentRoute: ShallowRef = shallowRef(); private currentPath: string; + private isLoggedIn: boolean; + private notFoundPageComponent: Component; private currentKey = Date.now().toString(); public navHook: ((path: string, flag?: any) => boolean) | null = null; - constructor(routes: Router['routes'], currentPath: Router['currentPath']) { + constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) { super(); this.routes = routes; this.currentPath = currentPath; + this.isLoggedIn = isLoggedIn; + this.notFoundPageComponent = notFoundPageComponent; this.navigate(currentPath, null, false); } @@ -159,11 +162,11 @@ export class Router extends EventEmitter<{ if (route.hash != null && hash != null) { props.set(route.hash, safeURIDecode(hash)); } - + if (route.query != null && queryString != null) { const queryObject = [...new URLSearchParams(queryString).entries()] .reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {}); - + for (const q in route.query) { const as = route.query[q]; if (queryObject[q]) { @@ -171,7 +174,7 @@ export class Router extends EventEmitter<{ } } } - + return { route, props, @@ -212,8 +215,9 @@ export class Router extends EventEmitter<{ throw new Error('no route found for: ' + path); } - if (res.route.loginRequired) { - pleaseLogin('/'); + if (res.route.loginRequired && !this.isLoggedIn) { + res.route.component = this.notFoundPageComponent; + res.props.set('showLoginPopup', true); } const isSamePath = beforePath === path; @@ -263,13 +267,33 @@ export class Router extends EventEmitter<{ }); } - public replace(path: string, key?: string | null, emitEvent = true) { + public replace(path: string, key?: string | null) { this.navigate(path, key); - if (emitEvent) { - this.emit('replace', { - path, - key: this.currentKey, - }); - } } } + +export function useScrollPositionManager(getScrollContainer: () => HTMLElement, router: Router) { + const scrollPosStore = new Map(); + + onMounted(() => { + const scrollContainer = getScrollContainer(); + + scrollContainer.addEventListener('scroll', () => { + scrollPosStore.set(router.getCurrentKey(), scrollContainer.scrollTop); + }, { passive: true }); + + router.addListener('change', ctx => { + const scrollPos = scrollPosStore.get(ctx.key) ?? 0; + scrollContainer.scroll({ top: scrollPos, behavior: 'instant' }); + if (scrollPos !== 0) { + window.setTimeout(() => { // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール + scrollContainer.scroll({ top: scrollPos, behavior: 'instant' }); + }, 100); + } + }); + + router.addListener('same', () => { + scrollContainer.scroll({ top: 0, behavior: 'smooth' }); + }); + }); +} diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index c44d3480462a..1a5ed90541c9 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -460,11 +460,13 @@ export async function pickEmoji(src: HTMLElement | null, opts) { export async function cropImage(image: Misskey.entities.DriveFile, options: { aspectRatio: number; + uploadFolder?: string | null; }): Promise { return new Promise((resolve, reject) => { popup(defineAsyncComponent(() => import('@/components/MkCropperDialog.vue')), { file: image, aspectRatio: options.aspectRatio, + uploadFolder: options.uploadFolder, }, { ok: x => { resolve(x); diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index 0017145fa151..6d2f7e155e44 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -88,10 +88,13 @@
    - Mask Network + Mask Network
    - DC Advirth + Skeb +
    +
    + DC Advirth
    @@ -155,6 +158,30 @@ const patronsWithIcon = [{ }, { name: 'spinlock', icon: 'https://misskey-hub.net/patrons/6a1cebc819d540a78bf20e9e3115baa8.jpg', +}, { + name: 'じゅくま', + icon: 'https://misskey-hub.net/patrons/3e56bdac69dd42f7a06e0f12cf2fc895.jpg', +}, { + name: '清遊あみ', + icon: 'https://misskey-hub.net/patrons/de25195b88e940a388388bea2e7637d8.jpg', +}, { + name: 'Nagi8410', + icon: 'https://misskey-hub.net/patrons/31b102ab4fc540ed806b0461575d38be.jpg', +}, { + name: '山岡士郎', + icon: 'https://misskey-hub.net/patrons/84b9056341684266bb1eda3e680d094d.jpg', +}, { + name: 'よもやまたろう', + icon: 'https://misskey-hub.net/patrons/4273c9cce50d445f8f7d0f16113d6d7f.jpg', +}, { + name: '花咲ももか', + icon: 'https://misskey-hub.net/patrons/8c9b2b9128cb4fee99f04bb4f86f2efa.jpg', +}, { + name: 'カガミ', + icon: 'https://misskey-hub.net/patrons/226ea3a4617749548580ec2d9a263e24.jpg', +}, { + name: 'フランギ・シュウ', + icon: 'https://misskey-hub.net/patrons/3016d37e35f3430b90420176c912d304.jpg', }]; const patrons = [ @@ -250,6 +277,9 @@ const patrons = [ 'binvinyl', '渡志郎', 'ぷーざ', + '越貝鯛丸', + 'Nick / pprmint.', + 'kino3277', ]; let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure')); diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue index 3744bed10ff6..cc0bf2eed21c 100644 --- a/packages/frontend/src/pages/about.emojis.vue +++ b/packages/frontend/src/pages/about.emojis.vue @@ -20,7 +20,7 @@
    - +
    @@ -56,7 +56,7 @@ function search() { const queryarry = q.match(/\:([a-z0-9_]*)\:/g); if (queryarry) { - searchEmojis = customEmojis.value.filter(emoji => + searchEmojis = customEmojis.value.filter(emoji => queryarry.includes(`:${emoji.name}:`), ); } else { diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue index 24c863ba626e..57e50b692ced 100644 --- a/packages/frontend/src/pages/admin-file.vue +++ b/packages/frontend/src/pages/admin-file.vue @@ -32,7 +32,7 @@
    - NSFW + {{ i18n.ts.sensitive }}
    diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue index 3bc5ee972358..9cf96d3d0450 100644 --- a/packages/frontend/src/pages/admin/abuses.vue +++ b/packages/frontend/src/pages/admin/abuses.vue @@ -75,7 +75,7 @@ const pagination = { }; function resolved(reportId) { - reports.removeItem(item => item.id === reportId); + reports.removeItem(reportId); } const headerActions = $computed(() => []); diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue index 2c9e18b0bffb..9a5bd88b2e54 100644 --- a/packages/frontend/src/pages/admin/ads.vue +++ b/packages/frontend/src/pages/admin/ads.vue @@ -36,6 +36,16 @@ + + + + {{ i18n.ts._ad.timezoneinfo }} +
    + + +
    +
    +
    @@ -59,6 +69,7 @@ import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkRadios from '@/components/MkRadios.vue'; +import MkFolder from '@/components/MkFolder.vue'; import FormSplit from '@/components/form/split.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; @@ -69,6 +80,7 @@ let ads: any[] = $ref([]); // ISO形式はTZがUTCになってしまうので、TZ分ずらして時間を初期化 const localTime = new Date(); const localTimeDiff = localTime.getTimezoneOffset() * 60 * 1000; +const daysOfWeek: string[] = [i18n.ts._weekday.sunday, i18n.ts._weekday.monday, i18n.ts._weekday.tuesday, i18n.ts._weekday.wednesday, i18n.ts._weekday.thursday, i18n.ts._weekday.friday, i18n.ts._weekday.saturday]; os.api('admin/ad/list').then(adsResponse => { ads = adsResponse.map(r => { @@ -84,6 +96,11 @@ os.api('admin/ad/list').then(adsResponse => { }); }); +// 選択された曜日(index)のビットフラグを操作する +function toggleDayOfWeek(ad, index) { + ad.dayOfWeek ^= 1 << index; +} + function add() { ads.unshift({ id: null, @@ -95,6 +112,7 @@ function add() { imageUrl: null, expiresAt: null, startsAt: null, + dayOfWeek: 0, }); } @@ -105,6 +123,7 @@ function remove(ad) { }).then(({ canceled }) => { if (canceled) return; ads = ads.filter(x => x !== ad); + if (ad.id == null) return; os.apiWithDialog('admin/ad/delete', { id: ad.id, }); diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index 8b083bc89654..e91f65b5d57e 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -1,6 +1,6 @@ - +
    + +
    diff --git a/packages/frontend/src/ui/minimum.vue b/packages/frontend/src/ui/minimum.vue index e656f00bb209..baba9e4da566 100644 --- a/packages/frontend/src/ui/minimum.vue +++ b/packages/frontend/src/ui/minimum.vue @@ -1,6 +1,8 @@