Compare commits

...

67 Commits

Author SHA1 Message Date
b58dd8c704 12.3.0 2020-02-08 18:37:00 +09:00
09c96286f9 🎨 2020-02-08 18:35:42 +09:00
f2d2089c21 New Crowdin translations (#5849)
* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)
2020-02-08 18:03:56 +09:00
79c366d1f2 Update container.vue 2020-02-08 18:02:28 +09:00
c97ce5255f i18n 2020-02-08 17:52:36 +09:00
1fb66254a4 🎨 2020-02-08 17:49:54 +09:00
2d74f0507b 🎨 2020-02-08 17:36:23 +09:00
9c06544c46 wip 2020-02-08 16:51:27 +09:00
641dad586f 🎨 2020-02-08 15:50:04 +09:00
016144b960 wip 2020-02-08 15:47:16 +09:00
4d6c8efe44 wip 2020-02-08 15:17:35 +09:00
860a7d1eeb wip 2020-02-08 15:11:12 +09:00
2389857be8 🎨 2020-02-08 14:31:51 +09:00
18458f418f [wip] フォルダー名の変更と削除機能を実装 (#5874)
* フォルダーの削除機能を実装

* フォルダ名の変更を実装

* ダイアログの削除(v11準拠)とエラーメッセージを表示するように

* ダイアログのテキストのkeypathを変更
2020-02-08 13:09:38 +09:00
e812d054bc Clean up 2020-02-08 13:06:42 +09:00
44d2c0195a 翻訳の抜けを修正 (#5875)
* missing translation

* fix

* fix

* ✌️
2020-02-08 13:06:09 +09:00
42b4949b7f Update app.vue 2020-02-08 12:49:22 +09:00
d915ae0807 Resolve #5879 2020-02-08 12:46:14 +09:00
8eec8ea35f Fix #5844 2020-02-08 12:13:15 +09:00
023e0ba7aa Revert "Better page transition"
This reverts commit d0d3b70c73.
2020-02-08 11:54:39 +09:00
d0d3b70c73 Better page transition 2020-02-08 11:33:32 +09:00
a509045b25 Fix bug 2020-02-08 11:33:25 +09:00
7be6501571 EmojiReaction => EmojiReact (#5877) 2020-02-07 21:37:24 +09:00
bb4c35d481 fix #5854 2020-02-07 20:25:49 +09:00
47ea84957d docs 2020-02-07 19:45:15 +09:00
fc76f7874e Docs 2020-02-07 19:43:37 +09:00
77a778acf1 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-07 19:15:12 +09:00
ff059d1268 wip docs 2020-02-07 19:15:08 +09:00
53bb5012b9 非ログイン時にエラーが発生していたのを修正 (#5872) 2020-02-07 11:39:44 +09:00
09a3a977d7 👀 (#5869) 2020-02-07 09:43:26 +09:00
04db5944d1 Fix #5854 2020-02-07 02:38:02 +09:00
9c97bb431c markdown 2020-02-07 01:20:04 +09:00
38215f2cf9 Fix wrong url on list page (#5865) 2020-02-07 00:19:40 +09:00
01e7a01daf Clean up 2020-02-06 23:27:47 +09:00
c2a8e29ef9 Not found page 2020-02-06 23:20:59 +09:00
15a41e31b0 Fix #5856 2020-02-06 23:12:27 +09:00
294c9840de 12.2.0 2020-02-06 22:27:32 +09:00
568ecd9477 Resolve #5861 2020-02-06 22:25:45 +09:00
169f3ed541 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-06 22:18:26 +09:00
ff7ae427fd Resolve #5860 2020-02-06 22:18:23 +09:00
1597415340 PWAとしてインストールできなかったのを修正 (#5863)
* pwa

* ✌️
2020-02-06 22:11:27 +09:00
47f3261b9f Update CHANGELOG.md 2020-02-06 22:10:55 +09:00
9e68eefbb7 Resolve #5859 2020-02-06 22:10:33 +09:00
630c531d99 Improve messaging form 2020-02-06 19:22:15 +09:00
c7da2a4b5f Resolve #5857 2020-02-06 19:11:14 +09:00
692078f490 🎨 2020-02-06 18:49:57 +09:00
0e29e864c8 Refactor 2020-02-06 18:25:25 +09:00
1b7a601d27 Fix i18n 2020-02-06 17:50:59 +09:00
a96076ee5b 🎨 2020-02-06 17:48:05 +09:00
d580622d1b Update ja-JP.yml 2020-02-06 17:44:41 +09:00
6edccad4dd 12.1.0 2020-02-06 17:29:59 +09:00
8fa47dbcb1 🎨 2020-02-06 17:28:45 +09:00
157f4bbc21 Update CHANGELOG.md 2020-02-06 17:26:09 +09:00
3b0d0df068 i18n 2020-02-06 17:25:04 +09:00
69802a9f00 Resolve #5850 2020-02-06 17:21:28 +09:00
b940da45af Update CHANGELOG.md 2020-02-06 17:11:46 +09:00
bd6de0e204 Fix #5848 (#5853) 2020-02-06 17:11:02 +09:00
958074e347 Update CHANGELOG.md 2020-02-06 17:08:05 +09:00
988ac80087 Correct Like id generation (#5852) 2020-02-06 17:07:37 +09:00
1c7c72181e Fix #5838 2020-02-06 17:05:19 +09:00
6857153367 Fix bug 2020-02-06 17:02:32 +09:00
0a3a0f3beb Update sequential-entrance.vue 2020-02-06 14:55:27 +09:00
e92e83746d Refactor 2020-02-06 14:37:29 +09:00
3b34b3e9ea Fix #5843 2020-02-06 14:29:36 +09:00
9506f53691 Update CHANGELOG.md 2020-02-06 14:25:36 +09:00
92dc6db134 Update CHANGELOG.md 2020-02-06 14:24:43 +09:00
1b88a7bc03 Fix #5842 and refactoring 2020-02-06 14:23:01 +09:00
80 changed files with 1814 additions and 1012 deletions

View File

@ -1,7 +1,46 @@
ChangeLog
=========
12.0.0 indigo (unreleased)
12.3.0 (2020/02/08)
--------------------
### ✨Improvements
* グループ実装
* /share実装
* 指定したURLのページが見つからなかった時のページを実装
* ドキュメント実装
* AP: EmojiReaction => EmojiReact
### 🐛Fixes
* 画面の縦の幅が狭いとメニューが一部隠れる問題を修正
* リストの設定ページが開けなかった問題を修正
* drive-file-thumbnailのicon-subがおかしい問題を修正
* ドライブのフォルダー名の変更と削除ができない問題を修正
12.2.0 (2020/02/06)
--------------------
### ✨Improvements
* UIのアニメーションを無効にできるように
* トークで絵文字ピッカーを表示できるように
* 戻るボタンだけでなく、ホームボタンを押してホームに戻ったときもスクロール位置を復元するように
* タブを見ていないときのタイムライン通知を削除
### 🐛Fixes
* PWAとしてインストールできなかったのを修正
* トークでドライブからファイルを添付出来ない問題を修正
12.1.0 (2020/02/06)
--------------------
### ✨Improvements
* サーバー切断時に自動でリロードできるように
### 🐛Fixes
* もっと読み込むを続けていくと表示が遅くなっていく問題を修正
* Renote メニューが自分の投稿のRenoteでない限り表示されない問題を修正
* MFM jump, spin, title が効かない問題を修正
* AP: Likeで正しいActivity IDを提示するように修正
* AP: Misskey以外からのトークの返信が受け取れないのを修正
12.0.0 indigo (2020/02/06)
--------------------
Misskey v12では、クライアントが設計し直され、全く新しいUIに生まれ変わりました。
レスポンシブになり、ひとつのコードで様々なデバイスに対応できるようにしました。

View File

@ -74,9 +74,17 @@ gulp.task('copy:client', () =>
.pipe(gulp.dest('./built/client/assets/'))
);
gulp.task('copy:docs', () =>
gulp.src([
'./src/docs/**/*',
])
.pipe(gulp.dest('./built/client/assets/docs/'))
);
gulp.task('build:client', gulp.parallel(
'build:client:styles',
'copy:client'
'copy:client',
'copy:docs'
));
gulp.task('build', gulp.parallel(

View File

@ -65,12 +65,13 @@ import: "Import"
export: "Export"
files: "Files"
download: "Download"
driveFileDeleteConfirm: "Are you sure that you want to delete file \"{name}\"? Notes with this file attached will also be deleted."
driveFileDeleteConfirm: "Are you sure you want to delete the file \"{name}\"? Notes with this file attached will also be deleted."
unfollowConfirm: "Are you sure that you want to unfollow {name}?"
exportRequested: "You have requested an export. This may take a while. After the export is complete, the resulting file will be added to the drive."
importRequested: "You requested an import. This may take a while."
lists: "Lists"
noLists: "You don't have any lists"
note: "Notes"
notes: "Notes"
following: "Following"
followers: "Followers"
@ -80,8 +81,6 @@ manageLists: "Manage lists"
error: "Something happened :("
retry: "Retry"
enterListName: "List name"
renameList: "Rename list"
deleteList: "Delete list"
privacy: "Privacy"
makeFollowManuallyApprove: "Follow requests require approval"
defaultNoteVisibility: "Default visibility"
@ -92,8 +91,9 @@ unfollow: "Unfollow"
followRequestPending: "Pending follow request"
enterEmoji: "Enter an emoji"
renote: "Renote"
unrenote: "Unrenote"
quote: "Quote"
pinnedNote: "Pinned note(s)"
pinnedNote: "Pinned note"
you: "You"
clickToShow: "Click to show"
sensitive: "NSFW"
@ -177,7 +177,7 @@ mutedUsers: "Muted users"
blockedUsers: "Blocked users"
noUsers: "There are no users"
editProfile: "Edit profile"
noteDeleteConfirm: "Are you sure that you want to delete this note?"
noteDeleteConfirm: "Are you sure you want to delete this note?"
pinLimitExceeded: "You cannot pin any more notes."
intro: "Installation of Misskey has been finished! Please create an admin user."
done: "Done"
@ -302,8 +302,8 @@ name: "Name"
antennaSource: "Antenna source"
antennaKeywords: "Antenna keywords"
antennaKeywordsDescription: "Separate with spaces for AND condition. Separate with line breaks for OR."
notifyAntenna: "Get notification for new notes"
withFileAntenna: "Notes with attachment only"
notifyAntenna: "Notify newer notes"
withFileAntenna: "Filter only notes with file attached"
serviceworker: "ServiceWorker"
enableServiceworker: "Enable ServiceWorker"
antennaUsersDescription: "List one username per line"
@ -311,7 +311,7 @@ caseSensitive: "Case sensitive"
withReplies: "Include replies"
connectedTo: "Following account(s) are connected"
notesAndReplies: "Notes and replies"
withFiles: "Attach file(s)"
withFiles: "Media"
silence: "Silence"
silenceConfirm: "Are you sure that you want to silence this user?"
unsilenceConfirm: "Are you sure that you want to undo silence of this user?"
@ -346,13 +346,37 @@ resetPassword: "Reste password"
newPasswordIs: "The new password is \"{password}\""
post: "Notes"
posted: "Posted!"
autoReloadWhenDisconnected: "Auto reload when disconnected with server"
autoNoteWatch: "Watch note automatically"
reduceUiAnimation: "Reduce animations of User Interface"
share: "Share"
notFound: "Not found"
notFoundDescription: "There was no page corresponding to the specified URL."
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"
help: "Help"
inputMessageHere: "Enter message here"
close: "Close"
groups: "Groups"
createGroup: "Create a group"
ownedGroups: "Owned Groups"
joinedGroups: "Membership in groups"
invites: "Invite"
groupName: "Group name"
members: "Members"
transfer: "Transfer"
_2fa:
alreadyRegistered: "You have already registered 2-factor authentication device."
registerDevice: "Register a new device"
registerKey: "Register a new Security Key"
step1: "First, install an authentication app (such as {a} or {b}) on your device."
step2: "Then, scan the QR code on the screen."
step3: "Enter the token provided by your app to finish setup."
step4: "From now, any login attempt will ask for your login token."
securityKeyInfo: "You can set up a hardware security key (must support FIDO2) to further secure your login."
securityKeyInfo: "You can setup WebAuthN authentication to further secure the log-in process with not only hardware security key which supports FIDO2, but also fingerprint or PIN authentication on your device."
_permissions:
"read:account": "View your account information"
"write:account": "Edit your account information"
@ -386,7 +410,7 @@ _auth:
_antennaSources:
all: "All notes"
homeTimeline: "Notes from following users"
users: "Notes from specific user(s)"
users: "Notes from specific users"
userList: "Notes from specific list"
_weekday:
sunday: "Sunday"
@ -403,6 +427,7 @@ _widgets:
calendar: "Calendar"
trends: "Trending"
clock: "Clock"
rss: "RSS reader"
_cw:
hide: "Hide"
show: "Load more"
@ -441,8 +466,8 @@ _visibility:
specified: "Direct"
specifiedDescription: "Post to specified users only"
_postForm:
replyPlaceholder: "Reply to this note"
quotePlaceholder: "Quote this post..."
replyPlaceholder: "Reply to this note..."
quotePlaceholder: "Quote this note..."
_placeholders:
a: "What are you up to?"
b: "What's happening around you?"
@ -471,7 +496,7 @@ _charts:
usersTotal: "Total # of users"
activeUsers: "Active users"
notesIncDec: "Difference in # of notes"
localNotesIncDec: "Total # of local notes"
localNotesIncDec: "Difference in # of local notes"
remoteNotesIncDec: "Difference in # of remote notes"
notesTotal: "Total # of notes"
filesIncDec: "Difference in # of files"

View File

@ -71,6 +71,7 @@ exportRequested: "Se ha solicitado la exportación. Puede tomar un tiempo. Cuand
importRequested: "Se ha solicitado la importación. Puede tomar un tiempo."
lists: "Listas"
noLists: "No tiene listas"
note: "Notas"
notes: "Notas"
following: "Sigue"
followers: "Seguidores"
@ -80,8 +81,6 @@ manageLists: "Administrar listas"
error: "Ocurrió un problema"
retry: "Reintentar"
enterListName: "Ingrese nombre de lista"
renameList: "Renombrar lista"
deleteList: "Borrar lista"
privacy: "Privacidad"
makeFollowManuallyApprove: "Aprobar manualmente las solicitudes de seguimiento"
defaultNoteVisibility: "Visibilidad por defecto"
@ -92,6 +91,7 @@ unfollow: "Dejar de seguir"
followRequestPending: "Solicitudes de seguimiento pendientes"
enterEmoji: "Ingresar emojis"
renote: "Renotar"
unrenote: "Quitar renota"
quote: "Citar"
pinnedNote: "Nota fijada"
you: "Tú"
@ -302,8 +302,8 @@ name: "Nombre"
antennaSource: "Origen de la antena"
antennaKeywords: "Palabras clave de la antena"
antennaKeywordsDescription: "Separar con espacios es una declaración AND, separar con una linea nueva es una declaración OR"
notifyAntenna: "Notificar notas nuevas"
withFileAntenna: "Solo notas con adjuntos"
notifyAntenna: "Notificar nueva nota"
withFileAntenna: "Sólo notas con archivos adjuntados"
serviceworker: "ServiceWorker"
enableServiceworker: "Activar ServiceWorker"
antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva"
@ -346,13 +346,20 @@ resetPassword: "Resetear contraseña"
newPasswordIs: "La nueva contraseña es \"{password}\""
post: "Nota"
posted: "Posteado"
autoReloadWhenDisconnected: "Recargar automáticamente cuando el servidor está desconectado"
autoNoteWatch: "Ver nota automáticamente"
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"
help: "Ayuda"
invites: "Invitar"
_2fa:
registerDevice: "Registrar dispositivo"
step1: "Primero, instale en su dispositivo la aplicación de autenticación {a} o {b} u otra."
step2: "Luego, escanee con la aplicación el código QR mostrado en pantalla."
step3: "Para terminar, ingrese el token mostrado en la aplicación."
step4: "Ahora cuando inicie sesión, ingrese el mismo token"
securityKeyInfo: "Se puede configurar para que se inicie sesión usando una clave de seguridad de hardware que soporte FIDO2."
_permissions:
"read:account": "Ver información de la cuenta"
"write:account": "Editar información de la cuenta"
@ -368,7 +375,7 @@ _permissions:
"write:messaging": "Administrar coversación"
"read:mutes": "Ver usuarios silenciados"
"write:mutes": "Administrar usuarios silenciados"
"write:notes": "Crear o borrar notas"
"write:notes": "Crear/borrar notas"
"read:notifications": "Ver notificaciones"
"write:notifications": "Administrar notificaciones"
"read:reactions": "Ver reacciones"
@ -386,8 +393,8 @@ _auth:
_antennaSources:
all: "Todas las notas"
homeTimeline: "Notas de los usuarios que sigues"
users: "Solo notas de determinados usuarios"
userList: "Solo notas de usuarios de una lista"
users: "Notas de un usuario o varios"
userList: "Notas de los usuarios de una lista"
_weekday:
sunday: "Domingo"
monday: "Lunes"
@ -403,6 +410,7 @@ _widgets:
calendar: "Calendario"
trends: "Tendencias"
clock: "Reloj"
rss: "Lector RSS"
_cw:
hide: "Ocultar"
show: "Ver más"
@ -470,9 +478,9 @@ _charts:
usersIncDec: "Variación de usuarios"
usersTotal: "Total de usuarios"
activeUsers: "Cantidad de usuarios activos"
notesIncDec: "Variación de cantidad de notas"
localNotesIncDec: "Variación de cantidad de notas locales"
remoteNotesIncDec: "Variación de cantidad de notas remotas"
notesIncDec: "Variación de la cantidad de notas"
localNotesIncDec: "Variación de la cantidad de notas locales"
remoteNotesIncDec: "Variación de la cantidad de notas remotas"
notesTotal: "Total de notas"
filesIncDec: "Variación de cantidad de archivos"
filesTotal: "Total de archivos"
@ -482,8 +490,8 @@ _instanceCharts:
requests: "Pedidos"
users: "Variación de usuarios"
usersTotal: "Total de usuarios"
notes: "Variación de cantidad de notas"
notesTotal: "Total de notas"
notes: "Variación de la cantidad de notas"
notesTotal: "Estimación de notas"
ff: "Variación de cantidad de seguidos/seguidores"
ffTotal: "Total de seguidos/seguidores"
cacheSize: "Variación del tamaño de la caché"

View File

@ -28,7 +28,7 @@ gotIt: "わかった"
cancel: "キャンセル"
enterUsername: "ユーザー名を入力"
renotedBy: "{user}がRenote"
noNotes: "投稿はありません"
noNotes: "ノートはありません"
noNotifications: "通知はありません"
instance: "インスタンス"
settings: "設定"
@ -66,13 +66,14 @@ import: "インポート"
export: "エクスポート"
files: "ファイル"
download: "ダウンロード"
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを添付した投稿も消えます。"
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを添付したノートも消えます。"
unfollowConfirm: "{name}のフォローを解除しますか?"
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。"
lists: "リスト"
noLists: "リストはありません"
notes: "投稿"
note: "ノート"
notes: "ノート"
following: "フォロー"
followers: "フォロワー"
followsYou: "フォローされています"
@ -81,8 +82,6 @@ manageLists: "リストの管理"
error: "問題が発生しました"
retry: "再試行"
enterListName: "リスト名を入力"
renameList: "リスト名を変更"
deleteList: "リストを削除"
privacy: "プライバシー"
makeFollowManuallyApprove: "フォローを承認制にする"
defaultNoteVisibility: "デフォルトの公開範囲"
@ -95,7 +94,7 @@ enterEmoji: "絵文字を入力"
renote: "Renote"
unrenote: "Renote解除"
quote: "引用"
pinnedNote: "ピン留めされた投稿"
pinnedNote: "ピン留めされたノート"
you: "あなた"
clickToShow: "クリックして表示"
sensitive: "閲覧注意"
@ -179,7 +178,7 @@ mutedUsers: "ミュートしたユーザー"
blockedUsers: "ブロックしたユーザー"
noUsers: "ユーザーはいません"
editProfile: "プロフィールを編集"
noteDeleteConfirm: "この投稿を削除しますか?"
noteDeleteConfirm: "このノートを削除しますか?"
pinLimitExceeded: "これ以上ピン留めできません"
intro: "Misskeyのインストールが完了しました管理者アカウントを作成しましょう。"
done: "完了"
@ -304,8 +303,8 @@ name: "名前"
antennaSource: "受信ソース"
antennaKeywords: "受信キーワード"
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
notifyAntenna: "新しい投稿を通知する"
withFileAntenna: "ファイルが添付された投稿のみ"
notifyAntenna: "新しいノートを通知する"
withFileAntenna: "ファイルが添付されたノートのみ"
serviceworker: "ServiceWorker"
enableServiceworker: "ServiceWorkerを有効にする"
antennaUsersDescription: "ユーザー名を改行で区切って指定します"
@ -348,14 +347,38 @@ resetPassword: "パスワードをリセット"
newPasswordIs: "新しいパスワードは「{password}」です"
post: "投稿"
posted: "投稿しました"
autoReloadWhenDisconnected: "サーバー切断時に自動リロード"
autoNoteWatch: "ノートの自動ウォッチ"
reduceUiAnimation: "UIのアニメーションを減らす"
share: "共有"
notFound: "見つかりません"
notFoundDescription: "指定されたURLに該当するページはありませんでした。"
uploadFolder: "既定アップロード先"
cacheClear: "キャッシュを削除"
markAsReadAllNotifications: "すべての通知を既読にする"
markAsReadAllUnreadNotes: "すべての投稿を既読にする"
markAsReadAllTalkMessages: "すべてのトークを既読にする"
help: "ヘルプ"
inputMessageHere: "ここにメッセージを入力"
close: "閉じる"
groups: "グループ"
createGroup: "グループを作成"
ownedGroups: "所有グループ"
joinedGroups: "参加しているグループ"
invites: "招待"
groupName: "グループ名"
members: "メンバー"
transfer: "譲渡"
_2fa:
alreadyRegistered: "既に設定は完了しています。"
registerDevice: "デバイスを登録"
registerKey: "キーを登録"
step1: "まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。"
step2: "次に、表示されているQRコードをアプリでスキャンします。"
step3: "アプリに表示されているトークンを入力して完了です。"
step4: "これからログインするときも、同じようにトークンを入力します。"
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーを使用してログインするように設定できます。"
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーもしくは端末の指紋認証やPINを使用してログインするように設定できます。"
_permissions:
"read:account": "アカウントの情報を見る"
@ -372,7 +395,7 @@ _permissions:
"write:messaging": "トークを操作する"
"read:mutes": "ミュートを見る"
"write:mutes": "ミュートを操作する"
"write:notes": "投稿を作成・削除する"
"write:notes": "ノートを作成・削除する"
"read:notifications": "通知を見る"
"write:notifications": "通知を操作する"
"read:reactions": "リアクションを見る"
@ -390,10 +413,10 @@ _auth:
permissionAsk: "このアプリは次の権限を要求しています"
_antennaSources:
all: "全ての投稿"
homeTimeline: "フォローしているユーザーの投稿"
users: "指定した一人または複数のユーザーの投稿"
userList: "指定したリストのユーザーの投稿"
all: "全てのノート"
homeTimeline: "フォローしているユーザーのノート"
users: "指定した一人または複数のユーザーのノート"
userList: "指定したリストのユーザーのノート"
_weekday:
sunday: "日曜日"
@ -411,6 +434,7 @@ _widgets:
calendar: "カレンダー"
trends: "トレンド"
clock: "時計"
rss: "RSSリーダー"
_cw:
hide: "隠す"
@ -453,8 +477,8 @@ _visibility:
specifiedDescription: "指定したユーザーのみに公開"
_postForm:
replyPlaceholder: "この投稿に返信..."
quotePlaceholder: "この投稿を引用..."
replyPlaceholder: "このノートに返信..."
quotePlaceholder: "このノートを引用..."
_placeholders:
a: "いまどうしてる?"
b: "何かありましたか?"
@ -473,7 +497,7 @@ _profile:
metadataContent: "内容"
_exportOrImport:
allNotes: "全ての投稿"
allNotes: "全てのノート"
followingList: "フォロー"
muteList: "ミュート"
blockingList: "ブロック"
@ -485,10 +509,10 @@ _charts:
usersIncDec: "ユーザーの増減"
usersTotal: "ユーザーの合計"
activeUsers: "アクティブユーザー数"
notesIncDec: "投稿の増減"
localNotesIncDec: "ローカルの投稿の増減"
remoteNotesIncDec: "リモートの投稿の増減"
notesTotal: "投稿の合計"
notesIncDec: "ノートの増減"
localNotesIncDec: "ローカルのノートの増減"
remoteNotesIncDec: "リモートのノートの増減"
notesTotal: "ノートの合計"
filesIncDec: "ファイルの増減"
filesTotal: "ファイルの合計"
storageUsageIncDec: "ストレージ使用量の増減"
@ -498,8 +522,8 @@ _instanceCharts:
requests: "リクエスト"
users: "ユーザーの増減"
usersTotal: "ユーザーの積算"
notes: "投稿の増減"
notesTotal: "投稿の積算"
notes: "ノートの増減"
notesTotal: "ノートの積算"
ff: "フォロー/フォロワーの増減"
ffTotal: "フォロー/フォロワーの積算"
cacheSize: "キャッシュサイズの増減"

View File

@ -26,8 +26,8 @@ ok: "OK"
gotIt: "알겠어요"
cancel: "취소"
enterUsername: "유저명 입력"
renotedBy: "{user}님이 리노트"
noNotes: "게시물이 없습니다"
renotedBy: "{user}님이 Renote"
noNotes: "노트가 없습니다"
noNotifications: "표시할 알림이 없습니다"
instance: "인스턴스"
settings: "설정"
@ -71,6 +71,7 @@ exportRequested: "내보내기를 요청하였습니다. 이 작업은 시간이
importRequested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다."
lists: "리스트"
noLists: "리스트가 없습니다"
note: "노트"
notes: "노트"
following: "팔로잉"
followers: "팔로워"
@ -80,8 +81,6 @@ manageLists: "리스트 관리"
error: "오류가 발생했습니다"
retry: "다시 시도"
enterListName: "리스트 이름을 입력"
renameList: "리스트 이름 바꾸기"
deleteList: "리스트 삭제"
privacy: "프라이버시"
makeFollowManuallyApprove: "팔로우를 수동으로 승인"
defaultNoteVisibility: "기본 공개 범위"
@ -91,9 +90,10 @@ followRequests: "팔로우 요청"
unfollow: "팔로우 해제"
followRequestPending: "팔로우 허가 대기중"
enterEmoji: "이모지 입력"
renote: "리노트"
renote: "Renote"
unrenote: "Renote 취소"
quote: "인용"
pinnedNote: "고정 노트"
pinnedNote: "고정해놓은 노트"
you: "당신"
clickToShow: "클릭하여 보기"
sensitive: "열람주의"
@ -257,8 +257,8 @@ banner: "배너"
nsfw: "열람주의"
disconnectedFromServer: "서버와의 연결이 끊어졌습니다"
reloadConfirm: "새로고침 하시겠습니까?"
watch: "알림 받기"
unwatch: "알림 받기 해제"
watch: "지켜보기"
unwatch: "지켜보기 해제"
accept: "허가"
reject: "거부"
instanceName: "인스턴스 이름"
@ -280,7 +280,7 @@ enableLocalTimeline: "로컬 타임라인 활성화"
enableGlobalTimeline: "글로벌 타임라인 활성화"
disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리자 및 모더레이터는 계속 사용할 수 있습니다."
registration: "등록"
enableRegistration: "신규 사용자 등록을 활성화"
enableRegistration: "신규 회원가입을 활성화"
invite: "초대"
proxyRemoteFiles: "원격 파일 프록시"
proxyRemoteFilesDescription: "이 설정을 활성화할 경우, 저장되지 않았거나 저장용량 초과로 삭제된 원격 파일을 로컬에서 프록시하여 썸네일을 생성하게 됩니다. 서버의 스토리지에는 영향을 주지 않습니다."
@ -302,8 +302,8 @@ name: "이름"
antennaSource: "받을 소스"
antennaKeywords: "받을 키워드"
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다"
notifyAntenna: "새로운 글이 게시될 때 알림 받기"
withFileAntenna: "파일이 첨부된 게시물만"
notifyAntenna: "새로운 노트를 알림"
withFileAntenna: "파일이 첨부된 노트만"
serviceworker: "ServiceWorker"
enableServiceworker: "ServiceWorker 사용"
antennaUsersDescription: "유저명을 한 줄에 한 명씩 적습니다"
@ -311,7 +311,7 @@ caseSensitive: "대소문자를 구분"
withReplies: "답글 포함"
connectedTo: "다음 계정에 연결되어 있습니다"
notesAndReplies: "글과 답글"
withFiles: "파일 첨부"
withFiles: "미디어"
silence: "사일런스"
silenceConfirm: "이 계정을 사일런스로 설정하시겠습니까?"
unsilenceConfirm: "이 계정의 사일런스를 해제하시겠습니까?"
@ -346,13 +346,37 @@ resetPassword: "비밀번호 재설정"
newPasswordIs: "새로운 비밀번호는 \"{password}\" 입니다"
post: "작성"
posted: "게시하였습니다"
autoReloadWhenDisconnected: "서버와의 연결이 끊기면 자동 새로고침"
autoNoteWatch: "노트를 자동으로 지켜보기"
reduceUiAnimation: "UI의 애니메이션을 줄이기"
share: "공유"
notFound: "찾을 수 없습니다"
notFoundDescription: "지정한 URL에 해당하는 페이지가 존재하지 않습니다."
uploadFolder: "기본 업로드 위치"
cacheClear: "캐시 지우기"
markAsReadAllNotifications: "모든 알림을 읽은 상태로 표시"
markAsReadAllUnreadNotes: "모든 글을 읽은 상태로 표시"
markAsReadAllTalkMessages: "모든 대화를 읽은 상태로 표시"
help: "도움말"
inputMessageHere: "여기에 메시지를 입력하세요"
close: "닫기"
groups: "그룹"
createGroup: "그룹 만들기"
ownedGroups: "소유 그룹"
joinedGroups: "참여중인 그룹"
invites: "초대"
groupName: "그룹명"
members: "멤버"
transfer: "양도"
_2fa:
alreadyRegistered: "이미 설정이 완료되었습니다."
registerDevice: "디바이스 등록"
registerKey: "키를 등록"
step1: "먼저, {a}나 {b}등의 인증 앱을 사용 중인 디바이스에 설치합니다."
step2: "그 후, 표시되어 있는 QR코드를 앱으로 스캔합니다."
step3: "앱에 표시된 토큰을 입력하시면 완료됩니다."
step4: "다음 로그인부터는 토큰을 입력해야 합니다."
securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키를 사용하여 로그인하도록 설정할 수 있습니다."
securityKeyInfo: "FIDO2를 지원하는 하드웨어 시큐리티 키 혹은 휴대전화의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다."
_permissions:
"read:account": "계정 정보 보기"
"write:account": "계정 정보 변경"
@ -368,7 +392,7 @@ _permissions:
"write:messaging": "대화 수정"
"read:mutes": "뮤트 보기"
"write:mutes": "뮤트 수정"
"write:notes": "노트 작성 삭제"
"write:notes": "노트 작성하거나 삭제"
"read:notifications": "알림 보기"
"write:notifications": "알림 수정"
"read:reactions": "리액션 보기"
@ -384,10 +408,10 @@ _auth:
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
permissionAsk: "이 앱은 다음의 권한을 요청합니다"
_antennaSources:
all: "모든 게시물"
homeTimeline: "팔로우중인 유저의 게시물"
users: "지정한 유저(들)의 게시물"
userList: "지정한 리스트에 속한 유저의 게시물"
all: "모든 노트"
homeTimeline: "팔로우중인 유저의 노트"
users: "지정한 한 명 혹은 여러 명의 유저의 노트"
userList: "지정한 리스트에 속한 유저의 노트"
_weekday:
sunday: "일요일"
monday: "월요일"
@ -403,6 +427,7 @@ _widgets:
calendar: "달력"
trends: "트렌드"
clock: "시계"
rss: "RSS 리더"
_cw:
hide: "숨기기"
show: "더 보기"
@ -441,8 +466,8 @@ _visibility:
specified: "다이렉트"
specifiedDescription: "지정한 유저에게만 공개"
_postForm:
replyPlaceholder: "이 에 답글..."
quotePlaceholder: "이 글을 인용..."
replyPlaceholder: "이 노트에 답글..."
quotePlaceholder: "이 노트를 인용..."
_placeholders:
a: "지금 무엇을 하고 있나요?"
b: "무슨 일이 일어나고 있나요?"
@ -459,7 +484,7 @@ _profile:
metadataLabel: "라벨"
metadataContent: "내용"
_exportOrImport:
allNotes: "모든 게시물"
allNotes: "모든 노트"
followingList: "팔로잉"
muteList: "뮤트"
blockingList: "차단"
@ -467,12 +492,12 @@ _exportOrImport:
_charts:
federationInstancesIncDec: "연합 인스턴스 수 증감"
federationInstancesTotal: "총 연합 인스턴스 수"
usersIncDec: "유저 증감"
usersTotal: "유저 합계"
usersIncDec: "유저 증감"
usersTotal: "유저 합계"
activeUsers: "활성 유저 수"
notesIncDec: "노트 수 증감"
localNotesIncDec: "로컬 노트 수 증감"
remoteNotesIncDec: "외부 노트 수 증감"
remoteNotesIncDec: "리모트 노트 수 증감"
notesTotal: "총 노트 수"
filesIncDec: "파일 수 증감"
filesTotal: "총 파일 수"
@ -480,10 +505,10 @@ _charts:
storageUsageTotal: "총 스토리지 사용량"
_instanceCharts:
requests: "요청"
users: "유저 증감"
usersTotal: "누적 사용자 수"
users: "유저 증감"
usersTotal: "누적 유저 수"
notes: "노트 수 증감"
notesTotal: "누적 노트 수"
notesTotal: " 노트 수"
ff: "팔로잉/팔로워 증감"
ffTotal: "팔로잉/팔로워 누적"
cacheSize: "캐시 용량 증감"

View File

@ -1 +1,265 @@
---
_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是一个开源的分散型SNS服务。\n通过「Note」来分享现在发生的事情吧📡\n「反应」工能也可以让你快速的对大家的「Note」来表达感情👍\n一起来探索新的世界吧🚀"
monthAndDay: "{month}月 {day}日"
search: "搜索"
notifications: "通知"
username: "用户名"
password: "密码"
fetchingAsApObject: "联合查询中"
ok: "OK"
gotIt: "我明白了"
cancel: "取消"
enterUsername: "输入用户名"
renotedBy: "由 {user} 转推"
noNotifications: "无通知"
instance: "实例"
settings: "设置"
profile: "个人资料"
timeline: "时间线"
noAccountDescription: "这个人很懒,没有写自我介绍"
login: "登录"
loggingIn: "正在登录..."
logout: "登出"
signup: "新用户注册"
uploading: "正在上传"
save: "保存"
users: "用户"
addUser: "添加用户"
favorite: "收藏"
favorites: "收藏"
unfavorite: "取消收藏"
pin: "置顶"
unpin: "取消置顶"
copyContent: "复制内容"
copyLink: "复制链接"
delete: "删除"
addToList: "添加至列表"
sendMessage: "发送"
copyUsername: "复制用户名"
reply: "回复"
loadMore: "查看更多"
importAndExport: "导入和导出"
import: "导入"
export: "导出"
files: "文件"
download: "下载"
lists: "列表"
noLists: "列表为空"
following: "关注中"
followers: "关注者"
followsYou: "关注了你"
error: "有点小问题"
retry: "重试"
follow: "关注"
followRequest: "关注申请"
followRequests: "关注申请"
unfollow: "取消关注"
followRequestPending: "发送关注申请"
enterEmoji: "输入Emoji"
renote: "转发"
unrenote: "取消转发"
quote: "引用"
you: "您"
clickToShow: "点击以显示"
sensitive: "阅读注意"
add: "添加"
reaction: "反应"
reactionSettingDescription: "快速选择回应中的自定义表情符号,以换行符分隔。"
renameFile: "重命名文件"
attachCancel: "删除附件"
markAsSensitive: "阅读注意"
enterFileName: "请输入文件名"
mute: "屏蔽"
unmute: "解除屏蔽"
block: "屏蔽"
unblock: "取消屏蔽"
suspend: "冻结"
unsuspend: "解除冻结"
blockConfirm: "确定要屏蔽吗?"
unblockConfirm: "确定要解除屏蔽吗?"
suspendConfirm: "要冻结吗?"
unsuspendConfirm: "要解除冻结吗?"
selectList: "选择列表"
customEmojis: "自定义Emoji"
emojiName: "Emoji 名称"
emojiUrl: "emoji 地址"
addEmoji: "添加Emoji"
cacheRemoteFiles: "远程文件缓存"
addAcount: "添加账户"
loginFailed: "登录失败"
general: "常规设置"
wallpaper: "壁纸"
removeWallpaper: "移除壁纸"
searchWith: "搜索:{q}"
federation: "联合"
instances: "实例"
latestRequestSentAt: "上次发送的请求"
latestRequestReceivedAt: "上次收到的请求"
storageUsage: "已用存储"
perHour: "每小时"
perDay: "每天"
operations: "操作"
monitor: "监视器"
jobQueue: "作业队列"
cpuAndMemory: "CPU使用量"
network: "网络"
disk: "存储"
statistics: "统计"
clearQueue: "清除队列"
mutedUsers: "禁言用户"
blockedUsers: "已屏蔽用户"
noUsers: "无用户"
editProfile: "编辑资料"
done: "完成"
processing: "处理中"
preview: "预览"
noCustomEmojis: "无自定义Emoji"
notResponding: "没有响应"
instanceFollowing: "关注实例"
instanceFollowers: "关注实例"
changePassword: "修改密码"
security: "安全性"
retypedNotMatch: "两次输入不一致!"
currentPassword: "现在的密码"
newPassword: "新密码"
newPasswordRetype: "重新输入密码:"
attachFile: "插入附件"
more: "更多!"
featured: "高亮"
lookup: "查询"
remove: "删除"
removed: "已删除"
removeAreYouSure: "要删掉「{x}」吗?"
saved: "已保存"
messaging: "聊天"
upload: "上传"
start: "开始"
images: "图片"
birthday: "生日"
yearsOld: "{age}岁"
registeredDate: "注册于"
location: "位置"
theme: "主题"
lightThemes: "亮色主题"
nsfw: "阅读注意"
accept: "允许"
reject: "拒绝"
instanceName: "实例名称"
instanceDescription: "实例介绍"
maintainerName: "管理员名称"
maintainerEmail: "管理员电子邮箱"
tosUrl: "服务条款URL"
thisYear: "今年"
thisMonth: "本月"
today: "今天"
dayX: "{day}日"
monthX: "{month}月"
yearX: "{year}年"
pages: "页面"
integration: "连携"
connectSerice: "已连接"
disconnectSerice: "断开连接"
enableLocalTimeline: "启用本地时间线功能"
enableGlobalTimeline: "启用全局时间线"
registration: "注册"
enableRegistration: "允许新用户注册"
invite: "邀请"
proxyRemoteFiles: "代理远程文件"
inMb: "以兆字节(Mbps)为单位"
iconUrl: "图标URL"
bannerUrl: "Banner URL"
basicInfo: "基本信息"
recaptcha: "reCAPTCHA"
enableRecaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)"
recaptchaSecretKey: "reCAPTCHA 密钥"
name: "名称"
userList: "列表"
lastUsed: "最后使用:"
unregister: "删除账户"
post: "投稿"
posted: "已投稿"
share: "分享"
help: "帮助"
invites: "邀请"
_permissions:
"read:blocks": "查看黑名单"
"write:blocks": "编辑黑名单"
"read:drive": "查看网盘"
"write:drive": "管理网盘文件"
"read:favorites": "查看收藏夹"
"write:favorites": "编辑收藏夹"
"read:following": "查看关注信息"
"write:following": "关注/取消关注"
"read:messaging": "查看对话"
"write:messaging": "对话操作"
"read:mutes": "查看屏蔽列表"
"write:mutes": "编辑屏蔽列表"
"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:
permissionAsk: "这个应用程序需要以下权限"
_widgets:
notifications: "通知"
timeline: "时间线"
_cw:
show: "查看更多"
_poll:
deadlineTime: "小时"
vote: "投票"
_visibility:
followers: "关注者"
_profile:
name: "名称"
username: "用户名"
_exportOrImport:
followingList: "关注中"
muteList: "屏蔽"
blockingList: "屏蔽"
userLists: "列表"
_pages:
viewPage: "查看页面"
blocks:
image: "图片"
script:
categories:
list: "列表"
blocks:
_join:
arg1: "列表"
_randomPick:
arg1: "列表"
_dailyRandomPick:
arg1: "列表"
_seedRandomPick:
arg2: "列表"
_pick:
arg1: "列表"
_listLen:
arg1: "列表"
types:
array: "列表"

View File

@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.0.0",
"version": "12.3.0",
"codename": "indigo",
"repository": {
"type": "git",
@ -44,6 +44,7 @@
"@types/cbor": "5.0.0",
"@types/dateformat": "3.0.1",
"@types/double-ended-queue": "2.1.1",
"@types/glob": "7.1.1",
"@types/gulp": "4.0.6",
"@types/gulp-mocha": "0.0.32",
"@types/gulp-rename": "0.0.33",
@ -65,6 +66,7 @@
"@types/koa__multer": "2.0.1",
"@types/koa__router": "8.0.2",
"@types/lolex": "5.1.0",
"@types/markdown-it": "0.0.9",
"@types/mocha": "7.0.1",
"@types/node": "13.7.0",
"@types/nodemailer": "6.4.0",
@ -127,6 +129,7 @@
"fibers": "4.0.2",
"file-type": "13.1.2",
"fluent-ffmpeg": "2.1.2",
"glob": "7.1.6",
"gulp": "4.0.2",
"gulp-clean-css": "4.2.0",
"gulp-dart-sass": "0.9.1",
@ -164,6 +167,7 @@
"loader-utils": "1.2.3",
"lolex": "5.1.2",
"lookup-dns-cache": "2.1.0",
"markdown-it": "10.0.0",
"mocha": "7.0.1",
"moji": "0.5.1",
"ms": "2.1.2",

View File

@ -2,10 +2,10 @@
<div class="mk-app" v-hotkey.global="keymap">
<header class="header">
<div class="title" ref="title">
<transition name="header" mode="out-in" appear>
<transition :name="$store.state.device.animation ? 'header' : ''" mode="out-in" appear>
<button class="_button back" v-if="canBack" @click="back()"><fa :icon="faChevronLeft"/></button>
</transition>
<transition name="header" mode="out-in" appear>
<transition :name="$store.state.device.animation ? 'header' : ''" mode="out-in" appear>
<div class="body" :key="pageKey">
<div class="default">
<portal-target name="avatar" slim/>
@ -24,59 +24,72 @@
</div>
</header>
<nav class="nav" ref="nav">
<div>
<button class="item _button account" @click="openAccountMenu" v-if="$store.getters.isSignedIn">
<mk-avatar :user="$store.state.i" class="avatar"/><mk-acct class="text" :user="$store.state.i"/>
</button>
<router-link class="item" active-class="active" to="/" exact v-if="$store.getters.isSignedIn">
<fa :icon="faHome" fixed-width/><span class="text">{{ $t('timeline') }}</span>
</router-link>
<router-link class="item" active-class="active" to="/" exact v-else>
<fa :icon="faHome" fixed-width/><span class="text">{{ $t('home') }}</span>
</router-link>
<router-link class="item" active-class="active" to="/featured">
<fa :icon="faFireAlt" fixed-width/><span class="text">{{ $t('featured') }}</span>
</router-link>
<router-link class="item" active-class="active" to="/explore">
<fa :icon="faHashtag" fixed-width/><span class="text">{{ $t('explore') }}</span>
</router-link>
<button class="item _button" @click="notificationsOpen = !notificationsOpen" ref="notificationButton" v-if="$store.getters.isSignedIn">
<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
</button>
<router-link class="item" active-class="active" to="/my/messaging" v-if="$store.getters.isSignedIn">
<fa :icon="faComments" fixed-width/><span class="text">{{ $t('messaging') }}</span>
<i v-if="$store.state.i.hasUnreadMessagingMessage"><fa :icon="faCircle"/></i>
</router-link>
<router-link class="item" active-class="active" to="/my/follow-requests" v-if="$store.getters.isSignedIn && $store.state.i.isLocked">
<fa :icon="faUserClock" fixed-width/><span class="text">{{ $t('followRequests') }}</span>
<i v-if="$store.state.i.pendingReceivedFollowRequestsCount"><fa :icon="faCircle"/></i>
</router-link>
<router-link class="item" active-class="active" to="/my/drive" v-if="$store.getters.isSignedIn">
<fa :icon="faCloud" fixed-width/><span class="text">{{ $t('drive') }}</span>
</router-link>
<router-link class="item" active-class="active" to="/announcements">
<fa :icon="faBroadcastTower" fixed-width/><span class="text">{{ $t('announcements') }}</span>
<i v-if="$store.getters.isSignedIn && $store.state.i.hasUnreadAnnouncement"><fa :icon="faCircle"/></i>
</router-link>
<button class="item _button" :class="{ active: $route.path === '/instance' || $route.path.startsWith('/instance/') }" v-if="$store.getters.isSignedIn && ($store.state.i.isAdmin || $store.state.i.isModerator)" @click="oepnInstanceMenu">
<fa :icon="faServer" fixed-width/><span class="text">{{ $t('instance') }}</span>
</button>
<button class="item _button" @click="search()">
<fa :icon="faSearch" fixed-width/><span class="text">{{ $t('search') }}</span>
</button>
<button class="item _button" @click="more">
<fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $t('more') }}</span>
<i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadMentions || $store.state.i.hasUnreadSpecifiedNotes)"><fa :icon="faCircle"/></i>
</button>
</div>
</nav>
<transition name="nav-back">
<div class="nav-back"
v-if="showNav"
@click="showNav = false"
@touchstart="showNav = false"
></div>
</transition>
<transition name="nav">
<nav class="nav" ref="nav" v-show="showNav">
<div>
<button class="item _button account" @click="openAccountMenu" v-if="$store.getters.isSignedIn">
<mk-avatar :user="$store.state.i" class="avatar"/><mk-acct class="text" :user="$store.state.i"/>
</button>
<div class="divider"></div>
<router-link class="item index" active-class="active" to="/" exact v-if="$store.getters.isSignedIn">
<fa :icon="faHome" fixed-width/><span class="text">{{ $t('timeline') }}</span>
</router-link>
<router-link class="item index" active-class="active" to="/" exact v-else>
<fa :icon="faHome" fixed-width/><span class="text">{{ $t('home') }}</span>
</router-link>
<button class="item _button notifications" @click="notificationsOpen = !notificationsOpen" ref="notificationButton" v-if="$store.getters.isSignedIn">
<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
</button>
<router-link class="item" active-class="active" to="/my/messaging" v-if="$store.getters.isSignedIn">
<fa :icon="faComments" fixed-width/><span class="text">{{ $t('messaging') }}</span>
<i v-if="$store.state.i.hasUnreadMessagingMessage"><fa :icon="faCircle"/></i>
</router-link>
<router-link class="item" active-class="active" to="/my/follow-requests" v-if="$store.getters.isSignedIn && $store.state.i.isLocked">
<fa :icon="faUserClock" fixed-width/><span class="text">{{ $t('followRequests') }}</span>
<i v-if="$store.state.i.pendingReceivedFollowRequestsCount"><fa :icon="faCircle"/></i>
</router-link>
<router-link class="item" active-class="active" to="/my/drive" v-if="$store.getters.isSignedIn">
<fa :icon="faCloud" fixed-width/><span class="text">{{ $t('drive') }}</span>
</router-link>
<div class="divider"></div>
<router-link class="item" active-class="active" to="/featured">
<fa :icon="faFireAlt" fixed-width/><span class="text">{{ $t('featured') }}</span>
</router-link>
<router-link class="item" active-class="active" to="/explore">
<fa :icon="faHashtag" fixed-width/><span class="text">{{ $t('explore') }}</span>
</router-link>
<router-link class="item" active-class="active" to="/announcements">
<fa :icon="faBroadcastTower" fixed-width/><span class="text">{{ $t('announcements') }}</span>
<i v-if="$store.getters.isSignedIn && $store.state.i.hasUnreadAnnouncement"><fa :icon="faCircle"/></i>
</router-link>
<button class="item _button" @click="search()">
<fa :icon="faSearch" fixed-width/><span class="text">{{ $t('search') }}</span>
</button>
<div class="divider"></div>
<button class="item _button" :class="{ active: $route.path === '/instance' || $route.path.startsWith('/instance/') }" v-if="$store.getters.isSignedIn && ($store.state.i.isAdmin || $store.state.i.isModerator)" @click="oepnInstanceMenu">
<fa :icon="faServer" fixed-width/><span class="text">{{ $t('instance') }}</span>
</button>
<button class="item _button" @click="more">
<fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $t('more') }}</span>
<i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadMentions || $store.state.i.hasUnreadSpecifiedNotes)"><fa :icon="faCircle"/></i>
</button>
</div>
</nav>
</transition>
<div class="contents">
<main ref="main">
<div class="content">
<transition name="page" mode="out-in">
<transition :name="$store.state.device.animation ? 'page' : ''" mode="out-in" @enter="onTransition">
<keep-alive :include="['index']">
<router-view></router-view>
</keep-alive>
@ -100,9 +113,9 @@
class="sortable"
@sort="onWidgetSort"
>
<div v-for="widget in widgets" class="customize-container" :key="widget.id">
<div v-for="widget in widgets" class="customize-container _panel" :key="widget.id">
<header>
<span class="handle"><fa :icon="faBars"/></span>{{ widget.name }}<button class="remove" @click="removeWidget(widget)"><fa :icon="faTimes"/></button>
<span class="handle"><fa :icon="faBars"/></span>{{ $t('_widgets.' + widget.name) }}<button class="remove _button" @click="removeWidget(widget)"><fa :icon="faTimes"/></button>
</header>
<div @click="widgetFunc(widget.id)">
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true"/>
@ -121,7 +134,7 @@
</div>
<div class="buttons">
<button v-if="$store.getters.isSignedIn" class="button nav _button" @click="showNav" ref="navButton"><fa :icon="faBars"/><i v-if="$store.state.i.hasUnreadSpecifiedNotes || $store.state.i.pendingReceivedFollowRequestsCount || $store.state.i.hasUnreadMessagingMessage || $store.state.i.hasUnreadAnnouncement"><fa :icon="faCircle"/></i></button>
<button v-if="$store.getters.isSignedIn" class="button nav _button" @click="showNav = true" ref="navButton"><fa :icon="faBars"/><i v-if="$store.state.i.hasUnreadSpecifiedNotes || $store.state.i.pendingReceivedFollowRequestsCount || $store.state.i.hasUnreadMessagingMessage || $store.state.i.hasUnreadAnnouncement"><fa :icon="faCircle"/></i></button>
<button v-if="$store.getters.isSignedIn" class="button home _button" :disabled="$route.path === '/'" @click="$router.push('/')"><fa :icon="faHome"/></button>
<button v-if="$store.getters.isSignedIn" class="button notifications _button" @click="notificationsOpen = !notificationsOpen" ref="notificationButton2"><fa :icon="notificationsOpen ? faTimes : faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
<button v-if="$store.getters.isSignedIn" class="button post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
@ -137,7 +150,7 @@
<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 } from '@fortawesome/free-solid-svg-icons';
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 { faBell, faEnvelope, faLaugh, faComments } from '@fortawesome/free-regular-svg-icons';
import { v4 as uuid } from 'uuid';
import i18n from './i18n';
@ -159,6 +172,7 @@ export default Vue.extend({
return {
host: host,
pageKey: 0,
showNav: false,
searching: false,
notificationsOpen: false,
accounts: [],
@ -179,6 +193,7 @@ export default Vue.extend({
return {
'p': this.post,
'n': this.post,
'h|/': this.help
};
},
@ -222,6 +237,10 @@ export default Vue.extend({
this.$root.stream.on('_disconnected_', () => {
if (!this.disconnectedDialog) {
if (this.$store.state.device.autoReload) {
location.reload();
return;
}
this.disconnectedDialog = this.$root.dialog({
type: 'warning',
showCancelButton: true,
@ -237,12 +256,15 @@ 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);
// 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 width = this.$refs.widgetsEditButton.offsetLeft + 300;
this.$refs.widgets.style.width = width + 'px';
}, 1000);
@ -250,10 +272,18 @@ export default Vue.extend({
},
methods: {
help() {
this.$router.push('/docs/keyboard-shortcut');
},
back() {
if (this.canBack) window.history.back();
},
onTransition() {
if (window._scroll) window._scroll();
},
post() {
this.$root.post();
},
@ -284,67 +314,6 @@ export default Vue.extend({
}
},
showNav(ev) {
this.$root.menu({
items: [{
text: this.$t('search'),
icon: faSearch,
action: this.search,
}, null, this.$store.state.i.isAdmin || this.$store.state.i.isModerator ? {
text: this.$t('instance'),
icon: faServer,
action: () => this.oepnInstanceMenu(ev),
} : undefined, {
type: 'link',
text: this.$t('announcements'),
to: '/announcements',
icon: faBroadcastTower,
indicate: this.$store.state.i.hasUnreadAnnouncement,
}, {
type: 'link',
text: this.$t('featured'),
to: '/featured',
icon: faFireAlt,
}, {
type: 'link',
text: this.$t('explore'),
to: '/explore',
icon: faHashtag,
}, {
type: 'link',
text: this.$t('messaging'),
to: '/my/messaging',
icon: faComments,
indicate: this.$store.state.i.hasUnreadMessagingMessage,
}, this.$store.state.i.isLocked ? {
type: 'link',
text: this.$t('followRequests'),
to: '/my/follow-requests',
icon: faUserClock,
indicate: this.$store.state.i.pendingReceivedFollowRequestsCount > 0,
} : undefined, {
type: 'link',
text: this.$t('drive'),
to: '/my/drive',
icon: faCloud,
}, {
text: this.$t('more'),
icon: faEllipsisH,
action: () => this.more(ev),
indicate: this.$store.state.i.hasUnreadMentions || this.$store.state.i.hasUnreadSpecifiedNotes
}, null, {
type: 'user',
user: this.$store.state.i,
action: () => this.openAccountMenu(ev),
}],
direction: 'up',
align: 'left',
fixed: true,
width: 200,
source: ev.currentTarget || ev.target,
});
},
async openAccountMenu(ev) {
const accounts = (await this.$root.api('users/show', { userIds: this.$store.state.device.accounts.map(x => x.id) })).filter(x => x.id !== this.$store.state.i.id);
@ -440,6 +409,11 @@ export default Vue.extend({
text: this.$t('lists'),
to: '/my/lists',
icon: faListUl,
}, {
type: 'link',
text: this.$t('groups'),
to: '/my/groups',
icon: faUsers,
}, {
type: 'link',
text: this.$t('antennas'),
@ -473,6 +447,11 @@ export default Vue.extend({
to: '/games',
icon: faGamepad,
}, null] : []), {
type: 'link',
text: this.$t('help'),
to: '/docs',
icon: faQuestionCircle,
}, {
type: 'link',
text: this.$t('about'),
to: '/about',
@ -572,12 +551,6 @@ export default Vue.extend({
</script>
<style lang="scss" scoped>
@keyframes blink {
0% { opacity: 1; }
30% { opacity: 1; }
90% { opacity: 0; }
}
.header-enter-active, .header-leave-active {
transition: opacity 0.5s, transform 0.5s !important;
}
@ -602,6 +575,28 @@ export default Vue.extend({
transform: translateY(32px);
}
.nav-enter-active,
.nav-leave-active {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.nav-enter,
.nav-leave-active {
opacity: 0;
transform: translateX(-240px);
}
.nav-back-enter-active,
.nav-back-leave-active {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.nav-back-enter,
.nav-back-leave-active {
opacity: 0;
}
.mk-app {
$header-height: 60px;
$nav-width: 250px;
@ -747,6 +742,16 @@ export default Vue.extend({
}
}
> .nav-back {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
width: 100%;
height: 100%;
background: var(--modalBg);
}
> .nav {
$avatar-size: 32px;
$avatar-margin: ($header-height - $avatar-size) / 2;
@ -761,7 +766,14 @@ export default Vue.extend({
}
@media (max-width: $nav-hide-threshold) {
display: none;
position: fixed;
top: 0;
left: 0;
z-index: 1001;
}
@media (min-width: $nav-hide-threshold + 1px) {
display: block !important;
}
> div {
@ -771,13 +783,24 @@ export default Vue.extend({
z-index: 1001;
width: $nav-width;
height: 100vh;
padding-top: 16px;
padding: 16px 0;
box-sizing: border-box;
overflow: auto;
background: var(--navBg);
border-right: solid 1px var(--divider);
@media (max-width: $nav-icon-only-threshold) {
> .divider {
margin: 16px 0;
border-top: solid 1px var(--divider);
}
@media (max-width: $nav-icon-only-threshold) and (min-width: $nav-hide-threshold + 1px) {
width: $nav-icon-only-width;
padding: 8px 0;
> .divider {
margin: 8px 0;
}
}
> .item {
@ -785,7 +808,6 @@ export default Vue.extend({
display: block;
padding-left: 32px;
font-size: $ui-font-size;
font-weight: bold;
line-height: 3.2rem;
text-overflow: ellipsis;
overflow: hidden;
@ -795,22 +817,6 @@ export default Vue.extend({
box-sizing: border-box;
color: var(--navFg);
&:not(.active) {
opacity: 0.85;
&:hover {
opacity: 1;
> [data-icon] {
opacity: 1;
}
}
> [data-icon] {
opacity: 0.85;
}
}
> [data-icon] {
width: ($header-height - ($avatar-margin * 2));
}
@ -843,7 +849,7 @@ export default Vue.extend({
color: var(--navActive);
}
@media (max-width: $nav-icon-only-threshold) {
@media (max-width: $nav-icon-only-threshold) and (min-width: $nav-hide-threshold + 1px) {
padding-left: 0;
width: 100%;
text-align: center;
@ -864,6 +870,13 @@ export default Vue.extend({
}
}
}
@media (max-width: $nav-hide-threshold) {
> .index,
> .notifications {
display: none;
}
}
}
}
@ -967,10 +980,10 @@ export default Vue.extend({
> header {
position: relative;
line-height: 32px;
background: #eee;
> .handle {
padding: 0 8px;
cursor: move;
}
> .remove {

View File

@ -1,6 +1,8 @@
<template>
<div>
<mk-avatar v-for="user in us" :user="user" :key="user.id" style="width:32px;height:32px;"/>
<div v-for="user in us" :key="user.id" style="width:32px;height:32px;margin-right:8px;">
<mk-avatar :user="user" style="width:32px;height:32px;"/>
</div>
</div>
</template>

View File

@ -1,8 +1,8 @@
<template>
<sequential-entrance class="sqadhkmv" ref="list" :direction="direction">
<sequential-entrance class="sqadhkmv" ref="list" :direction="direction" :reversed="reversed">
<template v-for="(item, i) in items">
<slot :item="item" :i="i"></slot>
<div class="separator" :key="item.id + '_date'" :data-index="i" v-if="i != items.length - 1 && new Date(item.createdAt).getDate() != new Date(items[i + 1].createdAt).getDate()">
<div class="separator" :key="item.id + '_date'" v-if="i != items.length - 1 && new Date(item.createdAt).getDate() != new Date(items[i + 1].createdAt).getDate()">
<p class="date">
<span><fa class="icon" :icon="faAngleUp"/>{{ getDateText(item.createdAt) }}</span>
<span>{{ getDateText(items[i + 1].createdAt) }}<fa class="icon" :icon="faAngleDown"/></span>
@ -28,6 +28,11 @@ export default Vue.extend({
direction: {
type: String,
required: false
},
reversed: {
type: Boolean,
required: false,
default: false
}
},

View File

@ -133,6 +133,7 @@ export default Vue.extend({
<style lang="scss" scoped>
.zdjebgpv {
display: flex;
position: relative;
> img,
> .icon {

View File

@ -19,7 +19,7 @@
{{ folder.name }}
</p>
<p class="upload" v-if="$store.state.settings.uploadFolder == folder.id">
{{ $t('upload-folder') }}
{{ $t('uploadFolder') }}
</p>
</div>
</template>

View File

@ -319,6 +319,49 @@ export default Vue.extend({
});
},
renameFolder(folder) {
this.$root.dialog({
title: this.$t('contextmenu.rename-folder'),
input: {
placeholder: this.$t('contextmenu.input-new-folder-name'),
default: folder.name
}
}).then(({ canceled, result: name }) => {
if (canceled) return;
this.$root.api('drive/folders/update', {
folderId: folder.id,
name: name
}).then(folder => {
// FIXME: 画面を更新するために自分自身に移動
this.move(folder);
});
});
},
deleteFolder(folder) {
this.$root.api('drive/folders/delete', {
folderId: folder.id
}).then(() => {
// 削除時に親フォルダに移動
this.move(folder.parentId);
}).catch(err => {
switch(err.id) {
case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
this.$root.dialog({
type: 'error',
title: this.$t('unable-to-delete'),
text: this.$t('has-child-files-or-folders')
});
break;
default:
this.$root.dialog({
type: 'error',
text: this.$t('unable-to-delete')
});
}
});
},
onChangeFileInput() {
for (const file of Array.from((this.$refs.fileInput as any).files)) {
this.upload(file, this.folder);

View File

@ -1,5 +1,6 @@
<template>
<div class="mjndxjcg _panel">
<img src="https://xn--931a.moe/assets/error.jpg" alt=""/>
<p><fa :icon="faExclamationTriangle"/> {{ $t('error') }}</p>
<mk-button @click="() => $emit('retry')" class="button">{{ $t('retry') }}</mk-button>
</div>
@ -38,5 +39,12 @@ export default Vue.extend({
> .button {
margin: 0 auto;
}
> img {
vertical-align: bottom;
height: 150px;
margin-bottom: 16px;
border-radius: 16px;
}
}
</style>

View File

@ -2,26 +2,26 @@
<x-popup :source="source" :no-center="noCenter" :fixed="fixed" :width="width" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }">
<sequential-entrance class="rrevdjwt" :class="{ left: align === 'left' }" :delay="15" :direction="direction">
<template v-for="(item, i) in items.filter(item => item !== undefined)">
<div v-if="item === null" class="divider" :key="i" :data-index="i"></div>
<span v-else-if="item.type === 'label'" class="label item" :key="i" :data-index="i">
<div v-if="item === null" class="divider" :key="i"></div>
<span v-else-if="item.type === 'label'" class="label item" :key="i">
<span>{{ item.text }}</span>
</span>
<router-link v-else-if="item.type === 'link'" :to="item.to" @click.native="close()" :tabindex="i" class="_button item" :key="i" :data-index="i">
<router-link v-else-if="item.type === 'link'" :to="item.to" @click.native="close()" :tabindex="i" class="_button item" :key="i">
<fa v-if="item.icon" :icon="item.icon" fixed-width/>
<mk-avatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
<span>{{ item.text }}</span>
<i v-if="item.indicate"><fa :icon="faCircle"/></i>
</router-link>
<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" @click="close()" :tabindex="i" class="_button item" :key="i" :data-index="i">
<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" @click="close()" :tabindex="i" class="_button item" :key="i">
<fa v-if="item.icon" :icon="item.icon" fixed-width/>
<span>{{ item.text }}</span>
<i v-if="item.indicate"><fa :icon="faCircle"/></i>
</a>
<button v-else-if="item.type === 'user'" @click="clicked(item.action)" :tabindex="i" class="_button item" :key="i" :data-index="i">
<button v-else-if="item.type === 'user'" @click="clicked(item.action)" :tabindex="i" class="_button item" :key="i">
<mk-avatar :user="item.user" class="avatar"/><mk-user-name :user="item.user"/>
<i v-if="item.indicate"><fa :icon="faCircle"/></i>
</button>
<button v-else @click="clicked(item.action)" :tabindex="i" class="_button item" :key="i" :data-index="i">
<button v-else @click="clicked(item.action)" :tabindex="i" class="_button item" :key="i">
<fa v-if="item.icon" :icon="item.icon" fixed-width/>
<mk-avatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
<span>{{ item.text }}</span>
@ -88,12 +88,6 @@ export default Vue.extend({
</script>
<style lang="scss" scoped>
@keyframes blink {
0% { opacity: 1; }
30% { opacity: 1; }
90% { opacity: 0; }
}
.rrevdjwt {
padding: 8px 0;

View File

@ -154,21 +154,17 @@ export default Vue.component('misskey-flavored-markdown', {
url: token.node.props.url,
rel: 'nofollow noopener',
},
attrs: {
style: 'color:var(--link);'
}
})];
}
case 'link': {
return [createElement('a', {
attrs: {
class: 'link',
class: 'link _link',
href: token.node.props.url,
rel: 'nofollow noopener',
target: '_blank',
title: token.node.props.url,
style: 'color:var(--link);'
}
}, genEl(token.children))];
}

View File

@ -36,5 +36,10 @@ export default Vue.extend({
::v-deep pre {
font-size: 0.8em;
}
::v-deep .title {
text-align: center;
border-bottom: solid 1px var(--divider);
}
}
</style>

View File

@ -1,9 +1,9 @@
<template>
<div class="mk-modal">
<transition name="bg-fade" appear>
<transition :name="$store.state.device.animation ? 'bg-fade' : ''" appear>
<div class="bg" ref="bg" v-if="show" @click="close()"></div>
</transition>
<transition name="modal" appear @after-leave="() => { $emit('closed'); destroyDom(); }">
<transition :name="$store.state.device.animation ? 'modal' : ''" appear @after-leave="() => { $emit('closed'); destroyDom(); }">
<div class="content" ref="content" v-if="show" @click.self="close()"><slot></slot></div>
</transition>
</div>

View File

@ -83,7 +83,7 @@
<script lang="ts">
import Vue from 'vue';
import { faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import { faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faTrashAlt, faQuoteRight } from '@fortawesome/free-solid-svg-icons';
import { parse } from '../../mfm/parse';
import { sum, unique } from '../../prelude/array';
import i18n from '../i18n';
@ -97,7 +97,6 @@ import XPoll from './poll.vue';
import XUrlPreview from './url-preview.vue';
import MkNoteMenu from './note-menu.vue';
import MkReactionPicker from './reaction-picker.vue';
import MkRenotePicker from './renote-picker.vue';
import pleaseLogin from '../scripts/please-login';
function focus(el, fn) {
@ -275,8 +274,14 @@ export default Vue.extend({
methods: {
capture(withHandler = false) {
if (this.$store.getters.isSignedIn) {
this.connection.send('sn', { id: this.appearNote.id });
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
if (document.body.contains(this.$el)) {
this.connection.send('sn', { id: this.appearNote.id });
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
} else {
this.$once('hook:activated', () => {
this.capture(withHandler);
});
}
}
},
@ -373,10 +378,26 @@ export default Vue.extend({
renote() {
pleaseLogin(this.$root);
this.blur();
this.$root.new(MkRenotePicker, {
this.$root.menu({
items: [{
text: this.$t('renote'),
icon: faRetweet,
action: () => {
(this as any).$root.api('notes/create', {
renoteId: this.appearNote.id
});
}
}, {
text: this.$t('quote'),
icon: faQuoteRight,
action: () => {
this.$root.post({
renote: this.appearNote,
});
}
}]
source: this.$refs.renoteButton,
note: this.appearNote,
}).$once('closed', this.focus);
}).then(this.focus);
},
renoteDirectly() {
@ -458,7 +479,7 @@ export default Vue.extend({
},
showRenoteMenu(ev) {
if (!this.isMyNote) return;
if (!this.$store.getters.isSignedIn || (this.$store.state.i.id !== this.note.userId)) return;
this.$root.menu({
items: [{
text: this.$t('unrenote'),

View File

@ -1,11 +1,14 @@
<template>
<div class="mk-notes" v-size="[{ max: 500 }]">
<div class="empty" v-if="empty">{{ $t('noNotes') }}</div>
<div class="empty _panel" 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-note :note="note" :detail="detail" :key="note.id" :data-index="i"/>
<x-note :note="note" :detail="detail" :key="note.id"/>
</x-list>
<footer v-if="more">
@ -24,8 +27,6 @@ import i18n from '../i18n';
import paging from '../scripts/paging';
import XNote from './note.vue';
import XList from './date-separated-list.vue';
import getUserName from '../../misc/get-user-name';
import getNoteSummary from '../../misc/get-note-summary';
export default Vue.extend({
i18n,
@ -36,19 +37,6 @@ export default Vue.extend({
mixins: [
paging({
onPrepend: (self, note) => {
// タブが非表示なら通知
if (document.hidden) {
if ('Notification' in window && Notification.permission === 'granted') {
new Notification(getUserName(note.user), {
body: getNoteSummary(note),
icon: note.user.avatarUrl,
tag: 'newNote'
});
}
}
},
before: (self) => {
self.$emit('before');
},
@ -98,14 +86,15 @@ export default Vue.extend({
<style lang="scss" scoped>
.mk-notes {
> .empty {
margin: 0 auto;
padding: 32px;
text-align: center;
background: rgba(0, 0, 0, 0.3);
color: #fff;
-webkit-backdrop-filter: blur(16px);
backdrop-filter: blur(16px);
border-radius: 6px;
> img {
vertical-align: bottom;
height: 128px;
margin-bottom: 16px;
border-radius: 16px;
}
}
> .notes {

View File

@ -2,7 +2,7 @@
<div class="mk-notifications">
<div class="contents">
<x-list class="notifications" :items="items" v-slot="{ item: notification, i }">
<x-notification :notification="notification" :with-time="true" :full="true" class="notification" :key="notification.id" :data-index="i"/>
<x-notification :notification="notification" :with-time="true" :full="true" class="notification" :key="notification.id"/>
</x-list>
<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">

View File

@ -1,9 +1,9 @@
<template>
<div class="mk-popup">
<transition name="bg-fade" appear>
<transition :name="$store.state.device.animation ? 'bg-fade' : ''" appear>
<div class="bg" ref="bg" @click="close()" v-if="show"></div>
</transition>
<transition name="popup" appear @after-leave="() => { $emit('closed'); destroyDom(); }">
<transition :name="$store.state.device.animation ? 'popup' : ''" appear @after-leave="() => { $emit('closed'); destroyDom(); }">
<div class="content" :class="{ fixed }" ref="content" v-if="show" :style="{ width: width ? width + 'px' : 'auto' }"><slot></slot></div>
</transition>
</div>
@ -134,7 +134,7 @@ export default Vue.extend({
position: absolute;
z-index: 10001;
background: var(--panel);
border-radius: 4px;
border-radius: 8px;
box-shadow: 0 3px 12px rgba(27, 31, 35, 0.15);
overflow: hidden;
transform-origin: center top;

View File

@ -1,10 +1,10 @@
<template>
<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
<transition name="form-fade" appear>
<transition :name="$store.state.device.animation ? 'form-fade' : ''" appear>
<div class="bg" ref="bg" v-if="show" @click="close()"></div>
</transition>
<div class="main" ref="main" @click.self="close()" @keydown="onKeydown">
<transition name="form" appear
<transition :name="$store.state.device.animation ? 'form' : ''" appear
@after-leave="destroyDom"
>
<x-post-form ref="form"

View File

@ -15,7 +15,7 @@
<span v-if="visibility === 'followers'"><fa :icon="faUnlock"/></span>
<span v-if="visibility === 'specified'"><fa :icon="faEnvelope"/></span>
</button>
<button class="submit _buttonPrimary" :disabled="!canPost" @click="post">{{ submitText }}</button>
<button class="submit _buttonPrimary" :disabled="!canPost" @click="post">{{ submitText }}<fa :icon="reply ? faReply : renote ? faQuoteRight : faPaperPlane"/></button>
</div>
</header>
<div class="form">
@ -52,7 +52,7 @@
<script lang="ts">
import Vue from 'vue';
import { faTimes, faUpload, faChartPie, faGlobe, faHome, faUnlock, faEnvelope, faPlus, faPhotoVideo, faCloud, faLink, faAt, faBiohazard } from '@fortawesome/free-solid-svg-icons';
import { faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faChartPie, faGlobe, faHome, faUnlock, faEnvelope, faPlus, faPhotoVideo, faCloud, faLink, faAt, faBiohazard } from '@fortawesome/free-solid-svg-icons';
import { faEyeSlash, faLaughSquint } from '@fortawesome/free-regular-svg-icons';
import insertTextAtCursor from 'insert-text-at-cursor';
import { length } from 'stringz';
@ -130,7 +130,7 @@ export default Vue.extend({
draghover: false,
quoteId: null,
recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'),
faTimes, faUpload, faChartPie, faGlobe, faHome, faUnlock, faEnvelope, faEyeSlash, faLaughSquint, faPlus, faPhotoVideo, faCloud, faLink, faAt, faBiohazard
faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faChartPie, faGlobe, faHome, faUnlock, faEnvelope, faEyeSlash, faLaughSquint, faPlus, faPhotoVideo, faCloud, faLink, faAt, faBiohazard
};
},
@ -163,10 +163,10 @@ export default Vue.extend({
submitText(): string {
return this.renote
? this.$t('renote')
? this.$t('quote')
: this.reply
? this.$t('reply')
: this.$t('post');
: this.$t('note');
},
canPost(): boolean {
@ -622,8 +622,9 @@ export default Vue.extend({
> .submit {
margin: 16px 16px 16px 0;
padding: 0 16px;
padding: 0 12px;
line-height: 34px;
font-weight: bold;
vertical-align: bottom;
border-radius: 4px;
@ -634,6 +635,10 @@ export default Vue.extend({
&:disabled {
opacity: 0.7;
}
> [data-icon] {
margin-left: 6px;
}
}
}
}

View File

@ -13,7 +13,7 @@
mode="out-in"
appear
>
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :data-index="i" :tabindex="i + 1" :title="/^[a-z]+$/.test(reaction) ? $t('@.reactions.' + reaction) : reaction"><x-reaction-icon :reaction="reaction"/></button>
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="/^[a-z]+$/.test(reaction) ? $t('@.reactions.' + reaction) : reaction"><x-reaction-icon :reaction="reaction"/></button>
</transition-group>
<input class="text" v-model="text" :placeholder="$t('enterEmoji')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
</div>

View File

@ -1,94 +0,0 @@
<template>
<x-popup :source="source" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }" v-hotkey.global="keymap">
<div class="rdfaahpc">
<button class="_button" @click="quote()"><fa :icon="faQuoteRight"/></button>
<button class="_button" @click="renote()"><fa :icon="faRetweet"/></button>
</div>
</x-popup>
</template>
<script lang="ts">
import Vue from 'vue';
import { faQuoteRight, faRetweet } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
import XPopup from './popup.vue';
export default Vue.extend({
i18n,
components: {
XPopup,
},
props: {
note: {
type: Object,
required: true
},
source: {
required: true
},
},
data() {
return {
faQuoteRight, faRetweet
};
},
computed: {
keymap(): any {
return {
'esc': this.close,
};
}
},
methods: {
renote() {
(this as any).$root.api('notes/create', {
renoteId: this.note.id
}).then(() => {
this.$emit('closed');
this.destroyDom();
});
},
quote() {
this.$emit('closed');
this.destroyDom();
this.$root.post({
renote: this.note,
});
}
}
});
</script>
<style lang="scss" scoped>
.rdfaahpc {
padding: 4px;
> button {
padding: 0;
width: 40px;
height: 40px;
font-size: 16px;
border-radius: 2px;
> * {
height: 1em;
}
&:hover {
background: rgba(0, 0, 0, 0.05);
}
&:active {
background: var(--accent);
box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<transition-group
<transition-group v-if="$store.state.device.animation"
name="staggered-fade"
tag="div"
:css="false"
@ -11,6 +11,9 @@
>
<slot></slot>
</transition-group>
<div v-else>
<slot></slot>
</div>
</template>
<script lang="ts">
@ -27,27 +30,37 @@ export default Vue.extend({
type: String,
required: false,
default: 'down'
},
reversed: {
type: Boolean,
required: false,
default: false
}
},
i: 0,
methods: {
beforeEnter(el) {
el.style.opacity = 0;
el.style.transform = this.direction === 'down' ? 'translateY(-64px)' : 'translateY(64px)';
let index = this.$options.i;
const delay = this.delay * index;
el.style.transition = [getComputedStyle(el).transition, `transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) ${delay}ms`, `opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1) ${delay}ms`].filter(x => x != '').join(',');
this.$options.i++;
},
enter(el, done) {
el.style.transition = [getComputedStyle(el).transition, 'transform 0.7s cubic-bezier(0.23, 1, 0.32, 1)', 'opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1)'].filter(x => x != '').join(',');
setTimeout(() => {
el.style.opacity = 1;
el.style.transform = 'translateY(0px)';
setTimeout(done, 700);
}, this.delay * el.dataset.index)
el.addEventListener('transitionend', () => {
el.style.transition = '';
this.$options.i--;
done();
}, { once: true });
});
},
leave(el, done) {
setTimeout(() => {
el.style.opacity = 0;
el.style.transform = this.direction === 'down' ? 'translateY(64px)' : 'translateY(-64px)';
setTimeout(done, 700);
}, this.delay * el.dataset.index)
leave(el) {
el.style.opacity = 0;
el.style.transform = this.direction === 'down' ? 'translateY(64px)' : 'translateY(-64px)';
},
focus() {
this.$slots.default[0].elm.focus();

View File

@ -8,9 +8,16 @@
<template v-else><fa :icon="faAngleDown"/></template>
</button>
</header>
<div v-show="showBody">
<slot></slot>
</div>
<transition name="container-toggle"
@enter="enter"
@after-enter="afterEnter"
@leave="leave"
@after-leave="afterLeave"
>
<div v-show="showBody">
<slot></slot>
</div>
</transition>
</div>
</template>
@ -51,12 +58,42 @@ export default Vue.extend({
toggleContent(show: boolean) {
if (!this.bodyTogglable) return;
this.showBody = show;
}
},
enter(el) {
const elementHeight = el.getBoundingClientRect().height;
el.style.height = 0;
el.offsetHeight; // reflow
el.style.height = elementHeight + 'px';
},
afterEnter(el) {
el.style.height = null;
},
leave(el) {
const elementHeight = el.getBoundingClientRect().height;
el.style.height = elementHeight + 'px';
el.offsetHeight; // reflow
el.style.height = 0;
},
afterLeave(el) {
el.style.height = null;
},
}
});
</script>
<style lang="scss" scoped>
.container-toggle-enter-active, .container-toggle-leave-active {
overflow-y: hidden;
transition: opacity 0.5s, height 0.5s !important;
}
.container-toggle-enter {
opacity: 0;
}
.container-toggle-leave-to {
opacity: 0;
}
.ukygtjoj {
position: relative;
overflow: hidden;
@ -72,6 +109,7 @@ export default Vue.extend({
> header {
position: relative;
box-shadow: 0 1px 0 0 var(--divider);
> .title {
margin: 0;

View File

@ -1,5 +1,5 @@
<template>
<component :is="hasRoute ? 'router-link' : 'a'" class="mk-url" :[attr]="hasRoute ? url.substr(local.length) : url" :rel="rel" :target="target">
<component :is="hasRoute ? 'router-link' : 'a'" class="ieqqeuvs _link" :[attr]="hasRoute ? url.substr(local.length) : url" :rel="rel" :target="target">
<template v-if="!self">
<span class="schema">{{ schema }}//</span>
<span class="hostname">{{ hostname }}</span>
@ -58,7 +58,7 @@ export default Vue.extend({
</script>
<style lang="scss" scoped>
.mk-url {
.ieqqeuvs {
word-break: break-all;
> .icon {

View File

@ -84,7 +84,7 @@ export default Vue.extend({
methods: {
close() {
this.show = false;
(this.$refs.content as any).style.pointerEvents = 'none';
if (this.$refs.content) (this.$refs.content as any).style.pointerEvents = 'none';
}
}
});

View File

@ -7,7 +7,7 @@
</div>
<sequential-entrance class="users">
<router-link v-for="(item, i) in items" class="user" :key="item.id" :data-index="i" :to="extract ? extract(item) : item | userPage">
<router-link v-for="(item, i) in items" class="user" :key="item.id" :to="extract ? extract(item) : item | userPage">
<mk-avatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true"/>
<div class="body">
<mk-user-name :user="extract ? extract(item) : item" class="name"/>

View File

@ -27,17 +27,17 @@
<div style="margin-bottom: 1em;">{{ $t('aboutMisskeyText') }}</div>
<div>{{ $t('misskeyMembers') }}</div>
<span class="members">
<a href="https://github.com/syuilo" target="_blank">@syuilo</a>
<a href="https://github.com/AyaMorisawa" target="_blank">@AyaMorisawa</a>
<a href="https://github.com/mei23" target="_blank">@mei23</a>
<a href="https://github.com/acid-chicken" target="_blank">@acid-chicken</a>
<a href="https://github.com/tamaina" target="_blank">@tamaina</a>
<a href="https://github.com/rinsuki" target="_blank">@rinsuki</a>
<a href="https://github.com/syuilo" target="_blank" class="_link">@syuilo</a>
<a href="https://github.com/AyaMorisawa" target="_blank" class="_link">@AyaMorisawa</a>
<a href="https://github.com/mei23" target="_blank" class="_link">@mei23</a>
<a href="https://github.com/acid-chicken" target="_blank" class="_link">@acid-chicken</a>
<a href="https://github.com/tamaina" target="_blank" class="_link">@tamaina</a>
<a href="https://github.com/rinsuki" target="_blank" class="_link">@rinsuki</a>
</span>
<div style="margin-top: 1em;">{{ $t('misskeySource') }}</div>
<a href="https://github.com/syuilo/misskey" target="_blank" style="color: var(--link);">https://github.com/syuilo/misskey</a>
<mk-url url="https://github.com/syuilo/misskey"/>
<div style="margin-top: 1em;">{{ $t('misskeyDonate') }}</div>
<a href="https://www.patreon.com/syuilo" target="_blank" style="color: var(--link);">https://www.patreon.com/syuilo</a>
<mk-url url="https://www.patreon.com/syuilo"/>
</div>
<div class="_content">
<span><mfm text="<motion>❤</motion>"/> {{ $t('patrons') }}</span>
@ -121,7 +121,6 @@ export default Vue.extend({
> ._content {
> .members {
> a {
color: var(--link);
margin-right: 0.5em;
}
}

View File

@ -4,7 +4,7 @@
<portal to="title">{{ $t('announcements') }}</portal>
<mk-pagination :pagination="pagination" #default="{items}" class="ruryvtyk" ref="list">
<section class="_card announcement" v-for="(announcement, i) in items" :key="announcement.id" :data-index="i">
<section class="_card announcement" v-for="(announcement, i) in items" :key="announcement.id">
<div class="_title"><span v-if="$store.getters.isSignedIn && !announcement.isRead">🆕 </span>{{ announcement.title }}</div>
<div class="_content">
<mfm :text="announcement.text"/>

134
src/client/pages/doc.vue Normal file
View File

@ -0,0 +1,134 @@
<template>
<div>
<portal to="icon"><fa :icon="faFileAlt"/></portal>
<portal to="title">{{ title }}</portal>
<main class="_card">
<div class="_content">
<div v-html="body" class="qyqbqfal"></div>
</div>
</main>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faFileAlt } from '@fortawesome/free-solid-svg-icons'
import MarkdownIt from 'markdown-it';
import { url, lang } from '../config';
const markdown = MarkdownIt({
html: true
});
export default Vue.extend({
metaInfo() {
return {
title: this.title,
};
},
props: {
doc: {
type: String,
required: true
}
},
watch: {
doc: {
handler() {
this.fetchDoc();
},
immediate: true,
}
},
data() {
return {
faFileAlt,
title: '',
body: '',
markdown: '',
}
},
methods: {
fetchDoc() {
fetch(`${url}/assets/docs/${this.doc}.${lang}.md`).then(res => res.text()).then(md => {
this.parse(md);
});
},
parse(md: string) {
// markdown の全容をパースする
const parsed = markdown.parse(md, {});
if (parsed.length === 0) return;
const buf = [...parsed];
const headingTokens = [];
let headingStart = 0;
// もっとも上にある見出しを抽出する
while (buf[0].type !== 'heading_open') {
buf.shift();
headingStart++;
}
buf.shift();
while (buf[0].type as string !== 'heading_close') {
const token = buf.shift();
if (token) {
headingTokens.push(token);
}
}
// 抽出した見出しを除く部分をbodyとして抽出する
const bodyTokens = [...parsed];
bodyTokens.splice(headingStart, headingTokens.length + 2);
// 各々レンダーする
this.title = markdown.renderer.render(headingTokens, {}, {});
this.body = markdown.renderer.render(bodyTokens, {}, {});
}
}
});
</script>
<style lang="scss" scoped>
.qyqbqfal {
> *:first-child {
margin-top: 0;
}
> *:last-child {
margin-bottom: 0;
}
::v-deep h2 {
font-size: 1.25em;
padding: 0 0 0.5em 0;
border-bottom: solid 1px var(--divider);
}
::v-deep table {
width: 100%;
max-width: 100%;
overflow: auto;
}
::v-deep kbd.group {
display: inline-block;
padding: 2px;
border: 1px solid var(--divider);
border-radius: 4px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
}
::v-deep kbd.key {
display: inline-block;
padding: 6px 8px;
border: solid 1px var(--divider);
border-radius: 4px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
}
}
</style>

42
src/client/pages/docs.vue Normal file
View File

@ -0,0 +1,42 @@
<template>
<div>
<portal to="icon"><fa :icon="faQuestionCircle"/></portal>
<portal to="title">{{ $t('help') }}</portal>
<main class="_card">
<div class="_content">
<ul>
<li v-for="doc in docs" :key="doc.path">
<router-link :to="`/docs/${doc.path}`">{{ doc.title }}</router-link>
</li>
</ul>
</div>
</main>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'
import { url, lang } from '../config';
export default Vue.extend({
metaInfo() {
return {
title: this.$t('help') as string,
};
},
data() {
return {
docs: [],
faQuestionCircle
}
},
created() {
fetch(`${url}/docs.json?lang=${lang}`).then(res => res.json()).then(docs => {
this.docs = docs;
});
},
});
</script>

View File

@ -57,11 +57,11 @@ export default Vue.extend({
}, this.folder ? {
text: this.$t('renameFolder'),
icon: faICursor,
action: () => { this.$refs.drive.renameFolder(); }
action: () => { this.$refs.drive.renameFolder(this.folder); }
} : undefined, this.folder ? {
text: this.$t('deleteFolder'),
icon: faTrashAlt,
action: () => { this.$refs.drive.deleteFolder(); }
action: () => { this.$refs.drive.deleteFolder(this.folder); }
} : undefined, {
text: this.$t('createFolder'),
icon: faFolderPlus,

View File

@ -1,6 +1,6 @@
<template>
<mk-pagination :pagination="pagination" #default="{items}" class="mk-follow-requests" ref="list">
<div class="user _panel" v-for="(req, i) in items" :key="req.id" :data-index="i">
<div class="user _panel" v-for="(req, i) in items" :key="req.id">
<mk-avatar class="avatar" :user="req.follower"/>
<div class="body">
<div class="name">

View File

@ -173,12 +173,6 @@ export default Vue.extend({
</script>
<style lang="scss">
@keyframes blink {
0% { opacity: 1; }
30% { opacity: 1; }
90% { opacity: 0; }
}
._kjvfvyph_ {
position: relative;
height: 100%;

View File

@ -9,7 +9,7 @@
<mk-pagination :pagination="pagination" class="emojis" ref="emojis">
<template #empty><span>{{ $t('noCustomEmojis') }}</span></template>
<template #default="{items}">
<div class="emoji" v-for="(emoji, i) in items" :key="emoji.id" :data-index="i" @click="selected = emoji" :class="{ selected: selected && (selected.id === emoji.id) }">
<div class="emoji" v-for="(emoji, i) in items" :key="emoji.id" @click="selected = emoji" :class="{ selected: selected && (selected.id === emoji.id) }">
<img :src="emoji.url" class="img" :alt="emoji.name"/>
<div class="body">
<span class="name">{{ emoji.name }}</span>
@ -30,7 +30,7 @@
<mk-pagination :pagination="remotePagination" class="emojis" ref="remoteEmojis">
<template #empty><span>{{ $t('noCustomEmojis') }}</span></template>
<template #default="{items}">
<div class="emoji" v-for="(emoji, i) in items" :key="emoji.id" :data-index="i" @click="selectedRemote = emoji" :class="{ selected: selectedRemote && (selectedRemote.id === emoji.id) }">
<div class="emoji" v-for="(emoji, i) in items" :key="emoji.id" @click="selectedRemote = emoji" :class="{ selected: selectedRemote && (selectedRemote.id === emoji.id) }">
<img :src="emoji.url" class="img" :alt="emoji.name"/>
<div class="body">
<span class="name">{{ emoji.name }}</span>

View File

@ -18,7 +18,7 @@
</div>
<div class="_content">
<mk-pagination :pagination="pagination" #default="{items}" class="instances" ref="instances" :key="host + state">
<div class="instance" v-for="(instance, i) in items" :key="instance.id" :data-index="i" @click="info(instance)">
<div class="instance" v-for="(instance, i) in items" :key="instance.id" @click="info(instance)">
<div class="host"><fa :icon="faCircle" class="indicator" :class="getStatus(instance)"/><b>{{ instance.host }}</b></div>
<div class="status">
<span class="sub" v-if="instance.followersCount > 0"><fa :icon="faCaretDown" class="icon"/>Sub</span>

View File

@ -12,7 +12,7 @@
</div>
<div class="_content" style="max-height: 180px; overflow: auto;">
<sequential-entrance :delay="15" v-if="jobs.length > 0">
<div v-for="(job, i) in jobs" :key="job[0]" :data-index="i">
<div v-for="(job, i) in jobs" :key="job[0]">
<span>{{ job[0] }}</span>
<span style="margin-left: 8px; opacity: 0.7;">({{ job[1] | number }} jobs)</span>
</div>

View File

@ -20,7 +20,7 @@
<div class="_title"><fa :icon="faUsers"/> {{ $t('users') }}</div>
<div class="_content _list">
<mk-pagination :pagination="pagination" #default="{items}" class="users" ref="users" :auto-margin="false">
<button class="user _button _listItem" v-for="(user, i) in items" :key="user.id" :data-index="i" @click="show(user)">
<button class="user _button _listItem" v-for="(user, i) in items" :key="user.id" @click="show(user)">
<mk-avatar :user="user" class="avatar"/>
<div class="body">
<mk-user-name :user="user" class="name"/>

View File

@ -5,10 +5,10 @@
>
<textarea
v-model="text"
ref="textarea"
ref="text"
@keypress="onKeypress"
@paste="onPaste"
:placeholder="$t('input-message-here')"
:placeholder="$t('inputMessageHere')"
v-autocomplete="{ model: 'text' }"
></textarea>
<div class="file" @click="file = null" v-if="file">{{ file.name }}</div>
@ -16,22 +16,20 @@
<button class="send _button" @click="send" :disabled="!canSend || sending" :title="$t('send')">
<template v-if="!sending"><fa :icon="faPaperPlane"/></template><template v-if="sending"><fa icon="spinner .spin"/></template>
</button>
<button class="attach-from-local _button" @click="chooseFile" :title="$t('attach-from-local')">
<fa :icon="faUpload"/>
</button>
<button class="attach-from-drive _button" @click="chooseFileFromDrive" :title="$t('attach-from-drive')">
<fa :icon="faCloud"/>
</button>
<button class="_button" @click="chooseFile"><fa :icon="faPhotoVideo"/></button>
<button class="_button" @click="insertEmoji"><fa :icon="faLaughSquint"/></button>
<input ref="file" type="file" @change="onChangeFile"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faPaperPlane, faUpload, faCloud } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
import { faPaperPlane, faPhotoVideo, faLaughSquint } from '@fortawesome/free-solid-svg-icons';
import insertTextAtCursor from 'insert-text-at-cursor';
import * as autosize from 'autosize';
import i18n from '../i18n';
import { formatTimeString } from '../../misc/format-time-string';
import { selectFile } from '../scripts/select-file';
export default Vue.extend({
i18n,
@ -53,7 +51,7 @@ export default Vue.extend({
text: null,
file: null,
sending: false,
faPaperPlane, faUpload, faCloud
faPaperPlane, faPhotoVideo, faLaughSquint
};
},
computed: {
@ -80,7 +78,7 @@ export default Vue.extend({
}
},
mounted() {
autosize(this.$refs.textarea);
autosize(this.$refs.text);
// 書きかけの投稿を復元
const draft = JSON.parse(localStorage.getItem('message_drafts') || '{}')[this.draftId];
@ -160,14 +158,8 @@ export default Vue.extend({
}
},
chooseFile() {
(this.$refs.file as any).click();
},
chooseFileFromDrive() {
this.$chooseDriveFile({
multiple: false
}).then(file => {
chooseFile(e) {
selectFile(this, e.currentTarget || e.target, this.$t('selectFile'), false).then(file => {
this.file = file;
});
},
@ -227,6 +219,15 @@ export default Vue.extend({
localStorage.setItem('message_drafts', JSON.stringify(data));
},
async insertEmoji(ev) {
const vm = this.$root.new(await import('../components/emoji-picker.vue').then(m => m.default), {
source: ev.currentTarget || ev.target
}).$once('chosen', emoji => {
insertTextAtCursor(this.$refs.text, emoji);
vm.close();
});
}
}
});
</script>
@ -330,8 +331,7 @@ export default Vue.extend({
}
}
.attach-from-local,
.attach-from-drive {
._button {
margin: 0;
padding: 16px;
font-size: 1em;

View File

@ -18,8 +18,8 @@
<button class="more _button" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
</button>
<x-list class="messages" :items="messages" v-slot="{ item: message, i }" direction="up">
<x-message :message="message" :is-group="group != null" :key="message.id" :data-index="messages.length - i"/>
<x-list class="messages" :items="messages" v-slot="{ item: message, i }" direction="up" reversed>
<x-message :message="message" :is-group="group != null" :key="message.id"/>
</x-list>
</div>
<footer>

View File

@ -32,7 +32,7 @@
</router-link>
</sequential-entrance>
<p class="no-history" v-if="!fetching && messages.length == 0">{{ $t('no-history') }}</p>
<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<mk-loading v-if="fetching"/>
</div>
</template>
@ -282,17 +282,6 @@ export default Vue.extend({
font-weight: 500;
}
> .fetching {
margin: 0;
padding: 16px;
text-align: center;
color: var(--text);
> [data-icon] {
margin-right: 4px;
}
}
@media (max-width: 400px) {
> .search {
> .result {

View File

@ -3,12 +3,12 @@
<portal to="icon"><fa :icon="faSatellite"/></portal>
<portal to="title">{{ $t('manageAntennas') }}</portal>
<mk-button @click="create" primary class="add"><fa :icon="faPlus"/> {{ $t('createAntenna') }}</mk-button>
<mk-button @click="create" primary class="add"><fa :icon="faPlus"/> {{ $t('add') }}</mk-button>
<x-antenna v-if="draft" :antenna="draft" @created="onAntennaCreated" style="margin-bottom: var(--margin);"/>
<mk-pagination :pagination="pagination" #default="{items}" class="antennas" ref="list">
<x-antenna v-for="(antenna, i) in items" :key="antenna.id" :data-index="i" :antenna="antenna" @created="onAntennaDeleted"/>
<x-antenna v-for="(antenna, i) in items" :key="antenna.id" :antenna="antenna" @created="onAntennaDeleted"/>
</mk-pagination>
</div>
</template>

View File

@ -0,0 +1,214 @@
<template>
<div class="mk-group-page">
<portal to="icon"><fa :icon="faUsers"/></portal>
<portal to="title">{{ group.name }}</portal>
<transition name="zoom" mode="out-in">
<div v-if="group" class="_card">
<div class="_content">
<mk-button inline @click="renameGroup()">{{ $t('rename') }}</mk-button>
<mk-button inline @click="transfer()">{{ $t('transfer') }}</mk-button>
<mk-button inline @click="deleteGroup()">{{ $t('delete') }}</mk-button>
</div>
</div>
</transition>
<transition name="zoom" mode="out-in">
<div v-if="group" class="_card members">
<div class="_title">{{ $t('members') }}</div>
<div class="_content">
<div class="users">
<div class="user" v-for="user in users" :key="user.id">
<mk-avatar :user="user" class="avatar"/>
<div class="body">
<mk-user-name :user="user" class="name"/>
<mk-acct :user="user" class="acct"/>
</div>
<div class="action">
<button class="_button" @click="removeUser(user)"><fa :icon="faTimes"/></button>
</div>
</div>
</div>
</div>
<div class="_footer">
<mk-button inline @click="invite()">{{ $t('invite') }}</mk-button>
</div>
</div>
</transition>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faTimes, faUsers } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../i18n';
import Progress from '../../scripts/loading';
import MkButton from '../../components/ui/button.vue';
import MkUserSelect from '../../components/user-select.vue';
export default Vue.extend({
i18n,
metaInfo() {
return {
title: this.group ? `${this.group.name} | ${this.$t('manageGroups')}` : this.$t('manageGroups')
};
},
components: {
MkButton
},
data() {
return {
group: null,
users: [],
faTimes, faUsers
};
},
watch: {
$route: 'fetch'
},
created() {
this.fetch();
},
methods: {
fetch() {
Progress.start();
this.$root.api('users/groups/show', {
groupId: this.$route.params.group
}).then(group => {
this.group = group;
this.$root.api('users/show', {
userIds: this.group.userIds
}).then(users => {
this.users = users;
Progress.done();
});
});
},
invite() {
this.$root.new(MkUserSelect, {}).$once('selected', user => {
this.$root.api('users/groups/invite', {
groupId: this.group.id,
userId: user.id
}).then(() => {
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
}).catch(e => {
this.$root.dialog({
type: 'error',
text: e
});
});
});
},
removeUser(user) {
this.$root.api('users/groups/pull', {
groupId: this.group.id,
userId: user.id
}).then(() => {
this.users = this.users.filter(x => x.id !== user.id);
});
},
async renameGroup() {
const { canceled, result: name } = await this.$root.dialog({
title: this.$t('groupName'),
input: {
default: this.group.name
}
});
if (canceled) return;
await this.$root.api('users/groups/update', {
groupId: this.group.id,
name: name
});
this.group.name = name;
},
transfer() {
this.$root.new(MkUserSelect, {}).$once('selected', user => {
this.$root.api('users/groups/transfer', {
groupId: this.group.id,
userId: user.id
}).then(() => {
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
}).catch(e => {
this.$root.dialog({
type: 'error',
text: e
});
});
});
},
async deleteGroup() {
const { canceled } = await this.$root.dialog({
type: 'warning',
text: this.$t('removeAreYouSure', { x: this.group.name }),
showCancelButton: true
});
if (canceled) return;
await this.$root.api('users/groups/delete', {
groupId: this.group.id
});
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
this.$router.push('/my/groups');
}
}
});
</script>
<style lang="scss" scoped>
.mk-group-page {
> .members {
> ._content {
max-height: 400px;
overflow: auto;
> .users {
> .user {
display: flex;
align-items: center;
> .avatar {
width: 50px;
height: 50px;
}
> .body {
flex: 1;
padding: 8px;
> .name {
display: block;
font-weight: bold;
}
> .acct {
opacity: 0.5;
}
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,103 @@
<template>
<div class="">
<portal to="icon"><fa :icon="faUsers"/></portal>
<portal to="title">{{ $t('groups') }}</portal>
<mk-button @click="create" primary style="margin: 0 auto var(--margin) auto;"><fa :icon="faPlus"/> {{ $t('createGroup') }}</mk-button>
<mk-container :body-togglable="true">
<template #header><fa :icon="faUsers"/> {{ $t('ownedGroups') }}</template>
<mk-pagination :pagination="ownedPagination" #default="{items}" ref="owned">
<div class="_frame" v-for="group in items" :key="group.id">
<div class="_title"><router-link :to="`/my/groups/${ group.id }`" class="_link">{{ group.name }}</router-link></div>
<div class="_content"><mk-avatars :user-ids="group.userIds"/></div>
</div>
</mk-pagination>
</mk-container>
<mk-container :body-togglable="true">
<template #header><fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</template>
<mk-pagination :pagination="invitePagination" #default="{items}">
<div class="_frame" v-for="invite in items" :key="invite.id">
<div class="_title">{{ invite.group.name }}</div>
<div class="_content"><mk-avatars :user-ids="invite.group.userIds"/></div>
<div class="_footer">
<mk-button @click="acceptInvite(invite)" primary inline><fa :icon="faCheck"/> {{ $t('accept') }}</mk-button>
<mk-button @click="rejectInvite(invite)" primary inline><fa :icon="faBan"/> {{ $t('reject') }}</mk-button>
</div>
</div>
</mk-pagination>
</mk-container>
<mk-container :body-togglable="true">
<template #header><fa :icon="faUsers"/> {{ $t('joinedGroups') }}</template>
<mk-pagination :pagination="joinedPagination" #default="{items}">
<div class="_frame" v-for="group in items" :key="group.id">
<div class="_title">{{ group.name }}</div>
<div class="_content"><mk-avatars :user-ids="group.userIds"/></div>
</div>
</mk-pagination>
</mk-container>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faUsers, faPlus, faEnvelopeOpenText } from '@fortawesome/free-solid-svg-icons';
import MkPagination from '../../components/ui/pagination.vue';
import MkButton from '../../components/ui/button.vue';
import MkContainer from '../../components/ui/container.vue';
import MkAvatars from '../../components/avatars.vue';
export default Vue.extend({
metaInfo() {
return {
title: this.$t('groups') as string,
};
},
components: {
MkPagination,
MkButton,
MkContainer,
MkAvatars,
},
data() {
return {
ownedPagination: {
endpoint: 'users/groups/owned',
limit: 10,
},
joinedPagination: {
endpoint: 'users/groups/joined',
limit: 10,
},
invitePagination: {
endpoint: 'i/user-group-invites',
limit: 10,
},
faUsers, faPlus, faEnvelopeOpenText
};
},
methods: {
async create() {
const { canceled, result: name } = await this.$root.dialog({
title: this.$t('groupName'),
input: true
});
if (canceled) return;
await this.$root.api('users/groups/create', { name: name });
this.$refs.owned.reload();
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
},
}
});
</script>
<style lang="scss" scoped>
</style>

View File

@ -6,8 +6,8 @@
<mk-button @click="create" primary class="add"><fa :icon="faPlus"/> {{ $t('createList') }}</mk-button>
<mk-pagination :pagination="pagination" #default="{items}" class="lists" ref="list">
<div class="list _panel" v-for="(list, i) in items" :key="list.id" :data-index="i">
<router-link :to="`/lists/${ list.id }`">{{ list.name }}</router-link>
<div class="list _panel" v-for="(list, i) in items" :key="list.id">
<router-link :to="`/my/lists/${ list.id }`">{{ list.name }}</router-link>
</div>
</mk-pagination>
</div>
@ -62,7 +62,7 @@ export default Vue.extend({
<style lang="scss" scoped>
.qkcjvfiv {
> .add {
margin: 0 auto 16px auto;
margin: 0 auto var(--margin) auto;
}
> .lists {

View File

@ -1,11 +1,23 @@
<template>
<div class="mk-list-page">
<portal to="icon"><fa :icon="faListUl"/></portal>
<portal to="title">{{ list.name }}</portal>
<transition name="zoom" mode="out-in">
<div v-if="list" :key="list.id" class="_card list">
<div class="_title">{{ list.name }}</div>
<div v-if="list" class="_card">
<div class="_content">
<mk-button inline @click="renameList()">{{ $t('rename') }}</mk-button>
<mk-button inline @click="deleteList()">{{ $t('delete') }}</mk-button>
</div>
</div>
</transition>
<transition name="zoom" mode="out-in">
<div v-if="list" class="_card members">
<div class="_title">{{ $t('members') }}</div>
<div class="_content">
<div class="users">
<div class="user" v-for="(user, i) in users" :key="user.id" :data-index="i">
<div class="user" v-for="user in users" :key="user.id">
<mk-avatar :user="user" class="avatar"/>
<div class="body">
<mk-user-name :user="user" class="name"/>
@ -18,8 +30,7 @@
</div>
</div>
<div class="_footer">
<mk-button inline @click="renameList()">{{ $t('renameList') }}</mk-button>
<mk-button inline @click="deleteList()">{{ $t('deleteList') }}</mk-button>
<mk-button inline @click="addUser()">{{ $t('addUser') }}</mk-button>
</div>
</div>
</transition>
@ -28,10 +39,11 @@
<script lang="ts">
import Vue from 'vue';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { faTimes, faListUl } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../i18n';
import Progress from '../../scripts/loading';
import MkButton from '../../components/ui/button.vue';
import MkUserSelect from '../../components/user-select.vue';
export default Vue.extend({
i18n,
@ -50,7 +62,7 @@ export default Vue.extend({
return {
list: null,
users: [],
faTimes
faTimes, faListUl
};
},
@ -78,6 +90,26 @@ export default Vue.extend({
});
},
addUser() {
this.$root.new(MkUserSelect, {}).$once('selected', user => {
this.$root.api('users/lists/push', {
listId: this.list.id,
userId: user.id
}).then(() => {
this.users.push(user);
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
}).catch(e => {
this.$root.dialog({
type: 'error',
text: e
});
});
});
},
removeUser(user) {
this.$root.api('users/lists/pull', {
listId: this.list.id,
@ -107,7 +139,7 @@ export default Vue.extend({
async deleteList() {
const { canceled } = await this.$root.dialog({
type: 'warning',
text: this.$t('deleteListConfirm', { list: this.list.name }),
text: this.$t('removeAreYouSure', { x: this.list.name }),
showCancelButton: true
});
if (canceled) return;
@ -127,7 +159,7 @@ export default Vue.extend({
<style lang="scss" scoped>
.mk-list-page {
> .list {
> .members {
> ._content {
max-height: 400px;
overflow: auto;

View File

@ -0,0 +1,52 @@
<template>
<div class="ipledcug">
<portal to="icon"><fa :icon="faExclamationTriangle"/></portal>
<portal to="title">{{ $t('notFound') }}</portal>
<section class="_card">
<div class="_content">
<img src="https://xn--931a.moe/assets/not-found.jpg" alt=""/>
<div>{{ $t('notFoundDescription') }}</div>
</div>
</section>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
export default Vue.extend({
i18n,
metaInfo() {
return {
title: this.$t('notFound') as string
};
},
data() {
return {
faExclamationTriangle
}
},
});
</script>
<style lang="scss" scoped>
.ipledcug {
> ._card {
> ._content {
text-align: center;
> img {
vertical-align: bottom;
height: 150px;
margin-bottom: 16px;
border-radius: 16px;
}
}
}
}
</style>

View File

@ -4,8 +4,7 @@
<div class="_content">
<p v-if="!data && !$store.state.i.twoFactorEnabled"><mk-button @click="register">{{ $t('_2fa.registerDevice') }}</mk-button></p>
<template v-if="$store.state.i.twoFactorEnabled">
<h2 class="heading">{{ $t('totp-header') }}</h2>
<p>{{ $t('already-registered') }}</p>
<p>{{ $t('_2fa.alreadyRegistered') }}</p>
<mk-button @click="unregister">{{ $t('unregister') }}</mk-button>
<template v-if="supportsCredentials">
@ -24,7 +23,7 @@
<mk-switch v-model="usePasswordLessLogin" @change="updatePasswordLessLogin" v-if="$store.state.i.securityKeysList.length > 0">{{ $t('passwordLessLogin') }}</mk-switch>
<mk-info warn v-if="registration && registration.error">{{ $t('something-went-wrong') }} {{ registration.error }}</mk-info>
<mk-button v-if="!registration || registration.error" @click="addSecurityKey">{{ $t('register') }}</mk-button>
<mk-button v-if="!registration || registration.error" @click="addSecurityKey">{{ $t('_2fa.registerKey') }}</mk-button>
<ol v-if="registration && !registration.error">
<li v-if="registration.stage >= 0">
@ -47,8 +46,8 @@
<ol style="margin: 0; padding: 0 0 0 1em;">
<li>
<i18n path="_2fa.step1" tag="span">
<a href="https://authy.com/" rel="noopener" target="_blank" place="a" style="color: var(--link);">Authy</a>
<a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" place="b" style="color: var(--link);">Google Authenticator</a>
<a href="https://authy.com/" rel="noopener" target="_blank" place="a" class="_link">Authy</a>
<a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" place="b" class="_link">Google Authenticator</a>
</i18n>
</li>
<li>{{ $t('_2fa.step2') }}<br><img :src="data.qr"></li>

View File

@ -3,7 +3,7 @@
<div class="_title"><fa :icon="faCloud"/> {{ $t('drive') }}</div>
<div class="_content">
<mk-pagination :pagination="drivePagination" #default="{items}" class="drive" ref="drive">
<div class="file" v-for="(file, i) in items" :key="file.id" :data-index="i" @click="selected = file" :class="{ selected: selected && (selected.id === file.id) }">
<div class="file" v-for="(file, i) in items" :key="file.id" @click="selected = file" :class="{ selected: selected && (selected.id === file.id) }">
<x-file-thumbnail class="thumbnail" :file="file" fit="cover"/>
<div class="body">
<p class="name">

View File

@ -10,14 +10,22 @@
<mk-button primary :disabled="$store.state.settings.wallpaper == null" @click="delWallpaper()">{{ $t('removeWallpaper') }}</mk-button>
</div>
<div class="_content">
<mk-switch v-model="autoReload">
{{ $t('autoReloadWhenDisconnected') }}
</mk-switch>
<mk-switch v-model="$store.state.i.autoWatch" @change="onChangeAutoWatch">
{{ $t('auto-watch') }}<template #desc>{{ $t('auto-watch-desc') }}</template>
{{ $t('autoNoteWatch') }}<template #desc>{{ $t('auto-watch-desc') }}</template>
</mk-switch>
</div>
<div class="_content">
<mk-button @click="readAllNotifications">{{ $t('mark-as-read-all-notifications') }}</mk-button>
<mk-button @click="readAllUnreadNotes">{{ $t('mark-as-read-all-unread-notes') }}</mk-button>
<mk-button @click="readAllMessagingMessages">{{ $t('mark-as-read-all-talk-messages') }}</mk-button>
<mk-button @click="readAllNotifications">{{ $t('markAsReadAllNotifications') }}</mk-button>
<mk-button @click="readAllUnreadNotes">{{ $t('markAsReadAllUnreadNotes') }}</mk-button>
<mk-button @click="readAllMessagingMessages">{{ $t('markAsReadAllTalkMessages') }}</mk-button>
</div>
<div class="_content">
<mk-switch v-model="reduceAnimation">
{{ $t('reduceUiAnimation') }}
</mk-switch>
</div>
</section>
</template>
@ -52,6 +60,16 @@ export default Vue.extend({
get() { return this.$store.state.settings.wallpaper; },
set(value) { this.$store.dispatch('settings/set', { key: 'wallpaper', value }); }
},
autoReload: {
get() { return this.$store.state.device.autoReload; },
set(value) { this.$store.commit('device/set', { key: 'autoReload', value }); }
},
reduceAnimation: {
get() { return !this.$store.state.device.animation; },
set(value) { this.$store.commit('device/set', { key: 'animation', value: !value }); }
},
},
methods: {

View File

@ -6,7 +6,7 @@
<mk-pagination :pagination="mutingPagination" class="muting">
<template #empty><span>{{ $t('noUsers') }}</span></template>
<template #default="{items}">
<div class="user" v-for="(mute, i) in items" :key="mute.id" :data-index="i">
<div class="user" v-for="(mute, i) in items" :key="mute.id">
<router-link class="name" :to="mute.mutee | userPage">
<mk-acct :user="mute.mutee"/>
</router-link>
@ -19,7 +19,7 @@
<mk-pagination :pagination="blockingPagination" class="blocking">
<template #empty><span>{{ $t('noUsers') }}</span></template>
<template #default="{items}">
<div class="user" v-for="(block, i) in items" :key="block.id" :data-index="i">
<div class="user" v-for="(block, i) in items" :key="block.id">
<router-link class="name" :to="block.blockee | userPage">
<mk-acct :user="block.blockee"/>
</router-link>

View File

@ -0,0 +1,85 @@
<template>
<div class="">
<portal to="icon"><fa :icon="faShareAlt"/></portal>
<portal to="title">{{ $t('share') }}</portal>
<section class="_card">
<div class="_title" v-if="title">{{ title }}</div>
<div class="_content">
<div>{{ text }}</div>
<mk-button @click="post()" v-if="!posted">{{ $t('post') }}</mk-button>
<mk-button primary @click="close()" v-else>{{ $t('close') }}</mk-button>
</div>
<div class="_footer" v-if="url">{{ url }}</div>
</section>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faShareAlt } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
import PostFormDialog from '../components/post-form-dialog.vue';
import MkButton from '../components/ui/button.vue';
export default Vue.extend({
i18n,
metaInfo() {
return {
title: this.$t('share') as string
};
},
components: {
MkButton
},
data() {
return {
title: null,
text: null,
url: null,
posted: false,
faShareAlt
}
},
created() {
const urlParams = new URLSearchParams(window.location.search);
this.title = urlParams.get('title');
this.text = urlParams.get('text');
this.url = urlParams.get('url');
},
mounted() {
this.post();
},
methods: {
post() {
let text = '';
if (this.title) text += `${this.title}\n`;
if (this.text) text += `${this.text}\n`;
if (this.url) text += `${this.url}`;
this.$root.new(PostFormDialog, {
instant: true,
initialText: text.trim()
}).$once('posted', () => {
this.posted = true;
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
});
},
close() {
window.close()
}
}
});
</script>
<style lang="scss" scoped>
</style>

View File

@ -1,6 +1,6 @@
<template>
<mk-pagination :pagination="pagination" #default="{items}" class="mk-following-or-followers" ref="list">
<div class="user _panel" v-for="(user, i) in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" :data-index="i">
<div class="user _panel" v-for="(user, i) in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id">
<mk-avatar class="avatar" :user="user"/>
<div class="body">
<div class="name">

View File

@ -4,7 +4,7 @@
<portal to="avatar" v-if="user"><mk-avatar class="avatar" :user="user" :disable-preview="true"/></portal>
<div class="remote-caution _panel" v-if="user.host != null"><fa :icon="faExclamationTriangle" style="margin-right: 8px;"/>{{ $t('remoteUserCaution') }}<a :href="user.url" rel="nofollow noopener" target="_blank">{{ $t('showOnRemote') }}</a></div>
<transition name="zoom" mode="out-in" appear>
<transition :name="$store.state.device.animation ? 'zoom' : ''" mode="out-in" appear>
<div class="profile _panel" :key="user.id">
<div class="banner-container" :style="style">
<div class="banner" ref="banner" :style="style"></div>
@ -83,7 +83,7 @@
<router-view :user="user"></router-view>
<template v-if="$route.name == 'user'">
<sequential-entrance class="pins">
<x-note v-for="(note, i) in user.pinnedNotes" class="note" :note="note" :key="note.id" :data-index="i" :detail="true" :pinned="true"/>
<x-note v-for="(note, i) in user.pinnedNotes" class="note" :note="note" :key="note.id" :detail="true" :pinned="true"/>
</sequential-entrance>
<mk-container :body-togglable="true" class="content">
<template #header><fa :icon="faImage"/>{{ $t('images') }}</template>
@ -269,10 +269,11 @@ export default Vue.extend({
position: absolute;
top: 12px;
left: 12px;
padding: 4px 6px;
padding: 4px 8px;
color: #fff;
background: rgba(0, 0, 0, 0.7);
font-size: 12px;
font-size: 0.7em;
border-radius: 6px;
}
> .actions {

View File

@ -6,6 +6,8 @@ Vue.use(VueRouter);
const page = (path: string) => () => import(`./pages/${path}.vue`).then(m => m.default);
let indexScrollPos = 0;
export const router = new VueRouter({
mode: 'history',
routes: [
@ -19,6 +21,8 @@ export const router = new VueRouter({
{ path: '/announcements', component: page('announcements') },
{ path: '/about', component: page('about') },
{ path: '/featured', component: page('featured') },
{ path: '/docs', component: page('docs') },
{ path: '/docs/:doc', component: page('doc'), props: true },
{ path: '/explore', component: page('explore') },
{ path: '/explore/tags/:tag', props: true, component: page('explore') },
{ path: '/search', component: page('search') },
@ -36,6 +40,8 @@ export const router = new VueRouter({
{ path: '/my/follow-requests', component: page('follow-requests') },
{ path: '/my/lists', component: page('my-lists/index') },
{ path: '/my/lists/:list', component: page('my-lists/list') },
{ path: '/my/groups', component: page('my-groups/index') },
{ path: '/my/groups/:group', component: page('my-groups/group') },
{ path: '/my/antennas', component: page('my-antennas/index') },
{ path: '/instance', component: page('instance/index') },
{ path: '/instance/emojis', component: page('instance/emojis') },
@ -50,19 +56,25 @@ export const router = new VueRouter({
{ path: '/tags/:tag', component: page('tag') },
{ path: '/auth/:token', component: page('auth') },
{ path: '/authorize-follow', component: page('follow') },
/*{ path: '*', component: MkNotFound }*/
{ path: '/share', component: page('share') },
{ path: '*', component: page('not-found') }
],
// なんかHacky
// 通常の使い方をすると scroll メソッドの behavior を設定できないため、自前で window.scroll するようにする
// setTimeout しないと、アニメーション(トランジション)の関係でうまく動かない
scrollBehavior(to, from, savedPosition) {
setTimeout(() => {
if (savedPosition) {
window.scroll({ top: savedPosition.y, behavior: 'instant' });
scrollBehavior(to) {
window._scroll = () => { // さらにHacky
if (to.name === 'index') {
window.scroll({ top: indexScrollPos, behavior: 'instant' });
} else {
window.scroll({ top: 0, behavior: 'instant' });
}
}, 600);
return;
};
}
});
router.afterEach((to, from) => {
if (from.name === 'index') {
indexScrollPos = window.scrollY;
}
});

View File

@ -22,12 +22,14 @@ const defaultDeviceSettings = {
loadRawImages: false,
alwaysShowNsfw: false,
useOsDefaultEmojis: false,
autoReload: false,
accounts: [],
recentEmojis: [],
visibility: 'public',
localOnly: false,
themes: [],
theme: 'light',
animation: true,
};
export default (os: MiOS) => new Vuex.Store({

View File

@ -301,6 +301,37 @@ a {
}
}
._frame {
position: relative;
border: solid 1px var(--divider);
border-radius: var(--radius);
margin: var(--margin);
> ._title {
margin: 0;
padding: 16px;
border-bottom: solid 1px var(--divider);
font-weight: bold;
}
> ._content {
padding: 16px;
& + ._content {
border-top: solid 1px var(--divider);
}
}
> ._footer {
border-top: solid 1px var(--divider);
padding: 16px;
}
}
._link {
color: var(--link);
}
.zoom-enter-active, .zoom-leave-active {
transition: opacity 0.5s, transform 0.5s !important;
}
@ -339,3 +370,22 @@ a {
transform: rotate(360deg);
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes jump {
0% { transform: translateY(0); }
25% { transform: translateY(-16px); }
50% { transform: translateY(0); }
75% { transform: translateY(-8px); }
100% { transform: translateY(0); }
}
@keyframes blink {
0% { opacity: 1; }
30% { opacity: 1; }
90% { opacity: 0; }
}

View File

@ -18,7 +18,7 @@ self.addEventListener('install', ev => {
caches.open(cacheName)
.then(cache => {
return cache.addAll([
'/assets/error.jpg'
'/'
]);
})
.then(() => self.skipWaiting())

View File

@ -1,3 +0,0 @@
# About Misskey
Misskey is a mini blog SNS.

View File

@ -1,3 +0,0 @@
# Misskeyについて
MisskeyはミニブログSNSです。

View File

@ -1,9 +0,0 @@
extends ./base
block main
!= html
block footer
p
= i18n('docs.edit-this-page-on-github')
a(href=src rel="noopener" target="_blank")= i18n('docs.edit-this-page-on-github-link')

View File

@ -1,50 +0,0 @@
doctype html
html(lang= lang)
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no")
title
| #{title} | Misskey Docs
link(rel="stylesheet" href="/docs/assets/style.css")
link(rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css")
script(src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js")
link(rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous")
block meta
body
nav
ul
each doc in docs
li: a(href=`/docs/${lang}/${doc.name}`)= doc.title[lang] || doc.title['ja-JP']
main
article
block main
if content
| !{content}
aside.
<div id="disqus_thread"></div>
<script>
/**
* RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
* LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables*/
/*
var disqus_config = function () {
this.page.url = PAGE_URL; // Replace PAGE_URL with your page's canonical URL variable
this.page.identifier = "#{ id }"; // Replace PAGE_IDENTIFIER with your page's unique identifier variable
};
*/
(function() { // DON'T EDIT BELOW THIS LINE
var d = document, s = d.createElement('script');
s.src = 'https://misskey.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
footer
block footer
small= copyright

View File

@ -10,9 +10,6 @@
<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">A</kbd>, <kbd class="key">M</kbd></td><td>アカウントメニューを表示/隠す</td><td><b>A</b>ccount, <b>M</b>y, <b>M</b>e, <b>M</b>enu</td></tr>
<tr><td><kbd class="key">D</kbd></td><td>ダークモード切り替え</td><td><b>D</b>ark</td></tr>
<tr><td><kbd class="key">Z</kbd></td><td>上部のバーを隠す</td><td><b>Z</b>en</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>
@ -62,51 +59,7 @@
<tr><td><kbd class="key"></kbd>, <kbd class="key">H</kbd>, <kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">Tab</kbd></kbd></td><td>左のリアクションにフォーカスを移動</td><td>-</td></tr>
<tr><td><kbd class="key"></kbd>, <kbd class="key">L</kbd>, <kbd class="key">Tab</kbd></td><td>右のリアクションにフォーカスを移動</td><td>-</td></tr>
<tr><td><kbd class="key">Enter</kbd>, <kbd class="key">Space</kbd>, <kbd class="key">+</kbd></td><td>リアクション確定</td><td>-</td></tr>
<tr><td><kbd class="key">0</kbd>~<kbd class="key">9</kbd></td><td>数字に対応したリアクションで確定(対応については後述)</td><td>-</td></tr>
<tr><td><kbd class="key">0</kbd>~<kbd class="key">9</kbd></td><td>数字に対応したリアクションで確定</td><td>-</td></tr>
<tr><td><kbd class="key">Esc</kbd></td><td>リアクションするのをやめる</td><td>-</td></tr>
</tbody>
</table>
## リアクションと数字キーの対応
<table>
<thead>
<tr><th>数字キー</th><th>リアクション</th></tr>
</thead>
<tbody>
<tr><td><kbd class="key">1</kbd></td><td>👍</td></tr>
<tr><td><kbd class="key">2</kbd></td><td>❤️</td></tr>
<tr><td><kbd class="key">3</kbd></td><td>😆</td></tr>
<tr><td><kbd class="key">4</kbd></td><td>🤔</td></tr>
<tr><td><kbd class="key">5</kbd></td><td>😮</td></tr>
<tr><td><kbd class="key">6</kbd></td><td>🎉</td></tr>
<tr><td><kbd class="key">7</kbd></td><td>💢</td></tr>
<tr><td><kbd class="key">8</kbd></td><td>😥</td></tr>
<tr><td><kbd class="key">9</kbd></td><td>😇</td></tr>
<tr><td><kbd class="key">0</kbd></td><td>🍮 or 🍣</td></tr>
</tbody>
</table>
## デッキ
<table>
<thead>
<tr><th>ショートカット</th><th>効果</th><th>由来</th></tr>
</thead>
<tbody>
<tr><td>投稿にフォーカスした状態で<kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key"></kbd></kbd></td><td>上のカラムにフォーカス</td><td>-</td></tr>
<tr><td>投稿にフォーカスした状態で<kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key"></kbd></kbd></td><td>下のカラムにフォーカス</td><td>-</td></tr>
<tr><td>投稿にフォーカスした状態で<kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key"></kbd></kbd></td><td>右のカラムにフォーカス</td><td>-</td></tr>
<tr><td>投稿にフォーカスした状態で<kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key"></kbd></kbd></td><td>左のカラムにフォーカス</td><td>-</td></tr>
</tbody>
</table>
# 例
<table>
<thead>
<tr><th>ショートカット</th><th>動作</th></tr>
</thead>
<tbody>
<tr><td><kbd class="key">t</kbd><kbd class="key">+</kbd><kbd class="key">+</kbd></td><td>タイムラインの最新の投稿に👍する</td></tr>
<tr><td><kbd class="key">t</kbd><kbd class="key">1</kbd></td><td>タイムラインの最新の投稿に👍する</td></tr>
<tr><td><kbd class="key">t</kbd><kbd class="key">0</kbd></td><td>タイムラインの最新の投稿に🍮する</td></tr>
</tbody>
</table>

View File

@ -1,156 +0,0 @@
@import "../client/style"
@import "./ui"
html
--accent #fb4e4e
--link #fb4e4e
--linkTapHighlight #fb4e4eb3
body
margin 0
color #34495e
word-break break-word
main
margin 0 0 0 330px
padding 64px
width 850px
max-width calc(100% - 330px)
h1
margin 0 0 24px 0
padding 16px 0
font-size 1.5em
border-bottom solid 2px #eee
h2
margin 1em 0 24px 0
padding 0 0 16px 0
font-size 1.4em
border-bottom solid 1px #eee
h3
margin 1em 0 0 0
padding 0
font-size 1.25em
h4
margin 1em 0 0 0
p
margin 1em 0
line-height 1.6em
hr
border none
border-bottom solid 2px #eee
> aside
margin-top 32px
padding-top 32px
border-top solid 2px #eee
> footer
margin 32px 0 0 0
border-top solid 2px #eee
> small
display block
margin 16px 0 0 0
color #aaa
nav
display block
position fixed
z-index 10000
top 0
left 0
width 330px
height 100%
overflow auto
padding 32px
background #fff
border-right solid 2px #eee
ul
padding 0
margin 0
@media (max-width 1025px)
main
margin 0
max-width 100%
nav
position relative
width 100%
max-height 128px
background #f9f9f9
border-right none
@media (max-width 768px)
main
padding 32px
@media (max-width 512px)
main
padding 16px
table
width 100%
max-width 100%
overflow auto
border-spacing 0
border-collapse collapse
thead
font-weight bold
border-bottom solid 2px #eee
tr
th
text-align left
tbody
tr
&:nth-child(odd)
background #fbfbfb
th, td
padding 8px 16px
min-width 128px
code
padding 4px 8px
font-family Consolas, 'Courier New', Courier, Monaco, monospace
//color #295c92
background #f2f2f2
border-radius 4px
pre
overflow auto
> code
display block
padding 16px
kbd.group
display inline-block
padding 4px
background #fbfbfb
border 1px solid #d6d6d6
border-radius 4px
box-shadow 0 1px 1px rgba(0, 0, 0, 0.1)
kbd.key
display inline-block
padding 6px 8px
background #fff
border solid 1px #cecece
border-radius 4px
box-shadow 0 1px 1px rgba(0, 0, 0, 0.1)
td
> kbd.group,
> kbd.key
margin 4px

View File

@ -1,19 +0,0 @@
.ui.info
display block
margin 1em 0
padding 0 1em
font-size 90%
color rgba(#000, 0.87)
background #f8f8f9
border-radius 4px
overflow hidden
> p
opacity 0.8
> [data-icon]:first-child
margin-right 0.25em
&.warn
color #573a08
background #FFFAF3

View File

@ -34,6 +34,7 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
break;
case 'Like':
case 'EmojiReaction':
case 'EmojiReact':
undoLike(actor, object as ILike);
break;
case 'Announce':

View File

@ -15,7 +15,7 @@ import { apLogger } from '../logger';
import { DriveFile } from '../../../models/entities/drive-file';
import { deliverQuestionUpdate } from '../../../services/note/polls/update';
import { extractDbHost, toPuny } from '../../../misc/convert-host';
import { Notes, Emojis, Polls } from '../../../models';
import { Notes, Emojis, Polls, MessagingMessages } from '../../../models';
import { Note } from '../../../models/entities/note';
import { IObject, getOneApId, getApId, validPost, ICreate, isCreate, IPost } from '../type';
import { Emoji } from '../../../models/entities/emoji';
@ -129,6 +129,8 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
}
}
let isTalk = note._misskey_talk && visibility === 'specified';
const apHashtags = await extractHashtags(note.tag);
// 添付ファイル
@ -153,7 +155,18 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
} else {
return x;
}
}).catch(e => {
}).catch(async e => {
// トークだったらinReplyToのエラーは無視
const uri = getApId(note.inReplyTo);
if (uri.startsWith(config.url + '/')) {
const id = uri.split('/').pop();
const talk = await MessagingMessages.findOne(id);
if (talk) {
isTalk = true;
return null;
}
}
logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${e.statusCode || e}`);
throw e;
})
@ -250,7 +263,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
if (actor.uri) updatePerson(actor.uri);
}
if (note._misskey_talk && visibility === 'specified') {
if (isTalk) {
for (const recipient of visibleUsers) {
await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null, object.id);
return null;

View File

@ -1,10 +1,12 @@
import config from '../../../config';
import { ILocalUser } from '../../../models/entities/user';
import { NoteReaction } from '../../../models/entities/note-reaction';
import { Note } from '../../../models/entities/note';
export default (user: ILocalUser, note: Note, reaction: string) => ({
export const renderLike = (noteReaction: NoteReaction, note: Note) => ({
type: 'Like',
actor: `${config.url}/users/${user.id}`,
object: note.uri ? note.uri : `${config.url}/notes/${note.id}`,
_misskey_reaction: reaction
id: `${config.url}/likes/${noteReaction.id}`,
actor: `${config.url}/users/${noteReaction.userId}`,
object: note.uri ? note.uri : `${config.url}/notes/${noteReaction.noteId}`,
content: noteReaction.reaction,
_misskey_reaction: noteReaction.reaction
});

View File

@ -171,7 +171,7 @@ export interface IRemove extends IActivity {
}
export interface ILike extends IActivity {
type: 'Like' | 'EmojiReaction';
type: 'Like' | 'EmojiReaction' | 'EmojiReact';
_misskey_reaction?: string;
}
@ -193,6 +193,6 @@ export const isAccept = (object: IObject): object is IAccept => object.type ===
export const isReject = (object: IObject): object is IReject => object.type === 'Reject';
export const isAdd = (object: IObject): object is IAdd => object.type === 'Add';
export const isRemove = (object: IObject): object is IRemove => object.type === 'Remove';
export const isLike = (object: IObject): object is ILike => object.type === 'Like' || object.type === 'EmojiReaction';
export const isLike = (object: IObject): object is ILike => object.type === 'Like' || object.type === 'EmojiReaction' || object.type === 'EmojiReact';
export const isAnnounce = (object: IObject): object is IAnnounce => object.type === 'Announce';
export const isBlock = (object: IObject): object is IBlock => object.type === 'Block';

View File

@ -13,10 +13,11 @@ import Following from './activitypub/following';
import Featured from './activitypub/featured';
import { inbox as processInbox } from '../queue';
import { isSelfHost } from '../misc/convert-host';
import { Notes, Users, Emojis, UserKeypairs } from '../models';
import { Notes, Users, Emojis, UserKeypairs, NoteReactions } from '../models';
import { ILocalUser, User } from '../models/entities/user';
import { In } from 'typeorm';
import { ensure } from '../prelude/ensure';
import { renderLike } from '../remote/activitypub/renderer/like';
// Init router
const router = new Router();
@ -202,4 +203,25 @@ router.get('/emojis/:emoji', async ctx => {
setResponseType(ctx);
});
// like
router.get('/likes/:like', async ctx => {
const reaction = await NoteReactions.findOne(ctx.params.like);
if (reaction == null) {
ctx.status = 404;
return;
}
const note = await Notes.findOne(reaction.noteId);
if (note == null) {
ctx.status = 404;
return;
}
ctx.body = renderActivity(await renderLike(reaction, note));
ctx.set('Cache-Control', 'public, max-age=180');
setResponseType(ctx);
});
export default router;

View File

@ -1,106 +0,0 @@
/**
* Docs
*/
import * as fs from 'fs';
import * as path from 'path';
import * as showdown from 'showdown';
import 'showdown-highlightjs-extension';
import ms = require('ms');
import * as Router from '@koa/router';
import * as send from 'koa-send';
import * as glob from 'glob';
import config from '../../config';
import { licenseHtml } from '../../misc/license';
import * as locales from '../../../locales';
import * as nestedProperty from 'nested-property';
function getLang(lang: string): string {
if (['en-US', 'ja-JP'].includes(lang)) {
return lang;
} else {
return 'en-US';
}
}
async function genVars(lang: string): Promise<{ [key: string]: any }> {
const vars = {} as { [key: string]: any };
vars['lang'] = lang;
const cwd = path.resolve(__dirname + '/../../../') + '/';
const docs = glob.sync(`src/docs/**/*.${lang}.md`, { cwd });
vars['docs'] = {};
for (const x of docs) {
const [, name] = x.match(/docs\/(.+?)\.(.+?)\.md$/)!;
if (vars['docs'][name] == null) {
vars['docs'][name] = {
name,
title: {}
};
}
vars['docs'][name]['title'][lang] = fs.readFileSync(cwd + x, 'utf-8').match(/^# (.+?)\r?\n/)![1];
}
vars['kebab'] = (string: string) => string.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase();
vars['config'] = config;
vars['copyright'] = '(c) Misskey';
vars['license'] = licenseHtml;
vars['i18n'] = (key: string) => nestedProperty.get(locales[lang], key);
return vars;
}
const router = new Router();
router.get('/assets/*', async ctx => {
await send(ctx as any, ctx.params[0], {
root: `${__dirname}/../../docs/assets/`,
maxage: ms('1 days')
});
});
router.get('/*/*', async ctx => {
const lang = getLang(ctx.params[0]);
const doc = ctx.params[1];
showdown.extension('urlExtension', () => ({
type: 'output',
regex: /%URL%/g,
replace: config.url
}));
showdown.extension('wsUrlExtension', () => ({
type: 'output',
regex: /%WS_URL%/g,
replace: config.wsUrl
}));
showdown.extension('apiUrlExtension', () => ({
type: 'output',
regex: /%API_URL%/g,
replace: config.apiUrl
}));
const conv = new showdown.Converter({
tables: true,
extensions: ['urlExtension', 'apiUrlExtension', 'highlightjs']
});
const md = fs.readFileSync(`${__dirname}/../../../src/docs/${doc}.${lang}.md`, 'utf8');
await ctx.render('../../../../src/docs/article', Object.assign({
id: doc,
html: conv.makeHtml(md),
title: md.match(/^# (.+?)\r?\n/)![1],
src: `https://github.com/syuilo/misskey/tree/master/src/docs/${doc}.${lang}.md`
}, await genVars(lang)));
ctx.set('Cache-Control', 'public, max-age=300');
});
export default router;

View File

@ -3,14 +3,16 @@
*/
import * as os from 'os';
import * as fs from 'fs';
import ms = require('ms');
import * as Koa from 'koa';
import * as Router from '@koa/router';
import * as send from 'koa-send';
import * as favicon from 'koa-favicon';
import * as views from 'koa-views';
import * as glob from 'glob';
import * as MarkdownIt from 'markdown-it';
import docs from './docs';
import packFeed from './feed';
import { fetchMeta } from '../../misc/fetch-meta';
import { genOpenapiSpec } from '../api/openapi/gen-spec';
@ -21,6 +23,11 @@ import getNoteSummary from '../../misc/get-note-summary';
import { ensure } from '../../prelude/ensure';
import { getConnection } from 'typeorm';
import redis from '../../db/redis';
import locales = require('../../../locales');
const markdown = MarkdownIt({
html: true
});
const client = `${__dirname}/../../client/`;
@ -84,7 +91,6 @@ router.get('/robots.txt', async ctx => {
//#endregion
// Docs
router.use('/docs', docs.routes());
router.get('/api-doc', async ctx => {
await send(ctx as any, '/assets/redoc.html', {
root: client
@ -98,6 +104,43 @@ router.get('/api.json', async ctx => {
ctx.body = genOpenapiSpec();
});
router.get('/docs.json', async ctx => {
const lang = ctx.query.lang;
if (!Object.keys(locales).includes(lang)) {
ctx.body = [];
return;
}
const paths = glob.sync(__dirname + `/../../../src/docs/*.${lang}.md`);
const docs: { path: string; title: string; }[] = [];
for (const path of paths) {
const md = fs.readFileSync(path, { encoding: 'utf8' });
const parsed = markdown.parse(md, {});
if (parsed.length === 0) return;
const buf = [...parsed];
const headingTokens = [];
// もっとも上にある見出しを抽出する
while (buf[0].type !== 'heading_open') {
buf.shift();
}
buf.shift();
while (buf[0].type as string !== 'heading_close') {
const token = buf.shift();
if (token) {
headingTokens.push(token);
}
}
docs.push({
path: path.split('/').pop()!.split('.')[0],
title: markdown.renderer.render(headingTokens, {}, {})
});
}
ctx.body = docs;
});
const getFeed = async (acct: string) => {
const { username, host } = parseAcct(acct);
const user = await Users.findOne({

View File

@ -1,6 +1,6 @@
import { publishNoteStream } from '../../stream';
import watch from '../watch';
import renderLike from '../../../remote/activitypub/renderer/like';
import { renderLike } from '../../../remote/activitypub/renderer/like';
import DeliverManager from '../../../remote/activitypub/deliver-manager';
import { renderActivity } from '../../../remote/activitypub/renderer';
import { IdentifiableError } from '../../../misc/identifiable-error';
@ -38,7 +38,7 @@ export default async (user: User, note: Note, reaction?: string) => {
}
// Create reaction
await NoteReactions.save({
const inserted = await NoteReactions.save({
id: genId(),
createdAt: new Date(),
noteId: note.id,
@ -94,7 +94,7 @@ export default async (user: User, note: Note, reaction?: string) => {
//#region 配信
if (Users.isLocalUser(user) && !note.localOnly) {
const content = renderActivity(renderLike(user, note, reaction));
const content = renderActivity(renderLike(inserted, note));
const dm = new DeliverManager(user, content);
if (note.userHost !== null) {
const reactee = await Users.findOne(note.userId)

View File

@ -1,5 +1,5 @@
import { publishNoteStream } from '../../stream';
import renderLike from '../../../remote/activitypub/renderer/like';
import { renderLike } from '../../../remote/activitypub/renderer/like';
import renderUndo from '../../../remote/activitypub/renderer/undo';
import { renderActivity } from '../../../remote/activitypub/renderer';
import DeliverManager from '../../../remote/activitypub/deliver-manager';
@ -40,7 +40,7 @@ export default async (user: User, note: Note) => {
//#region 配信
if (Users.isLocalUser(user) && !note.localOnly) {
const content = renderActivity(renderUndo(renderLike(user, note, exist.reaction), user));
const content = renderActivity(renderUndo(renderLike(exist, note), user));
const dm = new DeliverManager(user, content);
if (note.userHost !== null) {
const reactee = await Users.findOne(note.userId)

148
yarn.lock
View File

@ -285,7 +285,7 @@
"@types/glob" "*"
"@types/node" "*"
"@types/glob@*":
"@types/glob@*", "@types/glob@7.1.1":
version "7.1.1"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
@ -468,11 +468,23 @@
dependencies:
"@types/koa" "*"
"@types/linkify-it@*":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-2.1.0.tgz#ea3dd64c4805597311790b61e872cbd1ed2cd806"
integrity sha512-Q7DYAOi9O/+cLLhdaSvKdaumWyHbm7HAk/bFwwyTuU0arR5yyCeW5GOoqt4tJTpDRxhpx9Q8kQL6vMpuw9hDSw==
"@types/lolex@5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@types/lolex/-/lolex-5.1.0.tgz#11b4c4756c007306d0feeaf2f08f88350c635d2b"
integrity sha512-hCQ2dOEQUw1LwofdIpMMGGqENd5p5ANzvcTe1nXTjcQL84r7tcLXFJlBgi0Ggz0f7BLmE2epf0C5Q07iq2gV0g==
"@types/markdown-it@0.0.9":
version "0.0.9"
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.9.tgz#a5d552f95216c478e0a27a5acc1b28dcffd989ce"
integrity sha512-IFSepyZXbF4dgSvsk8EsgaQ/8Msv1I5eTL0BZ0X3iGO9jw6tCVtPG8HchIPm3wrkmGdqZOD42kE0zplVi1gYDA==
dependencies:
"@types/linkify-it" "*"
"@types/mime@*":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
@ -2966,7 +2978,7 @@ debug@3.1.0, debug@~3.1.0:
dependencies:
ms "2.0.0"
debug@3.2.6, debug@3.X, debug@^3.1.0, debug@^3.2.6:
debug@3.2.6, debug@3.X, debug@^3.1.0:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@ -3128,7 +3140,7 @@ detect-indent@^5.0.0:
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
detect-libc@^1.0.2, detect-libc@^1.0.3:
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
@ -3389,7 +3401,7 @@ entities@^1.1.1, entities@~1.1.1:
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
entities@^2.0.0:
entities@^2.0.0, entities@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
@ -4054,13 +4066,6 @@ fs-constants@^1.0.0:
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs-minipass@^1.2.5:
version "1.2.7"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==
dependencies:
minipass "^2.6.0"
fs-minipass@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
@ -4241,7 +4246,7 @@ glob@7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
glob@7.1.6, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@ -4767,7 +4772,7 @@ humanize-number@0.0.2:
resolved "https://registry.yarnpkg.com/humanize-number/-/humanize-number-0.0.2.tgz#11c0af6a471643633588588048f1799541489c18"
integrity sha1-EcCvakcWQ2M1iFiASPF5lUFInBg=
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@ -4791,13 +4796,6 @@ iferr@^0.1.5:
resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
ignore-walk@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"
integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==
dependencies:
minimatch "^3.0.4"
ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
@ -5800,6 +5798,13 @@ liftoff@^3.1.0:
rechoir "^0.6.2"
resolve "^1.1.7"
linkify-it@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf"
integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==
dependencies:
uc.micro "^1.0.1"
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@ -6058,6 +6063,17 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
markdown-it@10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc"
integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==
dependencies:
argparse "^1.0.7"
entities "~2.0.0"
linkify-it "^2.0.0"
mdurl "^1.0.1"
uc.micro "^1.0.5"
matchdep@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e"
@ -6087,6 +6103,11 @@ mdn-data@2.0.4:
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
mdurl@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@ -6258,14 +6279,6 @@ minipass-pipeline@^1.2.2:
dependencies:
minipass "^3.0.0"
minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6"
integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==
dependencies:
safe-buffer "^5.1.2"
yallist "^3.0.0"
minipass@^3.0.0, minipass@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5"
@ -6273,13 +6286,6 @@ minipass@^3.0.0, minipass@^3.1.1:
dependencies:
yallist "^4.0.0"
minizlib@^1.2.1:
version "1.3.3"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==
dependencies:
minipass "^2.9.0"
minizlib@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3"
@ -6489,15 +6495,6 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
needle@^2.2.1:
version "2.4.0"
resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==
dependencies:
debug "^3.2.6"
iconv-lite "^0.4.4"
sax "^1.2.4"
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@ -6597,22 +6594,6 @@ node-object-hash@^1.2.0:
resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-1.4.2.tgz#385833d85b229902b75826224f6077be969a9e94"
integrity sha512-UdS4swXs85fCGWWf6t6DMGgpN/vnlKeSGEQ7hJcrs7PBFoxoKLmibc3QRb7fwiYsjdL7PX8iI/TMSlZ90dgHhQ==
node-pre-gyp@*:
version "0.14.0"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83"
integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==
dependencies:
detect-libc "^1.0.2"
mkdirp "^0.5.1"
needle "^2.2.1"
nopt "^4.0.1"
npm-packlist "^1.1.6"
npmlog "^4.0.2"
rc "^1.2.7"
rimraf "^2.6.1"
semver "^5.3.0"
tar "^4.4.2"
node-releases@^1.1.47:
version "1.1.47"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.47.tgz#c59ef739a1fd7ecbd9f0b7cf5b7871e8a8b591e4"
@ -6635,7 +6616,7 @@ noop-logger@^0.1.1:
resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2"
integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=
nopt@^4.0.1, nopt@~4.0.1:
nopt@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=
@ -6677,27 +6658,6 @@ now-and-later@^2.0.0:
dependencies:
once "^1.3.2"
npm-bundled@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b"
integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==
dependencies:
npm-normalize-package-bin "^1.0.1"
npm-normalize-package-bin@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2"
integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==
npm-packlist@^1.1.6:
version "1.4.8"
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
dependencies:
ignore-walk "^3.0.1"
npm-bundled "^1.0.1"
npm-normalize-package-bin "^1.0.1"
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@ -6712,7 +6672,7 @@ npm-run-path@^3.0.0:
dependencies:
path-key "^3.0.0"
npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2:
npmlog@^4.0.1, npmlog@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@ -8566,7 +8526,7 @@ rimraf@3.0.1:
dependencies:
glob "^7.1.3"
rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1:
rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@ -9498,19 +9458,6 @@ tar-stream@^2.0.0:
inherits "^2.0.3"
readable-stream "^3.1.1"
tar@^4.4.2:
version "4.4.13"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==
dependencies:
chownr "^1.1.1"
fs-minipass "^1.2.5"
minipass "^2.8.6"
minizlib "^1.2.1"
mkdirp "^0.5.0"
safe-buffer "^5.1.2"
yallist "^3.0.3"
tar@^5.0.5:
version "5.0.5"
resolved "https://registry.yarnpkg.com/tar/-/tar-5.0.5.tgz#03fcdb7105bc8ea3ce6c86642b9c942495b04f93"
@ -9951,6 +9898,11 @@ typescript@3.7.5:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
uglify-js@^2.6.1:
version "2.8.29"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
@ -10778,7 +10730,7 @@ yallist@^2.1.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==