Compare commits

...

81 Commits

Author SHA1 Message Date
90a9cf376e 12.5.0 2020-02-09 22:44:07 +09:00
16d6c55407 🎨 2020-02-09 22:25:36 +09:00
f3508d15a3 Refactor 2020-02-09 22:25:32 +09:00
0add490097 Update ja-JP.yml 2020-02-09 22:05:56 +09:00
2d2d1bd58d Refactor 2020-02-09 22:00:45 +09:00
7813c8a942 Fix #5896 2020-02-09 22:00:38 +09:00
ac5453232f お知らせの固定表示 (#5887)
* お知らせの固定

* ✌️

* Update index.home.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2020-02-09 21:31:17 +09:00
e78e5274d3 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-09 20:53:08 +09:00
982520bcef fix typo #5890 2020-02-09 20:53:00 +09:00
7abd91f031 いくつかのスタイルを調整 (#5894)
* Chrome(Android)で誕生日欄が崩れていたのを修正

* 入力欄のフォントを親要素から継承するように変更
2020-02-09 19:59:28 +09:00
b93bfb7e5c 🎨 2020-02-09 19:40:15 +09:00
2b20c34c1e Add search shortcut 2020-02-09 19:34:26 +09:00
0f63acea5b Update messaging-room.vue 2020-02-09 19:31:23 +09:00
e600fb7096 Fix #5888 2020-02-09 19:29:49 +09:00
b63fc71865 i18n 2020-02-09 19:18:06 +09:00
23e7650983 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-09 18:53:31 +09:00
01b5ccfdc6 Fix #5891 2020-02-09 18:52:53 +09:00
cc72f91465 Fix cannot update (#5890) 2020-02-09 12:47:50 +09:00
45cb5ff4ef 12.4.1 2020-02-09 03:56:39 +09:00
390279a4a8 Fix #5885 2020-02-09 03:49:18 +09:00
851dececab 非ログイン時に検索欄がズレていたのを修正 (#5883)
* 非ログイン時に検索欄がズレていたのを修正

* flexboxを用いてセンタリングを行うように変更
2020-02-09 03:42:18 +09:00
482afa93a2 Update avatars.vue 2020-02-09 03:41:11 +09:00
25bdbd7ae0 Fix #5886 2020-02-09 03:40:09 +09:00
527a639242 Update ja-JP.yml 2020-02-09 03:33:17 +09:00
0d5e000ad3 12.4.0 2020-02-09 00:01:01 +09:00
f4cb467e7a New Crowdin translations (#5882)
* 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 (Chinese Simplified)

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

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)
2020-02-08 23:59:47 +09:00
2f6b0b142a nanka iroiro 2020-02-08 23:52:40 +09:00
78c2535c3c ✌️ 2020-02-08 22:42:35 +09:00
26260392a8 Fix bug 2020-02-08 22:23:44 +09:00
aa573c0063 Create ActivityでattributedToの補完とaudienceのコピーを行うように (#5873)
* attributedTo

* Create

* copy audiences between activity <=> object

* やっぱり匿名GETのpublicは必要

* fix
2020-02-08 21:40:06 +09:00
b2859bcd2a Fix defalut note visibility setting (#5881)
* Fix default note visibility setting

* refactor

* missing translation

* fix
2020-02-08 20:02:15 +09:00
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
76 changed files with 2589 additions and 1215 deletions

View File

@ -1,6 +1,64 @@
ChangeLog
=========
12.5.0 (2020/02/09)
--------------------
### ✨Improvements
* チュートリアルを実装
* 検索のキーボードショートカットを追加
* タイムラインを遡っている状況でないときに、誰かをフォローまたはフォロー解除したときにタイムラインをリロードするように
### 🐛Fixes
* グループチャットが開始できない問題を修正
* Renoteメニューが開けない問題を修正
* 誕生日設定が崩れていたのを修正
* キャッシュが削除できない問題を修正
12.4.1 (2020/02/09)
--------------------
### 🐛Fixes
* グループの招待をacceptもrejectも出来ない問題を修正
* 非ログイン時に検索欄がズレていたのを修正
* バックグラウンドで受信したタイムラインの投稿のリアクションが受信されていない問題を修正
12.4.0 (2020/02/09)
--------------------
### ✨Improvements
* ローカルのみをデフォルトで操作できるように
* キーボード操作を改善
* AP: Create ActivityでattributedToの補完とaudienceのコピーを行うように
### 🐛Fixes
* ページ遷移してもナビゲーションが閉じない問題を修正
* デフォルトの公開範囲のリストにホームがなかったので復活
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

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,38 @@ 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"
group: "Groups"
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 +411,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 +428,7 @@ _widgets:
calendar: "Calendar"
trends: "Trending"
clock: "Clock"
rss: "RSS reader"
_cw:
hide: "Hide"
show: "Load more"
@ -441,8 +467,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 +497,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: "設定"
@ -72,6 +72,7 @@ exportRequested: "エクスポートをリクエストしました。これに
importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。"
lists: "リスト"
noLists: "リストはありません"
note: "ノート"
notes: "ノート"
following: "フォロー"
followers: "フォロワー"
@ -81,8 +82,6 @@ manageLists: "リストの管理"
error: "問題が発生しました"
retry: "再試行"
enterListName: "リスト名を入力"
renameList: "リスト名を変更"
deleteList: "リストを削除"
privacy: "プライバシー"
makeFollowManuallyApprove: "フォローを承認制にする"
defaultNoteVisibility: "デフォルトの公開範囲"
@ -216,7 +215,7 @@ remove: "削除"
removed: "削除しました"
removeAreYouSure: "「{x}」を削除しますか?"
saved: "保存しました"
messaging: "トーク"
messaging: "チャット"
upload: "アップロード"
fromDrive: "ドライブから"
fromUrl: "URLから"
@ -227,7 +226,7 @@ games: "Misskey Games"
messageRead: "既読"
recentUsedEmojis: "最近使用した絵文字"
noMoreHistory: "これより過去の履歴はありません"
startMessaging: "トークを開始"
startMessaging: "チャットを開始"
nUsersRead: "{n}人が読みました"
agreeTo: "{0}に同意"
tos: "利用規約"
@ -350,14 +349,67 @@ post: "投稿"
posted: "投稿しました"
autoReloadWhenDisconnected: "サーバー切断時に自動リロード"
autoNoteWatch: "ノートの自動ウォッチ"
autoNoteWatchDescription: "あなたがリアクションしたり返信したりした他のユーザーのノートに関する通知を受け取るようにします。"
reduceUiAnimation: "UIのアニメーションを減らす"
share: "共有"
notFound: "見つかりません"
notFoundDescription: "指定されたURLに該当するページはありませんでした。"
uploadFolder: "既定アップロード先"
cacheClear: "キャッシュを削除"
markAsReadAllNotifications: "すべての通知を既読にする"
markAsReadAllUnreadNotes: "すべての投稿を既読にする"
markAsReadAllTalkMessages: "すべてのチャットを既読にする"
help: "ヘルプ"
inputMessageHere: "ここにメッセージを入力"
close: "閉じる"
group: "グループ"
groups: "グループ"
createGroup: "グループを作成"
ownedGroups: "所有グループ"
joinedGroups: "参加しているグループ"
invites: "招待"
groupName: "グループ名"
members: "メンバー"
transfer: "譲渡"
messagingWithUser: "ユーザーとチャット"
messagingWithGroup: "グループでチャット"
enable: "有効にする"
next: "次"
retype: "再入力"
_tutorial:
title: "Misskeyの使い方"
step1_1: "ようこそ。"
step1_2: "この画面は「タイムライン」と呼ばれ、あなたや、あなたが「フォロー」する人の「ノート」が時系列で表示されます。"
step1_3: "あなたはまだ何もノートを投稿しておらず、誰もフォローしていないので、タイムラインには何も表示されていないはずです。"
step2_1: "ノートを作成したり誰かをフォローしたりする前に、まずあなたのプロフィールを完成させましょう。"
step2_2: "あなたがどんな人かわかると、多くの人にノートを見てもらえたり、フォローしてもらいやすくなります。"
step3_1: "プロフィール設定はうまくできましたか?"
step3_2: "では試しに、何かノートを投稿してみてください。画面上にある鉛筆マークのボタンを押すとフォームが開きます。"
step3_3: "内容を書いたら、フォーム右上のボタンを押すと投稿できます。"
step3_4: "内容が思いつかない「Misskey始めました」というのはいかがでしょう。"
step4_1: "投稿できましたか?"
step4_2: "あなたのノートがタイムラインに表示されていれば成功です。"
step5_1: "次は、他の人をフォローしてタイムラインを賑やかにしたいところです。"
step5_2: "{featured}で人気のノートが見れるので、その中から気になった人を選んでフォローしたり、{explore}で人気のユーザーを探すこともできます。"
step5_3: "ユーザーをフォローするには、ユーザーのアイコンをクリックしてユーザーページを表示し、「フォロー」ボタンを押します。"
step5_4: "ユーザーによっては、フォローが承認されるまで時間がかかる場合があります。"
step6_1: "タイムラインに他のユーザーのノートが表示されていれば成功です。"
step6_2: "他の人のノートには、「リアクション」を付けることができ、簡単にあなたの反応を伝えられます。"
step6_3: "リアクションを付けるには、ノートの「+」マークをクリックして、好きなリアクションを選択します。"
step7_1: "これで、Misskeyの基本的な使い方の説明は終わりました。お疲れ様でした。"
step7_2: "もっとMisskeyについて知りたいときは、{help}を見てみてください。"
step7_3: "では、Misskeyをお楽しみください🚀"
_2fa:
alreadyRegistered: "既に設定は完了しています。"
registerDevice: "デバイスを登録"
registerKey: "キーを登録"
step1: "まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。"
step2: "次に、表示されているQRコードをアプリでスキャンします。"
step3: "アプリに表示されているトークンを入力して完了です。"
step4: "これからログインするときも、同じようにトークンを入力します。"
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーを使用してログインするように設定できます。"
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーもしくは端末の指紋認証やPINを使用してログインするように設定できます。"
_permissions:
"read:account": "アカウントの情報を見る"
@ -370,8 +422,8 @@ _permissions:
"write:favorites": "お気に入りを操作する"
"read:following": "フォローの情報を見る"
"write:following": "フォロー・フォロー解除する"
"read:messaging": "トークを見る"
"write:messaging": "トークを操作する"
"read:messaging": "チャットを見る"
"write:messaging": "チャットを操作する"
"read:mutes": "ミュートを見る"
"write:mutes": "ミュートを操作する"
"write:notes": "ノートを作成・削除する"
@ -413,6 +465,7 @@ _widgets:
calendar: "カレンダー"
trends: "トレンド"
clock: "時計"
rss: "RSSリーダー"
_cw:
hide: "隠す"
@ -453,6 +506,7 @@ _visibility:
followersDescription: "自分のフォロワーのみに公開"
specified: "ダイレクト"
specifiedDescription: "指定したユーザーのみに公開"
localOnly: "ローカルのみ"
_postForm:
replyPlaceholder: "このノートに返信..."

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,38 @@ resetPassword: "비밀번호 재설정"
newPasswordIs: "새로운 비밀번호는 \"{password}\" 입니다"
post: "작성"
posted: "게시하였습니다"
autoReloadWhenDisconnected: "서버와의 연결이 끊기면 자동 새로고침"
autoNoteWatch: "노트를 자동으로 지켜보기"
reduceUiAnimation: "UI의 애니메이션을 줄이기"
share: "공유"
notFound: "찾을 수 없습니다"
notFoundDescription: "지정한 URL에 해당하는 페이지가 존재하지 않습니다."
uploadFolder: "기본 업로드 위치"
cacheClear: "캐시 지우기"
markAsReadAllNotifications: "모든 알림을 읽은 상태로 표시"
markAsReadAllUnreadNotes: "모든 글을 읽은 상태로 표시"
markAsReadAllTalkMessages: "모든 대화를 읽은 상태로 표시"
help: "도움말"
inputMessageHere: "여기에 메시지를 입력하세요"
close: "닫기"
group: "그룹"
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 +393,7 @@ _permissions:
"write:messaging": "대화 수정"
"read:mutes": "뮤트 보기"
"write:mutes": "뮤트 수정"
"write:notes": "노트 작성 삭제"
"write:notes": "노트 작성하거나 삭제"
"read:notifications": "알림 보기"
"write:notifications": "알림 수정"
"read:reactions": "리액션 보기"
@ -384,10 +409,10 @@ _auth:
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
permissionAsk: "이 앱은 다음의 권한을 요청합니다"
_antennaSources:
all: "모든 게시물"
homeTimeline: "팔로우중인 유저의 게시물"
users: "지정한 유저(들)의 게시물"
userList: "지정한 리스트에 속한 유저의 게시물"
all: "모든 노트"
homeTimeline: "팔로우중인 유저의 노트"
users: "지정한 한 명 혹은 여러 명의 유저의 노트"
userList: "지정한 리스트에 속한 유저의 노트"
_weekday:
sunday: "일요일"
monday: "월요일"
@ -403,6 +428,7 @@ _widgets:
calendar: "달력"
trends: "트렌드"
clock: "시계"
rss: "RSS 리더"
_cw:
hide: "숨기기"
show: "더 보기"
@ -441,8 +467,8 @@ _visibility:
specified: "다이렉트"
specifiedDescription: "지정한 유저에게만 공개"
_postForm:
replyPlaceholder: "이 에 답글..."
quotePlaceholder: "이 글을 인용..."
replyPlaceholder: "이 노트에 답글..."
quotePlaceholder: "이 노트를 인용..."
_placeholders:
a: "지금 무엇을 하고 있나요?"
b: "무슨 일이 일어나고 있나요?"
@ -459,7 +485,7 @@ _profile:
metadataLabel: "라벨"
metadataContent: "내용"
_exportOrImport:
allNotes: "모든 게시물"
allNotes: "모든 노트"
followingList: "팔로잉"
muteList: "뮤트"
blockingList: "차단"
@ -467,12 +493,12 @@ _exportOrImport:
_charts:
federationInstancesIncDec: "연합 인스턴스 수 증감"
federationInstancesTotal: "총 연합 인스턴스 수"
usersIncDec: "유저 증감"
usersTotal: "유저 합계"
usersIncDec: "유저 증감"
usersTotal: "유저 합계"
activeUsers: "활성 유저 수"
notesIncDec: "노트 수 증감"
localNotesIncDec: "로컬 노트 수 증감"
remoteNotesIncDec: "외부 노트 수 증감"
remoteNotesIncDec: "리모트 노트 수 증감"
notesTotal: "총 노트 수"
filesIncDec: "파일 수 증감"
filesTotal: "총 파일 수"
@ -480,10 +506,10 @@ _charts:
storageUsageTotal: "총 스토리지 사용량"
_instanceCharts:
requests: "요청"
users: "유저 증감"
usersTotal: "누적 사용자 수"
users: "유저 증감"
usersTotal: "누적 유저 수"
notes: "노트 수 증감"
notesTotal: "누적 노트 수"
notesTotal: " 노트 수"
ff: "팔로잉/팔로워 증감"
ffTotal: "팔로잉/팔로워 누적"
cacheSize: "캐시 용량 증감"

View File

@ -1 +1,649 @@
---
_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: "关注了你"
createList: "创建列表"
manageLists: "管理列表"
error: "有点小问题"
retry: "重试"
enterListName: "输入列表名称"
privacy: "隐私"
makeFollowManuallyApprove: "关注者请求需要批准"
defaultNoteVisibility: "默认可见性"
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: "远程文件缓存"
flagAsBot: "这个账户是Bot"
flagAsCat: "这个账户是Cat"
addAcount: "添加账户"
loginFailed: "登录失败"
general: "常规设置"
wallpaper: "壁纸"
removeWallpaper: "移除壁纸"
searchWith: "搜索:{q}"
host: "主机名"
selectUser: "选择用户"
recipient: "收件人"
annotation: "注解"
federation: "联合"
instances: "实例"
latestRequestSentAt: "上次发送的请求"
latestRequestReceivedAt: "上次收到的请求"
storageUsage: "已用存储"
perHour: "每小时"
perDay: "每天"
operations: "操作"
software: "软件"
version: "版本"
metadata: "元数据"
monitor: "监视器"
jobQueue: "作业队列"
cpuAndMemory: "CPU使用量"
network: "网络"
disk: "存储"
statistics: "统计"
clearQueue: "清除队列"
clearCachedFiles: "清除缓存"
muteAndBlock: "屏蔽/拉黑"
mutedUsers: "禁言用户"
blockedUsers: "已屏蔽用户"
noUsers: "无用户"
editProfile: "编辑资料"
done: "完成"
processing: "处理中"
preview: "预览"
noCustomEmojis: "无自定义Emoji"
federating: "联合中"
blocked: "已拦截"
all: "全部"
subscribing: "已订阅"
notResponding: "没有响应"
instanceFollowing: "关注实例"
instanceFollowers: "关注实例"
changePassword: "修改密码"
security: "安全性"
retypedNotMatch: "两次输入不一致!"
currentPassword: "现在的密码"
newPassword: "新密码"
newPasswordRetype: "重新输入密码:"
attachFile: "插入附件"
more: "更多!"
featured: "高亮"
lookup: "查询"
imageUrl: "图片URL"
remove: "删除"
removed: "已删除"
removeAreYouSure: "要删掉「{x}」吗?"
saved: "已保存"
messaging: "聊天"
upload: "上传"
fromUrl: "从 URL"
editWidgets: "编辑部件"
exitEdit: "停止编辑"
explore: "发现"
games: "Misskey游戏"
messageRead: "已读"
recentUsedEmojis: "最近使用的Emoji表情"
noMoreHistory: "没有更多的历史记录"
tos: "服务条款"
start: "开始"
home: "首页"
activity: "活动"
images: "图片"
birthday: "生日"
yearsOld: "{age}岁"
registeredDate: "注册于"
location: "位置"
theme: "主题"
lightThemes: "亮色主题"
darkThemes: "暗色主题"
drive: "网盘"
selectFile: "选择文件"
selectFiles: "选择文件"
renameFolder: "重命名文件夹"
createFolder: "创建文件夹"
deleteFolder: "删除文件夹"
addFile: "添加文件"
emptyDrive: "驱动器为空"
emptyFolder: "空文件夹"
copyUrl: "复制链接"
rename: "重命名"
avatar: "头像"
banner: "Banner"
nsfw: "阅读注意"
disconnectedFromServer: "已从服务器断开连接"
reloadConfirm: "确定要重新加载吗"
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: "代理远程文件"
proxyRemoteFilesDescription: "启用此设置后,由于超出存储容量而导致未保存被删除的远程文件将被本地代理,并且会生成缩略图。不会影响服务器的存储。"
driveCapacityPerLocalAccount: "每个用户的网盘空间"
driveCapacityPerRemoteAccount: "每个远程用户的网盘容量"
inMb: "以兆字节(Mbps)为单位"
iconUrl: "图标URL"
bannerUrl: "Banner URL"
basicInfo: "基本信息"
pinnedUsers: "置顶用户"
recaptcha: "reCAPTCHA"
enableRecaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)"
recaptchaSiteKey: "网站密钥"
recaptchaSecretKey: "reCAPTCHA 密钥"
name: "名称"
serviceworker: "ServiceWorker"
enableServiceworker: "启用ServiceWorker"
caseSensitive: "区分大小写"
connectedTo: "您的账号已连到接以下社交账号"
notesAndReplies: "帖子与回复"
withFiles: "附件"
silence: "禁言"
silenceConfirm: "确认要禁言吗?"
unsilenceConfirm: "要解除禁言吗?"
popularUsers: "热门用户"
recentlyUpdatedUsers: "最近投稿用户"
recentlyRegisteredUsers: "最近登录用户"
recentlyDiscoveredUsers: "最近发现的用户"
popularTags: "热门标签"
userList: "列表"
about: "关于"
aboutMisskey: "关于 Misskey"
aboutMisskeyText: "Misskey是由syuilo于2014年开发的开放源代码软件。"
misskeyMembers: "现在由以下成员进行开发和维护:"
misskeySource: "源代码在这里公开:"
misskeyDonate: "可以向 Misskey 进行捐款以支持开发:"
morePatrons: "还有很多其他的人也在支持我们,非常感谢🥰"
patrons: "支持者"
administrator: "管理员"
token: "令牌"
twoStepAuthentication: "两步验证"
moderator: "版主"
nUsersMentioned: "{n} 被提到"
securityKey: "安全密钥"
securityKeyName: "密钥名称"
lastUsed: "最后使用:"
unregister: "删除账户"
resetPassword: "重置密码"
newPasswordIs: "新的密码是「{password}」"
post: "投稿"
posted: "已投稿"
autoReloadWhenDisconnected: "断开连接时自动重新加载"
reduceUiAnimation: "减少UI动画"
share: "分享"
notFound: "未找到"
uploadFolder: "默认上传文件夹"
cacheClear: "清空缓存"
markAsReadAllNotifications: "将所有通知标为已读"
markAsReadAllUnreadNotes: "将所有帖子标记为已读"
markAsReadAllTalkMessages: "将所有对话标为已读"
help: "帮助"
inputMessageHere: "在此键入信息"
close: "关闭"
group: "群组"
groups: "群组"
createGroup: "创建群组"
ownedGroups: "拥有的群组"
joinedGroups: "已加入的群组"
invites: "邀请"
groupName: "群组名"
members: "成员"
transfer: "转让"
_2fa:
alreadyRegistered: "此设备已被注册"
registerDevice: "注册设备"
registerKey: "注册密钥"
_permissions:
"read:account": "查看账户信息"
"write:account": "更改帐户信息"
"read:blocks": "查看黑名单"
"write:blocks": "编辑黑名单"
"read:drive": "查看网盘"
"write:drive": "管理网盘文件"
"read:favorites": "查看收藏夹"
"write:favorites": "编辑收藏夹"
"read:following": "查看关注信息"
"write:following": "关注/取消关注"
"read:messaging": "查看对话"
"write:messaging": "对话操作"
"read:mutes": "查看屏蔽列表"
"write:mutes": "编辑屏蔽列表"
"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: "这个应用程序需要以下权限"
_weekday:
sunday: "星期日"
monday: "星期一"
tuesday: "星期二"
wednesday: "星期三"
thursday: "星期四"
friday: "星期五"
saturday: "星期六"
_widgets:
memo: "便签"
notifications: "通知"
timeline: "时间线"
calendar: "日历"
trends: "趋势"
clock: "时钟"
rss: "RSS阅读器"
_cw:
hide: "隐藏"
show: "查看更多"
chars: "{count}个字符"
files: "{count} 个文件"
poll: "投票"
_poll:
noOnlyOneChoice: "需要至少两个选项"
choiceN: "选择{n}"
noMore: "无法再添加更多了"
canMultipleVote: "允许多个投票"
expiration: "截止时间"
infinite: "无限期"
at: "指定日期"
after: "指定时间"
deadlineDate: "截止日期"
deadlineTime: "小时"
duration: "时长"
votesCount: "{n}票"
totalVotes: "总票数{n}"
vote: "投票"
showResult: "显示结果"
voted: "已投票"
closed: "已截止"
remainingDays: "{d}天{h}小时后截止"
remainingHours: "{h}小时{m}分后截止"
remainingMinutes: "{m}分{s}秒后截止"
remainingSeconds: "{s}秒后截止"
_visibility:
public: "公开"
home: "首页"
followers: "关注者"
specified: "指定用户"
_profile:
name: "名称"
username: "用户名"
_exportOrImport:
followingList: "关注中"
muteList: "屏蔽"
blockingList: "屏蔽"
userLists: "列表"
_charts:
usersIncDec: "用户数量:增加/减少"
_instanceCharts:
users: "用户数量:增加/减少"
usersTotal: "用户总数"
ff: "关注/被关注:数量变化"
ffTotal: "关注/被关注:总数"
cacheSize: "缓存大小:增加/减少"
_timelines:
home: "首页"
local: "本地"
social: "社交"
global: "全局"
_pages:
newPage: "创建页面"
editPage: "编辑页面"
page-created: "页面已创建"
page-updated: "页面已更新"
name-already-exists: "该页面URL已存在"
title-invalid-name: "无效的页面URL"
text-invalid-name: "请确认该项不为空"
editThisPage: "编辑此页面"
viewSource: "查看源代码"
viewPage: "查看页面"
like: "赞"
unlike: "取消赞"
liked-pages: "喜欢的页面"
my-pages: "我的页面"
inspector: "检查器"
content: "页面内容"
variables: "变量"
variables-info: "您可以使用变量创建动态页面。在文本中通过<b>{变量名}</b>的写法来嵌入变量值。例如在文本<b>Hello { thing } world!</b>中,如果变量(thing)的值为<b>ai</b>,那么该文本会成为<b>Hello ai world!</b>。"
variables-info2: "因为变量的计算(计算变量值)是从上到下执行的,所以不能在变量中引用下面的变量。例如从上到下依次定义了<b>ABC</b>3个变量那么<b>C</b>中可以引用<b>A</b>或<b>B</b>,但是<b>A</b>无法引用<b>B</b>或<b>C</b>。"
variables-info3: "为了接收来自用户的输入,页面上设有“用户输入”块,在“变量名称”中设置要在其中保存输入值的变量名(变量会自动创建)。您可以使用该变量执行操作以响应用户输入。"
variables-info4: "通过使用函数,您可以将数值计算过程组合成可重用的形式。要创建函数,需要创建一个“函数”类型的变量。你可以将函数设定为槽函数(参数)的格式槽函数的值可作为函数中的变量使用。另外AiScript标准中还有一些函数会将函数作为参数(称为高阶函数)。\n除了已经预先定义的函数外您也可以将它们设置为这些高阶函数的槽函数。"
more-details: "详细说明"
title: "标题"
url: "页面URL"
summary: "页面摘要"
alignCenter: "居中"
hide-title-when-pinned: "置顶时隐藏标题"
font: "字体"
fontSerif: "衬线字体"
fontSansSerif: "无衬线字体"
set-eye-catching-image: "设置封面图片"
remove-eye-catching-image: "删除封面图片"
chooseBlock: "添加块"
selectType: "选择类型"
enterVariableName: "请输入变量名"
the-variable-name-is-already-used: "变量名已使用"
content-blocks: "内容"
input-blocks: "输入"
special-blocks: "特殊"
post-from-post-form: "发布此内容"
posted-from-post-form: "已发布"
blocks:
text: "文本"
image: "图片"
_if:
variable: "变量"
post: "投稿窗口"
_textInput:
name: "变量名"
text: "标题"
default: "默认值"
textareaInput: "多行文本输入"
_textareaInput:
name: "变量名"
text: "标题"
default: "默认值"
numberInput: "输入数值"
_numberInput:
name: "变量名"
text: "标题"
default: "默认值"
switch: "开关"
_switch:
name: "变量名"
text: "标题"
default: "默认值"
counter: "计数器"
_counter:
name: "变量名"
text: "标题"
inc: "增加值"
_button:
text: "标题"
colored: "彩色"
action: "按下按钮时的行为"
_action:
dialog: "显示对话框"
_dialog:
content: "内容"
_radioButton:
name: "变量名"
title: "标题"
default: "默认值"
script:
categories:
list: "列表"
blocks:
text: "文本"
multiLineText: "文本 (多行)"
textList: "文本列表"
_textList:
info: "请使用换行符分隔每行"
strLen: "文本长度"
_strLen:
arg1: "文本"
strPick: "提取字符"
_strPick:
arg1: "文本"
arg2: "字符位置"
strReplace: "替换文本"
_strReplace:
arg1: "文本"
arg2: "替换之前"
arg3: "替换之后"
strReverse: "文本反向"
_strReverse:
arg1: "文本"
join: "合并文本"
_join:
arg1: "列表"
arg2: "分隔符"
add: "加"
_add:
arg1: "A"
arg2: "B"
subtract: "减"
_subtract:
arg1: "A"
arg2: "B"
multiply: "乘"
_multiply:
arg1: "A"
arg2: "B"
divide: "除"
_divide:
arg1: "A"
arg2: "B"
mod: "取模(MOD)"
_mod:
arg1: "A"
arg2: "B"
round: "四舍五入"
_round:
arg1: "数值"
eq: "A和B相等"
_eq:
arg1: "A"
arg2: "B"
notEq: "A和B不等"
_notEq:
arg1: "A"
arg2: "B"
and: "A和B"
_and:
arg1: "A"
arg2: "B"
or: "A或B"
_or:
arg1: "A"
arg2: "B"
lt: "< A小于B"
_lt:
arg1: "A"
arg2: "B"
gt: "> A大于B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "<= A小于等于B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: ">= A大于等于B"
_gtEq:
arg1: "A"
arg2: "B"
if: "分支"
_if:
arg1: "如果"
arg2: "如果"
arg3: "否则"
not: "否"
_not:
arg1: "否"
random: "随机"
_random:
arg1: "概率"
rannum: "随机数"
_rannum:
arg1: "最小值"
arg2: "最大值"
randomPick: "从列表中随机选择"
_randomPick:
arg1: "列表"
dailyRandom: "随机(每个用户每日)"
_dailyRandom:
arg1: "概率"
dailyRannum: "随机数(每个用户每日)"
_dailyRannum:
arg1: "最小值"
arg2: "最大值"
dailyRandomPick: "从列表中随机选择(每个用户每日)"
_dailyRandomPick:
arg1: "列表"
seedRandom: "随机 (种子)"
_seedRandom:
arg1: "种子"
arg2: "概率"
seedRannum: "随机数(种子)"
_seedRannum:
arg1: "种子"
arg2: "最小值"
arg3: "最大值"
seedRandomPick: "从列表中随机选择 (种子)"
_seedRandomPick:
arg1: "种子"
arg2: "列表"
DRPWPM: "从概率列表中随机选择(每用户每天)"
_DRPWPM:
arg1: "文本列表"
pick: "从列表中选择"
_pick:
arg1: "列表"
arg2: "位置"
listLen: "获取列表长度"
_listLen:
arg1: "列表"
number: "数值"
stringToNumber: "文本到数字"
_stringToNumber:
arg1: "文本"
numberToString: "数字到文本"
_numberToString:
arg1: "数值"
splitStrByLine: "将文本按行拆分"
_splitStrByLine:
arg1: "文本"
ref: "变量"
fn: "函数"
_fn:
arg1: "输出"
for: "重复"
_for:
arg1: "次数"
arg2: "处理"
types:
string: "文字"
number: "数值"
boolean: "Flag"
array: "列表"
stringArray: "文本列表"
enviromentVariables: "环境变量"
pageVariables: "页面元素"
argVariables: "输入变量"

View File

@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.0.0",
"version": "12.5.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,8 @@ export default Vue.extend({
return {
'p': this.post,
'n': this.post,
's': this.search,
'h|/': this.help
};
},
@ -191,6 +207,7 @@ export default Vue.extend({
$route(to, from) {
this.pageKey++;
this.notificationsOpen = false;
this.showNav = false;
this.canBack = (window.history.length > 0 && !['index'].includes(to.name));
},
@ -241,12 +258,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);
@ -254,10 +274,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();
},
@ -288,67 +316,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);
@ -444,6 +411,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'),
@ -477,6 +449,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',
@ -600,6 +577,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;
@ -699,6 +698,8 @@ export default Vue.extend({
> .sub {
$post-button-size: 42px;
$post-button-margin: (($header-height - $post-button-size) / 2);
display: flex;
align-items: center;
position: absolute;
top: 0;
right: 16px;
@ -738,13 +739,23 @@ export default Vue.extend({
> .post {
width: $post-button-size;
height: $post-button-size;
margin: $post-button-margin 0 $post-button-margin $post-button-margin;
margin-left: $post-button-margin;
border-radius: 100%;
font-size: 16px;
}
}
}
> .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;
@ -759,7 +770,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 {
@ -769,13 +787,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 {
@ -783,7 +812,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;
@ -793,22 +821,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));
}
@ -841,7 +853,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;
@ -862,6 +874,13 @@ export default Vue.extend({
}
}
}
@media (max-width: $nav-hide-threshold) {
> .index,
> .notifications {
display: none;
}
}
}
}
@ -965,10 +984,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="display:inline-block;width:32px;height:32px;margin-right:8px;">
<mk-avatar :user="user" style="width:32px;height:32px;"/>
</div>
</div>
</template>

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,14 @@ export default Vue.extend({
> .button {
margin: 0 auto;
}
> img {
vertical-align: bottom;
height: 150px;
margin-bottom: 16px;
border-radius: 16px;
pointer-events: none;
user-select: none;
}
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<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">
<x-popup :source="source" :no-center="noCenter" :fixed="fixed" :width="width" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }" v-hotkey.global="keymap">
<sequential-entrance class="rrevdjwt" :class="{ left: align === 'left' }" :delay="15" :direction="direction" ref="items">
<template v-for="(item, i) in items.filter(item => item !== undefined)">
<div v-if="item === null" class="divider" :key="i"></div>
<span v-else-if="item.type === 'label'" class="label item" :key="i">
@ -36,6 +36,7 @@
import Vue from 'vue';
import { faCircle } from '@fortawesome/free-solid-svg-icons';
import XPopup from './popup.vue';
import { focusPrev, focusNext } from '../scripts/focus';
export default Vue.extend({
components: {
@ -69,12 +70,31 @@ export default Vue.extend({
type: String,
required: false
},
viaKeyboard: {
type: Boolean,
required: false
},
},
data() {
return {
faCircle
};
},
computed: {
keymap(): any {
return {
'up|k|shift+tab': this.focusUp,
'down|j|tab': this.focusDown,
};
},
},
mounted() {
if (this.viaKeyboard) {
this.$nextTick(() => {
focusNext(this.$refs.items.$slots.default[0].elm, true);
});
}
},
methods: {
clicked(fn) {
fn();
@ -82,6 +102,12 @@ export default Vue.extend({
},
close() {
this.$refs.popup.close();
},
focusUp() {
focusPrev(document.activeElement);
},
focusDown() {
focusNext(document.activeElement);
}
}
});
@ -119,6 +145,10 @@ export default Vue.extend({
background: var(--accentDarken);
}
&:not(:active):focus {
box-shadow: 0 0 0 2px var(--focus) inset;
}
&.label {
pointer-events: none;
font-size: 0.7em;

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

@ -1,5 +1,5 @@
<template>
<mfm-core v-bind="$attrs" class="havbbuyv" :class="{ nowrap: $attrs['nowrap'] }" v-once/>
<mfm-core v-bind="$attrs" class="havbbuyv" :class="{ nowrap: $attrs['nowrap'] }"/>
</template>
<script lang="ts">

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

@ -1,200 +0,0 @@
<template>
<x-menu :source="source" :items="items" @closed="closed"/>
</template>
<script lang="ts">
import Vue from 'vue';
import { faStar, faLink, faThumbtack, faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons';
import { faCopy, faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
import i18n from '../i18n';
import { url } from '../config';
import copyToClipboard from '../scripts/copy-to-clipboard';
import XMenu from './menu.vue';
export default Vue.extend({
i18n,
components: {
XMenu
},
props: ['note', 'source'],
data() {
return {
isFavorited: false,
isWatching: false
};
},
computed: {
items(): any[] {
if (this.$store.getters.isSignedIn) {
return [{
icon: faCopy,
text: this.$t('copyContent'),
action: this.copyContent
}, {
icon: faLink,
text: this.$t('copyLink'),
action: this.copyLink
}, this.note.uri ? {
icon: faExternalLinkSquareAlt,
text: this.$t('showOnRemote'),
action: () => {
window.open(this.note.uri, '_blank');
}
} : undefined,
null,
this.isFavorited ? {
icon: faStar,
text: this.$t('unfavorite'),
action: () => this.toggleFavorite(false)
} : {
icon: faStar,
text: this.$t('favorite'),
action: () => this.toggleFavorite(true)
},
this.note.userId != this.$store.state.i.id ? this.isWatching ? {
icon: faEyeSlash,
text: this.$t('unwatch'),
action: () => this.toggleWatch(false)
} : {
icon: faEye,
text: this.$t('watch'),
action: () => this.toggleWatch(true)
} : undefined,
this.note.userId == this.$store.state.i.id ? (this.$store.state.i.pinnedNoteIds || []).includes(this.note.id) ? {
icon: faThumbtack,
text: this.$t('unpin'),
action: () => this.togglePin(false)
} : {
icon: faThumbtack,
text: this.$t('pin'),
action: () => this.togglePin(true)
} : undefined,
...(this.note.userId == this.$store.state.i.id ? [
null,
{
icon: faTrashAlt,
text: this.$t('delete'),
action: this.del
}]
: []
)]
.filter(x => x !== undefined);
} else {
return [{
icon: faCopy,
text: this.$t('copyContent'),
action: this.copyContent
}, {
icon: faLink,
text: this.$t('copyLink'),
action: this.copyLink
}, this.note.uri ? {
icon: faExternalLinkSquareAlt,
text: this.$t('showOnRemote'),
action: () => {
window.open(this.note.uri, '_blank');
}
} : undefined]
.filter(x => x !== undefined);
}
}
},
created() {
this.$root.api('notes/state', {
noteId: this.note.id
}).then(state => {
this.isFavorited = state.isFavorited;
this.isWatching = state.isWatching;
});
},
methods: {
copyContent() {
copyToClipboard(this.note.text);
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
},
copyLink() {
copyToClipboard(`${url}/notes/${this.note.id}`);
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
},
togglePin(pin: boolean) {
this.$root.api(pin ? 'i/pin' : 'i/unpin', {
noteId: this.note.id
}).then(() => {
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
this.$emit('closed');
this.destroyDom();
}).catch(e => {
if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') {
this.$root.dialog({
type: 'error',
text: this.$t('pinLimitExceeded')
});
}
});
},
del() {
this.$root.dialog({
type: 'warning',
text: this.$t('noteDeleteConfirm'),
showCancelButton: true
}).then(({ canceled }) => {
if (canceled) return;
this.$root.api('notes/delete', {
noteId: this.note.id
}).then(() => {
this.$emit('closed');
this.destroyDom();
});
});
},
toggleFavorite(favorite: boolean) {
this.$root.api(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', {
noteId: this.note.id
}).then(() => {
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
this.$emit('closed');
this.destroyDom();
});
},
toggleWatch(watch: boolean) {
this.$root.api(watch ? 'notes/watching/create' : 'notes/watching/delete', {
noteId: this.note.id
}).then(() => {
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
this.$emit('closed');
this.destroyDom();
});
},
closed() {
this.$emit('closed');
this.$nextTick(() => {
this.destroyDom();
});
}
}
});
</script>

View File

@ -19,7 +19,7 @@
</router-link>
</i18n>
<div class="info">
<button class="_button time" @click="showRenoteMenu"><mk-time :time="note.createdAt"/></button>
<button class="_button time" @click="showRenoteMenu()" ref="renoteTime"><mk-time :time="note.createdAt"/></button>
<span class="visibility" v-if="note.visibility != 'public'">
<fa v-if="note.visibility == 'home'" :icon="faHome"/>
<fa v-if="note.visibility == 'followers'" :icon="faUnlock"/>
@ -83,7 +83,8 @@
<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 { faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight } from '@fortawesome/free-solid-svg-icons';
import { faCopy, faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
import { parse } from '../../mfm/parse';
import { sum, unique } from '../../prelude/array';
import i18n from '../i18n';
@ -95,21 +96,11 @@ import XMediaList from './media-list.vue';
import XCwButton from './cw-button.vue';
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) {
const target = fn(el);
if (target) {
if (target.hasAttribute('tabindex')) {
target.focus();
} else {
focus(target, fn);
}
}
}
import { focusPrev, focusNext } from '../scripts/focus';
import { url } from '../config';
import copyToClipboard from '../scripts/copy-to-clipboard';
export default Vue.extend({
i18n,
@ -149,7 +140,6 @@ export default Vue.extend({
replies: [],
showContent: false,
hideThisNote: false,
openingMenu: false,
faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan
};
},
@ -275,7 +265,7 @@ export default Vue.extend({
methods: {
capture(withHandler = false) {
if (this.$store.getters.isSignedIn) {
this.connection.send('sn', { id: this.appearNote.id });
this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id });
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
}
},
@ -370,13 +360,30 @@ export default Vue.extend({
});
},
renote() {
renote(viaKeyboard = false) {
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);
viaKeyboard
}).then(this.focus);
},
renoteDirectly() {
@ -444,20 +451,114 @@ export default Vue.extend({
});
},
menu(viaKeyboard = false) {
if (this.openingMenu) return;
this.openingMenu = true;
const w = this.$root.new(MkNoteMenu, {
source: this.$refs.menuButton,
note: this.appearNote,
animation: !viaKeyboard
}).$once('closed', () => {
this.openingMenu = false;
this.focus();
toggleFavorite(favorite: boolean) {
this.$root.api(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', {
noteId: this.appearNote.id
}).then(() => {
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
});
},
showRenoteMenu(ev) {
toggleWatch(watch: boolean) {
this.$root.api(watch ? 'notes/watching/create' : 'notes/watching/delete', {
noteId: this.appearNote.id
}).then(() => {
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
});
},
async menu(viaKeyboard = false) {
let menu;
if (this.$store.getters.isSignedIn) {
const state = await this.$root.api('notes/state', {
noteId: this.appearNote.id
});
menu = [{
icon: faCopy,
text: this.$t('copyContent'),
action: this.copyContent
}, {
icon: faLink,
text: this.$t('copyLink'),
action: this.copyLink
}, this.appearNote.uri ? {
icon: faExternalLinkSquareAlt,
text: this.$t('showOnRemote'),
action: () => {
window.open(this.appearNote.uri, '_blank');
}
} : undefined,
null,
state.isFavorited ? {
icon: faStar,
text: this.$t('unfavorite'),
action: () => this.toggleFavorite(false)
} : {
icon: faStar,
text: this.$t('favorite'),
action: () => this.toggleFavorite(true)
},
this.appearNote.userId != this.$store.state.i.id ? state.isWatching ? {
icon: faEyeSlash,
text: this.$t('unwatch'),
action: () => this.toggleWatch(false)
} : {
icon: faEye,
text: this.$t('watch'),
action: () => this.toggleWatch(true)
} : undefined,
this.appearNote.userId == this.$store.state.i.id ? (this.$store.state.i.pinnedNoteIds || []).includes(this.appearNote.id) ? {
icon: faThumbtack,
text: this.$t('unpin'),
action: () => this.togglePin(false)
} : {
icon: faThumbtack,
text: this.$t('pin'),
action: () => this.togglePin(true)
} : undefined,
...(this.appearNote.userId == this.$store.state.i.id ? [
null,
{
icon: faTrashAlt,
text: this.$t('delete'),
action: this.del
}]
: []
)]
.filter(x => x !== undefined);
} else {
menu = [{
icon: faCopy,
text: this.$t('copyContent'),
action: this.copyContent
}, {
icon: faLink,
text: this.$t('copyLink'),
action: this.copyLink
}, this.appearNote.uri ? {
icon: faExternalLinkSquareAlt,
text: this.$t('showOnRemote'),
action: () => {
window.open(this.appearNote.uri, '_blank');
}
} : undefined]
.filter(x => x !== undefined);
}
this.$root.menu({
items: menu,
source: this.$refs.menuButton,
viaKeyboard
}).then(this.focus);
},
showRenoteMenu(viaKeyboard = false) {
if (!this.$store.getters.isSignedIn || (this.$store.state.i.id !== this.note.userId)) return;
this.$root.menu({
items: [{
@ -470,7 +571,8 @@ export default Vue.extend({
Vue.set(this.note, 'deletedAt', new Date());
}
}],
source: ev.currentTarget || ev.target,
source: this.$refs.renoteTime,
viaKeyboard: viaKeyboard
});
},
@ -478,6 +580,40 @@ export default Vue.extend({
this.showContent = !this.showContent;
},
copyContent() {
copyToClipboard(this.appearNote.text);
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
},
copyLink() {
copyToClipboard(`${url}/notes/${this.appearNote.id}`);
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
},
togglePin(pin: boolean) {
this.$root.api(pin ? 'i/pin' : 'i/unpin', {
noteId: this.appearNote.id
}).then(() => {
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
}).catch(e => {
if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') {
this.$root.dialog({
type: 'error',
text: this.$t('pinLimitExceeded')
});
}
});
},
focus() {
this.$el.focus();
},
@ -487,11 +623,11 @@ export default Vue.extend({
},
focusBefore() {
focus(this.$el, e => e.previousElementSibling);
focusPrev(this.$el);
},
focusAfter() {
focus(this.$el, e => e.nextElementSibling);
focusNext(this.$el);
}
}
});

View File

@ -1,10 +1,13 @@
<template>
<div class="mk-notes" v-size="[{ max: 500 }]">
<div class="empty" v-if="empty">{{ $t('noNotes') }}</div>
<div class="empty" v-if="empty">
<img src="https://xn--931a.moe/assets/info.jpg" alt=""/>
<div>{{ $t('noNotes') }}</div>
</div>
<mk-error v-if="error" @retry="init()"/>
<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note, i }">
<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note }">
<x-note :note="note" :detail="detail" :key="note.id"/>
</x-list>
@ -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,17 @@ 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;
pointer-events: none;
user-select: none;
}
}
> .notes {

View File

@ -1,9 +1,9 @@
<template>
<div class="mk-popup">
<transition name="bg-fade" appear>
<div class="mk-popup" v-hotkey.global="keymap">
<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>
@ -35,6 +35,13 @@ export default Vue.extend({
show: true,
};
},
computed: {
keymap(): any {
return {
'esc': this.close,
};
},
},
mounted() {
this.$nextTick(() => {
const popover = this.$refs.content as any;
@ -96,8 +103,8 @@ export default Vue.extend({
methods: {
close() {
this.show = false;
(this.$refs.bg as any).style.pointerEvents = 'none';
(this.$refs.content as any).style.pointerEvents = 'none';
if (this.$refs.bg) (this.$refs.bg as any).style.pointerEvents = 'none';
if (this.$refs.content) (this.$refs.content as any).style.pointerEvents = 'none';
}
}
});
@ -134,7 +141,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
};
},
@ -153,7 +153,7 @@ export default Vue.extend({
this.$t('_postForm._placeholders.f')
];
const x = xs[Math.floor(Math.random() * xs.length)];
return this.renote
? this.$t('_postForm.quotePlaceholder')
: this.reply
@ -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 {
@ -217,7 +217,7 @@ export default Vue.extend({
// デフォルト公開範囲
this.applyVisibility(this.$store.state.settings.rememberNoteVisibility ? this.$store.state.device.visibility : this.$store.state.settings.defaultNoteVisibility);
this.localOnly = this.$store.state.settings.rememberNoteVisibility ? this.$store.state.device.localOnly : false;
this.localOnly = this.$store.state.settings.rememberNoteVisibility ? this.$store.state.device.localOnly : this.$store.state.settings.defaultNoteLocalOnly;
// 公開以外へのリプライ時は元の公開範囲を引き継ぐ
if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) {
@ -398,8 +398,7 @@ export default Vue.extend({
},
applyVisibility(v: string) {
if (!['public', 'home', 'followers', 'specified'].includes(v)) v = 'public'; // v11互換性のため
this.visibility = v;
this.visibility = ['public', 'home', 'followers', 'specified'].includes(v) ? v : 'public'; // v11互換性のため
},
addVisibleUser() {
@ -622,8 +621,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 +634,10 @@ export default Vue.extend({
&:disabled {
opacity: 0.7;
}
> [data-icon] {
margin-left: 6px;
}
}
}
}
@ -709,7 +713,7 @@ export default Vue.extend({
border-radius: 0;
background: transparent;
color: var(--fg);
font-family: initial;
font-family: inherit;
@media (max-width: 500px) {
padding: 0 16px;

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">

View File

@ -27,6 +27,7 @@ export default Vue.extend({
data() {
return {
connection: null,
connection2: null,
pagination: null,
baseQuery: {
includeMyRenotes: this.$store.state.settings.showMyRenotes,
@ -40,6 +41,7 @@ export default Vue.extend({
created() {
this.$once('hook:beforeDestroy', () => {
this.connection.dispose();
if (this.connection2) this.connection2.dispose();
});
const prepend = note => {
@ -54,6 +56,12 @@ export default Vue.extend({
(this.$refs.tl as any).reload();
};
const onChangeFollowing = () => {
if (!this.$refs.tl.backed) {
this.$refs.tl.reload();
}
};
let endpoint;
if (this.src == 'antenna') {
@ -67,13 +75,12 @@ export default Vue.extend({
this.connection.on('note', prepend);
} else if (this.src == 'home') {
endpoint = 'notes/timeline';
const onChangeFollowing = () => {
this.fetch();
};
this.connection = this.$root.stream.useSharedConnection('homeTimeline');
this.connection.on('note', prepend);
this.connection.on('follow', onChangeFollowing);
this.connection.on('unfollow', onChangeFollowing);
this.connection2 = this.$root.stream.useSharedConnection('main');
this.connection2.on('follow', onChangeFollowing);
this.connection2.on('unfollow', onChangeFollowing);
} else if (this.src == 'local') {
endpoint = 'notes/local-timeline';
this.connection = this.$root.stream.useSharedConnection('localTimeline');

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

@ -254,7 +254,7 @@ export default Vue.extend({
> .input {
position: relative;
&:before {
content: '';
display: block;
@ -327,14 +327,16 @@ export default Vue.extend({
}
> input {
$height: 32px;
display: block;
height: $height;
width: 100%;
margin: 0;
padding: 0;
font: inherit;
font-weight: normal;
font-size: 16px;
line-height: 32px;
line-height: $height;
color: var(--inputText);
background: transparent;
border: none;

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

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

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

@ -0,0 +1,127 @@
<template>
<div class="_card tbkwesmv">
<div class="_title"><fa :icon="faInfoCircle"/> {{ $t('_tutorial.title') }}</div>
<div class="_content" v-if="tutorial === 0">
<div>{{ $t('_tutorial.step1_1') }}</div>
<div>{{ $t('_tutorial.step1_2') }}</div>
<div>{{ $t('_tutorial.step1_3') }}</div>
</div>
<div class="_content" v-else-if="tutorial === 1">
<div>{{ $t('_tutorial.step2_1') }}</div>
<div>{{ $t('_tutorial.step2_2') }}</div>
<router-link class="_link" to="/my/settings">{{ $t('editProfile') }}</router-link>
</div>
<div class="_content" v-else-if="tutorial === 2">
<div>{{ $t('_tutorial.step3_1') }}</div>
<div>{{ $t('_tutorial.step3_2') }}</div>
<div>{{ $t('_tutorial.step3_3') }}</div>
<small>{{ $t('_tutorial.step3_4') }}</small>
</div>
<div class="_content" v-else-if="tutorial === 3">
<div>{{ $t('_tutorial.step4_1') }}</div>
<div>{{ $t('_tutorial.step4_2') }}</div>
</div>
<div class="_content" v-else-if="tutorial === 4">
<div>{{ $t('_tutorial.step5_1') }}</div>
<i18n path="_tutorial.step5_2" tag="div">
<router-link class="_link" place="featured" to="/featured">{{ $t('featured') }}</router-link>
<router-link class="_link" place="explore" to="/explore">{{ $t('explore') }}</router-link>
</i18n>
<div>{{ $t('_tutorial.step5_3') }}</div>
<small>{{ $t('_tutorial.step5_4') }}</small>
</div>
<div class="_content" v-else-if="tutorial === 5">
<div>{{ $t('_tutorial.step6_1') }}</div>
<div>{{ $t('_tutorial.step6_2') }}</div>
<div>{{ $t('_tutorial.step6_3') }}</div>
</div>
<div class="_content" v-else-if="tutorial === 6">
<div>{{ $t('_tutorial.step7_1') }}</div>
<i18n path="_tutorial.step7_2" tag="div">
<router-link class="_link" place="help" to="/docs">{{ $t('help') }}</router-link>
</i18n>
<div>{{ $t('_tutorial.step7_3') }}</div>
</div>
<div class="_footer navigation">
<div class="step">
<button class="arrow _button" @click="tutorial--" :disabled="tutorial === 0">
<fa :icon="faChevronLeft"/>
</button>
<span>{{ tutorial + 1 }} / 7</span>
<button class="arrow _button" @click="tutorial++" :disabled="tutorial === 6">
<fa :icon="faChevronRight"/>
</button>
</div>
<mk-button class="ok" @click="tutorial = -1" primary v-if="tutorial === 6"><fa :icon="faCheck"/> {{ $t('gotIt') }}</mk-button>
<mk-button class="ok" @click="tutorial++" primary v-else><fa :icon="faCheck"/> {{ $t('next') }}</mk-button>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faInfoCircle, faChevronLeft, faChevronRight, faCheck } from '@fortawesome/free-solid-svg-icons'
import MkButton from '../components/ui/button.vue';
export default Vue.extend({
components: {
MkButton,
},
data() {
return {
faInfoCircle, faChevronLeft, faChevronRight, faCheck
}
},
computed: {
tutorial: {
get() { return this.$store.state.settings.tutorial || 0; },
set(value) { this.$store.dispatch('settings/set', { key: 'tutorial', value }); }
},
},
});
</script>
<style lang="scss" scoped>
.tbkwesmv {
> ._content {
> small {
opacity: 0.7;
}
}
> .navigation {
display: flex;
flex-direction: row;
align-items: baseline;
> .step {
> .arrow {
padding: 4px;
&:disabled {
opacity: 0.5;
}
&:first-child {
padding-right: 8px;
}
&:last-child {
padding-left: 8px;
}
}
> span {
margin: 0 4px;
}
}
> .ok {
margin-left: auto;
}
}
}
</style>

View File

@ -13,6 +13,9 @@
<fa :icon="menuOpened ? faAngleUp : faAngleDown" style="margin-left: 8px;"/>
</button>
</portal>
<x-tutorial class="tutorial" v-if="$store.state.settings.tutorial != -1"/>
<x-timeline ref="tl" :key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src" :src="src" :list="list" :antenna="antenna" @before="before()" @after="after()"/>
</div>
</template>
@ -23,6 +26,7 @@ import { faAngleDown, faAngleUp, faHome, faShareAlt, faGlobe, faListUl, faSatell
import { faComments } from '@fortawesome/free-regular-svg-icons';
import Progress from '../scripts/loading';
import XTimeline from '../components/timeline.vue';
import XTutorial from './index.home.tutorial.vue';
export default Vue.extend({
metaInfo() {
@ -32,7 +36,8 @@ export default Vue.extend({
},
components: {
XTimeline
XTimeline,
XTutorial,
},
props: {
@ -57,7 +62,7 @@ export default Vue.extend({
return {
't': this.focus
};
}
},
},
watch: {
@ -172,7 +177,13 @@ export default Vue.extend({
});
</script>
<style lang="scss">
<style lang="scss" scoped>
.mk-home {
> .tutorial {
margin-bottom: var(--margin);
}
}
._kjvfvyph_ {
position: relative;
height: 100%;

View File

@ -120,30 +120,30 @@
<section class="_card">
<div class="_title"><fa :icon="faShareAlt"/> {{ $t('integration') }}</div>
<div class="_content">
<header><fa :icon="faTwitter"/> {{ $t('twitter-integration-config') }}</header>
<mk-switch v-model="enableTwitterIntegration">{{ $t('enable-twitter-integration') }}</mk-switch>
<header><fa :icon="faTwitter"/> Twitter</header>
<mk-switch v-model="enableTwitterIntegration">{{ $t('enable') }}</mk-switch>
<template v-if="enableTwitterIntegration">
<mk-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('twitter-integration-consumer-key') }}</mk-input>
<mk-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('twitter-integration-consumer-secret') }}</mk-input>
<mk-info>{{ $t('twitter-integration-info', { url: `${url}/api/tw/cb` }) }}</mk-info>
<mk-info>Callback URL: {{ `${url}/api/tw/cb` }}</mk-info>
<mk-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Key</mk-input>
<mk-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Secret</mk-input>
</template>
</div>
<div class="_content">
<header><fa :icon="faGithub"/> {{ $t('github-integration-config') }}</header>
<mk-switch v-model="enableGithubIntegration">{{ $t('enable-github-integration') }}</mk-switch>
<header><fa :icon="faGithub"/> GitHub</header>
<mk-switch v-model="enableGithubIntegration">{{ $t('enable') }}</mk-switch>
<template v-if="enableGithubIntegration">
<mk-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('github-integration-client-id') }}</mk-input>
<mk-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('github-integration-client-secret') }}</mk-input>
<mk-info>{{ $t('github-integration-info', { url: `${url}/api/gh/cb` }) }}</mk-info>
<mk-info>Callback URL: {{ `${url}/api/gh/cb` }}</mk-info>
<mk-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
<mk-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
</template>
</div>
<div class="_content">
<header><fa :icon="faDiscord"/> {{ $t('discord-integration-config') }}</header>
<mk-switch v-model="enableDiscordIntegration">{{ $t('enable-discord-integration') }}</mk-switch>
<header><fa :icon="faDiscord"/> Discord</header>
<mk-switch v-model="enableDiscordIntegration">{{ $t('enable') }}</mk-switch>
<template v-if="enableDiscordIntegration">
<mk-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('discord-integration-client-id') }}</mk-input>
<mk-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('discord-integration-client-secret') }}</mk-input>
<mk-info>{{ $t('discord-integration-info', { url: `${url}/api/dc/cb` }) }}</mk-info>
<mk-info>Callback URL: {{ `${url}/api/dc/cb` }}</mk-info>
<mk-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
<mk-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
</template>
</div>
<div class="_footer">
@ -180,7 +180,7 @@ import MkTextarea from '../../components/ui/textarea.vue';
import MkSwitch from '../../components/ui/switch.vue';
import MkInfo from '../../components/ui/info.vue';
import MkUserSelect from '../../components/user-select.vue';
import { version } from '../../config';
import { version, url } from '../../config';
import i18n from '../../i18n';
import getAcct from '../../../misc/acct/render';
@ -204,6 +204,7 @@ export default Vue.extend({
data() {
return {
version,
url,
meta: null,
stats: null,
serverInfo: null,

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>
@ -246,6 +247,7 @@ export default Vue.extend({
padding: 16px 16px 0 16px;
resize: none;
font-size: 1em;
font-family: inherit;
outline: none;
border: none;
border-radius: 0;
@ -330,8 +332,7 @@ export default Vue.extend({
}
}
.attach-from-local,
.attach-from-drive {
._button {
margin: 0;
padding: 16px;
font-size: 1em;

View File

@ -8,6 +8,7 @@
<portal to="avatar"><mk-avatar class="avatar" :user="user" :disable-preview="true"/></portal>
</template>
<template v-if="!fetching && group">
<portal to="icon"><fa :icon="faUsers"/></portal>
<portal to="title">{{ group.name }}</portal>
</template>
@ -35,7 +36,7 @@
<script lang="ts">
import Vue from 'vue';
import { faArrowCircleDown, faFlag } from '@fortawesome/free-solid-svg-icons';
import { faArrowCircleDown, faFlag, faUsers } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
import XList from '../components/date-separated-list.vue';
import XMessage from './messaging-room.message.vue';
@ -63,7 +64,7 @@ export default Vue.extend({
connection: null,
showIndicator: false,
timer: null,
faArrowCircleDown, faFlag
faArrowCircleDown, faFlag, faUsers
};
},

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>
@ -117,10 +117,12 @@ export default Vue.extend({
start(ev) {
this.$root.menu({
items: [{
text: this.$t('withUser'),
text: this.$t('messagingWithUser'),
icon: faUser,
action: () => { this.startUser() }
}, {
text: this.$t('withGroup'),
text: this.$t('messagingWithGroup'),
icon: faUsers,
action: () => { this.startGroup() }
}],
noCenter: true,
@ -139,7 +141,7 @@ export default Vue.extend({
const groups2 = await this.$root.api('users/groups/joined');
const { canceled, result: group } = await this.$root.dialog({
type: null,
title: this.$t('select-group'),
title: this.$t('group'),
select: {
items: groups1.concat(groups2).map(group => ({
value: group, text: group.name
@ -148,7 +150,7 @@ export default Vue.extend({
showCancelButton: true
});
if (canceled) return;
this.navigateGroup(group);
this.$router.push(`/my/messaging/group/${group.id}`);
}
}
});
@ -282,17 +284,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,7 +3,7 @@
<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);"/>

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,122 @@
<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}" ref="invites">
<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}" ref="joined">
<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
});
},
acceptInvite(invite) {
this.$root.api('users/groups/invitations/accept', {
inviteId: invite.id
}).then(() => {
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
this.$refs.invites.reload();
this.$refs.joined.reload();
});
},
rejectInvite(invite) {
this.$root.api('users/groups/invitations/reject', {
inviteId: invite.id
}).then(() => {
this.$refs.invites.reload();
});
}
}
});
</script>
<style lang="scss" scoped>
</style>

View File

@ -7,7 +7,7 @@
<mk-pagination :pagination="pagination" #default="{items}" class="lists" ref="list">
<div class="list _panel" v-for="(list, i) in items" :key="list.id">
<router-link :to="`/lists/${ list.id }`">{{ list.name }}</router-link>
<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">
<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,54 @@
<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;
pointer-events: none;
user-select: none;
}
}
}
}
</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

@ -14,13 +14,18 @@
{{ $t('autoReloadWhenDisconnected') }}
</mk-switch>
<mk-switch v-model="$store.state.i.autoWatch" @change="onChangeAutoWatch">
{{ $t('autoNoteWatch') }}<template #desc>{{ $t('auto-watch-desc') }}</template>
{{ $t('autoNoteWatch') }}<template #desc>{{ $t('autoNoteWatchDescription') }}</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>
@ -60,6 +65,11 @@ export default Vue.extend({
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

@ -10,9 +10,11 @@
<mk-select v-model="defaultNoteVisibility" style="margin-bottom: 8px;" v-if="!rememberNoteVisibility">
<template #label>{{ $t('defaultNoteVisibility') }}</template>
<option value="public">{{ $t('_visibility.public') }}</option>
<option value="home">{{ $t('_visibility.home') }}</option>
<option value="followers">{{ $t('_visibility.followers') }}</option>
<option value="specified">{{ $t('_visibility.specified') }}</option>
</mk-select>
<mk-switch v-model="defaultNoteLocalOnly" v-if="!rememberNoteVisibility">{{ $t('_visibility.localOnly') }}</mk-switch>
</div>
</section>
</template>
@ -46,6 +48,11 @@ export default Vue.extend({
set(value) { this.$store.dispatch('settings/set', { key: 'defaultNoteVisibility', value }); }
},
defaultNoteLocalOnly: {
get() { return this.$store.state.settings.defaultNoteLocalOnly; },
set(value) { this.$store.dispatch('settings/set', { key: 'defaultNoteLocalOnly', value }); }
},
rememberNoteVisibility: {
get() { return this.$store.state.settings.rememberNoteVisibility; },
set(value) { this.$store.dispatch('settings/set', { key: 'rememberNoteVisibility', value }); }

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

@ -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>

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') },
@ -27,6 +31,7 @@ export const router = new VueRouter({
{ path: '/my/mentions', component: page('mentions') },
{ path: '/my/messaging', name: 'messaging', component: page('messaging') },
{ path: '/my/messaging/:user', component: page('messaging-room') },
{ path: '/my/messaging/group/:group', component: page('messaging-room') },
{ path: '/my/drive', name: 'drive', component: page('drive') },
{ path: '/my/drive/folder/:folder', component: page('drive') },
{ path: '/my/pages', name: 'pages', component: page('pages') },
@ -36,6 +41,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 +57,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

@ -0,0 +1,23 @@
export function focusPrev(el: Element | null, self = false) {
if (el == null) return;
if (!self) el = el.previousElementSibling;
if (el) {
if (el.hasAttribute('tabindex')) {
(el as HTMLElement).focus();
} else {
focusPrev(el.previousElementSibling, true);
}
}
}
export function focusNext(el: Element | null, self = false) {
if (el == null) return;
if (!self) el = el.nextElementSibling;
if (el) {
if (el.hasAttribute('tabindex')) {
(el as HTMLElement).focus();
} else {
focusPrev(el.nextElementSibling, true);
}
}
}

View File

@ -8,7 +8,8 @@ export default (opts) => ({
fetching: true,
moreFetching: false,
inited: false,
more: false
more: false,
backed: false,
};
},
@ -78,6 +79,7 @@ export default (opts) => ({
async fetchMore() {
if (!this.more || this.moreFetching || this.items.length === 0) return;
this.moreFetching = true;
this.backed = true;
let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params;
if (params && params.then) params = await params;
const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint;

View File

@ -5,10 +5,12 @@ import * as nestedProperty from 'nested-property';
import MiOS from './mios';
const defaultSettings = {
tutorial: 0,
keepCw: false,
showFullAcct: false,
rememberNoteVisibility: false,
defaultNoteVisibility: 'public',
defaultNoteLocalOnly: false,
uploadFolder: null,
pastedFileName: 'yyyy-MM-dd HH-mm-ss [{{number}}]',
wallpaper: null,
@ -29,6 +31,7 @@ const defaultDeviceSettings = {
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;
}

View File

@ -18,7 +18,7 @@ self.addEventListener('install', ev => {
caches.open(cacheName)
.then(cache => {
return cache.addAll([
'/assets/error.jpg'
`/?v=${version}`
]);
})
.then(() => self.skipWaiting())
@ -45,7 +45,7 @@ self.addEventListener('fetch', ev => {
return response || fetch(ev.request);
})
.catch(() => {
return caches.match('/');
return caches.match(`/?v=${version}`);
})
);
});

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,7 @@
<tr><td><kbd class="key">P</kbd>, <kbd class="key">N</kbd></td><td>新規投稿</td><td><b>P</b>ost, <b>N</b>ew, <b>N</b>ote</td></tr>
<tr><td><kbd class="key">T</kbd></td><td>タイムラインの最も新しい投稿にフォーカス</td><td><b>T</b>imeline, <b>T</b>op</td></tr>
<tr><td><kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">N</kbd></kbd></td><td>通知を表示/隠す</td><td><b>N</b>otifications</td></tr>
<tr><td><kbd class="key">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">S</kbd></td><td>検索</td><td><b>S</b>earch</td></tr>
<tr><td><kbd class="key">H</kbd>, <kbd class="key">?</kbd></td><td>ヘルプを表示</td><td><b>H</b>elp</td></tr>
</tbody>
</table>
@ -62,51 +60,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

@ -3,6 +3,7 @@ import { IRemoteUser } from '../../../../models/entities/user';
import createNote from './note';
import { ICreate, getApId, validPost } from '../../type';
import { apLogger } from '../../logger';
import { toArray, concat, unique } from '../../../../prelude/array';
const logger = apLogger;
@ -11,6 +12,22 @@ export default async (actor: IRemoteUser, activity: ICreate): Promise<void> => {
logger.info(`Create: ${uri}`);
// copy audiences between activity <=> object.
if (typeof activity.object === 'object') {
const to = unique(concat([toArray(activity.to), toArray(activity.object.to)]));
const cc = unique(concat([toArray(activity.cc), toArray(activity.object.cc)]));
activity.to = to;
activity.cc = cc;
activity.object.to = to;
activity.object.cc = cc;
}
// If there is no attributedTo, use Activity actor.
if (typeof activity.object === 'object' && !activity.object.attributedTo) {
activity.object.attributedTo = activity.actor;
}
const resolver = new Resolver();
const object = await resolver.resolve(activity.object).catch(e => {

View File

@ -15,7 +15,7 @@ export default async function(resolver: Resolver, actor: IRemoteUser, note: IObj
try {
const exist = await fetchNote(note);
if (exist == null) {
await createNote(note, resolver, silent, activity);
await createNote(note, resolver, silent);
}
} finally {
unlock();

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

@ -17,7 +17,7 @@ import { deliverQuestionUpdate } from '../../../services/note/polls/update';
import { extractDbHost, toPuny } from '../../../misc/convert-host';
import { Notes, Emojis, Polls, MessagingMessages } from '../../../models';
import { Note } from '../../../models/entities/note';
import { IObject, getOneApId, getApId, validPost, ICreate, isCreate, IPost } from '../type';
import { IObject, getOneApId, getApId, validPost, IPost } from '../type';
import { Emoji } from '../../../models/entities/emoji';
import { genId } from '../../../misc/gen-id';
import { fetchMeta } from '../../../misc/fetch-meta';
@ -78,7 +78,7 @@ export async function fetchNote(value: string | IObject, resolver?: Resolver): P
/**
* Noteを作成します。
*/
export async function createNote(value: string | IObject, resolver?: Resolver, silent = false, activity?: ICreate): Promise<Note | null> {
export async function createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<Note | null> {
if (resolver == null) resolver = new Resolver();
const object: any = await resolver.resolve(value);
@ -112,18 +112,12 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
const noteAudience = await parseAudience(actor, note.to, note.cc);
let visibility = noteAudience.visibility;
let visibleUsers = noteAudience.visibleUsers;
let apMentions = noteAudience.mentionedUsers;
const visibleUsers = noteAudience.visibleUsers;
const apMentions = noteAudience.mentionedUsers;
// Audience (to, cc) が指定されてなかった場合
if (visibility === 'specified' && visibleUsers.length === 0) {
if (activity && isCreate(activity)) {
// Create 起因ならば Activity を見る
const activityAudience = await parseAudience(actor, activity.to, activity.cc);
visibility = activityAudience.visibility;
visibleUsers = activityAudience.visibleUsers;
apMentions = activityAudience.mentionedUsers;
} else if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している
if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している
// こちらから匿名GET出来たものならばpublic
visibility = 'public';
}

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,6 +13,11 @@ export const meta = {
default: 10
},
withUnreads: {
validator: $.optional.boolean,
default: false
},
sinceId: {
validator: $.optional.type(ID),
},
@ -38,5 +43,5 @@ export default define(meta, async (ps, user) => {
}
}
return announcements;
return ps.withUnreads ? announcements.filter((a: any) => !a.isRead) : announcements;
});

View File

@ -60,8 +60,9 @@ export default class Connection {
switch (type) {
case 'api': this.onApiRequest(body); break;
case 'readNotification': this.onReadNotification(body); break;
case 'subNote': this.onSubscribeNote(body); break;
case 'sn': this.onSubscribeNote(body); break; // alias
case 'subNote': this.onSubscribeNote(body, true); break;
case 'sn': this.onSubscribeNote(body, true); break; // alias
case 's': this.onSubscribeNote(body, false); break;
case 'unsubNote': this.onUnsubscribeNote(body); break;
case 'un': this.onUnsubscribeNote(body); break; // alias
case 'connect': this.onChannelConnectRequested(body); break;
@ -107,7 +108,7 @@ export default class Connection {
* 投稿購読要求時
*/
@autobind
private onSubscribeNote(payload: any) {
private onSubscribeNote(payload: any, read: boolean) {
if (!payload.id) return;
if (this.subscribingNotes[payload.id] == null) {
@ -120,7 +121,7 @@ export default class Connection {
this.subscriber.on(`noteStream:${payload.id}`, this.onNoteStreamMessage);
}
if (this.user) {
if (this.user && read) {
readNote(this.user.id, payload.id);
}
}

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({
@ -284,6 +327,10 @@ const override = (source: string, target: string, depth: number = 0) =>
router.get('/othello', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games/reversi', 1)));
router.get('/reversi', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games')));
router.get('/flush', async ctx => {
await ctx.render('flush');
});
// Render base html for all requests
router.get('*', async ctx => {
const meta = await fetchMeta();

View File

@ -0,0 +1,20 @@
doctype html
html
script.
localStorage.removeItem('locale');
try {
navigator.serviceWorker.controller.postMessage('clear');
navigator.serviceWorker.getRegistrations().then(registrations => {
return Promise.all(registrations.map(registration => registration.unregister()));
}).then(() => {
location = '/';
});
} catch (e) {
console.error(e);
setTimeout(() => {
location = '/';
}, 10000)
}

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==