Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
72b03e009c | ||
![]() |
1b113c1045 | ||
![]() |
54959557ea | ||
![]() |
d44cb7f256 | ||
![]() |
3d063c95d1 | ||
![]() |
09cab605fc | ||
![]() |
666c8c0498 | ||
![]() |
d3e764d7f9 | ||
![]() |
7060625adf | ||
![]() |
21b6e23e98 | ||
![]() |
a0f794e372 | ||
![]() |
9195504329 | ||
![]() |
8c5d9dd549 | ||
![]() |
580f6a5b6c | ||
![]() |
74e76b460b | ||
![]() |
c4570b37b7 | ||
![]() |
cd0b0012d9 | ||
![]() |
c055b4d32d | ||
![]() |
75a9ff832a | ||
![]() |
b64d3af1f3 | ||
![]() |
fb6605bb40 | ||
![]() |
3bfae80fa7 | ||
![]() |
cb16cb0610 | ||
![]() |
0baed1a275 | ||
![]() |
42162c8015 | ||
![]() |
0fab0c416d | ||
![]() |
e2e262c8ce | ||
![]() |
cf6596203b | ||
![]() |
471911a54f | ||
![]() |
9394f4f540 | ||
![]() |
4e968216ad | ||
![]() |
84a7a9555f | ||
![]() |
8d12fd152b |
@@ -412,7 +412,7 @@ noMessagesYet: "Noch keine Nachrichten"
|
||||
newMessageExists: "Du hast eine neue Nachricht"
|
||||
onlyOneFileCanBeAttached: "Es kann pro Nachricht nur eine Datei angehängt werden"
|
||||
signinRequired: "Anmeldung erforderlich"
|
||||
invitations: "Einladen"
|
||||
invitations: "Einladungen"
|
||||
invitationCode: "Einladungscode"
|
||||
checking: "Wird überprüft..."
|
||||
available: "Verfügbar"
|
||||
@@ -598,10 +598,43 @@ openInNewTab: "In neuem Tab öffnen"
|
||||
openInSideView: "In Seitenansicht öffnen"
|
||||
defaultNavigationBehaviour: "Standardnavigationsverhalten"
|
||||
editTheseSettingsMayBreakAccount: "Bei Bearbeitung dieser Einstellungen besteht die Gefahr, dein Benutzerkonto zu beschädigen."
|
||||
instanceTicker: "Instanz-Informationen der Notiz"
|
||||
instanceTicker: "Instanz-Informationen von Notizen"
|
||||
waitingFor: "Warte auf {x}"
|
||||
random: "Zufällig"
|
||||
system: "System"
|
||||
_reversi:
|
||||
reversi: "Reversi"
|
||||
gameSettings: "Spieleinstellungen"
|
||||
chooseBoard: "Spielbrett auswählen"
|
||||
blackOrWhite: "Schwarz/Weiß"
|
||||
blackIs: "{name} spielt Schwarz"
|
||||
rules: "Regeln"
|
||||
botSettings: "Optionen des Computergegners"
|
||||
thisGameIsStartedSoon: "Dieses Spiel beginnt in wenigen Sekunden"
|
||||
waitingForOther: "Warte auf den Zug des Gegenspielers"
|
||||
waitingForMe: "Warte auf deinen Zug"
|
||||
waitingBoth: "Mach dich bereit"
|
||||
ready: "Bereit"
|
||||
cancelReady: "Nicht bereit"
|
||||
opponentTurn: "Zug deines Gegners"
|
||||
myTurn: "Dein Zug"
|
||||
turnOf: "Zug von {name}"
|
||||
pastTurnOf: "Zug von {name}"
|
||||
surrender: "Aufgeben"
|
||||
surrendered: "durch Aufgabe"
|
||||
drawn: "Unentschieden"
|
||||
won: "{name} hat gesiegt"
|
||||
black: "Schwarz"
|
||||
white: "Weiß"
|
||||
total: "Gesamt"
|
||||
turnCount: " Zug {count}"
|
||||
myGames: "Meine Runden"
|
||||
allGames: "Alle Runden"
|
||||
ended: "Beendet"
|
||||
playing: "Laufend"
|
||||
isLlotheo: "Der mit weniger Steinen gewinnt (Llotheo)"
|
||||
loopedMap: "Wiederholendes Spielbrett"
|
||||
canPutEverywhere: "Steine können überall platziert werden"
|
||||
_instanceTicker:
|
||||
none: "Nie anzeigen"
|
||||
remote: "Für Benutzer fremder Instanzen anzeigen"
|
||||
|
@@ -405,14 +405,14 @@ next: "Next"
|
||||
retype: "Enter again"
|
||||
noteOf: "{user}'s notes"
|
||||
inviteToGroup: "Invite to group"
|
||||
maxNoteTextLength: "Character limit of the note"
|
||||
maxNoteTextLength: "Character limit of notes"
|
||||
quoteAttached: "Quoted"
|
||||
quoteQuestion: "Do you want to append a quote?"
|
||||
noMessagesYet: "No messages yet"
|
||||
newMessageExists: "You've got a new message"
|
||||
onlyOneFileCanBeAttached: "You can only attach one file to a message"
|
||||
signinRequired: "Please sign in"
|
||||
invitations: "Invite"
|
||||
invitations: "Invitations"
|
||||
invitationCode: "Invitation code"
|
||||
checking: "Checking"
|
||||
available: "Available"
|
||||
@@ -598,10 +598,43 @@ openInNewTab: "Open in new tab"
|
||||
openInSideView: "Open in side view"
|
||||
defaultNavigationBehaviour: "Default navigation behavior"
|
||||
editTheseSettingsMayBreakAccount: "Editing these settings may damage your account."
|
||||
instanceTicker: "Instance information of the Note"
|
||||
instanceTicker: "Instance information of notes"
|
||||
waitingFor: "Waiting for {x}"
|
||||
random: "Random"
|
||||
system: "System"
|
||||
_reversi:
|
||||
reversi: "Reversi"
|
||||
gameSettings: "Game settings"
|
||||
chooseBoard: "Choose a board"
|
||||
blackOrWhite: "Black/White"
|
||||
blackIs: "{name} is playing Black"
|
||||
rules: "Rules"
|
||||
botSettings: "Bot options"
|
||||
thisGameIsStartedSoon: "The game will start in a few seconds"
|
||||
waitingForOther: "Waiting for the opponent's turn"
|
||||
waitingForMe: "Waiting for your turn"
|
||||
waitingBoth: "Get ready"
|
||||
ready: "Ready"
|
||||
cancelReady: "Cancel ready"
|
||||
opponentTurn: "Opponent's turn"
|
||||
myTurn: "Your turn"
|
||||
turnOf: "{name}'s turn"
|
||||
pastTurnOf: "{name}'s turn"
|
||||
surrender: "Surrender"
|
||||
surrendered: "By surrender"
|
||||
drawn: "Draw"
|
||||
won: "{name}'s win"
|
||||
black: "Black"
|
||||
white: "White"
|
||||
total: "Total"
|
||||
turnCount: "Turn {count}"
|
||||
myGames: "My rounds"
|
||||
allGames: "All rounds"
|
||||
ended: "Ended"
|
||||
playing: "Currently playing"
|
||||
isLlotheo: "The one with fewer stones wins (Llotheo)"
|
||||
loopedMap: "Looped map"
|
||||
canPutEverywhere: "Tiles are placeable everywhere"
|
||||
_instanceTicker:
|
||||
none: "Never show"
|
||||
remote: "Show for remote users"
|
||||
|
@@ -601,6 +601,7 @@ editTheseSettingsMayBreakAccount: "これらの設定を編集するとアカウ
|
||||
instanceTicker: "ノートのインスタンス情報"
|
||||
waitingFor: "{x}を待っています"
|
||||
random: "ランダム"
|
||||
system: "システム"
|
||||
|
||||
_reversi:
|
||||
reversi: "リバーシ"
|
||||
|
@@ -1,12 +1,12 @@
|
||||
---
|
||||
_lang_: "日本語 (関西弁)"
|
||||
introMisskey: "ようこそ!Misskeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ノート」を作成しぃ、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「リアクション」機能で、皆のノートに素はよ反応を追加することもできます✌\n新しい世界を探検しよう🚀"
|
||||
introMisskey: "ようこそ!Misskeyってのは、オープンソースの分散型マイクロブログサービスやねん。\n「ノート」を作成し、いま起こっとることを共有したり、あんたんこととか皆に伝えていこう📡\n「リアクション」機能で、皆のノートに素はよ反応を追加することもできるんやで✌\n新しい世界を探検してみらん?🚀"
|
||||
monthAndDay: "{month}月 {day}日"
|
||||
search: "探す"
|
||||
notifications: "通知"
|
||||
username: "ユーザー名"
|
||||
password: "パスワード"
|
||||
fetchingAsApObject: "連合に照会中"
|
||||
fetchingAsApObject: "今ちと連合に照会しとるで"
|
||||
ok: "おっけー"
|
||||
gotIt: "ほい"
|
||||
cancel: "やめとくわ"
|
||||
@@ -16,35 +16,40 @@ noNotes: "ノートはあらへん"
|
||||
noNotifications: "通知はあらへん"
|
||||
instance: "インスタンス"
|
||||
settings: "設定"
|
||||
basicSettings: "基本設定"
|
||||
otherSettings: "その他の設定"
|
||||
openInWindow: "ウィンドウで開いてや"
|
||||
profile: "プロフィール"
|
||||
timeline: "タイムライン"
|
||||
noAccountDescription: "自己紹介はあらへん"
|
||||
login: "ログイン"
|
||||
loggingIn: "ログインしとります"
|
||||
loggingIn: "ログインしよるで"
|
||||
logout: "ログアウト"
|
||||
signup: "新規登録"
|
||||
uploading: "アップロードしとります"
|
||||
save: "保存"
|
||||
uploading: "アップロードしよるで"
|
||||
save: "とっとく"
|
||||
users: "ユーザー"
|
||||
addUser: "ユーザー増やす"
|
||||
addUser: "ユーザーを追加や"
|
||||
favorite: "お気に入り"
|
||||
favorites: "お気に入り"
|
||||
unfavorite: "お気に入りやめる"
|
||||
pin: "ピン留め"
|
||||
unpin: "ピン留めやめる"
|
||||
unfavorite: "やっぱ気に入らん"
|
||||
pin: "ピン留めしとく"
|
||||
unpin: "やっぱピン留めせん"
|
||||
copyContent: "内容をコピー"
|
||||
copyLink: "リンクをコピー"
|
||||
delete: "ほかす"
|
||||
deleteAndEdit: "ほかして直す"
|
||||
deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのリアクション、Remote、返信も全部消えんで"
|
||||
deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのリアクション、Renote、返信も全部消えるんやけどそれでもええん?"
|
||||
addToList: "リストに入れたる"
|
||||
sendMessage: "メッセージを送る"
|
||||
copyUsername: "ユーザー名をコピー"
|
||||
searchUser: "ユーザーを検索"
|
||||
reply: "返す"
|
||||
loadMore: "もっとあるやろ!"
|
||||
youGotNewFollower: "フォローされたで"
|
||||
receiveFollowRequest: "フォローリクエストされたで"
|
||||
followRequestAccepted: "フォローが承認されたで"
|
||||
mention: "メンション"
|
||||
mentions: "あんた宛て"
|
||||
directNotes: "ダイレクト投稿"
|
||||
importAndExport: "インポートとエクスポート"
|
||||
@@ -57,7 +62,7 @@ unfollowConfirm: "{name}のフォローを解除してもええんか?"
|
||||
exportRequested: "エクスポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。エクスポート終わったら「ドライブ」に突っ込んどくで。"
|
||||
importRequested: "インポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。"
|
||||
lists: "リスト"
|
||||
noLists: "リストはあらへん"
|
||||
noLists: "リストなんてあらへんで"
|
||||
note: "ノート"
|
||||
notes: "ノート"
|
||||
following: "フォロー"
|
||||
@@ -65,31 +70,35 @@ followers: "フォロワー"
|
||||
followsYou: "フォローされとるで"
|
||||
createList: "リスト作る"
|
||||
manageLists: "リストの管理"
|
||||
retry: "もっぺんやってみる"
|
||||
error: "エラー"
|
||||
somethingHappened: "なんかアカンことが起こったで"
|
||||
retry: "もっぺんやる?"
|
||||
pageLoadError: "ページの読み込みに失敗してしもうたで…"
|
||||
pageLoadErrorDescription: "これは普通、ネットワークかブラウザキャッシュが原因やからね。キャッシュをクリアするか、もうちっとだけ待ってくれへんか?"
|
||||
enterListName: "リスト名を入れてや"
|
||||
privacy: "プライバシーってなんや?オカンの年齢か?"
|
||||
makeFollowManuallyApprove: "他人のフォローは許可してからや!"
|
||||
privacy: "プライバシーってなんぞや?"
|
||||
makeFollowManuallyApprove: "他人からのフォローは自分が決める"
|
||||
defaultNoteVisibility: "もとからの公開範囲"
|
||||
follow: "フォロー"
|
||||
followRequest: "フォロー許してくれや!言うてみる"
|
||||
followRequests: "フォロー許してくれや!"
|
||||
followRequest: "フォローを頼む"
|
||||
followRequests: "フォローを頼む"
|
||||
unfollow: "フォローやめる"
|
||||
followRequestPending: "フォロー許してくれるん待っとる"
|
||||
enterEmoji: "絵文字を入れてや"
|
||||
renote: "Renote"
|
||||
unrenote: "Renoteやめる"
|
||||
quote: "引用"
|
||||
pinnedNote: "ピン留めされたノート"
|
||||
pinnedNote: "ピン留めされとるノート"
|
||||
you: "あんた"
|
||||
clickToShow: "押してみ、見せたるわ"
|
||||
sensitive: "見たらあかんで"
|
||||
clickToShow: "押したら見えるようになるで"
|
||||
sensitive: "ちょっとアカンやつやで"
|
||||
add: "増やす"
|
||||
reaction: "リアクション"
|
||||
reactionSettingDescription: "リアクションピッカーに出しとくリアクションを選んでや。"
|
||||
rememberNoteVisibility: "公開範囲覚えといて"
|
||||
attachCancel: "くっつけるのやめよか"
|
||||
markAsSensitive: "ちょっと見せられへんわ"
|
||||
unmarkAsSensitive: "別にええんじゃね?"
|
||||
attachCancel: "やっぱ添付やめてくれん?"
|
||||
markAsSensitive: "ちょっとこれはアカン"
|
||||
unmarkAsSensitive: "そこまでアカンことないやろ"
|
||||
enterFileName: "ファイル名を入れてや"
|
||||
mute: "ミュート"
|
||||
unmute: "ミュートやめたる"
|
||||
@@ -97,30 +106,35 @@ block: "ブロック"
|
||||
unblock: "ブロックやめたる"
|
||||
suspend: "凍結"
|
||||
unsuspend: "溶かす"
|
||||
blockConfirm: "ブロックしてしもうてええか?"
|
||||
unblockConfirm: "ブロックすんのやめるけどええか?"
|
||||
blockConfirm: "ブロックしてもええんか?"
|
||||
unblockConfirm: "ブロックやめたるってほんまか?"
|
||||
suspendConfirm: "凍結してしもうてええか?"
|
||||
unsuspendConfirm: "解凍するけどええか?"
|
||||
selectList: "リストを選ぶ"
|
||||
selectAntenna: "アンテナを選ぶ"
|
||||
selectWidget: "ウィジェットを選ぶ"
|
||||
editWidgets: "ウィジェットをいじる"
|
||||
editWidgetsExit: "編集終ったで"
|
||||
customEmojis: "カスタム絵文字"
|
||||
emoji: "絵文字"
|
||||
emojiName: "絵文字名"
|
||||
emojiUrl: "絵文字画像URL"
|
||||
addEmoji: "絵文字を追加"
|
||||
settingGuide: "ええ感じの設定"
|
||||
cacheRemoteFiles: "リモートのファイルをキャッシュする"
|
||||
cacheRemoteFilesDescription: "この設定をチャラにすると、リモートファイルをキャッシュせず直リンクするようになります。サーバーのストレージを節約できますが、サムネイルが生成されへんので通信量が増加します。"
|
||||
flagAsBot: "Botやでと言っとく"
|
||||
flagAsCat: "Catやでと言っとく"
|
||||
autoAcceptFollowed: "フォローしとるユーザーからのフォロリクは全部勝手にええでって言うで"
|
||||
cacheRemoteFilesDescription: "この設定を切っとくと、リモートファイルをキャッシュせず直リンクするようになってしまうんやで? サーバーのストレージは節約できるんやけど、かわりにサムネイルが作られんくなるから通信量が増えるで?"
|
||||
flagAsBot: "Botやで"
|
||||
flagAsCat: "Catやで"
|
||||
autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストには勝手に許可しとくで。"
|
||||
addAcount: "アカウント追加"
|
||||
loginFailed: "ログインに失敗してん"
|
||||
loginFailed: "ログインに失敗してしもうた…"
|
||||
showOnRemote: "リモートで見る"
|
||||
general: "全般"
|
||||
wallpaper: "壁紙"
|
||||
setWallpaper: "壁紙を設定"
|
||||
removeWallpaper: "壁紙ほかす"
|
||||
removeWallpaper: "壁紙を削除"
|
||||
searchWith: "検索: {q}"
|
||||
youHaveNoLists: "リストはあらへん"
|
||||
youHaveNoLists: "リストがあらへんで?"
|
||||
followConfirm: "{name}をフォローしてええか?"
|
||||
proxyAccount: "プロキシアカウント"
|
||||
proxyAccountDescription: "プロキシアカウントは、代わりにフォローしてくれるアカウントや。例えば、551に豚まんが無いときやったり、ユーザーがリモートユーザーをアカウントに入れたとき、リストに入れられたユーザーが誰からもフォローされてないと寂しいやん。寂しいし、アクティビティも配達されへんから、プロキシアカウントがフォローしてくれるで。ええやつやん…"
|
||||
@@ -130,7 +144,7 @@ recipient: "宛先"
|
||||
annotation: "注釈"
|
||||
federation: "連合"
|
||||
instances: "インスタンス"
|
||||
registeredAt: "一見さんになった日"
|
||||
registeredAt: "初観測"
|
||||
latestRequestSentAt: "ちょっと前のリクエスト送信"
|
||||
latestRequestReceivedAt: "ちょっと前のリクエスト受信"
|
||||
latestStatus: "ちょっと前のステータス"
|
||||
@@ -257,7 +271,8 @@ copyUrl: "URLをコピー"
|
||||
rename: "名前を変えるで"
|
||||
avatar: "アイコン"
|
||||
banner: "バナー"
|
||||
nsfw: "見たらあかんで"
|
||||
nsfw: "ちょっとアカンやつやで"
|
||||
whenServerDisconnected: "サーバーとの接続が失くなってしもうたとき"
|
||||
disconnectedFromServer: "サーバーが機嫌悪いねん"
|
||||
reload: "リロード"
|
||||
doNothing: "何もせんとく"
|
||||
@@ -293,7 +308,19 @@ proxyRemoteFilesDescription: "この設定を入れると、保存しとらん
|
||||
driveCapacityPerLocalAccount: "ローカルユーザーひとりあたりのドライブ容量"
|
||||
driveCapacityPerRemoteAccount: "リモートユーザーひとりあたりのドライブ容量"
|
||||
inMb: "メガバイト単位"
|
||||
iconUrl: "アイコン画像のURL"
|
||||
bannerUrl: "バナー画像のURL"
|
||||
basicInfo: "基本情報"
|
||||
pinnedUsers: "ピン留めしたユーザー"
|
||||
pinnedUsersDescription: "「みつける」ページとかにピン留めしたいユーザーをここに書けばええんやで。他ん人との名前は改行で区切ればええんやで。"
|
||||
hcaptcha: "hCaptcha(キャプチャ)"
|
||||
enableHcaptcha: "hCaptcha(キャプチャ)をつけとく"
|
||||
hcaptchaSiteKey: "サイトキー"
|
||||
hcaptchaSecretKey: "シークレットキー"
|
||||
recaptcha: "reCAPTCHA"
|
||||
enableRecaptcha: "reCAPTCHA(リキャプチャ)を有効にする"
|
||||
recaptchaSiteKey: "サイトキー"
|
||||
recaptchaSecretKey: "シークレットキー"
|
||||
avoidMultiCaptchaConfirm: "ぎょうさんのCaptchaをつこてしまうと、仲良うせんことがあるんや。他のCaptchaをなおしとこか?別にキャンセルしてもろうたらCaptchaは消されへんで済むけど知らんで。"
|
||||
antennas: "アンテナ"
|
||||
manageAntennas: "アンテナいじる"
|
||||
@@ -346,11 +373,47 @@ unregister: "登録やめる"
|
||||
passwordLessLogin: "パスワード無くてもログインできるようにする"
|
||||
resetPassword: "パスワードをリセット"
|
||||
newPasswordIs: "今度のパスワードは「{password}」や"
|
||||
autoNoteWatch: "ノートを勝手に見張っとく"
|
||||
autoNoteWatchDescription: "あんたがリアクションや返信した他のユーザーのノートの通知をあんたも受け取れるようになるんやで。通知欄の流れがめっちゃ早くなるで。"
|
||||
reduceUiAnimation: "UIの動きやアニメーションを減らしてくれや。"
|
||||
share: "わけわけ"
|
||||
notFound: "見つからへんね"
|
||||
notFoundDescription: "指定されたURLに該当するページはあらへんやった。"
|
||||
uploadFolder: "とりあえずここへアップロード"
|
||||
cacheClear: "キャッシュをほかす"
|
||||
markAsReadAllNotifications: "通知はもう全て読んだわっ"
|
||||
markAsReadAllUnreadNotes: "投稿は全て読んだわっ"
|
||||
markAsReadAllTalkMessages: "チャットはもうぜんぶ読んだわっ"
|
||||
help: "ヘルプ"
|
||||
inputMessageHere: "ここにメッセージ書いてや"
|
||||
close: "さいなら"
|
||||
group: "グループ"
|
||||
groups: "グループ"
|
||||
createGroup: "グループを作るで"
|
||||
ownedGroups: "所有しとるグループ"
|
||||
joinedGroups: "参加しとるグループ"
|
||||
invites: "来てや"
|
||||
groupName: "グループ名"
|
||||
members: "メンバー"
|
||||
transfer: "譲渡"
|
||||
messagingWithUser: "ユーザーとチャット"
|
||||
messagingWithGroup: "グループでチャット"
|
||||
title: "タイトル"
|
||||
text: "テキスト"
|
||||
enable: "有効にするで"
|
||||
next: "次"
|
||||
retype: "もっかい入力"
|
||||
noteOf: "{user}のノート"
|
||||
inviteToGroup: "グループに招く"
|
||||
maxNoteTextLength: "ノートの文字数制限"
|
||||
quoteAttached: "引用付いとるで"
|
||||
quoteQuestion: "引用として添付してもええか?"
|
||||
noMessagesYet: "まだチャットはあらへんで"
|
||||
newMessageExists: "新しいメッセージがきたで"
|
||||
onlyOneFileCanBeAttached: "すまん、メッセージに添付できるファイルはひとつだけなんや。"
|
||||
invitations: "来てや"
|
||||
invitationCode: "招待コード"
|
||||
checking: "確認しとるで"
|
||||
smtpHost: "ホスト"
|
||||
smtpUser: "ユーザー名"
|
||||
smtpPass: "パスワード"
|
||||
@@ -358,6 +421,7 @@ _sidebar:
|
||||
icon: "アイコン"
|
||||
_theme:
|
||||
keys:
|
||||
mention: "メンション"
|
||||
renote: "Renote"
|
||||
_sfx:
|
||||
note: "ノート"
|
||||
@@ -441,6 +505,7 @@ _notification:
|
||||
youWereFollowed: "フォローされたで"
|
||||
_types:
|
||||
follow: "フォロー"
|
||||
mention: "メンション"
|
||||
renote: "Renote"
|
||||
quote: "引用"
|
||||
reaction: "リアクション"
|
||||
|
@@ -598,9 +598,47 @@ openInNewTab: "Открыть в новой вкладке"
|
||||
openInSideView: "Открывать в боковой колонке"
|
||||
defaultNavigationBehaviour: "Поведение навигации по умолчанию"
|
||||
editTheseSettingsMayBreakAccount: "От изменений в этих настройках ваша учётная запись может поломаться."
|
||||
instanceTicker: "Строка с инстансом в заметке"
|
||||
waitingFor: "Ждём {x}"
|
||||
random: "Случайные"
|
||||
system: "Система"
|
||||
_reversi:
|
||||
reversi: "Реверси"
|
||||
gameSettings: "Настройки игры"
|
||||
chooseBoard: "Выберите доску"
|
||||
blackOrWhite: "Черные/Белые"
|
||||
blackIs: "{name} за чёрных"
|
||||
rules: "Правила"
|
||||
botSettings: "Настройки бота"
|
||||
thisGameIsStartedSoon: "Игра скоро начнётся"
|
||||
waitingForOther: "Ожидание оппонента..."
|
||||
waitingForMe: "В ожидании, когда будете готовы"
|
||||
waitingBoth: "Приготовьтесь"
|
||||
ready: "Готово"
|
||||
cancelReady: "Возврат к подготовке"
|
||||
opponentTurn: "Ход соперника"
|
||||
myTurn: "Ваш ход"
|
||||
turnOf: "Ходит {name}"
|
||||
pastTurnOf: "Ходит {name}"
|
||||
surrender: "Сдаться"
|
||||
surrendered: "Сдавшись"
|
||||
drawn: "Ничья"
|
||||
won: "{name} — победитель"
|
||||
black: "Чёрные"
|
||||
white: "Белые"
|
||||
total: "Всего"
|
||||
turnCount: "Ход {count}"
|
||||
myGames: "Мои игры"
|
||||
allGames: "Все игры"
|
||||
ended: "Завершено"
|
||||
playing: "Идёт игра"
|
||||
isLlotheo: "Выигрывает меньшее число камней (LLoTheO)"
|
||||
loopedMap: "Замкнутая в кольцо доска"
|
||||
canPutEverywhere: "Камни можно ставить везде"
|
||||
_instanceTicker:
|
||||
none: "Не показывать"
|
||||
remote: "Только у пользователей с других сайтов"
|
||||
always: "Показывать всегда"
|
||||
_serverDisconnectedBehavior:
|
||||
reload: "Автоматическая перезагрузка"
|
||||
dialog: "Предупреждение"
|
||||
|
133
locales/uk-UA.yml
Normal file
133
locales/uk-UA.yml
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
_lang_: "Українська"
|
||||
monthAndDay: "{month}/{day}"
|
||||
search: "Пошук"
|
||||
notifications: "Сповіщення"
|
||||
username: "Ім'я користувача"
|
||||
password: "Пароль"
|
||||
ok: "OK"
|
||||
gotIt: "Зрозуміло!"
|
||||
cancel: "Скасувати"
|
||||
enterUsername: "Введіть ім'я користувача"
|
||||
renotedBy: "Поширено {user}"
|
||||
noNotes: "Немає дописів"
|
||||
noNotifications: "Немає сповіщень"
|
||||
instance: "Інстанс"
|
||||
settings: "Налаштування"
|
||||
basicSettings: "Основні налаштування"
|
||||
otherSettings: "Інші налаштування"
|
||||
openInWindow: "Відкрити у вікні"
|
||||
profile: "Профіль"
|
||||
timeline: "Стрічка"
|
||||
noAccountDescription: "Цей користувач ще нічого не написав про себе"
|
||||
login: "Увійти"
|
||||
loggingIn: "Здійснюємо вхід..."
|
||||
logout: "Вийти"
|
||||
signup: "Реєстрація"
|
||||
uploading: "Завантаження..."
|
||||
save: "Зберегти"
|
||||
users: "Користувачі"
|
||||
addUser: "Додати користувача"
|
||||
favorite: "Обране"
|
||||
favorites: "Обране"
|
||||
unfavorite: "Видалити з обраного"
|
||||
pin: "Закріпити"
|
||||
unpin: "Відкріпити"
|
||||
copyContent: "Скопіювати контент"
|
||||
copyLink: "Скопіювати посилання"
|
||||
delete: "Видалити"
|
||||
addToList: "Додати до списку"
|
||||
sendMessage: "Надіслати повідомлення"
|
||||
copyUsername: "Скопіювати ім’я користувача"
|
||||
searchUser: "Пошук користувачів"
|
||||
reply: "Відповісти"
|
||||
loadMore: "Показати більше"
|
||||
mention: "Згадка"
|
||||
mentions: "Згадки"
|
||||
importAndExport: "Імпорт та експорт"
|
||||
import: "Імпорт"
|
||||
export: "Експорт"
|
||||
files: "Файли"
|
||||
download: "Завантажити"
|
||||
lists: "Списки"
|
||||
noLists: "Немає списків"
|
||||
following: "Підписки"
|
||||
followers: "Підписники"
|
||||
followsYou: "Підписаний(-а) на вас"
|
||||
error: "Помилка"
|
||||
somethingHappened: "Щось пішло не так"
|
||||
retry: "Спробувати знову"
|
||||
pageLoadError: "Помилка при завантаженні сторінки"
|
||||
privacy: "Приватність"
|
||||
follow: "Підписки"
|
||||
unfollow: "Відписатися"
|
||||
quote: "Цитата"
|
||||
you: "Ви"
|
||||
clickToShow: "Натисніть для перегляду"
|
||||
sensitive: "NSFW"
|
||||
add: "Додати"
|
||||
reaction: "Реакції"
|
||||
markAsSensitive: "Відмітити як NSFW"
|
||||
enterFileName: "Введіть ім'я файлу"
|
||||
mute: "Ігнорувати"
|
||||
unmute: "Показувати"
|
||||
block: "Заблокувати"
|
||||
unblock: "Розблокувати"
|
||||
instances: "Інстанс"
|
||||
remove: "Видалити"
|
||||
nsfw: "NSFW"
|
||||
userList: "Списки"
|
||||
smtpUser: "Ім'я користувача"
|
||||
smtpPass: "Пароль"
|
||||
_theme:
|
||||
keys:
|
||||
mention: "Згадка"
|
||||
_sfx:
|
||||
notification: "Сповіщення"
|
||||
_widgets:
|
||||
notifications: "Сповіщення"
|
||||
timeline: "Стрічка"
|
||||
_cw:
|
||||
show: "Показати більше"
|
||||
_visibility:
|
||||
followers: "Підписники"
|
||||
_postForm:
|
||||
replyPlaceholder: "Відповідь на допис..."
|
||||
_profile:
|
||||
username: "Ім'я користувача"
|
||||
_exportOrImport:
|
||||
followingList: "Підписки"
|
||||
muteList: "Ігнорувати"
|
||||
blockingList: "Заблокувати"
|
||||
userLists: "Списки"
|
||||
_pages:
|
||||
script:
|
||||
categories:
|
||||
list: "Списки"
|
||||
blocks:
|
||||
_join:
|
||||
arg1: "Списки"
|
||||
_randomPick:
|
||||
arg1: "Списки"
|
||||
_dailyRandomPick:
|
||||
arg1: "Списки"
|
||||
_seedRandomPick:
|
||||
arg2: "Списки"
|
||||
_pick:
|
||||
arg1: "Списки"
|
||||
_listLen:
|
||||
arg1: "Списки"
|
||||
types:
|
||||
array: "Списки"
|
||||
_notification:
|
||||
_types:
|
||||
follow: "Підписки"
|
||||
mention: "Згадка"
|
||||
quote: "Цитата"
|
||||
reaction: "Реакції"
|
||||
_deck:
|
||||
_columns:
|
||||
notifications: "Сповіщення"
|
||||
tl: "Стрічка"
|
||||
list: "Списки"
|
||||
mentions: "Згадки"
|
@@ -598,9 +598,47 @@ openInNewTab: "在新标签页中打开"
|
||||
openInSideView: "在侧边栏中打开"
|
||||
defaultNavigationBehaviour: "默认导航"
|
||||
editTheseSettingsMayBreakAccount: "编辑这些设置可以会损坏您的账号"
|
||||
instanceTicker: "帖子的实例信息"
|
||||
waitingFor: "等待{x}"
|
||||
random: "随机"
|
||||
system: "系统"
|
||||
_reversi:
|
||||
reversi: "黑白棋"
|
||||
gameSettings: "对局设置"
|
||||
chooseBoard: "棋盘选择"
|
||||
blackOrWhite: "先手/后手"
|
||||
blackIs: "{name}执黑(先走)"
|
||||
rules: "规则"
|
||||
botSettings: "机器人设置"
|
||||
thisGameIsStartedSoon: "对局在几秒后开始"
|
||||
waitingForOther: "等待对手准备"
|
||||
waitingForMe: "等待您的准备"
|
||||
waitingBoth: "请准备"
|
||||
ready: "准备就绪"
|
||||
cancelReady: "重新准备"
|
||||
opponentTurn: "对手的会合"
|
||||
myTurn: "您的回合"
|
||||
turnOf: "{name}的回合"
|
||||
pastTurnOf: "{name}的回合"
|
||||
surrender: "认输 "
|
||||
surrendered: "对手认输"
|
||||
drawn: "平局"
|
||||
won: "{name}获胜"
|
||||
black: "黑"
|
||||
white: "白"
|
||||
total: "总计"
|
||||
turnCount: "{count}回合"
|
||||
myGames: "我的对局"
|
||||
allGames: "所有对局"
|
||||
ended: "结束"
|
||||
playing: "对局中"
|
||||
isLlotheo: "棋子较少一方获胜(LLoTheO规则)"
|
||||
loopedMap: "循环棋盘"
|
||||
canPutEverywhere: "可以下在任意位置"
|
||||
_instanceTicker:
|
||||
none: "不显示"
|
||||
remote: "显示给远程用户"
|
||||
always: "始终显示"
|
||||
_serverDisconnectedBehavior:
|
||||
reload: "自动重载"
|
||||
dialog: "对话框警告"
|
||||
|
@@ -99,8 +99,8 @@ attachCancel: "移除附件"
|
||||
markAsSensitive: "標記為敏感內容"
|
||||
unmarkAsSensitive: "取消標記為敏感內容"
|
||||
enterFileName: "請輸入檔案名稱"
|
||||
mute: "消音"
|
||||
unmute: "解除消音"
|
||||
mute: "靜音"
|
||||
unmute: "解除靜音"
|
||||
block: "封鎖"
|
||||
unblock: "解除封鎖"
|
||||
suspend: "凍結"
|
||||
@@ -171,8 +171,8 @@ clearCachedFiles: "清除快取資料"
|
||||
clearCachedFilesConfirm: "確定要清除緩存資料嗎?"
|
||||
blockedInstances: "已封鎖的實例"
|
||||
blockedInstancesDescription: "請逐行輸入需要封鎖的實例。已封鎖的實例將無法與本實例進行通訊。"
|
||||
muteAndBlock: "禁言 / 封鎖"
|
||||
mutedUsers: "已禁言用戶"
|
||||
muteAndBlock: "靜音/封鎖"
|
||||
mutedUsers: "已靜音用戶"
|
||||
blockedUsers: "已封鎖用戶"
|
||||
noUsers: "無用戶"
|
||||
editProfile: "編輯個人檔案"
|
||||
@@ -452,8 +452,10 @@ appearance: "外觀"
|
||||
clientSettings: "用戶端設定"
|
||||
accountSettings: "帳號設定"
|
||||
promotion: "推廣貼文"
|
||||
promote: "推廣"
|
||||
numberOfDays: "有效天數"
|
||||
hideThisNote: "隱藏此貼文"
|
||||
showFeaturedNotesInTimeline: "在時間軸上顯示熱門推薦"
|
||||
objectStorageBaseUrl: "Base URL"
|
||||
objectStorageBucket: "儲存空間(Bucket)"
|
||||
objectStoragePrefix: "前綴"
|
||||
@@ -536,6 +538,7 @@ smtpUser: "使用者名稱"
|
||||
smtpPass: "密碼"
|
||||
emptyToDisableSmtpAuth: "留空使用者名稱和密碼以禁用SMTP驗證。"
|
||||
testEmail: "郵件測試發送"
|
||||
wordMute: "靜音文字"
|
||||
display: "檢視"
|
||||
copy: "複製"
|
||||
metrics: "指標"
|
||||
@@ -547,6 +550,7 @@ channel: "頻道"
|
||||
create: "新增"
|
||||
notificationSetting: "通知設定"
|
||||
other: "其他"
|
||||
regenerateLoginTokenDescription: "再生用於登入的內部權杖。一般情況下是不需要這樣做的。一旦再生,所有裝置將會被登出。"
|
||||
sample: "範例 "
|
||||
abuseReports: "檢舉"
|
||||
reportAbuse: "檢舉"
|
||||
@@ -554,8 +558,25 @@ reportAbuseOf: "檢舉{name}"
|
||||
send: "發送"
|
||||
openInNewTab: "在新分頁中開啟"
|
||||
random: "隨機"
|
||||
system: "系統"
|
||||
_reversi:
|
||||
reversi: "黑白棋"
|
||||
gameSettings: "對弈設定"
|
||||
chooseBoard: "選擇棋盤"
|
||||
rules: "規則"
|
||||
botSettings: "機器人設定"
|
||||
opponentTurn: "對手回合"
|
||||
myTurn: "你的回合"
|
||||
turnOf: "{name}的回合"
|
||||
pastTurnOf: "{name}的回合"
|
||||
surrender: "認輸"
|
||||
black: "黑"
|
||||
white: "白"
|
||||
total: "合計"
|
||||
ended: "已結束"
|
||||
playing: "正在對弈"
|
||||
_instanceTicker:
|
||||
always: "總是顯示"
|
||||
_serverDisconnectedBehavior:
|
||||
reload: "自動重載"
|
||||
dialog: "以對話框警告"
|
||||
@@ -575,14 +596,24 @@ _sidebar:
|
||||
hide: "隱藏"
|
||||
_wordMute:
|
||||
muteWords: "加入靜音文字"
|
||||
mutedNotes: "已靜音的貼文"
|
||||
_theme:
|
||||
constant: "常數"
|
||||
defaultValue: "預設值"
|
||||
color: "顏色"
|
||||
func: "函数"
|
||||
argument: "引數"
|
||||
alpha: "透明度"
|
||||
darken: "暗度"
|
||||
lighten: "亮度"
|
||||
keys:
|
||||
bg: "背景"
|
||||
fg: "文本"
|
||||
shadow: "陰影"
|
||||
link: "鏈接"
|
||||
hashtag: "#tag"
|
||||
mention: "提及"
|
||||
mentionMe: "提及我"
|
||||
renote: "轉發貼文"
|
||||
divider: "分割線"
|
||||
infoBg: "資訊背景"
|
||||
@@ -633,6 +664,8 @@ _tutorial:
|
||||
step6_3: "在他人的貼文按下「+」的圖示即可選擇想要的表情符號來進行「反應」。"
|
||||
step7_1: "以上為Misskey的基本操作說明,教學在此告一段落。辛苦了。"
|
||||
step7_2: "歡迎到{help}來瞭解更多Misskey相關介紹。"
|
||||
_2fa:
|
||||
registerDevice: "註冊裝置"
|
||||
_permissions:
|
||||
"read:blocks": "已封鎖用戶名單"
|
||||
"write:blocks": "編輯已封鎖用戶名單"
|
||||
@@ -641,15 +674,26 @@ _permissions:
|
||||
"read:favorites": "瀏覽已收藏"
|
||||
"write:favorites": "編輯收藏清單"
|
||||
"write:following": "追隨/解除追隨"
|
||||
"read:messaging": "顯示訊息"
|
||||
"write:messaging": "撰寫或刪除私人訊息"
|
||||
"read:mutes": "顯示已靜音列表"
|
||||
"write:mutes": "編輯已靜音列表"
|
||||
"write:notes": "撰寫或刪除貼文"
|
||||
"read:notifications": "查看通知"
|
||||
"write:notifications": "編輯通知"
|
||||
"read:reactions": "查看反應"
|
||||
"write:reactions": "編輯反應"
|
||||
"write:votes": "投票"
|
||||
"read:pages": "顯示頁面"
|
||||
"write:pages": "編輯頁面"
|
||||
"read:page-likes": "顯示頁面的已喜歡"
|
||||
"write:page-likes": "編輯頁面上喜歡"
|
||||
"read:user-groups": "顯示使用者群組"
|
||||
"write:user-groups": "編輯使用者群組"
|
||||
"read:channels": "已查看的頻道"
|
||||
"write:channels": "操作頻道"
|
||||
"write:channels": "編輯頻道"
|
||||
_auth:
|
||||
shareAccess: "要授權「“{name}”」存取您的帳戶嗎?"
|
||||
_antennaSources:
|
||||
all: "全部貼文"
|
||||
homeTimeline: "來自已追隨使用者的貼文"
|
||||
@@ -685,14 +729,17 @@ _poll:
|
||||
noOnlyOneChoice: "至少需要兩個選項。"
|
||||
expiration: "期限"
|
||||
infinite: "無期限"
|
||||
at: "結束時間"
|
||||
deadlineDate: "截止日期"
|
||||
deadlineTime: "小時"
|
||||
duration: "時長"
|
||||
votesCount: "{n}票"
|
||||
totalVotes: "一共{n}票"
|
||||
vote: "投票"
|
||||
showResult: "顯示結果"
|
||||
voted: "已投票"
|
||||
closed: "已結束"
|
||||
remainingDays: "{d}天{h}小時後結束"
|
||||
_visibility:
|
||||
public: "公開"
|
||||
home: "首頁"
|
||||
@@ -723,7 +770,7 @@ _profile:
|
||||
_exportOrImport:
|
||||
allNotes: "全部貼文"
|
||||
followingList: "追隨中"
|
||||
muteList: "消音"
|
||||
muteList: "靜音"
|
||||
blockingList: "封鎖"
|
||||
userLists: "清單"
|
||||
_charts:
|
||||
@@ -820,10 +867,13 @@ _pages:
|
||||
variables: "變數"
|
||||
title: "標題"
|
||||
url: "頁面網址"
|
||||
font: "字型"
|
||||
fontSerif: "襯線體"
|
||||
fontSansSerif: "無襯線體"
|
||||
inputBlocks: "輸入"
|
||||
blocks:
|
||||
text: "文本"
|
||||
textarea: "文字區域"
|
||||
section: "區段"
|
||||
image: "圖片"
|
||||
button: "按鈕"
|
||||
@@ -832,17 +882,25 @@ _pages:
|
||||
variable: "變數"
|
||||
_post:
|
||||
text: "内容"
|
||||
canvasId: "畫布ID"
|
||||
textInput: "插入文字"
|
||||
_textInput:
|
||||
name: "變數名稱"
|
||||
text: "標題"
|
||||
default: "預設值"
|
||||
textareaInput: "多行文字输入"
|
||||
_textareaInput:
|
||||
name: "變數名稱"
|
||||
text: "標題"
|
||||
default: "預設值"
|
||||
numberInput: "輸入數值"
|
||||
_numberInput:
|
||||
name: "變數名稱"
|
||||
text: "標題"
|
||||
default: "預設值"
|
||||
canvas: "畫布"
|
||||
_canvas:
|
||||
id: "畫布ID"
|
||||
width: "寬度"
|
||||
height: "高度"
|
||||
switch: "開關"
|
||||
@@ -858,6 +916,7 @@ _pages:
|
||||
_button:
|
||||
text: "標題"
|
||||
colored: "彩色"
|
||||
action: "按下按鈕後發生的行為"
|
||||
_action:
|
||||
_dialog:
|
||||
content: "内容"
|
||||
@@ -873,6 +932,7 @@ _pages:
|
||||
_radioButton:
|
||||
name: "變數名稱"
|
||||
title: "標題"
|
||||
default: "預設值"
|
||||
script:
|
||||
categories:
|
||||
logical: "邏輯運算"
|
||||
@@ -888,6 +948,8 @@ _pages:
|
||||
text: "文本"
|
||||
multiLineText: "文本 (多行)"
|
||||
textList: "文本列表"
|
||||
_strLen:
|
||||
arg1: "文本"
|
||||
_strPick:
|
||||
arg1: "文本"
|
||||
arg2: "字元位置"
|
||||
@@ -1001,6 +1063,8 @@ _pages:
|
||||
arg1: "文字"
|
||||
_numberToString:
|
||||
arg1: "數值"
|
||||
_splitStrByLine:
|
||||
arg1: "文本"
|
||||
ref: "變數"
|
||||
aiScriptVar: "AiScript的變數"
|
||||
fn: "函数"
|
||||
@@ -1016,6 +1080,7 @@ _pages:
|
||||
array: "清單"
|
||||
stringArray: "文本列表"
|
||||
enviromentVariables: "環境變數"
|
||||
pageVariables: "頁面元素"
|
||||
_relayStatus:
|
||||
requesting: "等待核准"
|
||||
accepted: "已通過核准"
|
||||
@@ -1034,7 +1099,13 @@ _notification:
|
||||
renote: "轉發貼文"
|
||||
quote: "引用"
|
||||
reaction: "反應"
|
||||
receiveFollowRequest: "已收到追隨請求"
|
||||
followRequestAccepted: "追隨請求已接受"
|
||||
app: "應用程式通知"
|
||||
_deck:
|
||||
alwaysShowMainColumn: "總是顯示主欄"
|
||||
columnAlign: "對齊欄位"
|
||||
addColumn: "新增欄位"
|
||||
swapLeft: "向左移動"
|
||||
swapRight: "向右移動"
|
||||
swapUp: "往上移動"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
||||
"version": "12.51.0",
|
||||
"version": "12.52.0",
|
||||
"codename": "indigo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -253,7 +253,7 @@
|
||||
"vuex": "4.0.0-beta.4",
|
||||
"vuex-persistedstate": "3.1.0",
|
||||
"web-push": "3.4.4",
|
||||
"webpack": "5.2.0",
|
||||
"webpack": "5.3.2",
|
||||
"webpack-cli": "4.1.0",
|
||||
"websocket": "1.0.32",
|
||||
"ws": "7.3.1",
|
||||
|
@@ -122,6 +122,7 @@ export default defineComponent({
|
||||
users: [],
|
||||
hashtags: [],
|
||||
emojis: [],
|
||||
items: [],
|
||||
select: -1,
|
||||
emojilist,
|
||||
emojiDb: [] as EmojiDef[]
|
||||
@@ -129,10 +130,6 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
computed: {
|
||||
items(): HTMLCollection {
|
||||
return (this.$refs.suggests as Element).children;
|
||||
},
|
||||
|
||||
useOsNativeEmojis(): boolean {
|
||||
return this.$store.state.device.useOsNativeEmojis;
|
||||
}
|
||||
@@ -148,6 +145,7 @@ export default defineComponent({
|
||||
|
||||
updated() {
|
||||
this.setPosition();
|
||||
this.items = (this.$refs.suggests as Element | undefined)?.children || [];
|
||||
},
|
||||
|
||||
mounted() {
|
||||
@@ -371,6 +369,7 @@ export default defineComponent({
|
||||
|
||||
selectNext() {
|
||||
if (++this.select >= this.items.length) this.select = 0;
|
||||
if (this.items.length === 0) this.select = -1;
|
||||
this.applySelect();
|
||||
},
|
||||
|
||||
@@ -384,8 +383,10 @@ export default defineComponent({
|
||||
el.removeAttribute('data-selected');
|
||||
}
|
||||
|
||||
this.items[this.select].setAttribute('data-selected', 'true');
|
||||
(this.items[this.select] as any).focus();
|
||||
if (this.select !== -1) {
|
||||
this.items[this.select].setAttribute('data-selected', 'true');
|
||||
(this.items[this.select] as any).focus();
|
||||
}
|
||||
},
|
||||
|
||||
chooseUser() {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<MkA :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1">
|
||||
<div class="banner" v-if="channel.bannerUrl" :style="`background-image: url('${channel.bannerUrl}')`">
|
||||
<div class="banner" :style="bannerStyle">
|
||||
<div class="fade"></div>
|
||||
<div class="name"><Fa :icon="faSatelliteDish"/> {{ channel.name }}</div>
|
||||
<div class="status">
|
||||
@@ -45,6 +45,16 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
bannerStyle() {
|
||||
if (this.channel.bannerUrl) {
|
||||
return { backgroundImage: `url(${this.channel.bannerUrl})` };
|
||||
} else {
|
||||
return { backgroundColor: '#4c5e6d' };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
faSatelliteDish, faUsers, faPencilAlt,
|
||||
|
@@ -169,15 +169,15 @@ export default defineComponent({
|
||||
font-size: 32px;
|
||||
|
||||
&.success {
|
||||
color: var(--accent);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
&.error {
|
||||
color: #ec4137;
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: #ecb637;
|
||||
color: var(--warn);
|
||||
}
|
||||
|
||||
> * {
|
||||
|
@@ -32,8 +32,6 @@ export default defineComponent({
|
||||
raw: {
|
||||
default: false
|
||||
},
|
||||
// specify the parent element
|
||||
parentElement: {}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -66,7 +64,7 @@ export default defineComponent({
|
||||
|
||||
if (this.$refs.gridOuter) {
|
||||
let height = 287;
|
||||
const parent = this.parentElement || this.$parent.$el;
|
||||
const parent = this.$parent.$el;
|
||||
|
||||
if (this.$refs.gridOuter.clientHeight) {
|
||||
height = this.$refs.gridOuter.clientHeight;
|
||||
@@ -81,11 +79,6 @@ export default defineComponent({
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
parentElement() {
|
||||
this.size();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@@ -125,6 +125,18 @@ export default defineComponent({
|
||||
}, genEl(token.children));
|
||||
}
|
||||
|
||||
case 'twitch': {
|
||||
return h('span', {
|
||||
style: this.$store.state.device.animatedMfm ? 'display: inline-block; animation: anime-twitch 0.5s ease infinite;' : 'display: inline-block;'
|
||||
}, genEl(token.children));
|
||||
}
|
||||
|
||||
case 'shake': {
|
||||
return h('span', {
|
||||
style: this.$store.state.device.animatedMfm ? 'display: inline-block; animation: anime-shake 0.5s ease infinite;' : 'display: inline-block;'
|
||||
}, genEl(token.children));
|
||||
}
|
||||
|
||||
case 'url': {
|
||||
return [h(MkUrl, {
|
||||
key: Math.random(),
|
||||
|
@@ -41,7 +41,7 @@
|
||||
<div class="main">
|
||||
<XNoteHeader class="header" :note="appearNote" :mini="true"/>
|
||||
<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/>
|
||||
<div class="body" ref="noteBody">
|
||||
<div class="body">
|
||||
<p v-if="appearNote.cw != null" class="cw">
|
||||
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis"/>
|
||||
<XCwButton v-model:value="showContent" :note="appearNote"/>
|
||||
@@ -54,7 +54,7 @@
|
||||
<a class="rp" v-if="appearNote.renote != null">RN:</a>
|
||||
</div>
|
||||
<div class="files" v-if="appearNote.files.length > 0">
|
||||
<XMediaList :media-list="appearNote.files" :parent-element="noteBody"/>
|
||||
<XMediaList :media-list="appearNote.files"/>
|
||||
</div>
|
||||
<XPoll v-if="appearNote.poll" :note="appearNote" ref="pollViewer" class="poll"/>
|
||||
<MkUrlPreview v-for="url in urls" :url="url" :key="url" :compact="true" :detail="detail" class="url-preview"/>
|
||||
@@ -176,7 +176,6 @@ export default defineComponent({
|
||||
showContent: false,
|
||||
isDeleted: false,
|
||||
muted: false,
|
||||
noteBody: this.$refs.noteBody,
|
||||
faEdit, faBolt, faTimes, faBullhorn, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faBiohazard, faPlug, faSatelliteDish
|
||||
};
|
||||
},
|
||||
@@ -309,8 +308,6 @@ export default defineComponent({
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
this.connection.on('_connected_', this.onStreamConnected);
|
||||
}
|
||||
|
||||
this.noteBody = this.$refs.noteBody;
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
|
@@ -22,7 +22,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faExternalLinkAlt, faExpandAlt, faLink, faChevronLeft } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faExternalLinkAlt, faExpandAlt, faLink, faChevronLeft, faColumns } from '@fortawesome/free-solid-svg-icons';
|
||||
import XWindow from '@/components/ui/window.vue';
|
||||
import XHeader from '@/ui/_common_/header.vue';
|
||||
import { popout } from '@/scripts/popout';
|
||||
@@ -35,6 +35,12 @@ export default defineComponent({
|
||||
XHeader,
|
||||
},
|
||||
|
||||
inject: {
|
||||
sideViewHook: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
|
||||
provide() {
|
||||
return {
|
||||
navHook: (url) => {
|
||||
@@ -81,7 +87,14 @@ export default defineComponent({
|
||||
icon: faExpandAlt,
|
||||
text: this.$t('showInPage'),
|
||||
action: this.expand
|
||||
}, {
|
||||
}, this.sideViewHook ? {
|
||||
icon: faColumns,
|
||||
text: this.$t('openInSideView'),
|
||||
action: () => {
|
||||
this.sideViewHook(this.url);
|
||||
this.$refs.window.close();
|
||||
}
|
||||
} : undefined, {
|
||||
icon: faExternalLinkAlt,
|
||||
text: this.$t('popout'),
|
||||
action: this.popout
|
||||
|
@@ -564,7 +564,7 @@ export default defineComponent({
|
||||
this.posting = false;
|
||||
os.dialog({
|
||||
type: 'error',
|
||||
text: err.message + '<br>' + (err as any).id,
|
||||
text: err.message + '\n' + (err as any).id,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@@ -422,9 +422,9 @@ export default defineComponent({
|
||||
> .item {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding-left: 32px;
|
||||
padding-left: 24px;
|
||||
font-size: $ui-font-size;
|
||||
line-height: 3.2rem;
|
||||
line-height: 3rem;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
@@ -7,9 +7,7 @@
|
||||
>
|
||||
<template #header>{{ $t('login') }}</template>
|
||||
|
||||
<div class="_section">
|
||||
<MkSignin :auto-set="autoSet" @login="onLogin"/>
|
||||
</div>
|
||||
<MkSignin :auto-set="autoSet" @login="onLogin"/>
|
||||
</XModalWindow>
|
||||
</template>
|
||||
|
||||
|
@@ -1,43 +1,47 @@
|
||||
<template>
|
||||
<form class="eppvobhk" :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
|
||||
<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div>
|
||||
<div class="normal-signin" v-if="!totpLogin">
|
||||
<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:value="onUsernameChange">
|
||||
<span>{{ $t('username') }}</span>
|
||||
<template #prefix>@</template>
|
||||
<template #suffix>@{{ host }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required>
|
||||
<span>{{ $t('password') }}</span>
|
||||
<template #prefix><Fa :icon="faLock"/></template>
|
||||
</MkInput>
|
||||
<MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $t('loggingIn') : $t('login') }}</MkButton>
|
||||
<a class="_panelButton" style="margin: 8px auto;" v-if="meta && meta.enableTwitterIntegration" :href="`${apiUrl}/signin/twitter`"><Fa :icon="faTwitter" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Twitter' }) }}</a>
|
||||
<a class="_panelButton" style="margin: 8px auto;" v-if="meta && meta.enableGithubIntegration" :href="`${apiUrl}/signin/github`"><Fa :icon="faGithub" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'GitHub' }) }}</a>
|
||||
<a class="_panelButton" style="margin: 8px auto;" v-if="meta && meta.enableDiscordIntegration" :href="`${apiUrl}/signin/discord`"><Fa :icon="faDiscord" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Discord' }) }}</a>
|
||||
</div>
|
||||
<div class="2fa-signin" v-if="totpLogin" :class="{ securityKeys: user && user.securityKeys }">
|
||||
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
|
||||
<p>{{ $t('tapSecurityKey') }}</p>
|
||||
<MkButton @click="queryKey" v-if="!queryingKey">
|
||||
{{ $t('retry') }}
|
||||
</MkButton>
|
||||
</div>
|
||||
<div class="or-hr" v-if="user && user.securityKeys">
|
||||
<p class="or-msg">{{ $t('or') }}</p>
|
||||
</div>
|
||||
<div class="twofa-group totp-group">
|
||||
<p style="margin-bottom:0;">{{ $t('twoStepAuthentication') }}</p>
|
||||
<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="user && user.usePasswordLessLogin" required>
|
||||
<div class="auth _section">
|
||||
<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div>
|
||||
<div class="normal-signin" v-if="!totpLogin">
|
||||
<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:value="onUsernameChange">
|
||||
<span>{{ $t('username') }}</span>
|
||||
<template #prefix>@</template>
|
||||
<template #suffix>@{{ host }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required>
|
||||
<span>{{ $t('password') }}</span>
|
||||
<template #prefix><Fa :icon="faLock"/></template>
|
||||
</MkInput>
|
||||
<MkInput v-model:value="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required>
|
||||
<span>{{ $t('token') }}</span>
|
||||
<template #prefix><Fa :icon="faGavel"/></template>
|
||||
</MkInput>
|
||||
<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $t('loggingIn') : $t('login') }}</MkButton>
|
||||
<MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $t('loggingIn') : $t('login') }}</MkButton>
|
||||
</div>
|
||||
<div class="2fa-signin" v-if="totpLogin" :class="{ securityKeys: user && user.securityKeys }">
|
||||
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
|
||||
<p>{{ $t('tapSecurityKey') }}</p>
|
||||
<MkButton @click="queryKey" v-if="!queryingKey">
|
||||
{{ $t('retry') }}
|
||||
</MkButton>
|
||||
</div>
|
||||
<div class="or-hr" v-if="user && user.securityKeys">
|
||||
<p class="or-msg">{{ $t('or') }}</p>
|
||||
</div>
|
||||
<div class="twofa-group totp-group">
|
||||
<p style="margin-bottom:0;">{{ $t('twoStepAuthentication') }}</p>
|
||||
<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="user && user.usePasswordLessLogin" required>
|
||||
<span>{{ $t('password') }}</span>
|
||||
<template #prefix><Fa :icon="faLock"/></template>
|
||||
</MkInput>
|
||||
<MkInput v-model:value="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required>
|
||||
<span>{{ $t('token') }}</span>
|
||||
<template #prefix><Fa :icon="faGavel"/></template>
|
||||
</MkInput>
|
||||
<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $t('loggingIn') : $t('login') }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="social _section">
|
||||
<a class="_borderButton _vMargin" v-if="meta && meta.enableTwitterIntegration" :href="`${apiUrl}/signin/twitter`"><Fa :icon="faTwitter" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Twitter' }) }}</a>
|
||||
<a class="_borderButton _vMargin" v-if="meta && meta.enableGithubIntegration" :href="`${apiUrl}/signin/github`"><Fa :icon="faGithub" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'GitHub' }) }}</a>
|
||||
<a class="_borderButton _vMargin" v-if="meta && meta.enableDiscordIntegration" :href="`${apiUrl}/signin/discord`"><Fa :icon="faDiscord" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Discord' }) }}</a>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -203,14 +207,16 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.eppvobhk {
|
||||
> .avatar {
|
||||
margin: 0 auto 0 auto;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: #ddd;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border-radius: 100%;
|
||||
> .auth {
|
||||
> .avatar {
|
||||
margin: 0 auto 0 auto;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: #ddd;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="pxhvhrfw" v-size="{ max: [500] }">
|
||||
<button v-for="item in items" class="_button" @click="$emit('update:value', item.value)" :class="{ active: value === item.value }" :key="item.value"><Fa v-if="item.icon" :icon="item.icon" class="icon"/>{{ item.label }}</button>
|
||||
<button v-for="item in items" class="_button" @click="$emit('update:value', item.value)" :class="{ active: value === item.value }" :disabled="value === item.value" :key="item.value"><Fa v-if="item.icon" :icon="item.icon" class="icon"/>{{ item.label }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -23,19 +23,26 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
.pxhvhrfw {
|
||||
display: flex;
|
||||
max-width: var(--baseContentWidth);
|
||||
margin: 0 auto;
|
||||
|
||||
> button {
|
||||
flex: 1;
|
||||
padding: 15px 12px 12px 12px;
|
||||
border-bottom: solid 3px transparent;
|
||||
|
||||
&:disabled {
|
||||
opacity: 1 !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--accent);
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
|
||||
&:not(.active):hover {
|
||||
color: var(--fgHighlighted);
|
||||
}
|
||||
|
||||
> .icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
70
src/client/components/taskmanager.api-window.vue
Normal file
70
src/client/components/taskmanager.api-window.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<XWindow ref="window"
|
||||
:initial-width="370"
|
||||
:initial-height="450"
|
||||
:can-resize="true"
|
||||
@close="$refs.window.close()"
|
||||
@closed="$emit('closed')"
|
||||
>
|
||||
<template #header>Req Viewer</template>
|
||||
|
||||
<div class="rlkneywz">
|
||||
<MkTab v-model:value="tab" :items="[{ label: 'Request', value: 'req', }, { label: 'Response', value: 'res', }]" style="border-bottom: solid 1px var(--divider);"/>
|
||||
|
||||
<code v-if="tab === 'req'">{{ reqStr }}</code>
|
||||
<code v-if="tab === 'res'">{{ resStr }}</code>
|
||||
</div>
|
||||
</XWindow>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import * as JSON5 from 'json5';
|
||||
import XWindow from '@/components/ui/window.vue';
|
||||
import MkTab from '@/components/tab.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
XWindow,
|
||||
MkTab,
|
||||
},
|
||||
|
||||
props: {
|
||||
req: {
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
|
||||
emits: ['closed'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
tab: 'req',
|
||||
reqStr: JSON5.stringify(this.req.req, null, '\t'),
|
||||
resStr: JSON5.stringify(this.req.res, null, '\t'),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rlkneywz {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
> code {
|
||||
display: block;
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
overflow: auto;
|
||||
font-size: 0.9em;
|
||||
tab-size: 2;
|
||||
white-space: pre;
|
||||
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
|
||||
}
|
||||
}
|
||||
</style>
|
231
src/client/components/taskmanager.vue
Normal file
231
src/client/components/taskmanager.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<XWindow ref="window" :initial-width="650" :initial-height="420" :can-resize="true" @closed="$emit('closed')">
|
||||
<template #header>
|
||||
<Fa :icon="faTerminal" style="margin-right: 0.5em;"/>Task Manager
|
||||
</template>
|
||||
<div class="qljqmnzj">
|
||||
<MkTab v-model:value="tab" :items="[{ label: 'Windows', value: 'windows', }, { label: 'Stream', value: 'stream', }, { label: 'Stream (Pool)', value: 'streamPool', }, { label: 'API', value: 'api', }]" style="border-bottom: solid 1px var(--divider);"/>
|
||||
|
||||
<div class="content">
|
||||
<div v-if="tab === 'windows'" class="windows" v-follow>
|
||||
<div class="header">
|
||||
<div>#ID</div>
|
||||
<div>Component</div>
|
||||
<div>Action</div>
|
||||
</div>
|
||||
<div v-for="p in popups">
|
||||
<div>#{{ p.id }}</div>
|
||||
<div>{{ p.component.name ? p.component.name : '<anonymous>' }}</div>
|
||||
<div><button class="_textButton" @click="killPopup(p)">Kill</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tab === 'stream'" class="stream" v-follow>
|
||||
<div class="header">
|
||||
<div>#ID</div>
|
||||
<div>Ch</div>
|
||||
<div>Handle</div>
|
||||
<div>In</div>
|
||||
<div>Out</div>
|
||||
</div>
|
||||
<div v-for="c in connections">
|
||||
<div>#{{ c.id }}</div>
|
||||
<div>{{ c.channel }}</div>
|
||||
<div v-if="c.users !== null">(shared)<span v-if="c.name">{{ ' ' + c.name }}</span></div>
|
||||
<div v-else>{{ c.name ? c.name : '<anonymous>' }}</div>
|
||||
<div>{{ c.in }}</div>
|
||||
<div>{{ c.out }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tab === 'streamPool'" class="streamPool" v-follow>
|
||||
<div class="header">
|
||||
<div>#ID</div>
|
||||
<div>Ch</div>
|
||||
<div>Users</div>
|
||||
</div>
|
||||
<div v-for="p in pools">
|
||||
<div>#{{ p.id }}</div>
|
||||
<div>{{ p.channel }}</div>
|
||||
<div>{{ p.users }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tab === 'api'" class="api" v-follow>
|
||||
<div class="header">
|
||||
<div>#ID</div>
|
||||
<div>Endpoint</div>
|
||||
<div>State</div>
|
||||
</div>
|
||||
<div v-for="req in apiRequests" @click="showReq(req)">
|
||||
<div>#{{ req.id }}</div>
|
||||
<div>{{ req.endpoint }}</div>
|
||||
<div class="state" :class="req.state">{{ req.state }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div><span class="label">Windows</span>{{ popups.length }}</div>
|
||||
<div><span class="label">Stream</span>{{ connections.length }}</div>
|
||||
<div><span class="label">Stream (Pool)</span>{{ pools.length }}</div>
|
||||
</footer>
|
||||
</div>
|
||||
</XWindow>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, markRaw, onBeforeUnmount, ref, shallowRef } from 'vue';
|
||||
import { faTerminal } from '@fortawesome/free-solid-svg-icons';
|
||||
import XWindow from '@/components/ui/window.vue';
|
||||
import MkTab from '@/components/tab.vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import follow from '@/directives/follow-append';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
XWindow,
|
||||
MkTab,
|
||||
MkButton,
|
||||
},
|
||||
|
||||
directives: {
|
||||
follow
|
||||
},
|
||||
|
||||
props: {
|
||||
},
|
||||
|
||||
emits: ['closed'],
|
||||
|
||||
setup() {
|
||||
const connections = shallowRef([]);
|
||||
const pools = shallowRef([]);
|
||||
const refreshStreamInfo = () => {
|
||||
console.log(os.stream.sharedConnectionPools, os.stream.sharedConnections, os.stream.nonSharedConnections);
|
||||
const conn = os.stream.sharedConnections.map(c => ({
|
||||
id: c.id, name: c.name, channel: c.channel, users: c.pool.users, in: c.inCount, out: c.outCount,
|
||||
})).concat(os.stream.nonSharedConnections.map(c => ({
|
||||
id: c.id, name: c.name, channel: c.channel, users: null, in: c.inCount, out: c.outCount,
|
||||
})));
|
||||
conn.sort((a, b) => (a.id > b.id) ? 1 : -1);
|
||||
connections.value = conn;
|
||||
pools.value = os.stream.sharedConnectionPools;
|
||||
};
|
||||
const interval = setInterval(refreshStreamInfo, 1000);
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(interval);
|
||||
});
|
||||
|
||||
const killPopup = p => {
|
||||
os.popups.value = os.popups.value.filter(x => x !== p);
|
||||
};
|
||||
|
||||
const showReq = async req => {
|
||||
os.popup(await import('./taskmanager.api-window.vue'), {
|
||||
req: req
|
||||
}, {
|
||||
}, 'closed');
|
||||
};
|
||||
|
||||
return {
|
||||
tab: ref('stream'),
|
||||
popups: os.popups,
|
||||
apiRequests: os.apiRequests,
|
||||
connections,
|
||||
pools,
|
||||
killPopup,
|
||||
showReq,
|
||||
faTerminal,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.qljqmnzj {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
|
||||
|
||||
> .content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
|
||||
> div {
|
||||
display: table;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
|
||||
> div {
|
||||
display: table-row;
|
||||
|
||||
&:nth-child(even) {
|
||||
//background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&.header {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: table-cell;
|
||||
white-space: nowrap;
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.api {
|
||||
> div {
|
||||
&:not(.header) {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
> .state {
|
||||
&.pending {
|
||||
color: var(--warn);
|
||||
}
|
||||
|
||||
&.success {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
&.failed {
|
||||
color: var(--error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> footer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
box-sizing: border-box;
|
||||
border-top: solid 1px var(--divider);
|
||||
font-size: 0.9em;
|
||||
|
||||
> div {
|
||||
flex: 1;
|
||||
|
||||
> .label {
|
||||
opacity: 0.7;
|
||||
margin-right: 0.5em;
|
||||
|
||||
&:after {
|
||||
content: ":";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -10,7 +10,7 @@ import { faExpandAlt, faColumns, faExternalLinkAlt, faLink, faWindowMaximize } f
|
||||
import * as os from '@/os';
|
||||
import copyToClipboard from '@/scripts/copy-to-clipboard';
|
||||
import { router } from '@/router';
|
||||
import { deckmode } from '@/config';
|
||||
import { deckmode, url } from '@/config';
|
||||
|
||||
export default defineComponent({
|
||||
inject: {
|
||||
@@ -60,7 +60,7 @@ export default defineComponent({
|
||||
action: () => {
|
||||
os.pageWindow(this.to);
|
||||
}
|
||||
}, !this.navHook && this.sideViewHook ? {
|
||||
}, this.sideViewHook ? {
|
||||
icon: faColumns,
|
||||
text: this.$t('openInSideView'),
|
||||
action: () => {
|
||||
@@ -82,7 +82,7 @@ export default defineComponent({
|
||||
icon: faLink,
|
||||
text: this.$t('copyLink'),
|
||||
action: () => {
|
||||
copyToClipboard(this.to);
|
||||
copyToClipboard(`${url}${this.to}`);
|
||||
}
|
||||
}], e);
|
||||
},
|
||||
|
@@ -119,6 +119,9 @@ export default defineComponent({
|
||||
z: Number(document.defaultView.getComputedStyle(this.$el, null).zIndex)
|
||||
});
|
||||
|
||||
// 他のウィンドウ内のボタンなどを押してこのウィンドウが開かれた場合、親が最前面になろうとするのでそれに隠されないようにする
|
||||
this.top();
|
||||
|
||||
window.addEventListener('resize', this.onBrowserResize);
|
||||
},
|
||||
|
||||
|
@@ -14,3 +14,4 @@ export const getLocale = async () => Object.fromEntries((await entries(clientDb.
|
||||
export const version = _VERSION_;
|
||||
export const instanceName = siteName === 'Misskey' ? host : siteName;
|
||||
export const deckmode = localStorage.getItem('deckmode') === 'true';
|
||||
export const debug = localStorage.getItem('debug') === 'true';
|
||||
|
25
src/client/directives/follow-append.ts
Normal file
25
src/client/directives/follow-append.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Directive } from 'vue';
|
||||
import { getScrollContainer, getScrollPosition } from '@/scripts/scroll';
|
||||
|
||||
export default {
|
||||
mounted(src, binding, vn) {
|
||||
const ro = new ResizeObserver((entries, observer) => {
|
||||
const pos = getScrollPosition(src);
|
||||
const container = getScrollContainer(src);
|
||||
const viewHeight = container.clientHeight;
|
||||
const height = container.scrollHeight;
|
||||
if (pos + viewHeight > height - 32) {
|
||||
container.scrollTop = height;
|
||||
}
|
||||
});
|
||||
|
||||
ro.observe(src);
|
||||
|
||||
// TODO: 新たにプロパティを作るのをやめMapを使う
|
||||
src._ro_ = ro;
|
||||
},
|
||||
|
||||
unmounted(src, binding, vn) {
|
||||
src._ro_.unobserve(src);
|
||||
}
|
||||
} as Directive;
|
@@ -252,7 +252,7 @@ if (store.getters.isSignedIn) {
|
||||
}
|
||||
}
|
||||
|
||||
const main = stream.useSharedConnection('main');
|
||||
const main = stream.useSharedConnection('main', 'System');
|
||||
|
||||
// 自分の情報が更新されたとき
|
||||
main.on('meUpdated', i => {
|
||||
|
@@ -2,7 +2,7 @@ import { Component, defineAsyncComponent, markRaw, reactive, Ref, ref } from 'vu
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import Stream from '@/scripts/stream';
|
||||
import { store } from '@/store';
|
||||
import { apiUrl } from '@/config';
|
||||
import { apiUrl, debug } from '@/config';
|
||||
import MkPostFormDialog from '@/components/post-form-dialog.vue';
|
||||
import MkWaitingDialog from '@/components/waiting-dialog.vue';
|
||||
import { resolve } from '@/router';
|
||||
@@ -13,28 +13,30 @@ export const isMobile = /mobile|iphone|ipad|android/.test(ua);
|
||||
export const stream = markRaw(new Stream());
|
||||
|
||||
export const pendingApiRequestsCount = ref(0);
|
||||
let apiRequestsCount = 0; // for debug
|
||||
export const apiRequests = ref([]); // for debug
|
||||
|
||||
export const windows = new Map();
|
||||
|
||||
export function api(endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined) {
|
||||
pendingApiRequestsCount.value++;
|
||||
|
||||
if (_DEV_) {
|
||||
performance.mark(_PERF_PREFIX_ + 'api:begin');
|
||||
}
|
||||
|
||||
const onFinally = () => {
|
||||
pendingApiRequestsCount.value--;
|
||||
|
||||
if (_DEV_) {
|
||||
performance.mark(_PERF_PREFIX_ + 'api:end');
|
||||
|
||||
performance.measure(_PERF_PREFIX_ + 'api',
|
||||
_PERF_PREFIX_ + 'api:begin',
|
||||
_PERF_PREFIX_ + 'api:end');
|
||||
}
|
||||
};
|
||||
|
||||
const log = debug ? reactive({
|
||||
id: ++apiRequestsCount,
|
||||
endpoint,
|
||||
req: markRaw(data),
|
||||
res: null,
|
||||
state: 'pending',
|
||||
}) : null;
|
||||
if (debug) {
|
||||
apiRequests.value.push(log);
|
||||
if (apiRequests.value.length > 128) apiRequests.value.shift();
|
||||
}
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
// Append a credential
|
||||
if (store.getters.isSignedIn) (data as any).i = store.state.i.token;
|
||||
@@ -51,10 +53,21 @@ export function api(endpoint: string, data: Record<string, any> = {}, token?: st
|
||||
|
||||
if (res.status === 200) {
|
||||
resolve(body);
|
||||
if (debug) {
|
||||
log.res = markRaw(body);
|
||||
log.state = 'success';
|
||||
}
|
||||
} else if (res.status === 204) {
|
||||
resolve();
|
||||
if (debug) {
|
||||
log.state = 'success';
|
||||
}
|
||||
} else {
|
||||
reject(body.error);
|
||||
if (debug) {
|
||||
log.res = markRaw(body.error);
|
||||
log.state = 'failed';
|
||||
}
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
@@ -75,7 +88,7 @@ export function apiWithDialog(
|
||||
promiseDialog(promise, onSuccess, onFailure ? onFailure : (e) => {
|
||||
dialog({
|
||||
type: 'error',
|
||||
text: e.message + '<br>' + (e as any).id,
|
||||
text: e.message + '\n' + (e as any).id,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -127,6 +140,7 @@ function isModule(x: any): x is typeof import('*.vue') {
|
||||
return x.default != null;
|
||||
}
|
||||
|
||||
let popupIdCount = 0;
|
||||
export const popups = ref([]) as Ref<{
|
||||
id: any;
|
||||
component: any;
|
||||
@@ -137,7 +151,7 @@ export function popup(component: Component | typeof import('*.vue'), props: Reco
|
||||
if (isModule(component)) component = component.default;
|
||||
markRaw(component);
|
||||
|
||||
const id = Math.random().toString(); // TODO: uuidとか使う
|
||||
const id = ++popupIdCount;
|
||||
const dispose = () => {
|
||||
if (_DEV_) console.log('os:popup close', id, component, props, events);
|
||||
// このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ?
|
||||
|
@@ -42,7 +42,8 @@
|
||||
<small style="opacity: 0.7;">{{ file.name }}</small>
|
||||
</div>
|
||||
<div>
|
||||
<MkAcct :user="file.user"/>
|
||||
<MkAcct v-if="file.user" :user="file.user"/>
|
||||
<div v-else>{{ $t('system') }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span style="margin-right: 1em;">{{ file.type }}</span>
|
||||
|
@@ -231,7 +231,7 @@ export default defineComponent({
|
||||
set(pos) {
|
||||
if (this.game.isEnded) return;
|
||||
if (!this.iAmPlayer) return;
|
||||
if (!this.isMyTurn) return;
|
||||
if (!this.isMyTurn()) return;
|
||||
if (!this.o.canPut(this.myColor, pos)) return;
|
||||
|
||||
this.o.put(this.myColor, pos);
|
||||
|
@@ -10,22 +10,31 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="_section">
|
||||
<MkA to="/settings/regedit">RegEdit</MkA>
|
||||
<MkSwitch v-model:value="debug" @update:value="changeDebug">
|
||||
DEBUG MODE
|
||||
</MkSwitch>
|
||||
<div v-if="debug">
|
||||
<MkA to="/settings/regedit">RegEdit</MkA>
|
||||
<MkButton @click="taskmanager">Task Manager</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineAsyncComponent, defineComponent } from 'vue';
|
||||
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkSelect from '@/components/ui/select.vue';
|
||||
import MkSwitch from '@/components/ui/switch.vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import * as os from '@/os';
|
||||
import { debug } from '@/config';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkSelect,
|
||||
MkSwitch,
|
||||
MkButton,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
@@ -38,6 +47,7 @@ export default defineComponent({
|
||||
icon: faEllipsisH
|
||||
}]
|
||||
},
|
||||
debug
|
||||
}
|
||||
},
|
||||
|
||||
@@ -46,11 +56,22 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeDebug(v) {
|
||||
console.log(v);
|
||||
localStorage.setItem('debug', v.toString());
|
||||
location.reload();
|
||||
},
|
||||
|
||||
onChangeInjectFeaturedNote(v) {
|
||||
os.api('i/update', {
|
||||
injectFeaturedNote: v
|
||||
});
|
||||
},
|
||||
|
||||
taskmanager() {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/taskmanager.vue')), {
|
||||
}, {}, 'closed');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -10,6 +10,10 @@
|
||||
<MkInput v-model:value="dialogBody">
|
||||
<span>Body</span>
|
||||
</MkInput>
|
||||
<MkRadio v-model="dialogType" value="info">Info</MkRadio>
|
||||
<MkRadio v-model="dialogType" value="success">Success</MkRadio>
|
||||
<MkRadio v-model="dialogType" value="warning">Warn</MkRadio>
|
||||
<MkRadio v-model="dialogType" value="error">Error</MkRadio>
|
||||
<MkSwitch v-model:value="dialogCancel">
|
||||
<span>With cancel button</span>
|
||||
</MkSwitch>
|
||||
@@ -133,6 +137,7 @@ import MkButton from '@/components/ui/button.vue';
|
||||
import MkInput from '@/components/ui/input.vue';
|
||||
import MkSwitch from '@/components/ui/switch.vue';
|
||||
import MkTextarea from '@/components/ui/textarea.vue';
|
||||
import MkRadio from '@/components/ui/radio.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -141,6 +146,7 @@ export default defineComponent({
|
||||
MkInput,
|
||||
MkSwitch,
|
||||
MkTextarea,
|
||||
MkRadio,
|
||||
},
|
||||
|
||||
data() {
|
||||
@@ -153,6 +159,7 @@ export default defineComponent({
|
||||
},
|
||||
dialogTitle: 'Hello',
|
||||
dialogBody: 'World!',
|
||||
dialogType: 'info',
|
||||
dialogCancel: false,
|
||||
dialogCancelByBgClick: true,
|
||||
dialogInput: false,
|
||||
@@ -192,6 +199,7 @@ export default defineComponent({
|
||||
async showDialog() {
|
||||
this.dialogResult = null;
|
||||
this.dialogResult = await os.dialog({
|
||||
type: this.dialogType,
|
||||
title: this.dialogTitle,
|
||||
text: this.dialogBody,
|
||||
showCancelButton: this.dialogCancel,
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import autobind from 'autobind-decorator';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import ReconnectingWebsocket from 'reconnecting-websocket';
|
||||
import { wsUrl } from '@/config';
|
||||
import { markRaw } from 'vue';
|
||||
import { debug, wsUrl } from '@/config';
|
||||
import { query as urlQuery } from '../../prelude/url';
|
||||
|
||||
/**
|
||||
@@ -28,7 +29,7 @@ export default class Stream extends EventEmitter {
|
||||
}
|
||||
|
||||
@autobind
|
||||
public useSharedConnection(channel: string): SharedConnection {
|
||||
public useSharedConnection(channel: string, name?: string): SharedConnection {
|
||||
let pool = this.sharedConnectionPools.find(p => p.channel === channel);
|
||||
|
||||
if (pool == null) {
|
||||
@@ -36,7 +37,7 @@ export default class Stream extends EventEmitter {
|
||||
this.sharedConnectionPools.push(pool);
|
||||
}
|
||||
|
||||
const connection = new SharedConnection(this, channel, pool);
|
||||
const connection = markRaw(new SharedConnection(this, channel, pool, name));
|
||||
this.sharedConnections.push(connection);
|
||||
return connection;
|
||||
}
|
||||
@@ -53,7 +54,7 @@ export default class Stream extends EventEmitter {
|
||||
|
||||
@autobind
|
||||
public connectToChannel(channel: string, params?: any): NonSharedConnection {
|
||||
const connection = new NonSharedConnection(this, channel, params);
|
||||
const connection = markRaw(new NonSharedConnection(this, channel, params));
|
||||
this.nonSharedConnections.push(connection);
|
||||
return connection;
|
||||
}
|
||||
@@ -113,6 +114,7 @@ export default class Stream extends EventEmitter {
|
||||
|
||||
for (const c of connections.filter(c => c != null)) {
|
||||
c.emit(body.type, Object.freeze(body.body));
|
||||
if (debug) c.inCount++;
|
||||
}
|
||||
} else {
|
||||
this.emit(type, Object.freeze(body));
|
||||
@@ -142,6 +144,8 @@ export default class Stream extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
let idCounter = 0;
|
||||
|
||||
class Pool {
|
||||
public channel: string;
|
||||
public id: string;
|
||||
@@ -154,7 +158,7 @@ class Pool {
|
||||
this.channel = channel;
|
||||
this.stream = stream;
|
||||
|
||||
this.id = Math.random().toString().substr(2, 8);
|
||||
this.id = (++idCounter).toString();
|
||||
|
||||
this.stream.on('_disconnected_', this.onStreamDisconnected);
|
||||
}
|
||||
@@ -216,11 +220,16 @@ abstract class Connection extends EventEmitter {
|
||||
protected stream: Stream;
|
||||
public abstract id: string;
|
||||
|
||||
constructor(stream: Stream, channel: string) {
|
||||
public name?: string; // for debug
|
||||
public inCount: number = 0; // for debug
|
||||
public outCount: number = 0; // for debug
|
||||
|
||||
constructor(stream: Stream, channel: string, name?: string) {
|
||||
super();
|
||||
|
||||
this.stream = stream;
|
||||
this.channel = channel;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@autobind
|
||||
@@ -233,6 +242,8 @@ abstract class Connection extends EventEmitter {
|
||||
type: type,
|
||||
body: body
|
||||
});
|
||||
|
||||
if (debug) this.outCount++;
|
||||
}
|
||||
|
||||
public abstract dispose(): void;
|
||||
@@ -245,8 +256,8 @@ class SharedConnection extends Connection {
|
||||
return this.pool.id;
|
||||
}
|
||||
|
||||
constructor(stream: Stream, channel: string, pool: Pool) {
|
||||
super(stream, channel);
|
||||
constructor(stream: Stream, channel: string, pool: Pool, name?: string) {
|
||||
super(stream, channel, name);
|
||||
|
||||
this.pool = pool;
|
||||
this.pool.inc();
|
||||
@@ -273,7 +284,7 @@ class NonSharedConnection extends Connection {
|
||||
super(stream, channel);
|
||||
|
||||
this.params = params;
|
||||
this.id = Math.random().toString().substr(2, 8);
|
||||
this.id = (++idCounter).toString();
|
||||
|
||||
this.connect();
|
||||
}
|
||||
|
@@ -297,6 +297,21 @@ hr {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
._borderButton {
|
||||
@extend ._button;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: var(--radius);
|
||||
|
||||
&:active {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
._popup {
|
||||
background: var(--panel);
|
||||
border-radius: var(--radius);
|
||||
@@ -484,6 +499,60 @@ hr {
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
|
||||
// const val = () => `translate(${Math.floor(Math.random() * 20) - 10}px, ${Math.floor(Math.random() * 20) - 10}px)`;
|
||||
// let css = '';
|
||||
// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; }
|
||||
@keyframes anime-twitch {
|
||||
0% { transform: translate(7px, -2px) }
|
||||
5% { transform: translate(-3px, 1px) }
|
||||
10% { transform: translate(-7px, -1px) }
|
||||
15% { transform: translate(0px, -1px) }
|
||||
20% { transform: translate(-8px, 6px) }
|
||||
25% { transform: translate(-4px, -3px) }
|
||||
30% { transform: translate(-4px, -6px) }
|
||||
35% { transform: translate(-8px, -8px) }
|
||||
40% { transform: translate(4px, 6px) }
|
||||
45% { transform: translate(-3px, 1px) }
|
||||
50% { transform: translate(2px, -10px) }
|
||||
55% { transform: translate(-7px, 0px) }
|
||||
60% { transform: translate(-2px, 4px) }
|
||||
65% { transform: translate(3px, -8px) }
|
||||
70% { transform: translate(6px, 7px) }
|
||||
75% { transform: translate(-7px, -2px) }
|
||||
80% { transform: translate(-7px, -8px) }
|
||||
85% { transform: translate(9px, 3px) }
|
||||
90% { transform: translate(-3px, -2px) }
|
||||
95% { transform: translate(-10px, 2px) }
|
||||
100% { transform: translate(-2px, -6px) }
|
||||
}
|
||||
|
||||
// const val = () => `translate(${Math.floor(Math.random() * 6) - 3}px, ${Math.floor(Math.random() * 6) - 3}px) rotate(${Math.floor(Math.random() * 24) - 12}deg)`;
|
||||
// let css = '';
|
||||
// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; }
|
||||
@keyframes anime-shake {
|
||||
0% { transform: translate(-3px, -1px) rotate(-8deg) }
|
||||
5% { transform: translate(0px, -1px) rotate(-10deg) }
|
||||
10% { transform: translate(1px, -3px) rotate(0deg) }
|
||||
15% { transform: translate(1px, 1px) rotate(11deg) }
|
||||
20% { transform: translate(-2px, 1px) rotate(1deg) }
|
||||
25% { transform: translate(-1px, -2px) rotate(-2deg) }
|
||||
30% { transform: translate(-1px, 2px) rotate(-3deg) }
|
||||
35% { transform: translate(2px, 1px) rotate(6deg) }
|
||||
40% { transform: translate(-2px, -3px) rotate(-9deg) }
|
||||
45% { transform: translate(0px, -1px) rotate(-12deg) }
|
||||
50% { transform: translate(1px, 2px) rotate(10deg) }
|
||||
55% { transform: translate(0px, -3px) rotate(8deg) }
|
||||
60% { transform: translate(1px, -1px) rotate(8deg) }
|
||||
65% { transform: translate(0px, -1px) rotate(-7deg) }
|
||||
70% { transform: translate(-1px, -3px) rotate(6deg) }
|
||||
75% { transform: translate(0px, -2px) rotate(4deg) }
|
||||
80% { transform: translate(-2px, -1px) rotate(3deg) }
|
||||
85% { transform: translate(1px, -3px) rotate(-10deg) }
|
||||
90% { transform: translate(1px, 0px) rotate(3deg) }
|
||||
95% { transform: translate(-2px, 0px) rotate(-3deg) }
|
||||
100% { transform: translate(2px, 1px) rotate(2deg) }
|
||||
}
|
||||
|
||||
@keyframes anime-tada {
|
||||
from {
|
||||
transform: scale3d(1, 1, 1);
|
||||
|
@@ -56,6 +56,9 @@
|
||||
wallpaperOverlay: 'rgba(0, 0, 0, 0.5)',
|
||||
badge: '#31b1ce',
|
||||
messageBg: ':lighten<5<@bg',
|
||||
success: '#86b300',
|
||||
error: '#ec4137',
|
||||
warn: '#ecb637',
|
||||
htmlThemeColor: '@bg',
|
||||
X1: ':alpha<0<@bg',
|
||||
X2: ':darken<2<@panel',
|
||||
|
@@ -56,6 +56,9 @@
|
||||
wallpaperOverlay: 'rgba(255, 255, 255, 0.5)',
|
||||
badge: '#31b1ce',
|
||||
messageBg: '@panel',
|
||||
success: '#86b300',
|
||||
error: '#ec4137',
|
||||
warn: '#ecb637',
|
||||
htmlThemeColor: '@bg',
|
||||
X1: ':alpha<0<@bg',
|
||||
X2: ':darken<2<@panel',
|
||||
|
@@ -109,7 +109,7 @@ export default defineComponent({
|
||||
window.addEventListener('wheel', this.onWheel);
|
||||
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
this.connection = os.stream.useSharedConnection('main');
|
||||
this.connection = os.stream.useSharedConnection('main', 'UI');
|
||||
this.connection.on('notification', this.onNotification);
|
||||
}
|
||||
},
|
||||
|
@@ -110,7 +110,7 @@ export default defineComponent({
|
||||
},
|
||||
'p': os.post,
|
||||
'n': os.post,
|
||||
's': search,
|
||||
's': () => search(),
|
||||
'h|/': this.help
|
||||
};
|
||||
},
|
||||
@@ -141,7 +141,7 @@ export default defineComponent({
|
||||
created() {
|
||||
document.documentElement.style.overflowY = 'scroll';
|
||||
|
||||
this.connection = os.stream.useSharedConnection('main');
|
||||
this.connection = os.stream.useSharedConnection('main', 'UI');
|
||||
this.connection.on('notification', this.onNotification);
|
||||
|
||||
if (this.$store.state.deviceUser.widgets.length === 0) {
|
||||
|
@@ -71,7 +71,7 @@ export default defineComponent({
|
||||
created() {
|
||||
document.documentElement.style.overflowY = 'scroll';
|
||||
|
||||
this.connection = os.stream.useSharedConnection('main');
|
||||
this.connection = os.stream.useSharedConnection('main', 'UI');
|
||||
this.connection.on('notification', this.onNotification);
|
||||
},
|
||||
|
||||
|
@@ -76,6 +76,8 @@ export const mfmLanguage = P.createLanguage({
|
||||
r.spin,
|
||||
r.jump,
|
||||
r.flip,
|
||||
r.twitch,
|
||||
r.shake,
|
||||
r.inlineCode,
|
||||
r.mathInline,
|
||||
r.mention,
|
||||
@@ -122,6 +124,8 @@ export const mfmLanguage = P.createLanguage({
|
||||
},
|
||||
jump: r => P.regexp(/<jump>(.+?)<\/jump>/, 1).map(x => createTree('jump', r.inline.atLeast(1).tryParse(x), {})),
|
||||
flip: r => P.regexp(/<flip>(.+?)<\/flip>/, 1).map(x => createTree('flip', r.inline.atLeast(1).tryParse(x), {})),
|
||||
twitch: r => P.regexp(/<twitch>(.+?)<\/twitch>/, 1).map(x => createTree('twitch', r.inline.atLeast(1).tryParse(x), {})),
|
||||
shake: r => P.regexp(/<shake>(.+?)<\/shake>/, 1).map(x => createTree('shake', r.inline.atLeast(1).tryParse(x), {})),
|
||||
center: r => r.startOfLine.then(P.regexp(/<center>([\s\S]+?)<\/center>/, 1).map(x => createTree('center', r.inline.atLeast(1).tryParse(x), {}))),
|
||||
inlineCode: () => P.regexp(/`([^´\n]+?)`/, 1).map(x => createLeaf('inlineCode', { code: x })),
|
||||
mathBlock: r => r.startOfLine.then(P.regexp(/\\\[([\s\S]+?)\\\]/, 1).map(x => createLeaf('mathBlock', { formula: x.trim() }))),
|
||||
|
@@ -67,6 +67,18 @@ export function toHtml(tokens: MfmForest | null, mentionedRemoteUsers: IMentione
|
||||
return el;
|
||||
},
|
||||
|
||||
twitch(token) {
|
||||
const el = doc.createElement('i');
|
||||
appendChildren(token.children, el);
|
||||
return el;
|
||||
},
|
||||
|
||||
shake(token) {
|
||||
const el = doc.createElement('i');
|
||||
appendChildren(token.children, el);
|
||||
return el;
|
||||
},
|
||||
|
||||
flip(token) {
|
||||
const el = doc.createElement('span');
|
||||
appendChildren(token.children, el);
|
||||
|
@@ -48,6 +48,14 @@ export function toString(tokens: MfmForest | null, opts?: RestoreOptions): strin
|
||||
return `<jump>${appendChildren(token.children, opts)}</jump>`;
|
||||
},
|
||||
|
||||
twitch(token, opts) {
|
||||
return `<twitch>${appendChildren(token.children, opts)}</twitch>`;
|
||||
},
|
||||
|
||||
shake(token, opts) {
|
||||
return `<shake>${appendChildren(token.children, opts)}</shake>`;
|
||||
},
|
||||
|
||||
flip(token, opts) {
|
||||
return `<flip>${appendChildren(token.children, opts)}</flip>`;
|
||||
},
|
||||
|
@@ -124,8 +124,8 @@ export class DriveFileRepository extends Repository<DriveFile> {
|
||||
folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
|
||||
detail: true
|
||||
}) : null,
|
||||
userId: opts.withUser ? file.userId! : null,
|
||||
user: opts.withUser ? Users.pack(file.userId!) : null
|
||||
userId: opts.withUser ? file.userId : null,
|
||||
user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,8 @@ import config from '../../config';
|
||||
import { Note } from '../../models/entities/note';
|
||||
import { User, IRemoteUser } from '../../models/entities/user';
|
||||
import { UserPublickey } from '../../models/entities/user-publickey';
|
||||
import { Notes, Users, UserPublickeys } from '../../models';
|
||||
import { MessagingMessage } from '../../models/entities/messaging-message';
|
||||
import { Notes, Users, UserPublickeys, MessagingMessages } from '../../models';
|
||||
import { IObject, getApId } from './type';
|
||||
import { resolvePerson } from './models/person';
|
||||
import { ensure } from '../../prelude/ensure';
|
||||
@@ -33,6 +34,24 @@ export default class DbResolver {
|
||||
return null;
|
||||
}
|
||||
|
||||
public async getMessageFromApId(value: string | IObject): Promise<MessagingMessage | null> {
|
||||
const parsed = this.parseUri(value);
|
||||
|
||||
if (parsed.id) {
|
||||
return (await MessagingMessages.findOne({
|
||||
id: parsed.id
|
||||
})) || null;
|
||||
}
|
||||
|
||||
if (parsed.uri) {
|
||||
return (await MessagingMessages.findOne({
|
||||
uri: parsed.uri
|
||||
})) || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* AP Person => Misskey User in DB
|
||||
*/
|
||||
|
@@ -3,6 +3,7 @@ import deleteNode from '../../../../services/note/delete';
|
||||
import { apLogger } from '../../logger';
|
||||
import DbResolver from '../../db-resolver';
|
||||
import { getApLock } from '../../../../misc/app-lock';
|
||||
import { deleteMessage } from '../../../../services/messages/delete';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
@@ -16,7 +17,16 @@ export default async function(actor: IRemoteUser, uri: string): Promise<string>
|
||||
const note = await dbResolver.getNoteFromApId(uri);
|
||||
|
||||
if (note == null) {
|
||||
return 'note not found';
|
||||
const message = await dbResolver.getMessageFromApId(uri);
|
||||
if (message == null) return 'message not found';
|
||||
|
||||
if (message.userId !== actor.id) {
|
||||
return '投稿を削除しようとしているユーザーは投稿の作成者ではありません';
|
||||
}
|
||||
|
||||
await deleteMessage(message);
|
||||
|
||||
return 'ok: message deleted';
|
||||
}
|
||||
|
||||
if (note.userId !== actor.id) {
|
||||
@@ -24,7 +34,7 @@ export default async function(actor: IRemoteUser, uri: string): Promise<string>
|
||||
}
|
||||
|
||||
await deleteNode(actor, note);
|
||||
return 'ok: deleted';
|
||||
return 'ok: note deleted';
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
|
@@ -87,5 +87,7 @@ export default define(meta, async (ps, user) => {
|
||||
noteUserId: blockee.id
|
||||
});
|
||||
|
||||
return await Users.pack(blockee.id, user);
|
||||
return await Users.pack(blockee.id, user, {
|
||||
detail: true
|
||||
});
|
||||
});
|
||||
|
@@ -82,5 +82,7 @@ export default define(meta, async (ps, user) => {
|
||||
// Delete blocking
|
||||
await deleteBlocking(blocker, blockee);
|
||||
|
||||
return await Users.pack(blockee.id, user);
|
||||
return await Users.pack(blockee.id, user, {
|
||||
detail: true
|
||||
});
|
||||
});
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '../../../../../misc/cafy-id';
|
||||
import define from '../../../define';
|
||||
import { publishMessagingStream, publishGroupMessagingStream } from '../../../../../services/stream';
|
||||
import * as ms from 'ms';
|
||||
import { ApiError } from '../../../error';
|
||||
import { MessagingMessages } from '../../../../../models';
|
||||
import { deleteMessage } from '../../../../../services/messages/delete';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -53,12 +53,5 @@ export default define(meta, async (ps, user) => {
|
||||
throw new ApiError(meta.errors.noSuchMessage);
|
||||
}
|
||||
|
||||
await MessagingMessages.delete(message.id);
|
||||
|
||||
if (message.recipientId) {
|
||||
publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id);
|
||||
publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id);
|
||||
} else if (message.groupId) {
|
||||
publishGroupMessagingStream(message.groupId, 'deleted', message.id);
|
||||
}
|
||||
await deleteMessage(message);
|
||||
});
|
||||
|
31
src/services/messages/delete.ts
Normal file
31
src/services/messages/delete.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import config from '../../config';
|
||||
import { ensure } from '../../prelude/ensure';
|
||||
import { MessagingMessages, Users } from '../../models';
|
||||
import { MessagingMessage } from '../../models/entities/messaging-message';
|
||||
import { publishGroupMessagingStream, publishMessagingStream } from '../stream';
|
||||
import { renderActivity } from '../../remote/activitypub/renderer';
|
||||
import renderDelete from '../../remote/activitypub/renderer/delete';
|
||||
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
|
||||
import { deliver } from '../../queue';
|
||||
|
||||
export async function deleteMessage(message: MessagingMessage) {
|
||||
await MessagingMessages.delete(message.id);
|
||||
postDeleteMessage(message);
|
||||
}
|
||||
|
||||
async function postDeleteMessage(message: MessagingMessage) {
|
||||
if (message.recipientId) {
|
||||
const user = await Users.findOne(message.userId).then(ensure);
|
||||
const recipient = await Users.findOne(message.recipientId).then(ensure);
|
||||
|
||||
if (Users.isLocalUser(user)) publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id);
|
||||
if (Users.isLocalUser(recipient)) publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id);
|
||||
|
||||
if (Users.isLocalUser(user) && Users.isRemoteUser(recipient)) {
|
||||
const activity = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${message.id}`), user));
|
||||
deliver(user, activity, recipient.inbox);
|
||||
}
|
||||
} else if (message.groupId) {
|
||||
publishGroupMessagingStream(message.groupId, 'deleted', message.id);
|
||||
}
|
||||
}
|
32
src/tools/demote-admin.ts
Normal file
32
src/tools/demote-admin.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { initDb } from '../db/postgre';
|
||||
import { getRepository } from 'typeorm';
|
||||
import { User } from '../models/entities/user';
|
||||
|
||||
async function main(username: string) {
|
||||
if (!username) throw `username required`;
|
||||
username = username.replace(/^@/, '');
|
||||
|
||||
await initDb();
|
||||
const Users = getRepository(User);
|
||||
|
||||
const res = await Users.update({
|
||||
usernameLower: username.toLowerCase(),
|
||||
host: null
|
||||
}, {
|
||||
isAdmin: false
|
||||
});
|
||||
|
||||
if (res.affected !== 1) {
|
||||
throw 'Failed';
|
||||
}
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
main(args[0]).then(() => {
|
||||
console.log('Success');
|
||||
process.exit(0);
|
||||
}).catch(e => {
|
||||
console.error(`Error: ${e.message || e}`);
|
||||
process.exit(1);
|
||||
});
|
52
yarn.lock
52
yarn.lock
@@ -3476,7 +3476,7 @@ enhanced-resolve@^4.0.0:
|
||||
memory-fs "^0.5.0"
|
||||
tapable "^1.0.0"
|
||||
|
||||
enhanced-resolve@^5.3.0:
|
||||
enhanced-resolve@^5.3.1:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.3.1.tgz#3f988d0d7775bdc2d96ede321dc81f8249492f57"
|
||||
integrity sha512-G1XD3MRGrGfNcf6Hg0LVZG7GIKcYkbfHa5QMxt1HDUTdYoXH0JR1xXyg+MaKLF73E9A27uWNVxvFivNRYeUB6w==
|
||||
@@ -5498,10 +5498,10 @@ iterate-value@^1.0.0:
|
||||
es-get-iterator "^1.0.2"
|
||||
iterate-iterator "^1.0.1"
|
||||
|
||||
jest-worker@^26.5.0:
|
||||
version "26.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.5.0.tgz#87deee86dbbc5f98d9919e0dadf2c40e3152fa30"
|
||||
integrity sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug==
|
||||
jest-worker@^26.6.1:
|
||||
version "26.6.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.1.tgz#c2ae8cde6802cc14056043f997469ec170d9c32a"
|
||||
integrity sha512-R5IE3qSGz+QynJx8y+ICEkdI2OJ3RJjRQVEyCcFAd3yVhQSEtquziPO29Mlzgn07LOVE8u8jhJ1FqcwegiXWOw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
merge-stream "^2.0.0"
|
||||
@@ -9713,17 +9713,17 @@ tar@^6.0.2:
|
||||
mkdirp "^1.0.3"
|
||||
yallist "^4.0.0"
|
||||
|
||||
terser-webpack-plugin@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.0.0.tgz#88f58d27d1c8244965c59540d3ccda1598fc958c"
|
||||
integrity sha512-rf7l5a9xamIVX3enQeTl0MY2MNeZClo5yPX/tVPy22oY0nzu0b45h7JqyFi/bygqKWtzXMnml0u12mArhQPsBQ==
|
||||
terser-webpack-plugin@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.0.3.tgz#ec60542db2421f45735c719d2e17dabfbb2e3e42"
|
||||
integrity sha512-zFdGk8Lh9ZJGPxxPE6jwysOlATWB8GMW8HcfGULWA/nPal+3VdATflQvSBSLQJRCmYZnfFJl6vkRTiwJGNgPiQ==
|
||||
dependencies:
|
||||
jest-worker "^26.5.0"
|
||||
jest-worker "^26.6.1"
|
||||
p-limit "^3.0.2"
|
||||
schema-utils "^3.0.0"
|
||||
serialize-javascript "^5.0.1"
|
||||
source-map "^0.6.1"
|
||||
terser "^5.3.5"
|
||||
terser "^5.3.8"
|
||||
|
||||
terser@>=4:
|
||||
version "4.8.0"
|
||||
@@ -9734,10 +9734,10 @@ terser@>=4:
|
||||
source-map "~0.6.1"
|
||||
source-map-support "~0.5.12"
|
||||
|
||||
terser@^5.3.5:
|
||||
version "5.3.5"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.5.tgz#9e080baa0568f96654621b20eb9effa440b1484e"
|
||||
integrity sha512-Qw3CZAMmmfU824AoGKalx+riwocSI5Cs0PoGp9RdSLfmxkmJgyBxqLBP/isDNtFyhHnitikvRMZzyVgeq+U+Tg==
|
||||
terser@^5.3.8:
|
||||
version "5.3.8"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.8.tgz#991ae8ba21a3d990579b54aa9af11586197a75dd"
|
||||
integrity sha512-zVotuHoIfnYjtlurOouTazciEfL7V38QMAOhGqpXDEg6yT13cF4+fEP9b0rrCEQTn+tT46uxgFsTZzhygk+CzQ==
|
||||
dependencies:
|
||||
commander "^2.20.0"
|
||||
source-map "~0.7.2"
|
||||
@@ -10633,18 +10633,18 @@ webpack-sources@^1.0.1:
|
||||
source-list-map "^2.0.0"
|
||||
source-map "~0.6.1"
|
||||
|
||||
webpack-sources@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.0.1.tgz#1467f6e692ddce91e88b8044c44347b1087bbd4f"
|
||||
integrity sha512-A9oYz7ANQBK5EN19rUXbvNgfdfZf5U2gP0769OXsj9CvYkCR6OHOsd6OKyEy4H38GGxpsQPKIL83NC64QY6Xmw==
|
||||
webpack-sources@^2.1.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac"
|
||||
integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==
|
||||
dependencies:
|
||||
source-list-map "^2.0.1"
|
||||
source-map "^0.6.1"
|
||||
|
||||
webpack@5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.2.0.tgz#02f22466b79751a80a50f20f027a716e296b3ef5"
|
||||
integrity sha512-evtOjOJQq3zaHJIWsJjM4TGtNHtSrNVAIyQ+tdPW/fRd+4PLGbUG6S3xt+N4+QwDBOaCVd0xCWiHd4R6lWO5DQ==
|
||||
webpack@5.3.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.3.2.tgz#f88f6f2c54eaa1f68c8f37d8984657eaf68b00f0"
|
||||
integrity sha512-DXsfHoI6lQAR3KnQh7+FsRfs9fs+TEvzXCA35UbKv4kVuzslg7QCMAcpFRZNDMjdtm9N/PoO54XEzGN9TeacQg==
|
||||
dependencies:
|
||||
"@types/eslint-scope" "^3.7.0"
|
||||
"@types/estree" "^0.0.45"
|
||||
@@ -10655,7 +10655,7 @@ webpack@5.2.0:
|
||||
acorn "^8.0.4"
|
||||
browserslist "^4.14.5"
|
||||
chrome-trace-event "^1.0.2"
|
||||
enhanced-resolve "^5.3.0"
|
||||
enhanced-resolve "^5.3.1"
|
||||
eslint-scope "^5.1.1"
|
||||
events "^3.2.0"
|
||||
glob-to-regexp "^0.4.1"
|
||||
@@ -10667,9 +10667,9 @@ webpack@5.2.0:
|
||||
pkg-dir "^4.2.0"
|
||||
schema-utils "^3.0.0"
|
||||
tapable "^2.0.0"
|
||||
terser-webpack-plugin "^5.0.0"
|
||||
terser-webpack-plugin "^5.0.3"
|
||||
watchpack "^2.0.0"
|
||||
webpack-sources "^2.0.1"
|
||||
webpack-sources "^2.1.1"
|
||||
|
||||
websocket@1.0.32:
|
||||
version "1.0.32"
|
||||
|
Reference in New Issue
Block a user