diff --git a/.config/example.yml b/.config/example.yml index 900ac0905..02661b7fb 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -96,6 +96,9 @@ id: 'aid' # Max note length, should be < 8000. #maxNoteLength: 3000 +# Maximum lenght of an image caption or file comment (default 1500, max 8192) +#maxCaptionLength: 1500 + # Whether disable HSTS #disableHsts: true diff --git a/docs/migrate.md b/docs/migrate.md index ee3446c85..cdb59951f 100644 --- a/docs/migrate.md +++ b/docs/migrate.md @@ -19,6 +19,8 @@ git remote set-url origin https://codeberg.org/calckey/calckey.git git fetch git checkout main # or beta or develop git pull --ff + +NODE_ENV=production pnpm run migrate # build using prefered method ``` @@ -29,6 +31,8 @@ git remote set-url origin https://codeberg.org/calckey/calckey.git git fetch git checkout main # or beta or develop git pull --ff + +NODE_ENV=production pnpm run migrate # build using prefered method ``` @@ -48,5 +52,7 @@ git remote set-url origin https://codeberg.org/calckey/calckey.git git fetch git checkout main # or beta or develop git pull --ff + +NODE_ENV=production pnpm run migrate # build using prefered method ``` diff --git a/gulpfile.js b/gulpfile.js index 87063c0bc..f994f0029 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -24,10 +24,6 @@ gulp.task('copy:client:fonts', () => gulp.src('./packages/client/node_modules/three/examples/fonts/**/*').pipe(gulp.dest('./built/_client_dist_/fonts/')) ); -gulp.task('copy:client:phosphor', () => - gulp.src('./node_modules/phosphor-icons/src/fonts/*').pipe(gulp.dest('./built/_client_dist_/phosphor/')) -); - gulp.task('copy:client:locales', cb => { fs.mkdirSync('./built/_client_dist_/locales', { recursive: true }); @@ -59,7 +55,7 @@ gulp.task('build:backend:style', () => { }); gulp.task('build', gulp.parallel( - 'copy:client:locales', 'copy:backend:views', 'copy:backend:custom', 'build:backend:script', 'build:backend:style', 'copy:client:fonts', 'copy:client:phosphor' + 'copy:client:locales', 'copy:backend:views', 'copy:backend:custom', 'build:backend:script', 'build:backend:style', 'copy:client:fonts' )); gulp.task('default', gulp.task('build')); diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index f32927519..360ef4c22 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -69,8 +69,8 @@ exportRequested: "Has sol·licitat una exportació. Això pot trigar una estona. importRequested: "Has sol·licitat una importació. Això pot trigar una estona." lists: "Llistes" noLists: "No tens cap llista" -note: "Nota" -notes: "Notes" +note: "Post" +notes: "Posts" following: "Seguint" followers: "Seguidors" followsYou: "Et segueix" @@ -141,7 +141,7 @@ _theme: mention: "Menció" renote: "Renotar" _sfx: - note: "Notes" + note: "Posts" notification: "Notificacions" _2fa: step2Url: "També pots inserir aquest enllaç i utilitzes una aplicació d'escriptori:" diff --git a/locales/en-US.yml b/locales/en-US.yml index a8a3e056a..775327b47 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1806,7 +1806,7 @@ _apps: pwa: "Install PWA" kaiteki: "Kaiteki" milktea: "Milktea" - subwayTooter: "Subway Tooter" - kimis: "Kimis" + missLi: "MissLi" + mona: "Mona" theDesk: "TheDesk" lesskey: "Lesskey" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 9f7028d3b..2d918b5ad 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -70,8 +70,8 @@ exportRequested: "Vous avez demandé une exportation. L’opération pourrait pr importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps." lists: "Listes" noLists: "Vous n’avez aucune liste" -note: "Notes" -notes: "Notes" +note: "Post" +notes: "Posts" following: "Abonnements" followers: "Abonné·e·s" followsYou: "Vous suit" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 5372c8e3d..fe672c16e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1,7 +1,7 @@ +--- _lang_: "日本語" - -headlineMisskey: "ノートでつながるネットワーク" -introMisskey: "ようこそ!Misskeyは、オープンソースの分散型マイクロブログサービスです。\n「ノート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「リアクション」機能で、皆のノートに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀" +headlineMisskey: "ずっと無料でオープンソースの非中央集権型ソーシャルメディアプラットフォーム🚀" +introMisskey: "ようこそ!Calckeyは、オープンソースの非中央集権型ソーシャルメディアプラットフォームです。\nいま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「リアクション」機能で、皆の投稿に素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀" monthAndDay: "{month}月 {day}日" search: "検索" notifications: "通知" @@ -10,11 +10,11 @@ password: "パスワード" forgotPassword: "パスワードを忘れた" fetchingAsApObject: "連合に照会中" ok: "OK" -gotIt: "わかった" +gotIt: "わかった!" cancel: "キャンセル" enterUsername: "ユーザー名を入力" -renotedBy: "{user}がRenote" -noNotes: "ノートはありません" +renotedBy: "{user}がブースト" +noNotes: "投稿はありません" noNotifications: "通知はありません" instance: "インスタンス" settings: "設定" @@ -32,6 +32,7 @@ uploading: "アップロード中" save: "保存" users: "ユーザー" addUser: "ユーザーを追加" +addInstance: "インスタンスを追加" favorite: "お気に入り" favorites: "お気に入り" unfavorite: "お気に入り解除" @@ -44,7 +45,7 @@ copyContent: "内容をコピー" copyLink: "リンクをコピー" delete: "削除" deleteAndEdit: "削除して編集" -deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、Renote、返信も全て削除されます。" +deleteAndEditConfirm: "この投稿を削除してもう一度編集しますか?この投稿へのリアクション、ブースト、返信も全て削除されます。" addToList: "リストに追加" sendMessage: "メッセージを送信" copyUsername: "ユーザー名をコピー" @@ -64,14 +65,14 @@ import: "インポート" export: "エクスポート" files: "ファイル" download: "ダウンロード" -driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを添付したノートも消えます。" +driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを添付した投稿も消えます。" unfollowConfirm: "{name}のフォローを解除しますか?" exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。" importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。" lists: "リスト" noLists: "リストはありません" -note: "ノート" -notes: "ノート" +note: "投稿" +notes: "投稿" following: "フォロー" followers: "フォロワー" followsYou: "フォローされています" @@ -94,13 +95,13 @@ followRequests: "フォロー申請" unfollow: "フォロー解除" followRequestPending: "フォロー許可待ち" enterEmoji: "絵文字を入力" -renote: "Renote" -unrenote: "Renote解除" -renoted: "Renoteしました。" -cantRenote: "この投稿はRenoteできません。" -cantReRenote: "RenoteをRenoteすることはできません。" +renote: "ブースト" +unrenote: "ブースト解除" +renoted: "ブーストしました。" +cantRenote: "この投稿はブーストできません。" +cantReRenote: "ブーストをブーストすることはできません。" quote: "引用" -pinnedNote: "ピン留めされたノート" +pinnedNote: "ピン留めされた投稿" pinned: "ピン留め" you: "あなた" clickToShow: "クリックして表示" @@ -139,11 +140,11 @@ settingGuide: "おすすめ設定" cacheRemoteFiles: "リモートのファイルをキャッシュする" cacheRemoteFilesDescription: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。サーバーのストレージを節約できますが、サムネイルが生成されないので通信量が増加します。" flagAsBot: "Botとして設定" -flagAsBotDescription: "このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Misskeyのシステム上での扱いがBotに合ったものになります。" -flagAsCat: "Catとして設定" +flagAsBotDescription: "このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Calckeyのシステム上での扱いがBotに合ったものになります。" +flagAsCat: "あなたは…猫?😺" flagAsCatDescription: "このアカウントが猫であることを示す場合は、このフラグをオンにします。" -flagShowTimelineReplies: "タイムラインにノートへの返信を表示する" -flagShowTimelineRepliesDescription: "オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。" +flagShowTimelineReplies: "タイムラインに投稿の返信を表示する" +flagShowTimelineRepliesDescription: "オンにすると、タイムラインにユーザーの投稿以外にもそのユーザーの他の投稿への返信を表示します。" autoAcceptFollowed: "フォロー中ユーザーからのフォロリクを自動承認" addAccount: "アカウントを追加" loginFailed: "ログインに失敗しました" @@ -160,6 +161,7 @@ proxyAccount: "プロキシアカウント" proxyAccountDescription: "プロキシアカウントは、特定の条件下でユーザーのリモートフォローを代行するアカウントです。例えば、ユーザーがリモートユーザーをリストに入れたとき、リストに入れられたユーザーを誰もフォローしていないとアクティビティがインスタンスに配達されないため、代わりにプロキシアカウントがフォローするようにします。" host: "ホスト" selectUser: "ユーザーを選択" +selectInstance: "インスタンスを選択" recipient: "宛先" annotation: "注釈" federation: "連合" @@ -197,10 +199,11 @@ muteAndBlock: "ミュートとブロック" mutedUsers: "ミュートしたユーザー" blockedUsers: "ブロックしたユーザー" noUsers: "ユーザーはいません" +noInstances: "インスタンスはありません" editProfile: "プロフィールを編集" -noteDeleteConfirm: "このノートを削除しますか?" +noteDeleteConfirm: "この投稿を削除しますか?" pinLimitExceeded: "これ以上ピン留めできません" -intro: "Misskeyのインストールが完了しました!管理者アカウントを作成しましょう。" +intro: "Calckeyのインストールが完了しました!管理者アカウントを作成しましょう。" done: "完了" processing: "処理中" preview: "プレビュー" @@ -325,7 +328,7 @@ connectService: "接続する" disconnectService: "切断する" enableLocalTimeline: "ローカルタイムラインを有効にする" enableGlobalTimeline: "グローバルタイムラインを有効にする" -enableRecommendedTimeline: "推奨されるタイムラインを有効にする" +enableRecommendedTimeline: "おすすめタイムラインを有効にする" disablingTimelinesInfo: "これらのタイムラインを無効化しても、利便性のため管理者およびモデレーターは引き続き利用することができます。" registration: "登録" enableRegistration: "誰でも新規登録できるようにする" @@ -342,7 +345,7 @@ pinnedUsersDescription: "「みつける」ページなどにピン留めした pinnedPages: "ピン留めページ" pinnedPagesDescription: "インスタンスのトップページにピン留めしたいページのパスを改行で区切って記述します。" pinnedClipId: "ピン留めするクリップのID" -pinnedNotes: "ピン留めされたノート" +pinnedNotes: "ピン留めされた投稿" hcaptcha: "hCaptcha" enableHcaptcha: "hCaptchaを有効にする" hcaptchaSiteKey: "サイトキー" @@ -359,10 +362,11 @@ antennaSource: "受信ソース" antennaKeywords: "受信キーワード" antennaExcludeKeywords: "除外キーワード" antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" -notifyAntenna: "新しいノートを通知する" -withFileAntenna: "ファイルが添付されたノートのみ" +notifyAntenna: "新しい投稿を通知する" +withFileAntenna: "ファイルが添付された投稿のみ" enableServiceworker: "ブラウザへのプッシュ通知を有効にする" antennaUsersDescription: "ユーザー名を改行で区切って指定します" +antennaInstancesDescription: "インスタンスを改行で区切って指定します" caseSensitive: "大文字小文字を区別する" withReplies: "返信を含む" connectedTo: "次のアカウントに接続されています" @@ -393,7 +397,7 @@ securityKeyName: "キーの名前" registerSecurityKey: "セキュリティキーを登録する" lastUsed: "最後の使用" unregister: "登録を解除" -passwordLessLogin: "パスワード無しログイン" +passwordLessLogin: "パスワード無しでログイン" resetPassword: "パスワードをリセット" newPasswordIs: "新しいパスワードは「{password}」です" reduceUiAnimation: "UIのアニメーションを減らす" @@ -422,9 +426,9 @@ messagingWithGroup: "グループでチャット" title: "タイトル" text: "テキスト" enable: "有効にする" -next: "次" +next: "次へ" retype: "再入力" -noteOf: "{user}のノート" +noteOf: "{user}の投稿" inviteToGroup: "グループに招待" quoteAttached: "引用付き" quoteQuestion: "引用として添付しますか?" @@ -482,8 +486,8 @@ accountSettings: "アカウント設定" promotion: "プロモーション" promote: "プロモート" numberOfDays: "日数" -hideThisNote: "このノートを非表示" -showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを表示する" +hideThisNote: "この投稿を非表示" +showFeaturedNotesInTimeline: "タイムラインにおすすめの投稿を表示する" objectStorage: "オブジェクトストレージ" useObjectStorage: "オブジェクトストレージを使用" objectStorageBaseUrl: "Base URL" @@ -504,7 +508,7 @@ objectStorageSetPublicRead: "アップロード時に'public-read'を設定す serverLogs: "サーバーログ" deleteAll: "全て削除" showFixedPostForm: "タイムライン上部に投稿フォームを表示する" -newNoteRecived: "新しいノートがあります" +newNoteRecived: "新しい投稿があります" sounds: "サウンド" listen: "聴く" none: "なし" @@ -519,7 +523,7 @@ recentUsed: "最近使用" install: "インストール" uninstall: "アンインストール" installedApps: "インストールされたアプリ" -nothing: "ありません" +nothing: "まだ何もありません" installedDate: "インストール日時" lastUsedDate: "最終使用日時" state: "状態" @@ -527,10 +531,10 @@ sort: "ソート" ascendingOrder: "昇順" descendingOrder: "降順" scratchpad: "スクラッチパッド" -scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。" +scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Calckeyと対話するコードの記述、実行、結果の確認ができます。" output: "出力" script: "スクリプト" -disablePagesScript: "Pagesのスクリプトを無効にする" +disablePagesScript: "ページのスクリプトを無効にする" updateRemoteUser: "リモートユーザー情報の更新" deleteAllFiles: "すべてのファイルを削除" deleteAllFilesConfirm: "すべてのファイルを削除しますか?" @@ -626,7 +630,7 @@ sample: "サンプル" abuseReports: "通報" reportAbuse: "通報" reportAbuseOf: "{name}を通報する" -fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。" +fillAbuseReportDescription: "通報理由の詳細を記入してください。対象の投稿がある場合はそのURLも記入してください。" abuseReported: "内容が送信されました。ご報告ありがとうございました。" reporter: "通報者" reporteeOrigin: "通報先" @@ -639,7 +643,7 @@ openInNewTab: "新しいタブで開く" openInSideView: "サイドビューで開く" defaultNavigationBehaviour: "デフォルトのナビゲーション" editTheseSettingsMayBreakAccount: "これらの設定を編集するとアカウントが破損する可能性があります。" -instanceTicker: "ノートのインスタンス情報" +instanceTicker: "投稿のインスタンス情報" waitingFor: "{x}を待っています" random: "ランダム" system: "システム" @@ -650,16 +654,16 @@ createNew: "新規作成" optional: "任意" createNewClip: "新しいクリップを作成" unclip: "クリップ解除" -confirmToUnclipAlreadyClippedNote: "このノートはすでにクリップ「{name}」に含まれています。ノートをこのクリップから除外しますか?" +confirmToUnclipAlreadyClippedNote: "この投稿はすでにクリップ「{name}」に含まれています。投稿をこのクリップから除外しますか?" public: "パブリック" i18nInfo: "Calckeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。" manageAccessTokens: "アクセストークンの管理" accountInfo: "アカウント情報" -notesCount: "ノートの数" +notesCount: "投稿の数" repliesCount: "返信した数" -renotesCount: "Renoteした数" +renotesCount: "ブーストした数" repliedCount: "返信された数" -renotedCount: "Renoteされた数" +renotedCount: "ブーストされた数" followingCount: "フォロー数" followersCount: "フォロワー数" sentReactionsCount: "リアクションした数" @@ -671,17 +675,17 @@ no: "いいえ" driveFilesCount: "ドライブのファイル数" driveUsage: "ドライブ使用量" noCrawle: "クローラーによるインデックスを拒否" -noCrawleDescription: "検索エンジンにあなたのユーザーページ、ノート、Pagesなどのコンテンツを登録(インデックス)しないよう要請します。" -lockedAccountInfo: "フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。" +noCrawleDescription: "検索エンジンにあなたのプロフィールや投稿、ページなどのコンテンツを登録(インデックス)しないよう要請します。" +lockedAccountInfo: "フォローを承認制にしても、投稿の公開範囲を「フォロワー」にしない限り、誰でもあなたの投稿を見ることができます。" alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする" loadRawImages: "添付画像のサムネイルをオリジナル画質にする" disableShowingAnimatedImages: "アニメーション画像を再生しない" verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。" notSet: "未設定" emailVerified: "メールアドレスが確認されました" -noteFavoritesCount: "お気に入りノートの数" -pageLikesCount: "Pageにいいねした数" -pageLikedCount: "Pageにいいねされた数" +noteFavoritesCount: "お気に入りの投稿の数" +pageLikesCount: "ページにいいねした数" +pageLikedCount: "ページにいいねされた数" contact: "連絡先" useSystemFont: "システムのデフォルトのフォントを使う" clips: "クリップ" @@ -689,7 +693,7 @@ experimentalFeatures: "実験的機能" developer: "開発者" makeExplorable: "アカウントを見つけやすくする" makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らなくなります。" -showGapBetweenNotesInTimeline: "タイムラインのノートを離して表示" +showGapBetweenNotesInTimeline: "タイムラインの投稿を離して表示" duplicate: "複製" left: "左" center: "中央" @@ -701,9 +705,9 @@ showTitlebar: "タイトルバーを表示する" clearCache: "キャッシュをクリア" onlineUsersCount: "{n}人がオンライン" nUsers: "{n}ユーザー" -nNotes: "{n}ノート" +nNotes: "{n}投稿" sendErrorReports: "エラーリポートを送信" -sendErrorReportsDescription: "オンにすると、問題が発生したときにエラーの詳細情報がMisskeyに共有され、ソフトウェアの品質向上に役立てることができます。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれます。" +sendErrorReportsDescription: "オンにすると、問題が発生したときにエラーの詳細情報がCalckeyに共有され、ソフトウェアの品質向上に役立てることができます。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれます。" myTheme: "マイテーマ" backgroundColor: "背景" accentColor: "アクセント" @@ -742,7 +746,7 @@ unlikeConfirm: "いいね解除しますか?" fullView: "フルビュー" quitFullView: "フルビュー解除" addDescription: "説明を追加" -userPagePinTip: "個々のノートのメニューから「ピン留め」を選択することで、ここにノートを表示しておくことができます。" +userPagePinTip: "個々の投稿のメニューから「ピン留め」を選択することで、ここに投稿を表示しておくことができます。" notSpecifiedMentionWarning: "宛先に含まれていないメンションがあります" info: "情報" userInfo: "ユーザー情報" @@ -772,7 +776,7 @@ postToGallery: "ギャラリーへ投稿" gallery: "ギャラリー" recentPosts: "最近の投稿" popularPosts: "人気の投稿" -shareWithNote: "ノートで共有" +shareWithNote: "投稿で共有" ads: "広告" expiration: "期限" memo: "メモ" @@ -786,7 +790,7 @@ secureMode: "セキュアモード (Authorized Fetch)" instanceSecurity: "インスタンスのセキュリティー" secureModeInfo: "他のインスタンスからリクエストするときに、証明を付けなければ返送しません。他のインスタンスの設定ファイルでsignToActivityPubGetはtrueにしてください。" privateMode: "非公開モード" -privateModeInfo: "有効にして、許可されているインスタンスのみがリクエストできます。すべてのノートが公開に非表示にします。" +privateModeInfo: "有効にして、許可されているインスタンスのみがリクエストできます。すべての投稿が公開に非表示にします。" allowedInstances: "許可されたインスタンス" allowedInstancesDescription: "許可したいインスタンスのホストを改行で区切って設定します。非公開モードだけで有効です。" previewNoteText: "本文をプレビュー" @@ -794,7 +798,7 @@ customCss: "カスタムCSS" customCssWarn: "この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。" global: "グローバル" squareAvatars: "アイコンを四角形で表示" -seperateRenoteQuote: "リノートと引用ボタンを分ける" +seperateRenoteQuote: "ブーストと引用ボタンを分ける" sent: "送信" received: "受信" searchResult: "検索結果" @@ -802,13 +806,13 @@ hashtags: "ハッシュタグ" troubleshooting: "トラブルシューティング" useBlurEffect: "UIにぼかし効果を使用" learnMore: "詳しく" -misskeyUpdated: "Misskeyが更新されました!" +misskeyUpdated: "Calckeyが更新されました!" whatIsNew: "更新情報を見る" translate: "翻訳" translatedFrom: "{x}から翻訳" accountDeletionInProgress: "アカウントの削除が進行中です" usernameInfo: "サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。" -aiChanMode: "藍モード" +aiChanMode: "藍モード(クラシックUI)" enterSendsMessage: "メッセージングでReturnキーを押すと、メッセージが送信されます(デフォルトはCtrl + Returnです)" keepCw: "CWを維持する" pubSub: "Pub/Subのアカウント" @@ -912,15 +916,25 @@ customMOTDDescription: "ユーザがページをロード/リロードするた customSplashIcons: "カスタムスプラッシュスクリーンアイコン" customSplashIconsDescription: "ユーザがページをロード/リロードするたびにランダムに表示される、改行で区切られたカスタムスプラッシュスクリーンアイコンの URL。画像は静的なURLで、できればすべて192x192にリサイズしてください。" showUpdates: "Calckeyの更新時にポップアップを表示する" -recommendedInstances: "推奨インスタンス" -recommendedInstancesDescription: "推奨タイムラインに表示するために改行で区切られた推奨インスタンス。`https://`を追加しないでください。ドメインのみを追加してください。" +recommendedInstances: "おすすめインスタンス" +recommendedInstancesDescription: "おすすめタイムラインに表示される、改行で区切られたインスタンス。`https://`を追加しないでください。ドメインのみを追加してください。" caption: "自動キャプション" splash: "スプラッシュスクリーン" -updateAvailable: "アップデートがありますよ" +updateAvailable: "アップデートがありますよ!" swipeOnDesktop: "デスクトップでモバイルスタイルのスワイプを可能にする" logoImageUrl: "ロゴのURL" showAdminUpdates: "新しいCalckeyのバージョンが利用可能であることを示す(管理者のみ)" -replayTutorial: "リプレイチュートリアル" +replayTutorial: "もう一度チュートリアルを見る" +migration: "アカウントの引っ越し" +moveTo: "このアカウントを新しいアカウントに引っ越す" +moveToLabel: "引っ越し先のアカウント:" +moveAccount: "引っ越し実行!" +moveAccountDescription: "この操作は取り消せません。まずは引っ越し先のアカウントでこのアカウントに対しエイリアスを作成したことを確認してください。エイリアス作成後、引っ越し先のアカウントをこのように入力してください:@person@instance.com" +moveFrom: "別のアカウントからこのアカウントに引っ越す" +moveFromLabel: "引っ越し元のアカウント:" +moveFromDescription: "別のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。必ず引っ越しを実行する前に作成してください!引っ越し元のアカウントをこのように入力してください:@person@instance.com" +migrationConfirm: "本当にこのアカウントを {account} に引っ越しますか?一度引っ越しを行うと取り消せず、二度とこのアカウントを元の状態で使用することはできません。\nまた、引っ越し先のアカウントでエイリアスを作成したことを確認してください。" +defaultReaction: "リモートとローカルの投稿に対するデフォルトの絵文字リアクション" _sensitiveMediaDetection: description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" @@ -930,24 +944,20 @@ _sensitiveMediaDetection: setSensitiveFlagAutomaticallyDescription: "この設定をオフにしても内部的に判定結果は保持されます。" analyzeVideos: "動画の解析を有効化" analyzeVideosDescription: "静止画に加えて動画も解析するようにします。サーバーの負荷が少し増えます。" - _emailUnavailable: used: "既に使用されています" format: "形式が正しくありません" disposable: "恒久的に使用可能なアドレスではありません" mx: "正しいメールサーバーではありません" smtp: "メールサーバーが応答しません" - _ffVisibility: public: "公開" followers: "フォロワーだけに公開" private: "非公開" - _signup: almostThere: "ほとんど完了です" emailAddressInfo: "あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。" emailSent: "入力されたメールアドレス({email})宛に確認のメールが送信されました。メールに記載されたリンクにアクセスすると、アカウントの作成が完了します。" - _accountDelete: accountDelete: "アカウントの削除" mayTakeTime: "アカウントの削除は負荷のかかる処理であるため、作成したコンテンツの数やアップロードしたファイルの数が多いと完了までに時間がかかることがあります。" @@ -955,33 +965,27 @@ _accountDelete: requestAccountDelete: "アカウント削除をリクエスト" started: "削除処理が開始されました。" inProgress: "削除が進行中" - _ad: back: "戻る" reduceFrequencyOfThisAd: "この広告の表示頻度を下げる" - _forgotPassword: enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。" ifNoEmail: "メールアドレスを登録していない場合は、管理者までお問い合わせください。" contactAdmin: "このインスタンスではメールがサポートされていないため、パスワードリセットを行う場合は管理者までお問い合わせください。" - _gallery: my: "自分の投稿" liked: "いいねした投稿" like: "いいね!" unlike: "いいね解除" - _email: _follow: title: "フォローされました" _receiveFollowRequest: title: "フォローリクエストを受け取りました" - _plugin: install: "プラグインのインストール" installWarn: "信頼できないプラグインはインストールしないでください。" manage: "プラグインの管理" - _preferencesBackups: list: "作成したバックアップ" saveNew: "新規保存" @@ -1000,33 +1004,29 @@ _preferencesBackups: updatedAt: "更新日時: {date} {time}" cannotLoad: "読み込みできません" invalidFile: "ファイル形式が違います。" - _registry: scope: "スコープ" key: "キー" keys: "キー" domain: "ドメイン" createKey: "キーを作成" - _aboutMisskey: - about: "Calckeyは、2022年から開発されているThatOneCalculator社製のMisskeyのforkです。" + about: "Calckeyは、2022年に生まれたThatOneCalculatorによるMisskeyのforkです。" contributors: "主なコントリビューター" allContributors: "全てのコントリビューター" source: "ソースコード" - translation: "Misskeyを翻訳" - donate: "Misskeyに寄付" - morePatrons: "他にも多くの方が支援してくれています。ありがとうございます🥰" + translation: "Calckeyを翻訳" + donate: "Calckeyに寄付" + morePatrons: "他にも多くの方が支援してくれています。ありがとうございます! 🥰" patrons: "支援者" - _nsfw: respect: "閲覧注意のメディアは隠す" ignore: "閲覧注意のメディアを隠さない" force: "常にメディアを隠す" - _mfm: cheatSheet: "MFMチートシート" - intro: "MFMは、Misskey内の様々な場所で使用できる専用のマークアップ言語です。ここでは、MFMで使用可能な構文一覧が確認できます。" - dummy: "MisskeyでFediverseの世界が広がります" + intro: "MFMは、MisskeyやCalckey、Akkomaなどの様々な場所で使用できるマークアップ言語です。ここでは、MFMで使用可能な構文一覧が確認できます。" + dummy: "CalckeyでFediverseの世界が広がります" mention: "メンション" mentionDescription: "アットマーク + ユーザー名で、特定のユーザーを示すことができます。" hashtag: "ハッシュタグ" @@ -1089,18 +1089,15 @@ _mfm: rotateDescription: "指定した角度で回転させます。" plain: "プレーン" plainDescription: "内側の構文を全て無効にします。" - _instanceTicker: none: "表示しない" remote: "リモートユーザーに表示" always: "常に表示" - _serverDisconnectedBehavior: reload: "自動でリロード" dialog: "ダイアログで警告" quiet: "控えめに警告" nothing: "何も起こらない" - _channel: create: "チャンネルを作成" edit: "チャンネルを編集" @@ -1111,33 +1108,28 @@ _channel: following: "フォロー中" usersCount: "{n}人が参加中" notesCount: "{n}投稿があります" - _messaging: dms: "ディーエム" groups: "グループ" - _menuDisplay: sideFull: "横" sideIcon: "横(アイコン)" top: "上部" hide: "隠す" - _wordMute: muteWords: "ミュートするワード" muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。" muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。" - softDescription: "指定した条件のノートをタイムラインから隠します。" - hardDescription: "指定した条件のノートをタイムラインに追加しないようにします。追加されなかったノートは、条件を変更しても除外されたままになります。" + softDescription: "指定した条件の投稿をタイムラインから隠します。" + hardDescription: "指定した条件の投稿をタイムラインに追加しないようにします。追加されなかった投稿は、条件を変更しても除外されたままになります。" soft: "ソフト" hard: "ハード" - mutedNotes: "ミュートされたノート" - + mutedNotes: "ミュートされた投稿" _instanceMute: - instanceMuteDescription: "ミュートしたインスタンスのユーザーへの返信を含めて、設定したインスタンスの全てのノートとRenoteをミュートします。" + instanceMuteDescription: "ミュートしたインスタンスのユーザーへの返信を含めて、設定したインスタンスの全ての投稿とブーストをミュートします。" instanceMuteDescription2: "改行で区切って設定します" - title: "設定したインスタンスのノートを隠します。" + title: "設定したインスタンスの投稿を隠します。" heading: "ミュートするインスタンス" - _theme: explore: "テーマを探す" install: "テーマのインストール" @@ -1168,7 +1160,6 @@ _theme: inputConstantName: "定数名を入力してください" importInfo: "ここにテーマコードを貼り付けて、エディターにインポートできます" deleteConstantConfirm: "定数 {const} を削除しても良いですか?" - keys: accent: "アクセント" bg: "背景" @@ -1187,7 +1178,7 @@ _theme: hashtag: "ハッシュタグ" mention: "メンション" mentionMe: "あなた宛てメンション" - renote: "Renote" + renote: "ブースト" modalBg: "モーダルの背景" divider: "分割線" scrollbarHandle: "スクロールバーの取っ手" @@ -1213,16 +1204,14 @@ _theme: accentDarken: "アクセント (暗め)" accentLighten: "アクセント (明るめ)" fgHighlighted: "強調された文字" - _sfx: - note: "ノート" - noteMy: "ノート(自分)" + note: "投稿" + noteMy: "投稿(自分)" notification: "通知" chat: "チャット" chatBg: "チャット(バックグラウンド)" antenna: "アンテナ受信" channel: "チャンネル通知" - _ago: future: "未来" justNow: "たった今" @@ -1233,35 +1222,32 @@ _ago: weeksAgo: "{n}週間前" monthsAgo: "{n}ヶ月前" yearsAgo: "{n}年前" - _time: second: "秒" minute: "分" hour: "時間" day: "日" - _tutorial: title: "Calckeyの使い方" - step1_1: "ようこそ!" - step1_2: "設定をしてみましょう" - step2_1: "メモを書いたり、誰かをフォローする前に、プロフィールの設定を済ませましょう。" - step2_2: "あなたが誰なのか、いくつかの情報を提供することで、他の人があなたのメモを見たり、フォローしたりしたいのかがわかりやすくなります。" - step3_1: "さあ、何人かの人をフォローする時間です!" - step3_2: "あなたのホームとソーシャルタイムラインは、あなたが誰をフォローしているかで決まります。 まずは、いくつかのアカウントをフォローしてみましょう。" - step4_1: "さあ、外に出てみましょう。" - step4_2: "最初の投稿は、{introduction}の投稿や、シンプルに「こんにちは、世界よ!」的な投稿をするのが好きな人もいます。" + step1_1: "ようこそ!" + step1_2: "使い始める前に、いくつか設定を済ませましょう。すぐできますよ!" + step2_1: "最初に、あなたのプロフィールを作りましょう。" + step2_2: "プロフィールを設定することで、他の人があなたの投稿を見たり、フォローしたりするときの助けになります。" + step3_1: "それでは、何人かフォローしてみましょう!" + step3_2: "あなたのホームとソーシャルタイムラインは、あなたが誰をフォローしているかで決まります。まずは、いくつかのアカウントをフォローしてみましょう。\nプロフィールの右上にある丸い+ボタンをクリックするとフォローできます。" + step4_1: "投稿してみましょう!" + step4_2: "最初は{introduction}に投稿したり、シンプルに「こんにちは、アカウント作ってみました!」などの投稿をする人もいます。" step5_1: "タイムライン、タイムラインだらけ!" - step5_2: "あなたのインスタンスは{timelines}異なるタイムラインを有効にしています。" - step5_3: "ホーム{icon}のタイムラインは、あなたのフォロワーからの投稿を見ることができます。" - step5_4: "ローカル{icon}タイムラインは、このインスタンスのみんなの投稿を見ることができる場所です。" - step5_5: "おすすめ{icon}のタイムラインは、管理人がおすすめするインスタンスの投稿を見ることができます。" - step5_6: "ソーシャル{icon}のタイムラインは、あなたのフォロワーの友達の投稿を見ることができる場所です。" - step5_7: "グローバル{icon}タイムラインは、接続している他のすべてのインスタンスからの投稿を見ることができます。" - step6_1: "それで、ここは何なの?" - step6_2: "まあ、あなたはCalckeyに参加しただけではありません。何千ものサーバーが相互接続されたネットワークで インスタンスと呼ばれる。" - step6_3: "各サーバーは異なる方法で動作し、すべてのサーバーがCalckeyを実行するわけではありません。でも、このサーバーは動くんです" - step6_4: "さあ、探検して、楽しんでください!" - + step5_2: "あなたのインスタンスでは{timelines}種類のタイムラインが有効になっています。" + step5_3: "ホーム{icon}タイムラインでは、あなたがフォローしているアカウントの投稿を見ることができます。" + step5_4: "ローカル{icon}タイムラインでは、このインスタンスのみんなの投稿を見ることができます。" + step5_5: "おすすめ{icon}タイムラインでは、管理人がおすすめするインスタンスの投稿を見ることができます。" + step5_6: "ソーシャル{icon}タイムラインでは、ホームタイムラインとローカルタイムラインの投稿を同時に見ることができます。" + step5_7: "グローバル{icon}タイムラインでは、接続している他のすべてのインスタンスからの投稿を見ることができます。" + step6_1: "じゃあ、ここはどんな場所なの?" + step6_2: "実は、あなたはただCalckeyに参加しただけではありません。ここは、何千もの相互接続されたサーバーが構成する Fediverse への入口です。各サーバーは「インスタンス」と呼ばれます。" + step6_3: "それぞれのサーバーでは必ずしもCalckeyが使われているわけではなく、異なる動作をするサーバーもあります。しかし、あなたは他のサーバーのアカウントもフォローしたり、返信・ブーストができます。一見難しそうですが大丈夫!すぐ慣れます。" + step6_4: "これで完了です。お楽しみください!" _2fa: alreadyRegistered: "既に設定は完了しています。" registerDevice: "デバイスを登録" @@ -1272,7 +1258,6 @@ _2fa: step3: "アプリに表示されているトークンを入力して完了です。" step4: "これからログインするときも、同じようにトークンを入力します。" securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーもしくは端末の指紋認証やPINを使用してログインするように設定できます。" - _permissions: "read:account": "アカウントの情報を見る" "write:account": "アカウントの情報を変更する" @@ -1288,7 +1273,7 @@ _permissions: "write:messaging": "チャットを操作する" "read:mutes": "ミュートを見る" "write:mutes": "ミュートを操作する" - "write:notes": "ノートを作成・削除する" + "write:notes": "投稿を作成・削除する" "read:notifications": "通知を見る" "write:notifications": "通知を操作する" "read:reactions": "リアクションを見る" @@ -1306,22 +1291,21 @@ _permissions: "write:gallery": "ギャラリーを操作する" "read:gallery-likes": "ギャラリーのいいねを見る" "write:gallery-likes": "ギャラリーのいいねを操作する" - _auth: shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?" shareAccessAsk: "アカウントへのアクセスを許可しますか?" - permissionAsk: "このアプリは次の権限を要求しています" - pleaseGoBack: "アプリケーションに戻ってやっていってください" + permissionAsk: "このアプリケーションは次の権限を要求しています" + pleaseGoBack: "アプリケーションに戻り続行してください" callback: "アプリケーションに戻っています" denied: "アクセスを拒否しました" - + copyAsk: "以下の認証コードをアプリケーションにコピーしてください" _antennaSources: - all: "全てのノート" - homeTimeline: "フォローしているユーザーのノート" - users: "指定した一人または複数のユーザーのノート" - userList: "指定したリストのユーザーのノート" - userGroup: "指定したグループのユーザーのノート" - + all: "全ての投稿" + homeTimeline: "フォローしているユーザーの投稿" + users: "指定した一人または複数のユーザーの投稿" + userList: "指定したリストのユーザーの投稿" + userGroup: "指定したグループのユーザーの投稿" + instances: "指定したインスタンスの全ユーザーの投稿" _weekday: sunday: "日曜日" monday: "月曜日" @@ -1330,7 +1314,6 @@ _weekday: thursday: "木曜日" friday: "金曜日" saturday: "土曜日" - _widgets: memo: "付箋" notifications: "通知" @@ -1353,14 +1336,14 @@ _widgets: jobQueue: "ジョブキュー" serverMetric: "サーバーメトリクス" aiscript: "AiScriptコンソール" - aichan: "藍" - + userList: "ユーザーリスト" + _userList: + chooseList: "リストを選択" _cw: hide: "隠す" show: "もっと見る" chars: "{count}文字" files: "{count}ファイル" - _poll: noOnlyOneChoice: "選択肢は最低2つ必要です" choiceN: "選択肢{n}" @@ -1383,7 +1366,6 @@ _poll: remainingHours: "終了まであと{h}時間{m}分" remainingMinutes: "終了まであと{m}分{s}秒" remainingSeconds: "終了まであと{s}秒" - _visibility: public: "パブリック" publicDescription: "全てのユーザーに公開" @@ -1395,10 +1377,9 @@ _visibility: specifiedDescription: "指定したユーザーのみに公開" localOnly: "ローカルのみ" localOnlyDescription: "リモートユーザーには非公開" - _postForm: - replyPlaceholder: "このノートに返信..." - quotePlaceholder: "このノートを引用..." + replyPlaceholder: "この投稿に返信..." + quotePlaceholder: "この投稿を引用..." channelPlaceholder: "チャンネルに投稿..." _placeholders: a: "いまどうしてる?" @@ -1407,7 +1388,6 @@ _postForm: d: "言いたいことは?" e: "ここに書いてください" f: "あなたが書くのを待っています..." - _profile: name: "名前" username: "ユーザー名" @@ -1420,51 +1400,47 @@ _profile: metadataContent: "内容" changeAvatar: "アバター画像を変更" changeBanner: "バナー画像を変更" - + locationDescription: "正しく入力すると、あなたの現地時間が他のユーザーに表示されます。" _exportOrImport: - allNotes: "全てのノート" + allNotes: "全ての投稿" followingList: "フォロー" muteList: "ミュート" blockingList: "ブロック" userLists: "リスト" excludeMutingUsers: "ミュートしているユーザーを除外" excludeInactiveUsers: "使われていないアカウントを除外" - _charts: federation: "連合" apRequest: "リクエスト" usersIncDec: "ユーザーの増減" usersTotal: "ユーザーの合計" activeUsers: "アクティブユーザー数" - notesIncDec: "ノートの増減" - localNotesIncDec: "ローカルのノートの増減" - remoteNotesIncDec: "リモートのノートの増減" - notesTotal: "ノートの合計" + notesIncDec: "投稿の増減" + localNotesIncDec: "ローカルの投稿の増減" + remoteNotesIncDec: "リモートの投稿の増減" + notesTotal: "投稿の合計" filesIncDec: "ファイルの増減" filesTotal: "ファイルの合計" storageUsageIncDec: "ストレージ使用量の増減" storageUsageTotal: "ストレージ使用量の合計" - _instanceCharts: requests: "リクエスト" users: "ユーザーの増減" usersTotal: "ユーザーの累積" - notes: "ノートの増減" - notesTotal: "ノートの累積" + notes: "投稿の増減" + notesTotal: "投稿の累積" ff: "フォロー/フォロワーの増減" ffTotal: "フォロー/フォロワーの累積" cacheSize: "キャッシュサイズの増減" cacheSizeTotal: "キャッシュサイズの累積" files: "ファイル数の増減" filesTotal: "ファイル数の累積" - _timelines: home: "ホーム" local: "ローカル" - recommended: "一押し" + recommended: "おすすめ" social: "ソーシャル" global: "グローバル" - _pages: newPage: "ページの作成" editPage: "ページの編集" @@ -1511,59 +1487,49 @@ _pages: section: "セクション" image: "画像" button: "ボタン" - if: "もし" _if: variable: "変数" - post: "投稿フォーム" _post: text: "内容" attachCanvasImage: "キャンバスの画像を添付する" canvasId: "キャンバスID" - textInput: "テキスト入力" _textInput: name: "変数名" text: "タイトル" default: "デフォルト値" - textareaInput: "複数行テキスト入力" _textareaInput: name: "変数名" text: "タイトル" default: "デフォルト値" - numberInput: "数値入力" _numberInput: name: "変数名" text: "タイトル" default: "デフォルト値" - canvas: "キャンバス" _canvas: id: "キャンバスID" width: "幅" height: "高さ" - - note: "ノート埋め込み" + note: "投稿の埋め込み" _note: - id: "ノートID" - idDescription: "ノートURLをペーストして設定することもできます。" + id: "投稿のID" + idDescription: "投稿のURLをペーストして設定することもできます。" detailed: "詳細な表示" - switch: "スイッチ" _switch: name: "変数名" text: "タイトル" default: "デフォルト値" - counter: "カウンター" _counter: name: "変数名" text: "タイトル" inc: "増加値" - _button: text: "タイトル" colored: "色付き" @@ -1582,14 +1548,12 @@ _pages: callAiScript: "AiScript呼び出し" _callAiScript: functionName: "関数名" - radioButton: "選択肢" _radioButton: name: "変数名" title: "タイトル" values: "改行で区切った選択肢" default: "デフォルト値" - script: categories: flow: "制御" @@ -1766,18 +1730,16 @@ _pages: enviromentVariables: "環境変数" pageVariables: "ページ要素" argVariables: "入力スロット" - _relayStatus: requesting: "承認待ち" accepted: "承認済み" rejected: "拒否済み" - _notification: fileUploaded: "ファイルがアップロードされました" youGotMention: "{name}からのメンション" youGotReply: "{name}からのリプライ" youGotQuote: "{name}による引用" - youRenoted: "{name}がRenoteしました" + youRenoted: "{name}がブーストしました" youGotPoll: "{name}が投票しました" youGotMessagingMessageFromUser: "{name}からのチャットがあります" youGotMessagingMessageFromGroup: "{name}のチャットがあります" @@ -1787,13 +1749,12 @@ _notification: youWereInvitedToGroup: "{userName}があなたをグループに招待しました" pollEnded: "アンケートの結果が出ました" emptyPushNotificationMessage: "プッシュ通知の更新をしました" - _types: all: "すべて" follow: "フォロー" mention: "メンション" reply: "リプライ" - renote: "Renote" + renote: "ブースト" quote: "引用" reaction: "リアクション" pollVote: "アンケートに投票された" @@ -1802,12 +1763,10 @@ _notification: followRequestAccepted: "フォローが受理された" groupInvited: "グループに招待された" app: "連携アプリからの通知" - _actions: followBack: "フォローバック" reply: "返信" - renote: "Renote" - + renote: "ブースト" _deck: alwaysShowMainColumn: "常にメインカラムを表示" columnAlign: "カラムの寄せ" @@ -1825,7 +1784,6 @@ _deck: introduction: "カラムを組み合わせて自分だけのインターフェイスを作りましょう!" introduction2: "画面の右にある + を押して、いつでもカラムを追加できます。" widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選択してウィジェットを追加してください" - _columns: main: "メイン" widgets: "ウィジェット" @@ -1835,3 +1793,20 @@ _deck: list: "リスト" mentions: "あなた宛て" direct: "ダイレクト" +_apps: + apps: "アプリ" + crossPlatform: "クロスプラットフォーム" + mobile: "モバイル" + firstParty: "ファーストパーティ" + firstClass: "対応度◎" + secondClass: "対応度○" + thirdClass: "対応度△" + free: "無料" + paid: "有料" + pwa: "PWAをインストール" + kaiteki: "Kaiteki" + milktea: "Milktea" + missLi: "MissLi" + mona: "Mona" + theDesk: "TheDesk" + lesskey: "Lesskey" diff --git a/package.json b/package.json index 11a539409..7f14982fd 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "@tensorflow/tfjs": "^3.21.0", "calckey-js": "^0.0.22", "js-yaml": "4.1.0", - "phosphor-icons": "^1.4.2", "seedrandom": "^3.0.5" }, "devDependencies": { diff --git a/packages/backend/check_connect.js b/packages/backend/check_connect.js new file mode 100644 index 000000000..8bf134a10 --- /dev/null +++ b/packages/backend/check_connect.js @@ -0,0 +1,10 @@ +import {loadConfig} from './built/config.js'; +import {createRedisConnection} from "./built/redis.js"; + +const config = loadConfig(); +const redis = createRedisConnection(config); + +redis.on('connect', () => redis.disconnect()); +redis.on('error', (e) => { + throw e; +}); diff --git a/packages/backend/migration/1677935903517-DriveComment.js b/packages/backend/migration/1677935903517-DriveComment.js new file mode 100644 index 000000000..41c7556f0 --- /dev/null +++ b/packages/backend/migration/1677935903517-DriveComment.js @@ -0,0 +1,11 @@ +export class DriveComment1677935903517 { + name = 'DriveComment1677935903517' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "drive_file" ALTER "comment" TYPE character varying(8192)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "drive_file" ALTER "comment" TYPE character varying(512)`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 55a64191a..910ffd374 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -8,6 +8,7 @@ "start:test": "NODE_ENV=test pnpm node ./built/index.js", "migrate": "typeorm migration:run -d ormconfig.js", "revertmigration": "typeorm migration:revert -d ormconfig.js", + "check:connect": "node ./check_connect.js", "build": "pnpm swc src -d built -D", "watch": "pnpm swc src -d built -D -w", "lint": "pnpm rome check \"src/**/*.ts\"", @@ -71,6 +72,7 @@ "jsonld": "6.0.0", "jsrsasign": "10.6.1", "koa": "2.13.4", + "koa-remove-trailing-slashes": "2.0.3", "koa-bodyparser": "4.3.0", "koa-favicon": "2.1.0", "koa-json-body": "5.3.0", @@ -97,6 +99,7 @@ "punycode": "2.1.1", "pureimage": "0.3.15", "qrcode": "1.5.1", + "qs": "6.9.7", "random-seed": "0.3.0", "ratelimiter": "3.4.1", "re2": "1.18.0", @@ -157,6 +160,7 @@ "@types/pug": "2.0.6", "@types/punycode": "2.1.0", "@types/qrcode": "1.5.0", + "@types/qs": "6.9.7", "@types/random-seed": "0.3.3", "@types/ratelimiter": "3.4.4", "@types/redis": "4.0.11", diff --git a/packages/backend/src/@types/koa-remove-trailing-slashes/index.d.ts b/packages/backend/src/@types/koa-remove-trailing-slashes/index.d.ts new file mode 100644 index 000000000..eda0cf142 --- /dev/null +++ b/packages/backend/src/@types/koa-remove-trailing-slashes/index.d.ts @@ -0,0 +1 @@ +declare module 'koa-remove-trailing-slashes'; diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts index e9c42c08f..ed9b0ece0 100644 --- a/packages/backend/src/config/types.ts +++ b/packages/backend/src/config/types.ts @@ -74,6 +74,7 @@ export type Source = { maxUserSignups?: number; isManagedHosting?: boolean; maxNoteLength?: number; + maxCaptionLength?: number; deepl: { managed?: boolean; authKey?: string; diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts index 82a093100..7e8f96444 100644 --- a/packages/backend/src/const.ts +++ b/packages/backend/src/const.ts @@ -1,7 +1,12 @@ import config from "@/config/index.js"; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; export const MAX_NOTE_TEXT_LENGTH = config.maxNoteLength != null ? config.maxNoteLength : 3000; // <- should we increase this? +export const MAX_CAPTION_TEXT_LENGTH = Math.min( + config.maxCaptionLength ?? 1500, + DB_MAX_IMAGE_COMMENT_LENGTH, +); export const SECOND = 1000; export const SEC = 1000; // why do we need this duplicate here? diff --git a/packages/backend/src/misc/hard-limits.ts b/packages/backend/src/misc/hard-limits.ts index 4ba90293c..51d2c0f5d 100644 --- a/packages/backend/src/misc/hard-limits.ts +++ b/packages/backend/src/misc/hard-limits.ts @@ -10,4 +10,4 @@ export const DB_MAX_NOTE_TEXT_LENGTH = 8192; * Maximum image description length that can be stored in DB. * Surrogate pairs count as one */ -export const DB_MAX_IMAGE_COMMENT_LENGTH = 512; +export const DB_MAX_IMAGE_COMMENT_LENGTH = 8192; diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts index 1fa91b1a9..32e19bc6e 100644 --- a/packages/backend/src/models/entities/drive-file.ts +++ b/packages/backend/src/models/entities/drive-file.ts @@ -9,6 +9,7 @@ import { import { id } from "../id.js"; import { User } from "./user.js"; import { DriveFolder } from "./drive-folder.js"; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; @Entity() @Index(['userId', 'folderId', 'id']) @@ -69,7 +70,8 @@ export class DriveFile { public size: number; @Column('varchar', { - length: 512, nullable: true, + length: DB_MAX_IMAGE_COMMENT_LENGTH, + nullable: true, comment: 'The comment of the DriveFile.', }) public comment: string | null; diff --git a/packages/backend/src/queue/processors/webhook-deliver.ts b/packages/backend/src/queue/processors/webhook-deliver.ts index a130fcd38..2edf4f696 100644 --- a/packages/backend/src/queue/processors/webhook-deliver.ts +++ b/packages/backend/src/queue/processors/webhook-deliver.ts @@ -20,6 +20,7 @@ export default async (job: Bull.Job) => { "X-Calckey-Host": config.host, "X-Calckey-Hook-Id": job.data.webhookId, "X-Calckey-Hook-Secret": job.data.secret, + 'Content-Type': 'application/json' }, body: JSON.stringify({ hookId: job.data.webhookId, diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 372ef99e1..16b265b0b 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -413,8 +413,8 @@ export async function updatePerson( isBot: getApType(object) === "Service", isCat: (person as any).isCat === true, isLocked: !!person.manuallyApprovesFollowers, - movedToUri: person.movedTo, - alsoKnownAs: person.alsoKnownAs, + movedToUri: person.movedTo || null, + alsoKnownAs: person.alsoKnownAs || null, isExplorable: !!person.discoverable, } as Partial; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 1808de118..c8c639f50 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -1,6 +1,6 @@ import config from "@/config/index.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; -import { MAX_NOTE_TEXT_LENGTH } from "@/const.js"; +import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "@/const.js"; import define from "../../define.js"; export const meta = { @@ -86,6 +86,11 @@ export const meta = { optional: false, nullable: false, }, + maxCaptionTextLength: { + type: "number", + optional: false, + nullable: false, + }, emojis: { type: "array", optional: false, @@ -499,6 +504,7 @@ export default define(meta, paramDef, async (ps, me) => { backgroundImageUrl: instance.backgroundImageUrl, logoImageUrl: instance.logoImageUrl, maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため + maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH, defaultLightTheme: instance.defaultLightTheme, defaultDarkTheme: instance.defaultDarkTheme, enableEmail: instance.enableEmail, diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 476a0c29f..4dc1c941e 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -2,8 +2,7 @@ import { IsNull, MoreThan } from "typeorm"; import config from "@/config/index.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; import { Ads, Emojis, Users } from "@/models/index.js"; -import { DB_MAX_NOTE_TEXT_LENGTH } from "@/misc/hard-limits.js"; -import { MAX_NOTE_TEXT_LENGTH } from "@/const.js"; +import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "@/const.js"; import define from "../define.js"; export const meta = { @@ -178,6 +177,11 @@ export const meta = { optional: false, nullable: false, }, + maxCaptionTextLength: { + type: "number", + optional: false, + nullable: false, + }, emojis: { type: "array", optional: false, @@ -456,6 +460,7 @@ export default define(meta, paramDef, async (ps, me) => { backgroundImageUrl: instance.backgroundImageUrl, logoImageUrl: instance.logoImageUrl, maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため + maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH, emojis: instance.privateMode && !me ? [] : await Emojis.packMany(emojis), defaultLightTheme: instance.defaultLightTheme, defaultDarkTheme: instance.defaultDarkTheme, diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index 752303e95..4eb87a614 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -22,6 +22,35 @@ import github from "./service/github.js"; import twitter from "./service/twitter.js"; import { koaBody } from "koa-body"; +export enum IdType { + CalckeyId, + MastodonId +}; + +export function convertId(idIn: string, idConvertTo: IdType ) { + let idArray = [] + switch (idConvertTo) { + case IdType.MastodonId: + idArray = [...idIn].map(item => item.charCodeAt(0)); + idArray = idArray.map(item => { + if (item.toString().length < 3) { + return `0${item.toString()}` + } + else return item.toString() + }); + return idArray.join(''); + case IdType.CalckeyId: + for (let i = 0; i < idIn.length; i += 3) { + if ((idIn.length % 3) !== 0) { + idIn = `0${idIn}` + } + idArray.push(idIn.slice(i, i+3)); + } + idArray = idArray.map(item => String.fromCharCode(item)); + return idArray.join(''); + } +}; + // Init app const app = new Koa(); @@ -82,7 +111,7 @@ mastoFileRouter.post("/v1/media", upload.single("file"), async (ctx) => { ctx.status = 401; return; } - const data = await client.uploadMedia(multipartData.buffer); + const data = await client.uploadMedia(multipartData); ctx.body = data.data; } catch (e: any) { console.error(e); @@ -101,7 +130,7 @@ mastoFileRouter.post("/v2/media", upload.single("file"), async (ctx) => { ctx.status = 401; return; } - const data = await client.uploadMedia(multipartData.buffer); + const data = await client.uploadMedia(multipartData); ctx.body = data.data; } catch (e: any) { console.error(e); diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 5b7df948e..1c5e31fe8 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -4,8 +4,9 @@ import Router from "@koa/router"; import { FindOptionsWhere, IsNull } from "typeorm"; import { getClient } from "../ApiMastodonCompatibleService.js"; import { argsToBools, limitToInt } from "./timeline.js"; +import { convertId, IdType } from "../../index.js"; -const relationshopModel = { +const relationshipModel = { id: "", following: false, followed_by: false, @@ -29,7 +30,8 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.verifyAccountCredentials(); - const acct = data.data; + let acct = data.data; + acct.id = convertId(acct.id, IdType.MastodonId); acct.url = `${BASE_URL}/@${acct.url}`; acct.note = ""; acct.avatar_static = acct.avatar; @@ -42,6 +44,7 @@ export function apiAccountMastodon(router: Router): void { sensitive: false, language: "", }; + console.log(acct); ctx.body = acct; } catch (e: any) { console.error(e); @@ -58,7 +61,9 @@ export function apiAccountMastodon(router: Router): void { const data = await client.updateCredentials( (ctx.request as any).body as any, ); - ctx.body = data.data; + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -71,26 +76,10 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - let userArray = ctx.query.acct?.toString().split("@"); - let userid; - if (userArray === undefined) { - ctx.status = 401; - ctx.body = { error: "no user specified" }; - return; - } - if (userArray.length === 1) { - const q: FindOptionsWhere = { - usernameLower: userArray[0].toLowerCase(), - host: IsNull(), - }; - - const user = await Users.findOneBy(q); - userid = user?.id; - } else { - userid = (await resolveUser(userArray[0], userArray[1])).id; - } - const data = await client.getAccount(userid ? userid : ""); - ctx.body = data.data; + const data = await client.search((ctx.request.query as any).acct, 'accounts'); + let resp = data.data.accounts[0]; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -105,8 +94,11 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getAccount(ctx.params.id); - ctx.body = data.data; + const calcId = convertId(ctx.params.id, IdType.CalckeyId); + const data = await client.getAccount(calcId); + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -123,10 +115,20 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getAccountStatuses( - ctx.params.id, + convertId(ctx.params.id, IdType.CalckeyId), argsToBools(limitToInt(ctx.query as any)), ); - ctx.body = data.data; + let resp = data.data; + for (let statIdx = 0; statIdx < resp.length; statIdx++) { + resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId); + resp[statIdx].in_reply_to_account_id = resp[statIdx].in_reply_to_account_id ? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId) : null; + resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) : null; + let mentions = resp[statIdx].mentions + for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) { + resp[statIdx].mentions[mtnIdx].id = convertId(mentions[mtnIdx].id, IdType.MastodonId); + } + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -143,10 +145,14 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getAccountFollowers( - ctx.params.id, - ctx.query as any, + convertId(ctx.params.id, IdType.CalckeyId), + limitToInt(ctx.query as any), ); - ctx.body = data.data; + let resp = data.data; + for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { + resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -163,10 +169,14 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getAccountFollowing( - ctx.params.id, - ctx.query as any, + convertId(ctx.params.id, IdType.CalckeyId), + limitToInt(ctx.query as any), ); - ctx.body = data.data; + let resp = data.data; + for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { + resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -199,10 +209,11 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.followAccount(ctx.params.id); - const acct = data.data; + const data = await client.followAccount(convertId(ctx.params.id, IdType.CalckeyId)); + let acct = data.data; acct.following = true; - ctx.body = data.data; + acct.id = convertId(acct.id, IdType.MastodonId); + ctx.body = acct; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -218,10 +229,11 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.unfollowAccount(ctx.params.id); - const acct = data.data; + const data = await client.unfollowAccount(convertId(ctx.params.id, IdType.CalckeyId)); + let acct = data.data; + acct.id = convertId(acct.id, IdType.MastodonId); acct.following = false; - ctx.body = data.data; + ctx.body = acct; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -237,8 +249,10 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.blockAccount(ctx.params.id); - ctx.body = data.data; + const data = await client.blockAccount(convertId(ctx.params.id, IdType.CalckeyId)); + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -254,8 +268,10 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.unblockAccount(ctx.params.id); - ctx.body = data.data; + const data = await client.unblockAccount(convertId(ctx.params.id, IdType.MastodonId)); + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -272,10 +288,12 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.muteAccount( - ctx.params.id, + convertId(ctx.params.id, IdType.CalckeyId), (ctx.request as any).body as any, ); - ctx.body = data.data; + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -291,8 +309,10 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.unmuteAccount(ctx.params.id); - ctx.body = data.data; + const data = await client.unmuteAccount(convertId(ctx.params.id, IdType.CalckeyId)); + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -308,16 +328,23 @@ export function apiAccountMastodon(router: Router): void { let users; try { // TODO: this should be body - const idsRaw = ctx.request.query ? ctx.request.query["id[]"] : null; - const ids = typeof idsRaw === "string" ? [idsRaw] : idsRaw; + let ids = ctx.request.query ? ctx.request.query["id[]"] : null; + if (typeof ids === "string") { + ids = [ids]; + } users = ids; - relationshopModel.id = idsRaw?.toString() || "1"; - if (!idsRaw) { - ctx.body = [relationshopModel]; + relationshipModel.id = ids?.toString() || "1"; + if (!ids) { + ctx.body = [relationshipModel]; return; } + const data = await client.getRelationships(ids); - ctx.body = data.data; + let resp = data.data; + for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { + resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); + } + ctx.body = resp; } catch (e: any) { console.error(e); let data = e.response.data; @@ -333,7 +360,17 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = (await client.getBookmarks(ctx.query as any)) as any; - ctx.body = data.data; + let resp = data.data; + for (let statIdx = 0; statIdx < resp.length; statIdx++) { + resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId); + resp[statIdx].in_reply_to_account_id = resp[statIdx].in_reply_to_account_id ? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId) : null; + resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) : null; + let mentions = resp[statIdx].mentions + for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) { + resp[statIdx].mentions[mtnIdx].id = convertId(mentions[mtnIdx].id, IdType.MastodonId); + } + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -347,7 +384,17 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getFavourites(ctx.query as any); - ctx.body = data.data; + let resp = data.data; + for (let statIdx = 0; statIdx < resp.length; statIdx++) { + resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId); + resp[statIdx].in_reply_to_account_id = resp[statIdx].in_reply_to_account_id ? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId) : null; + resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) : null; + let mentions = resp[statIdx].mentions + for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) { + resp[statIdx].mentions[mtnIdx].id = convertId(mentions[mtnIdx].id, IdType.MastodonId); + } + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -361,7 +408,11 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getMutes(ctx.query as any); - ctx.body = data.data; + let resp = data.data; + for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { + resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -375,7 +426,11 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getBlocks(ctx.query as any); - ctx.body = data.data; + let resp = data.data; + for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { + resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -383,7 +438,7 @@ export function apiAccountMastodon(router: Router): void { ctx.body = e.response.data; } }); - router.get("/v1/follow_ctxs", async (ctx) => { + router.get("/v1/follow_requests", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); @@ -391,7 +446,11 @@ export function apiAccountMastodon(router: Router): void { const data = await client.getFollowRequests( ((ctx.query as any) || { limit: 20 }).limit, ); - ctx.body = data.data; + let resp = data.data; + for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { + resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); + } + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -400,14 +459,16 @@ export function apiAccountMastodon(router: Router): void { } }); router.post<{ Params: { id: string } }>( - "/v1/follow_ctxs/:id/authorize", + "/v1/follow_requests/:id/authorize", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.acceptFollowRequest(ctx.params.id); - ctx.body = data.data; + const data = await client.acceptFollowRequest(convertId(ctx.params.id, IdType.CalckeyId)); + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); @@ -417,14 +478,16 @@ export function apiAccountMastodon(router: Router): void { }, ); router.post<{ Params: { id: string } }>( - "/v1/follow_ctxs/:id/reject", + "/v1/follow_requests/:id/reject", async (ctx) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.rejectFollowRequest(ctx.params.id); - ctx.body = data.data; + const data = await client.rejectFollowRequest(convertId(ctx.params.id, IdType.CalckeyId)); + let resp = data.data; + resp.id = convertId(resp.id, IdType.MastodonId); + ctx.body = resp; } catch (e: any) { console.error(e); console.error(e.response.data); diff --git a/packages/backend/src/server/api/mastodon/endpoints/auth.ts b/packages/backend/src/server/api/mastodon/endpoints/auth.ts index 5d10641fb..f1c54be0a 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/auth.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/auth.ts @@ -44,12 +44,10 @@ const writeScope = [ export function apiAuthMastodon(router: Router): void { router.post("/v1/apps", async (ctx) => { const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; - const accessTokens = ctx.request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); - const body: any = ctx.request.body; + const client = getClient(BASE_URL, ''); + const body: any = ctx.request.body || ctx.request.query; try { let scope = body.scopes; - console.log(body); if (typeof scope === "string") scope = scope.split(" "); const pushScope = new Set(); for (const s of scope) { @@ -64,14 +62,16 @@ export function apiAuthMastodon(router: Router): void { redirect_uris: red, website: body.website, }); - ctx.body = { - id: appData.id, + const returns = { + id: Math.floor(Math.random() * 100).toString(), name: appData.name, - website: appData.website, + website: body.website, redirect_uri: red, client_id: Buffer.from(appData.url || "").toString("base64"), - client_secret: appData.clientSecret, + client_secret: appData.clientSecret }; + console.log(returns) + ctx.body = returns; } catch (e: any) { console.error(e); ctx.status = 401; diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index 67f3901e4..e5e0f2622 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -10,18 +10,18 @@ export async function getInstance(response: Entity.Instance) { const totalStatuses = Notes.count({ where: { userHost: IsNull() } }); return { uri: response.uri, - title: response.title || "", - short_description: response.description || "", - description: response.description || "", + title: response.title || "Calckey", + short_description: response.description.substring(0, 50) || "See real server website", + description: response.description || "This is a vanilla Calckey Instance. It doesnt seem to have a description. BTW you are using the Mastodon api to access this server :)", email: response.email || "", - version: "3.0.0 compatible (Calckey)", + version: "3.0.0 compatible (3.5+ Calckey)", //I hope this version string is correct, we will need to test it. urls: response.urls, stats: { user_count: (await totalUsers), status_count: (await totalStatuses), domain_count: response.stats.domain_count }, - thumbnail: response.thumbnail || "", + thumbnail: response.thumbnail || 'https://http.cat/404', languages: meta.langs, registrations: !meta.disableRegistration || response.registrations, approval_required: !response.registrations, diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index c8ca1f050..b610e784d 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -1,6 +1,9 @@ import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import Router from "@koa/router"; import { getClient } from "../ApiMastodonCompatibleService.js"; +import axios from "axios"; +import { Converter } from "@calckey/megalodon"; +import { limitToInt } from "./timeline.js"; export function apiSearchMastodon(router: Router): void { router.get("/v1/search", async (ctx) => { @@ -9,7 +12,7 @@ export function apiSearchMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); const body: any = ctx.request.body; try { - const query: any = ctx.query; + const query: any = limitToInt(ctx.query); const type = query.type || ""; const data = await client.search(query.q, type, query); ctx.body = data.data; @@ -19,4 +22,110 @@ export function apiSearchMastodon(router: Router): void { ctx.body = e.response.data; } }); + router.get("/v2/search", async (ctx) => { + const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; + const accessTokens = ctx.headers.authorization; + const client = getClient(BASE_URL, accessTokens); + try { + const query: any = limitToInt(ctx.query); + const type = query.type; + if (type) { + const data = await client.search(query.q, type, query); + ctx.body = data.data; + } else { + const acct = await client.search(query.q, "accounts", query); + const stat = await client.search(query.q, "statuses", query); + const tags = await client.search(query.q, "hashtags", query); + ctx.body = { + accounts: acct.data.accounts, + statuses: stat.data.statuses, + hashtags: tags.data.hashtags, + }; + } + } catch (e: any) { + console.error(e); + ctx.status = (401); + ctx.body = e.response.data; + } + }); + router.get("/v1/trends/statuses", async (ctx) => { + const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; + const accessTokens = ctx.headers.authorization; + try { + const data = await getHighlight(BASE_URL, ctx.request.hostname, accessTokens); + ctx.body = data; + } catch (e: any) { + console.error(e); + ctx.status = (401); + ctx.body = e.response.data; + } + }); + router.get("/v2/suggestions", async (ctx) => { + const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; + const accessTokens = ctx.headers.authorization; + try { + const query: any = ctx.query; + const data = await getFeaturedUser( + BASE_URL, + ctx.request.hostname, + accessTokens, + query.limit || 20, + ); + console.log(data); + ctx.body = data; + } catch (e: any) { + console.error(e); + ctx.status = (401); + ctx.body = e.response.data; + } + }); +} +async function getHighlight( + BASE_URL: string, + domain: string, + accessTokens: string | undefined, +) { + const accessTokenArr = accessTokens?.split(" ") ?? [null]; + const accessToken = accessTokenArr[accessTokenArr.length - 1]; + try { + const api = await axios.post(`${BASE_URL}/api/notes/featured`, { + i: accessToken, + }); + const data: MisskeyEntity.Note[] = api.data; + return data.map((note) => Converter.note(note, domain)); + } catch (e: any) { + console.log(e); + console.log(e.response.data); + return []; + } +} +async function getFeaturedUser( + BASE_URL: string, + host: string, + accessTokens: string | undefined, + limit: number, +) { + const accessTokenArr = accessTokens?.split(" ") ?? [null]; + const accessToken = accessTokenArr[accessTokenArr.length - 1]; + try { + const api = await axios.post(`${BASE_URL}/api/users`, { + i: accessToken, + limit, + origin: "local", + sort: "+follower", + state: "alive", + }); + const data: MisskeyEntity.UserDetail[] = api.data; + console.log(data); + return data.map((u) => { + return { + source: "past_interactions", + account: Converter.userDetail(u, host), + }; + }); + } catch (e: any) { + console.log(e); + console.log(e.response.data); + return []; + } } diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index 3981a1781..d04b7a8b9 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -2,6 +2,12 @@ import Router from "@koa/router"; import { getClient } from "../ApiMastodonCompatibleService.js"; import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js"; import axios from "axios"; +import querystring from 'node:querystring' +import qs from 'qs' +function normalizeQuery(data: any) { + const str = querystring.stringify(data); + return qs.parse(str); +} export function apiStatusMastodon(router: Router): void { router.post("/v1/statuses", async (ctx) => { @@ -9,9 +15,12 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const body: any = ctx.request.body; + let body: any = ctx.request.body; + if ((!body.poll && body['poll[options][]']) || (!body.media_ids && body['media_ids[]'])) { + body = normalizeQuery(body) + } const text = body.status; - const removed = text.replace(/@\S+/g, "").replaceAll(" ", ""); + const removed = text.replace(/@\S+/g, "").replace(/\s|​/g, '') const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed); const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed); if ((body.in_reply_to_id && isDefaultEmoji) || isCustomEmoji) { @@ -35,7 +44,9 @@ export function apiStatusMastodon(router: Router): void { } } if (!body.media_ids) body.media_ids = undefined; - if (body.media_ids && !body.media_ids.length) body.media_ids = undefined; + if (body.media_ids && !body.media_ids.length) body.media_ids = undefined; + const { sensitive } = body + body.sensitive = typeof sensitive === 'string' ? sensitive === 'true' : sensitive const data = await client.postStatus(text, body); ctx.body = data.data; } catch (e: any) { @@ -70,7 +81,7 @@ export function apiStatusMastodon(router: Router): void { const data = await client.deleteStatus(ctx.params.id); ctx.body = data.data; } catch (e: any) { - console.error(e); + console.error(e.response.data, request.params.id); ctx.status = 401; ctx.body = e.response.data; } @@ -430,6 +441,6 @@ export function statusModel( pinned: false, emoji_reactions: [], bookmarked: false, - quote: false, + quote: null, }; } diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index 7cd9e26cb..1b5afd6d0 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -9,7 +9,9 @@ export function limitToInt(q: ParsedUrlQuery) { let object: any = q; if (q.limit) if (typeof q.limit === "string") object.limit = parseInt(q.limit, 10); - return q; + if (q.offset) + if (typeof q.offset === "string") object.offset = parseInt(q.offset, 10); + return object; } export function argsToBools(q: ParsedUrlQuery) { @@ -26,12 +28,29 @@ export function argsToBools(q: ParsedUrlQuery) { export function toTextWithReaction(status: Entity.Status[], host: string) { return status.map((t) => { if (!t) return statusModel(null, null, [], "no content"); + t.quote = null as any; if (!t.emoji_reactions) return t; if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0]; - const reactions = t.emoji_reactions.map( - (r) => `${r.name.replace("@.", "")} (${r.count}${r.me ? "* " : ""})`, - ); - //t.emojis = getEmoji(t.content, host) + const reactions = t.emoji_reactions.map((r) => { + const emojiNotation = r.url ? `:${r.name.replace('@.', '')}:` : r.name + return `${emojiNotation} (${r.count}${r.me ? `* ` : ''})` + }); + const reaction = t.emoji_reactions as Entity.Reaction[]; + const emoji = t.emojis || [] + for (const r of reaction) { + if (!r.url) continue + emoji.push({ + 'shortcode': r.name, + 'url': r.url, + 'static_url': r.url, + 'visible_in_picker': true, + },) + } + const isMe = reaction.findIndex((r) => r.me) > -1; + const total = reaction.reduce((sum, reaction) => sum + reaction.count, 0); + t.favourited = isMe; + t.favourites_count = total; + t.emojis = emoji t.content = `

${autoLinker(t.content, host)}

${reactions.join( ", ", )}

`; @@ -103,7 +122,7 @@ export function apiTimelineMastodon(router: Router): void { } }, ); - router.get<{ Params: { hashtag: string } }>( + router.get( "/v1/timelines/home", async (ctx, reply) => { const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index 10b9fb649..f285b2f5b 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -414,12 +414,13 @@ export default class Connection { const client = getClient(this.host, this.accessToken); client.getStatus(payload.id).then((data) => { const newPost = toTextWithReaction([data.data], this.host); + const targetPost = newPost[0] for (const stream of this.currentSubscribe) { this.wsConnection.send( JSON.stringify({ stream, event: "status.update", - payload: JSON.stringify(newPost[0]), + payload: JSON.stringify(targetPost), }), ); } diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 6dff70665..174aaf222 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -30,6 +30,8 @@ import proxyServer from "./proxy/index.js"; import webServer from "./web/index.js"; import { initializeStreamingServer } from "./api/streaming.js"; import { koaBody } from "koa-body"; +import removeTrailingSlash from "koa-remove-trailing-slashes"; +import {v4 as uuid} from "uuid"; export const serverLogger = new Logger("server", "gray", false); @@ -37,6 +39,8 @@ export const serverLogger = new Logger("server", "gray", false); const app = new Koa(); app.proxy = true; +app.use(removeTrailingSlash()); + if (!["production", "test"].includes(process.env.NODE_ENV || "")) { // Logger app.use( @@ -75,6 +79,7 @@ const mastoRouter = new Router(); mastoRouter.use( koaBody({ urlencoded: true, + multipart: true, }), ); @@ -154,24 +159,46 @@ router.get("/verify-email/:code", async (ctx) => { }); mastoRouter.get("/oauth/authorize", async (ctx) => { - const client_id = ctx.request.query.client_id; + const { client_id, state, redirect_uri } = ctx.request.query; console.log(ctx.request.req); - ctx.redirect(Buffer.from(client_id?.toString() || "", "base64").toString()); + let param = "mastodon=true"; + if (state) + param += `&state=${state}`; + if (redirect_uri) + param += `&redirect_uri=${redirect_uri}`; + const client = client_id? client_id : ""; + ctx.redirect(`${Buffer.from(client.toString(), 'base64').toString()}?${param}`); }); mastoRouter.post("/oauth/token", async (ctx) => { - const body: any = ctx.request.body; - let client_id: any = ctx.request.query.client_id; + const body: any = ctx.request.body || ctx.request.query; + console.log('token-request', body); + console.log('token-query', ctx.request.query); + if (body.redirect_uri.startsWith('com.tapbots') && body.grant_type === 'client_credentials') { + const ret = { + access_token: uuid(), + token_type: "Bearer", + scope: "read", + created_at: Math.floor(new Date().getTime() / 1000), + }; + ctx.body = ret; + return; + } + let client_id: any = body.client_id; const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; const generator = (megalodon as any).default; const client = generator("misskey", BASE_URL, null) as MegalodonInterface; let m = null; + let token = null; if (body.code) { - m = body.code.match(/^[a-zA-Z0-9-]+/); - if (!m.length) { - ctx.body = { error: "Invalid code" }; - return; - } + //m = body.code.match(/^([a-zA-Z0-9]{8})([a-zA-Z0-9]{4})([a-zA-Z0-9]{4})([a-zA-Z0-9]{4})([a-zA-Z0-9]{12})/); + //if (!m.length) { + // ctx.body = { error: "Invalid code" }; + // return; + //} + //token = `${m[1]}-${m[2]}-${m[3]}-${m[4]}-${m[5]}` + console.log(body.code, token) + token = body.code } if (client_id instanceof Array) { client_id = client_id.toString(); @@ -182,14 +209,16 @@ mastoRouter.post("/oauth/token", async (ctx) => { const atData = await client.fetchAccessToken( client_id, body.client_secret, - m ? m[0] : "", + token ? token : "", ); - ctx.body = { + const ret = { access_token: atData.accessToken, token_type: "Bearer", - scope: "read write follow", + scope: body.scope || 'read write follow push', created_at: Math.floor(new Date().getTime() / 1000), }; + console.log('token-response', ret) + ctx.body = ret; } catch (err: any) { console.error(err); ctx.status = 401; diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts index a7fa0de4c..8563573d4 100644 --- a/packages/backend/src/server/nodeinfo.ts +++ b/packages/backend/src/server/nodeinfo.ts @@ -3,7 +3,7 @@ import config from "@/config/index.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; import { Users, Notes } from "@/models/index.js"; import { IsNull, MoreThan } from "typeorm"; -import { MAX_NOTE_TEXT_LENGTH } from "@/const.js"; +import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "@/const.js"; import { Cache } from "@/misc/cache.js"; const router = new Router(); @@ -85,6 +85,7 @@ const nodeinfo2 = async () => { enableHcaptcha: meta.enableHcaptcha, enableRecaptcha: meta.enableRecaptcha, maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, + maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH, enableTwitterIntegration: meta.enableTwitterIntegration, enableGithubIntegration: meta.enableGithubIntegration, enableDiscordIntegration: meta.enableDiscordIntegration, diff --git a/packages/client/package.json b/packages/client/package.json index 4289d8049..1f3270b1d 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -9,6 +9,7 @@ "devDependencies": { "@discordapp/twemoji": "14.0.2", "@khmyznikov/pwa-install": "^0.2.0", + "@phosphor-icons/web": "^2.0.3", "@rollup/plugin-alias": "3.1.9", "@rollup/plugin-json": "4.1.0", "@rollup/pluginutils": "^4.2.1", @@ -34,9 +35,9 @@ "calckey-js": "^0.0.22", "chart.js": "4.1.1", "chartjs-adapter-date-fns": "2.0.1", + "chartjs-chart-matrix": "^2.0.1", "chartjs-plugin-gradient": "0.5.1", "chartjs-plugin-zoom": "1.2.1", - "chartjs-chart-matrix": "^2.0.1", "city-timezones": "^1.2.1", "compare-versions": "5.0.3", "cropperjs": "2.0.0-beta.2", diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index 6f8a8292e..fd9bf48e6 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -242,7 +242,7 @@ export async function openAccountMenu( ...accountItemPromises, { type: "parent", - icon: "ph-plus-bold ph-lg", + icon: "ph-plus ph-bold ph-lg", text: i18n.ts.addAccount, children: [ { @@ -261,13 +261,13 @@ export async function openAccountMenu( }, { type: "link", - icon: "ph-users-bold ph-lg", + icon: "ph-users ph-bold ph-lg", text: i18n.ts.manageAccounts, to: "/settings/accounts", }, { type: "button", - icon: "ph-sign-out-bold ph-lg", + icon: "ph-sign-out ph-bold ph-lg", text: i18n.ts.logout, action: () => { signout(); diff --git a/packages/client/src/components/MkAbuseReportWindow.vue b/packages/client/src/components/MkAbuseReportWindow.vue index b8ef6686c..f1b3ae431 100644 --- a/packages/client/src/components/MkAbuseReportWindow.vue +++ b/packages/client/src/components/MkAbuseReportWindow.vue @@ -1,7 +1,7 @@ diff --git a/packages/client/src/components/MkLink.vue b/packages/client/src/components/MkLink.vue index 41c8c7e4d..307e6bec2 100644 --- a/packages/client/src/components/MkLink.vue +++ b/packages/client/src/components/MkLink.vue @@ -3,7 +3,7 @@ :title="url" > - + diff --git a/packages/client/src/components/MkMediaBanner.vue b/packages/client/src/components/MkMediaBanner.vue index daf9bfc23..b09e61d37 100644 --- a/packages/client/src/components/MkMediaBanner.vue +++ b/packages/client/src/components/MkMediaBanner.vue @@ -1,7 +1,7 @@ diff --git a/packages/client/src/components/MkMediaVideo.vue b/packages/client/src/components/MkMediaVideo.vue index e5b4ba390..48a599407 100644 --- a/packages/client/src/components/MkMediaVideo.vue +++ b/packages/client/src/components/MkMediaVideo.vue @@ -1,7 +1,7 @@ diff --git a/packages/client/src/components/MkMenu.vue b/packages/client/src/components/MkMenu.vue index c07185377..1b8a609c9 100644 --- a/packages/client/src/components/MkMenu.vue +++ b/packages/client/src/components/MkMenu.vue @@ -19,16 +19,16 @@ {{ item.text }} - + {{ item.text }} - + {{ item.text }} @@ -36,13 +36,13 @@ diff --git a/packages/client/src/components/MkModalPageWindow.vue b/packages/client/src/components/MkModalPageWindow.vue index 60ce03a2c..7d2a0e569 100644 --- a/packages/client/src/components/MkModalPageWindow.vue +++ b/packages/client/src/components/MkModalPageWindow.vue @@ -2,13 +2,13 @@
- + {{ pageMetadata?.value.title }} - +
@@ -68,22 +68,22 @@ const contextmenu = $computed(() => { type: 'label', text: path, }, { - icon: 'ph-arrows-out-simple-bold ph-lg', + icon: 'ph-arrows-out-simple ph-bold ph-lg', text: i18n.ts.showInPage, action: expand, }, { - icon: 'ph-arrow-square-out-bold ph-lg', + icon: 'ph-arrow-square-out ph-bold ph-lg', text: i18n.ts.popout, action: popout, }, null, { - icon: 'ph-arrow-square-out-bold ph-lg', + icon: 'ph-arrow-square-out ph-bold ph-lg', text: i18n.ts.openInNewTab, action: () => { window.open(pageUrl, '_blank'); modal.close(); }, }, { - icon: 'ph-link-simple-bold ph-lg', + icon: 'ph-link-simple ph-bold ph-lg', text: i18n.ts.copyLink, action: () => { copyToClipboard(pageUrl); diff --git a/packages/client/src/components/MkModalWindow.vue b/packages/client/src/components/MkModalWindow.vue index 2765a9927..1d10eba77 100644 --- a/packages/client/src/components/MkModalWindow.vue +++ b/packages/client/src/components/MkModalWindow.vue @@ -2,12 +2,12 @@
- + - - + +
diff --git a/packages/client/src/components/MkMoved.vue b/packages/client/src/components/MkMoved.vue index e9be9a079..d50d49faa 100644 --- a/packages/client/src/components/MkMoved.vue +++ b/packages/client/src/components/MkMoved.vue @@ -1,6 +1,6 @@ diff --git a/packages/client/src/components/MkRenoteButton.vue b/packages/client/src/components/MkRenoteButton.vue index a39b916b6..5a9369a90 100644 --- a/packages/client/src/components/MkRenoteButton.vue +++ b/packages/client/src/components/MkRenoteButton.vue @@ -6,11 +6,11 @@ class="eddddedb _button canRenote" @click="renote(false, $event)" > - +

{{ count }}

@@ -66,7 +66,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => { let buttonActions = [{ text: i18n.ts.renote, - icon: 'ph-repeat-bold ph-lg', + icon: 'ph-repeat ph-bold ph-lg', danger: false, action: () => { os.api('notes/create', { @@ -86,7 +86,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => { if (!defaultStore.state.seperateRenoteQuote) { buttonActions.push({ text: i18n.ts.quote, - icon: 'ph-quotes-bold ph-lg', + icon: 'ph-quotes ph-bold ph-lg', danger: false, action: () => { os.post({ @@ -99,7 +99,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => { if (hasRenotedBefore) { buttonActions.push({ text: i18n.ts.unrenote, - icon: 'ph-trash-bold ph-lg', + icon: 'ph-trash ph-bold ph-lg', danger: true, action: () => { os.api('notes/unrenote', { diff --git a/packages/client/src/components/MkSignin.vue b/packages/client/src/components/MkSignin.vue index b04f1cea6..63a5bf10e 100644 --- a/packages/client/src/components/MkSignin.vue +++ b/packages/client/src/components/MkSignin.vue @@ -11,7 +11,7 @@ - + {{ signing ? i18n.ts.loggingIn : i18n.ts.login }} @@ -30,20 +30,20 @@

{{ i18n.ts.twoStepAuthentication }}

- + - + {{ signing ? i18n.ts.loggingIn : i18n.ts.login }}
diff --git a/packages/client/src/components/MkSignup.vue b/packages/client/src/components/MkSignup.vue index 9b09e852e..b3f7f473c 100644 --- a/packages/client/src/components/MkSignup.vue +++ b/packages/client/src/components/MkSignup.vue @@ -2,53 +2,53 @@
- +
- + - - + + - + - + diff --git a/packages/client/src/components/MkStarButton.vue b/packages/client/src/components/MkStarButton.vue index 9a0cca414..9327e1dd4 100644 --- a/packages/client/src/components/MkStarButton.vue +++ b/packages/client/src/components/MkStarButton.vue @@ -6,9 +6,9 @@ - - - + + + diff --git a/packages/client/src/components/MkSubNoteContent.vue b/packages/client/src/components/MkSubNoteContent.vue index 5799bc6fb..299ed2ea0 100644 --- a/packages/client/src/components/MkSubNoteContent.vue +++ b/packages/client/src/components/MkSubNoteContent.vue @@ -2,7 +2,7 @@
({{ i18n.ts.deleted }}) - + {{ i18n.ts.quoteAttached }}: ...
diff --git a/packages/client/src/components/MkTutorialDialog.vue b/packages/client/src/components/MkTutorialDialog.vue index 0160ace21..c077d3fc5 100644 --- a/packages/client/src/components/MkTutorialDialog.vue +++ b/packages/client/src/components/MkTutorialDialog.vue @@ -13,17 +13,17 @@ -

{{ i18n.ts._tutorial.title }}

+

{{ i18n.ts._tutorial.title }}

{{ i18n.ts._tutorial.step1_1 }}

@@ -41,7 +41,7 @@
{{ i18n.ts._tutorial.step3_2 }}

- {{ i18n.ts.next }} + {{ i18n.ts.next }}

{{ i18n.ts._tutorial.step4_1 }}

@@ -64,35 +64,35 @@
  • diff --git a/packages/client/src/components/MkUrlPreview.vue b/packages/client/src/components/MkUrlPreview.vue index 6c8f19f58..3aa9a9be2 100644 --- a/packages/client/src/components/MkUrlPreview.vue +++ b/packages/client/src/components/MkUrlPreview.vue @@ -1,6 +1,6 @@