Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
b587fefe44 | |||
2511114c28 | |||
9cd267fee5 | |||
aeac96a4f7 | |||
88a75e2a99 | |||
e14760775b | |||
9e7e464ba5 | |||
060d4fd27f | |||
940578d062 | |||
9cf42d8b33 | |||
1d62d2924e | |||
e23bac47ba | |||
b5d38adfcc | |||
f30513b20b | |||
ade1e40395 | |||
c93b8677e4 | |||
62683d8878 | |||
3d1239c1b4 | |||
5268bade66 | |||
514eb39a14 | |||
18628b821e | |||
344fbe6bcd | |||
afb8cd2dc1 | |||
1a5f385eb5 | |||
8df7864064 | |||
9ca60bad7f | |||
bd828bb072 | |||
892cb44d84 | |||
517ea6a119 | |||
ba8ffda32a | |||
90a9cf376e | |||
16d6c55407 | |||
f3508d15a3 | |||
0add490097 | |||
2d2d1bd58d | |||
7813c8a942 | |||
ac5453232f | |||
e78e5274d3 | |||
982520bcef | |||
7abd91f031 | |||
b93bfb7e5c | |||
2b20c34c1e | |||
0f63acea5b | |||
e600fb7096 | |||
b63fc71865 | |||
23e7650983 | |||
01b5ccfdc6 | |||
cc72f91465 |
32
CHANGELOG.md
32
CHANGELOG.md
@ -1,6 +1,38 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
12.7.0 (2020/02/10)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* ノートの文字数制限の設定を復活
|
||||
* デザインの調整
|
||||
|
||||
### 🐛Fixes
|
||||
* 中国語で表示できない問題を修正
|
||||
|
||||
12.6.0 (2020/02/10)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* リンクにホバーするとURLプレビューを表示するように
|
||||
* ユーザーページからグループに招待できるように
|
||||
* ウィジェットはブラウザごとに記憶するように
|
||||
|
||||
### 🐛Fixes
|
||||
* 要素の幅を判定する処理が上手くいかないことがある問題を修正
|
||||
|
||||
12.5.0 (2020/02/09)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* チュートリアルを実装
|
||||
* 検索のキーボードショートカットを追加
|
||||
* タイムラインを遡っている状況でないときに、誰かをフォローまたはフォロー解除したときにタイムラインをリロードするように
|
||||
|
||||
### 🐛Fixes
|
||||
* グループチャットが開始できない問題を修正
|
||||
* Renoteメニューが開けない問題を修正
|
||||
* 誕生日設定が崩れていたのを修正
|
||||
* キャッシュが削除できない問題を修正
|
||||
|
||||
12.4.1 (2020/02/09)
|
||||
--------------------
|
||||
### 🐛Fixes
|
||||
|
@ -214,12 +214,10 @@ remove: "Delete"
|
||||
removed: "Successfully deleted"
|
||||
removeAreYouSure: "Are you sure that you want to delete \"{x}\"?"
|
||||
saved: "Saved"
|
||||
messaging: "Talk"
|
||||
messaging: "Messaging"
|
||||
upload: "Upload"
|
||||
fromDrive: "From Drive"
|
||||
fromUrl: "From URL"
|
||||
editWidgets: "Edit widgets"
|
||||
exitEdit: "Finish editing"
|
||||
explore: "Explore"
|
||||
games: "Misskey Games"
|
||||
messageRead: "Read"
|
||||
@ -348,6 +346,7 @@ post: "Notes"
|
||||
posted: "Posted!"
|
||||
autoReloadWhenDisconnected: "Auto reload when disconnected with server"
|
||||
autoNoteWatch: "Watch note automatically"
|
||||
autoNoteWatchDescription: "Get notified about the notes which you reactioned or replied."
|
||||
reduceUiAnimation: "Reduce animations of User Interface"
|
||||
share: "Share"
|
||||
notFound: "Not found"
|
||||
@ -356,7 +355,7 @@ uploadFolder: "Default Upload location"
|
||||
cacheClear: "Clear cache"
|
||||
markAsReadAllNotifications: "Mark all notifications as read"
|
||||
markAsReadAllUnreadNotes: "Mark all notes as read"
|
||||
markAsReadAllTalkMessages: "Mark all conversations as read"
|
||||
markAsReadAllTalkMessages: "Mark all messages as read"
|
||||
help: "Help"
|
||||
inputMessageHere: "Enter message here"
|
||||
close: "Close"
|
||||
@ -369,6 +368,36 @@ invites: "Invite"
|
||||
groupName: "Group name"
|
||||
members: "Members"
|
||||
transfer: "Transfer"
|
||||
messagingWithUser: "Messaging with other user"
|
||||
messagingWithGroup: "Messaging within group"
|
||||
enable: "Enable"
|
||||
next: "Next"
|
||||
retype: "Enter again"
|
||||
noteOf: "{user}'s notes"
|
||||
inviteToGroup: "Invite to group"
|
||||
_tutorial:
|
||||
title: "How to use Misskey"
|
||||
step1_1: "Welcome!"
|
||||
step1_2: "This page is called \"timeline\". It shows chronologically ordered \"notes\" of people who you \"follow\"."
|
||||
step1_3: "Your timeline is currently empty, since you have not posed any notes or followed anyone yet."
|
||||
step2_1: "Let's finish setting up your profile before writing a note or following anyone."
|
||||
step2_2: "Providing some information about who you are will make it easier for others to follow you back."
|
||||
step3_1: "Finished setting up your profile?"
|
||||
step3_2: "The next step is to post a note. You can do this by pressing a pencil icon on the screen."
|
||||
step3_3: "Fill in the modal and press the button on the right top to post."
|
||||
step3_4: "Have nothing to say? Try \"I just started Misskey!\""
|
||||
step4_1: "Finished posting your first note?"
|
||||
step4_2: "Hurray! Now your first note is displayed on your timeline."
|
||||
step5_1: "Now, let's try making your timeline more lively by following other people."
|
||||
step5_2: "{featured} will show you trending notes in this instance. {explore} will let you find trending users. Try following people you like!"
|
||||
step5_3: "To follow other users, click on their icon and press \"follow\" button on their profile."
|
||||
step5_4: "If the other user has a lock icon next to their name, that user will have to manually approve your follow request."
|
||||
step6_1: "Now you will be able to see other users' notes on your timeline."
|
||||
step6_2: "You can also put \"reactions\" on other people's notes to quickly respond."
|
||||
step6_3: "To attach a \"reaction\", press \"+\" mark on other user's note and choose an emoji you'd like to react with."
|
||||
step7_1: "Congratulations! You have now finished Misskey's basic tutorial."
|
||||
step7_2: "If you would like to learn more about Misskey, try the {help} section."
|
||||
step7_3: "Good luck and have fun! 🚀"
|
||||
_2fa:
|
||||
alreadyRegistered: "You have already registered 2-factor authentication device."
|
||||
registerDevice: "Register a new device"
|
||||
@ -389,11 +418,11 @@ _permissions:
|
||||
"write:favorites": "Edit your favorites list"
|
||||
"read:following": "View your following information"
|
||||
"write:following": "Follow or unfollow other accounts"
|
||||
"read:messaging": "View your talks"
|
||||
"write:messaging": "Start or delete your talks"
|
||||
"read:messaging": "View your messages"
|
||||
"write:messaging": "Compose or Delete messages"
|
||||
"read:mutes": "View the list of people you muted"
|
||||
"write:mutes": "Edit the list of people you muted"
|
||||
"write:notes": "Compose and delete notes"
|
||||
"write:notes": "Compose or Delete notes"
|
||||
"read:notifications": "View notifications"
|
||||
"write:notifications": "Work with notifications"
|
||||
"read:reactions": "View reactions"
|
||||
@ -466,6 +495,7 @@ _visibility:
|
||||
followersDescription: "Post to followers only"
|
||||
specified: "Direct"
|
||||
specifiedDescription: "Post to specified users only"
|
||||
localOnly: "Local only"
|
||||
_postForm:
|
||||
replyPlaceholder: "Reply to this note..."
|
||||
quotePlaceholder: "Quote this note..."
|
||||
|
@ -214,18 +214,16 @@ remove: "Borrar"
|
||||
removed: "Borrado"
|
||||
removeAreYouSure: "¿Desea borrar \"{x}\"?"
|
||||
saved: "Guardado"
|
||||
messaging: "Conversación"
|
||||
messaging: "Chat"
|
||||
upload: "Subir"
|
||||
fromDrive: "Desde el drive"
|
||||
fromUrl: "Desde la URL"
|
||||
editWidgets: "Editar widgets"
|
||||
exitEdit: "Terminar edición"
|
||||
explore: "Explorar"
|
||||
games: "Misskey Games"
|
||||
messageRead: "Ya leído"
|
||||
recentUsedEmojis: "Emojis usados recientemente"
|
||||
noMoreHistory: "El historial se ha acabado"
|
||||
startMessaging: "Iniciar conversación"
|
||||
startMessaging: "Iniciar chat"
|
||||
nUsersRead: "Leído por {n} personas"
|
||||
agreeTo: "De acuerdo con {0}"
|
||||
tos: "Términos de uso"
|
||||
@ -348,12 +346,39 @@ post: "Nota"
|
||||
posted: "Posteado"
|
||||
autoReloadWhenDisconnected: "Recargar automáticamente cuando el servidor está desconectado"
|
||||
autoNoteWatch: "Ver nota automáticamente"
|
||||
autoNoteWatchDescription: "Recibe notificaciones sobre las notas de otros usuarios que a los que respondiste y reaccionaste"
|
||||
reduceUiAnimation: "Reducir la animación de la UI"
|
||||
share: "Compartir"
|
||||
notFound: "No se encuentra"
|
||||
notFoundDescription: "No se encontró la página correspondiente a la URL elegida"
|
||||
uploadFolder: "Carpeta de subidas por defecto"
|
||||
cacheClear: "Borrar caché"
|
||||
markAsReadAllNotifications: "Marcar todas las notificaciones como leídas"
|
||||
markAsReadAllUnreadNotes: "Marcar todas las notas como leídas"
|
||||
markAsReadAllTalkMessages: "Marcar todos los chats como leídos"
|
||||
help: "Ayuda"
|
||||
inputMessageHere: "Escribe el mensaje aquí"
|
||||
close: "Cerrar"
|
||||
group: "Grupo"
|
||||
groups: "Grupos"
|
||||
createGroup: "Crear grupo"
|
||||
ownedGroups: "Tus"
|
||||
joinedGroups: "Grupos a los que me uní"
|
||||
invites: "Invitar"
|
||||
groupName: "Nombre del grupo"
|
||||
members: "Miembros"
|
||||
transfer: "Transferir"
|
||||
messagingWithUser: "Chatear con usuario"
|
||||
messagingWithGroup: "Chatear en grupo"
|
||||
enable: "Activar"
|
||||
next: "Siguiente"
|
||||
retype: "Intentar de nuevo"
|
||||
_tutorial:
|
||||
title: "Cómo usar Misskey"
|
||||
step1_1: "Bienvenido"
|
||||
step1_2: "Esta imagen se llama \"Linea de tiempo\" y muestra en orden cronológico las \"notas\" tuyas y de la gente que \"sigues\""
|
||||
step1_3: "Si no estás escribiendo ninguna nota y no estás siguiendo a nadie, es esperable que no se muestre nada en la linea de tiempo"
|
||||
step2_1: "Antes de crear notas y seguir a alguien, primero vamos a crear tu perfil"
|
||||
_2fa:
|
||||
registerDevice: "Registrar dispositivo"
|
||||
step1: "Primero, instale en su dispositivo la aplicación de autenticación {a} o {b} u otra."
|
||||
@ -371,8 +396,6 @@ _permissions:
|
||||
"write:favorites": "Addministrar favoritos"
|
||||
"read:following": "Ver información de seguidor"
|
||||
"write:following": "Seguir o dejar de seguir"
|
||||
"read:messaging": "Ver conversación"
|
||||
"write:messaging": "Administrar coversación"
|
||||
"read:mutes": "Ver usuarios silenciados"
|
||||
"write:mutes": "Administrar usuarios silenciados"
|
||||
"write:notes": "Crear/borrar notas"
|
||||
|
@ -25,7 +25,7 @@ const languages = [
|
||||
'ko-KR',
|
||||
//'nl-NL',
|
||||
//'pl-PL',
|
||||
//'zh-CN',
|
||||
'zh-CN',
|
||||
//'zh-TW',
|
||||
];
|
||||
|
||||
|
@ -219,8 +219,6 @@ messaging: "チャット"
|
||||
upload: "アップロード"
|
||||
fromDrive: "ドライブから"
|
||||
fromUrl: "URLから"
|
||||
editWidgets: "ウィジェットを編集"
|
||||
exitEdit: "編集を終了"
|
||||
explore: "みつける"
|
||||
games: "Misskey Games"
|
||||
messageRead: "既読"
|
||||
@ -373,6 +371,36 @@ members: "メンバー"
|
||||
transfer: "譲渡"
|
||||
messagingWithUser: "ユーザーとチャット"
|
||||
messagingWithGroup: "グループでチャット"
|
||||
enable: "有効にする"
|
||||
next: "次"
|
||||
retype: "再入力"
|
||||
noteOf: "{user}のノート"
|
||||
inviteToGroup: "グループに招待"
|
||||
maxNoteTextLength: "ノートの文字数制限"
|
||||
|
||||
_tutorial:
|
||||
title: "Misskeyの使い方"
|
||||
step1_1: "ようこそ。"
|
||||
step1_2: "この画面は「タイムライン」と呼ばれ、あなたや、あなたが「フォロー」する人の「ノート」が時系列で表示されます。"
|
||||
step1_3: "あなたはまだ何もノートを投稿しておらず、誰もフォローしていないので、タイムラインには何も表示されていないはずです。"
|
||||
step2_1: "ノートを作成したり誰かをフォローしたりする前に、まずあなたのプロフィールを完成させましょう。"
|
||||
step2_2: "あなたがどんな人かわかると、多くの人にノートを見てもらえたり、フォローしてもらいやすくなります。"
|
||||
step3_1: "プロフィール設定はうまくできましたか?"
|
||||
step3_2: "では試しに、何かノートを投稿してみてください。画面上にある鉛筆マークのボタンを押すとフォームが開きます。"
|
||||
step3_3: "内容を書いたら、フォーム右上のボタンを押すと投稿できます。"
|
||||
step3_4: "内容が思いつかない?「Misskey始めました」というのはいかがでしょう。"
|
||||
step4_1: "投稿できましたか?"
|
||||
step4_2: "あなたのノートがタイムラインに表示されていれば成功です。"
|
||||
step5_1: "次は、他の人をフォローしてタイムラインを賑やかにしたいところです。"
|
||||
step5_2: "{featured}で人気のノートが見れるので、その中から気になった人を選んでフォローしたり、{explore}で人気のユーザーを探すこともできます。"
|
||||
step5_3: "ユーザーをフォローするには、ユーザーのアイコンをクリックしてユーザーページを表示し、「フォロー」ボタンを押します。"
|
||||
step5_4: "ユーザーによっては、フォローが承認されるまで時間がかかる場合があります。"
|
||||
step6_1: "タイムラインに他のユーザーのノートが表示されていれば成功です。"
|
||||
step6_2: "他の人のノートには、「リアクション」を付けることができ、簡単にあなたの反応を伝えられます。"
|
||||
step6_3: "リアクションを付けるには、ノートの「+」マークをクリックして、好きなリアクションを選択します。"
|
||||
step7_1: "これで、Misskeyの基本的な使い方の説明は終わりました。お疲れ様でした。"
|
||||
step7_2: "もっとMisskeyについて知りたいときは、{help}を見てみてください。"
|
||||
step7_3: "では、Misskeyをお楽しみください🚀"
|
||||
|
||||
_2fa:
|
||||
alreadyRegistered: "既に設定は完了しています。"
|
||||
|
@ -1 +1,169 @@
|
||||
---
|
||||
_ago:
|
||||
unknown: "謎"
|
||||
future: "未来"
|
||||
justNow: "たった今"
|
||||
secondsAgo: "{n}秒前"
|
||||
minutesAgo: "{n}分前"
|
||||
hoursAgo: "{n}時間前"
|
||||
daysAgo: "{n}日前"
|
||||
weeksAgo: "{n}週間前"
|
||||
monthsAgo: "{n}ヶ月前"
|
||||
yearsAgo: "{n}年前"
|
||||
_time:
|
||||
second: "秒"
|
||||
minute: "分"
|
||||
hour: "時間"
|
||||
day: "日"
|
||||
introMisskey: "ようこそ!Misskeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ノート」を作成しぃ、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「リアクション」機能で、皆のノートに素はよ反応を追加することもできます✌\n新しい世界を探検しよう🚀"
|
||||
monthAndDay: "{month}月 {day}日"
|
||||
search: "探す"
|
||||
notifications: "通知"
|
||||
username: "ユーザー名"
|
||||
password: "パスワード"
|
||||
fetchingAsApObject: "連合に照会中"
|
||||
ok: "おっけー"
|
||||
gotIt: "ほい"
|
||||
cancel: "やめとくわ"
|
||||
enterUsername: "ユーザー名を入れてや"
|
||||
renotedBy: "{user}がRenote"
|
||||
noNotes: "ノートはあらへん"
|
||||
noNotifications: "通知はあらへん"
|
||||
instance: "インスタンス"
|
||||
settings: "設定"
|
||||
profile: "プロフィール"
|
||||
timeline: "タイムライン"
|
||||
noAccountDescription: "自己紹介はあらへん"
|
||||
login: "ログイン"
|
||||
loggingIn: "ログインしとります"
|
||||
logout: "ログアウト"
|
||||
signup: "新規登録"
|
||||
uploading: "アップロードしとります"
|
||||
save: "保存"
|
||||
users: "ユーザー"
|
||||
addUser: "ユーザー増やす"
|
||||
favorite: "お気に入り"
|
||||
favorites: "お気に入り"
|
||||
unfavorite: "お気に入りやめる"
|
||||
pin: "ピン留め"
|
||||
unpin: "ピン留めやめる"
|
||||
copyContent: "内容をコピー"
|
||||
copyLink: "リンクをコピー"
|
||||
delete: "ほかす"
|
||||
addToList: "リストに入れたる"
|
||||
reply: "返す"
|
||||
loadMore: "もっとあるやろ!"
|
||||
mentions: "あんた宛て"
|
||||
directNotes: "ダイレクト投稿"
|
||||
import: "インポート"
|
||||
export: "エクスポート"
|
||||
files: "ファイル"
|
||||
download: "ダウンロード"
|
||||
lists: "リスト"
|
||||
noLists: "リストはあらへん"
|
||||
followsYou: "フォローされとるで"
|
||||
error: "問題が発生してん"
|
||||
enterListName: "リスト名を入れてや"
|
||||
privacy: "プライバシーってなんや?オカンの年齢か?"
|
||||
makeFollowManuallyApprove: "他人のフォローは許可してからや!"
|
||||
defaultNoteVisibility: "もとからの公開範囲"
|
||||
follow: "フォロー"
|
||||
followRequest: "フォロー許してくれや!言うてみる"
|
||||
followRequests: "フォロー許してくれや!"
|
||||
followRequestPending: "フォロー許してくれるん待っとる"
|
||||
enterEmoji: "絵文字を入れてや"
|
||||
you: "あんた"
|
||||
clickToShow: "押してみ、見せたるわ"
|
||||
sensitive: "見たらあかんで"
|
||||
add: "増やす"
|
||||
reaction: "リアクション"
|
||||
renameFile: "ファイル名をいらう"
|
||||
attachCancel: "くっつけるのやめよか"
|
||||
markAsSensitive: "ちょっと見せられへんわ"
|
||||
unmarkAsSensitive: "別にええんじゃね?"
|
||||
enterFileName: "ファイル名を入れてや"
|
||||
mute: "ミュート"
|
||||
unmute: "ミュートやめたる"
|
||||
block: "ブロック"
|
||||
unblock: "ブロックやめたる"
|
||||
suspend: "凍結"
|
||||
unsuspend: "溶かす"
|
||||
customEmojis: "カスタム絵文字"
|
||||
cacheRemoteFiles: "リモートのファイルをキャッシュする"
|
||||
cacheRemoteFilesDescription: "この設定をチャラにすると、リモートファイルをキャッシュせず直リンクするようになります。サーバーのストレージを節約できますが、サムネイルが生成されへんので通信量が増加します。"
|
||||
loginFailed: "ログインに失敗してん"
|
||||
wallpaper: "壁紙"
|
||||
removeWallpaper: "壁紙ほかす"
|
||||
youHaveNoLists: "リストはあらへん"
|
||||
proxyAccountDescription: "プロキシアカウントは、代わりにフォローしてくれるアカウントや。例えば、551に豚まんが無いときやったり、ユーザーがリモートユーザーをアカウントに入れたとき、リストに入れられたユーザーが誰からもフォローされてないと寂しいやん。寂しいし、アクティビティも配達されへんから、プロキシアカウントがフォローしてくれるで。ええやつやん…"
|
||||
host: "ホスト"
|
||||
federation: "連合"
|
||||
instances: "インスタンス"
|
||||
charts: "チャート"
|
||||
perHour: "1時間ごと"
|
||||
perDay: "1日ごと"
|
||||
operations: "操作"
|
||||
version: "バージョン"
|
||||
network: "ネットワーク"
|
||||
statistics: "統計"
|
||||
clearQueueConfirmText: "未配達の投稿は配送されなくなるで。通常この操作を行う必要はあらへんや。"
|
||||
muteAndBlock: "ミュートとブロック"
|
||||
noUsers: "ユーザーはおらへん"
|
||||
pinLimitExceeded: "これ以上ピン留めできひん"
|
||||
intro: "Misskeyのインストールが完了してん!管理者アカウントを作ってや。"
|
||||
noCustomEmojis: "絵文字はあらへん"
|
||||
noJobs: "ジョブはあらへん"
|
||||
all: "みな"
|
||||
retypedNotMatch: "そやないねん。"
|
||||
remove: "ほかす"
|
||||
noMoreHistory: "これより過去の履歴はあらへんで"
|
||||
nsfw: "見たらあかんで"
|
||||
userList: "リスト"
|
||||
about: "情報"
|
||||
aboutMisskey: "Misskeyってなんや?"
|
||||
notFoundDescription: "指定されたURLに該当するページはあらへんやった。"
|
||||
close: "さいなら"
|
||||
joinedGroups: "参加しとるグループ"
|
||||
_2fa:
|
||||
alreadyRegistered: "もう設定終わっとるわ"
|
||||
_auth:
|
||||
permissionAsk: "このアプリは次の権限を要求しとるで"
|
||||
_antennaSources:
|
||||
all: "みなのノート"
|
||||
homeTimeline: "フォローしとるユーザーのノート"
|
||||
_widgets:
|
||||
notifications: "通知"
|
||||
timeline: "タイムライン"
|
||||
_cw:
|
||||
show: "もっとあるやろ!"
|
||||
_poll:
|
||||
noMore: "これ以上追加でけへん"
|
||||
deadlineTime: "時間"
|
||||
_visibility:
|
||||
publicDescription: "みなのユーザーに公開"
|
||||
_profile:
|
||||
username: "ユーザー名"
|
||||
_exportOrImport:
|
||||
allNotes: "全てのノート"
|
||||
muteList: "ミュート"
|
||||
blockingList: "ブロック"
|
||||
userLists: "リスト"
|
||||
_pages:
|
||||
script:
|
||||
categories:
|
||||
list: "リスト"
|
||||
blocks:
|
||||
_join:
|
||||
arg1: "リスト"
|
||||
_randomPick:
|
||||
arg1: "リスト"
|
||||
_dailyRandomPick:
|
||||
arg1: "リスト"
|
||||
_seedRandomPick:
|
||||
arg2: "リスト"
|
||||
_pick:
|
||||
arg1: "リスト"
|
||||
_listLen:
|
||||
arg1: "リスト"
|
||||
types:
|
||||
array: "リスト"
|
||||
|
@ -218,14 +218,12 @@ messaging: "대화"
|
||||
upload: "업로드"
|
||||
fromDrive: "드라이브에서"
|
||||
fromUrl: "URL로부터"
|
||||
editWidgets: "위젯 편집"
|
||||
exitEdit: "편집 종료"
|
||||
explore: "발견하기"
|
||||
games: "Misskey Games"
|
||||
messageRead: "읽음"
|
||||
recentUsedEmojis: "최근에 사용한 이모지"
|
||||
noMoreHistory: "이것보다 과거의 기록이 없습니다"
|
||||
startMessaging: "대화 시작"
|
||||
startMessaging: "대화 시작하기"
|
||||
nUsersRead: "{n}명이 읽음"
|
||||
agreeTo: "{0}에 동의"
|
||||
tos: "이용 약관"
|
||||
@ -348,6 +346,7 @@ post: "작성"
|
||||
posted: "게시하였습니다"
|
||||
autoReloadWhenDisconnected: "서버와의 연결이 끊기면 자동 새로고침"
|
||||
autoNoteWatch: "노트를 자동으로 지켜보기"
|
||||
autoNoteWatchDescription: "리액션하거나 답글을 남긴 다른 유저의 노트에 대한 알림을 받습니다."
|
||||
reduceUiAnimation: "UI의 애니메이션을 줄이기"
|
||||
share: "공유"
|
||||
notFound: "찾을 수 없습니다"
|
||||
@ -369,6 +368,36 @@ invites: "초대"
|
||||
groupName: "그룹명"
|
||||
members: "멤버"
|
||||
transfer: "양도"
|
||||
messagingWithUser: "유저와 대화하기"
|
||||
messagingWithGroup: "그룹끼리 대화하기"
|
||||
enable: "사용"
|
||||
next: "다음"
|
||||
retype: "다시 입력"
|
||||
noteOf: "{user}의 노트"
|
||||
inviteToGroup: "그룹에 초대하기"
|
||||
_tutorial:
|
||||
title: "Misskey의 사용 방법"
|
||||
step1_1: "환영합니다!"
|
||||
step1_2: "이 페이지는 \"타임라인\"이라고 불립니다. 당신이 \"팔로우\"하고 있는 사람들의 \"노트\"가 시간순으로 나타납니다."
|
||||
step1_3: "아직 아무 유저도 팔로우하고 있지 않기에 타임라인은 비어 있을 것입니다."
|
||||
step2_1: "새 노트를 작성하거나 다른 사람을 팔로우하기 전에, 먼저 프로필을 완성해보도록 합시다."
|
||||
step2_2: "당신이 어떤 사람인지를 알린다면, 다른 사람들이 당신을 팔로우할 확률이 올라갈 것입니다."
|
||||
step3_1: "프로필 설정은 잘 끝내셨나요?"
|
||||
step3_2: "그럼 시험삼아 노트를 작성해 보세요. 화면에 있는 연필 버튼을 누르면 작성 폼이 열립니다."
|
||||
step3_3: "내용을 작성한 후, 폼 오른쪽 상단의 버튼을 눌러 노트를 올릴 수 있습니다."
|
||||
step3_4: "쓸 말이 없나요? \"Misskey 시작했어요!\" 같은 건 어떨까요? :>"
|
||||
step4_1: "노트 작성을 끝내셨나요?"
|
||||
step4_2: "당신의 노트가 타임라인에 표시되어 있다면 성공입니다."
|
||||
step5_1: "이제, 다른 사람을 팔로우하여 타임라인을 활기차게 만들어보도록 합시다."
|
||||
step5_2: "{featured}에서 이 인스턴스의 인기 노트를 보실 수 있습니다. {explore}에서는 인기 사용자를 찾을 수 있구요. 마음에 드는 사람을 골라 팔로우해 보세요!"
|
||||
step5_3: "다른 유저를 팔로우하려면 해당 유저의 아이콘을 클릭하여 프로필 페이지를 띄운 후, 팔로우 버튼을 눌러 주세요."
|
||||
step5_4: "사용자에 따라 팔로우가 승인될 때까지 시간이 걸릴 수 있습니다."
|
||||
step6_1: "타임라인에 다른 사용자의 노트가 나타난다면 성공입니다."
|
||||
step6_2: "다른 유저의 노트에 \"리액션\"을 붙여 간단하게 당신의 반응을 전달할 수도 있습니다."
|
||||
step6_3: "리액션을 붙이려면, 노트의 \"+\" 버튼을 클릭하고 원하는 이모지를 선택합니다."
|
||||
step7_1: "이것으로 Misskey의 기본 튜토리얼을 마치겠습니다. 수고하셨습니다!"
|
||||
step7_2: "Misskey에 대해 더 알고 싶으시다면 {help}를 참고해 주세요."
|
||||
step7_3: "그럼 Misskey를 즐기세요! 🚀"
|
||||
_2fa:
|
||||
alreadyRegistered: "이미 설정이 완료되었습니다."
|
||||
registerDevice: "디바이스 등록"
|
||||
@ -379,32 +408,32 @@ _2fa:
|
||||
step4: "다음 로그인부터는 토큰을 입력해야 합니다."
|
||||
securityKeyInfo: "FIDO2를 지원하는 하드웨어 시큐리티 키 혹은 휴대전화의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다."
|
||||
_permissions:
|
||||
"read:account": "계정 정보 보기"
|
||||
"write:account": "계정 정보 변경"
|
||||
"read:blocks": "차단 보기"
|
||||
"write:blocks": "차단 수정"
|
||||
"read:drive": "드라이브 보기"
|
||||
"write:drive": "드라이브 수정"
|
||||
"read:favorites": "즐겨찾기 보기"
|
||||
"write:favorites": "즐겨찾기 수정"
|
||||
"read:following": "팔로우 정보 보기"
|
||||
"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:account": "계정의 정보를 봅니다"
|
||||
"write:account": "계정의 정보를 변경합니다"
|
||||
"read:blocks": "차단 여부를 확인합니다"
|
||||
"write:blocks": "차단을 하거나 해제합니다"
|
||||
"read:drive": "드라이브를 조회합니다"
|
||||
"write:drive": "드라이브에 파일을 올리거나, 이름을 변경하거나, 삭제합니다"
|
||||
"read:favorites": "즐겨찾기를 조회합니다"
|
||||
"write:favorites": "즐겨찾기에 추가하거나 삭제합니다"
|
||||
"read:following": "팔로우 상태를 봅니다"
|
||||
"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": "유저 그룹을 만들거나, 초대하거나, 이름을 변경하거나, 양도하거나, 삭제합니다"
|
||||
_auth:
|
||||
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
|
||||
permissionAsk: "이 앱은 다음의 권한을 요청합니다"
|
||||
@ -466,6 +495,7 @@ _visibility:
|
||||
followersDescription: "팔로워에게만 공개"
|
||||
specified: "다이렉트"
|
||||
specifiedDescription: "지정한 유저에게만 공개"
|
||||
localOnly: "로컬에만"
|
||||
_postForm:
|
||||
replyPlaceholder: "이 노트에 답글..."
|
||||
quotePlaceholder: "이 노트를 인용..."
|
||||
|
@ -15,7 +15,7 @@ _time:
|
||||
minute: "分"
|
||||
hour: "小时"
|
||||
day: "日"
|
||||
introMisskey: "欢迎!Misskey是一个开源的分散型SNS服务。\n通过「Note」来分享现在发生的事情吧!📡\n「反应」工能也可以让你快速的对大家的「Note」来表达感情👍\n一起来探索新的世界吧!🚀"
|
||||
introMisskey: "欢迎!Misskey是一个开源的分散型SNS服务。\n通过「帖子」来分享现在发生的事情吧!📡\n「反应」功能,可以让你快速的对大家的「帖子」来表达感情👍\n一起来探索新的世界吧!🚀"
|
||||
monthAndDay: "{month}月 {day}日"
|
||||
search: "搜索"
|
||||
notifications: "通知"
|
||||
@ -27,6 +27,7 @@ gotIt: "我明白了"
|
||||
cancel: "取消"
|
||||
enterUsername: "输入用户名"
|
||||
renotedBy: "由 {user} 转推"
|
||||
noNotes: "没有投稿"
|
||||
noNotifications: "无通知"
|
||||
instance: "实例"
|
||||
settings: "设置"
|
||||
@ -54,13 +55,24 @@ sendMessage: "发送"
|
||||
copyUsername: "复制用户名"
|
||||
reply: "回复"
|
||||
loadMore: "查看更多"
|
||||
youGotNewFollower: "你有新的关注者"
|
||||
receiveFollowRequest: "收到关注请求"
|
||||
followRequestAccepted: "同意关注请求"
|
||||
mentions: "提及"
|
||||
directNotes: "指定用户可见"
|
||||
importAndExport: "导入和导出"
|
||||
import: "导入"
|
||||
export: "导出"
|
||||
files: "文件"
|
||||
download: "下载"
|
||||
driveFileDeleteConfirm: "要删除「{name}」文件吗?附加此文件的帖子也会消失。"
|
||||
unfollowConfirm: "要取消对{name}的关注吗?"
|
||||
exportRequested: "导出请求已提交。可能需要花一些时间。导出的文件将保存到网盘中。"
|
||||
importRequested: "导入请求已提交。这可能需要花一点时间。"
|
||||
lists: "列表"
|
||||
noLists: "列表为空"
|
||||
note: "帖子"
|
||||
notes: "帖子"
|
||||
following: "关注中"
|
||||
followers: "关注者"
|
||||
followsYou: "关注了你"
|
||||
@ -81,15 +93,18 @@ enterEmoji: "输入Emoji"
|
||||
renote: "转发"
|
||||
unrenote: "取消转发"
|
||||
quote: "引用"
|
||||
pinnedNote: "已置顶的帖子"
|
||||
you: "您"
|
||||
clickToShow: "点击以显示"
|
||||
sensitive: "阅读注意"
|
||||
add: "添加"
|
||||
reaction: "反应"
|
||||
reactionSettingDescription: "快速选择回应中的自定义表情符号,以换行符分隔。"
|
||||
rememberNoteVisibility: "记录公开范围"
|
||||
renameFile: "重命名文件"
|
||||
attachCancel: "删除附件"
|
||||
markAsSensitive: "阅读注意"
|
||||
unmarkAsSensitive: "取消标记为敏感内容"
|
||||
enterFileName: "请输入文件名"
|
||||
mute: "屏蔽"
|
||||
unmute: "解除屏蔽"
|
||||
@ -107,14 +122,20 @@ emojiName: "Emoji 名称"
|
||||
emojiUrl: "emoji 地址"
|
||||
addEmoji: "添加Emoji"
|
||||
cacheRemoteFiles: "远程文件缓存"
|
||||
cacheRemoteFilesDescription: "当禁用此设定时远程文件将直接从远程实例载入。禁用后会减小储存空间需求,但是会增加流量,因为缩略图不会被生成。"
|
||||
flagAsBot: "这个账户是Bot"
|
||||
flagAsCat: "这个账户是Cat"
|
||||
autoAcceptFollowed: "自动允许关注"
|
||||
addAcount: "添加账户"
|
||||
loginFailed: "登录失败"
|
||||
showOnRemote: "转到所在实例显示"
|
||||
general: "常规设置"
|
||||
wallpaper: "壁纸"
|
||||
removeWallpaper: "移除壁纸"
|
||||
searchWith: "搜索:{q}"
|
||||
youHaveNoLists: "列表为空"
|
||||
followConfirm: "你确定要关注{name}吗?"
|
||||
proxyAccount: "代理账户"
|
||||
host: "主机名"
|
||||
selectUser: "选择用户"
|
||||
recipient: "收件人"
|
||||
@ -124,36 +145,51 @@ instances: "实例"
|
||||
latestRequestSentAt: "上次发送的请求"
|
||||
latestRequestReceivedAt: "上次收到的请求"
|
||||
storageUsage: "已用存储"
|
||||
charts: "图表"
|
||||
perHour: "每小时"
|
||||
perDay: "每天"
|
||||
operations: "操作"
|
||||
software: "软件"
|
||||
version: "版本"
|
||||
metadata: "元数据"
|
||||
withNFiles: "{n}个文件"
|
||||
monitor: "监视器"
|
||||
jobQueue: "作业队列"
|
||||
cpuAndMemory: "CPU使用量"
|
||||
network: "网络"
|
||||
disk: "存储"
|
||||
instanceInfo: "实例情报"
|
||||
statistics: "统计"
|
||||
clearQueue: "清除队列"
|
||||
clearQueueConfirmTitle: "确定清除队列?"
|
||||
clearCachedFiles: "清除缓存"
|
||||
clearCachedFilesConfirm: "确定要清除缓存文件?"
|
||||
blockedInstances: "被阻拦的实例"
|
||||
blockedInstancesDescription: "设定要阻拦的实例,以换行来进行分割。被阻拦的实例将无法与本实例进行交换通讯。"
|
||||
muteAndBlock: "屏蔽/拉黑"
|
||||
mutedUsers: "禁言用户"
|
||||
blockedUsers: "已屏蔽用户"
|
||||
noUsers: "无用户"
|
||||
editProfile: "编辑资料"
|
||||
noteDeleteConfirm: "要删除该帖子吗?"
|
||||
pinLimitExceeded: "无法置顶更多了"
|
||||
intro: "Misskey的部署结束啦!填写管理员账号吧!"
|
||||
done: "完成"
|
||||
processing: "处理中"
|
||||
preview: "预览"
|
||||
noCustomEmojis: "无自定义Emoji"
|
||||
customEmojisOfRemote: "远程Emoji"
|
||||
noJobs: "没有任务"
|
||||
federating: "联合中"
|
||||
blocked: "已拦截"
|
||||
suspended: "停止推流"
|
||||
all: "全部"
|
||||
subscribing: "已订阅"
|
||||
publishing: "直播中"
|
||||
notResponding: "没有响应"
|
||||
instanceFollowing: "关注实例"
|
||||
instanceFollowers: "关注实例"
|
||||
instanceUsers: "实例用户"
|
||||
changePassword: "修改密码"
|
||||
security: "安全性"
|
||||
retypedNotMatch: "两次输入不一致!"
|
||||
@ -163,7 +199,10 @@ newPasswordRetype: "重新输入密码:"
|
||||
attachFile: "插入附件"
|
||||
more: "更多!"
|
||||
featured: "高亮"
|
||||
usernameOrUserId: "用户名或用户ID"
|
||||
noSuchUser: "用户不存在"
|
||||
lookup: "查询"
|
||||
announcements: "公告"
|
||||
imageUrl: "图片URL"
|
||||
remove: "删除"
|
||||
removed: "已删除"
|
||||
@ -171,17 +210,20 @@ removeAreYouSure: "要删掉「{x}」吗?"
|
||||
saved: "已保存"
|
||||
messaging: "聊天"
|
||||
upload: "上传"
|
||||
fromDrive: "从网盘中"
|
||||
fromUrl: "从 URL"
|
||||
editWidgets: "编辑部件"
|
||||
exitEdit: "停止编辑"
|
||||
explore: "发现"
|
||||
games: "Misskey游戏"
|
||||
messageRead: "已读"
|
||||
recentUsedEmojis: "最近使用的Emoji表情"
|
||||
noMoreHistory: "没有更多的历史记录"
|
||||
startMessaging: "开始聊天"
|
||||
nUsersRead: "{n}人已读"
|
||||
agreeTo: "{0}人同意"
|
||||
tos: "服务条款"
|
||||
start: "开始"
|
||||
home: "首页"
|
||||
remoteUserCaution: "由于是远程用户,信息不完整。"
|
||||
activity: "活动"
|
||||
images: "图片"
|
||||
birthday: "生日"
|
||||
@ -226,6 +268,7 @@ connectSerice: "已连接"
|
||||
disconnectSerice: "断开连接"
|
||||
enableLocalTimeline: "启用本地时间线功能"
|
||||
enableGlobalTimeline: "启用全局时间线"
|
||||
disablingTimelinesInfo: "即使时间线功能被禁用,出于便利性的原因,管理员和数据图表也可以继续使用。"
|
||||
registration: "注册"
|
||||
enableRegistration: "允许新用户注册"
|
||||
invite: "邀请"
|
||||
@ -238,11 +281,13 @@ iconUrl: "图标URL"
|
||||
bannerUrl: "Banner URL"
|
||||
basicInfo: "基本信息"
|
||||
pinnedUsers: "置顶用户"
|
||||
pinnedUsersDescription: "在「发现」页面中使用换行标记想要置顶的用户。"
|
||||
recaptcha: "reCAPTCHA"
|
||||
enableRecaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)"
|
||||
recaptchaSiteKey: "网站密钥"
|
||||
recaptchaSecretKey: "reCAPTCHA 密钥"
|
||||
name: "名称"
|
||||
antennaKeywordsDescription: "使用空格分隔会产生AND规范,并且使用换行符分隔会产生OR规范"
|
||||
serviceworker: "ServiceWorker"
|
||||
enableServiceworker: "启用ServiceWorker"
|
||||
caseSensitive: "区分大小写"
|
||||
@ -287,7 +332,6 @@ uploadFolder: "默认上传文件夹"
|
||||
cacheClear: "清空缓存"
|
||||
markAsReadAllNotifications: "将所有通知标为已读"
|
||||
markAsReadAllUnreadNotes: "将所有帖子标记为已读"
|
||||
markAsReadAllTalkMessages: "将所有对话标为已读"
|
||||
help: "帮助"
|
||||
inputMessageHere: "在此键入信息"
|
||||
close: "关闭"
|
||||
@ -300,6 +344,14 @@ invites: "邀请"
|
||||
groupName: "群组名"
|
||||
members: "成员"
|
||||
transfer: "转让"
|
||||
enable: "启用"
|
||||
next: "下一个"
|
||||
retype: "重新输入"
|
||||
noteOf: "{user}的帖子"
|
||||
inviteToGroup: "群组邀请"
|
||||
_tutorial:
|
||||
title: "Misskey的使用方法"
|
||||
step1_1: "欢迎!"
|
||||
_2fa:
|
||||
alreadyRegistered: "此设备已被注册"
|
||||
registerDevice: "注册设备"
|
||||
@ -315,8 +367,6 @@ _permissions:
|
||||
"write:favorites": "编辑收藏夹"
|
||||
"read:following": "查看关注信息"
|
||||
"write:following": "关注/取消关注"
|
||||
"read:messaging": "查看对话"
|
||||
"write:messaging": "对话操作"
|
||||
"read:mutes": "查看屏蔽列表"
|
||||
"write:mutes": "编辑屏蔽列表"
|
||||
"read:notifications": "查看通知"
|
||||
@ -384,7 +434,12 @@ _visibility:
|
||||
_profile:
|
||||
name: "名称"
|
||||
username: "用户名"
|
||||
description: "个人简介"
|
||||
metadata: "额外信息"
|
||||
metadataLabel: "标签"
|
||||
metadataContent: "内容"
|
||||
_exportOrImport:
|
||||
allNotes: "所有帖子"
|
||||
followingList: "关注中"
|
||||
muteList: "屏蔽"
|
||||
blockingList: "屏蔽"
|
||||
@ -447,9 +502,13 @@ _pages:
|
||||
blocks:
|
||||
text: "文本"
|
||||
image: "图片"
|
||||
if: "如果"
|
||||
_if:
|
||||
variable: "变量"
|
||||
post: "投稿窗口"
|
||||
_post:
|
||||
text: "内容"
|
||||
textInput: "文本输入"
|
||||
_textInput:
|
||||
name: "变量名"
|
||||
text: "标题"
|
||||
@ -482,12 +541,30 @@ _pages:
|
||||
dialog: "显示对话框"
|
||||
_dialog:
|
||||
content: "内容"
|
||||
resetRandom: "重置随机值"
|
||||
pushEvent: "发送事件"
|
||||
_pushEvent:
|
||||
event: "事件名称"
|
||||
message: "按下时显示的消息"
|
||||
variable: "发送的变量"
|
||||
no-variable: "空"
|
||||
radioButton: "选择项"
|
||||
_radioButton:
|
||||
name: "变量名"
|
||||
title: "标题"
|
||||
values: "使用换行区分的选择项"
|
||||
default: "默认值"
|
||||
script:
|
||||
categories:
|
||||
flow: "控制"
|
||||
logical: "逻辑运算"
|
||||
operation: "计算"
|
||||
comparison: "比较"
|
||||
random: "随机"
|
||||
value: "值"
|
||||
fn: "函数"
|
||||
text: "文本操作"
|
||||
convert: "转换"
|
||||
list: "列表"
|
||||
blocks:
|
||||
text: "文本"
|
||||
@ -638,12 +715,14 @@ _pages:
|
||||
_for:
|
||||
arg1: "次数"
|
||||
arg2: "处理"
|
||||
thereIsEmptySlot: "槽函数{slot}为空!"
|
||||
types:
|
||||
string: "文字"
|
||||
number: "数值"
|
||||
boolean: "Flag"
|
||||
array: "列表"
|
||||
stringArray: "文本列表"
|
||||
emptySlot: "空白槽函数"
|
||||
enviromentVariables: "环境变量"
|
||||
pageVariables: "页面元素"
|
||||
argVariables: "输入变量"
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
||||
"version": "12.4.1",
|
||||
"version": "12.7.0",
|
||||
"codename": "indigo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -36,6 +36,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "5.12.0",
|
||||
"@fortawesome/free-solid-svg-icons": "5.12.0",
|
||||
"@fortawesome/vue-fontawesome": "0.1.9",
|
||||
"@juggle/resize-observer": "3.0.2",
|
||||
"@koa/cors": "3.0.0",
|
||||
"@koa/multer": "2.0.2",
|
||||
"@koa/router": "8.0.6",
|
||||
|
@ -18,8 +18,12 @@
|
||||
</transition>
|
||||
</div>
|
||||
<div class="sub">
|
||||
<fa :icon="faSearch"/>
|
||||
<input type="search" class="search" :placeholder="$t('search')" v-model="searchQuery" v-autocomplete="{ model: 'searchQuery' }" :disabled="searchWait" @keypress="searchKeypress"/>
|
||||
<button v-if="widgetsEditMode" class="_button edit active" @click="widgetsEditMode = false"><fa :icon="faGripVertical"/></button>
|
||||
<button v-else class="_button edit" @click="widgetsEditMode = true"><fa :icon="faGripVertical"/></button>
|
||||
<div class="search">
|
||||
<fa :icon="faSearch"/>
|
||||
<input type="search" :placeholder="$t('search')" v-model="searchQuery" v-autocomplete="{ model: 'searchQuery' }" :disabled="searchWait" @keypress="searchKeypress"/>
|
||||
</div>
|
||||
<button v-if="$store.getters.isSignedIn" class="post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
|
||||
</div>
|
||||
</header>
|
||||
@ -86,7 +90,7 @@
|
||||
</nav>
|
||||
</transition>
|
||||
|
||||
<div class="contents">
|
||||
<div class="contents" ref="contents">
|
||||
<main ref="main">
|
||||
<div class="content">
|
||||
<transition :name="$store.state.device.animation ? 'page' : ''" mode="out-in" @enter="onTransition">
|
||||
@ -126,8 +130,6 @@
|
||||
<template v-else>
|
||||
<component class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget"/>
|
||||
</template>
|
||||
<button ref="widgetsEditButton" v-if="widgetsEditMode" class="_button edit" @click="widgetsEditMode = false">{{ $t('exitEdit') }}</button>
|
||||
<button ref="widgetsEditButton" v-else class="_button edit" @click="widgetsEditMode = true">{{ $t('editWidgets') }}</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@ -150,8 +152,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faChevronLeft, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faListUl, faPlus, faUserClock, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faGamepad, faServer, faFileAlt, faSatellite, faInfoCircle, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faGripVertical, faChevronLeft, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faListUl, faPlus, faUserClock, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faGamepad, faServer, faFileAlt, faSatellite, faInfoCircle, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faBell, faEnvelope, faLaugh, faComments } from '@fortawesome/free-regular-svg-icons';
|
||||
import { ResizeObserver } from '@juggle/resize-observer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import i18n from './i18n';
|
||||
import { host } from './config';
|
||||
@ -184,7 +187,7 @@ export default Vue.extend({
|
||||
enableWidgets: window.innerWidth >= 1100,
|
||||
canBack: false,
|
||||
disconnectedDialog: null as Promise<void> | null,
|
||||
faChevronLeft, faComments, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faBell, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faEnvelope, faListUl, faPlus, faUserClock, faLaugh, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer
|
||||
faGripVertical, faChevronLeft, faComments, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faBell, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faEnvelope, faListUl, faPlus, faUserClock, faLaugh, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer
|
||||
};
|
||||
},
|
||||
|
||||
@ -193,12 +196,13 @@ export default Vue.extend({
|
||||
return {
|
||||
'p': this.post,
|
||||
'n': this.post,
|
||||
's': this.search,
|
||||
'h|/': this.help
|
||||
};
|
||||
},
|
||||
|
||||
widgets(): any[] {
|
||||
return this.$store.state.settings.widgets;
|
||||
return this.$store.state.deviceUser.widgets;
|
||||
}
|
||||
},
|
||||
|
||||
@ -229,9 +233,15 @@ export default Vue.extend({
|
||||
this.connection.on('notification', this.onNotification);
|
||||
|
||||
if (this.widgets.length === 0) {
|
||||
this.$store.dispatch('settings/setWidgets', [{
|
||||
name: 'notifications',
|
||||
this.$store.commit('deviceUser/setWidgets', [{
|
||||
name: 'calendar',
|
||||
id: 'a', data: {}
|
||||
}, {
|
||||
name: 'notifications',
|
||||
id: 'b', data: {}
|
||||
}, {
|
||||
name: 'trends',
|
||||
id: 'c', data: {}
|
||||
}]);
|
||||
}
|
||||
}
|
||||
@ -255,21 +265,34 @@ export default Vue.extend({
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setInterval(() => {
|
||||
if (this.showNav) return; // TODO: トランジション中も false になるので、これだけでは不十分
|
||||
this.$refs.title.style.left = (this.$refs.main.getBoundingClientRect().left - this.$refs.nav.offsetWidth) + 'px';
|
||||
}, 1000);
|
||||
|
||||
mounted() {
|
||||
// https://stackoverflow.com/questions/33891709/when-flexbox-items-wrap-in-column-mode-container-does-not-grow-its-width
|
||||
if (this.enableWidgets) {
|
||||
setInterval(() => {
|
||||
if (!this.$refs.widgetsEditButton) return;
|
||||
const adjustWidgetsWidth = () => {
|
||||
const lastChild = this.$refs.widgets.children[this.$refs.widgets.children.length - 1];
|
||||
if (lastChild == null) return;
|
||||
|
||||
const width = this.$refs.widgetsEditButton.offsetLeft + 300;
|
||||
const width = lastChild.offsetLeft + 300;
|
||||
this.$refs.widgets.style.width = width + 'px';
|
||||
}, 1000);
|
||||
};
|
||||
setInterval(adjustWidgetsWidth, 1000);
|
||||
}
|
||||
|
||||
const adjustTitlePosition = () => {
|
||||
this.$refs.title.style.left = (this.$refs.main.getBoundingClientRect().left - this.$refs.nav.offsetWidth) + 'px';
|
||||
};
|
||||
|
||||
adjustTitlePosition();
|
||||
|
||||
const ro = new ResizeObserver((entries, observer) => {
|
||||
adjustTitlePosition();
|
||||
});
|
||||
|
||||
ro.observe(this.$refs.contents);
|
||||
|
||||
window.addEventListener('resize', adjustTitlePosition);
|
||||
},
|
||||
|
||||
methods: {
|
||||
@ -481,8 +504,9 @@ export default Vue.extend({
|
||||
this.$store.dispatch('switchAccount', {
|
||||
...i,
|
||||
token: token
|
||||
}).then(() => {
|
||||
location.reload();
|
||||
});
|
||||
location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
@ -529,7 +553,7 @@ export default Vue.extend({
|
||||
items: widgets.map(widget => ({
|
||||
text: this.$t('_widgets.' + widget),
|
||||
action: () => {
|
||||
this.$store.dispatch('settings/addWidget', {
|
||||
this.$store.commit('deviceUser/addWidget', {
|
||||
name: widget,
|
||||
id: uuid(),
|
||||
data: {}
|
||||
@ -541,11 +565,11 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
removeWidget(widget) {
|
||||
this.$store.dispatch('settings/removeWidget', widget);
|
||||
this.$store.commit('deviceUser/removeWidget', widget);
|
||||
},
|
||||
|
||||
saveHome() {
|
||||
this.$store.dispatch('settings/setWidgets', this.widgets);
|
||||
this.$store.commit('deviceUser/setWidgets', this.widgets);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -708,30 +732,42 @@ export default Vue.extend({
|
||||
display: none;
|
||||
}
|
||||
|
||||
> [data-icon] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 16px;
|
||||
height: $header-height;
|
||||
pointer-events: none;
|
||||
font-size: 16px;
|
||||
> .edit {
|
||||
padding: 16px;
|
||||
|
||||
&.active {
|
||||
color: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
> .search {
|
||||
$margin: 8px;
|
||||
width: calc(100% - #{$post-button-size + $post-button-margin + $margin});
|
||||
box-sizing: border-box;
|
||||
margin-right: $margin;
|
||||
padding: 0 12px 0 42px;
|
||||
font-size: 1rem;
|
||||
line-height: 38px;
|
||||
border: none;
|
||||
border-radius: 38px;
|
||||
color: var(--fg);
|
||||
background: var(--bg);
|
||||
position: relative;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
> input {
|
||||
$margin: 8px;
|
||||
width: 200px;
|
||||
box-sizing: border-box;
|
||||
margin-right: $margin;
|
||||
padding: 0 12px 0 42px;
|
||||
font-size: 1rem;
|
||||
line-height: 38px;
|
||||
border: none;
|
||||
border-radius: 38px;
|
||||
color: var(--fg);
|
||||
background: var(--bg);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
> [data-icon] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 16px;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -802,7 +838,8 @@ export default Vue.extend({
|
||||
padding: 8px 0;
|
||||
|
||||
> .divider {
|
||||
margin: 8px 0;
|
||||
margin: 8px auto;
|
||||
width: calc(100% - 32px);
|
||||
}
|
||||
}
|
||||
|
||||
@ -970,12 +1007,6 @@ export default Vue.extend({
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
> .edit {
|
||||
display: block;
|
||||
font-size: 0.9em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.customize-container {
|
||||
margin: 8px 0;
|
||||
background: #fff;
|
||||
|
@ -143,7 +143,7 @@ export default Vue.extend({
|
||||
this.setPosition();
|
||||
|
||||
//#region Construct Emoji DB
|
||||
const customEmojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
|
||||
const customEmojis = this.$store.state.instance.meta.emojis;
|
||||
const emojiDefinitions: EmojiDef[] = [];
|
||||
|
||||
for (const x of customEmojis) {
|
||||
|
@ -140,7 +140,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
created() {
|
||||
let local = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
|
||||
let local = this.$store.state.instance.meta.emojis;
|
||||
local = groupByX(local, (x: any) => x.category || '');
|
||||
this.customEmojis = local;
|
||||
},
|
||||
|
@ -55,38 +55,35 @@ export default Vue.extend({
|
||||
|
||||
useOsDefaultEmojis(): boolean {
|
||||
return this.$store.state.device.useOsDefaultEmojis && !this.isReaction;
|
||||
},
|
||||
|
||||
ce() {
|
||||
let ce = [];
|
||||
if (this.customEmojis) ce = ce.concat(this.customEmojis);
|
||||
if (this.$store.state.instance.meta && this.$store.state.instance.meta.emojis) ce = ce.concat(this.$store.state.instance.meta.emojis);
|
||||
return ce;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
customEmojis() {
|
||||
if (this.name) {
|
||||
const customEmoji = this.customEmojis.find(x => x.name == this.name);
|
||||
if (customEmoji) {
|
||||
this.customEmoji = customEmoji;
|
||||
this.url = this.$store.state.device.disableShowingAnimatedImages
|
||||
? getStaticImageUrl(customEmoji.url)
|
||||
: customEmoji.url;
|
||||
ce: {
|
||||
handler() {
|
||||
if (this.name) {
|
||||
const customEmoji = this.ce.find(x => x.name == this.name);
|
||||
if (customEmoji) {
|
||||
this.customEmoji = customEmoji;
|
||||
this.url = this.$store.state.device.disableShowingAnimatedImages
|
||||
? getStaticImageUrl(customEmoji.url)
|
||||
: customEmoji.url;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.name) {
|
||||
const customEmoji = this.customEmojis.find(x => x.name == this.name);
|
||||
if (customEmoji) {
|
||||
this.customEmoji = customEmoji;
|
||||
this.url = this.$store.state.device.disableShowingAnimatedImages
|
||||
? getStaticImageUrl(customEmoji.url)
|
||||
: customEmoji.url;
|
||||
} else {
|
||||
//const emoji = lib[this.name];
|
||||
//if (emoji) {
|
||||
// this.char = emoji.char;
|
||||
//}
|
||||
}
|
||||
} else {
|
||||
if (!this.name) {
|
||||
this.char = this.emoji;
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,8 @@ export default Vue.extend({
|
||||
height: 150px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 16px;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
88
src/client/components/link.vue
Normal file
88
src/client/components/link.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<component :is="self ? 'router-link' : 'a'" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
|
||||
@mouseover="onMouseover"
|
||||
@mouseleave="onMouseleave"
|
||||
:title="url"
|
||||
>
|
||||
<slot></slot>
|
||||
<fa :icon="faExternalLinkSquareAlt" v-if="target === '_blank'" class="icon"/>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { url as local } from '../config';
|
||||
import XUrlPreview from './url-preview-popup.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
rel: {
|
||||
type: String,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const self = this.url.startsWith(local);
|
||||
return {
|
||||
local,
|
||||
self: self,
|
||||
attr: self ? 'to' : 'href',
|
||||
target: self ? null : '_blank',
|
||||
showTimer: null,
|
||||
hideTimer: null,
|
||||
preview: null,
|
||||
faExternalLinkSquareAlt
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
showPreview() {
|
||||
if (!document.body.contains(this.$el)) return;
|
||||
if (this.preview) return;
|
||||
|
||||
this.preview = new XUrlPreview({
|
||||
parent: this,
|
||||
propsData: {
|
||||
url: this.url,
|
||||
source: this.$el
|
||||
}
|
||||
}).$mount();
|
||||
|
||||
document.body.appendChild(this.preview.$el);
|
||||
},
|
||||
closePreview() {
|
||||
if (this.preview) {
|
||||
this.preview.destroyDom();
|
||||
this.preview = null;
|
||||
}
|
||||
},
|
||||
onMouseover() {
|
||||
clearTimeout(this.showTimer);
|
||||
clearTimeout(this.hideTimer);
|
||||
this.showTimer = setTimeout(this.showPreview, 500);
|
||||
},
|
||||
onMouseleave() {
|
||||
clearTimeout(this.showTimer);
|
||||
clearTimeout(this.hideTimer);
|
||||
this.hideTimer = setTimeout(this.closePreview, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.xlcxczvw {
|
||||
word-break: break-all;
|
||||
|
||||
> .icon {
|
||||
padding-left: 2px;
|
||||
font-size: .9em;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,30 +1,47 @@
|
||||
<template>
|
||||
<div class="yxspomdl">
|
||||
<fa :icon="faSpinner" pulse fixed-width class="icon"/>
|
||||
<div class="ring"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
faSpinner
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@keyframes ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.yxspomdl {
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
|
||||
> .icon {
|
||||
font-size: 32px;
|
||||
opacity: 0.5;
|
||||
> .ring {
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
> .ring:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 8px;
|
||||
border-radius: 50%;
|
||||
border: solid 6px;
|
||||
border-color: var(--fg) transparent transparent transparent;
|
||||
animation: ring 0.5s linear infinite;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -2,6 +2,7 @@ import Vue, { VNode } from 'vue';
|
||||
import { MfmForest } from '../../mfm/types';
|
||||
import { parse, parsePlain } from '../../mfm/parse';
|
||||
import MkUrl from './url.vue';
|
||||
import MkLink from './link.vue';
|
||||
import MkMention from './mention.vue';
|
||||
import { concat } from '../../prelude/array';
|
||||
import MkFormula from './formula.vue';
|
||||
@ -158,14 +159,12 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
}
|
||||
|
||||
case 'link': {
|
||||
return [createElement('a', {
|
||||
attrs: {
|
||||
class: 'link _link',
|
||||
href: token.node.props.url,
|
||||
return [createElement(MkLink, {
|
||||
key: Math.random(),
|
||||
props: {
|
||||
url: token.node.props.url,
|
||||
rel: 'nofollow noopener',
|
||||
target: '_blank',
|
||||
title: token.node.props.url,
|
||||
}
|
||||
},
|
||||
}, genEl(token.children))];
|
||||
}
|
||||
|
||||
@ -235,7 +234,6 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
}
|
||||
|
||||
case 'emoji': {
|
||||
const customEmojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
|
||||
return [createElement('mk-emoji', {
|
||||
key: Math.random(),
|
||||
attrs: {
|
||||
@ -243,7 +241,7 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
name: token.node.props.name
|
||||
},
|
||||
props: {
|
||||
customEmojis: this.customEmojis || customEmojis,
|
||||
customEmojis: this.customEmojis,
|
||||
normal: this.plain
|
||||
}
|
||||
})];
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mfm-core v-bind="$attrs" class="havbbuyv" :class="{ nowrap: $attrs['nowrap'] }" v-once/>
|
||||
<mfm-core v-bind="$attrs" class="havbbuyv" :class="{ nowrap: $attrs['nowrap'] }"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -19,7 +19,7 @@
|
||||
</router-link>
|
||||
</i18n>
|
||||
<div class="info">
|
||||
<button class="_button time" @click="showRenoteMenu"><mk-time :time="note.createdAt"/></button>
|
||||
<button class="_button time" @click="showRenoteMenu()" ref="renoteTime"><mk-time :time="note.createdAt"/></button>
|
||||
<span class="visibility" v-if="note.visibility != 'public'">
|
||||
<fa v-if="note.visibility == 'home'" :icon="faHome"/>
|
||||
<fa v-if="note.visibility == 'followers'" :icon="faUnlock"/>
|
||||
@ -558,7 +558,7 @@ export default Vue.extend({
|
||||
}).then(this.focus);
|
||||
},
|
||||
|
||||
showRenoteMenu(ev) {
|
||||
showRenoteMenu(viaKeyboard = false) {
|
||||
if (!this.$store.getters.isSignedIn || (this.$store.state.i.id !== this.note.userId)) return;
|
||||
this.$root.menu({
|
||||
items: [{
|
||||
@ -571,7 +571,7 @@ export default Vue.extend({
|
||||
Vue.set(this.note, 'deletedAt', new Date());
|
||||
}
|
||||
}],
|
||||
source: ev.currentTarget || ev.target,
|
||||
source: this.$refs.renoteTime,
|
||||
viaKeyboard: viaKeyboard
|
||||
});
|
||||
},
|
||||
|
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="mk-notes" v-size="[{ max: 500 }]">
|
||||
<div class="empty _panel" v-if="empty">
|
||||
<div class="empty" v-if="empty">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" alt=""/>
|
||||
<div>{{ $t('noNotes') }}</div>
|
||||
</div>
|
||||
|
||||
<mk-error v-if="error" @retry="init()"/>
|
||||
|
||||
<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note, i }">
|
||||
<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note }">
|
||||
<x-note :note="note" :detail="detail" :key="note.id"/>
|
||||
</x-list>
|
||||
|
||||
@ -94,6 +94,8 @@ export default Vue.extend({
|
||||
height: 128px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 16px;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
<header>
|
||||
<button class="cancel _button" @click="cancel"><fa :icon="faTimes"/></button>
|
||||
<div>
|
||||
<span class="text-count" :class="{ over: trimmedLength(text) > 500 }">{{ 500 - trimmedLength(text) }}</span>
|
||||
<span class="text-count" :class="{ over: trimmedLength(text) > max }">{{ max - trimmedLength(text) }}</span>
|
||||
<button class="_button visibility" @click="setVisibility" ref="visibilityButton">
|
||||
<span v-if="visibility === 'public'"><fa :icon="faGlobe"/></span>
|
||||
<span v-if="visibility === 'home'"><fa :icon="faHome"/></span>
|
||||
@ -153,7 +153,7 @@ export default Vue.extend({
|
||||
this.$t('_postForm._placeholders.f')
|
||||
];
|
||||
const x = xs[Math.floor(Math.random() * xs.length)];
|
||||
|
||||
|
||||
return this.renote
|
||||
? this.$t('_postForm.quotePlaceholder')
|
||||
: this.reply
|
||||
@ -172,14 +172,18 @@ export default Vue.extend({
|
||||
canPost(): boolean {
|
||||
return !this.posting &&
|
||||
(1 <= this.text.length || 1 <= this.files.length || this.poll || this.renote) &&
|
||||
(length(this.text.trim()) <= 500) &&
|
||||
(length(this.text.trim()) <= this.max) &&
|
||||
(!this.poll || this.pollChoices.length >= 2);
|
||||
},
|
||||
|
||||
max(): number {
|
||||
return this.$store.state.instance.meta ? this.$store.state.instance.meta.maxNoteTextLength : 1000;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
localOnly() {
|
||||
this.$store.commit('device/setLocalOnly', this.localOnly);
|
||||
this.$store.commit('deviceUser/setLocalOnly', this.localOnly);
|
||||
}
|
||||
},
|
||||
|
||||
@ -215,9 +219,9 @@ export default Vue.extend({
|
||||
}
|
||||
|
||||
// デフォルト公開範囲
|
||||
this.applyVisibility(this.$store.state.settings.rememberNoteVisibility ? this.$store.state.device.visibility : this.$store.state.settings.defaultNoteVisibility);
|
||||
this.applyVisibility(this.$store.state.settings.rememberNoteVisibility ? this.$store.state.deviceUser.visibility : this.$store.state.settings.defaultNoteVisibility);
|
||||
|
||||
this.localOnly = this.$store.state.settings.rememberNoteVisibility ? this.$store.state.device.localOnly : this.$store.state.settings.defaultNoteLocalOnly;
|
||||
this.localOnly = this.$store.state.settings.rememberNoteVisibility ? this.$store.state.deviceUser.localOnly : this.$store.state.settings.defaultNoteLocalOnly;
|
||||
|
||||
// 公開以外へのリプライ時は元の公開範囲を引き継ぐ
|
||||
if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) {
|
||||
@ -713,7 +717,7 @@ export default Vue.extend({
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
font-family: initial;
|
||||
font-family: inherit;
|
||||
|
||||
@media (max-width: 500px) {
|
||||
padding: 0 16px;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-emoji :emoji="reaction.startsWith(':') ? null : reaction" :name="reaction.startsWith(':') ? reaction.substr(1, reaction.length - 2) : null" :is-reaction="true" :custom-emojis="customEmojis" :normal="true" :no-style="noStyle"/>
|
||||
<mk-emoji :emoji="reaction.startsWith(':') ? null : reaction" :name="reaction.startsWith(':') ? reaction.substr(1, reaction.length - 2) : null" :is-reaction="true" :normal="true" :no-style="noStyle"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@ -18,15 +18,5 @@ export default Vue.extend({
|
||||
default: false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
customEmojis: []
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
if (meta && meta.emojis) this.customEmojis = meta.emojis;
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -82,7 +82,6 @@ export default Vue.extend({
|
||||
token: '',
|
||||
apiUrl,
|
||||
host: toUnicode(host),
|
||||
meta: null,
|
||||
totpLogin: false,
|
||||
credential: null,
|
||||
challengeData: null,
|
||||
@ -91,11 +90,13 @@ export default Vue.extend({
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
});
|
||||
computed: {
|
||||
meta() {
|
||||
return this.$store.state.instance.meta;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.autoSet) {
|
||||
this.$once('login', res => {
|
||||
localStorage.setItem('i', res.i);
|
||||
|
@ -79,7 +79,6 @@ export default Vue.extend({
|
||||
usernameState: null,
|
||||
passwordStrength: '',
|
||||
passwordRetypeState: null,
|
||||
meta: {},
|
||||
submitting: false,
|
||||
ToSAgreement: false,
|
||||
faLock, faExclamationTriangle, faSpinner, faCheck
|
||||
@ -87,6 +86,10 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
computed: {
|
||||
meta() {
|
||||
return this.$store.state.instance.meta;
|
||||
},
|
||||
|
||||
shouldShowProfileUrl(): boolean {
|
||||
return (this.username != '' &&
|
||||
this.usernameState != 'invalid-format' &&
|
||||
@ -95,12 +98,6 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
});
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const head = document.getElementsByTagName('head')[0];
|
||||
const script = document.createElement('script');
|
||||
|
@ -27,6 +27,7 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
connection: null,
|
||||
connection2: null,
|
||||
pagination: null,
|
||||
baseQuery: {
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
@ -40,6 +41,7 @@ export default Vue.extend({
|
||||
created() {
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
this.connection.dispose();
|
||||
if (this.connection2) this.connection2.dispose();
|
||||
});
|
||||
|
||||
const prepend = note => {
|
||||
@ -54,6 +56,12 @@ export default Vue.extend({
|
||||
(this.$refs.tl as any).reload();
|
||||
};
|
||||
|
||||
const onChangeFollowing = () => {
|
||||
if (!this.$refs.tl.backed) {
|
||||
this.$refs.tl.reload();
|
||||
}
|
||||
};
|
||||
|
||||
let endpoint;
|
||||
|
||||
if (this.src == 'antenna') {
|
||||
@ -67,13 +75,12 @@ export default Vue.extend({
|
||||
this.connection.on('note', prepend);
|
||||
} else if (this.src == 'home') {
|
||||
endpoint = 'notes/timeline';
|
||||
const onChangeFollowing = () => {
|
||||
this.fetch();
|
||||
};
|
||||
this.connection = this.$root.stream.useSharedConnection('homeTimeline');
|
||||
this.connection.on('note', prepend);
|
||||
this.connection.on('follow', onChangeFollowing);
|
||||
this.connection.on('unfollow', onChangeFollowing);
|
||||
|
||||
this.connection2 = this.$root.stream.useSharedConnection('main');
|
||||
this.connection2.on('follow', onChangeFollowing);
|
||||
this.connection2.on('unfollow', onChangeFollowing);
|
||||
} else if (this.src == 'local') {
|
||||
endpoint = 'notes/local-timeline';
|
||||
this.connection = this.$root.stream.useSharedConnection('localTimeline');
|
||||
|
@ -124,6 +124,7 @@ export default Vue.extend({
|
||||
&.primary {
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 6px 16px var(--accentShadow);
|
||||
|
||||
&:not(:disabled):hover {
|
||||
background: var(--jkhztclx);
|
||||
|
@ -254,7 +254,7 @@ export default Vue.extend({
|
||||
|
||||
> .input {
|
||||
position: relative;
|
||||
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: block;
|
||||
@ -327,14 +327,16 @@ export default Vue.extend({
|
||||
}
|
||||
|
||||
> input {
|
||||
$height: 32px;
|
||||
display: block;
|
||||
height: $height;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
line-height: 32px;
|
||||
line-height: $height;
|
||||
color: var(--inputText);
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
56
src/client/components/url-preview-popup.vue
Normal file
56
src/client/components/url-preview-popup.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="fgmtyycl _panel" :style="{ top: top + 'px', left: left + 'px' }">
|
||||
<x-url-preview :url="url"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../i18n';
|
||||
import XUrlPreview from './url-preview.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n,
|
||||
|
||||
components: {
|
||||
XUrlPreview
|
||||
},
|
||||
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
source: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
u: null,
|
||||
top: 0,
|
||||
left: 0,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const rect = this.source.getBoundingClientRect();
|
||||
const x = ((rect.left + (this.source.offsetWidth / 2)) - (300 / 2)) + window.pageXOffset;
|
||||
const y = rect.top + this.source.offsetHeight + window.pageYOffset;
|
||||
|
||||
this.top = y;
|
||||
this.left = x;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fgmtyycl {
|
||||
position: absolute;
|
||||
z-index: 11000;
|
||||
width: 500px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div v-else class="mk-url-preview" v-size="[{ max: 400 }, { max: 350 }]">
|
||||
<transition name="zoom" mode="out-in">
|
||||
<component :is="hasRoute ? 'router-link' : 'a'" :class="{ compact }" :[attr]="hasRoute ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url" v-if="!fetching">
|
||||
<component :is="self ? 'router-link' : 'a'" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url" v-if="!fetching">
|
||||
<div class="thumbnail" v-if="thumbnail" :style="`background-image: url('${thumbnail}')`">
|
||||
<button class="_button" v-if="!playerEnabled && player.url" @click.prevent="playerEnabled = true" :title="$t('enable-player')"><fa :icon="faPlayCircle"/></button>
|
||||
</div>
|
||||
@ -58,12 +58,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
data() {
|
||||
const isSelf = this.url.startsWith(local);
|
||||
const hasRoute =
|
||||
(this.url.substr(local.length) === '/') ||
|
||||
this.url.substr(local.length).startsWith('/@') ||
|
||||
this.url.substr(local.length).startsWith('/notes/') ||
|
||||
this.url.substr(local.length).startsWith('/tags/');
|
||||
const self = this.url.startsWith(local);
|
||||
return {
|
||||
local,
|
||||
fetching: true,
|
||||
@ -79,10 +74,9 @@ export default Vue.extend({
|
||||
},
|
||||
tweetUrl: null,
|
||||
playerEnabled: false,
|
||||
self: isSelf,
|
||||
hasRoute: hasRoute,
|
||||
attr: hasRoute ? 'to' : 'href',
|
||||
target: hasRoute ? null : '_blank',
|
||||
self: self,
|
||||
attr: self ? 'to' : 'href',
|
||||
target: self ? null : '_blank',
|
||||
faPlayCircle
|
||||
};
|
||||
},
|
||||
|
@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<component :is="hasRoute ? 'router-link' : 'a'" class="ieqqeuvs _link" :[attr]="hasRoute ? url.substr(local.length) : url" :rel="rel" :target="target">
|
||||
<component :is="self ? 'router-link' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
|
||||
@mouseover="onMouseover"
|
||||
@mouseleave="onMouseleave"
|
||||
>
|
||||
<template v-if="!self">
|
||||
<span class="schema">{{ schema }}//</span>
|
||||
<span class="hostname">{{ hostname }}</span>
|
||||
@ -20,28 +23,35 @@ import Vue from 'vue';
|
||||
import { faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { toUnicode as decodePunycode } from 'punycode';
|
||||
import { url as local } from '../config';
|
||||
import XUrlPreview from './url-preview-popup.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['url', 'rel'],
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
rel: {
|
||||
type: String,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const isSelf = this.url.startsWith(local);
|
||||
const hasRoute = isSelf && (
|
||||
(this.url.substr(local.length) === '/') ||
|
||||
this.url.substr(local.length).startsWith('/@') ||
|
||||
this.url.substr(local.length).startsWith('/notes/') ||
|
||||
this.url.substr(local.length).startsWith('/tags/'));
|
||||
const self = this.url.startsWith(local);
|
||||
return {
|
||||
local,
|
||||
schema: null,
|
||||
hostname: null,
|
||||
port: null,
|
||||
pathname: null,
|
||||
query: null,
|
||||
hash: null,
|
||||
self: isSelf,
|
||||
hasRoute: hasRoute,
|
||||
attr: hasRoute ? 'to' : 'href',
|
||||
target: hasRoute ? null : '_blank',
|
||||
schema: null as string | null,
|
||||
hostname: null as string | null,
|
||||
port: null as string | null,
|
||||
pathname: null as string | null,
|
||||
query: null as string | null,
|
||||
hash: null as string | null,
|
||||
self: self,
|
||||
attr: self ? 'to' : 'href',
|
||||
target: self ? null : '_blank',
|
||||
showTimer: null,
|
||||
hideTimer: null,
|
||||
preview: null,
|
||||
faExternalLinkSquareAlt
|
||||
};
|
||||
},
|
||||
@ -53,6 +63,38 @@ export default Vue.extend({
|
||||
this.pathname = decodeURIComponent(url.pathname);
|
||||
this.query = decodeURIComponent(url.search);
|
||||
this.hash = decodeURIComponent(url.hash);
|
||||
},
|
||||
methods: {
|
||||
showPreview() {
|
||||
if (!document.body.contains(this.$el)) return;
|
||||
if (this.preview) return;
|
||||
|
||||
this.preview = new XUrlPreview({
|
||||
parent: this,
|
||||
propsData: {
|
||||
url: this.url,
|
||||
source: this.$el
|
||||
}
|
||||
}).$mount();
|
||||
|
||||
document.body.appendChild(this.preview.$el);
|
||||
},
|
||||
closePreview() {
|
||||
if (this.preview) {
|
||||
this.preview.destroyDom();
|
||||
this.preview = null;
|
||||
}
|
||||
},
|
||||
onMouseover() {
|
||||
clearTimeout(this.showTimer);
|
||||
clearTimeout(this.hideTimer);
|
||||
this.showTimer = setTimeout(this.showPreview, 500);
|
||||
},
|
||||
onMouseleave() {
|
||||
clearTimeout(this.showTimer);
|
||||
clearTimeout(this.hideTimer);
|
||||
this.hideTimer = setTimeout(this.closePreview, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faAt, faListUl, faEye, faEyeSlash, faBan, faPencilAlt, faComments } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faAt, faListUl, faEye, faEyeSlash, faBan, faPencilAlt, faComments, faUsers } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faSnowflake, faEnvelope } from '@fortawesome/free-regular-svg-icons';
|
||||
import i18n from '../i18n';
|
||||
import XMenu from './menu.vue';
|
||||
@ -43,7 +43,11 @@ export default Vue.extend({
|
||||
icon: faListUl,
|
||||
text: this.$t('addToList'),
|
||||
action: this.pushList
|
||||
}] as any;
|
||||
}, this.$store.state.i.id != this.user.id ? {
|
||||
icon: faUsers,
|
||||
text: this.$t('inviteToGroup'),
|
||||
action: this.inviteGroup
|
||||
} : undefined] as any;
|
||||
|
||||
if (this.$store.getters.isSignedIn && this.$store.state.i.id != this.user.id) {
|
||||
menu = menu.concat([null, {
|
||||
@ -118,6 +122,42 @@ export default Vue.extend({
|
||||
});
|
||||
},
|
||||
|
||||
async inviteGroup() {
|
||||
const groups = await this.$root.api('users/groups/owned');
|
||||
if (groups.length === 0) {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: this.$t('youHaveNoGroups')
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { canceled, result: groupId } = await this.$root.dialog({
|
||||
type: null,
|
||||
title: this.$t('group'),
|
||||
select: {
|
||||
items: groups.map(group => ({
|
||||
value: group.id, text: group.name
|
||||
}))
|
||||
},
|
||||
showCancelButton: true
|
||||
});
|
||||
if (canceled) return;
|
||||
this.$root.api('users/groups/invite', {
|
||||
groupId: groupId,
|
||||
userId: this.user.id
|
||||
}).then(() => {
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
iconOnly: true, autoClose: true
|
||||
});
|
||||
}).catch(e => {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: e
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async toggleMute() {
|
||||
this.$root.api(this.user.isMuted ? 'mute/delete' : 'mute/create', {
|
||||
userId: this.user.id
|
||||
|
@ -56,14 +56,14 @@ export default Vue.extend({
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
v: this.$store.state.settings.rememberNoteVisibility ? this.$store.state.device.visibility : (this.currentVisibility || this.$store.state.settings.defaultNoteVisibility),
|
||||
v: this.$store.state.settings.rememberNoteVisibility ? this.$store.state.deviceUser.visibility : (this.currentVisibility || this.$store.state.settings.defaultNoteVisibility),
|
||||
faGlobe, faUnlock, faEnvelope, faHome
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
choose(visibility) {
|
||||
if (this.$store.state.settings.rememberNoteVisibility) {
|
||||
this.$store.commit('device/setVisibility', visibility);
|
||||
this.$store.commit('deviceUser/setVisibility', visibility);
|
||||
}
|
||||
this.$emit('chosen', visibility);
|
||||
this.destroyDom();
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { ResizeObserver } from '@juggle/resize-observer';
|
||||
|
||||
export default {
|
||||
inserted(el, binding, vn) {
|
||||
const query = binding.value;
|
||||
@ -52,13 +54,16 @@ export default {
|
||||
|
||||
calc();
|
||||
|
||||
el._sizeResizeCb_ = calc;
|
||||
const ro = new ResizeObserver((entries, observer) => {
|
||||
calc();
|
||||
});
|
||||
|
||||
ro.observe(el);
|
||||
|
||||
window.addEventListener('resize', calc);
|
||||
vn.context.$on('hook:activated', calc);
|
||||
el._ro_ = ro;
|
||||
},
|
||||
|
||||
unbind(el, binding, vn) {
|
||||
window.removeEventListener('resize', el._sizeResizeCb_);
|
||||
el._ro_.unobserve(el);
|
||||
}
|
||||
};
|
||||
|
@ -157,8 +157,6 @@ os.init(async () => {
|
||||
},
|
||||
methods: {
|
||||
api: os.api,
|
||||
getMeta: os.getMeta,
|
||||
getMetaSync: os.getMetaSync,
|
||||
signout: os.signout,
|
||||
new(vm, props) {
|
||||
const x = new vm({
|
||||
|
@ -17,16 +17,6 @@ let pending = 0;
|
||||
* Misskey Operating System
|
||||
*/
|
||||
export default class MiOS extends EventEmitter {
|
||||
/**
|
||||
* Misskeyの /meta で取得できるメタ情報
|
||||
*/
|
||||
private meta: {
|
||||
data: { [x: string]: any };
|
||||
chachedAt: Date;
|
||||
};
|
||||
|
||||
private isMetaFetching = false;
|
||||
|
||||
public app: Vue;
|
||||
|
||||
public store: ReturnType<typeof initStore>;
|
||||
@ -88,7 +78,7 @@ export default class MiOS extends EventEmitter {
|
||||
// When failure
|
||||
.catch(() => {
|
||||
// Render the error screen
|
||||
document.body.innerHTML = '<div id="err">Error</div>';
|
||||
document.body.innerHTML = '<div id="err">Oops!</div>';
|
||||
|
||||
Progress.done();
|
||||
});
|
||||
@ -107,9 +97,9 @@ export default class MiOS extends EventEmitter {
|
||||
// Finish init
|
||||
callback();
|
||||
|
||||
// Init service worker
|
||||
this.getMeta().then(data => {
|
||||
if (data.swPublickey) this.registerSw(data.swPublickey);
|
||||
this.store.dispatch('instance/fetch').then(() => {
|
||||
// Init service worker
|
||||
if (this.store.state.instance.meta.swPublickey) this.registerSw(this.store.state.instance.meta.swPublickey);
|
||||
});
|
||||
};
|
||||
|
||||
@ -350,49 +340,6 @@ export default class MiOS extends EventEmitter {
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Misskeyのメタ情報を取得します
|
||||
*/
|
||||
@autobind
|
||||
public getMetaSync() {
|
||||
return this.meta ? this.meta.data : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Misskeyのメタ情報を取得します
|
||||
* @param force キャッシュを無視するか否か
|
||||
*/
|
||||
@autobind
|
||||
public getMeta(force = false) {
|
||||
return new Promise<{ [x: string]: any }>(async (res, rej) => {
|
||||
if (this.isMetaFetching) {
|
||||
this.once('_meta_fetched_', () => {
|
||||
res(this.meta.data);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const expire = 1000 * 60; // 1min
|
||||
|
||||
// forceが有効, meta情報を保持していない or 期限切れ
|
||||
if (force || this.meta == null || Date.now() - this.meta.chachedAt.getTime() > expire) {
|
||||
this.isMetaFetching = true;
|
||||
const meta = await this.api('meta', {
|
||||
detail: false
|
||||
});
|
||||
this.meta = {
|
||||
data: meta,
|
||||
chachedAt: new Date()
|
||||
};
|
||||
this.isMetaFetching = false;
|
||||
this.emit('_meta_fetched_');
|
||||
res(meta);
|
||||
} else {
|
||||
res(this.meta.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -84,18 +84,19 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
version,
|
||||
meta: null,
|
||||
stats: null,
|
||||
serverInfo: null,
|
||||
faInfoCircle
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
});
|
||||
computed: {
|
||||
meta() {
|
||||
return this.$store.state.instance.meta;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.api('stats').then(res => {
|
||||
this.stats = res;
|
||||
});
|
||||
|
@ -115,13 +115,15 @@ export default Vue.extend({
|
||||
tagsLocal: [],
|
||||
tagsRemote: [],
|
||||
stats: null,
|
||||
meta: null,
|
||||
num: Vue.filter('number'),
|
||||
faBookmark, faChartLine, faCommentAlt, faPlus, faHashtag, faRocket
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
meta() {
|
||||
return this.$store.state.instance.meta;
|
||||
},
|
||||
tagUsers(): any {
|
||||
return {
|
||||
endpoint: 'hashtags/users',
|
||||
@ -159,9 +161,6 @@ export default Vue.extend({
|
||||
this.$root.api('stats').then(stats => {
|
||||
this.stats = stats;
|
||||
});
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
127
src/client/pages/index.home.tutorial.vue
Normal file
127
src/client/pages/index.home.tutorial.vue
Normal file
@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="_card tbkwesmv">
|
||||
<div class="_title"><fa :icon="faInfoCircle"/> {{ $t('_tutorial.title') }}</div>
|
||||
<div class="_content" v-if="tutorial === 0">
|
||||
<div>{{ $t('_tutorial.step1_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step1_2') }}</div>
|
||||
<div>{{ $t('_tutorial.step1_3') }}</div>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 1">
|
||||
<div>{{ $t('_tutorial.step2_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step2_2') }}</div>
|
||||
<router-link class="_link" to="/my/settings">{{ $t('editProfile') }}</router-link>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 2">
|
||||
<div>{{ $t('_tutorial.step3_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step3_2') }}</div>
|
||||
<div>{{ $t('_tutorial.step3_3') }}</div>
|
||||
<small>{{ $t('_tutorial.step3_4') }}</small>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 3">
|
||||
<div>{{ $t('_tutorial.step4_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step4_2') }}</div>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 4">
|
||||
<div>{{ $t('_tutorial.step5_1') }}</div>
|
||||
<i18n path="_tutorial.step5_2" tag="div">
|
||||
<router-link class="_link" place="featured" to="/featured">{{ $t('featured') }}</router-link>
|
||||
<router-link class="_link" place="explore" to="/explore">{{ $t('explore') }}</router-link>
|
||||
</i18n>
|
||||
<div>{{ $t('_tutorial.step5_3') }}</div>
|
||||
<small>{{ $t('_tutorial.step5_4') }}</small>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 5">
|
||||
<div>{{ $t('_tutorial.step6_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step6_2') }}</div>
|
||||
<div>{{ $t('_tutorial.step6_3') }}</div>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 6">
|
||||
<div>{{ $t('_tutorial.step7_1') }}</div>
|
||||
<i18n path="_tutorial.step7_2" tag="div">
|
||||
<router-link class="_link" place="help" to="/docs">{{ $t('help') }}</router-link>
|
||||
</i18n>
|
||||
<div>{{ $t('_tutorial.step7_3') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="_footer navigation">
|
||||
<div class="step">
|
||||
<button class="arrow _button" @click="tutorial--" :disabled="tutorial === 0">
|
||||
<fa :icon="faChevronLeft"/>
|
||||
</button>
|
||||
<span>{{ tutorial + 1 }} / 7</span>
|
||||
<button class="arrow _button" @click="tutorial++" :disabled="tutorial === 6">
|
||||
<fa :icon="faChevronRight"/>
|
||||
</button>
|
||||
</div>
|
||||
<mk-button class="ok" @click="tutorial = -1" primary v-if="tutorial === 6"><fa :icon="faCheck"/> {{ $t('gotIt') }}</mk-button>
|
||||
<mk-button class="ok" @click="tutorial++" primary v-else><fa :icon="faCheck"/> {{ $t('next') }}</mk-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faInfoCircle, faChevronLeft, faChevronRight, faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||
import MkButton from '../components/ui/button.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
MkButton,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
faInfoCircle, faChevronLeft, faChevronRight, faCheck
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
tutorial: {
|
||||
get() { return this.$store.state.settings.tutorial || 0; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'tutorial', value }); }
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tbkwesmv {
|
||||
> ._content {
|
||||
> small {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
> .navigation {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
|
||||
> .step {
|
||||
> .arrow {
|
||||
padding: 4px;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> span {
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
> .ok {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -13,6 +13,9 @@
|
||||
<fa :icon="menuOpened ? faAngleUp : faAngleDown" style="margin-left: 8px;"/>
|
||||
</button>
|
||||
</portal>
|
||||
|
||||
<x-tutorial class="tutorial" v-if="$store.state.settings.tutorial != -1"/>
|
||||
|
||||
<x-timeline ref="tl" :key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src" :src="src" :list="list" :antenna="antenna" @before="before()" @after="after()"/>
|
||||
</div>
|
||||
</template>
|
||||
@ -23,6 +26,7 @@ import { faAngleDown, faAngleUp, faHome, faShareAlt, faGlobe, faListUl, faSatell
|
||||
import { faComments } from '@fortawesome/free-regular-svg-icons';
|
||||
import Progress from '../scripts/loading';
|
||||
import XTimeline from '../components/timeline.vue';
|
||||
import XTutorial from './index.home.tutorial.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
metaInfo() {
|
||||
@ -32,7 +36,8 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
components: {
|
||||
XTimeline
|
||||
XTimeline,
|
||||
XTutorial,
|
||||
},
|
||||
|
||||
props: {
|
||||
@ -57,7 +62,7 @@ export default Vue.extend({
|
||||
return {
|
||||
't': this.focus
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
@ -78,21 +83,11 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.getMeta().then((meta: Record<string, any>) => {
|
||||
if (!(
|
||||
this.enableGlobalTimeline = !meta.disableGlobalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin
|
||||
) && this.src === 'global') this.src = 'local';
|
||||
if (!(
|
||||
this.enableLocalTimeline = !meta.disableLocalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin
|
||||
) && ['local', 'social'].includes(this.src)) this.src = 'home';
|
||||
});
|
||||
if (this.$store.state.device.tl) {
|
||||
this.src = this.$store.state.device.tl.src;
|
||||
if (this.src === 'list') {
|
||||
this.list = this.$store.state.device.tl.arg;
|
||||
} else if (this.src === 'antenna') {
|
||||
this.antenna = this.$store.state.device.tl.arg;
|
||||
}
|
||||
this.src = this.$store.state.deviceUser.tl.src;
|
||||
if (this.src === 'list') {
|
||||
this.list = this.$store.state.deviceUser.tl.arg;
|
||||
} else if (this.src === 'antenna') {
|
||||
this.antenna = this.$store.state.deviceUser.tl.arg;
|
||||
}
|
||||
},
|
||||
|
||||
@ -159,7 +154,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
saveSrc() {
|
||||
this.$store.commit('device/setTl', {
|
||||
this.$store.commit('deviceUser/setTl', {
|
||||
src: this.src,
|
||||
arg: this.src == 'list' ? this.list : this.antenna
|
||||
});
|
||||
@ -172,7 +167,13 @@ export default Vue.extend({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.mk-home {
|
||||
> .tutorial {
|
||||
margin-bottom: var(--margin);
|
||||
}
|
||||
}
|
||||
|
||||
._kjvfvyph_ {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="rsqzvsbo">
|
||||
<div class="_panel about">
|
||||
<div class="banner" :style="{ backgroundImage: `url(${ banner })` }"></div>
|
||||
<div class="_panel about" v-if="meta">
|
||||
<div class="banner" :style="{ backgroundImage: `url(${ meta.bannerUrl })` }"></div>
|
||||
<div class="body">
|
||||
<h1 class="name" v-html="name || host"></h1>
|
||||
<div class="desc" v-html="description || $t('introMisskey')"></div>
|
||||
<h1 class="name" v-html="meta.name || host"></h1>
|
||||
<div class="desc" v-html="meta.description || $t('introMisskey')"></div>
|
||||
<mk-button @click="signup()" style="display: inline-block; margin-right: 16px;" primary>{{ $t('signup') }}</mk-button>
|
||||
<mk-button @click="signin()" style="display: inline-block;">{{ $t('login') }}</mk-button>
|
||||
</div>
|
||||
@ -39,23 +39,16 @@ export default Vue.extend({
|
||||
noPaging: true,
|
||||
},
|
||||
host: toUnicode(host),
|
||||
meta: null,
|
||||
name: null,
|
||||
description: null,
|
||||
banner: null,
|
||||
announcements: [],
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
this.name = meta.name;
|
||||
this.description = meta.description;
|
||||
this.announcements = meta.announcements;
|
||||
this.banner = meta.bannerUrl;
|
||||
});
|
||||
computed: {
|
||||
meta() {
|
||||
return this.$store.state.instance.meta;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.api('stats').then(stats => {
|
||||
this.stats = stats;
|
||||
});
|
||||
|
@ -20,15 +20,14 @@ export default Vue.extend({
|
||||
|
||||
data() {
|
||||
return {
|
||||
meta: null,
|
||||
instanceName: getInstanceName(),
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
});
|
||||
}
|
||||
computed: {
|
||||
meta() {
|
||||
return this.$store.state.instance.meta;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -98,7 +98,7 @@
|
||||
<div class="operations">
|
||||
<span class="label">{{ $t('operations') }}</span>
|
||||
<mk-switch v-model="isSuspended" class="switch">{{ $t('stopActivityDelivery') }}</mk-switch>
|
||||
<mk-switch v-model="isBlocked" class="switch">{{ $t('blockThisInstance') }}</mk-switch>
|
||||
<mk-switch :value="isBlocked" class="switch" @change="changeBlock">{{ $t('blockThisInstance') }}</mk-switch>
|
||||
</div>
|
||||
<details class="metadata">
|
||||
<summary class="label">{{ $t('metadata') }}</summary>
|
||||
@ -147,9 +147,7 @@ export default Vue.extend({
|
||||
|
||||
data() {
|
||||
return {
|
||||
meta: null,
|
||||
isSuspended: false,
|
||||
isBlocked: false,
|
||||
isSuspended: this.instance.isSuspended,
|
||||
now: null,
|
||||
chart: null,
|
||||
chartInstance: null,
|
||||
@ -184,6 +182,14 @@ export default Vue.extend({
|
||||
null;
|
||||
|
||||
return stats;
|
||||
},
|
||||
|
||||
meta() {
|
||||
return this.$store.state.instance.meta;
|
||||
},
|
||||
|
||||
isBlocked() {
|
||||
return this.meta && this.meta.blockedHosts.includes(this.instance.host);
|
||||
}
|
||||
},
|
||||
|
||||
@ -195,12 +201,6 @@ export default Vue.extend({
|
||||
});
|
||||
},
|
||||
|
||||
isBlocked() {
|
||||
this.$root.api('admin/update-meta', {
|
||||
blockedHosts: this.isBlocked ? this.meta.blockedHosts.concat([this.instance.host]) : this.meta.blockedHosts.filter(x => x !== this.instance.host)
|
||||
});
|
||||
},
|
||||
|
||||
chartSrc() {
|
||||
this.renderChart();
|
||||
},
|
||||
@ -210,13 +210,7 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
|
||||
async created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
this.isSuspended = this.instance.isSuspended;
|
||||
this.isBlocked = this.meta.blockedHosts.includes(this.instance.host);
|
||||
});
|
||||
|
||||
async created() {
|
||||
this.now = new Date();
|
||||
|
||||
const [perHour, perDay] = await Promise.all([
|
||||
@ -235,6 +229,12 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeBlock(e) {
|
||||
this.$root.api('admin/update-meta', {
|
||||
blockedHosts: this.isBlocked ? this.meta.blockedHosts.concat([this.instance.host]) : this.meta.blockedHosts.filter(x => x !== this.instance.host)
|
||||
});
|
||||
},
|
||||
|
||||
setSrc(src) {
|
||||
this.chartSrc = src;
|
||||
},
|
||||
|
@ -20,6 +20,9 @@
|
||||
</section>
|
||||
|
||||
<section class="_card info">
|
||||
<div class="_content">
|
||||
<mk-input v-model="maxNoteTextLength" type="number" :save="() => save()" style="margin:0;"><template #icon><fa :icon="faPencilAlt"/></template>{{ $t('maxNoteTextLength') }}</mk-input>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<mk-switch v-model="enableLocalTimeline" @change="save()">{{ $t('enableLocalTimeline') }}</mk-switch>
|
||||
<mk-switch v-model="enableGlobalTimeline" @change="save()">{{ $t('enableGlobalTimeline') }}</mk-switch>
|
||||
@ -120,30 +123,30 @@
|
||||
<section class="_card">
|
||||
<div class="_title"><fa :icon="faShareAlt"/> {{ $t('integration') }}</div>
|
||||
<div class="_content">
|
||||
<header><fa :icon="faTwitter"/> {{ $t('twitter-integration-config') }}</header>
|
||||
<mk-switch v-model="enableTwitterIntegration">{{ $t('enable-twitter-integration') }}</mk-switch>
|
||||
<header><fa :icon="faTwitter"/> Twitter</header>
|
||||
<mk-switch v-model="enableTwitterIntegration">{{ $t('enable') }}</mk-switch>
|
||||
<template v-if="enableTwitterIntegration">
|
||||
<mk-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('twitter-integration-consumer-key') }}</mk-input>
|
||||
<mk-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('twitter-integration-consumer-secret') }}</mk-input>
|
||||
<mk-info>{{ $t('twitter-integration-info', { url: `${url}/api/tw/cb` }) }}</mk-info>
|
||||
<mk-info>Callback URL: {{ `${url}/api/tw/cb` }}</mk-info>
|
||||
<mk-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Key</mk-input>
|
||||
<mk-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Secret</mk-input>
|
||||
</template>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<header><fa :icon="faGithub"/> {{ $t('github-integration-config') }}</header>
|
||||
<mk-switch v-model="enableGithubIntegration">{{ $t('enable-github-integration') }}</mk-switch>
|
||||
<header><fa :icon="faGithub"/> GitHub</header>
|
||||
<mk-switch v-model="enableGithubIntegration">{{ $t('enable') }}</mk-switch>
|
||||
<template v-if="enableGithubIntegration">
|
||||
<mk-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('github-integration-client-id') }}</mk-input>
|
||||
<mk-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('github-integration-client-secret') }}</mk-input>
|
||||
<mk-info>{{ $t('github-integration-info', { url: `${url}/api/gh/cb` }) }}</mk-info>
|
||||
<mk-info>Callback URL: {{ `${url}/api/gh/cb` }}</mk-info>
|
||||
<mk-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
|
||||
<mk-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
|
||||
</template>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<header><fa :icon="faDiscord"/> {{ $t('discord-integration-config') }}</header>
|
||||
<mk-switch v-model="enableDiscordIntegration">{{ $t('enable-discord-integration') }}</mk-switch>
|
||||
<header><fa :icon="faDiscord"/> Discord</header>
|
||||
<mk-switch v-model="enableDiscordIntegration">{{ $t('enable') }}</mk-switch>
|
||||
<template v-if="enableDiscordIntegration">
|
||||
<mk-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('discord-integration-client-id') }}</mk-input>
|
||||
<mk-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('discord-integration-client-secret') }}</mk-input>
|
||||
<mk-info>{{ $t('discord-integration-info', { url: `${url}/api/dc/cb` }) }}</mk-info>
|
||||
<mk-info>Callback URL: {{ `${url}/api/dc/cb` }}</mk-info>
|
||||
<mk-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
|
||||
<mk-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
|
||||
</template>
|
||||
</div>
|
||||
<div class="_footer">
|
||||
@ -171,7 +174,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faShareAlt, faGhost, faCog, faPlus, faCloud, faInfoCircle, faBan, faSave, faServer, faLink, faThumbtack, faUser, faShieldAlt, faKey, faBolt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faPencilAlt, faShareAlt, faGhost, faCog, faPlus, faCloud, faInfoCircle, faBan, faSave, faServer, faLink, faThumbtack, faUser, faShieldAlt, faKey, faBolt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faTrashAlt, faEnvelope } from '@fortawesome/free-regular-svg-icons';
|
||||
import { faTwitter, faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons';
|
||||
import MkButton from '../../components/ui/button.vue';
|
||||
@ -180,7 +183,7 @@ import MkTextarea from '../../components/ui/textarea.vue';
|
||||
import MkSwitch from '../../components/ui/switch.vue';
|
||||
import MkInfo from '../../components/ui/info.vue';
|
||||
import MkUserSelect from '../../components/user-select.vue';
|
||||
import { version } from '../../config';
|
||||
import { version, url } from '../../config';
|
||||
import i18n from '../../i18n';
|
||||
import getAcct from '../../../misc/acct/render';
|
||||
|
||||
@ -204,7 +207,7 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
version,
|
||||
meta: null,
|
||||
url,
|
||||
stats: null,
|
||||
serverInfo: null,
|
||||
proxyAccount: null,
|
||||
@ -222,6 +225,7 @@ export default Vue.extend({
|
||||
tosUrl: null,
|
||||
bannerUrl: null,
|
||||
iconUrl: null,
|
||||
maxNoteTextLength: 0,
|
||||
enableRegistration: false,
|
||||
enableLocalTimeline: false,
|
||||
enableGlobalTimeline: false,
|
||||
@ -240,52 +244,56 @@ export default Vue.extend({
|
||||
enableDiscordIntegration: false,
|
||||
discordClientId: null,
|
||||
discordClientSecret: null,
|
||||
faTwitter, faDiscord, faGithub, faShareAlt, faTrashAlt, faGhost, faCog, faPlus, faCloud, faInfoCircle, faBan, faSave, faServer, faLink, faEnvelope, faThumbtack, faUser, faShieldAlt, faKey, faBolt
|
||||
faPencilAlt, faTwitter, faDiscord, faGithub, faShareAlt, faTrashAlt, faGhost, faCog, faPlus, faCloud, faInfoCircle, faBan, faSave, faServer, faLink, faEnvelope, faThumbtack, faUser, faShieldAlt, faKey, faBolt
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
this.name = this.meta.name;
|
||||
this.description = this.meta.description;
|
||||
this.tosUrl = this.meta.tosUrl;
|
||||
this.bannerUrl = this.meta.bannerUrl;
|
||||
this.iconUrl = this.meta.iconUrl;
|
||||
this.maintainerName = this.meta.maintainerName;
|
||||
this.maintainerEmail = this.meta.maintainerEmail;
|
||||
this.enableRegistration = !this.meta.disableRegistration;
|
||||
this.enableLocalTimeline = !this.meta.disableLocalTimeline;
|
||||
this.enableGlobalTimeline = !this.meta.disableGlobalTimeline;
|
||||
this.enableRecaptcha = this.meta.enableRecaptcha;
|
||||
this.recaptchaSiteKey = this.meta.recaptchaSiteKey;
|
||||
this.recaptchaSecretKey = this.meta.recaptchaSecretKey;
|
||||
this.proxyAccountId = this.meta.proxyAccountId;
|
||||
this.cacheRemoteFiles = this.meta.cacheRemoteFiles;
|
||||
this.proxyRemoteFiles = this.meta.proxyRemoteFiles;
|
||||
this.localDriveCapacityMb = this.meta.driveCapacityPerLocalUserMb;
|
||||
this.remoteDriveCapacityMb = this.meta.driveCapacityPerRemoteUserMb;
|
||||
this.blockedHosts = this.meta.blockedHosts.join('\n');
|
||||
this.pinnedUsers = this.meta.pinnedUsers.join('\n');
|
||||
this.enableServiceWorker = this.meta.enableServiceWorker;
|
||||
this.swPublicKey = this.meta.swPublickey;
|
||||
this.swPrivateKey = this.meta.swPrivateKey;
|
||||
this.enableTwitterIntegration = this.meta.enableTwitterIntegration;
|
||||
this.twitterConsumerKey = this.meta.twitterConsumerKey;
|
||||
this.twitterConsumerSecret = this.meta.twitterConsumerSecret;
|
||||
this.enableGithubIntegration = this.meta.enableGithubIntegration;
|
||||
this.githubClientId = this.meta.githubClientId;
|
||||
this.githubClientSecret = this.meta.githubClientSecret;
|
||||
this.enableDiscordIntegration = this.meta.enableDiscordIntegration;
|
||||
this.discordClientId = this.meta.discordClientId;
|
||||
this.discordClientSecret = this.meta.discordClientSecret;
|
||||
computed: {
|
||||
meta() {
|
||||
return this.$store.state.instance.meta;
|
||||
},
|
||||
},
|
||||
|
||||
if (this.proxyAccountId) {
|
||||
this.$root.api('users/show', { userId: this.proxyAccountId }).then(proxyAccount => {
|
||||
this.proxyAccount = proxyAccount;
|
||||
});
|
||||
}
|
||||
});
|
||||
created() {
|
||||
this.name = this.meta.name;
|
||||
this.description = this.meta.description;
|
||||
this.tosUrl = this.meta.tosUrl;
|
||||
this.bannerUrl = this.meta.bannerUrl;
|
||||
this.iconUrl = this.meta.iconUrl;
|
||||
this.maintainerName = this.meta.maintainerName;
|
||||
this.maintainerEmail = this.meta.maintainerEmail;
|
||||
this.maxNoteTextLength = this.meta.maxNoteTextLength;
|
||||
this.enableRegistration = !this.meta.disableRegistration;
|
||||
this.enableLocalTimeline = !this.meta.disableLocalTimeline;
|
||||
this.enableGlobalTimeline = !this.meta.disableGlobalTimeline;
|
||||
this.enableRecaptcha = this.meta.enableRecaptcha;
|
||||
this.recaptchaSiteKey = this.meta.recaptchaSiteKey;
|
||||
this.recaptchaSecretKey = this.meta.recaptchaSecretKey;
|
||||
this.proxyAccountId = this.meta.proxyAccountId;
|
||||
this.cacheRemoteFiles = this.meta.cacheRemoteFiles;
|
||||
this.proxyRemoteFiles = this.meta.proxyRemoteFiles;
|
||||
this.localDriveCapacityMb = this.meta.driveCapacityPerLocalUserMb;
|
||||
this.remoteDriveCapacityMb = this.meta.driveCapacityPerRemoteUserMb;
|
||||
this.blockedHosts = this.meta.blockedHosts.join('\n');
|
||||
this.pinnedUsers = this.meta.pinnedUsers.join('\n');
|
||||
this.enableServiceWorker = this.meta.enableServiceWorker;
|
||||
this.swPublicKey = this.meta.swPublickey;
|
||||
this.swPrivateKey = this.meta.swPrivateKey;
|
||||
this.enableTwitterIntegration = this.meta.enableTwitterIntegration;
|
||||
this.twitterConsumerKey = this.meta.twitterConsumerKey;
|
||||
this.twitterConsumerSecret = this.meta.twitterConsumerSecret;
|
||||
this.enableGithubIntegration = this.meta.enableGithubIntegration;
|
||||
this.githubClientId = this.meta.githubClientId;
|
||||
this.githubClientSecret = this.meta.githubClientSecret;
|
||||
this.enableDiscordIntegration = this.meta.enableDiscordIntegration;
|
||||
this.discordClientId = this.meta.discordClientId;
|
||||
this.discordClientSecret = this.meta.discordClientSecret;
|
||||
|
||||
if (this.proxyAccountId) {
|
||||
this.$root.api('users/show', { userId: this.proxyAccountId }).then(proxyAccount => {
|
||||
this.proxyAccount = proxyAccount;
|
||||
});
|
||||
}
|
||||
|
||||
this.$root.api('admin/server-info').then(res => {
|
||||
this.serverInfo = res;
|
||||
@ -346,6 +354,7 @@ export default Vue.extend({
|
||||
iconUrl: this.iconUrl,
|
||||
maintainerName: this.maintainerName,
|
||||
maintainerEmail: this.maintainerEmail,
|
||||
maxNoteTextLength: this.maxNoteTextLength,
|
||||
disableRegistration: !this.enableRegistration,
|
||||
disableLocalTimeline: !this.enableLocalTimeline,
|
||||
disableGlobalTimeline: !this.enableGlobalTimeline,
|
||||
@ -372,6 +381,7 @@ export default Vue.extend({
|
||||
discordClientId: this.discordClientId,
|
||||
discordClientSecret: this.discordClientSecret,
|
||||
}).then(() => {
|
||||
this.$store.dispatch('instance/fetch');
|
||||
if (withDialog) {
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
|
@ -247,6 +247,7 @@ export default Vue.extend({
|
||||
padding: 16px 16px 0 16px;
|
||||
resize: none;
|
||||
font-size: 1em;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
@ -270,12 +271,8 @@ export default Vue.extend({
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
font-size: 1em;
|
||||
color: #aaa;
|
||||
transition: color 0.1s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
color: var(--accent);
|
||||
|
||||
&:active {
|
||||
color: var(--accentDarken);
|
||||
@ -337,7 +334,6 @@ export default Vue.extend({
|
||||
font-size: 1em;
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
color: #aaa;
|
||||
transition: color 0.1s ease;
|
||||
|
||||
&:hover {
|
||||
|
@ -231,7 +231,6 @@ export default Vue.extend({
|
||||
display: block;
|
||||
margin: 2px 0 0 0;
|
||||
font-size: 10px;
|
||||
color: var(--messagingRoomMessageInfo);
|
||||
|
||||
> .read {
|
||||
margin: 0 8px;
|
||||
@ -288,6 +287,7 @@ export default Vue.extend({
|
||||
|
||||
> .balloon {
|
||||
background: $me-balloon-color;
|
||||
box-shadow: 0 6px 16px var(--accentShadow);
|
||||
text-align: left;
|
||||
|
||||
&[data-no-text] {
|
||||
|
@ -8,6 +8,7 @@
|
||||
<portal to="avatar"><mk-avatar class="avatar" :user="user" :disable-preview="true"/></portal>
|
||||
</template>
|
||||
<template v-if="!fetching && group">
|
||||
<portal to="icon"><fa :icon="faUsers"/></portal>
|
||||
<portal to="title">{{ group.name }}</portal>
|
||||
</template>
|
||||
|
||||
@ -35,7 +36,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faArrowCircleDown, faFlag } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faArrowCircleDown, faFlag, faUsers } from '@fortawesome/free-solid-svg-icons';
|
||||
import i18n from '../i18n';
|
||||
import XList from '../components/date-separated-list.vue';
|
||||
import XMessage from './messaging-room.message.vue';
|
||||
@ -63,7 +64,7 @@ export default Vue.extend({
|
||||
connection: null,
|
||||
showIndicator: false,
|
||||
timer: null,
|
||||
faArrowCircleDown, faFlag
|
||||
faArrowCircleDown, faFlag, faUsers
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -150,7 +150,7 @@ export default Vue.extend({
|
||||
showCancelButton: true
|
||||
});
|
||||
if (canceled) return;
|
||||
this.navigateGroup(group);
|
||||
this.$router.push(`/my/messaging/group/${group.id}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -45,6 +45,8 @@ export default Vue.extend({
|
||||
height: 150px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 16px;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="mk-note-page">
|
||||
<portal to="avatar" v-if="note"><mk-avatar class="avatar" :user="note.user" :disable-preview="true"/></portal>
|
||||
<portal to="title" v-if="note">{{ $t('noteOf', { user: note.user.name }) }}</portal>
|
||||
|
||||
<transition name="zoom" mode="out-in">
|
||||
<x-note v-if="note" :note="note" :key="note.id" :detail="true"/>
|
||||
<div v-else-if="error">
|
||||
|
@ -56,15 +56,17 @@ export default Vue.extend({
|
||||
computed: {
|
||||
integrations() {
|
||||
return this.$store.state.i.integrations;
|
||||
}
|
||||
},
|
||||
|
||||
meta() {
|
||||
return this.$store.state.instance.meta;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.enableTwitterIntegration = meta.enableTwitterIntegration;
|
||||
this.enableDiscordIntegration = meta.enableDiscordIntegration;
|
||||
this.enableGithubIntegration = meta.enableGithubIntegration;
|
||||
});
|
||||
this.enableTwitterIntegration = this.meta.enableTwitterIntegration;
|
||||
this.enableDiscordIntegration = this.meta.enableDiscordIntegration;
|
||||
this.enableGithubIntegration = this.meta.enableGithubIntegration;
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
@ -31,6 +31,7 @@ export const router = new VueRouter({
|
||||
{ path: '/my/mentions', component: page('mentions') },
|
||||
{ path: '/my/messaging', name: 'messaging', component: page('messaging') },
|
||||
{ path: '/my/messaging/:user', component: page('messaging-room') },
|
||||
{ path: '/my/messaging/group/:group', component: page('messaging-room') },
|
||||
{ path: '/my/drive', name: 'drive', component: page('drive') },
|
||||
{ path: '/my/drive/folder/:folder', component: page('drive') },
|
||||
{ path: '/my/pages', name: 'pages', component: page('pages') },
|
||||
|
@ -8,7 +8,8 @@ export default (opts) => ({
|
||||
fetching: true,
|
||||
moreFetching: false,
|
||||
inited: false,
|
||||
more: false
|
||||
more: false,
|
||||
backed: false,
|
||||
};
|
||||
},
|
||||
|
||||
@ -78,6 +79,7 @@ export default (opts) => ({
|
||||
async fetchMore() {
|
||||
if (!this.more || this.moreFetching || this.items.length === 0) return;
|
||||
this.moreFetching = true;
|
||||
this.backed = true;
|
||||
let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params;
|
||||
if (params && params.then) params = await params;
|
||||
const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint;
|
||||
|
@ -5,6 +5,7 @@ import * as nestedProperty from 'nested-property';
|
||||
import MiOS from './mios';
|
||||
|
||||
const defaultSettings = {
|
||||
tutorial: 0,
|
||||
keepCw: false,
|
||||
showFullAcct: false,
|
||||
rememberNoteVisibility: false,
|
||||
@ -15,7 +16,15 @@ const defaultSettings = {
|
||||
wallpaper: null,
|
||||
memo: null,
|
||||
reactions: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
|
||||
widgets: []
|
||||
};
|
||||
|
||||
const defaultDeviceUserSettings = {
|
||||
visibility: 'public',
|
||||
localOnly: false,
|
||||
widgets: [],
|
||||
tl: {
|
||||
src: 'home'
|
||||
},
|
||||
};
|
||||
|
||||
const defaultDeviceSettings = {
|
||||
@ -26,16 +35,19 @@ const defaultDeviceSettings = {
|
||||
autoReload: false,
|
||||
accounts: [],
|
||||
recentEmojis: [],
|
||||
visibility: 'public',
|
||||
localOnly: false,
|
||||
themes: [],
|
||||
theme: 'light',
|
||||
animation: true,
|
||||
userData: {},
|
||||
};
|
||||
|
||||
function copy<T>(data: T): T {
|
||||
return JSON.parse(JSON.stringify(data));
|
||||
}
|
||||
|
||||
export default (os: MiOS) => new Vuex.Store({
|
||||
plugins: [createPersistedState({
|
||||
paths: ['i', 'device', 'settings']
|
||||
paths: ['i', 'device', 'deviceUser', 'settings', 'instance']
|
||||
})],
|
||||
|
||||
state: {
|
||||
@ -57,10 +69,11 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
},
|
||||
|
||||
actions: {
|
||||
login(ctx, i) {
|
||||
async login(ctx, i) {
|
||||
ctx.commit('updateI', i);
|
||||
ctx.dispatch('settings/merge', i.clientData);
|
||||
ctx.dispatch('addAcount', { id: i.id, i: localStorage.getItem('i') });
|
||||
ctx.commit('settings/init', i.clientData);
|
||||
ctx.commit('deviceUser/init', ctx.state.device.userData[i.id] || {});
|
||||
await ctx.dispatch('addAcount', { id: i.id, i: localStorage.getItem('i') });
|
||||
},
|
||||
|
||||
addAcount(ctx, info) {
|
||||
@ -73,14 +86,17 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
},
|
||||
|
||||
logout(ctx) {
|
||||
ctx.commit('device/setUserData', { userId: ctx.state.i.id, data: ctx.state.deviceUser });
|
||||
ctx.commit('updateI', null);
|
||||
ctx.commit('settings/init', {});
|
||||
ctx.commit('deviceUser/init', {});
|
||||
localStorage.removeItem('i');
|
||||
},
|
||||
|
||||
switchAccount(ctx, i) {
|
||||
ctx.commit('updateI', i);
|
||||
ctx.commit('settings/init', i.clientData);
|
||||
async switchAccount(ctx, i) {
|
||||
ctx.commit('device/setUserData', { userId: ctx.state.i.id, data: ctx.state.deviceUser });
|
||||
localStorage.setItem('i', i.token);
|
||||
await ctx.dispatch('login', i);
|
||||
},
|
||||
|
||||
mergeMe(ctx, me) {
|
||||
@ -89,12 +105,36 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
}
|
||||
|
||||
if (me.clientData) {
|
||||
ctx.dispatch('settings/merge', me.clientData);
|
||||
ctx.commit('settings/init', me.clientData);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
modules: {
|
||||
instance: {
|
||||
namespaced: true,
|
||||
|
||||
state: {
|
||||
meta: null
|
||||
},
|
||||
|
||||
mutations: {
|
||||
set(state, meta) {
|
||||
state.meta = meta;
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
async fetch(ctx) {
|
||||
const meta = await os.api('meta', {
|
||||
detail: false
|
||||
});
|
||||
|
||||
ctx.commit('set', meta);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
device: {
|
||||
namespaced: true,
|
||||
|
||||
@ -105,6 +145,32 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
state[x.key] = x.value;
|
||||
},
|
||||
|
||||
setUserData(state, x: { userId: string; data: any }) {
|
||||
state.userData[x.userId] = copy(x.data);
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
deviceUser: {
|
||||
namespaced: true,
|
||||
|
||||
state: defaultDeviceUserSettings,
|
||||
|
||||
mutations: {
|
||||
init(state, x) {
|
||||
for (const [key, value] of Object.entries(defaultDeviceUserSettings)) {
|
||||
if (x[key]) {
|
||||
state[key] = x[key];
|
||||
} else {
|
||||
state[key] = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
set(state, x: { key: string; value: any }) {
|
||||
state[x.key] = x.value;
|
||||
},
|
||||
|
||||
setTl(state, x) {
|
||||
state.tl = {
|
||||
src: x.src,
|
||||
@ -119,6 +185,25 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
setLocalOnly(state, localOnly) {
|
||||
state.localOnly = localOnly;
|
||||
},
|
||||
|
||||
setWidgets(state, widgets) {
|
||||
state.widgets = widgets;
|
||||
},
|
||||
|
||||
addWidget(state, widget) {
|
||||
state.widgets.unshift(widget);
|
||||
},
|
||||
|
||||
removeWidget(state, widget) {
|
||||
state.widgets = state.widgets.filter(w => w.id != widget.id);
|
||||
},
|
||||
|
||||
updateWidget(state, x) {
|
||||
const w = state.widgets.find(w => w.id == x.id);
|
||||
if (w) {
|
||||
w.data = x.data;
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@ -144,13 +229,6 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
},
|
||||
|
||||
actions: {
|
||||
merge(ctx, settings) {
|
||||
if (settings == null) return;
|
||||
for (const [key, value] of Object.entries(settings)) {
|
||||
ctx.commit('set', { key, value });
|
||||
}
|
||||
},
|
||||
|
||||
set(ctx, x) {
|
||||
ctx.commit('set', x);
|
||||
|
||||
@ -161,41 +239,6 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setWidgets(ctx, widgets) {
|
||||
ctx.state.widgets = widgets;
|
||||
ctx.dispatch('updateWidgets');
|
||||
},
|
||||
|
||||
addWidget(ctx, widget) {
|
||||
ctx.state.widgets.unshift(widget);
|
||||
ctx.dispatch('updateWidgets');
|
||||
},
|
||||
|
||||
removeWidget(ctx, widget) {
|
||||
ctx.state.widgets = ctx.state.widgets.filter(w => w.id != widget.id);
|
||||
ctx.dispatch('updateWidgets');
|
||||
},
|
||||
|
||||
updateWidget(ctx, x) {
|
||||
const w = ctx.state.widgets.find(w => w.id == x.id);
|
||||
if (w) {
|
||||
w.data = x.data;
|
||||
ctx.dispatch('updateWidgets');
|
||||
}
|
||||
},
|
||||
|
||||
updateWidgets(ctx) {
|
||||
const widgets = ctx.state.widgets;
|
||||
ctx.commit('set', {
|
||||
key: 'widgets',
|
||||
value: widgets
|
||||
});
|
||||
os.api('i/update-client-setting', {
|
||||
name: 'widgets',
|
||||
value: widgets
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +89,8 @@ body {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
animation: ini 0.6s infinite linear;
|
||||
color: var(--accent);
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,6 +198,7 @@ a {
|
||||
@extend ._button;
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 6px 16px var(--accentShadow);
|
||||
|
||||
&:not(:disabled):hover {
|
||||
background: var(--jkhztclx);
|
||||
@ -385,7 +388,7 @@ a {
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0% { opacity: 1; }
|
||||
30% { opacity: 1; }
|
||||
90% { opacity: 0; }
|
||||
0% { opacity: 1; transform: scale(1); }
|
||||
30% { opacity: 1; transform: scale(1); }
|
||||
90% { opacity: 0; transform: scale(0.5); }
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ self.addEventListener('install', ev => {
|
||||
caches.open(cacheName)
|
||||
.then(cache => {
|
||||
return cache.addAll([
|
||||
'/'
|
||||
`/?v=${version}`
|
||||
]);
|
||||
})
|
||||
.then(() => self.skipWaiting())
|
||||
@ -45,7 +45,7 @@ self.addEventListener('fetch', ev => {
|
||||
return response || fetch(ev.request);
|
||||
})
|
||||
.catch(() => {
|
||||
return caches.match('/');
|
||||
return caches.match(`/?v=${version}`);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -10,6 +10,7 @@
|
||||
accent: '#86b300',
|
||||
accentDarken: ':darken<10<@accent',
|
||||
accentLighten: ':lighten<10<@accent',
|
||||
accentShadow: ':alpha<0.3<@accent',
|
||||
focus: ':alpha<0.3<@accent',
|
||||
bg: '#000',
|
||||
fg: '#c7d1d8',
|
||||
|
@ -10,6 +10,7 @@
|
||||
accent: '#86b300',
|
||||
accentDarken: ':darken<10<@accent',
|
||||
accentLighten: ':lighten<10<@accent',
|
||||
accentShadow: ':alpha<0.4<@accent',
|
||||
focus: ':alpha<0.3<@accent',
|
||||
bg: '#fafafa',
|
||||
fg: '#5c6a73',
|
||||
|
@ -51,7 +51,7 @@ export default function <T extends object>(data: {
|
||||
},
|
||||
|
||||
save() {
|
||||
this.$store.dispatch('settings/updateWidget', this.widget);
|
||||
this.$store.commit('deviceUser/updateWidget', this.widget);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -10,6 +10,7 @@
|
||||
<tr><td><kbd class="key">P</kbd>, <kbd class="key">N</kbd></td><td>新規投稿</td><td><b>P</b>ost, <b>N</b>ew, <b>N</b>ote</td></tr>
|
||||
<tr><td><kbd class="key">T</kbd></td><td>タイムラインの最も新しい投稿にフォーカス</td><td><b>T</b>imeline, <b>T</b>op</td></tr>
|
||||
<tr><td><kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">N</kbd></kbd></td><td>通知を表示/隠す</td><td><b>N</b>otifications</td></tr>
|
||||
<tr><td><kbd class="key">S</kbd></td><td>検索</td><td><b>S</b>earch</td></tr>
|
||||
<tr><td><kbd class="key">H</kbd>, <kbd class="key">?</kbd></td><td>ヘルプを表示</td><td><b>H</b>elp</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -13,6 +13,11 @@ export const meta = {
|
||||
default: 10
|
||||
},
|
||||
|
||||
withUnreads: {
|
||||
validator: $.optional.boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
},
|
||||
@ -38,5 +43,5 @@ export default define(meta, async (ps, user) => {
|
||||
}
|
||||
}
|
||||
|
||||
return announcements;
|
||||
return ps.withUnreads ? announcements.filter((a: any) => !a.isRead) : announcements;
|
||||
});
|
||||
|
@ -327,6 +327,10 @@ const override = (source: string, target: string, depth: number = 0) =>
|
||||
router.get('/othello', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games/reversi', 1)));
|
||||
router.get('/reversi', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games')));
|
||||
|
||||
router.get('/flush', async ctx => {
|
||||
await ctx.render('flush');
|
||||
});
|
||||
|
||||
// Render base html for all requests
|
||||
router.get('*', async ctx => {
|
||||
const meta = await fetchMeta();
|
||||
|
@ -55,6 +55,6 @@ html
|
||||
| Please turn on your JavaScript
|
||||
div#ini.
|
||||
<svg viewBox="0 0 50 50">
|
||||
<path fill=#fb4e4e d="M25.251,6.461c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615V6.461z" />
|
||||
<path d="M25.251,6.461c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615V6.461z" />
|
||||
</svg>
|
||||
block content
|
||||
|
20
src/server/web/views/flush.pug
Normal file
20
src/server/web/views/flush.pug
Normal file
@ -0,0 +1,20 @@
|
||||
doctype html
|
||||
|
||||
html
|
||||
script.
|
||||
localStorage.removeItem('locale');
|
||||
|
||||
try {
|
||||
navigator.serviceWorker.controller.postMessage('clear');
|
||||
|
||||
navigator.serviceWorker.getRegistrations().then(registrations => {
|
||||
return Promise.all(registrations.map(registration => registration.unregister()));
|
||||
}).then(() => {
|
||||
location = '/';
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setTimeout(() => {
|
||||
location = '/';
|
||||
}, 10000)
|
||||
}
|
@ -3,6 +3,8 @@ import { Note } from '../../models/entities/note';
|
||||
import { User } from '../../models/entities/user';
|
||||
import { NoteUnreads, Antennas, AntennaNotes, Users } from '../../models';
|
||||
|
||||
// TODO: 状態が変化していない場合は各種イベントを送信しない
|
||||
|
||||
/**
|
||||
* Mark a note as read
|
||||
*/
|
||||
|
@ -102,6 +102,11 @@
|
||||
normalize-path "^2.0.1"
|
||||
through2 "^2.0.3"
|
||||
|
||||
"@juggle/resize-observer@3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.0.2.tgz#f4f326806826c0da5221411374e4c54902c327bb"
|
||||
integrity sha512-9fEfZIxZ1qTahMSX50pigfSJ/1N6X2oABhmgd4Dt2SsPkZi0lTlvoHu5V2yrGZ6m+oALfS4tJrpx9nbVIxwOYQ==
|
||||
|
||||
"@koa/cors@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.0.0.tgz#df021b4df2dadf1e2b04d7c8ddf93ba2d42519cb"
|
||||
|
Reference in New Issue
Block a user