Merge branch 'develop' of https://codeberg.org/calckey/calckey into note-improvements

This commit is contained in:
Freeplay 2023-03-15 18:40:21 -04:00
commit 23727bae92
299 changed files with 1864 additions and 22470 deletions

View file

@ -96,6 +96,9 @@ id: 'aid'
# Max note length, should be < 8000. # Max note length, should be < 8000.
#maxNoteLength: 3000 #maxNoteLength: 3000
# Maximum lenght of an image caption or file comment (default 1500, max 8192)
#maxCaptionLength: 1500
# Whether disable HSTS # Whether disable HSTS
#disableHsts: true #disableHsts: true

View file

@ -19,6 +19,8 @@ git remote set-url origin https://codeberg.org/calckey/calckey.git
git fetch git fetch
git checkout main # or beta or develop git checkout main # or beta or develop
git pull --ff git pull --ff
NODE_ENV=production pnpm run migrate
# build using prefered method # build using prefered method
``` ```
@ -29,6 +31,8 @@ git remote set-url origin https://codeberg.org/calckey/calckey.git
git fetch git fetch
git checkout main # or beta or develop git checkout main # or beta or develop
git pull --ff git pull --ff
NODE_ENV=production pnpm run migrate
# build using prefered method # build using prefered method
``` ```
@ -48,5 +52,7 @@ git remote set-url origin https://codeberg.org/calckey/calckey.git
git fetch git fetch
git checkout main # or beta or develop git checkout main # or beta or develop
git pull --ff git pull --ff
NODE_ENV=production pnpm run migrate
# build using prefered method # build using prefered method
``` ```

View file

@ -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.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 => { gulp.task('copy:client:locales', cb => {
fs.mkdirSync('./built/_client_dist_/locales', { recursive: true }); fs.mkdirSync('./built/_client_dist_/locales', { recursive: true });
@ -59,7 +55,7 @@ gulp.task('build:backend:style', () => {
}); });
gulp.task('build', gulp.parallel( 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')); gulp.task('default', gulp.task('build'));

View file

@ -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." importRequested: "Has sol·licitat una importació. Això pot trigar una estona."
lists: "Llistes" lists: "Llistes"
noLists: "No tens cap llista" noLists: "No tens cap llista"
note: "Nota" note: "Post"
notes: "Notes" notes: "Posts"
following: "Seguint" following: "Seguint"
followers: "Seguidors" followers: "Seguidors"
followsYou: "Et segueix" followsYou: "Et segueix"
@ -141,7 +141,7 @@ _theme:
mention: "Menció" mention: "Menció"
renote: "Renotar" renote: "Renotar"
_sfx: _sfx:
note: "Notes" note: "Posts"
notification: "Notificacions" notification: "Notificacions"
_2fa: _2fa:
step2Url: "També pots inserir aquest enllaç i utilitzes una aplicació d'escriptori:" step2Url: "També pots inserir aquest enllaç i utilitzes una aplicació d'escriptori:"

View file

@ -1806,7 +1806,7 @@ _apps:
pwa: "Install PWA" pwa: "Install PWA"
kaiteki: "Kaiteki" kaiteki: "Kaiteki"
milktea: "Milktea" milktea: "Milktea"
subwayTooter: "Subway Tooter" missLi: "MissLi"
kimis: "Kimis" mona: "Mona"
theDesk: "TheDesk" theDesk: "TheDesk"
lesskey: "Lesskey" lesskey: "Lesskey"

View file

@ -70,8 +70,8 @@ exportRequested: "Vous avez demandé une exportation. Lopération pourrait pr
importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps." importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps."
lists: "Listes" lists: "Listes"
noLists: "Vous navez aucune liste" noLists: "Vous navez aucune liste"
note: "Notes" note: "Post"
notes: "Notes" notes: "Posts"
following: "Abonnements" following: "Abonnements"
followers: "Abonné·e·s" followers: "Abonné·e·s"
followsYou: "Vous suit" followsYou: "Vous suit"

View file

@ -1,7 +1,7 @@
---
_lang_: "日本語" _lang_: "日本語"
headlineMisskey: "ずっと無料でオープンソースの非中央集権型ソーシャルメディアプラットフォーム🚀"
headlineMisskey: "ノートでつながるネットワーク" introMisskey: "ようこそCalckeyは、オープンソースの非中央集権型ソーシャルメディアプラットフォームです。\nいま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「リアクション」機能で、皆の投稿に素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀"
introMisskey: "ようこそMisskeyは、オープンソースの分散型マイクロブログサービスです。\n「ート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「リアクション」機能で、皆のートに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀"
monthAndDay: "{month}月 {day}日" monthAndDay: "{month}月 {day}日"
search: "検索" search: "検索"
notifications: "通知" notifications: "通知"
@ -10,11 +10,11 @@ password: "パスワード"
forgotPassword: "パスワードを忘れた" forgotPassword: "パスワードを忘れた"
fetchingAsApObject: "連合に照会中" fetchingAsApObject: "連合に照会中"
ok: "OK" ok: "OK"
gotIt: "わかった" gotIt: "わかった"
cancel: "キャンセル" cancel: "キャンセル"
enterUsername: "ユーザー名を入力" enterUsername: "ユーザー名を入力"
renotedBy: "{user}がRenote" renotedBy: "{user}がブースト"
noNotes: "ノートはありません" noNotes: "投稿はありません"
noNotifications: "通知はありません" noNotifications: "通知はありません"
instance: "インスタンス" instance: "インスタンス"
settings: "設定" settings: "設定"
@ -32,6 +32,7 @@ uploading: "アップロード中"
save: "保存" save: "保存"
users: "ユーザー" users: "ユーザー"
addUser: "ユーザーを追加" addUser: "ユーザーを追加"
addInstance: "インスタンスを追加"
favorite: "お気に入り" favorite: "お気に入り"
favorites: "お気に入り" favorites: "お気に入り"
unfavorite: "お気に入り解除" unfavorite: "お気に入り解除"
@ -44,7 +45,7 @@ copyContent: "内容をコピー"
copyLink: "リンクをコピー" copyLink: "リンクをコピー"
delete: "削除" delete: "削除"
deleteAndEdit: "削除して編集" deleteAndEdit: "削除して編集"
deleteAndEditConfirm: "このートを削除してもう一度編集しますかこのートへのリアクション、Renote、返信も全て削除されます。" deleteAndEditConfirm: "この投稿を削除してもう一度編集しますか?この投稿へのリアクション、ブースト、返信も全て削除されます。"
addToList: "リストに追加" addToList: "リストに追加"
sendMessage: "メッセージを送信" sendMessage: "メッセージを送信"
copyUsername: "ユーザー名をコピー" copyUsername: "ユーザー名をコピー"
@ -64,14 +65,14 @@ import: "インポート"
export: "エクスポート" export: "エクスポート"
files: "ファイル" files: "ファイル"
download: "ダウンロード" download: "ダウンロード"
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを添付したノートも消えます。" driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを添付した投稿も消えます。"
unfollowConfirm: "{name}のフォローを解除しますか?" unfollowConfirm: "{name}のフォローを解除しますか?"
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。" exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。" importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。"
lists: "リスト" lists: "リスト"
noLists: "リストはありません" noLists: "リストはありません"
note: "ノート" note: "投稿"
notes: "ノート" notes: "投稿"
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
followsYou: "フォローされています" followsYou: "フォローされています"
@ -94,13 +95,13 @@ followRequests: "フォロー申請"
unfollow: "フォロー解除" unfollow: "フォロー解除"
followRequestPending: "フォロー許可待ち" followRequestPending: "フォロー許可待ち"
enterEmoji: "絵文字を入力" enterEmoji: "絵文字を入力"
renote: "Renote" renote: "ブースト"
unrenote: "Renote解除" unrenote: "ブースト解除"
renoted: "Renoteしました。" renoted: "ブーストしました。"
cantRenote: "この投稿はRenoteできません。" cantRenote: "この投稿はブーストできません。"
cantReRenote: "RenoteをRenoteすることはできません。" cantReRenote: "ブーストをブーストすることはできません。"
quote: "引用" quote: "引用"
pinnedNote: "ピン留めされたノート" pinnedNote: "ピン留めされた投稿"
pinned: "ピン留め" pinned: "ピン留め"
you: "あなた" you: "あなた"
clickToShow: "クリックして表示" clickToShow: "クリックして表示"
@ -139,11 +140,11 @@ settingGuide: "おすすめ設定"
cacheRemoteFiles: "リモートのファイルをキャッシュする" cacheRemoteFiles: "リモートのファイルをキャッシュする"
cacheRemoteFilesDescription: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。サーバーのストレージを節約できますが、サムネイルが生成されないので通信量が増加します。" cacheRemoteFilesDescription: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。サーバーのストレージを節約できますが、サムネイルが生成されないので通信量が増加します。"
flagAsBot: "Botとして設定" flagAsBot: "Botとして設定"
flagAsBotDescription: "このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Misskeyのシステム上での扱いがBotに合ったものになります。" flagAsBotDescription: "このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Calckeyのシステム上での扱いがBotに合ったものになります。"
flagAsCat: "Catとして設定" flagAsCat: "あなたは…猫?😺"
flagAsCatDescription: "このアカウントが猫であることを示す場合は、このフラグをオンにします。" flagAsCatDescription: "このアカウントが猫であることを示す場合は、このフラグをオンにします。"
flagShowTimelineReplies: "タイムラインにノートへの返信を表示する" flagShowTimelineReplies: "タイムラインに投稿の返信を表示する"
flagShowTimelineRepliesDescription: "オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。" flagShowTimelineRepliesDescription: "オンにすると、タイムラインにユーザーの投稿以外にもそのユーザーの他の投稿への返信を表示します。"
autoAcceptFollowed: "フォロー中ユーザーからのフォロリクを自動承認" autoAcceptFollowed: "フォロー中ユーザーからのフォロリクを自動承認"
addAccount: "アカウントを追加" addAccount: "アカウントを追加"
loginFailed: "ログインに失敗しました" loginFailed: "ログインに失敗しました"
@ -160,6 +161,7 @@ proxyAccount: "プロキシアカウント"
proxyAccountDescription: "プロキシアカウントは、特定の条件下でユーザーのリモートフォローを代行するアカウントです。例えば、ユーザーがリモートユーザーをリストに入れたとき、リストに入れられたユーザーを誰もフォローしていないとアクティビティがインスタンスに配達されないため、代わりにプロキシアカウントがフォローするようにします。" proxyAccountDescription: "プロキシアカウントは、特定の条件下でユーザーのリモートフォローを代行するアカウントです。例えば、ユーザーがリモートユーザーをリストに入れたとき、リストに入れられたユーザーを誰もフォローしていないとアクティビティがインスタンスに配達されないため、代わりにプロキシアカウントがフォローするようにします。"
host: "ホスト" host: "ホスト"
selectUser: "ユーザーを選択" selectUser: "ユーザーを選択"
selectInstance: "インスタンスを選択"
recipient: "宛先" recipient: "宛先"
annotation: "注釈" annotation: "注釈"
federation: "連合" federation: "連合"
@ -197,10 +199,11 @@ muteAndBlock: "ミュートとブロック"
mutedUsers: "ミュートしたユーザー" mutedUsers: "ミュートしたユーザー"
blockedUsers: "ブロックしたユーザー" blockedUsers: "ブロックしたユーザー"
noUsers: "ユーザーはいません" noUsers: "ユーザーはいません"
noInstances: "インスタンスはありません"
editProfile: "プロフィールを編集" editProfile: "プロフィールを編集"
noteDeleteConfirm: "このノートを削除しますか?" noteDeleteConfirm: "この投稿を削除しますか?"
pinLimitExceeded: "これ以上ピン留めできません" pinLimitExceeded: "これ以上ピン留めできません"
intro: "Misskeyのインストールが完了しました管理者アカウントを作成しましょう。" intro: "Calckeyのインストールが完了しました管理者アカウントを作成しましょう。"
done: "完了" done: "完了"
processing: "処理中" processing: "処理中"
preview: "プレビュー" preview: "プレビュー"
@ -325,7 +328,7 @@ connectService: "接続する"
disconnectService: "切断する" disconnectService: "切断する"
enableLocalTimeline: "ローカルタイムラインを有効にする" enableLocalTimeline: "ローカルタイムラインを有効にする"
enableGlobalTimeline: "グローバルタイムラインを有効にする" enableGlobalTimeline: "グローバルタイムラインを有効にする"
enableRecommendedTimeline: "推奨されるタイムラインを有効にする" enableRecommendedTimeline: "おすすめタイムラインを有効にする"
disablingTimelinesInfo: "これらのタイムラインを無効化しても、利便性のため管理者およびモデレーターは引き続き利用することができます。" disablingTimelinesInfo: "これらのタイムラインを無効化しても、利便性のため管理者およびモデレーターは引き続き利用することができます。"
registration: "登録" registration: "登録"
enableRegistration: "誰でも新規登録できるようにする" enableRegistration: "誰でも新規登録できるようにする"
@ -342,7 +345,7 @@ pinnedUsersDescription: "「みつける」ページなどにピン留めした
pinnedPages: "ピン留めページ" pinnedPages: "ピン留めページ"
pinnedPagesDescription: "インスタンスのトップページにピン留めしたいページのパスを改行で区切って記述します。" pinnedPagesDescription: "インスタンスのトップページにピン留めしたいページのパスを改行で区切って記述します。"
pinnedClipId: "ピン留めするクリップのID" pinnedClipId: "ピン留めするクリップのID"
pinnedNotes: "ピン留めされたノート" pinnedNotes: "ピン留めされた投稿"
hcaptcha: "hCaptcha" hcaptcha: "hCaptcha"
enableHcaptcha: "hCaptchaを有効にする" enableHcaptcha: "hCaptchaを有効にする"
hcaptchaSiteKey: "サイトキー" hcaptchaSiteKey: "サイトキー"
@ -359,10 +362,11 @@ antennaSource: "受信ソース"
antennaKeywords: "受信キーワード" antennaKeywords: "受信キーワード"
antennaExcludeKeywords: "除外キーワード" antennaExcludeKeywords: "除外キーワード"
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
notifyAntenna: "新しいノートを通知する" notifyAntenna: "新しい投稿を通知する"
withFileAntenna: "ファイルが添付されたノートのみ" withFileAntenna: "ファイルが添付された投稿のみ"
enableServiceworker: "ブラウザへのプッシュ通知を有効にする" enableServiceworker: "ブラウザへのプッシュ通知を有効にする"
antennaUsersDescription: "ユーザー名を改行で区切って指定します" antennaUsersDescription: "ユーザー名を改行で区切って指定します"
antennaInstancesDescription: "インスタンスを改行で区切って指定します"
caseSensitive: "大文字小文字を区別する" caseSensitive: "大文字小文字を区別する"
withReplies: "返信を含む" withReplies: "返信を含む"
connectedTo: "次のアカウントに接続されています" connectedTo: "次のアカウントに接続されています"
@ -393,7 +397,7 @@ securityKeyName: "キーの名前"
registerSecurityKey: "セキュリティキーを登録する" registerSecurityKey: "セキュリティキーを登録する"
lastUsed: "最後の使用" lastUsed: "最後の使用"
unregister: "登録を解除" unregister: "登録を解除"
passwordLessLogin: "パスワード無しログイン" passwordLessLogin: "パスワード無しログイン"
resetPassword: "パスワードをリセット" resetPassword: "パスワードをリセット"
newPasswordIs: "新しいパスワードは「{password}」です" newPasswordIs: "新しいパスワードは「{password}」です"
reduceUiAnimation: "UIのアニメーションを減らす" reduceUiAnimation: "UIのアニメーションを減らす"
@ -422,9 +426,9 @@ messagingWithGroup: "グループでチャット"
title: "タイトル" title: "タイトル"
text: "テキスト" text: "テキスト"
enable: "有効にする" enable: "有効にする"
next: "次" next: "次"
retype: "再入力" retype: "再入力"
noteOf: "{user}のノート" noteOf: "{user}の投稿"
inviteToGroup: "グループに招待" inviteToGroup: "グループに招待"
quoteAttached: "引用付き" quoteAttached: "引用付き"
quoteQuestion: "引用として添付しますか?" quoteQuestion: "引用として添付しますか?"
@ -482,8 +486,8 @@ accountSettings: "アカウント設定"
promotion: "プロモーション" promotion: "プロモーション"
promote: "プロモート" promote: "プロモート"
numberOfDays: "日数" numberOfDays: "日数"
hideThisNote: "このノートを非表示" hideThisNote: "この投稿を非表示"
showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを表示する" showFeaturedNotesInTimeline: "タイムラインにおすすめの投稿を表示する"
objectStorage: "オブジェクトストレージ" objectStorage: "オブジェクトストレージ"
useObjectStorage: "オブジェクトストレージを使用" useObjectStorage: "オブジェクトストレージを使用"
objectStorageBaseUrl: "Base URL" objectStorageBaseUrl: "Base URL"
@ -504,7 +508,7 @@ objectStorageSetPublicRead: "アップロード時に'public-read'を設定す
serverLogs: "サーバーログ" serverLogs: "サーバーログ"
deleteAll: "全て削除" deleteAll: "全て削除"
showFixedPostForm: "タイムライン上部に投稿フォームを表示する" showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
newNoteRecived: "新しいノートがあります" newNoteRecived: "新しい投稿があります"
sounds: "サウンド" sounds: "サウンド"
listen: "聴く" listen: "聴く"
none: "なし" none: "なし"
@ -519,7 +523,7 @@ recentUsed: "最近使用"
install: "インストール" install: "インストール"
uninstall: "アンインストール" uninstall: "アンインストール"
installedApps: "インストールされたアプリ" installedApps: "インストールされたアプリ"
nothing: "ありません" nothing: "まだ何もありません"
installedDate: "インストール日時" installedDate: "インストール日時"
lastUsedDate: "最終使用日時" lastUsedDate: "最終使用日時"
state: "状態" state: "状態"
@ -527,10 +531,10 @@ sort: "ソート"
ascendingOrder: "昇順" ascendingOrder: "昇順"
descendingOrder: "降順" descendingOrder: "降順"
scratchpad: "スクラッチパッド" scratchpad: "スクラッチパッド"
scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。" scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Calckeyと対話するコードの記述、実行、結果の確認ができます。"
output: "出力" output: "出力"
script: "スクリプト" script: "スクリプト"
disablePagesScript: "Pagesのスクリプトを無効にする" disablePagesScript: "ページのスクリプトを無効にする"
updateRemoteUser: "リモートユーザー情報の更新" updateRemoteUser: "リモートユーザー情報の更新"
deleteAllFiles: "すべてのファイルを削除" deleteAllFiles: "すべてのファイルを削除"
deleteAllFilesConfirm: "すべてのファイルを削除しますか?" deleteAllFilesConfirm: "すべてのファイルを削除しますか?"
@ -626,7 +630,7 @@ sample: "サンプル"
abuseReports: "通報" abuseReports: "通報"
reportAbuse: "通報" reportAbuse: "通報"
reportAbuseOf: "{name}を通報する" reportAbuseOf: "{name}を通報する"
fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。" fillAbuseReportDescription: "通報理由の詳細を記入してください。対象の投稿がある場合はそのURLも記入してください。"
abuseReported: "内容が送信されました。ご報告ありがとうございました。" abuseReported: "内容が送信されました。ご報告ありがとうございました。"
reporter: "通報者" reporter: "通報者"
reporteeOrigin: "通報先" reporteeOrigin: "通報先"
@ -639,7 +643,7 @@ openInNewTab: "新しいタブで開く"
openInSideView: "サイドビューで開く" openInSideView: "サイドビューで開く"
defaultNavigationBehaviour: "デフォルトのナビゲーション" defaultNavigationBehaviour: "デフォルトのナビゲーション"
editTheseSettingsMayBreakAccount: "これらの設定を編集するとアカウントが破損する可能性があります。" editTheseSettingsMayBreakAccount: "これらの設定を編集するとアカウントが破損する可能性があります。"
instanceTicker: "ノートのインスタンス情報" instanceTicker: "投稿のインスタンス情報"
waitingFor: "{x}を待っています" waitingFor: "{x}を待っています"
random: "ランダム" random: "ランダム"
system: "システム" system: "システム"
@ -650,16 +654,16 @@ createNew: "新規作成"
optional: "任意" optional: "任意"
createNewClip: "新しいクリップを作成" createNewClip: "新しいクリップを作成"
unclip: "クリップ解除" unclip: "クリップ解除"
confirmToUnclipAlreadyClippedNote: "このノートはすでにクリップ「{name}」に含まれています。ノートをこのクリップから除外しますか?" confirmToUnclipAlreadyClippedNote: "この投稿はすでにクリップ「{name}」に含まれています。投稿をこのクリップから除外しますか?"
public: "パブリック" public: "パブリック"
i18nInfo: "Calckeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。" i18nInfo: "Calckeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。"
manageAccessTokens: "アクセストークンの管理" manageAccessTokens: "アクセストークンの管理"
accountInfo: "アカウント情報" accountInfo: "アカウント情報"
notesCount: "ノートの数" notesCount: "投稿の数"
repliesCount: "返信した数" repliesCount: "返信した数"
renotesCount: "Renoteした数" renotesCount: "ブーストした数"
repliedCount: "返信された数" repliedCount: "返信された数"
renotedCount: "Renoteされた数" renotedCount: "ブーストされた数"
followingCount: "フォロー数" followingCount: "フォロー数"
followersCount: "フォロワー数" followersCount: "フォロワー数"
sentReactionsCount: "リアクションした数" sentReactionsCount: "リアクションした数"
@ -671,17 +675,17 @@ no: "いいえ"
driveFilesCount: "ドライブのファイル数" driveFilesCount: "ドライブのファイル数"
driveUsage: "ドライブ使用量" driveUsage: "ドライブ使用量"
noCrawle: "クローラーによるインデックスを拒否" noCrawle: "クローラーによるインデックスを拒否"
noCrawleDescription: "検索エンジンにあなたのユーザーページ、ート、Pagesなどのコンテンツを登録(インデックス)しないよう要請します。" noCrawleDescription: "検索エンジンにあなたのプロフィールや投稿、ページなどのコンテンツを登録(インデックス)しないよう要請します。"
lockedAccountInfo: "フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。" lockedAccountInfo: "フォローを承認制にしても、投稿の公開範囲を「フォロワー」にしない限り、誰でもあなたの投稿を見ることができます。"
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする" alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする"
loadRawImages: "添付画像のサムネイルをオリジナル画質にする" loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
disableShowingAnimatedImages: "アニメーション画像を再生しない" disableShowingAnimatedImages: "アニメーション画像を再生しない"
verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。" verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。"
notSet: "未設定" notSet: "未設定"
emailVerified: "メールアドレスが確認されました" emailVerified: "メールアドレスが確認されました"
noteFavoritesCount: "お気に入りノートの数" noteFavoritesCount: "お気に入りの投稿の数"
pageLikesCount: "Pageにいいねした数" pageLikesCount: "ページにいいねした数"
pageLikedCount: "Pageにいいねされた数" pageLikedCount: "ページにいいねされた数"
contact: "連絡先" contact: "連絡先"
useSystemFont: "システムのデフォルトのフォントを使う" useSystemFont: "システムのデフォルトのフォントを使う"
clips: "クリップ" clips: "クリップ"
@ -689,7 +693,7 @@ experimentalFeatures: "実験的機能"
developer: "開発者" developer: "開発者"
makeExplorable: "アカウントを見つけやすくする" makeExplorable: "アカウントを見つけやすくする"
makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らなくなります。" makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らなくなります。"
showGapBetweenNotesInTimeline: "タイムラインのノートを離して表示" showGapBetweenNotesInTimeline: "タイムラインの投稿を離して表示"
duplicate: "複製" duplicate: "複製"
left: "左" left: "左"
center: "中央" center: "中央"
@ -701,9 +705,9 @@ showTitlebar: "タイトルバーを表示する"
clearCache: "キャッシュをクリア" clearCache: "キャッシュをクリア"
onlineUsersCount: "{n}人がオンライン" onlineUsersCount: "{n}人がオンライン"
nUsers: "{n}ユーザー" nUsers: "{n}ユーザー"
nNotes: "{n}ノート" nNotes: "{n}投稿"
sendErrorReports: "エラーリポートを送信" sendErrorReports: "エラーリポートを送信"
sendErrorReportsDescription: "オンにすると、問題が発生したときにエラーの詳細情報がMisskeyに共有され、ソフトウェアの品質向上に役立てることができます。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれます。" sendErrorReportsDescription: "オンにすると、問題が発生したときにエラーの詳細情報がCalckeyに共有され、ソフトウェアの品質向上に役立てることができます。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれます。"
myTheme: "マイテーマ" myTheme: "マイテーマ"
backgroundColor: "背景" backgroundColor: "背景"
accentColor: "アクセント" accentColor: "アクセント"
@ -742,7 +746,7 @@ unlikeConfirm: "いいね解除しますか?"
fullView: "フルビュー" fullView: "フルビュー"
quitFullView: "フルビュー解除" quitFullView: "フルビュー解除"
addDescription: "説明を追加" addDescription: "説明を追加"
userPagePinTip: "個々のノートのメニューから「ピン留め」を選択することで、ここにノートを表示しておくことができます。" userPagePinTip: "個々の投稿のメニューから「ピン留め」を選択することで、ここに投稿を表示しておくことができます。"
notSpecifiedMentionWarning: "宛先に含まれていないメンションがあります" notSpecifiedMentionWarning: "宛先に含まれていないメンションがあります"
info: "情報" info: "情報"
userInfo: "ユーザー情報" userInfo: "ユーザー情報"
@ -772,7 +776,7 @@ postToGallery: "ギャラリーへ投稿"
gallery: "ギャラリー" gallery: "ギャラリー"
recentPosts: "最近の投稿" recentPosts: "最近の投稿"
popularPosts: "人気の投稿" popularPosts: "人気の投稿"
shareWithNote: "ノートで共有" shareWithNote: "投稿で共有"
ads: "広告" ads: "広告"
expiration: "期限" expiration: "期限"
memo: "メモ" memo: "メモ"
@ -786,7 +790,7 @@ secureMode: "セキュアモード (Authorized Fetch)"
instanceSecurity: "インスタンスのセキュリティー" instanceSecurity: "インスタンスのセキュリティー"
secureModeInfo: "他のインスタンスからリクエストするときに、証明を付けなければ返送しません。他のインスタンスの設定ファイルでsignToActivityPubGetはtrueにしてください。" secureModeInfo: "他のインスタンスからリクエストするときに、証明を付けなければ返送しません。他のインスタンスの設定ファイルでsignToActivityPubGetはtrueにしてください。"
privateMode: "非公開モード" privateMode: "非公開モード"
privateModeInfo: "有効にして、許可されているインスタンスのみがリクエストできます。すべてのノートが公開に非表示にします。" privateModeInfo: "有効にして、許可されているインスタンスのみがリクエストできます。すべての投稿が公開に非表示にします。"
allowedInstances: "許可されたインスタンス" allowedInstances: "許可されたインスタンス"
allowedInstancesDescription: "許可したいインスタンスのホストを改行で区切って設定します。非公開モードだけで有効です。" allowedInstancesDescription: "許可したいインスタンスのホストを改行で区切って設定します。非公開モードだけで有効です。"
previewNoteText: "本文をプレビュー" previewNoteText: "本文をプレビュー"
@ -794,7 +798,7 @@ customCss: "カスタムCSS"
customCssWarn: "この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。" customCssWarn: "この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。"
global: "グローバル" global: "グローバル"
squareAvatars: "アイコンを四角形で表示" squareAvatars: "アイコンを四角形で表示"
seperateRenoteQuote: "リノートと引用ボタンを分ける" seperateRenoteQuote: "ブーストと引用ボタンを分ける"
sent: "送信" sent: "送信"
received: "受信" received: "受信"
searchResult: "検索結果" searchResult: "検索結果"
@ -802,13 +806,13 @@ hashtags: "ハッシュタグ"
troubleshooting: "トラブルシューティング" troubleshooting: "トラブルシューティング"
useBlurEffect: "UIにぼかし効果を使用" useBlurEffect: "UIにぼかし効果を使用"
learnMore: "詳しく" learnMore: "詳しく"
misskeyUpdated: "Misskeyが更新されました" misskeyUpdated: "Calckeyが更新されました"
whatIsNew: "更新情報を見る" whatIsNew: "更新情報を見る"
translate: "翻訳" translate: "翻訳"
translatedFrom: "{x}から翻訳" translatedFrom: "{x}から翻訳"
accountDeletionInProgress: "アカウントの削除が進行中です" accountDeletionInProgress: "アカウントの削除が進行中です"
usernameInfo: "サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。" usernameInfo: "サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。"
aiChanMode: "藍モード" aiChanMode: "藍モードクラシックUI"
enterSendsMessage: "メッセージングでReturnキーを押すと、メッセージが送信されますデフォルトはCtrl + Returnです" enterSendsMessage: "メッセージングでReturnキーを押すと、メッセージが送信されますデフォルトはCtrl + Returnです"
keepCw: "CWを維持する" keepCw: "CWを維持する"
pubSub: "Pub/Subのアカウント" pubSub: "Pub/Subのアカウント"
@ -912,15 +916,25 @@ customMOTDDescription: "ユーザがページをロード/リロードするた
customSplashIcons: "カスタムスプラッシュスクリーンアイコン" customSplashIcons: "カスタムスプラッシュスクリーンアイコン"
customSplashIconsDescription: "ユーザがページをロード/リロードするたびにランダムに表示される、改行で区切られたカスタムスプラッシュスクリーンアイコンの URL。画像は静的なURLで、できればすべて192x192にリサイズしてください。" customSplashIconsDescription: "ユーザがページをロード/リロードするたびにランダムに表示される、改行で区切られたカスタムスプラッシュスクリーンアイコンの URL。画像は静的なURLで、できればすべて192x192にリサイズしてください。"
showUpdates: "Calckeyの更新時にポップアップを表示する" showUpdates: "Calckeyの更新時にポップアップを表示する"
recommendedInstances: "推奨インスタンス" recommendedInstances: "おすすめインスタンス"
recommendedInstancesDescription: "推奨タイムラインに表示するために改行で区切られた推奨インスタンス。`https://`を追加しないでください。ドメインのみを追加してください。" recommendedInstancesDescription: "おすすめタイムラインに表示される、改行で区切られたインスタンス。`https://`を追加しないでください。ドメインのみを追加してください。"
caption: "自動キャプション" caption: "自動キャプション"
splash: "スプラッシュスクリーン" splash: "スプラッシュスクリーン"
updateAvailable: "アップデートがありますよ" updateAvailable: "アップデートがありますよ"
swipeOnDesktop: "デスクトップでモバイルスタイルのスワイプを可能にする" swipeOnDesktop: "デスクトップでモバイルスタイルのスワイプを可能にする"
logoImageUrl: "ロゴのURL" logoImageUrl: "ロゴのURL"
showAdminUpdates: "新しいCalckeyのバージョンが利用可能であることを示す(管理者のみ)" showAdminUpdates: "新しいCalckeyのバージョンが利用可能であることを示す(管理者のみ)"
replayTutorial: "リプレイチュートリアル" replayTutorial: "もう一度チュートリアルを見る"
migration: "アカウントの引っ越し"
moveTo: "このアカウントを新しいアカウントに引っ越す"
moveToLabel: "引っ越し先のアカウント:"
moveAccount: "引っ越し実行!"
moveAccountDescription: "この操作は取り消せません。まずは引っ越し先のアカウントでこのアカウントに対しエイリアスを作成したことを確認してください。エイリアス作成後、引っ越し先のアカウントをこのように入力してください:@person@instance.com"
moveFrom: "別のアカウントからこのアカウントに引っ越す"
moveFromLabel: "引っ越し元のアカウント:"
moveFromDescription: "別のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。必ず引っ越しを実行する前に作成してください!引っ越し元のアカウントをこのように入力してください:@person@instance.com"
migrationConfirm: "本当にこのアカウントを {account} に引っ越しますか?一度引っ越しを行うと取り消せず、二度とこのアカウントを元の状態で使用することはできません。\nまた、引っ越し先のアカウントでエイリアスを作成したことを確認してください。"
defaultReaction: "リモートとローカルの投稿に対するデフォルトの絵文字リアクション"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。"
@ -930,24 +944,20 @@ _sensitiveMediaDetection:
setSensitiveFlagAutomaticallyDescription: "この設定をオフにしても内部的に判定結果は保持されます。" setSensitiveFlagAutomaticallyDescription: "この設定をオフにしても内部的に判定結果は保持されます。"
analyzeVideos: "動画の解析を有効化" analyzeVideos: "動画の解析を有効化"
analyzeVideosDescription: "静止画に加えて動画も解析するようにします。サーバーの負荷が少し増えます。" analyzeVideosDescription: "静止画に加えて動画も解析するようにします。サーバーの負荷が少し増えます。"
_emailUnavailable: _emailUnavailable:
used: "既に使用されています" used: "既に使用されています"
format: "形式が正しくありません" format: "形式が正しくありません"
disposable: "恒久的に使用可能なアドレスではありません" disposable: "恒久的に使用可能なアドレスではありません"
mx: "正しいメールサーバーではありません" mx: "正しいメールサーバーではありません"
smtp: "メールサーバーが応答しません" smtp: "メールサーバーが応答しません"
_ffVisibility: _ffVisibility:
public: "公開" public: "公開"
followers: "フォロワーだけに公開" followers: "フォロワーだけに公開"
private: "非公開" private: "非公開"
_signup: _signup:
almostThere: "ほとんど完了です" almostThere: "ほとんど完了です"
emailAddressInfo: "あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。" emailAddressInfo: "あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。"
emailSent: "入力されたメールアドレス({email})宛に確認のメールが送信されました。メールに記載されたリンクにアクセスすると、アカウントの作成が完了します。" emailSent: "入力されたメールアドレス({email})宛に確認のメールが送信されました。メールに記載されたリンクにアクセスすると、アカウントの作成が完了します。"
_accountDelete: _accountDelete:
accountDelete: "アカウントの削除" accountDelete: "アカウントの削除"
mayTakeTime: "アカウントの削除は負荷のかかる処理であるため、作成したコンテンツの数やアップロードしたファイルの数が多いと完了までに時間がかかることがあります。" mayTakeTime: "アカウントの削除は負荷のかかる処理であるため、作成したコンテンツの数やアップロードしたファイルの数が多いと完了までに時間がかかることがあります。"
@ -955,33 +965,27 @@ _accountDelete:
requestAccountDelete: "アカウント削除をリクエスト" requestAccountDelete: "アカウント削除をリクエスト"
started: "削除処理が開始されました。" started: "削除処理が開始されました。"
inProgress: "削除が進行中" inProgress: "削除が進行中"
_ad: _ad:
back: "戻る" back: "戻る"
reduceFrequencyOfThisAd: "この広告の表示頻度を下げる" reduceFrequencyOfThisAd: "この広告の表示頻度を下げる"
_forgotPassword: _forgotPassword:
enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。" enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。"
ifNoEmail: "メールアドレスを登録していない場合は、管理者までお問い合わせください。" ifNoEmail: "メールアドレスを登録していない場合は、管理者までお問い合わせください。"
contactAdmin: "このインスタンスではメールがサポートされていないため、パスワードリセットを行う場合は管理者までお問い合わせください。" contactAdmin: "このインスタンスではメールがサポートされていないため、パスワードリセットを行う場合は管理者までお問い合わせください。"
_gallery: _gallery:
my: "自分の投稿" my: "自分の投稿"
liked: "いいねした投稿" liked: "いいねした投稿"
like: "いいね!" like: "いいね!"
unlike: "いいね解除" unlike: "いいね解除"
_email: _email:
_follow: _follow:
title: "フォローされました" title: "フォローされました"
_receiveFollowRequest: _receiveFollowRequest:
title: "フォローリクエストを受け取りました" title: "フォローリクエストを受け取りました"
_plugin: _plugin:
install: "プラグインのインストール" install: "プラグインのインストール"
installWarn: "信頼できないプラグインはインストールしないでください。" installWarn: "信頼できないプラグインはインストールしないでください。"
manage: "プラグインの管理" manage: "プラグインの管理"
_preferencesBackups: _preferencesBackups:
list: "作成したバックアップ" list: "作成したバックアップ"
saveNew: "新規保存" saveNew: "新規保存"
@ -1000,33 +1004,29 @@ _preferencesBackups:
updatedAt: "更新日時: {date} {time}" updatedAt: "更新日時: {date} {time}"
cannotLoad: "読み込みできません" cannotLoad: "読み込みできません"
invalidFile: "ファイル形式が違います。" invalidFile: "ファイル形式が違います。"
_registry: _registry:
scope: "スコープ" scope: "スコープ"
key: "キー" key: "キー"
keys: "キー" keys: "キー"
domain: "ドメイン" domain: "ドメイン"
createKey: "キーを作成" createKey: "キーを作成"
_aboutMisskey: _aboutMisskey:
about: "Calckeyは、2022年から開発されているThatOneCalculator社製のMisskeyのforkです。" about: "Calckeyは、2022年に生まれたThatOneCalculatorによるMisskeyのforkです。"
contributors: "主なコントリビューター" contributors: "主なコントリビューター"
allContributors: "全てのコントリビューター" allContributors: "全てのコントリビューター"
source: "ソースコード" source: "ソースコード"
translation: "Misskeyを翻訳" translation: "Calckeyを翻訳"
donate: "Misskeyに寄付" donate: "Calckeyに寄付"
morePatrons: "他にも多くの方が支援してくれています。ありがとうございます🥰" morePatrons: "他にも多くの方が支援してくれています。ありがとうございます 🥰"
patrons: "支援者" patrons: "支援者"
_nsfw: _nsfw:
respect: "閲覧注意のメディアは隠す" respect: "閲覧注意のメディアは隠す"
ignore: "閲覧注意のメディアを隠さない" ignore: "閲覧注意のメディアを隠さない"
force: "常にメディアを隠す" force: "常にメディアを隠す"
_mfm: _mfm:
cheatSheet: "MFMチートシート" cheatSheet: "MFMチートシート"
intro: "MFMは、Misskey内の様々な場所で使用できる専用のマークアップ言語です。ここでは、MFMで使用可能な構文一覧が確認できます。" intro: "MFMは、MisskeyやCalckey、Akkomaなどの様々な場所で使用できるマークアップ言語です。ここでは、MFMで使用可能な構文一覧が確認できます。"
dummy: "MisskeyでFediverseの世界が広がります" dummy: "CalckeyでFediverseの世界が広がります"
mention: "メンション" mention: "メンション"
mentionDescription: "アットマーク + ユーザー名で、特定のユーザーを示すことができます。" mentionDescription: "アットマーク + ユーザー名で、特定のユーザーを示すことができます。"
hashtag: "ハッシュタグ" hashtag: "ハッシュタグ"
@ -1089,18 +1089,15 @@ _mfm:
rotateDescription: "指定した角度で回転させます。" rotateDescription: "指定した角度で回転させます。"
plain: "プレーン" plain: "プレーン"
plainDescription: "内側の構文を全て無効にします。" plainDescription: "内側の構文を全て無効にします。"
_instanceTicker: _instanceTicker:
none: "表示しない" none: "表示しない"
remote: "リモートユーザーに表示" remote: "リモートユーザーに表示"
always: "常に表示" always: "常に表示"
_serverDisconnectedBehavior: _serverDisconnectedBehavior:
reload: "自動でリロード" reload: "自動でリロード"
dialog: "ダイアログで警告" dialog: "ダイアログで警告"
quiet: "控えめに警告" quiet: "控えめに警告"
nothing: "何も起こらない" nothing: "何も起こらない"
_channel: _channel:
create: "チャンネルを作成" create: "チャンネルを作成"
edit: "チャンネルを編集" edit: "チャンネルを編集"
@ -1111,33 +1108,28 @@ _channel:
following: "フォロー中" following: "フォロー中"
usersCount: "{n}人が参加中" usersCount: "{n}人が参加中"
notesCount: "{n}投稿があります" notesCount: "{n}投稿があります"
_messaging: _messaging:
dms: "ディーエム" dms: "ディーエム"
groups: "グループ" groups: "グループ"
_menuDisplay: _menuDisplay:
sideFull: "横" sideFull: "横"
sideIcon: "横(アイコン)" sideIcon: "横(アイコン)"
top: "上部" top: "上部"
hide: "隠す" hide: "隠す"
_wordMute: _wordMute:
muteWords: "ミュートするワード" muteWords: "ミュートするワード"
muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。" muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。"
muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。" muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。"
softDescription: "指定した条件のノートをタイムラインから隠します。" softDescription: "指定した条件の投稿をタイムラインから隠します。"
hardDescription: "指定した条件のノートをタイムラインに追加しないようにします。追加されなかったノートは、条件を変更しても除外されたままになります。" hardDescription: "指定した条件の投稿をタイムラインに追加しないようにします。追加されなかった投稿は、条件を変更しても除外されたままになります。"
soft: "ソフト" soft: "ソフト"
hard: "ハード" hard: "ハード"
mutedNotes: "ミュートされたノート" mutedNotes: "ミュートされた投稿"
_instanceMute: _instanceMute:
instanceMuteDescription: "ミュートしたインスタンスのユーザーへの返信を含めて、設定したインスタンスの全てのートとRenoteをミュートします。" instanceMuteDescription: "ミュートしたインスタンスのユーザーへの返信を含めて、設定したインスタンスの全ての投稿とブーストをミュートします。"
instanceMuteDescription2: "改行で区切って設定します" instanceMuteDescription2: "改行で区切って設定します"
title: "設定したインスタンスのノートを隠します。" title: "設定したインスタンスの投稿を隠します。"
heading: "ミュートするインスタンス" heading: "ミュートするインスタンス"
_theme: _theme:
explore: "テーマを探す" explore: "テーマを探す"
install: "テーマのインストール" install: "テーマのインストール"
@ -1168,7 +1160,6 @@ _theme:
inputConstantName: "定数名を入力してください" inputConstantName: "定数名を入力してください"
importInfo: "ここにテーマコードを貼り付けて、エディターにインポートできます" importInfo: "ここにテーマコードを貼り付けて、エディターにインポートできます"
deleteConstantConfirm: "定数 {const} を削除しても良いですか?" deleteConstantConfirm: "定数 {const} を削除しても良いですか?"
keys: keys:
accent: "アクセント" accent: "アクセント"
bg: "背景" bg: "背景"
@ -1187,7 +1178,7 @@ _theme:
hashtag: "ハッシュタグ" hashtag: "ハッシュタグ"
mention: "メンション" mention: "メンション"
mentionMe: "あなた宛てメンション" mentionMe: "あなた宛てメンション"
renote: "Renote" renote: "ブースト"
modalBg: "モーダルの背景" modalBg: "モーダルの背景"
divider: "分割線" divider: "分割線"
scrollbarHandle: "スクロールバーの取っ手" scrollbarHandle: "スクロールバーの取っ手"
@ -1213,16 +1204,14 @@ _theme:
accentDarken: "アクセント (暗め)" accentDarken: "アクセント (暗め)"
accentLighten: "アクセント (明るめ)" accentLighten: "アクセント (明るめ)"
fgHighlighted: "強調された文字" fgHighlighted: "強調された文字"
_sfx: _sfx:
note: "ノート" note: "投稿"
noteMy: "ノート(自分)" noteMy: "投稿(自分)"
notification: "通知" notification: "通知"
chat: "チャット" chat: "チャット"
chatBg: "チャット(バックグラウンド)" chatBg: "チャット(バックグラウンド)"
antenna: "アンテナ受信" antenna: "アンテナ受信"
channel: "チャンネル通知" channel: "チャンネル通知"
_ago: _ago:
future: "未来" future: "未来"
justNow: "たった今" justNow: "たった今"
@ -1233,35 +1222,32 @@ _ago:
weeksAgo: "{n}週間前" weeksAgo: "{n}週間前"
monthsAgo: "{n}ヶ月前" monthsAgo: "{n}ヶ月前"
yearsAgo: "{n}年前" yearsAgo: "{n}年前"
_time: _time:
second: "秒" second: "秒"
minute: "分" minute: "分"
hour: "時間" hour: "時間"
day: "日" day: "日"
_tutorial: _tutorial:
title: "Calckeyの使い方" title: "Calckeyの使い方"
step1_1: "ようこそ!" step1_1: "ようこそ"
step1_2: "設定をしてみましょう" step1_2: "使い始める前に、いくつか設定を済ませましょう。すぐできますよ!"
step2_1: "メモを書いたり、誰かをフォローする前に、プロフィールの設定を済ませましょう。" step2_1: "最初に、あなたのプロフィールを作りましょう。"
step2_2: "あなたが誰なのか、いくつかの情報を提供することで、他の人があなたのメモを見たり、フォローしたりしたいのかがわかりやすくなります。" step2_2: "プロフィールを設定することで、他の人があなたの投稿を見たり、フォローしたりするときの助けになります。"
step3_1: "さあ、何人かの人をフォローする時間です" step3_1: "それでは、何人かフォローしてみましょう"
step3_2: "あなたのホームとソーシャルタイムラインは、あなたが誰をフォローしているかで決まります。 まずは、いくつかのアカウントをフォローしてみましょう。" step3_2: "あなたのホームとソーシャルタイムラインは、あなたが誰をフォローしているかで決まります。まずは、いくつかのアカウントをフォローしてみましょう。\nプロフィールの右上にある丸いボタンをクリックするとフォローできます。"
step4_1: "さあ、外に出てみましょう。" step4_1: "投稿してみましょう!"
step4_2: "最初の投稿は、{introduction}の投稿や、シンプルに「こんにちは、世界よ!」的な投稿をするのが好きな人もいます。" step4_2: "最初は{introduction}に投稿したり、シンプルに「こんにちは、アカウント作ってみました!」などの投稿をする人もいます。"
step5_1: "タイムライン、タイムラインだらけ!" step5_1: "タイムライン、タイムラインだらけ!"
step5_2: "あなたのインスタンスは{timelines}異なるタイムラインを有効にしています。" step5_2: "あなたのインスタンスでは{timelines}種類のタイムラインが有効になっています。"
step5_3: "ホーム{icon}のタイムラインは、あなたのフォロワーからの投稿を見ることができます。" step5_3: "ホーム{icon}タイムラインでは、あなたがフォローしているアカウントの投稿を見ることができます。"
step5_4: "ローカル{icon}タイムラインは、このインスタンスのみんなの投稿を見ることができる場所です。" step5_4: "ローカル{icon}タイムラインでは、このインスタンスのみんなの投稿を見ることができます。"
step5_5: "おすすめ{icon}のタイムラインは、管理人がおすすめするインスタンスの投稿を見ることができます。" step5_5: "おすすめ{icon}タイムラインでは、管理人がおすすめするインスタンスの投稿を見ることができます。"
step5_6: "ソーシャル{icon}のタイムラインは、あなたのフォロワーの友達の投稿を見ることができる場所です。" step5_6: "ソーシャル{icon}タイムラインでは、ホームタイムラインとローカルタイムラインの投稿を同時に見ることができます。"
step5_7: "グローバル{icon}タイムラインは、接続している他のすべてのインスタンスからの投稿を見ることができます。" step5_7: "グローバル{icon}タイムラインでは、接続している他のすべてのインスタンスからの投稿を見ることができます。"
step6_1: "それで、ここは何なの?" step6_1: "じゃあ、ここはどんな場所なの?"
step6_2: "まあ、あなたはCalckeyに参加しただけではありません。何千ものサーバーが相互接続されたネットワークで インスタンスと呼ばれる。" step6_2: "実は、あなたはただCalckeyに参加しただけではありません。ここは、何千もの相互接続されたサーバーが構成する Fediverse への入口です。各サーバーは「インスタンス」と呼ばれます。"
step6_3: "各サーバーは異なる方法で動作し、すべてのサーバーがCalckeyを実行するわけではありません。でも、このサーバーは動くんです" step6_3: "それぞれのサーバーでは必ずしもCalckeyが使われているわけではなく、異なる動作をするサーバーもあります。しかし、あなたは他のサーバーのアカウントもフォローしたり、返信・ブーストができます。一見難しそうですが大丈夫すぐ慣れます。"
step6_4: "さあ、探検して、楽しんでください!" step6_4: "これで完了です。お楽しみください!"
_2fa: _2fa:
alreadyRegistered: "既に設定は完了しています。" alreadyRegistered: "既に設定は完了しています。"
registerDevice: "デバイスを登録" registerDevice: "デバイスを登録"
@ -1272,7 +1258,6 @@ _2fa:
step3: "アプリに表示されているトークンを入力して完了です。" step3: "アプリに表示されているトークンを入力して完了です。"
step4: "これからログインするときも、同じようにトークンを入力します。" step4: "これからログインするときも、同じようにトークンを入力します。"
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーもしくは端末の指紋認証やPINを使用してログインするように設定できます。" securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーもしくは端末の指紋認証やPINを使用してログインするように設定できます。"
_permissions: _permissions:
"read:account": "アカウントの情報を見る" "read:account": "アカウントの情報を見る"
"write:account": "アカウントの情報を変更する" "write:account": "アカウントの情報を変更する"
@ -1288,7 +1273,7 @@ _permissions:
"write:messaging": "チャットを操作する" "write:messaging": "チャットを操作する"
"read:mutes": "ミュートを見る" "read:mutes": "ミュートを見る"
"write:mutes": "ミュートを操作する" "write:mutes": "ミュートを操作する"
"write:notes": "ノートを作成・削除する" "write:notes": "投稿を作成・削除する"
"read:notifications": "通知を見る" "read:notifications": "通知を見る"
"write:notifications": "通知を操作する" "write:notifications": "通知を操作する"
"read:reactions": "リアクションを見る" "read:reactions": "リアクションを見る"
@ -1306,22 +1291,21 @@ _permissions:
"write:gallery": "ギャラリーを操作する" "write:gallery": "ギャラリーを操作する"
"read:gallery-likes": "ギャラリーのいいねを見る" "read:gallery-likes": "ギャラリーのいいねを見る"
"write:gallery-likes": "ギャラリーのいいねを操作する" "write:gallery-likes": "ギャラリーのいいねを操作する"
_auth: _auth:
shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?" shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?"
shareAccessAsk: "アカウントへのアクセスを許可しますか?" shareAccessAsk: "アカウントへのアクセスを許可しますか?"
permissionAsk: "このアプリは次の権限を要求しています" permissionAsk: "このアプリケーションは次の権限を要求しています"
pleaseGoBack: "アプリケーションに戻ってやっていってください" pleaseGoBack: "アプリケーションに戻り続行してください"
callback: "アプリケーションに戻っています" callback: "アプリケーションに戻っています"
denied: "アクセスを拒否しました" denied: "アクセスを拒否しました"
copyAsk: "以下の認証コードをアプリケーションにコピーしてください"
_antennaSources: _antennaSources:
all: "全てのノート" all: "全ての投稿"
homeTimeline: "フォローしているユーザーのノート" homeTimeline: "フォローしているユーザーの投稿"
users: "指定した一人または複数のユーザーのノート" users: "指定した一人または複数のユーザーの投稿"
userList: "指定したリストのユーザーのノート" userList: "指定したリストのユーザーの投稿"
userGroup: "指定したグループのユーザーのノート" userGroup: "指定したグループのユーザーの投稿"
instances: "指定したインスタンスの全ユーザーの投稿"
_weekday: _weekday:
sunday: "日曜日" sunday: "日曜日"
monday: "月曜日" monday: "月曜日"
@ -1330,7 +1314,6 @@ _weekday:
thursday: "木曜日" thursday: "木曜日"
friday: "金曜日" friday: "金曜日"
saturday: "土曜日" saturday: "土曜日"
_widgets: _widgets:
memo: "付箋" memo: "付箋"
notifications: "通知" notifications: "通知"
@ -1353,14 +1336,14 @@ _widgets:
jobQueue: "ジョブキュー" jobQueue: "ジョブキュー"
serverMetric: "サーバーメトリクス" serverMetric: "サーバーメトリクス"
aiscript: "AiScriptコンソール" aiscript: "AiScriptコンソール"
aichan: "藍" userList: "ユーザーリスト"
_userList:
chooseList: "リストを選択"
_cw: _cw:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
chars: "{count}文字" chars: "{count}文字"
files: "{count}ファイル" files: "{count}ファイル"
_poll: _poll:
noOnlyOneChoice: "選択肢は最低2つ必要です" noOnlyOneChoice: "選択肢は最低2つ必要です"
choiceN: "選択肢{n}" choiceN: "選択肢{n}"
@ -1383,7 +1366,6 @@ _poll:
remainingHours: "終了まであと{h}時間{m}分" remainingHours: "終了まであと{h}時間{m}分"
remainingMinutes: "終了まであと{m}分{s}秒" remainingMinutes: "終了まであと{m}分{s}秒"
remainingSeconds: "終了まであと{s}秒" remainingSeconds: "終了まであと{s}秒"
_visibility: _visibility:
public: "パブリック" public: "パブリック"
publicDescription: "全てのユーザーに公開" publicDescription: "全てのユーザーに公開"
@ -1395,10 +1377,9 @@ _visibility:
specifiedDescription: "指定したユーザーのみに公開" specifiedDescription: "指定したユーザーのみに公開"
localOnly: "ローカルのみ" localOnly: "ローカルのみ"
localOnlyDescription: "リモートユーザーには非公開" localOnlyDescription: "リモートユーザーには非公開"
_postForm: _postForm:
replyPlaceholder: "このノートに返信..." replyPlaceholder: "この投稿に返信..."
quotePlaceholder: "このノートを引用..." quotePlaceholder: "この投稿を引用..."
channelPlaceholder: "チャンネルに投稿..." channelPlaceholder: "チャンネルに投稿..."
_placeholders: _placeholders:
a: "いまどうしてる?" a: "いまどうしてる?"
@ -1407,7 +1388,6 @@ _postForm:
d: "言いたいことは?" d: "言いたいことは?"
e: "ここに書いてください" e: "ここに書いてください"
f: "あなたが書くのを待っています..." f: "あなたが書くのを待っています..."
_profile: _profile:
name: "名前" name: "名前"
username: "ユーザー名" username: "ユーザー名"
@ -1420,51 +1400,47 @@ _profile:
metadataContent: "内容" metadataContent: "内容"
changeAvatar: "アバター画像を変更" changeAvatar: "アバター画像を変更"
changeBanner: "バナー画像を変更" changeBanner: "バナー画像を変更"
locationDescription: "正しく入力すると、あなたの現地時間が他のユーザーに表示されます。"
_exportOrImport: _exportOrImport:
allNotes: "全てのノート" allNotes: "全ての投稿"
followingList: "フォロー" followingList: "フォロー"
muteList: "ミュート" muteList: "ミュート"
blockingList: "ブロック" blockingList: "ブロック"
userLists: "リスト" userLists: "リスト"
excludeMutingUsers: "ミュートしているユーザーを除外" excludeMutingUsers: "ミュートしているユーザーを除外"
excludeInactiveUsers: "使われていないアカウントを除外" excludeInactiveUsers: "使われていないアカウントを除外"
_charts: _charts:
federation: "連合" federation: "連合"
apRequest: "リクエスト" apRequest: "リクエスト"
usersIncDec: "ユーザーの増減" usersIncDec: "ユーザーの増減"
usersTotal: "ユーザーの合計" usersTotal: "ユーザーの合計"
activeUsers: "アクティブユーザー数" activeUsers: "アクティブユーザー数"
notesIncDec: "ノートの増減" notesIncDec: "投稿の増減"
localNotesIncDec: "ローカルのノートの増減" localNotesIncDec: "ローカルの投稿の増減"
remoteNotesIncDec: "リモートのノートの増減" remoteNotesIncDec: "リモートの投稿の増減"
notesTotal: "ノートの合計" notesTotal: "投稿の合計"
filesIncDec: "ファイルの増減" filesIncDec: "ファイルの増減"
filesTotal: "ファイルの合計" filesTotal: "ファイルの合計"
storageUsageIncDec: "ストレージ使用量の増減" storageUsageIncDec: "ストレージ使用量の増減"
storageUsageTotal: "ストレージ使用量の合計" storageUsageTotal: "ストレージ使用量の合計"
_instanceCharts: _instanceCharts:
requests: "リクエスト" requests: "リクエスト"
users: "ユーザーの増減" users: "ユーザーの増減"
usersTotal: "ユーザーの累積" usersTotal: "ユーザーの累積"
notes: "ノートの増減" notes: "投稿の増減"
notesTotal: "ノートの累積" notesTotal: "投稿の累積"
ff: "フォロー/フォロワーの増減" ff: "フォロー/フォロワーの増減"
ffTotal: "フォロー/フォロワーの累積" ffTotal: "フォロー/フォロワーの累積"
cacheSize: "キャッシュサイズの増減" cacheSize: "キャッシュサイズの増減"
cacheSizeTotal: "キャッシュサイズの累積" cacheSizeTotal: "キャッシュサイズの累積"
files: "ファイル数の増減" files: "ファイル数の増減"
filesTotal: "ファイル数の累積" filesTotal: "ファイル数の累積"
_timelines: _timelines:
home: "ホーム" home: "ホーム"
local: "ローカル" local: "ローカル"
recommended: "一押し" recommended: "おすすめ"
social: "ソーシャル" social: "ソーシャル"
global: "グローバル" global: "グローバル"
_pages: _pages:
newPage: "ページの作成" newPage: "ページの作成"
editPage: "ページの編集" editPage: "ページの編集"
@ -1511,59 +1487,49 @@ _pages:
section: "セクション" section: "セクション"
image: "画像" image: "画像"
button: "ボタン" button: "ボタン"
if: "もし" if: "もし"
_if: _if:
variable: "変数" variable: "変数"
post: "投稿フォーム" post: "投稿フォーム"
_post: _post:
text: "内容" text: "内容"
attachCanvasImage: "キャンバスの画像を添付する" attachCanvasImage: "キャンバスの画像を添付する"
canvasId: "キャンバスID" canvasId: "キャンバスID"
textInput: "テキスト入力" textInput: "テキスト入力"
_textInput: _textInput:
name: "変数名" name: "変数名"
text: "タイトル" text: "タイトル"
default: "デフォルト値" default: "デフォルト値"
textareaInput: "複数行テキスト入力" textareaInput: "複数行テキスト入力"
_textareaInput: _textareaInput:
name: "変数名" name: "変数名"
text: "タイトル" text: "タイトル"
default: "デフォルト値" default: "デフォルト値"
numberInput: "数値入力" numberInput: "数値入力"
_numberInput: _numberInput:
name: "変数名" name: "変数名"
text: "タイトル" text: "タイトル"
default: "デフォルト値" default: "デフォルト値"
canvas: "キャンバス" canvas: "キャンバス"
_canvas: _canvas:
id: "キャンバスID" id: "キャンバスID"
width: "幅" width: "幅"
height: "高さ" height: "高さ"
note: "投稿の埋め込み"
note: "ノート埋め込み"
_note: _note:
id: "ノートID" id: "投稿のID"
idDescription: "ノートURLをペーストして設定することもできます。" idDescription: "投稿のURLをペーストして設定することもできます。"
detailed: "詳細な表示" detailed: "詳細な表示"
switch: "スイッチ" switch: "スイッチ"
_switch: _switch:
name: "変数名" name: "変数名"
text: "タイトル" text: "タイトル"
default: "デフォルト値" default: "デフォルト値"
counter: "カウンター" counter: "カウンター"
_counter: _counter:
name: "変数名" name: "変数名"
text: "タイトル" text: "タイトル"
inc: "増加値" inc: "増加値"
_button: _button:
text: "タイトル" text: "タイトル"
colored: "色付き" colored: "色付き"
@ -1582,14 +1548,12 @@ _pages:
callAiScript: "AiScript呼び出し" callAiScript: "AiScript呼び出し"
_callAiScript: _callAiScript:
functionName: "関数名" functionName: "関数名"
radioButton: "選択肢" radioButton: "選択肢"
_radioButton: _radioButton:
name: "変数名" name: "変数名"
title: "タイトル" title: "タイトル"
values: "改行で区切った選択肢" values: "改行で区切った選択肢"
default: "デフォルト値" default: "デフォルト値"
script: script:
categories: categories:
flow: "制御" flow: "制御"
@ -1766,18 +1730,16 @@ _pages:
enviromentVariables: "環境変数" enviromentVariables: "環境変数"
pageVariables: "ページ要素" pageVariables: "ページ要素"
argVariables: "入力スロット" argVariables: "入力スロット"
_relayStatus: _relayStatus:
requesting: "承認待ち" requesting: "承認待ち"
accepted: "承認済み" accepted: "承認済み"
rejected: "拒否済み" rejected: "拒否済み"
_notification: _notification:
fileUploaded: "ファイルがアップロードされました" fileUploaded: "ファイルがアップロードされました"
youGotMention: "{name}からのメンション" youGotMention: "{name}からのメンション"
youGotReply: "{name}からのリプライ" youGotReply: "{name}からのリプライ"
youGotQuote: "{name}による引用" youGotQuote: "{name}による引用"
youRenoted: "{name}がRenoteしました" youRenoted: "{name}がブーストしました"
youGotPoll: "{name}が投票しました" youGotPoll: "{name}が投票しました"
youGotMessagingMessageFromUser: "{name}からのチャットがあります" youGotMessagingMessageFromUser: "{name}からのチャットがあります"
youGotMessagingMessageFromGroup: "{name}のチャットがあります" youGotMessagingMessageFromGroup: "{name}のチャットがあります"
@ -1787,13 +1749,12 @@ _notification:
youWereInvitedToGroup: "{userName}があなたをグループに招待しました" youWereInvitedToGroup: "{userName}があなたをグループに招待しました"
pollEnded: "アンケートの結果が出ました" pollEnded: "アンケートの結果が出ました"
emptyPushNotificationMessage: "プッシュ通知の更新をしました" emptyPushNotificationMessage: "プッシュ通知の更新をしました"
_types: _types:
all: "すべて" all: "すべて"
follow: "フォロー" follow: "フォロー"
mention: "メンション" mention: "メンション"
reply: "リプライ" reply: "リプライ"
renote: "Renote" renote: "ブースト"
quote: "引用" quote: "引用"
reaction: "リアクション" reaction: "リアクション"
pollVote: "アンケートに投票された" pollVote: "アンケートに投票された"
@ -1802,12 +1763,10 @@ _notification:
followRequestAccepted: "フォローが受理された" followRequestAccepted: "フォローが受理された"
groupInvited: "グループに招待された" groupInvited: "グループに招待された"
app: "連携アプリからの通知" app: "連携アプリからの通知"
_actions: _actions:
followBack: "フォローバック" followBack: "フォローバック"
reply: "返信" reply: "返信"
renote: "Renote" renote: "ブースト"
_deck: _deck:
alwaysShowMainColumn: "常にメインカラムを表示" alwaysShowMainColumn: "常にメインカラムを表示"
columnAlign: "カラムの寄せ" columnAlign: "カラムの寄せ"
@ -1825,7 +1784,6 @@ _deck:
introduction: "カラムを組み合わせて自分だけのインターフェイスを作りましょう!" introduction: "カラムを組み合わせて自分だけのインターフェイスを作りましょう!"
introduction2: "画面の右にある + を押して、いつでもカラムを追加できます。" introduction2: "画面の右にある + を押して、いつでもカラムを追加できます。"
widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選択してウィジェットを追加してください" widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選択してウィジェットを追加してください"
_columns: _columns:
main: "メイン" main: "メイン"
widgets: "ウィジェット" widgets: "ウィジェット"
@ -1835,3 +1793,20 @@ _deck:
list: "リスト" list: "リスト"
mentions: "あなた宛て" mentions: "あなた宛て"
direct: "ダイレクト" 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"

View file

@ -41,7 +41,6 @@
"@tensorflow/tfjs": "^3.21.0", "@tensorflow/tfjs": "^3.21.0",
"calckey-js": "^0.0.22", "calckey-js": "^0.0.22",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"phosphor-icons": "^1.4.2",
"seedrandom": "^3.0.5" "seedrandom": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {

View file

@ -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;
});

View file

@ -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)`);
}
}

View file

@ -8,6 +8,7 @@
"start:test": "NODE_ENV=test pnpm node ./built/index.js", "start:test": "NODE_ENV=test pnpm node ./built/index.js",
"migrate": "typeorm migration:run -d ormconfig.js", "migrate": "typeorm migration:run -d ormconfig.js",
"revertmigration": "typeorm migration:revert -d ormconfig.js", "revertmigration": "typeorm migration:revert -d ormconfig.js",
"check:connect": "node ./check_connect.js",
"build": "pnpm swc src -d built -D", "build": "pnpm swc src -d built -D",
"watch": "pnpm swc src -d built -D -w", "watch": "pnpm swc src -d built -D -w",
"lint": "pnpm rome check \"src/**/*.ts\"", "lint": "pnpm rome check \"src/**/*.ts\"",
@ -71,6 +72,7 @@
"jsonld": "6.0.0", "jsonld": "6.0.0",
"jsrsasign": "10.6.1", "jsrsasign": "10.6.1",
"koa": "2.13.4", "koa": "2.13.4",
"koa-remove-trailing-slashes": "2.0.3",
"koa-bodyparser": "4.3.0", "koa-bodyparser": "4.3.0",
"koa-favicon": "2.1.0", "koa-favicon": "2.1.0",
"koa-json-body": "5.3.0", "koa-json-body": "5.3.0",
@ -97,6 +99,7 @@
"punycode": "2.1.1", "punycode": "2.1.1",
"pureimage": "0.3.15", "pureimage": "0.3.15",
"qrcode": "1.5.1", "qrcode": "1.5.1",
"qs": "6.9.7",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
"re2": "1.18.0", "re2": "1.18.0",
@ -157,6 +160,7 @@
"@types/pug": "2.0.6", "@types/pug": "2.0.6",
"@types/punycode": "2.1.0", "@types/punycode": "2.1.0",
"@types/qrcode": "1.5.0", "@types/qrcode": "1.5.0",
"@types/qs": "6.9.7",
"@types/random-seed": "0.3.3", "@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.4", "@types/ratelimiter": "3.4.4",
"@types/redis": "4.0.11", "@types/redis": "4.0.11",

View file

@ -0,0 +1 @@
declare module 'koa-remove-trailing-slashes';

View file

@ -74,6 +74,7 @@ export type Source = {
maxUserSignups?: number; maxUserSignups?: number;
isManagedHosting?: boolean; isManagedHosting?: boolean;
maxNoteLength?: number; maxNoteLength?: number;
maxCaptionLength?: number;
deepl: { deepl: {
managed?: boolean; managed?: boolean;
authKey?: string; authKey?: string;

View file

@ -1,7 +1,12 @@
import config from "@/config/index.js"; import config from "@/config/index.js";
import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js";
export const MAX_NOTE_TEXT_LENGTH = export const MAX_NOTE_TEXT_LENGTH =
config.maxNoteLength != null ? config.maxNoteLength : 3000; // <- should we increase this? 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 SECOND = 1000;
export const SEC = 1000; // why do we need this duplicate here? export const SEC = 1000; // why do we need this duplicate here?

View file

@ -10,4 +10,4 @@ export const DB_MAX_NOTE_TEXT_LENGTH = 8192;
* Maximum image description length that can be stored in DB. * Maximum image description length that can be stored in DB.
* Surrogate pairs count as one * Surrogate pairs count as one
*/ */
export const DB_MAX_IMAGE_COMMENT_LENGTH = 512; export const DB_MAX_IMAGE_COMMENT_LENGTH = 8192;

View file

@ -9,6 +9,7 @@ import {
import { id } from "../id.js"; import { id } from "../id.js";
import { User } from "./user.js"; import { User } from "./user.js";
import { DriveFolder } from "./drive-folder.js"; import { DriveFolder } from "./drive-folder.js";
import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js";
@Entity() @Entity()
@Index(['userId', 'folderId', 'id']) @Index(['userId', 'folderId', 'id'])
@ -69,7 +70,8 @@ export class DriveFile {
public size: number; public size: number;
@Column('varchar', { @Column('varchar', {
length: 512, nullable: true, length: DB_MAX_IMAGE_COMMENT_LENGTH,
nullable: true,
comment: 'The comment of the DriveFile.', comment: 'The comment of the DriveFile.',
}) })
public comment: string | null; public comment: string | null;

View file

@ -20,6 +20,7 @@ export default async (job: Bull.Job<WebhookDeliverJobData>) => {
"X-Calckey-Host": config.host, "X-Calckey-Host": config.host,
"X-Calckey-Hook-Id": job.data.webhookId, "X-Calckey-Hook-Id": job.data.webhookId,
"X-Calckey-Hook-Secret": job.data.secret, "X-Calckey-Hook-Secret": job.data.secret,
'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
hookId: job.data.webhookId, hookId: job.data.webhookId,

View file

@ -413,8 +413,8 @@ export async function updatePerson(
isBot: getApType(object) === "Service", isBot: getApType(object) === "Service",
isCat: (person as any).isCat === true, isCat: (person as any).isCat === true,
isLocked: !!person.manuallyApprovesFollowers, isLocked: !!person.manuallyApprovesFollowers,
movedToUri: person.movedTo, movedToUri: person.movedTo || null,
alsoKnownAs: person.alsoKnownAs, alsoKnownAs: person.alsoKnownAs || null,
isExplorable: !!person.discoverable, isExplorable: !!person.discoverable,
} as Partial<User>; } as Partial<User>;

View file

@ -1,6 +1,6 @@
import config from "@/config/index.js"; import config from "@/config/index.js";
import { fetchMeta } from "@/misc/fetch-meta.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"; import define from "../../define.js";
export const meta = { export const meta = {
@ -86,6 +86,11 @@ export const meta = {
optional: false, optional: false,
nullable: false, nullable: false,
}, },
maxCaptionTextLength: {
type: "number",
optional: false,
nullable: false,
},
emojis: { emojis: {
type: "array", type: "array",
optional: false, optional: false,
@ -499,6 +504,7 @@ export default define(meta, paramDef, async (ps, me) => {
backgroundImageUrl: instance.backgroundImageUrl, backgroundImageUrl: instance.backgroundImageUrl,
logoImageUrl: instance.logoImageUrl, logoImageUrl: instance.logoImageUrl,
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH,
defaultLightTheme: instance.defaultLightTheme, defaultLightTheme: instance.defaultLightTheme,
defaultDarkTheme: instance.defaultDarkTheme, defaultDarkTheme: instance.defaultDarkTheme,
enableEmail: instance.enableEmail, enableEmail: instance.enableEmail,

View file

@ -2,8 +2,7 @@ import { IsNull, MoreThan } from "typeorm";
import config from "@/config/index.js"; import config from "@/config/index.js";
import { fetchMeta } from "@/misc/fetch-meta.js"; import { fetchMeta } from "@/misc/fetch-meta.js";
import { Ads, Emojis, Users } from "@/models/index.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, MAX_CAPTION_TEXT_LENGTH } from "@/const.js";
import { MAX_NOTE_TEXT_LENGTH } from "@/const.js";
import define from "../define.js"; import define from "../define.js";
export const meta = { export const meta = {
@ -178,6 +177,11 @@ export const meta = {
optional: false, optional: false,
nullable: false, nullable: false,
}, },
maxCaptionTextLength: {
type: "number",
optional: false,
nullable: false,
},
emojis: { emojis: {
type: "array", type: "array",
optional: false, optional: false,
@ -456,6 +460,7 @@ export default define(meta, paramDef, async (ps, me) => {
backgroundImageUrl: instance.backgroundImageUrl, backgroundImageUrl: instance.backgroundImageUrl,
logoImageUrl: instance.logoImageUrl, logoImageUrl: instance.logoImageUrl,
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH,
emojis: instance.privateMode && !me ? [] : await Emojis.packMany(emojis), emojis: instance.privateMode && !me ? [] : await Emojis.packMany(emojis),
defaultLightTheme: instance.defaultLightTheme, defaultLightTheme: instance.defaultLightTheme,
defaultDarkTheme: instance.defaultDarkTheme, defaultDarkTheme: instance.defaultDarkTheme,

View file

@ -22,6 +22,35 @@ import github from "./service/github.js";
import twitter from "./service/twitter.js"; import twitter from "./service/twitter.js";
import { koaBody } from "koa-body"; 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 // Init app
const app = new Koa(); const app = new Koa();
@ -82,7 +111,7 @@ mastoFileRouter.post("/v1/media", upload.single("file"), async (ctx) => {
ctx.status = 401; ctx.status = 401;
return; return;
} }
const data = await client.uploadMedia(multipartData.buffer); const data = await client.uploadMedia(multipartData);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
@ -101,7 +130,7 @@ mastoFileRouter.post("/v2/media", upload.single("file"), async (ctx) => {
ctx.status = 401; ctx.status = 401;
return; return;
} }
const data = await client.uploadMedia(multipartData.buffer); const data = await client.uploadMedia(multipartData);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);

View file

@ -4,8 +4,9 @@ import Router from "@koa/router";
import { FindOptionsWhere, IsNull } from "typeorm"; import { FindOptionsWhere, IsNull } from "typeorm";
import { getClient } from "../ApiMastodonCompatibleService.js"; import { getClient } from "../ApiMastodonCompatibleService.js";
import { argsToBools, limitToInt } from "./timeline.js"; import { argsToBools, limitToInt } from "./timeline.js";
import { convertId, IdType } from "../../index.js";
const relationshopModel = { const relationshipModel = {
id: "", id: "",
following: false, following: false,
followed_by: false, followed_by: false,
@ -29,7 +30,8 @@ export function apiAccountMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.verifyAccountCredentials(); 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.url = `${BASE_URL}/@${acct.url}`;
acct.note = ""; acct.note = "";
acct.avatar_static = acct.avatar; acct.avatar_static = acct.avatar;
@ -42,6 +44,7 @@ export function apiAccountMastodon(router: Router): void {
sensitive: false, sensitive: false,
language: "", language: "",
}; };
console.log(acct);
ctx.body = acct; ctx.body = acct;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
@ -58,7 +61,9 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.updateCredentials( const data = await client.updateCredentials(
(ctx.request as any).body as any, (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) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -71,26 +76,10 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
let userArray = ctx.query.acct?.toString().split("@"); const data = await client.search((ctx.request.query as any).acct, 'accounts');
let userid; let resp = data.data.accounts[0];
if (userArray === undefined) { resp.id = convertId(resp.id, IdType.MastodonId);
ctx.status = 401; ctx.body = resp;
ctx.body = { error: "no user specified" };
return;
}
if (userArray.length === 1) {
const q: FindOptionsWhere<User> = {
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;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -105,8 +94,11 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getAccount(ctx.params.id); const calcId = convertId(ctx.params.id, IdType.CalckeyId);
ctx.body = data.data; const data = await client.getAccount(calcId);
let resp = data.data;
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -123,10 +115,20 @@ export function apiAccountMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getAccountStatuses( const data = await client.getAccountStatuses(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
argsToBools(limitToInt(ctx.query as any)), 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) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -143,10 +145,14 @@ export function apiAccountMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getAccountFollowers( const data = await client.getAccountFollowers(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
ctx.query as any, 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) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -163,10 +169,14 @@ export function apiAccountMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getAccountFollowing( const data = await client.getAccountFollowing(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
ctx.query as any, 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) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -199,10 +209,11 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.followAccount(ctx.params.id); const data = await client.followAccount(convertId(ctx.params.id, IdType.CalckeyId));
const acct = data.data; let acct = data.data;
acct.following = true; acct.following = true;
ctx.body = data.data; acct.id = convertId(acct.id, IdType.MastodonId);
ctx.body = acct;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -218,10 +229,11 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.unfollowAccount(ctx.params.id); const data = await client.unfollowAccount(convertId(ctx.params.id, IdType.CalckeyId));
const acct = data.data; let acct = data.data;
acct.id = convertId(acct.id, IdType.MastodonId);
acct.following = false; acct.following = false;
ctx.body = data.data; ctx.body = acct;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -237,8 +249,10 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.blockAccount(ctx.params.id); const data = await client.blockAccount(convertId(ctx.params.id, IdType.CalckeyId));
ctx.body = data.data; let resp = data.data;
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -254,8 +268,10 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.unblockAccount(ctx.params.id); const data = await client.unblockAccount(convertId(ctx.params.id, IdType.MastodonId));
ctx.body = data.data; let resp = data.data;
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -272,10 +288,12 @@ export function apiAccountMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.muteAccount( const data = await client.muteAccount(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
(ctx.request as any).body as any, (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) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -291,8 +309,10 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.unmuteAccount(ctx.params.id); const data = await client.unmuteAccount(convertId(ctx.params.id, IdType.CalckeyId));
ctx.body = data.data; let resp = data.data;
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -308,16 +328,23 @@ export function apiAccountMastodon(router: Router): void {
let users; let users;
try { try {
// TODO: this should be body // TODO: this should be body
const idsRaw = ctx.request.query ? ctx.request.query["id[]"] : null; let ids = ctx.request.query ? ctx.request.query["id[]"] : null;
const ids = typeof idsRaw === "string" ? [idsRaw] : idsRaw; if (typeof ids === "string") {
ids = [ids];
}
users = ids; users = ids;
relationshopModel.id = idsRaw?.toString() || "1"; relationshipModel.id = ids?.toString() || "1";
if (!idsRaw) { if (!ids) {
ctx.body = [relationshopModel]; ctx.body = [relationshipModel];
return; return;
} }
const data = await client.getRelationships(ids); 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) { } catch (e: any) {
console.error(e); console.error(e);
let data = e.response.data; let data = e.response.data;
@ -333,7 +360,17 @@ export function apiAccountMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = (await client.getBookmarks(ctx.query as any)) as any; 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) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -347,7 +384,17 @@ export function apiAccountMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getFavourites(ctx.query as any); 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) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -361,7 +408,11 @@ export function apiAccountMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getMutes(ctx.query as any); 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) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -375,7 +426,11 @@ export function apiAccountMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getBlocks(ctx.query as any); 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) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -383,7 +438,7 @@ export function apiAccountMastodon(router: Router): void {
ctx.body = e.response.data; 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 BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
@ -391,7 +446,11 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.getFollowRequests( const data = await client.getFollowRequests(
((ctx.query as any) || { limit: 20 }).limit, ((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) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -400,14 +459,16 @@ export function apiAccountMastodon(router: Router): void {
} }
}); });
router.post<{ Params: { id: string } }>( router.post<{ Params: { id: string } }>(
"/v1/follow_ctxs/:id/authorize", "/v1/follow_requests/:id/authorize",
async (ctx) => { async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.acceptFollowRequest(ctx.params.id); const data = await client.acceptFollowRequest(convertId(ctx.params.id, IdType.CalckeyId));
ctx.body = data.data; let resp = data.data;
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -417,14 +478,16 @@ export function apiAccountMastodon(router: Router): void {
}, },
); );
router.post<{ Params: { id: string } }>( router.post<{ Params: { id: string } }>(
"/v1/follow_ctxs/:id/reject", "/v1/follow_requests/:id/reject",
async (ctx) => { async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.rejectFollowRequest(ctx.params.id); const data = await client.rejectFollowRequest(convertId(ctx.params.id, IdType.CalckeyId));
ctx.body = data.data; let resp = data.data;
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);

View file

@ -44,12 +44,10 @@ const writeScope = [
export function apiAuthMastodon(router: Router): void { export function apiAuthMastodon(router: Router): void {
router.post("/v1/apps", async (ctx) => { router.post("/v1/apps", async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`; const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization; const client = getClient(BASE_URL, '');
const client = getClient(BASE_URL, accessTokens); const body: any = ctx.request.body || ctx.request.query;
const body: any = ctx.request.body;
try { try {
let scope = body.scopes; let scope = body.scopes;
console.log(body);
if (typeof scope === "string") scope = scope.split(" "); if (typeof scope === "string") scope = scope.split(" ");
const pushScope = new Set<string>(); const pushScope = new Set<string>();
for (const s of scope) { for (const s of scope) {
@ -64,14 +62,16 @@ export function apiAuthMastodon(router: Router): void {
redirect_uris: red, redirect_uris: red,
website: body.website, website: body.website,
}); });
ctx.body = { const returns = {
id: appData.id, id: Math.floor(Math.random() * 100).toString(),
name: appData.name, name: appData.name,
website: appData.website, website: body.website,
redirect_uri: red, redirect_uri: red,
client_id: Buffer.from(appData.url || "").toString("base64"), 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) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;

View file

@ -10,18 +10,18 @@ export async function getInstance(response: Entity.Instance) {
const totalStatuses = Notes.count({ where: { userHost: IsNull() } }); const totalStatuses = Notes.count({ where: { userHost: IsNull() } });
return { return {
uri: response.uri, uri: response.uri,
title: response.title || "", title: response.title || "Calckey",
short_description: response.description || "", short_description: response.description.substring(0, 50) || "See real server website",
description: response.description || "", 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 || "", 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, urls: response.urls,
stats: { stats: {
user_count: (await totalUsers), user_count: (await totalUsers),
status_count: (await totalStatuses), status_count: (await totalStatuses),
domain_count: response.stats.domain_count domain_count: response.stats.domain_count
}, },
thumbnail: response.thumbnail || "", thumbnail: response.thumbnail || 'https://http.cat/404',
languages: meta.langs, languages: meta.langs,
registrations: !meta.disableRegistration || response.registrations, registrations: !meta.disableRegistration || response.registrations,
approval_required: !response.registrations, approval_required: !response.registrations,

View file

@ -1,6 +1,9 @@
import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import megalodon, { MegalodonInterface } from "@calckey/megalodon";
import Router from "@koa/router"; import Router from "@koa/router";
import { getClient } from "../ApiMastodonCompatibleService.js"; 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 { export function apiSearchMastodon(router: Router): void {
router.get("/v1/search", async (ctx) => { router.get("/v1/search", async (ctx) => {
@ -9,7 +12,7 @@ export function apiSearchMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const query: any = ctx.query; const query: any = limitToInt(ctx.query);
const type = query.type || ""; const type = query.type || "";
const data = await client.search(query.q, type, query); const data = await client.search(query.q, type, query);
ctx.body = data.data; ctx.body = data.data;
@ -19,4 +22,110 @@ export function apiSearchMastodon(router: Router): void {
ctx.body = e.response.data; 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 [];
}
} }

View file

@ -2,6 +2,12 @@ import Router from "@koa/router";
import { getClient } from "../ApiMastodonCompatibleService.js"; import { getClient } from "../ApiMastodonCompatibleService.js";
import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js"; import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js";
import axios from "axios"; 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 { export function apiStatusMastodon(router: Router): void {
router.post("/v1/statuses", async (ctx) => { router.post("/v1/statuses", async (ctx) => {
@ -9,9 +15,12 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { 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 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 isDefaultEmoji = emojiRegexAtStartToEnd.test(removed);
const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed); const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed);
if ((body.in_reply_to_id && isDefaultEmoji) || isCustomEmoji) { if ((body.in_reply_to_id && isDefaultEmoji) || isCustomEmoji) {
@ -36,6 +45,8 @@ export function apiStatusMastodon(router: Router): void {
} }
if (!body.media_ids) body.media_ids = undefined; 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); const data = await client.postStatus(text, body);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
@ -70,7 +81,7 @@ export function apiStatusMastodon(router: Router): void {
const data = await client.deleteStatus(ctx.params.id); const data = await client.deleteStatus(ctx.params.id);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e.response.data, request.params.id);
ctx.status = 401; ctx.status = 401;
ctx.body = e.response.data; ctx.body = e.response.data;
} }
@ -430,6 +441,6 @@ export function statusModel(
pinned: false, pinned: false,
emoji_reactions: [], emoji_reactions: [],
bookmarked: false, bookmarked: false,
quote: false, quote: null,
}; };
} }

View file

@ -9,7 +9,9 @@ export function limitToInt(q: ParsedUrlQuery) {
let object: any = q; let object: any = q;
if (q.limit) if (q.limit)
if (typeof q.limit === "string") object.limit = parseInt(q.limit, 10); 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) { export function argsToBools(q: ParsedUrlQuery) {
@ -26,12 +28,29 @@ export function argsToBools(q: ParsedUrlQuery) {
export function toTextWithReaction(status: Entity.Status[], host: string) { export function toTextWithReaction(status: Entity.Status[], host: string) {
return status.map((t) => { return status.map((t) => {
if (!t) return statusModel(null, null, [], "no content"); if (!t) return statusModel(null, null, [], "no content");
t.quote = null as any;
if (!t.emoji_reactions) return t; if (!t.emoji_reactions) return t;
if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0]; if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0];
const reactions = t.emoji_reactions.map( const reactions = t.emoji_reactions.map((r) => {
(r) => `${r.name.replace("@.", "")} (${r.count}${r.me ? "* " : ""})`, const emojiNotation = r.url ? `:${r.name.replace('@.', '')}:` : r.name
); return `${emojiNotation} (${r.count}${r.me ? `* ` : ''})`
//t.emojis = getEmoji(t.content, host) });
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 = `<p>${autoLinker(t.content, host)}</p><p>${reactions.join( t.content = `<p>${autoLinker(t.content, host)}</p><p>${reactions.join(
", ", ", ",
)}</p>`; )}</p>`;
@ -103,7 +122,7 @@ export function apiTimelineMastodon(router: Router): void {
} }
}, },
); );
router.get<{ Params: { hashtag: string } }>( router.get(
"/v1/timelines/home", "/v1/timelines/home",
async (ctx, reply) => { async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;

View file

@ -414,12 +414,13 @@ export default class Connection {
const client = getClient(this.host, this.accessToken); const client = getClient(this.host, this.accessToken);
client.getStatus(payload.id).then((data) => { client.getStatus(payload.id).then((data) => {
const newPost = toTextWithReaction([data.data], this.host); const newPost = toTextWithReaction([data.data], this.host);
const targetPost = newPost[0]
for (const stream of this.currentSubscribe) { for (const stream of this.currentSubscribe) {
this.wsConnection.send( this.wsConnection.send(
JSON.stringify({ JSON.stringify({
stream, stream,
event: "status.update", event: "status.update",
payload: JSON.stringify(newPost[0]), payload: JSON.stringify(targetPost),
}), }),
); );
} }

View file

@ -30,6 +30,8 @@ import proxyServer from "./proxy/index.js";
import webServer from "./web/index.js"; import webServer from "./web/index.js";
import { initializeStreamingServer } from "./api/streaming.js"; import { initializeStreamingServer } from "./api/streaming.js";
import { koaBody } from "koa-body"; 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); export const serverLogger = new Logger("server", "gray", false);
@ -37,6 +39,8 @@ export const serverLogger = new Logger("server", "gray", false);
const app = new Koa(); const app = new Koa();
app.proxy = true; app.proxy = true;
app.use(removeTrailingSlash());
if (!["production", "test"].includes(process.env.NODE_ENV || "")) { if (!["production", "test"].includes(process.env.NODE_ENV || "")) {
// Logger // Logger
app.use( app.use(
@ -75,6 +79,7 @@ const mastoRouter = new Router();
mastoRouter.use( mastoRouter.use(
koaBody({ koaBody({
urlencoded: true, urlencoded: true,
multipart: true,
}), }),
); );
@ -154,24 +159,46 @@ router.get("/verify-email/:code", async (ctx) => {
}); });
mastoRouter.get("/oauth/authorize", 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); 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) => { mastoRouter.post("/oauth/token", async (ctx) => {
const body: any = ctx.request.body; const body: any = ctx.request.body || ctx.request.query;
let client_id: any = ctx.request.query.client_id; 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 BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const generator = (megalodon as any).default; const generator = (megalodon as any).default;
const client = generator("misskey", BASE_URL, null) as MegalodonInterface; const client = generator("misskey", BASE_URL, null) as MegalodonInterface;
let m = null; let m = null;
let token = null;
if (body.code) { if (body.code) {
m = body.code.match(/^[a-zA-Z0-9-]+/); //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) { //if (!m.length) {
ctx.body = { error: "Invalid code" }; // ctx.body = { error: "Invalid code" };
return; // 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) { if (client_id instanceof Array) {
client_id = client_id.toString(); client_id = client_id.toString();
@ -182,14 +209,16 @@ mastoRouter.post("/oauth/token", async (ctx) => {
const atData = await client.fetchAccessToken( const atData = await client.fetchAccessToken(
client_id, client_id,
body.client_secret, body.client_secret,
m ? m[0] : "", token ? token : "",
); );
ctx.body = { const ret = {
access_token: atData.accessToken, access_token: atData.accessToken,
token_type: "Bearer", token_type: "Bearer",
scope: "read write follow", scope: body.scope || 'read write follow push',
created_at: Math.floor(new Date().getTime() / 1000), created_at: Math.floor(new Date().getTime() / 1000),
}; };
console.log('token-response', ret)
ctx.body = ret;
} catch (err: any) { } catch (err: any) {
console.error(err); console.error(err);
ctx.status = 401; ctx.status = 401;

View file

@ -3,7 +3,7 @@ import config from "@/config/index.js";
import { fetchMeta } from "@/misc/fetch-meta.js"; import { fetchMeta } from "@/misc/fetch-meta.js";
import { Users, Notes } from "@/models/index.js"; import { Users, Notes } from "@/models/index.js";
import { IsNull, MoreThan } from "typeorm"; 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"; import { Cache } from "@/misc/cache.js";
const router = new Router(); const router = new Router();
@ -85,6 +85,7 @@ const nodeinfo2 = async () => {
enableHcaptcha: meta.enableHcaptcha, enableHcaptcha: meta.enableHcaptcha,
enableRecaptcha: meta.enableRecaptcha, enableRecaptcha: meta.enableRecaptcha,
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH,
enableTwitterIntegration: meta.enableTwitterIntegration, enableTwitterIntegration: meta.enableTwitterIntegration,
enableGithubIntegration: meta.enableGithubIntegration, enableGithubIntegration: meta.enableGithubIntegration,
enableDiscordIntegration: meta.enableDiscordIntegration, enableDiscordIntegration: meta.enableDiscordIntegration,

View file

@ -9,6 +9,7 @@
"devDependencies": { "devDependencies": {
"@discordapp/twemoji": "14.0.2", "@discordapp/twemoji": "14.0.2",
"@khmyznikov/pwa-install": "^0.2.0", "@khmyznikov/pwa-install": "^0.2.0",
"@phosphor-icons/web": "^2.0.3",
"@rollup/plugin-alias": "3.1.9", "@rollup/plugin-alias": "3.1.9",
"@rollup/plugin-json": "4.1.0", "@rollup/plugin-json": "4.1.0",
"@rollup/pluginutils": "^4.2.1", "@rollup/pluginutils": "^4.2.1",
@ -34,9 +35,9 @@
"calckey-js": "^0.0.22", "calckey-js": "^0.0.22",
"chart.js": "4.1.1", "chart.js": "4.1.1",
"chartjs-adapter-date-fns": "2.0.1", "chartjs-adapter-date-fns": "2.0.1",
"chartjs-chart-matrix": "^2.0.1",
"chartjs-plugin-gradient": "0.5.1", "chartjs-plugin-gradient": "0.5.1",
"chartjs-plugin-zoom": "1.2.1", "chartjs-plugin-zoom": "1.2.1",
"chartjs-chart-matrix": "^2.0.1",
"city-timezones": "^1.2.1", "city-timezones": "^1.2.1",
"compare-versions": "5.0.3", "compare-versions": "5.0.3",
"cropperjs": "2.0.0-beta.2", "cropperjs": "2.0.0-beta.2",

View file

@ -242,7 +242,7 @@ export async function openAccountMenu(
...accountItemPromises, ...accountItemPromises,
{ {
type: "parent", type: "parent",
icon: "ph-plus-bold ph-lg", icon: "ph-plus ph-bold ph-lg",
text: i18n.ts.addAccount, text: i18n.ts.addAccount,
children: [ children: [
{ {
@ -261,13 +261,13 @@ export async function openAccountMenu(
}, },
{ {
type: "link", type: "link",
icon: "ph-users-bold ph-lg", icon: "ph-users ph-bold ph-lg",
text: i18n.ts.manageAccounts, text: i18n.ts.manageAccounts,
to: "/settings/accounts", to: "/settings/accounts",
}, },
{ {
type: "button", type: "button",
icon: "ph-sign-out-bold ph-lg", icon: "ph-sign-out ph-bold ph-lg",
text: i18n.ts.logout, text: i18n.ts.logout,
action: () => { action: () => {
signout(); signout();

View file

@ -1,7 +1,7 @@
<template> <template>
<XWindow ref="uiWindow" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')"> <XWindow ref="uiWindow" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')">
<template #header> <template #header>
<i class="ph-warning-circle-bold ph-lg" style="margin-right: 0.5em;"></i> <i class="ph-warning-circle ph-bold ph-lg" style="margin-right: 0.5em;"></i>
<I18n :src="i18n.ts.reportAbuseOf" tag="span"> <I18n :src="i18n.ts.reportAbuseOf" tag="span">
<template #name> <template #name>
<b><MkAcct :user="user"/></b> <b><MkAcct :user="user"/></b>

View file

@ -6,14 +6,14 @@
> >
<template v-if="!wait"> <template v-if="!wait">
<template v-if="isFollowing"> <template v-if="isFollowing">
<span v-if="full">{{ i18n.ts.unfollow }}</span><i class="ph-minus-bold ph-lg"></i> <span v-if="full">{{ i18n.ts.unfollow }}</span><i class="ph-minus ph-bold ph-lg"></i>
</template> </template>
<template v-else> <template v-else>
<span v-if="full">{{ i18n.ts.follow }}</span><i class="ph-plus-bold ph-lg"></i> <span v-if="full">{{ i18n.ts.follow }}</span><i class="ph-plus ph-bold ph-lg"></i>
</template> </template>
</template> </template>
<template v-else> <template v-else>
<span v-if="full">{{ i18n.ts.processing }}</span><i class="ph-circle-notch-bold ph-lg fa-pulse ph-fw ph-lg"></i> <span v-if="full">{{ i18n.ts.processing }}</span><i class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"></i>
</template> </template>
</button> </button>
</template> </template>

View file

@ -2,10 +2,10 @@
<MkA :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1"> <MkA :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1">
<div class="banner" :style="bannerStyle"> <div class="banner" :style="bannerStyle">
<div class="fade"></div> <div class="fade"></div>
<div class="name"><i class="ph-television-bold ph-lg"></i> {{ channel.name }}</div> <div class="name"><i class="ph-television ph-bold ph-lg"></i> {{ channel.name }}</div>
<div class="status"> <div class="status">
<div> <div>
<i class="ph-users-bold ph-lg ph-fw ph-lg"></i> <i class="ph-users ph-bold ph-lg ph-fw ph-lg"></i>
<I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"> <I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;">
<template #n> <template #n>
<b>{{ channel.usersCount }}</b> <b>{{ channel.usersCount }}</b>
@ -13,7 +13,7 @@
</I18n> </I18n>
</div> </div>
<div> <div>
<i class="ph-pencil-bold ph-lg ph-fw ph-lg"></i> <i class="ph-pencil ph-bold ph-lg ph-fw ph-lg"></i>
<I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;"> <I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;">
<template #n> <template #n>
<b>{{ channel.notesCount }}</b> <b>{{ channel.notesCount }}</b>

View file

@ -666,7 +666,7 @@ const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData
const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
return { return {
series: [{ series: [{
name: 'Notes', name: 'Posts',
type: 'area', type: 'area',
color: '#31748f', color: '#31748f',
data: format(total data: format(total

View file

@ -5,8 +5,8 @@
<div class="sub"> <div class="sub">
<slot name="func"></slot> <slot name="func"></slot>
<button v-if="foldable" class="_button" @click="() => showBody = !showBody"> <button v-if="foldable" class="_button" @click="() => showBody = !showBody">
<template v-if="showBody"><i class="ph-caret-up-bold ph-lg"></i></template> <template v-if="showBody"><i class="ph-caret-up ph-bold ph-lg"></i></template>
<template v-else><i class="ph-caret-down-bold ph-lg"></i></template> <template v-else><i class="ph-caret-down ph-bold ph-lg"></i></template>
</button> </button>
</div> </div>
</header> </header>

View file

@ -64,14 +64,14 @@ export default defineComponent({
}, [ }, [
h('span', [ h('span', [
h('i', { h('i', {
class: 'ph-caret-up-bold ph-lg icon', class: 'ph-caret-up ph-bold ph-lg icon',
}), }),
getDateText(item.createdAt), getDateText(item.createdAt),
]), ]),
h('span', [ h('span', [
getDateText(props.items[i + 1].createdAt), getDateText(props.items[i + 1].createdAt),
h('i', { h('i', {
class: 'ph-caret-down-bold ph-lg icon', class: 'ph-caret-down ph-bold ph-lg icon',
}), }),
]), ]),
])); ]));

View file

@ -5,17 +5,17 @@
<i :class="icon"></i> <i :class="icon"></i>
</div> </div>
<div v-else-if="!input && !select" :class="[$style.icon, $style['type_' + type]]"> <div v-else-if="!input && !select" :class="[$style.icon, $style['type_' + type]]">
<i v-if="type === 'success'" :class="$style.iconInner" class="ph-check-bold ph-lg"></i> <i v-if="type === 'success'" :class="$style.iconInner" class="ph-check ph-bold ph-lg"></i>
<i v-else-if="type === 'error'" :class="$style.iconInner" class="ph-circle-wavy-warning-bold ph-lg"></i> <i v-else-if="type === 'error'" :class="$style.iconInner" class="ph-circle-wavy-warning ph-bold ph-lg"></i>
<i v-else-if="type === 'warning'" :class="$style.iconInner" class="ph-warning-bold ph-lg"></i> <i v-else-if="type === 'warning'" :class="$style.iconInner" class="ph-warning ph-bold ph-lg"></i>
<i v-else-if="type === 'info'" :class="$style.iconInner" class="ph-info-bold ph-lg"></i> <i v-else-if="type === 'info'" :class="$style.iconInner" class="ph-info ph-bold ph-lg"></i>
<i v-else-if="type === 'question'" :class="$style.iconInner" class="ph-circle-question-bold ph-lg"></i> <i v-else-if="type === 'question'" :class="$style.iconInner" class="ph-circle-question ph-bold ph-lg"></i>
<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/> <MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
</div> </div>
<header v-if="title" :class="$style.title"><Mfm :text="title"/></header> <header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
<div v-if="text" :class="$style.text"><Mfm :text="text"/></div> <div v-if="text" :class="$style.text"><Mfm :text="text"/></div>
<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" @keydown="onInputKeydown"> <MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" @keydown="onInputKeydown">
<template v-if="input.type === 'password'" #prefix><i class="ph-password-bold ph-lg"></i></template> <template v-if="input.type === 'password'" #prefix><i class="ph-password ph-bold ph-lg"></i></template>
</MkInput> </MkInput>
<MkSelect v-if="select" v-model="selectedValue" autofocus> <MkSelect v-if="select" v-model="selectedValue" autofocus>
<template v-if="select.items"> <template v-if="select.items">

View file

@ -63,30 +63,30 @@ const title = computed(() => `${props.file.name}\n${props.file.type} ${bytes(pro
function getMenu() { function getMenu() {
return [{ return [{
text: i18n.ts.rename, text: i18n.ts.rename,
icon: 'ph-cursor-text-bold ph-lg', icon: 'ph-cursor-text ph-bold ph-lg',
action: rename, action: rename,
}, { }, {
text: props.file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, text: props.file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
icon: props.file.isSensitive ? 'ph-eye-bold ph-lg' : 'ph-eye-slash-bold ph-lg', icon: props.file.isSensitive ? 'ph-eye ph-bold ph-lg' : 'ph-eye-slash ph-bold ph-lg',
action: toggleSensitive, action: toggleSensitive,
}, { }, {
text: i18n.ts.describeFile, text: i18n.ts.describeFile,
icon: 'ph-cursor-text-bold ph-lg', icon: 'ph-cursor-text ph-bold ph-lg',
action: describe, action: describe,
}, null, { }, null, {
text: i18n.ts.copyUrl, text: i18n.ts.copyUrl,
icon: 'ph-link-simple-bold ph-lg', icon: 'ph-link-simple ph-bold ph-lg',
action: copyUrl, action: copyUrl,
}, { }, {
type: 'a', type: 'a',
href: props.file.url, href: props.file.url,
target: '_blank', target: '_blank',
text: i18n.ts.download, text: i18n.ts.download,
icon: 'ph-download-simple-bold ph-lg', icon: 'ph-download-simple ph-bold ph-lg',
download: props.file.name, download: props.file.name,
}, null, { }, null, {
text: i18n.ts.delete, text: i18n.ts.delete,
icon: 'ph-trash-bold ph-lg', icon: 'ph-trash ph-bold ph-lg',
danger: true, danger: true,
action: deleteFile, action: deleteFile,
}]; }];

View file

@ -16,8 +16,8 @@
@dragend="onDragend" @dragend="onDragend"
> >
<p class="name"> <p class="name">
<template v-if="hover"><i class="ph-folder-notch-open-bold ph-lg ph-fw ph-lg"></i></template> <template v-if="hover"><i class="ph-folder-notch-open ph-bold ph-lg ph-fw ph-lg"></i></template>
<template v-if="!hover"><i class="ph-folder-notch-bold ph-lg ph-fw ph-lg"></i></template> <template v-if="!hover"><i class="ph-folder-notch ph-bold ph-lg ph-fw ph-lg"></i></template>
{{ folder.name }} {{ folder.name }}
</p> </p>
<p v-if="defaultStore.state.uploadFolder == folder.id" class="upload"> <p v-if="defaultStore.state.uploadFolder == folder.id" class="upload">
@ -229,7 +229,7 @@ function setAsUploadFolder() {
function onContextmenu(ev: MouseEvent) { function onContextmenu(ev: MouseEvent) {
os.contextMenu([{ os.contextMenu([{
text: i18n.ts.openInWindow, text: i18n.ts.openInWindow,
icon: 'ph-copy-bold ph-lg', icon: 'ph-copy ph-bold ph-lg',
action: () => { action: () => {
os.popup(defineAsyncComponent(() => import('@/components/MkDriveWindow.vue')), { os.popup(defineAsyncComponent(() => import('@/components/MkDriveWindow.vue')), {
initialFolder: props.folder, initialFolder: props.folder,
@ -238,11 +238,11 @@ function onContextmenu(ev: MouseEvent) {
}, },
}, null, { }, null, {
text: i18n.ts.rename, text: i18n.ts.rename,
icon: 'ph-cursor-text-bold ph-lg', icon: 'ph-cursor-text ph-bold ph-lg',
action: rename, action: rename,
}, null, { }, null, {
text: i18n.ts.delete, text: i18n.ts.delete,
icon: 'ph-trash-bold ph-lg', icon: 'ph-trash ph-bold ph-lg',
danger: true, danger: true,
action: deleteFolder, action: deleteFolder,
}], ev); }], ev);

View file

@ -7,7 +7,7 @@
@dragleave="onDragleave" @dragleave="onDragleave"
@drop.stop="onDrop" @drop.stop="onDrop"
> >
<i v-if="folder == null" class="ph-cloud-bold ph-lg"></i> <i v-if="folder == null" class="ph-cloud ph-bold ph-lg"></i>
<span>{{ folder == null ? i18n.ts.drive : folder.name }}</span> <span>{{ folder == null ? i18n.ts.drive : folder.name }}</span>
</div> </div>
</template> </template>

View file

@ -11,7 +11,7 @@
@removeFolder="removeFolder" @removeFolder="removeFolder"
/> />
<template v-for="f in hierarchyFolders"> <template v-for="f in hierarchyFolders">
<span class="separator"><i class="ph-caret-right-bold ph-lg"></i></span> <span class="separator"><i class="ph-caret-right ph-bold ph-lg"></i></span>
<XNavFolder <XNavFolder
:folder="f" :folder="f"
:parent-folder="folder" :parent-folder="folder"
@ -21,10 +21,10 @@
@removeFolder="removeFolder" @removeFolder="removeFolder"
/> />
</template> </template>
<span v-if="folder != null" class="separator"><i class="ph-caret-right-bold ph-lg"></i></span> <span v-if="folder != null" class="separator"><i class="ph-caret-right ph-bold ph-lg"></i></span>
<span v-if="folder != null" class="folder current">{{ folder.name }}</span> <span v-if="folder != null" class="folder current">{{ folder.name }}</span>
</div> </div>
<button class="menu _button" @click="showMenu"><i class="ph-dots-three-outline-bold ph-lg"></i></button> <button class="menu _button" @click="showMenu"><i class="ph-dots-three-outline ph-bold ph-lg"></i></button>
</nav> </nav>
<div <div
ref="main" class="main" ref="main" class="main"
@ -573,26 +573,26 @@ function getMenu() {
type: 'label', type: 'label',
}, { }, {
text: i18n.ts.upload, text: i18n.ts.upload,
icon: 'ph-upload-simple-bold ph-lg', icon: 'ph-upload-simple ph-bold ph-lg',
action: () => { selectLocalFile(); }, action: () => { selectLocalFile(); },
}, { }, {
text: i18n.ts.fromUrl, text: i18n.ts.fromUrl,
icon: 'ph-link-simple-bold ph-lg', icon: 'ph-link-simple ph-bold ph-lg',
action: () => { urlUpload(); }, action: () => { urlUpload(); },
}, null, { }, null, {
text: folder.value ? folder.value.name : i18n.ts.drive, text: folder.value ? folder.value.name : i18n.ts.drive,
type: 'label', type: 'label',
}, folder.value ? { }, folder.value ? {
text: i18n.ts.renameFolder, text: i18n.ts.renameFolder,
icon: 'ph-cursor-text-bold ph-lg', icon: 'ph-cursor-text ph-bold ph-lg',
action: () => { renameFolder(folder.value); }, action: () => { renameFolder(folder.value); },
} : undefined, folder.value ? { } : undefined, folder.value ? {
text: i18n.ts.deleteFolder, text: i18n.ts.deleteFolder,
icon: 'ph-trash-bold ph-lg', icon: 'ph-trash ph-bold ph-lg',
action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); }, action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); },
} : undefined, { } : undefined, {
text: i18n.ts.createFolder, text: i18n.ts.createFolder,
icon: 'ph-folder-notch-plus-bold ph-lg', icon: 'ph-folder-notch-plus ph-bold ph-lg',
action: () => { createFolder(); }, action: () => { createFolder(); },
}]; }];
} }

View file

@ -1,16 +1,16 @@
<template> <template>
<div ref="thumbnail" class="zdjebgpv"> <div ref="thumbnail" class="zdjebgpv">
<ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :cover="fit !== 'contain'"/> <ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :cover="fit !== 'contain'"/>
<i v-else-if="is === 'image'" class="ph-file-image-bold ph-lg icon"></i> <i v-else-if="is === 'image'" class="ph-file-image ph-bold ph-lg icon"></i>
<i v-else-if="is === 'video'" class="ph-file-video-bold ph-lg icon"></i> <i v-else-if="is === 'video'" class="ph-file-video ph-bold ph-lg icon"></i>
<i v-else-if="is === 'audio' || is === 'midi'" class="ph-file-audio-bold ph-lg icon"></i> <i v-else-if="is === 'audio' || is === 'midi'" class="ph-file-audio ph-bold ph-lg icon"></i>
<i v-else-if="is === 'csv'" class="ph-file-csv-bold ph-lg icon"></i> <i v-else-if="is === 'csv'" class="ph-file-csv ph-bold ph-lg icon"></i>
<i v-else-if="is === 'pdf'" class="ph-file-pdf-bold ph-lg icon"></i> <i v-else-if="is === 'pdf'" class="ph-file-pdf ph-bold ph-lg icon"></i>
<i v-else-if="is === 'textfile'" class="ph-file-text-bold ph-lg icon"></i> <i v-else-if="is === 'textfile'" class="ph-file-text ph-bold ph-lg icon"></i>
<i v-else-if="is === 'archive'" class="ph-file-zip-bold ph-lg icon"></i> <i v-else-if="is === 'archive'" class="ph-file-zip ph-bold ph-lg icon"></i>
<i v-else class="ph-file-bold ph-lg icon"></i> <i v-else class="ph-file ph-bold ph-lg icon"></i>
<i v-if="isThumbnailAvailable && is === 'video'" class="ph-file-video-bold ph-lg icon-sub"></i> <i v-if="isThumbnailAvailable && is === 'video'" class="ph-file-video ph-bold ph-lg icon-sub"></i>
</div> </div>
</template> </template>

View file

@ -2,7 +2,7 @@
<!-- このコンポーネントの要素のclassは親から利用されるのでむやみに弄らないこと --> <!-- このコンポーネントの要素のclassは親から利用されるのでむやみに弄らないこと -->
<section> <section>
<header class="_acrylic" @click="shown = !shown"> <header class="_acrylic" @click="shown = !shown">
<i class="toggle ph-fw ph-lg" :class="shown ? 'ph-caret-down-bold ph-lg' : 'ph-caret-up-bold ph-lg'"></i> <slot></slot> ({{ emojis.length }}) <i class="toggle ph-fw ph-lg" :class="shown ? 'ph-caret-down-bold ph-lg' : 'ph-caret-up ph-bold ph-lg'"></i> <slot></slot> ({{ emojis.length }})
</header> </header>
<div v-if="shown" class="body"> <div v-if="shown" class="body">
<button <button

View file

@ -46,7 +46,7 @@
</section> </section>
<section> <section>
<header><i class="ph-alarm-bold ph-fw ph-lg"></i> {{ i18n.ts.recentUsed }}</header> <header><i class="ph-alarm ph-bold ph-fw ph-lg"></i> {{ i18n.ts.recentUsed }}</header>
<div class="body"> <div class="body">
<button <button
v-for="emoji in recentlyUsedEmojis" v-for="emoji in recentlyUsedEmojis"
@ -69,10 +69,10 @@
</div> </div>
</div> </div>
<div class="tabs"> <div class="tabs">
<button class="_button tab" :class="{ active: tab === 'index' }" @click="tab = 'index'"><i class="ph-asterisk-bold ph-lg ph-fw ph-lg"></i></button> <button class="_button tab" :class="{ active: tab === 'index' }" @click="tab = 'index'"><i class="ph-asterisk ph-bold ph-lg ph-fw ph-lg"></i></button>
<button class="_button tab" :class="{ active: tab === 'custom' }" @click="tab = 'custom'"><i class="ph-smiley-bold ph-lg ph-fw ph-lg"></i></button> <button class="_button tab" :class="{ active: tab === 'custom' }" @click="tab = 'custom'"><i class="ph-smiley ph-bold ph-lg ph-fw ph-lg"></i></button>
<button class="_button tab" :class="{ active: tab === 'unicode' }" @click="tab = 'unicode'"><i class="ph-leaf-bold ph-lg ph-fw ph-lg"></i></button> <button class="_button tab" :class="{ active: tab === 'unicode' }" @click="tab = 'unicode'"><i class="ph-leaf ph-bold ph-lg ph-fw ph-lg"></i></button>
<button class="_button tab" :class="{ active: tab === 'tags' }" @click="tab = 'tags'"><i class="ph-hash-bold ph-lg ph-fw ph-lg"></i></button> <button class="_button tab" :class="{ active: tab === 'tags' }" @click="tab = 'tags'"><i class="ph-hash ph-bold ph-lg ph-fw ph-lg"></i></button>
</div> </div>
</div> </div>
</template> </template>

View file

@ -1,6 +1,6 @@
<template> <template>
<span class="mk-file-type-icon"> <span class="mk-file-type-icon">
<template v-if="kind == 'image'"><i class="ph-file-image-bold ph-lg"></i></template> <template v-if="kind == 'image'"><i class="ph-file-image ph-bold ph-lg"></i></template>
</span> </span>
</template> </template>

View file

@ -4,8 +4,8 @@
<div class="title"><slot name="header"></slot></div> <div class="title"><slot name="header"></slot></div>
<div class="divider"></div> <div class="divider"></div>
<button class="_button"> <button class="_button">
<template v-if="showBody"><i class="ph-caret-up-bold ph-lg"></i></template> <template v-if="showBody"><i class="ph-caret-up ph-bold ph-lg"></i></template>
<template v-else><i class="ph-caret-down-bold ph-lg"></i></template> <template v-else><i class="ph-caret-down ph-bold ph-lg"></i></template>
</button> </button>
</header> </header>
<transition <transition

View file

@ -13,27 +13,27 @@
> >
<template v-if="!wait"> <template v-if="!wait">
<template v-if="isBlocking"> <template v-if="isBlocking">
<span v-if="full">{{ i18n.ts.blocked }}</span><i class="ph-prohibit-bold ph-lg"></i> <span v-if="full">{{ i18n.ts.blocked }}</span><i class="ph-prohibit ph-bold ph-lg"></i>
</template> </template>
<template v-else-if="hasPendingFollowRequestFromYou && user.isLocked"> <template v-else-if="hasPendingFollowRequestFromYou && user.isLocked">
<span v-if="full">{{ i18n.ts.followRequestPending }}</span><i class="ph-hourglass-medium-bold ph-lg"></i> <span v-if="full">{{ i18n.ts.followRequestPending }}</span><i class="ph-hourglass-medium ph-bold ph-lg"></i>
</template> </template>
<template v-else-if="hasPendingFollowRequestFromYou && !user.isLocked"> <template v-else-if="hasPendingFollowRequestFromYou && !user.isLocked">
<!-- つまりリモートフォローの場合 --> <!-- つまりリモートフォローの場合 -->
<span v-if="full">{{ i18n.ts.processing }}</span><i class="ph-circle-notch-bold ph-lg fa-pulse"></i> <span v-if="full">{{ i18n.ts.processing }}</span><i class="ph-circle-notch ph-bold ph-lg fa-pulse"></i>
</template> </template>
<template v-else-if="isFollowing"> <template v-else-if="isFollowing">
<span v-if="full">{{ i18n.ts.unfollow }}</span><i class="ph-minus-bold ph-lg"></i> <span v-if="full">{{ i18n.ts.unfollow }}</span><i class="ph-minus ph-bold ph-lg"></i>
</template> </template>
<template v-else-if="!isFollowing && user.isLocked"> <template v-else-if="!isFollowing && user.isLocked">
<span v-if="full">{{ i18n.ts.followRequest }}</span><i class="ph-plus-bold ph-lg"></i> <span v-if="full">{{ i18n.ts.followRequest }}</span><i class="ph-plus ph-bold ph-lg"></i>
</template> </template>
<template v-else-if="!isFollowing && !user.isLocked"> <template v-else-if="!isFollowing && !user.isLocked">
<span v-if="full">{{ i18n.ts.follow }}</span><i class="ph-plus-bold ph-lg"></i> <span v-if="full">{{ i18n.ts.follow }}</span><i class="ph-plus ph-bold ph-lg"></i>
</template> </template>
</template> </template>
<template v-else> <template v-else>
<span v-if="full">{{ i18n.ts.processing }}</span><i class="ph-circle-notch-bold ph-lg fa-pulse ph-fw ph-lg"></i> <span v-if="full">{{ i18n.ts.processing }}</span><i class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"></i>
</template> </template>
</button> </button>
</template> </template>

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="mk-google"> <div class="mk-google">
<input v-model="query" type="search" :placeholder="q"> <input v-model="query" type="search" :placeholder="q">
<button @click="search"><i class="ph-magnifying-glass-bold ph-lg"></i> {{ i18n.ts.searchByGoogle }}</button> <button @click="search"><i class="ph-magnifying-glass ph-bold ph-lg"></i> {{ i18n.ts.searchByGoogle }}</button>
</div> </div>
</template> </template>

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="fpezltsf" :class="{ warn }"> <div class="fpezltsf" :class="{ warn }">
<i v-if="warn" class="ph-warning-bold ph-lg"></i> <i v-if="warn" class="ph-warning ph-bold ph-lg"></i>
<i v-else class="ph-info-bold ph-lg"></i> <i v-else class="ph-info ph-bold ph-lg"></i>
<slot></slot> <slot></slot>
</div> </div>
</template> </template>

View file

@ -5,7 +5,7 @@
</div> </div>
<div class="value"> <div class="value">
<slot name="value"></slot> <slot name="value"></slot>
<button v-if="copy" v-tooltip="i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="ph-clipboard-text-bold"></i></button> <button v-if="copy" v-tooltip="i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="ph-clipboard-text ph-bold"></i></button>
</div> </div>
</div> </div>
</template> </template>

View file

@ -6,12 +6,12 @@
<button v-if="item.action" v-click-anime class="_button" @click="$event => { item.action($event); close(); }"> <button v-if="item.action" v-click-anime class="_button" @click="$event => { item.action($event); close(); }">
<i class="icon" :class="item.icon"></i> <i class="icon" :class="item.icon"></i>
<div class="text">{{ item.text }}</div> <div class="text">{{ item.text }}</div>
<span v-if="item.indicate" class="indicator"><i class="ph-circle-fill"></i></span> <span v-if="item.indicate" class="indicator"><i class="ph-circle ph-fill"></i></span>
</button> </button>
<MkA v-else v-click-anime :to="item.to" @click.passive="close()"> <MkA v-else v-click-anime :to="item.to" @click.passive="close()">
<i class="icon" :class="item.icon"></i> <i class="icon" :class="item.icon"></i>
<div class="text">{{ item.text }}</div> <div class="text">{{ item.text }}</div>
<span v-if="item.indicate" class="indicator"><i class="ph-circle-fill"></i></span> <span v-if="item.indicate" class="indicator"><i class="ph-circle ph-fill"></i></span>
</MkA> </MkA>
</template> </template>
</div> </div>

View file

@ -3,7 +3,7 @@
:title="url" :title="url"
> >
<slot></slot> <slot></slot>
<i v-if="target === '_blank'" class="ph-arrow-square-out-bold ph-lg icon"></i> <i v-if="target === '_blank'" class="ph-arrow-square-out ph-bold ph-lg icon"></i>
</component> </component>
</template> </template>

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="mk-media-banner"> <div class="mk-media-banner">
<div v-if="media.isSensitive && hide" class="sensitive" @click="hide = false"> <div v-if="media.isSensitive && hide" class="sensitive" @click="hide = false">
<span class="icon"><i class="ph-warning-bold ph-lg"></i></span> <span class="icon"><i class="ph-warning ph-bold ph-lg"></i></span>
<b>{{ i18n.ts.sensitive }}</b> <b>{{ i18n.ts.sensitive }}</b>
<span>{{ i18n.ts.clickToShow }}</span> <span>{{ i18n.ts.clickToShow }}</span>
</div> </div>
@ -37,7 +37,7 @@
:title="media.name" :title="media.name"
:download="media.name" :download="media.name"
> >
<span class="icon"><i class="ph-download-simple-bold ph-lg"></i></span> <span class="icon"><i class="ph-download-simple ph-bold ph-lg"></i></span>
<b>{{ media.name }}</b> <b>{{ media.name }}</b>
</a> </a>
</div> </div>

View file

@ -39,6 +39,7 @@ import MkButton from '@/components/MkButton.vue';
import bytes from '@/filters/bytes'; import bytes from '@/filters/bytes';
import number from '@/filters/number'; import number from '@/filters/number';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { instance } from '@/instance';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -87,8 +88,9 @@ export default defineComponent({
computed: { computed: {
remainingLength(): number { remainingLength(): number {
if (typeof this.inputValue !== "string") return 512; const maxCaptionLength = instance.maxCaptionTextLength ?? 512;
return 512 - length(this.inputValue); if (typeof this.inputValue !== "string") return maxCaptionLength;
return maxCaptionLength - length(this.inputValue);
} }
}, },

View file

@ -3,7 +3,7 @@
<ImgWithBlurhash class="bg" :hash="image.blurhash" :title="image.comment" :alt="image.comment"/> <ImgWithBlurhash class="bg" :hash="image.blurhash" :title="image.comment" :alt="image.comment"/>
<div class="text"> <div class="text">
<div class="wrapper"> <div class="wrapper">
<b style="display: block;"><i class="ph-warning-bold ph-lg"></i> {{ i18n.ts.sensitive }}</b> <b style="display: block;"><i class="ph-warning ph-bold ph-lg"></i> {{ i18n.ts.sensitive }}</b>
<span style="display: block;">{{ i18n.ts.clickToShow }}</span> <span style="display: block;">{{ i18n.ts.clickToShow }}</span>
</div> </div>
</div> </div>
@ -16,7 +16,7 @@
<ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment" :type="image.type" :title="image.comment" :cover="false"/> <ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment" :type="image.type" :title="image.comment" :cover="false"/>
<div v-if="image.type === 'image/gif'" class="gif">GIF</div> <div v-if="image.type === 'image/gif'" class="gif">GIF</div>
</a> </a>
<button v-tooltip="i18n.ts.hide" class="_button hide" @click="hide = true"><i class="ph-eye-slash-bold ph-lg"></i></button> <button v-tooltip="i18n.ts.hide" class="_button hide" @click="hide = true"><i class="ph-eye-slash ph-bold ph-lg"></i></button>
</div> </div>
</template> </template>

View file

@ -1,7 +1,7 @@
<template> <template>
<div v-if="hide" class="icozogqfvdetwohsdglrbswgrejoxbdj" @click="hide = false"> <div v-if="hide" class="icozogqfvdetwohsdglrbswgrejoxbdj" @click="hide = false">
<div> <div>
<b><i class="ph-warning-bold ph-lg"></i> {{ i18n.ts.sensitive }}</b> <b><i class="ph-warning ph-bold ph-lg"></i> {{ i18n.ts.sensitive }}</b>
<span>{{ i18n.ts.clickToShow }}</span> <span>{{ i18n.ts.clickToShow }}</span>
</div> </div>
</div> </div>
@ -36,7 +36,7 @@
> >
</video> </video>
</VuePlyr> </VuePlyr>
<i class="ph-eye-slash-bold ph-lg" @click="hide = true"></i> <i class="ph-eye-slash ph-bold ph-lg" @click="hide = true"></i>
</div> </div>
</template> </template>

View file

@ -19,16 +19,16 @@
<i v-if="item.icon" class="ph-fw ph-lg" :class="item.icon"></i> <i v-if="item.icon" class="ph-fw ph-lg" :class="item.icon"></i>
<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/> <MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
<span>{{ item.text }}</span> <span>{{ item.text }}</span>
<span v-if="item.indicate" class="indicator"><i class="ph-circle-fill"></i></span> <span v-if="item.indicate" class="indicator"><i class="ph-circle ph-fill"></i></span>
</MkA> </MkA>
<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" :tabindex="i" class="_button item" @click="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" :tabindex="i" class="_button item" @click="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
<i v-if="item.icon" class="ph-fw ph-lg" :class="item.icon"></i> <i v-if="item.icon" class="ph-fw ph-lg" :class="item.icon"></i>
<span>{{ item.text }}</span> <span>{{ item.text }}</span>
<span v-if="item.indicate" class="indicator"><i class="ph-circle-fill"></i></span> <span v-if="item.indicate" class="indicator"><i class="ph-circle ph-fill"></i></span>
</a> </a>
<button v-else-if="item.type === 'user' && !items.hidden" :tabindex="i" class="_button item" :class="{ active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <button v-else-if="item.type === 'user' && !items.hidden" :tabindex="i" class="_button item" :class="{ active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
<MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/> <MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/>
<span v-if="item.indicate" class="indicator"><i class="ph-circle-fill"></i></span> <span v-if="item.indicate" class="indicator"><i class="ph-circle ph-fill"></i></span>
</button> </button>
<span v-else-if="item.type === 'switch'" :tabindex="i" class="item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <span v-else-if="item.type === 'switch'" :tabindex="i" class="item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
<FormSwitch v-model="item.ref" :disabled="item.disabled" class="form-switch">{{ item.text }}</FormSwitch> <FormSwitch v-model="item.ref" :disabled="item.disabled" class="form-switch">{{ item.text }}</FormSwitch>
@ -36,13 +36,13 @@
<button v-else-if="item.type === 'parent'" :tabindex="i" class="_button item parent" :class="{ childShowing: childShowingItem === item }" @mouseenter="showChildren(item, $event)"> <button v-else-if="item.type === 'parent'" :tabindex="i" class="_button item parent" :class="{ childShowing: childShowingItem === item }" @mouseenter="showChildren(item, $event)">
<i v-if="item.icon" class="ph-fw ph-lg" :class="item.icon"></i> <i v-if="item.icon" class="ph-fw ph-lg" :class="item.icon"></i>
<span>{{ item.text }}</span> <span>{{ item.text }}</span>
<span class="caret"><i class="ph-caret-right-bold ph-lg ph-fw ph-lg"></i></span> <span class="caret"><i class="ph-caret-right ph-bold ph-lg ph-fw ph-lg"></i></span>
</button> </button>
<button v-else-if="!item.hidden" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <button v-else-if="!item.hidden" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
<i v-if="item.icon" class="ph-fw ph-lg" :class="item.icon"></i> <i v-if="item.icon" class="ph-fw ph-lg" :class="item.icon"></i>
<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/> <MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
<span>{{ item.text }}</span> <span>{{ item.text }}</span>
<span v-if="item.indicate" class="indicator"><i class="ph-circle-fill"></i></span> <span v-if="item.indicate" class="indicator"><i class="ph-circle ph-fill"></i></span>
</button> </button>
</template> </template>
<span v-if="items2.length === 0" class="none item"> <span v-if="items2.length === 0" class="none item">

View file

@ -2,13 +2,13 @@
<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')"> <MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
<div ref="rootEl" class="hrmcaedk _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }"> <div ref="rootEl" class="hrmcaedk _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }">
<div class="header" @contextmenu="onContextmenu"> <div class="header" @contextmenu="onContextmenu">
<button v-if="history.length > 0" v-tooltip="i18n.ts.goBack" class="_button" @click="back()"><i class="ph-caret-left-bold ph-lg"></i></button> <button v-if="history.length > 0" v-tooltip="i18n.ts.goBack" class="_button" @click="back()"><i class="ph-caret-left ph-bold ph-lg"></i></button>
<span v-else style="display: inline-block; width: 20px"></span> <span v-else style="display: inline-block; width: 20px"></span>
<span v-if="pageMetadata?.value" class="title"> <span v-if="pageMetadata?.value" class="title">
<i v-if="pageMetadata?.value.icon" class="icon" :class="pageMetadata?.value.icon"></i> <i v-if="pageMetadata?.value.icon" class="icon" :class="pageMetadata?.value.icon"></i>
<span>{{ pageMetadata?.value.title }}</span> <span>{{ pageMetadata?.value.title }}</span>
</span> </span>
<button class="_button" @click="$refs.modal.close()"><i class="ph-x-bold ph-lg"></i></button> <button class="_button" @click="$refs.modal.close()"><i class="ph-x ph-bold ph-lg"></i></button>
</div> </div>
<div class="body"> <div class="body">
<MkStickyContainer> <MkStickyContainer>
@ -68,22 +68,22 @@ const contextmenu = $computed(() => {
type: 'label', type: 'label',
text: path, text: path,
}, { }, {
icon: 'ph-arrows-out-simple-bold ph-lg', icon: 'ph-arrows-out-simple ph-bold ph-lg',
text: i18n.ts.showInPage, text: i18n.ts.showInPage,
action: expand, action: expand,
}, { }, {
icon: 'ph-arrow-square-out-bold ph-lg', icon: 'ph-arrow-square-out ph-bold ph-lg',
text: i18n.ts.popout, text: i18n.ts.popout,
action: popout, action: popout,
}, null, { }, null, {
icon: 'ph-arrow-square-out-bold ph-lg', icon: 'ph-arrow-square-out ph-bold ph-lg',
text: i18n.ts.openInNewTab, text: i18n.ts.openInNewTab,
action: () => { action: () => {
window.open(pageUrl, '_blank'); window.open(pageUrl, '_blank');
modal.close(); modal.close();
}, },
}, { }, {
icon: 'ph-link-simple-bold ph-lg', icon: 'ph-link-simple ph-bold ph-lg',
text: i18n.ts.copyLink, text: i18n.ts.copyLink,
action: () => { action: () => {
copyToClipboard(pageUrl); copyToClipboard(pageUrl);

View file

@ -2,12 +2,12 @@
<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')"> <MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')">
<div ref="rootEl" class="ebkgoccj" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown"> <div ref="rootEl" class="ebkgoccj" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown">
<div ref="headerEl" class="header"> <div ref="headerEl" class="header">
<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="ph-x-bold ph-lg"></i></button> <button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="ph-x ph-bold ph-lg"></i></button>
<span class="title"> <span class="title">
<slot name="header"></slot> <slot name="header"></slot>
</span> </span>
<button v-if="!withOkButton" class="_button" @click="$emit('close')"><i class="ph-x-bold ph-lg"></i></button> <button v-if="!withOkButton" class="_button" @click="$emit('close')"><i class="ph-x ph-bold ph-lg"></i></button>
<button v-if="withOkButton" class="_button" :disabled="okButtonDisabled" @click="$emit('ok')"><i class="ph-check-bold ph-lg"></i></button> <button v-if="withOkButton" class="_button" :disabled="okButtonDisabled" @click="$emit('ok')"><i class="ph-check ph-bold ph-lg"></i></button>
</div> </div>
<div class="body"> <div class="body">
<slot :width="bodyWidth" :height="bodyHeight"></slot> <slot :width="bodyWidth" :height="bodyHeight"></slot>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="msjugskd _block"> <div class="msjugskd _block">
<i class="ph-airplane-takeoff-bold ph-lg" style="margin-right: 8px;"/> <i class="ph-airplane-takeoff ph-bold ph-lg" style="margin-right: 8px;"/>
{{ i18n.ts.accountMoved }} {{ i18n.ts.accountMoved }}
<MkMention class="link" :username="acct" :host="host"/> <MkMention class="link" :username="acct" :host="host"/>
</div> </div>

View file

@ -12,11 +12,11 @@
<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/>
<div class="note-context"> <div class="note-context">
<div class="line"></div> <div class="line"></div>
<div v-if="appearNote._prId_" class="info"><i class="ph-megaphone-simple-bold ph-lg"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ph-x-bold ph-lg"></i></button></div> <div v-if="appearNote._prId_" class="info"><i class="ph-megaphone-simple-bold ph-lg"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ph-x ph-bold ph-lg"></i></button></div>
<div v-if="appearNote._featuredId_" class="info"><i class="ph-lightning-bold ph-lg"></i> {{ i18n.ts.featured }}</div> <div v-if="appearNote._featuredId_" class="info"><i class="ph-lightning ph-bold ph-lg"></i> {{ i18n.ts.featured }}</div>
<div v-if="pinned" class="info"><i class="ph-push-pin-bold ph-lg"></i>{{ i18n.ts.pinnedNote }}</div> <div v-if="pinned" class="info"><i class="ph-push-pin ph-bold ph-lg"></i>{{ i18n.ts.pinnedNote }}</div>
<div v-if="isRenote" class="renote"> <div v-if="isRenote" class="renote">
<i class="ph-repeat-bold ph-lg"></i> <i class="ph-repeat ph-bold ph-lg"></i>
<I18n :src="i18n.ts.renotedBy" tag="span"> <I18n :src="i18n.ts.renotedBy" tag="span">
<template #user> <template #user>
<MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)"> <MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)">
@ -26,7 +26,7 @@
</I18n> </I18n>
<div class="info"> <div class="info">
<button ref="renoteTime" class="_button time" @click.stop="showRenoteMenu()"> <button ref="renoteTime" class="_button time" @click.stop="showRenoteMenu()">
<i v-if="isMyRenote" class="ph-dots-three-outline-bold ph-lg dropdownIcon"></i> <i v-if="isMyRenote" class="ph-dots-three-outline ph-bold ph-lg dropdownIcon"></i>
<MkTime :time="note.createdAt"/> <MkTime :time="note.createdAt"/>
</button> </button>
<MkVisibility :note="note"/> <MkVisibility :note="note"/>
@ -69,7 +69,7 @@
<span>{{ i18n.ts.showLess }}</span> <span>{{ i18n.ts.showLess }}</span>
</button> </button>
</div> </div>
<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`" @click.stop><i class="ph-television-bold ph-lg"></i> {{ appearNote.channel.name }}</MkA> <MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`" @click.stop><i class="ph-television ph-bold ph-lg"></i> {{ appearNote.channel.name }}</MkA>
</div> </div>
<MkNoteFooter :note="appearNote"></MkNoteFooter> <MkNoteFooter :note="appearNote"></MkNoteFooter>
</div> </div>
@ -235,7 +235,7 @@ function showRenoteMenu(viaKeyboard = false): void {
if (!isMyRenote) return; if (!isMyRenote) return;
os.popupMenu([{ os.popupMenu([{
text: i18n.ts.unrenote, text: i18n.ts.unrenote,
icon: 'ph-trash-bold ph-lg', icon: 'ph-trash ph-bold ph-lg',
danger: true, danger: true,
action: () => { action: () => {
os.api('notes/delete', { os.api('notes/delete', {

View file

@ -13,7 +13,7 @@
<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to" @click.self="router.push(notePage(appearNote))"/> <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to" @click.self="router.push(notePage(appearNote))"/>
<div v-if="isRenote" class="renote"> <div v-if="isRenote" class="renote">
<MkAvatar class="avatar" :user="note.user"/> <MkAvatar class="avatar" :user="note.user"/>
<i class="ph-repeat-bold ph-lg"></i> <i class="ph-repeat ph-bold ph-lg"></i>
<I18n :src="i18n.ts.renotedBy" tag="span"> <I18n :src="i18n.ts.renotedBy" tag="span">
<template #user> <template #user>
<MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)"> <MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)">
@ -23,7 +23,7 @@
</I18n> </I18n>
<div class="info"> <div class="info">
<button ref="renoteTime" class="_button time" @click="showRenoteMenu()"> <button ref="renoteTime" class="_button time" @click="showRenoteMenu()">
<i v-if="isMyRenote" class="ph-dots-three-outline-bold ph-lg dropdownIcon"></i> <i v-if="isMyRenote" class="ph-dots-three-outline ph-bold ph-lg dropdownIcon"></i>
<MkTime :time="note.createdAt"/> <MkTime :time="note.createdAt"/>
</button> </button>
<MkVisibility :note="note"/> <MkVisibility :note="note"/>
@ -70,7 +70,7 @@
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" class="url-preview"/> <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" class="url-preview"/>
<div v-if="appearNote.renote" class="renote"><XNoteSimple :note="appearNote.renote" @click.stop="router.push(notePage(appearNote.renote))"/></div> <div v-if="appearNote.renote" class="renote"><XNoteSimple :note="appearNote.renote" @click.stop="router.push(notePage(appearNote.renote))"/></div>
</div> </div>
<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="ph-television-bold ph-lg"></i> {{ appearNote.channel.name }}</MkA> <MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="ph-television ph-bold ph-lg"></i> {{ appearNote.channel.name }}</MkA>
</div> </div>
<footer class="footer"> <footer class="footer">
<div class="info"> <div class="info">
@ -80,21 +80,21 @@
</div> </div>
<XReactionsViewer ref="reactionsViewer" :note="appearNote"/> <XReactionsViewer ref="reactionsViewer" :note="appearNote"/>
<button v-tooltip.noDelay.bottom="i18n.ts.reply" class="button _button" @click="reply()"> <button v-tooltip.noDelay.bottom="i18n.ts.reply" class="button _button" @click="reply()">
<template v-if="appearNote.reply"><i class="ph-arrow-u-up-left-bold ph-lg"></i></template> <template v-if="appearNote.reply"><i class="ph-arrow-u-up-left ph-bold ph-lg"></i></template>
<template v-else><i class="ph-arrow-bend-up-left-bold ph-lg"></i></template> <template v-else><i class="ph-arrow-bend-up-left ph-bold ph-lg"></i></template>
<p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p> <p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p>
</button> </button>
<XRenoteButton ref="renoteButton" class="button" :note="appearNote" :count="appearNote.renoteCount"/> <XRenoteButton ref="renoteButton" class="button" :note="appearNote" :count="appearNote.renoteCount"/>
<XStarButton v-if="appearNote.myReaction == null" ref="starButton" class="button" :note="appearNote"/> <XStarButton v-if="appearNote.myReaction == null" ref="starButton" class="button" :note="appearNote"/>
<button v-if="appearNote.myReaction == null" ref="reactButton" v-tooltip.noDelay.bottom="i18n.ts.reaction" class="button _button" @click="react()"> <button v-if="appearNote.myReaction == null" ref="reactButton" v-tooltip.noDelay.bottom="i18n.ts.reaction" class="button _button" @click="react()">
<i class="ph-smiley-bold ph-lg"></i> <i class="ph-smiley ph-bold ph-lg"></i>
</button> </button>
<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)"> <button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)">
<i class="ph-minus-bold ph-lg"></i> <i class="ph-minus ph-bold ph-lg"></i>
</button> </button>
<XQuoteButton class="button" :note="appearNote"/> <XQuoteButton class="button" :note="appearNote"/>
<button ref="menuButton" v-tooltip.noDelay.bottom="i18n.ts.more" class="button _button" @click="menu()"> <button ref="menuButton" v-tooltip.noDelay.bottom="i18n.ts.more" class="button _button" @click="menu()">
<i class="ph-dots-three-outline-bold ph-lg"></i> <i class="ph-dots-three-outline ph-bold ph-lg"></i>
</button> </button>
</footer> </footer>
</div> </div>
@ -267,7 +267,7 @@ function showRenoteMenu(viaKeyboard = false): void {
if (!isMyRenote) return; if (!isMyRenote) return;
os.popupMenu([{ os.popupMenu([{
text: i18n.ts.unrenote, text: i18n.ts.unrenote,
icon: 'ph-trash-bold ph-lg', icon: 'ph-trash ph-bold ph-lg',
danger: true, danger: true,
action: () => { action: () => {
os.api('notes/delete', { os.api('notes/delete', {

View file

@ -29,7 +29,7 @@
</template> </template>
<div v-else-if="replies.length > 0" class="more"> <div v-else-if="replies.length > 0" class="more">
<div class="line"></div> <div class="line"></div>
<MkA class="text _link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ph-caret-double-right-bold ph-lg"></i></MkA> <MkA class="text _link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ph-caret-double-right ph-bold ph-lg"></i></MkA>
</div> </div>
</template> </template>
</div> </div>

View file

@ -5,16 +5,16 @@
<MkAvatar v-else-if="notification.user" class="icon" :user="notification.user"/> <MkAvatar v-else-if="notification.user" class="icon" :user="notification.user"/>
<img v-else-if="notification.icon" class="icon" :src="notification.icon" alt=""/> <img v-else-if="notification.icon" class="icon" :src="notification.icon" alt=""/>
<div class="sub-icon" :class="notification.type"> <div class="sub-icon" :class="notification.type">
<i v-if="notification.type === 'follow'" class="ph-hand-waving-bold"></i> <i v-if="notification.type === 'follow'" class="ph-hand-waving ph-bold"></i>
<i v-else-if="notification.type === 'receiveFollowRequest'" class="ph-clock-bold"></i> <i v-else-if="notification.type === 'receiveFollowRequest'" class="ph-clock ph-bold"></i>
<i v-else-if="notification.type === 'followRequestAccepted'" class="ph-check-bold"></i> <i v-else-if="notification.type === 'followRequestAccepted'" class="ph-check ph-bold"></i>
<i v-else-if="notification.type === 'groupInvited'" class="ph-identification-card-bold"></i> <i v-else-if="notification.type === 'groupInvited'" class="ph-identification-card ph-bold"></i>
<i v-else-if="notification.type === 'renote'" class="ph-repeat-bold"></i> <i v-else-if="notification.type === 'renote'" class="ph-repeat ph-bold"></i>
<i v-else-if="notification.type === 'reply'" class="ph-arrow-bend-up-left-bold"></i> <i v-else-if="notification.type === 'reply'" class="ph-arrow-bend-up-left ph-bold"></i>
<i v-else-if="notification.type === 'mention'" class="ph-at-bold"></i> <i v-else-if="notification.type === 'mention'" class="ph-at ph-bold"></i>
<i v-else-if="notification.type === 'quote'" class="ph-quotes-bold"></i> <i v-else-if="notification.type === 'quote'" class="ph-quotes ph-bold"></i>
<i v-else-if="notification.type === 'pollVote'" class="ph-microphone-stage-bold"></i> <i v-else-if="notification.type === 'pollVote'" class="ph-microphone-stage ph-bold"></i>
<i v-else-if="notification.type === 'pollEnded'" class="ph-microphone-stage-bold"></i> <i v-else-if="notification.type === 'pollEnded'" class="ph-microphone-stage ph-bold"></i>
<!-- notification.reaction null になることはまずないがここでoptional chaining使うと一部ブラウザで刺さるので念の為 --> <!-- notification.reaction null になることはまずないがここでoptional chaining使うと一部ブラウザで刺さるので念の為 -->
<XReactionIcon <XReactionIcon
v-else-if="notification.type === 'reaction'" v-else-if="notification.type === 'reaction'"
@ -33,14 +33,14 @@
<MkTime v-if="withTime" :time="notification.createdAt" class="time"/> <MkTime v-if="withTime" :time="notification.createdAt" class="time"/>
</header> </header>
<MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> <MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<i class="ph-quotes-fill ph-lg"></i> <i class="ph-quotes ph-fill ph-lg"></i>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<i class="ph-quotes-fill ph-lg"></i> <i class="ph-quotes ph-fill ph-lg"></i>
</MkA> </MkA>
<MkA v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)"> <MkA v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
<i class="ph-quotes-fill ph-lg"></i> <i class="ph-quotes ph-fill ph-lg"></i>
<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.renote.emojis"/> <Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.renote.emojis"/>
<i class="ph-quotes-fill ph-lg"></i> <i class="ph-quotes ph-fill ph-lg"></i>
</MkA> </MkA>
<MkA v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> <MkA v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
@ -52,14 +52,14 @@
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
</MkA> </MkA>
<MkA v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> <MkA v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<i class="ph-quotes-fill ph-lg"></i> <i class="ph-quotes ph-fill ph-lg"></i>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<i class="ph-quotes-fill ph-lg"></i> <i class="ph-quotes ph-fill ph-lg"></i>
</MkA> </MkA>
<MkA v-if="notification.type === 'pollEnded'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> <MkA v-if="notification.type === 'pollEnded'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<i class="ph-quotes-fill ph-lg"></i> <i class="ph-quotes ph-fill ph-lg"></i>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<i class="ph-quotes-fill ph-lg"></i> <i class="ph-quotes ph-fill ph-lg"></i>
</MkA> </MkA>
<span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span> <span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span>
<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span> <span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>

View file

@ -57,7 +57,7 @@ const buttonsLeft = $computed(() => {
if (history.length > 1) { if (history.length > 1) {
buttons.push({ buttons.push({
icon: 'ph-caret-left-bold ph-lg', icon: 'ph-caret-left ph-bold ph-lg',
onClick: back, onClick: back,
}); });
} }
@ -66,7 +66,7 @@ const buttonsLeft = $computed(() => {
}); });
const buttonsRight = $computed(() => { const buttonsRight = $computed(() => {
const buttons = [{ const buttons = [{
icon: 'ph-arrows-out-simple-bold ph-lg', icon: 'ph-arrows-out-simple ph-bold ph-lg',
title: i18n.ts.showInPage, title: i18n.ts.showInPage,
onClick: expand, onClick: expand,
}]; }];
@ -86,22 +86,22 @@ provide('shouldOmitHeaderTitle', true);
provide('shouldHeaderThin', true); provide('shouldHeaderThin', true);
const contextmenu = $computed(() => ([{ const contextmenu = $computed(() => ([{
icon: 'ph-arrows-out-simple-bold ph-lg', icon: 'ph-arrows-out-simple ph-bold ph-lg',
text: i18n.ts.showInPage, text: i18n.ts.showInPage,
action: expand, action: expand,
}, { }, {
icon: 'ph-arrow-square-out-bold ph-lg', icon: 'ph-arrow-square-out ph-bold ph-lg',
text: i18n.ts.popout, text: i18n.ts.popout,
action: popout, action: popout,
}, { }, {
icon: 'ph-arrow-square-out-bold ph-lg', icon: 'ph-arrow-square-out ph-bold ph-lg',
text: i18n.ts.openInNewTab, text: i18n.ts.openInNewTab,
action: () => { action: () => {
window.open(url + router.getCurrentPath(), '_blank'); window.open(url + router.getCurrentPath(), '_blank');
windowEl.close(); windowEl.close();
}, },
}, { }, {
icon: 'ph-link-simple-bold ph-lg', icon: 'ph-link-simple ph-bold ph-lg',
text: i18n.ts.copyLink, text: i18n.ts.copyLink,
action: () => { action: () => {
copyToClipboard(url + router.getCurrentPath()); copyToClipboard(url + router.getCurrentPath());

View file

@ -4,7 +4,7 @@
<li v-for="(choice, i) in note.poll.choices" :key="i" :class="{ voted: choice.voted }" @click.stop="vote(i)"> <li v-for="(choice, i) in note.poll.choices" :key="i" :class="{ voted: choice.voted }" @click.stop="vote(i)">
<div class="backdrop" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div> <div class="backdrop" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div>
<span> <span>
<template v-if="choice.isVoted"><i class="ph-check-bold ph-lg"></i></template> <template v-if="choice.isVoted"><i class="ph-check ph-bold ph-lg"></i></template>
<Mfm :text="choice.text" :plain="true" :custom-emojis="note.emojis"/> <Mfm :text="choice.text" :plain="true" :custom-emojis="note.emojis"/>
<span v-if="showResult" class="votes">({{ i18n.t('_poll.votesCount', { n: choice.votes }) }})</span> <span v-if="showResult" class="votes">({{ i18n.t('_poll.votesCount', { n: choice.votes }) }})</span>
</span> </span>

View file

@ -1,14 +1,14 @@
<template> <template>
<div class="zmdxowus"> <div class="zmdxowus">
<p v-if="choices.length < 2" class="caution"> <p v-if="choices.length < 2" class="caution">
<i class="ph-warning-bold ph-lg"></i>{{ i18n.ts._poll.noOnlyOneChoice }} <i class="ph-warning ph-bold ph-lg"></i>{{ i18n.ts._poll.noOnlyOneChoice }}
</p> </p>
<ul> <ul>
<li v-for="(choice, i) in choices" :key="i"> <li v-for="(choice, i) in choices" :key="i">
<MkInput class="input" small :model-value="choice" :placeholder="i18n.t('_poll.choiceN', { n: i + 1 })" @update:modelValue="onInput(i, $event)"> <MkInput class="input" small :model-value="choice" :placeholder="i18n.t('_poll.choiceN', { n: i + 1 })" @update:modelValue="onInput(i, $event)">
</MkInput> </MkInput>
<button class="_button" @click="remove(i)"> <button class="_button" @click="remove(i)">
<i class="ph-x-bold ph-lg"></i> <i class="ph-x ph-bold ph-lg"></i>
</button> </button>
</li> </li>
</ul> </ul>

View file

@ -8,35 +8,35 @@
@drop.stop="onDrop" @drop.stop="onDrop"
> >
<header> <header>
<button v-if="!fixed" class="cancel _button" @click="cancel"><i class="ph-x-bold ph-lg"></i></button> <button v-if="!fixed" class="cancel _button" @click="cancel"><i class="ph-x ph-bold ph-lg"></i></button>
<button v-click-anime v-tooltip="i18n.ts.switchAccount" class="account _button" @click="openAccountMenu"> <button v-click-anime v-tooltip="i18n.ts.switchAccount" class="account _button" @click="openAccountMenu">
<MkAvatar :user="postAccount ?? $i" class="avatar"/> <MkAvatar :user="postAccount ?? $i" class="avatar"/>
</button> </button>
<div class="right"> <div class="right">
<span class="text-count" :class="{ over: textLength > maxTextLength }">{{ maxTextLength - textLength }}</span> <span class="text-count" :class="{ over: textLength > maxTextLength }">{{ maxTextLength - textLength }}</span>
<span v-if="localOnly" class="local-only"><i class="ph-hand-fist-bold ph-lg"></i></span> <span v-if="localOnly" class="local-only"><i class="ph-hand-fist ph-bold ph-lg"></i></span>
<button ref="visibilityButton" v-tooltip="i18n.ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> <button ref="visibilityButton" v-tooltip="i18n.ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility">
<span v-if="visibility === 'public'"><i class="ph-planet-bold ph-lg"></i></span> <span v-if="visibility === 'public'"><i class="ph-planet ph-bold ph-lg"></i></span>
<span v-if="visibility === 'home'"><i class="ph-house-bold ph-lg"></i></span> <span v-if="visibility === 'home'"><i class="ph-house ph-bold ph-lg"></i></span>
<span v-if="visibility === 'followers'"><i class="ph-lock-simple-open-bold ph-lg"></i></span> <span v-if="visibility === 'followers'"><i class="ph-lock-simple-open ph-bold ph-lg"></i></span>
<span v-if="visibility === 'specified'"><i class="ph-envelope-simple-open-bold ph-lg"></i></span> <span v-if="visibility === 'specified'"><i class="ph-envelope-simple-open ph-bold ph-lg"></i></span>
</button> </button>
<button v-tooltip="i18n.ts.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="ph-file-code-bold ph-lg"></i></button> <button v-tooltip="i18n.ts.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="ph-file-code ph-bold ph-lg"></i></button>
<button class="submit _buttonGradate" :disabled="!canPost" data-cy-open-post-form-submit @click="post">{{ submitText }}<i :class="reply ? 'ph-arrow-bend-up-left-bold ph-lg' : renote ? 'ph-quotes-bold ph-lg' : 'ph-paper-plane-tilt-bold ph-lg'"></i></button> <button class="submit _buttonGradate" :disabled="!canPost" data-cy-open-post-form-submit @click="post">{{ submitText }}<i :class="reply ? 'ph-arrow-bend-up-left ph-bold ph-lg' : renote ? 'ph-quotes ph-bold ph-lg' : 'ph-paper-plane-tilt ph-bold ph-lg'"></i></button>
</div> </div>
</header> </header>
<div class="form" :class="{ fixed }"> <div class="form" :class="{ fixed }">
<XNoteSimple v-if="reply" class="preview" :note="reply"/> <XNoteSimple v-if="reply" class="preview" :note="reply"/>
<XNoteSimple v-if="renote" class="preview" :note="renote"/> <XNoteSimple v-if="renote" class="preview" :note="renote"/>
<div v-if="quoteId" class="with-quote"><i class="ph-quotes-bold ph-lg"></i> {{ i18n.ts.quoteAttached }}<button @click="quoteId = null"><i class="ph-x-bold ph-lg"></i></button></div> <div v-if="quoteId" class="with-quote"><i class="ph-quotes ph-bold ph-lg"></i> {{ i18n.ts.quoteAttached }}<button @click="quoteId = null"><i class="ph-x ph-bold ph-lg"></i></button></div>
<div v-if="visibility === 'specified'" class="to-specified"> <div v-if="visibility === 'specified'" class="to-specified">
<span style="margin-right: 8px;">{{ i18n.ts.recipient }}</span> <span style="margin-right: 8px;">{{ i18n.ts.recipient }}</span>
<div class="visibleUsers"> <div class="visibleUsers">
<span v-for="u in visibleUsers" :key="u.id"> <span v-for="u in visibleUsers" :key="u.id">
<MkAcct :user="u"/> <MkAcct :user="u"/>
<button class="_button" @click="removeVisibleUser(u)"><i class="ph-x-bold ph-lg"></i></button> <button class="_button" @click="removeVisibleUser(u)"><i class="ph-x ph-bold ph-lg"></i></button>
</span> </span>
<button class="_button" @click="addVisibleUser"><i class="ph-plus-bold ph-md ph-fw ph-lg"></i></button> <button class="_button" @click="addVisibleUser"><i class="ph-plus ph-bold ph-md ph-fw ph-lg"></i></button>
</div> </div>
</div> </div>
<MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo> <MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
@ -47,14 +47,14 @@
<XPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/> <XPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
<XNotePreview v-if="showPreview" class="preview" :text="text"/> <XNotePreview v-if="showPreview" class="preview" :text="text"/>
<footer> <footer>
<button v-tooltip="i18n.ts.attachFile" class="_button" @click="chooseFileFrom"><i class="ph-upload-bold ph-lg"></i></button> <button v-tooltip="i18n.ts.attachFile" class="_button" @click="chooseFileFrom"><i class="ph-upload ph-bold ph-lg"></i></button>
<button v-tooltip="i18n.ts.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="ph-microphone-stage-bold ph-lg"></i></button> <button v-tooltip="i18n.ts.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="ph-microphone-stage ph-bold ph-lg"></i></button>
<button v-tooltip="i18n.ts.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="ph-eye-slash-bold ph-lg"></i></button> <button v-tooltip="i18n.ts.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="ph-eye-slash ph-bold ph-lg"></i></button>
<button v-tooltip="i18n.ts.mention" class="_button" @click="insertMention"><i class="ph-at-bold ph-lg"></i></button> <button v-tooltip="i18n.ts.mention" class="_button" @click="insertMention"><i class="ph-at ph-bold ph-lg"></i></button>
<button v-tooltip="i18n.ts.hashtags" class="_button" :class="{ active: withHashtags }" @click="withHashtags = !withHashtags"><i class="ph-hash-bold ph-lg"></i></button> <button v-tooltip="i18n.ts.hashtags" class="_button" :class="{ active: withHashtags }" @click="withHashtags = !withHashtags"><i class="ph-hash ph-bold ph-lg"></i></button>
<button v-tooltip="i18n.ts.emoji" class="_button" @click="insertEmoji"><i class="ph-smiley-bold ph-lg"></i></button> <button v-tooltip="i18n.ts.emoji" class="_button" @click="insertEmoji"><i class="ph-smiley ph-bold ph-lg"></i></button>
<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" @click="showActions"><i class="ph-plug-bold ph-lg"></i></button> <button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" @click="showActions"><i class="ph-plug ph-bold ph-lg"></i></button>
<button v-tooltip="i18n.ts._mfm.cheatSheet" class="_button right" @click="openCheatSheet"><i class="ph-question-bold ph-lg"></i></button> <button v-tooltip="i18n.ts._mfm.cheatSheet" class="_button right" @click="openCheatSheet"><i class="ph-question ph-bold ph-lg"></i></button>
</footer> </footer>
<datalist id="hashtags"> <datalist id="hashtags">
<option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/> <option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/>

View file

@ -5,7 +5,7 @@
<div class="file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)"> <div class="file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)">
<MkDriveFileThumbnail :data-id="element.id" class="thumbnail" :file="element" fit="cover"/> <MkDriveFileThumbnail :data-id="element.id" class="thumbnail" :file="element" fit="cover"/>
<div v-if="element.isSensitive" class="sensitive"> <div v-if="element.isSensitive" class="sensitive">
<i class="ph-warning-bold ph-lg icon"></i> <i class="ph-warning ph-bold ph-lg icon"></i>
</div> </div>
</div> </div>
</template> </template>
@ -115,19 +115,19 @@ export default defineComponent({
if (this.menu) return; if (this.menu) return;
this.menu = os.popupMenu([{ this.menu = os.popupMenu([{
text: i18n.ts.renameFile, text: i18n.ts.renameFile,
icon: 'ph-cursor-text-bold ph-lg', icon: 'ph-cursor-text ph-bold ph-lg',
action: () => { this.rename(file); }, action: () => { this.rename(file); },
}, { }, {
text: file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, text: file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
icon: file.isSensitive ? 'ph-eye-slash-bold ph-lg' : 'ph-eye-bold ph-lg', icon: file.isSensitive ? 'ph-eye-slash ph-bold ph-lg' : 'ph-eye ph-bold ph-lg',
action: () => { this.toggleSensitive(file); }, action: () => { this.toggleSensitive(file); },
}, { }, {
text: i18n.ts.describeFile, text: i18n.ts.describeFile,
icon: 'ph-cursor-text-bold ph-lg', icon: 'ph-cursor-text ph-bold ph-lg',
action: () => { this.describe(file); }, action: () => { this.describe(file); },
}, { }, {
text: i18n.ts.attachCancel, text: i18n.ts.attachCancel,
icon: 'ph-circle-wavy-warning-bold ph-lg', icon: 'ph-circle-wavy-warning ph-bold ph-lg',
action: () => { this.detachMedia(file.id); }, action: () => { this.detachMedia(file.id); },
}], ev.currentTarget ?? ev.target).then(() => this.menu = null); }], ev.currentTarget ?? ev.target).then(() => this.menu = null);
}, },

View file

@ -5,7 +5,7 @@
class="eddddedb _button" class="eddddedb _button"
@click="quote()" @click="quote()"
> >
<i class="ph-quotes-bold ph-lg"></i> <i class="ph-quotes ph-bold ph-lg"></i>
</button> </button>
</template> </template>

View file

@ -6,11 +6,11 @@
class="eddddedb _button canRenote" class="eddddedb _button canRenote"
@click="renote(false, $event)" @click="renote(false, $event)"
> >
<i class="ph-repeat-bold ph-lg"></i> <i class="ph-repeat ph-bold ph-lg"></i>
<p v-if="count > 0" class="count">{{ count }}</p> <p v-if="count > 0" class="count">{{ count }}</p>
</button> </button>
<button v-else class="eddddedb _button"> <button v-else class="eddddedb _button">
<i class="ph-prohibit-bold ph-lg"></i> <i class="ph-prohibit ph-bold ph-lg"></i>
</button> </button>
</template> </template>
@ -66,7 +66,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
let buttonActions = [{ let buttonActions = [{
text: i18n.ts.renote, text: i18n.ts.renote,
icon: 'ph-repeat-bold ph-lg', icon: 'ph-repeat ph-bold ph-lg',
danger: false, danger: false,
action: () => { action: () => {
os.api('notes/create', { os.api('notes/create', {
@ -86,7 +86,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
if (!defaultStore.state.seperateRenoteQuote) { if (!defaultStore.state.seperateRenoteQuote) {
buttonActions.push({ buttonActions.push({
text: i18n.ts.quote, text: i18n.ts.quote,
icon: 'ph-quotes-bold ph-lg', icon: 'ph-quotes ph-bold ph-lg',
danger: false, danger: false,
action: () => { action: () => {
os.post({ os.post({
@ -99,7 +99,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
if (hasRenotedBefore) { if (hasRenotedBefore) {
buttonActions.push({ buttonActions.push({
text: i18n.ts.unrenote, text: i18n.ts.unrenote,
icon: 'ph-trash-bold ph-lg', icon: 'ph-trash ph-bold ph-lg',
danger: true, danger: true,
action: () => { action: () => {
os.api('notes/unrenote', { os.api('notes/unrenote', {

View file

@ -11,7 +11,7 @@
<template #suffix>@{{ host }}</template> <template #suffix>@{{ host }}</template>
</MkInput> </MkInput>
<MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="i18n.ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password> <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="i18n.ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password>
<template #prefix><i class="ph-lock-bold ph-lg"></i></template> <template #prefix><i class="ph-lock ph-bold ph-lg"></i></template>
<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template> <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
</MkInput> </MkInput>
<MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton> <MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
@ -30,20 +30,20 @@
<p style="margin-bottom:0;">{{ i18n.ts.twoStepAuthentication }}</p> <p style="margin-bottom:0;">{{ i18n.ts.twoStepAuthentication }}</p>
<MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" :with-password-toggle="true" required> <MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" :with-password-toggle="true" required>
<template #label>{{ i18n.ts.password }}</template> <template #label>{{ i18n.ts.password }}</template>
<template #prefix><i class="ph-lock-bold ph-lg"></i></template> <template #prefix><i class="ph-lock ph-bold ph-lg"></i></template>
</MkInput> </MkInput>
<MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" :spellcheck="false" required> <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" :spellcheck="false" required>
<template #label>{{ i18n.ts.token }}</template> <template #label>{{ i18n.ts.token }}</template>
<template #prefix><i class="ph-poker-chip-bold ph-lg"></i></template> <template #prefix><i class="ph-poker-chip ph-bold ph-lg"></i></template>
</MkInput> </MkInput>
<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton> <MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
</div> </div>
</div> </div>
</div> </div>
<div class="social _section"> <div class="social _section">
<a v-if="meta && meta.enableTwitterIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/twitter`"><i class="ph-twitter-logo-bold ph-lg" style="margin-right: 4px;"></i>{{ i18n.t('signinWith', { x: 'Twitter' }) }}</a> <a v-if="meta && meta.enableTwitterIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/twitter`"><i class="ph-twitter-logo ph-bold ph-lg" style="margin-right: 4px;"></i>{{ i18n.t('signinWith', { x: 'Twitter' }) }}</a>
<a v-if="meta && meta.enableGithubIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/github`"><i class="ph-github-logo-bold ph-lg" style="margin-right: 4px;"></i>{{ i18n.t('signinWith', { x: 'GitHub' }) }}</a> <a v-if="meta && meta.enableGithubIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/github`"><i class="ph-github-logo ph-bold ph-lg" style="margin-right: 4px;"></i>{{ i18n.t('signinWith', { x: 'GitHub' }) }}</a>
<a v-if="meta && meta.enableDiscordIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/discord`"><i class="ph-discord-logo-bold ph-lg" style="margin-right: 4px;"></i>{{ i18n.t('signinWith', { x: 'Discord' }) }}</a> <a v-if="meta && meta.enableDiscordIntegration" class="_borderButton _gap" :href="`${apiUrl}/signin/discord`"><i class="ph-discord-logo ph-bold ph-lg" style="margin-right: 4px;"></i>{{ i18n.t('signinWith', { x: 'Discord' }) }}</a>
</div> </div>
</form> </form>
</template> </template>

View file

@ -2,53 +2,53 @@
<form class="qlvuhzng _formRoot" autocomplete="new-password" @submit.prevent="onSubmit"> <form class="qlvuhzng _formRoot" autocomplete="new-password" @submit.prevent="onSubmit">
<MkInput v-if="instance.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" :spellcheck="false" required data-cy-signup-invitation-code @update:modelValue="onChangeInvitationCode"> <MkInput v-if="instance.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" :spellcheck="false" required data-cy-signup-invitation-code @update:modelValue="onChangeInvitationCode">
<template #label>{{ i18n.ts.invitationCode }}</template> <template #label>{{ i18n.ts.invitationCode }}</template>
<template #prefix><i class="ph-key-bold ph-lg"></i></template> <template #prefix><i class="ph-key ph-bold ph-lg"></i></template>
</MkInput> </MkInput>
<div v-if="!instance.disableRegistration || (instance.disableRegistration && invitationState === 'entered')"> <div v-if="!instance.disableRegistration || (instance.disableRegistration && invitationState === 'entered')">
<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername"> <MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername">
<template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="ph-question-bold"></i></div></template> <template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="ph-question ph-bold"></i></div></template>
<template #prefix>@</template> <template #prefix>@</template>
<template #suffix>@{{ host }}</template> <template #suffix>@{{ host }}</template>
<template #caption> <template #caption>
<span v-if="usernameState === 'wait'" style="color:#6e6a86"><i class="ph-circle-notch-bold ph-lg fa-pulse ph-fw ph-lg"></i> {{ i18n.ts.checking }}</span> <span v-if="usernameState === 'wait'" style="color:#6e6a86"><i class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"></i> {{ i18n.ts.checking }}</span>
<span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.available }}</span> <span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="ph-check ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.available }}</span>
<span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.unavailable }}</span> <span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.unavailable }}</span>
<span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.error }}</span> <span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.error }}</span>
<span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.usernameInvalidFormat }}</span> <span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.usernameInvalidFormat }}</span>
<span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.tooShort }}</span> <span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.tooShort }}</span>
<span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.tooLong }}</span> <span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.tooLong }}</span>
</template> </template>
</MkInput> </MkInput>
<MkInput v-if="instance.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail"> <MkInput v-if="instance.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail">
<template #label>{{ i18n.ts.emailAddress }} <div v-tooltip:dialog="i18n.ts._signup.emailAddressInfo" class="_button _help"><i class="ph-question-bold"></i></div></template> <template #label>{{ i18n.ts.emailAddress }} <div v-tooltip:dialog="i18n.ts._signup.emailAddressInfo" class="_button _help"><i class="ph-question ph-bold"></i></div></template>
<template #prefix><i class="ph-envelope-simple-open-bold ph-lg"></i></template> <template #prefix><i class="ph-envelope-simple-open ph-bold ph-lg"></i></template>
<template #caption> <template #caption>
<span v-if="emailState === 'wait'" style="color:#6e6a86"><i class="ph-circle-notch-bold ph-lg fa-pulse ph-fw ph-lg"></i> {{ i18n.ts.checking }}</span> <span v-if="emailState === 'wait'" style="color:#6e6a86"><i class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"></i> {{ i18n.ts.checking }}</span>
<span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.available }}</span> <span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="ph-check ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.available }}</span>
<span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.used }}</span> <span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.used }}</span>
<span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.format }}</span> <span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.format }}</span>
<span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.disposable }}</span> <span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.disposable }}</span>
<span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.mx }}</span> <span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.mx }}</span>
<span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.smtp }}</span> <span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.smtp }}</span>
<span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.unavailable }}</span> <span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.unavailable }}</span>
<span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.error }}</span> <span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.error }}</span>
</template> </template>
</MkInput> </MkInput>
<MkInput v-model="password" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword"> <MkInput v-model="password" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword">
<template #label>{{ i18n.ts.password }}</template> <template #label>{{ i18n.ts.password }}</template>
<template #prefix><i class="ph-lock-bold ph-lg"></i></template> <template #prefix><i class="ph-lock ph-bold ph-lg"></i></template>
<template #caption> <template #caption>
<span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.weakPassword }}</span> <span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.weakPassword }}</span>
<span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.normalPassword }}</span> <span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="ph-check ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.normalPassword }}</span>
<span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.strongPassword }}</span> <span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="ph-check ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.strongPassword }}</span>
</template> </template>
</MkInput> </MkInput>
<MkInput v-model="retypedPassword" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype"> <MkInput v-model="retypedPassword" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype">
<template #label>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template> <template #label>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template>
<template #prefix><i class="ph-lock-bold ph-lg"></i></template> <template #prefix><i class="ph-lock ph-bold ph-lg"></i></template>
<template #caption> <template #caption>
<span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.passwordMatched }}</span> <span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="ph-check ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.passwordMatched }}</span>
<span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.passwordNotMatched }}</span> <span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.passwordNotMatched }}</span>
</template> </template>
</MkInput> </MkInput>
<MkSwitch v-if="instance.tosUrl" v-model="ToSAgreement" class="_formBlock tou"> <MkSwitch v-if="instance.tosUrl" v-model="ToSAgreement" class="_formBlock tou">

View file

@ -6,9 +6,9 @@
<path d="m23.186 29.526c-0.993 0-1.952-0.455-2.788-1.339-2.816-2.985-3.569-2.333-4.817-1.251-0.781 0.679-1.754 1.523-3.205 1.523-2.351 0-3.969-2.302-4.036-2.4-0.314-0.454-0.2-1.077 0.254-1.391 0.451-0.312 1.074-0.2 1.39 0.251 0.301 0.429 1.317 1.54 2.393 1.54 0.704 0 1.256-0.479 1.895-1.033 1.816-1.578 3.764-2.655 7.583 1.388 0.823 0.873 1.452 0.774 1.908 0.592 1.659-0.665 3.205-3.698 3.197-5.15-3e-3 -0.552 0.442-1.002 0.994-1.005h6e-3c0.55 0 0.997 0.444 1 0.995 0.012 2.103-1.854 5.976-4.454 7.017-0.443 0.175-0.885 0.262-1.32 0.263z"/> <path d="m23.186 29.526c-0.993 0-1.952-0.455-2.788-1.339-2.816-2.985-3.569-2.333-4.817-1.251-0.781 0.679-1.754 1.523-3.205 1.523-2.351 0-3.969-2.302-4.036-2.4-0.314-0.454-0.2-1.077 0.254-1.391 0.451-0.312 1.074-0.2 1.39 0.251 0.301 0.429 1.317 1.54 2.393 1.54 0.704 0 1.256-0.479 1.895-1.033 1.816-1.578 3.764-2.655 7.583 1.388 0.823 0.873 1.452 0.774 1.908 0.592 1.659-0.665 3.205-3.698 3.197-5.15-3e-3 -0.552 0.442-1.002 0.994-1.005h6e-3c0.55 0 0.997 0.444 1 0.995 0.012 2.103-1.854 5.976-4.454 7.017-0.443 0.175-0.885 0.262-1.32 0.263z"/>
<path d="m14.815 15.375c-0.584 2.114-1.642 3.083-3.152 2.666-1.509-0.417-2.343-1.909-1.76-4.023 0.583-2.112 2.175-3.363 3.684-2.946 1.511 0.417 1.812 2.19 1.228 4.303zm11.416-0.755c0.473 2.141-0.675 4.838-2.204 5.176s-3.28-1.719-3.753-3.86c-0.473-2.14 0.419-3.971 1.948-4.309s3.536 0.853 4.009 2.993z"/> <path d="m14.815 15.375c-0.584 2.114-1.642 3.083-3.152 2.666-1.509-0.417-2.343-1.909-1.76-4.023 0.583-2.112 2.175-3.363 3.684-2.946 1.511 0.417 1.812 2.19 1.228 4.303zm11.416-0.755c0.473 2.141-0.675 4.838-2.204 5.176s-3.28-1.719-3.753-3.86c-0.473-2.14 0.419-3.971 1.948-4.309s3.536 0.853 4.009 2.993z"/>
</g></svg> </g></svg>
<i v-else-if="instance.defaultReaction === '👍'" class="ph-thumbs-up-bold ph-lg"></i> <i v-else-if="instance.defaultReaction === '👍'" class="ph-thumbs-up ph-bold ph-lg"></i>
<i v-else-if="instance.defaultReaction === ''" class="ph-heart-bold ph-lg"></i> <i v-else-if="instance.defaultReaction === ''" class="ph-heart ph-bold ph-lg"></i>
<i v-else class="ph-star-bold ph-lg"></i> <i v-else class="ph-star ph-bold ph-lg"></i>
</button> </button>
</template> </template>

View file

@ -2,7 +2,7 @@
<div class="wrmlmaau" :class="{ collapsed, isLong }"> <div class="wrmlmaau" :class="{ collapsed, isLong }">
<div class="body"> <div class="body">
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span> <span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ph-arrow-bend-up-left-bold ph-lg"></i></MkA> <MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ph-arrow-bend-up-left ph-bold ph-lg"></i></MkA>
<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :custom-emojis="note.emojis"/> <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :custom-emojis="note.emojis"/>
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">{{ i18n.ts.quoteAttached }}: ...</MkA> <MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">{{ i18n.ts.quoteAttached }}: ...</MkA>
</div> </div>

View file

@ -13,17 +13,17 @@
<div class="_footer navigation"> <div class="_footer navigation">
<div class="step"> <div class="step">
<button class="arrow _button" :disabled="tutorial === 0" @click="tutorial--"> <button class="arrow _button" :disabled="tutorial === 0" @click="tutorial--">
<i class="ph-caret-left-bold ph-lg"></i> <i class="ph-caret-left ph-bold ph-lg"></i>
</button> </button>
<span>{{ tutorial + 1 }} / 6</span> <span>{{ tutorial + 1 }} / 6</span>
<button class="arrow _button" :disabled="tutorial === 5" @click="tutorial++"> <button class="arrow _button" :disabled="tutorial === 5" @click="tutorial++">
<i class="ph-caret-right-bold ph-lg"></i> <i class="ph-caret-right ph-bold ph-lg"></i>
</button> </button>
</div> </div>
<MkButton v-if="tutorial === 5" class="ok" primary @click="close"><i class="ph-check-bold ph-lg"></i> {{ i18n.ts.gotIt }}</MkButton> <MkButton v-if="tutorial === 5" class="ok" primary @click="close"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts.gotIt }}</MkButton>
<MkButton v-else class="ok" primary @click="tutorial++"><i class="ph-check-bold ph-lg"></i> {{ i18n.ts.next }}</MkButton> <MkButton v-else class="ok" primary @click="tutorial++"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts.next }}</MkButton>
</div> </div>
<h2 class="_title title"><i class="ph-info-bold ph-lg"></i> {{ i18n.ts._tutorial.title }}</h2> <h2 class="_title title"><i class="ph-info ph-bold ph-lg"></i> {{ i18n.ts._tutorial.title }}</h2>
<Transition name="fade"> <Transition name="fade">
<div v-if="tutorial === 0" key="1" class="_content"> <div v-if="tutorial === 0" key="1" class="_content">
<h3>{{ i18n.ts._tutorial.step1_1 }}</h3> <h3>{{ i18n.ts._tutorial.step1_1 }}</h3>
@ -41,7 +41,7 @@
<div>{{ i18n.ts._tutorial.step3_2 }}</div> <div>{{ i18n.ts._tutorial.step3_2 }}</div>
<XFeaturedUsers/> <XFeaturedUsers/>
<br/> <br/>
<MkButton class="ok" primary @click="tutorial++"><i class="ph-check-bold ph-lg"></i> {{ i18n.ts.next }}</MkButton> <MkButton class="ok" primary @click="tutorial++"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts.next }}</MkButton>
</div> </div>
<div v-else-if="tutorial === 3" key="4" class="_content"> <div v-else-if="tutorial === 3" key="4" class="_content">
<h3>{{ i18n.ts._tutorial.step4_1 }}</h3> <h3>{{ i18n.ts._tutorial.step4_1 }}</h3>
@ -64,35 +64,35 @@
<li> <li>
<I18n :src="i18n.ts._tutorial.step5_3" tag="div"> <I18n :src="i18n.ts._tutorial.step5_3" tag="div">
<template #icon> <template #icon>
<i class="ph-house-bold ph-lg"/> <i class="ph-house ph-bold ph-lg"/>
</template> </template>
</I18n> </I18n>
</li> </li>
<li v-if="timelines.includes('local')"> <li v-if="timelines.includes('local')">
<I18n :src="i18n.ts._tutorial.step5_4" tag="div"> <I18n :src="i18n.ts._tutorial.step5_4" tag="div">
<template #icon> <template #icon>
<i class="ph-users-bold ph-lg"/> <i class="ph-users ph-bold ph-lg"/>
</template> </template>
</I18n> </I18n>
</li> </li>
<li v-if="timelines.includes('recommended')"> <li v-if="timelines.includes('recommended')">
<I18n :src="i18n.ts._tutorial.step5_5" tag="div"> <I18n :src="i18n.ts._tutorial.step5_5" tag="div">
<template #icon> <template #icon>
<i class="ph-thumbs-up-bold ph-lg"/> <i class="ph-thumbs-up ph-bold ph-lg"/>
</template> </template>
</I18n> </I18n>
</li> </li>
<li v-if="timelines.includes('social')"> <li v-if="timelines.includes('social')">
<I18n :src="i18n.ts._tutorial.step5_6" tag="div"> <I18n :src="i18n.ts._tutorial.step5_6" tag="div">
<template #icon> <template #icon>
<i class="ph-handshake-bold ph-lg"/> <i class="ph-handshake ph-bold ph-lg"/>
</template> </template>
</I18n> </I18n>
</li> </li>
<li v-if="timelines.includes('global')"> <li v-if="timelines.includes('global')">
<I18n :src="i18n.ts._tutorial.step5_7" tag="div"> <I18n :src="i18n.ts._tutorial.step5_7" tag="div">
<template #icon> <template #icon>
<i class="ph-planet-bold ph-lg"/> <i class="ph-planet ph-bold ph-lg"/>
</template> </template>
</I18n> </I18n>
</li> </li>

View file

@ -1,6 +1,6 @@
<template> <template>
<div v-if="playerEnabled" class="player" :style="`padding: ${(player.height || 0) / (player.width || 1) * 100}% 0 0`" @click.stop> <div v-if="playerEnabled" class="player" :style="`padding: ${(player.height || 0) / (player.width || 1) * 100}% 0 0`" @click.stop>
<button class="disablePlayer" :title="i18n.ts.disablePlayer" @click="playerEnabled = false"><i class="ph-x-bold ph-lg"></i></button> <button class="disablePlayer" :title="i18n.ts.disablePlayer" @click="playerEnabled = false"><i class="ph-x ph-bold ph-lg"></i></button>
<iframe :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen/> <iframe :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen/>
</div> </div>
<div v-else-if="tweetId && tweetExpanded" ref="twitter" class="twitter" @click.stop> <div v-else-if="tweetId && tweetExpanded" ref="twitter" class="twitter" @click.stop>
@ -10,7 +10,7 @@
<transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in">
<component :is="self ? 'MkA' : 'a'" v-if="!fetching" class="link" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url"> <component :is="self ? 'MkA' : 'a'" v-if="!fetching" class="link" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url">
<div v-if="thumbnail" class="thumbnail" :style="`background-image: url('${thumbnail}')`"> <div v-if="thumbnail" class="thumbnail" :style="`background-image: url('${thumbnail}')`">
<button v-if="!playerEnabled && player.url" class="_button" :title="i18n.ts.enablePlayer" @click.prevent="playerEnabled = true"><i class="ph-play-circle-bold ph-lg"></i></button> <button v-if="!playerEnabled && player.url" class="_button" :title="i18n.ts.enablePlayer" @click.prevent="playerEnabled = true"><i class="ph-play-circle ph-bold ph-lg"></i></button>
</div> </div>
<article> <article>
<header> <header>
@ -26,7 +26,7 @@
</transition> </transition>
<div v-if="tweetId" class="expandTweet"> <div v-if="tweetId" class="expandTweet">
<a @click="tweetExpanded = true"> <a @click="tweetExpanded = true">
<i class="ph-twitter-logo-bold ph-lg"></i> {{ i18n.ts.expandTweet }} <i class="ph-twitter-logo ph-bold ph-lg"></i> {{ i18n.ts.expandTweet }}
</a> </a>
</div> </div>
</div> </div>

View file

@ -1,10 +1,10 @@
<template> <template>
<span v-if="note.visibility !== 'public'" :class="$style.visibility"> <span v-if="note.visibility !== 'public'" :class="$style.visibility">
<i v-if="note.visibility === 'home'" class="ph-house-bold ph-lg"></i> <i v-if="note.visibility === 'home'" class="ph-house ph-bold ph-lg"></i>
<i v-else-if="note.visibility === 'followers'" class="ph-lock-simple-open-bold ph-lg"></i> <i v-else-if="note.visibility === 'followers'" class="ph-lock-simple-open ph-bold ph-lg"></i>
<i v-else-if="note.visibility === 'specified'" ref="specified" class="ph-envelope-simple-open-bold ph-lg"></i> <i v-else-if="note.visibility === 'specified'" ref="specified" class="ph-envelope-simple-open ph-bold ph-lg"></i>
</span> </span>
<span v-if="note.localOnly" :class="$style.localOnly"><i class="ph-hand-fist-bold ph-lg"></i></span> <span v-if="note.localOnly" :class="$style.localOnly"><i class="ph-hand-fist ph-bold ph-lg"></i></span>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -2,28 +2,28 @@
<MkModal ref="modal" :z-priority="'high'" :src="src" @click="modal.close()" @closed="emit('closed')"> <MkModal ref="modal" :z-priority="'high'" :src="src" @click="modal.close()" @closed="emit('closed')">
<div class="_popup" :class="$style.root"> <div class="_popup" :class="$style.root">
<button key="public" class="_button" :class="[$style.item, { [$style.active]: v === 'public' }]" data-index="1" @click="choose('public')"> <button key="public" class="_button" :class="[$style.item, { [$style.active]: v === 'public' }]" data-index="1" @click="choose('public')">
<div :class="$style.icon"><i class="ph-planet-bold ph-lg"></i></div> <div :class="$style.icon"><i class="ph-planet ph-bold ph-lg"></i></div>
<div :class="$style.body"> <div :class="$style.body">
<span :class="$style.itemTitle">{{ i18n.ts._visibility.public }}</span> <span :class="$style.itemTitle">{{ i18n.ts._visibility.public }}</span>
<span :class="$style.itemDescription">{{ i18n.ts._visibility.publicDescription }}</span> <span :class="$style.itemDescription">{{ i18n.ts._visibility.publicDescription }}</span>
</div> </div>
</button> </button>
<button key="home" class="_button" :class="[$style.item, { [$style.active]: v === 'home' }]" data-index="2" @click="choose('home')"> <button key="home" class="_button" :class="[$style.item, { [$style.active]: v === 'home' }]" data-index="2" @click="choose('home')">
<div :class="$style.icon"><i class="ph-house-bold ph-lg"></i></div> <div :class="$style.icon"><i class="ph-house ph-bold ph-lg"></i></div>
<div :class="$style.body"> <div :class="$style.body">
<span :class="$style.itemTitle">{{ i18n.ts._visibility.home }}</span> <span :class="$style.itemTitle">{{ i18n.ts._visibility.home }}</span>
<span :class="$style.itemDescription">{{ i18n.ts._visibility.homeDescription }}</span> <span :class="$style.itemDescription">{{ i18n.ts._visibility.homeDescription }}</span>
</div> </div>
</button> </button>
<button key="followers" class="_button" :class="[$style.item, { [$style.active]: v === 'followers' }]" data-index="3" @click="choose('followers')"> <button key="followers" class="_button" :class="[$style.item, { [$style.active]: v === 'followers' }]" data-index="3" @click="choose('followers')">
<div :class="$style.icon"><i class="ph-lock-simple-open-bold ph-lg"></i></div> <div :class="$style.icon"><i class="ph-lock-simple-open ph-bold ph-lg"></i></div>
<div :class="$style.body"> <div :class="$style.body">
<span :class="$style.itemTitle">{{ i18n.ts._visibility.followers }}</span> <span :class="$style.itemTitle">{{ i18n.ts._visibility.followers }}</span>
<span :class="$style.itemDescription">{{ i18n.ts._visibility.followersDescription }}</span> <span :class="$style.itemDescription">{{ i18n.ts._visibility.followersDescription }}</span>
</div> </div>
</button> </button>
<button key="specified" :disabled="localOnly" class="_button" :class="[$style.item, { [$style.active]: v === 'specified' }]" data-index="4" @click="choose('specified')"> <button key="specified" :disabled="localOnly" class="_button" :class="[$style.item, { [$style.active]: v === 'specified' }]" data-index="4" @click="choose('specified')">
<div :class="$style.icon"><i class="ph-envelope-simple-open-bold ph-lg"></i></div> <div :class="$style.icon"><i class="ph-envelope-simple-open ph-bold ph-lg"></i></div>
<div :class="$style.body"> <div :class="$style.body">
<span :class="$style.itemTitle">{{ i18n.ts._visibility.specified }}</span> <span :class="$style.itemTitle">{{ i18n.ts._visibility.specified }}</span>
<span :class="$style.itemDescription">{{ i18n.ts._visibility.specifiedDescription }}</span> <span :class="$style.itemDescription">{{ i18n.ts._visibility.specifiedDescription }}</span>
@ -31,12 +31,12 @@
</button> </button>
<div :class="$style.divider"></div> <div :class="$style.divider"></div>
<button key="localOnly" class="_button" :class="[$style.item, $style.localOnly, { [$style.active]: localOnly }]" data-index="5" @click="localOnly = !localOnly"> <button key="localOnly" class="_button" :class="[$style.item, $style.localOnly, { [$style.active]: localOnly }]" data-index="5" @click="localOnly = !localOnly">
<div :class="$style.icon"><i class="ph-hand-fist-bold ph-lg"></i></div> <div :class="$style.icon"><i class="ph-hand-fist ph-bold ph-lg"></i></div>
<div :class="$style.body"> <div :class="$style.body">
<span :class="$style.itemTitle">{{ i18n.ts._visibility.localOnly }}</span> <span :class="$style.itemTitle">{{ i18n.ts._visibility.localOnly }}</span>
<span :class="$style.itemDescription">{{ i18n.ts._visibility.localOnlyDescription }}</span> <span :class="$style.itemDescription">{{ i18n.ts._visibility.localOnlyDescription }}</span>
</div> </div>
<div :class="$style.toggle"><i :class="localOnly ? 'ph-toggle-right-bold ph-lg' : 'ph-toggle-left-bold ph-lg'"></i></div> <div :class="$style.toggle"><i :class="localOnly ? 'ph-toggle-right ph-bold ph-lg' : 'ph-toggle-left ph-bold ph-lg'"></i></div>
</button> </button>
</div> </div>
</MkModal> </MkModal>

View file

@ -1,7 +1,7 @@
<template> <template>
<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="success ? done() : () => {}" @closed="emit('closed')"> <MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="success ? done() : () => {}" @closed="emit('closed')">
<div :class="[$style.root, { [$style.iconOnly]: (text == null) || success }]"> <div :class="[$style.root, { [$style.iconOnly]: (text == null) || success }]">
<i v-if="success" :class="[$style.icon, $style.success]" class="ph-check-bold ph-lg"></i> <i v-if="success" :class="[$style.icon, $style.success]" class="ph-check ph-bold ph-lg"></i>
<MkLoading v-else :class="[$style.icon, $style.waiting]" :em="true"/> <MkLoading v-else :class="[$style.icon, $style.waiting]" :em="true"/>
<div v-if="text && !success" :class="$style.text">{{ text }}<MkEllipsis/></div> <div v-if="text && !success" :class="$style.text">{{ text }}<MkEllipsis/></div>
</div> </div>

View file

@ -6,7 +6,7 @@
<template #label>{{ i18n.ts.selectWidget }}</template> <template #label>{{ i18n.ts.selectWidget }}</template>
<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.t(`_widgets.${widget}`) }}</option> <option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.t(`_widgets.${widget}`) }}</option>
</MkSelect> </MkSelect>
<MkButton inline primary class="mk-widget-add" @click="addWidget"><i class="ph-plus-bold ph-lg"></i> {{ i18n.ts.add }}</MkButton> <MkButton inline primary class="mk-widget-add" @click="addWidget"><i class="ph-plus ph-bold ph-lg"></i> {{ i18n.ts.add }}</MkButton>
<MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton> <MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton>
</header> </header>
<XDraggable <XDraggable
@ -17,8 +17,8 @@
> >
<template #item="{element}"> <template #item="{element}">
<div class="customize-container"> <div class="customize-container">
<button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="ph-gear-six-bold ph-lg"></i></button> <button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="ph-gear-six ph-bold ph-lg"></i></button>
<button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="ph-x-bold ph-lg"></i></button> <button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="ph-x ph-bold ph-lg"></i></button>
<div class="handle"> <div class="handle">
<component :is="`mkw-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :widget="element" @updateProps="updateWidget(element.id, $event)"/> <component :is="`mkw-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :widget="element" @updateProps="updateWidget(element.id, $event)"/>
</div> </div>
@ -104,7 +104,7 @@ function onContextmenu(widget: Widget, ev: MouseEvent) {
type: 'label', type: 'label',
text: i18n.t(`_widgets.${widget.name}`), text: i18n.t(`_widgets.${widget.name}`),
}, { }, {
icon: 'ph-gear-six-bold ph-lg', icon: 'ph-gear-six ph-bold ph-lg',
text: i18n.ts.settings, text: i18n.ts.settings,
action: () => { action: () => {
configWidget(widget.id); configWidget(widget.id);

View file

@ -11,9 +11,9 @@
</span> </span>
<span class="right"> <span class="right">
<button v-for="button in buttonsRight" v-tooltip="button.title" class="button _button" :class="{ highlighted: button.highlighted }" @click="button.onClick"><i :class="button.icon"></i></button> <button v-for="button in buttonsRight" v-tooltip="button.title" class="button _button" :class="{ highlighted: button.highlighted }" @click="button.onClick"><i :class="button.icon"></i></button>
<button v-if="canResize && maximized" class="button _button" @click="unMaximize()"><i class="ph-copy-bold ph-lg"></i></button> <button v-if="canResize && maximized" class="button _button" @click="unMaximize()"><i class="ph-copy ph-bold ph-lg"></i></button>
<button v-else-if="canResize && !maximized" class="button _button" @click="maximize()"><i class="ph-browser-bold ph-lg"></i></button> <button v-else-if="canResize && !maximized" class="button _button" @click="maximize()"><i class="ph-browser ph-bold ph-lg"></i></button>
<button v-if="closeButton" class="button _button" @click="close()"><i class="ph-x-bold ph-lg"></i></button> <button v-if="closeButton" class="button _button" @click="close()"><i class="ph-x ph-bold ph-lg"></i></button>
</span> </span>
</div> </div>
<div class="body"> <div class="body">

View file

@ -10,7 +10,7 @@
@keydown.enter="toggle" @keydown.enter="toggle"
> >
<span ref="button" v-adaptive-border v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" class="button" @click.prevent="toggle"> <span ref="button" v-adaptive-border v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" class="button" @click.prevent="toggle">
<i class="check ph-check-bold ph-lg"></i> <i class="check ph-check ph-bold ph-lg"></i>
</span> </span>
<span class="label"> <span class="label">
<!-- TODO: 無名slotの方は廃止 --> <!-- TODO: 無名slotの方は廃止 -->

View file

@ -5,8 +5,8 @@
<span class="text"><slot name="label"></slot></span> <span class="text"><slot name="label"></slot></span>
<span class="right"> <span class="right">
<span class="text"><slot name="suffix"></slot></span> <span class="text"><slot name="suffix"></slot></span>
<i v-if="opened" class="ph-caret-up-bold ph-lg icon"></i> <i v-if="opened" class="ph-caret-up ph-bold ph-lg icon"></i>
<i v-else class="ph-caret-down-bold ph-lg icon"></i> <i v-else class="ph-caret-down ph-bold ph-lg icon"></i>
</span> </span>
</div> </div>
<KeepAlive> <KeepAlive>

View file

@ -29,7 +29,7 @@
</div> </div>
<div class="caption"><slot name="caption"></slot></div> <div class="caption"><slot name="caption"></slot></div>
<MkButton v-if="manualSave && changed" primary class="save" @click="updated"><i class="ph-check-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton> <MkButton v-if="manualSave && changed" primary class="save" @click="updated"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton>
</div> </div>
</template> </template>

View file

@ -5,7 +5,7 @@
<span class="text"><slot></slot></span> <span class="text"><slot></slot></span>
<span class="right"> <span class="right">
<span class="text"><slot name="suffix"></slot></span> <span class="text"><slot name="suffix"></slot></span>
<i class="ph-arrow-square-out-bold ph-lg icon"></i> <i class="ph-arrow-square-out ph-bold ph-lg icon"></i>
</span> </span>
</a> </a>
<MkA v-else class="main _button" :class="{ active }" :to="to" :behavior="behavior"> <MkA v-else class="main _button" :class="{ active }" :to="to" :behavior="behavior">
@ -13,7 +13,7 @@
<span class="text"><slot></slot></span> <span class="text"><slot></slot></span>
<span class="right"> <span class="right">
<span class="text"><slot name="suffix"></slot></span> <span class="text"><slot name="suffix"></slot></span>
<i class="ph-caret-right-bold ph-lg icon"></i> <i class="ph-caret-right ph-bold ph-lg icon"></i>
</span> </span>
</MkA> </MkA>
</div> </div>

View file

@ -18,11 +18,11 @@
> >
<slot></slot> <slot></slot>
</select> </select>
<div ref="suffixEl" class="suffix"><i class="ph-caret-down-bold ph-lg"></i></div> <div ref="suffixEl" class="suffix"><i class="ph-caret-down ph-bold ph-lg"></i></div>
</div> </div>
<div class="caption"><slot name="caption"></slot></div> <div class="caption"><slot name="caption"></slot></div>
<MkButton v-if="manualSave && changed" primary @click="updated"><i class="ph-floppy-disk-back-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton> <MkButton v-if="manualSave && changed" primary @click="updated"><i class="ph-floppy-disk-back ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton>
</div> </div>
</template> </template>

View file

@ -8,8 +8,8 @@
</div> </div>
<div v-else> <div v-else>
<div class="wszdbhzo"> <div class="wszdbhzo">
<div><i class="ph-warning-bold ph-lg"></i> {{ i18n.ts.somethingHappened }}</div> <div><i class="ph-warning ph-bold ph-lg"></i> {{ i18n.ts.somethingHappened }}</div>
<MkButton inline class="retry" @click="retry"><i class="ph-arrow-clockwise-bold ph-lg"></i> {{ i18n.ts.retry }}</MkButton> <MkButton inline class="retry" @click="retry"><i class="ph-arrow-clockwise ph-bold ph-lg"></i> {{ i18n.ts.retry }}</MkButton>
</div> </div>
</div> </div>
</transition> </transition>

View file

@ -22,7 +22,7 @@
</div> </div>
<div class="caption"><slot name="caption"></slot></div> <div class="caption"><slot name="caption"></slot></div>
<MkButton v-if="manualSave && changed" primary class="save" @click="updated"><i class="ph-floppy-disk-back-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton> <MkButton v-if="manualSave && changed" primary class="save" @click="updated"><i class="ph-floppy-disk-back ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton>
</div> </div>
</template> </template>

View file

@ -41,25 +41,25 @@ function onContextmenu(ev) {
type: 'label', type: 'label',
text: props.to, text: props.to,
}, { }, {
icon: 'ph-browser-bold ph-lg', icon: 'ph-browser ph-bold ph-lg',
text: i18n.ts.openInWindow, text: i18n.ts.openInWindow,
action: () => { action: () => {
os.pageWindow(props.to); os.pageWindow(props.to);
}, },
}, { }, {
icon: 'ph-arrows-out-simple-bold ph-lg', icon: 'ph-arrows-out-simple ph-bold ph-lg',
text: i18n.ts.showInPage, text: i18n.ts.showInPage,
action: () => { action: () => {
router.push(props.to, 'forcePage'); router.push(props.to, 'forcePage');
}, },
}, null, { }, null, {
icon: 'ph-arrow-square-out-bold ph-lg', icon: 'ph-arrow-square-out ph-bold ph-lg',
text: i18n.ts.openInNewTab, text: i18n.ts.openInNewTab,
action: () => { action: () => {
window.open(props.to, '_blank'); window.open(props.to, '_blank');
}, },
}, { }, {
icon: 'ph-link-simple-bold ph-lg', icon: 'ph-link-simple ph-bold ph-lg',
text: i18n.ts.copyLink, text: i18n.ts.copyLink,
action: () => { action: () => {
copyToClipboard(`${url}${props.to}`); copyToClipboard(`${url}${props.to}`);

View file

@ -3,7 +3,7 @@
<div v-if="!showMenu" class="main" :class="chosen.place"> <div v-if="!showMenu" class="main" :class="chosen.place">
<a :href="chosen.url" target="_blank"> <a :href="chosen.url" target="_blank">
<img :src="chosen.imageUrl"> <img :src="chosen.imageUrl">
<button class="_button menu" @click.prevent.stop="toggleMenu"><span class="ph-info-bold ph-lg info-circle"></span></button> <button class="_button menu" @click.prevent.stop="toggleMenu"><span class="ph-info ph-bold ph-lg info-circle"></span></button>
</a> </a>
</div> </div>
<div v-else class="menu"> <div v-else class="menu">
@ -56,9 +56,12 @@ const choseAd = (): Ad | null => {
} }
const lowPriorityAds = ads.filter(ad => ad.ratio === 0); const lowPriorityAds = ads.filter(ad => ad.ratio === 0);
const widgetAds = ads.filter(ad => ad.place === 'widget');
ads = ads.filter(ad => ad.ratio !== 0); ads = ads.filter(ad => ad.ratio !== 0);
if (ads.length === 0) { if (widgetAds.length !== 0) {
return widgetAds;
} else if (ads.length === 0) {
if (lowPriorityAds.length !== 0) { if (lowPriorityAds.length !== 0) {
return lowPriorityAds[Math.floor(Math.random() * lowPriorityAds.length)]; return lowPriorityAds[Math.floor(Math.random() * lowPriorityAds.length)];
} else { } else {
@ -132,7 +135,7 @@ function reduceFrequency(): void {
} }
} }
&.square { &.widget {
> a , > a ,
> a > img { > a > img {
max-width: min(300px, 100%); max-width: min(300px, 100%);
@ -140,7 +143,7 @@ function reduceFrequency(): void {
} }
} }
&.horizontal { &.inline {
padding: 8px; padding: 8px;
> a , > a ,
@ -150,7 +153,7 @@ function reduceFrequency(): void {
} }
} }
&.horizontal-big { &.inline-big {
padding: 8px; padding: 8px;
> a , > a ,

View file

@ -2,7 +2,7 @@
<transition :name="$store.state.animation ? 'zoom' : ''" appear> <transition :name="$store.state.animation ? 'zoom' : ''" appear>
<div class="mjndxjcg"> <div class="mjndxjcg">
<img src="/static-assets/badges/error.png" class="_ghost" alt="Error"/> <img src="/static-assets/badges/error.png" class="_ghost" alt="Error"/>
<p><i class="ph-warning-bold ph-lg"></i> {{ i18n.ts.somethingHappened }}</p> <p><i class="ph-warning ph-bold ph-lg"></i> {{ i18n.ts.somethingHappened }}</p>
<MkButton class="button" @click="() => $emit('retry')">{{ i18n.ts.retry }}</MkButton> <MkButton class="button" @click="() => $emit('retry')">{{ i18n.ts.retry }}</MkButton>
</div> </div>
</transition> </transition>

View file

@ -220,9 +220,14 @@ onUnmounted(() => {
} }
> .tabs { > .tabs {
padding-inline: 12px; padding-inline: 12px;
mask: linear-gradient(to right, black 80%, transparent); mask: linear-gradient(to right, transparent, black 10px 80%, transparent);
-webkit-mask: linear-gradient(to right, black 80%, transparent); -webkit-mask: linear-gradient(to right, transparent, black 10px 80%, transparent);
margin-left: -10px;
padding-left: 22px;
scrollbar-width: none; scrollbar-width: none;
&::before {
content: unset;
}
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none; display: none;
} }
@ -353,6 +358,16 @@ onUnmounted(() => {
white-space: nowrap; white-space: nowrap;
contain: strict; contain: strict;
&::before {
content: "";
display: inline-block;
height: 40%;
border-left: 1px solid var(--divider);
margin-right: 1em;
margin-left: 10px;
vertical-align: -1px;
}
> .tab { > .tab {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;

View file

@ -14,7 +14,7 @@
<span v-if="pathname != ''" class="pathname">{{ self ? pathname.substr(1) : pathname }}</span> <span v-if="pathname != ''" class="pathname">{{ self ? pathname.substr(1) : pathname }}</span>
<span class="query">{{ query }}</span> <span class="query">{{ query }}</span>
<span class="hash">{{ hash }}</span> <span class="hash">{{ hash }}</span>
<i v-if="target === '_blank'" class="ph-arrow-square-out-bold ph-lg icon"></i> <i v-if="target === '_blank'" class="ph-arrow-square-out ph-bold ph-lg icon"></i>
</component> </component>
</template> </template>

View file

@ -1,5 +1,5 @@
<template> <template>
<Mfm :text="user.name || user.username" :plain="true" :nowrap="nowrap" :custom-emojis="user.emojis"/> <Mfm :class="$style.root" :text="user.name || user.username" :plain="true" :nowrap="nowrap" :custom-emojis="user.emojis"/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -13,3 +13,9 @@ const props = withDefaults(defineProps<{
nowrap: true, nowrap: true,
}); });
</script> </script>
<style lang="scss" module>
.root {
unicode-bidi: isolate;
}
</style>

View file

@ -2,8 +2,8 @@
<div class="ngbfujlo"> <div class="ngbfujlo">
<MkTextarea :model-value="text" readonly style="margin: 0;"></MkTextarea> <MkTextarea :model-value="text" readonly style="margin: 0;"></MkTextarea>
<MkButton class="button" primary :disabled="posting || posted" @click="post()"> <MkButton class="button" primary :disabled="posting || posted" @click="post()">
<i v-if="posted" class="ph-check-bold ph-lg"></i> <i v-if="posted" class="ph-check ph-bold ph-lg"></i>
<i v-else class="ph-paper-plane-tilt-bold ph-lg"></i> <i v-else class="ph-paper-plane-tilt ph-bold ph-lg"></i>
</MkButton> </MkButton>
</div> </div>
</template> </template>

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,8 @@
import "vite/modulepreload-polyfill"; import "vite/modulepreload-polyfill";
import "@/style.scss"; import "@/style.scss";
import "@/icons.scss"; import "@phosphor-icons/web/bold";
import "@phosphor-icons/web/fill";
//#region account indexedDB migration //#region account indexedDB migration
import { set } from "@/scripts/idb-proxy"; import { set } from "@/scripts/idb-proxy";

Some files were not shown because too many files have changed in this diff Show more