diff --git a/.config/example.yml b/.config/example.yml index c179395966..fcf22debce 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 └──────────────────────────────── @@ -104,6 +108,7 @@ redis: # apiKey: '' # ssl: true # index: '' +# scope: local # ┌───────────────┐ #───┘ ID generation └─────────────────────────────────────────── diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a47804ab07..0664ecd110 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 8f8c5a13ab..2809cd2ca4 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 a6f988f8d7..def7baa1a8 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 25e7fc8b20..b889d96eb3 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 6cb1b34997..1aea8b5459 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 d7be15bd4f..96e64c322e 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 4ea4ba4628..eef68aa0d1 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 b15e704c7f..213657ce1f 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 5243a83777..8429465b5b 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 537232d37f..a66e527db0 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 6d80269a4f..dd0fe95cce 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -18.16.0 +20.3.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 60811201be..97e02174e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,14 +17,53 @@ ### General - identicon生成を無効にしてパフォーマンスを向上させることができるようになりました - サーバーのマシン情報の公開を無効にしてパフォーマンスを向上させることができるようになりました +- 招待機能を改善しました + * 過去に発行した招待コードを確認できるようになりました + * ロールごとに招待コードの発行数制限と制限対象期間、有効期限を設定できるようになりました + * 招待コードを作成したユーザーと使用したユーザーを確認できるようになりました - 通報の即時解決機能の追加 ### Client +- deck UIのカラムのメニューからアンテナとリストの編集画面を開けるように +- ドライブファイルのメニューで画像をクロップできるように +- 画像を動画と同様に簡単に隠せるように +- オリジナル画像を保持せずにアップロードする場合webpでアップロードされるように(Safari以外) +- 見たことのあるRenoteを省略して表示をオンのときに自分のnoteのrenoteを省略するように +- フォルダーやファイルに対しても開発者モード使用時、IDをコピーできるように +- 引用対象を「もっと見る」で展開した場合、「閉じる」で畳めるように +- プロフィールURLをコピーできるボタンを追加 #11190 +- `CURRENT_URL`で現在表示中のURLを取得できるように(AiScript) +- ユーザーのContextMenuに「アンテナに追加」ボタンを追加 +- フォローやお気に入り登録をしていないチャンネルを開く時は概要ページを開くように +- 画面ビューワをタップした場合、マウスクリックと同様に画像ビューワを閉じるように +- オフライン時の画面にリロードボタンを追加 +- Deckで非ルートページにアクセスした際に簡易UIで表示しない設定を追加 +- ロール設定画面でロールIDを確認できるように +- コンテキストメニュー表示時のパフォーマンスを改善 - 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回数を減らしました +- リモートサーバーからのNSFW映像のキャッシュだけを無効化できるオプションを追加 +- MeilisearchにIndexするノートの範囲を設定できるように +- Export notes with file detail +- Add unix socket support +- Fix: リモートサーバーに無意味なActivityPubの配信を行うことがあるのを修正 +- Fix: Remove Meilisearch index when notes are deleted +- Fix: 非英語環境でのPostgreSQLのエラーハンドリングを修正 +- Fix: インスタンスのアイコンがbase64の場合の挙動を修正 ## 13.13.2 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6b3804f84..896fb6b089 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. ``` diff --git a/Dockerfile b/Dockerfile index fb389659bc..5431c28aad 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 2aae4bb865..ab4388c2eb 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 43205ac1c4..ed1749e321 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 2e4f93e848..8e61c70484 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 a0a2450054..be0aed6524 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 40bae90b5e..dc5807b2dd 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 89050e10f0..e884455709 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 7c9ddadbf8..8fc5e0de5b 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -222,7 +222,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), diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index a65039277b..1f8a36b8de 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 709b5a52df..6e35ad4241 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 aabebb3abf..69d495d86f 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/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index 3a050889c8..c76ce7315d 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -15,9 +15,12 @@ {{ i18n.ts.poll }} - +
@@ -33,11 +36,13 @@ const props = defineProps<{ note: misskey.entities.Note; }>(); -const collapsed = $ref( +const isLong = props.note.cw == null && props.note.text != null && ( (props.note.text.split('\n').length > 9) || (props.note.text.length > 500) - )); + ); + +const collapsed = $ref(isLong); diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index 72b70416d9..0bc9b03160 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 fcad5b8064..5c37f70bd9 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -32,7 +32,7 @@
- +
diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue index c3b777a12e..3b6e348e0b 100644 --- a/packages/frontend/src/components/MkUserPopup.vue +++ b/packages/frontend/src/components/MkUserPopup.vue @@ -88,7 +88,7 @@ onMounted(() => { user = props.q; } else { const query = props.q.startsWith('@') ? - Acct.parse(props.q.substr(1)) : + Acct.parse(props.q.substring(1)) : { userId: props.q }; os.api('users/show', query).then(res => { @@ -195,7 +195,7 @@ onMounted(() => { .mfm { display: -webkit-box; -webkit-line-clamp: 5; - -webkit-box-orient: vertical; + -webkit-box-orient: vertical; overflow: hidden; } diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts index 7d5a65f41a..67243b78f3 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts +++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts @@ -26,7 +26,7 @@ export const Default = { }; }, args: { - + }, parameters: { layout: 'centered', diff --git a/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts index 70817d83c3..0726289722 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts +++ b/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts @@ -23,7 +23,7 @@ export const Default = { }; }, args: { - + }, parameters: { layout: 'centered', diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts index f4930aa26b..3444605e97 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts +++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts @@ -23,7 +23,7 @@ export const Default = { }; }, args: { - + }, parameters: { layout: 'centered', diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.vue b/packages/frontend/src/components/MkUserSetupDialog.User.vue index d66f34f165..b35f27c5b0 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.User.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.User.vue @@ -90,7 +90,7 @@ async function follow() { .mfm { display: -webkit-box; -webkit-line-clamp: 5; - -webkit-box-orient: vertical; + -webkit-box-orient: vertical; overflow: hidden; } diff --git a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts index 55790602d5..f47f4c13d5 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts +++ b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts @@ -26,7 +26,7 @@ export const Default = { }; }, args: { - + }, parameters: { layout: 'centered', diff --git a/packages/frontend/src/components/global/MkA.stories.impl.ts b/packages/frontend/src/components/global/MkA.stories.impl.ts index 639ed19af2..6e3ff573cb 100644 --- a/packages/frontend/src/components/global/MkA.stories.impl.ts +++ b/packages/frontend/src/components/global/MkA.stories.impl.ts @@ -29,11 +29,11 @@ export const Default = { const canvas = within(canvasElement); const a = canvas.getByRole('link'); await expect(a.href).toMatch(/^https?:\/\/.*#test$/); - await userEvent.click(a, { button: 2 }); + await userEvent.pointer({ keys: '[MouseRight]', target: a }); await tick(); const menu = canvas.getByRole('menu'); await expect(menu).toBeInTheDocument(); - await userEvent.click(a, { button: 0 }); + await userEvent.click(a); a.blur(); await tick(); await expect(menu).not.toBeInTheDocument(); diff --git a/packages/frontend/src/components/global/MkAd.stories.impl.ts b/packages/frontend/src/components/global/MkAd.stories.impl.ts index 7d8a42a03c..58b6cc14c4 100644 --- a/packages/frontend/src/components/global/MkAd.stories.impl.ts +++ b/packages/frontend/src/components/global/MkAd.stories.impl.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { expect } from '@storybook/jest'; -import { userEvent, within } from '@storybook/testing-library'; +import { userEvent, waitFor, within } from '@storybook/testing-library'; import { StoryObj } from '@storybook/vue3'; -import { i18n } from '@/i18n'; import MkAd from './MkAd.vue'; +import { i18n } from '@/i18n'; const common = { render(args) { return { @@ -36,6 +36,7 @@ const common = { const i = buttons[0]; await expect(i).toBeInTheDocument(); await userEvent.click(i); + await waitFor(() => expect(canvasElement).toHaveTextContent(i18n.ts._ad.back)); await expect(a).not.toBeInTheDocument(); await expect(i).not.toBeInTheDocument(); buttons = canvas.getAllByRole('button'); @@ -49,6 +50,7 @@ const common = { await expect(back).toBeInTheDocument(); await expect(back).toHaveTextContent(i18n.ts._ad.back); await userEvent.click(back); + await waitFor(() => expect(canvas.queryByRole('img')).toBeTruthy()); if (reduce) { await expect(reduce).not.toBeInTheDocument(); } diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index e8a7f17cc6..e7af472682 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -18,7 +18,7 @@ const props = defineProps<{ useOriginalSize?: boolean; }>(); -const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substr(1, props.name.length - 2) : props.name).replace('@.', '')); +const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substring(1, props.name.length - 1) : props.name).replace('@.', '')); const isLocal = computed(() => !props.host && (customEmojiName.value.endsWith('@.') || !customEmojiName.value.includes('@'))); const rawUrl = computed(() => { diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts index 2a50a34390..1c417991e0 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts @@ -199,7 +199,7 @@ export default function(props: { } const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5); const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5); - style = `transform: scale(${x}, ${y});`; + style = `transform: scale(${x}, ${y});`; scale = scale * Math.max(x, y); break; } @@ -256,7 +256,7 @@ export default function(props: { case 'mention': { return [h(MkMention, { key: Math.random(), - host: (token.props.host == null && props.author && props.author.host != null ? props.author.host : token.props.host) || host, + host: (token.props.host == null && props.author && props.author.host != null ? props.author.host : token.props.host) ?? host, username: token.props.username, })]; } diff --git a/packages/frontend/src/components/global/i18n.ts b/packages/frontend/src/components/global/i18n.ts index 2708b759aa..6706d08f2f 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 ad7fa372e9..1d883c038e 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 313aad7996..83bcd7089d 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 619c9f0b6d..5bd04024be 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 d31dc41ed4..8727183d36 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 706b7d60cc..9bc9bfe8ab 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 ca4f21f79b..f9d04f7950 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 68977ed796..3a03444de2 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 c44d348046..1a5ed90541 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 0017145fa1..39d9d020de 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,27 @@ 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', }]; const patrons = [ @@ -250,6 +274,8 @@ const patrons = [ 'binvinyl', '渡志郎', 'ぷーざ', + '越貝鯛丸', + 'Nick / pprmint.', ]; 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 3744bed10f..cc0bf2eed2 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/ads.vue b/packages/frontend/src/pages/admin/ads.vue index 2c9e18b0bf..9a5bd88b2e 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 8b083bc896..e91f65b5d5 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 e656f00bb2..baba9e4da5 100644 --- a/packages/frontend/src/ui/minimum.vue +++ b/packages/frontend/src/ui/minimum.vue @@ -1,6 +1,8 @@