Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
90a9cf376e | |||
16d6c55407 | |||
f3508d15a3 | |||
0add490097 | |||
2d2d1bd58d | |||
7813c8a942 | |||
ac5453232f | |||
e78e5274d3 | |||
982520bcef | |||
7abd91f031 | |||
b93bfb7e5c | |||
2b20c34c1e | |||
0f63acea5b | |||
e600fb7096 | |||
b63fc71865 | |||
23e7650983 | |||
01b5ccfdc6 | |||
cc72f91465 | |||
45cb5ff4ef | |||
390279a4a8 | |||
851dececab | |||
482afa93a2 | |||
25bdbd7ae0 | |||
527a639242 | |||
0d5e000ad3 | |||
f4cb467e7a | |||
2f6b0b142a | |||
78c2535c3c | |||
26260392a8 | |||
aa573c0063 | |||
b2859bcd2a | |||
b58dd8c704 | |||
09c96286f9 | |||
f2d2089c21 | |||
79c366d1f2 | |||
c97ce5255f | |||
1fb66254a4 | |||
2d74f0507b | |||
9c06544c46 | |||
641dad586f | |||
016144b960 | |||
4d6c8efe44 | |||
860a7d1eeb | |||
2389857be8 | |||
18458f418f | |||
e812d054bc | |||
44d2c0195a | |||
42b4949b7f | |||
d915ae0807 | |||
8eec8ea35f | |||
023e0ba7aa | |||
d0d3b70c73 | |||
a509045b25 | |||
7be6501571 | |||
bb4c35d481 | |||
47ea84957d | |||
fc76f7874e | |||
77a778acf1 | |||
ff059d1268 | |||
53bb5012b9 | |||
09a3a977d7 | |||
04db5944d1 | |||
9c97bb431c | |||
38215f2cf9 | |||
01e7a01daf | |||
c2a8e29ef9 | |||
15a41e31b0 | |||
294c9840de | |||
568ecd9477 | |||
169f3ed541 | |||
ff7ae427fd | |||
1597415340 | |||
47f3261b9f | |||
9e68eefbb7 | |||
630c531d99 | |||
c7da2a4b5f | |||
692078f490 | |||
0e29e864c8 | |||
1b7a601d27 | |||
a96076ee5b | |||
d580622d1b | |||
6edccad4dd | |||
8fa47dbcb1 | |||
157f4bbc21 | |||
3b0d0df068 | |||
69802a9f00 | |||
b940da45af | |||
bd6de0e204 | |||
958074e347 | |||
988ac80087 | |||
1c7c72181e | |||
6857153367 | |||
0a3a0f3beb | |||
e92e83746d | |||
3b34b3e9ea | |||
9506f53691 | |||
92dc6db134 | |||
1b88a7bc03 |
72
CHANGELOG.md
72
CHANGELOG.md
@ -1,7 +1,77 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
12.0.0 indigo (unreleased)
|
||||
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
|
||||
* サーバー切断時に自動でリロードできるように
|
||||
|
||||
### 🐛Fixes
|
||||
* もっと読み込むを続けていくと表示が遅くなっていく問題を修正
|
||||
* Renote メニューが自分の投稿のRenoteでない限り表示されない問題を修正
|
||||
* MFM jump, spin, title が効かない問題を修正
|
||||
* AP: Likeで正しいActivity IDを提示するように修正
|
||||
* AP: Misskey以外からのトークの返信が受け取れないのを修正
|
||||
|
||||
12.0.0 indigo (2020/02/06)
|
||||
--------------------
|
||||
Misskey v12では、クライアントが設計し直され、全く新しいUIに生まれ変わりました。
|
||||
レスポンシブになり、ひとつのコードで様々なデバイスに対応できるようにしました。
|
||||
|
10
gulpfile.ts
10
gulpfile.ts
@ -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(
|
||||
|
@ -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"
|
||||
|
@ -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é"
|
||||
|
@ -28,7 +28,7 @@ gotIt: "わかった"
|
||||
cancel: "キャンセル"
|
||||
enterUsername: "ユーザー名を入力"
|
||||
renotedBy: "{user}がRenote"
|
||||
noNotes: "投稿はありません"
|
||||
noNotes: "ノートはありません"
|
||||
noNotifications: "通知はありません"
|
||||
instance: "インスタンス"
|
||||
settings: "設定"
|
||||
@ -66,13 +66,14 @@ import: "インポート"
|
||||
export: "エクスポート"
|
||||
files: "ファイル"
|
||||
download: "ダウンロード"
|
||||
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを添付した投稿も消えます。"
|
||||
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを添付したノートも消えます。"
|
||||
unfollowConfirm: "{name}のフォローを解除しますか?"
|
||||
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
|
||||
importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。"
|
||||
lists: "リスト"
|
||||
noLists: "リストはありません"
|
||||
notes: "投稿"
|
||||
note: "ノート"
|
||||
notes: "ノート"
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
followsYou: "フォローされています"
|
||||
@ -81,8 +82,6 @@ manageLists: "リストの管理"
|
||||
error: "問題が発生しました"
|
||||
retry: "再試行"
|
||||
enterListName: "リスト名を入力"
|
||||
renameList: "リスト名を変更"
|
||||
deleteList: "リストを削除"
|
||||
privacy: "プライバシー"
|
||||
makeFollowManuallyApprove: "フォローを承認制にする"
|
||||
defaultNoteVisibility: "デフォルトの公開範囲"
|
||||
@ -95,7 +94,7 @@ enterEmoji: "絵文字を入力"
|
||||
renote: "Renote"
|
||||
unrenote: "Renote解除"
|
||||
quote: "引用"
|
||||
pinnedNote: "ピン留めされた投稿"
|
||||
pinnedNote: "ピン留めされたノート"
|
||||
you: "あなた"
|
||||
clickToShow: "クリックして表示"
|
||||
sensitive: "閲覧注意"
|
||||
@ -179,7 +178,7 @@ mutedUsers: "ミュートしたユーザー"
|
||||
blockedUsers: "ブロックしたユーザー"
|
||||
noUsers: "ユーザーはいません"
|
||||
editProfile: "プロフィールを編集"
|
||||
noteDeleteConfirm: "この投稿を削除しますか?"
|
||||
noteDeleteConfirm: "このノートを削除しますか?"
|
||||
pinLimitExceeded: "これ以上ピン留めできません"
|
||||
intro: "Misskeyのインストールが完了しました!管理者アカウントを作成しましょう。"
|
||||
done: "完了"
|
||||
@ -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: "利用規約"
|
||||
@ -304,8 +303,8 @@ name: "名前"
|
||||
antennaSource: "受信ソース"
|
||||
antennaKeywords: "受信キーワード"
|
||||
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
|
||||
notifyAntenna: "新しい投稿を通知する"
|
||||
withFileAntenna: "ファイルが添付された投稿のみ"
|
||||
notifyAntenna: "新しいノートを通知する"
|
||||
withFileAntenna: "ファイルが添付されたノートのみ"
|
||||
serviceworker: "ServiceWorker"
|
||||
enableServiceworker: "ServiceWorkerを有効にする"
|
||||
antennaUsersDescription: "ユーザー名を改行で区切って指定します"
|
||||
@ -348,14 +347,69 @@ resetPassword: "パスワードをリセット"
|
||||
newPasswordIs: "新しいパスワードは「{password}」です"
|
||||
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": "アカウントの情報を見る"
|
||||
@ -368,11 +422,11 @@ _permissions:
|
||||
"write:favorites": "お気に入りを操作する"
|
||||
"read:following": "フォローの情報を見る"
|
||||
"write:following": "フォロー・フォロー解除する"
|
||||
"read:messaging": "トークを見る"
|
||||
"write:messaging": "トークを操作する"
|
||||
"read:messaging": "チャットを見る"
|
||||
"write:messaging": "チャットを操作する"
|
||||
"read:mutes": "ミュートを見る"
|
||||
"write:mutes": "ミュートを操作する"
|
||||
"write:notes": "投稿を作成・削除する"
|
||||
"write:notes": "ノートを作成・削除する"
|
||||
"read:notifications": "通知を見る"
|
||||
"write:notifications": "通知を操作する"
|
||||
"read:reactions": "リアクションを見る"
|
||||
@ -390,10 +444,10 @@ _auth:
|
||||
permissionAsk: "このアプリは次の権限を要求しています"
|
||||
|
||||
_antennaSources:
|
||||
all: "全ての投稿"
|
||||
homeTimeline: "フォローしているユーザーの投稿"
|
||||
users: "指定した一人または複数のユーザーの投稿"
|
||||
userList: "指定したリストのユーザーの投稿"
|
||||
all: "全てのノート"
|
||||
homeTimeline: "フォローしているユーザーのノート"
|
||||
users: "指定した一人または複数のユーザーのノート"
|
||||
userList: "指定したリストのユーザーのノート"
|
||||
|
||||
_weekday:
|
||||
sunday: "日曜日"
|
||||
@ -411,6 +465,7 @@ _widgets:
|
||||
calendar: "カレンダー"
|
||||
trends: "トレンド"
|
||||
clock: "時計"
|
||||
rss: "RSSリーダー"
|
||||
|
||||
_cw:
|
||||
hide: "隠す"
|
||||
@ -451,10 +506,11 @@ _visibility:
|
||||
followersDescription: "自分のフォロワーのみに公開"
|
||||
specified: "ダイレクト"
|
||||
specifiedDescription: "指定したユーザーのみに公開"
|
||||
localOnly: "ローカルのみ"
|
||||
|
||||
_postForm:
|
||||
replyPlaceholder: "この投稿に返信..."
|
||||
quotePlaceholder: "この投稿を引用..."
|
||||
replyPlaceholder: "このノートに返信..."
|
||||
quotePlaceholder: "このノートを引用..."
|
||||
_placeholders:
|
||||
a: "いまどうしてる?"
|
||||
b: "何かありましたか?"
|
||||
@ -473,7 +529,7 @@ _profile:
|
||||
metadataContent: "内容"
|
||||
|
||||
_exportOrImport:
|
||||
allNotes: "全ての投稿"
|
||||
allNotes: "全てのノート"
|
||||
followingList: "フォロー"
|
||||
muteList: "ミュート"
|
||||
blockingList: "ブロック"
|
||||
@ -485,10 +541,10 @@ _charts:
|
||||
usersIncDec: "ユーザーの増減"
|
||||
usersTotal: "ユーザーの合計"
|
||||
activeUsers: "アクティブユーザー数"
|
||||
notesIncDec: "投稿の増減"
|
||||
localNotesIncDec: "ローカルの投稿の増減"
|
||||
remoteNotesIncDec: "リモートの投稿の増減"
|
||||
notesTotal: "投稿の合計"
|
||||
notesIncDec: "ノートの増減"
|
||||
localNotesIncDec: "ローカルのノートの増減"
|
||||
remoteNotesIncDec: "リモートのノートの増減"
|
||||
notesTotal: "ノートの合計"
|
||||
filesIncDec: "ファイルの増減"
|
||||
filesTotal: "ファイルの合計"
|
||||
storageUsageIncDec: "ストレージ使用量の増減"
|
||||
@ -498,8 +554,8 @@ _instanceCharts:
|
||||
requests: "リクエスト"
|
||||
users: "ユーザーの増減"
|
||||
usersTotal: "ユーザーの積算"
|
||||
notes: "投稿の増減"
|
||||
notesTotal: "投稿の積算"
|
||||
notes: "ノートの増減"
|
||||
notesTotal: "ノートの積算"
|
||||
ff: "フォロー/フォロワーの増減"
|
||||
ffTotal: "フォロー/フォロワーの積算"
|
||||
cacheSize: "キャッシュサイズの増減"
|
||||
|
@ -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: "캐시 용량 증감"
|
||||
|
@ -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>A,B,C</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: "输入变量"
|
||||
|
@ -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",
|
||||
|
@ -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));
|
||||
},
|
||||
|
||||
@ -222,6 +239,10 @@ export default Vue.extend({
|
||||
|
||||
this.$root.stream.on('_disconnected_', () => {
|
||||
if (!this.disconnectedDialog) {
|
||||
if (this.$store.state.device.autoReload) {
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
this.disconnectedDialog = this.$root.dialog({
|
||||
type: 'warning',
|
||||
showCancelButton: true,
|
||||
@ -237,12 +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);
|
||||
@ -250,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();
|
||||
},
|
||||
@ -284,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);
|
||||
|
||||
@ -440,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'),
|
||||
@ -473,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',
|
||||
@ -572,12 +553,6 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@keyframes blink {
|
||||
0% { opacity: 1; }
|
||||
30% { opacity: 1; }
|
||||
90% { opacity: 0; }
|
||||
}
|
||||
|
||||
.header-enter-active, .header-leave-active {
|
||||
transition: opacity 0.5s, transform 0.5s !important;
|
||||
}
|
||||
@ -602,6 +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;
|
||||
@ -701,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;
|
||||
@ -740,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;
|
||||
@ -761,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 {
|
||||
@ -771,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 {
|
||||
@ -785,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;
|
||||
@ -795,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));
|
||||
}
|
||||
@ -843,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;
|
||||
@ -864,6 +874,13 @@ export default Vue.extend({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $nav-hide-threshold) {
|
||||
> .index,
|
||||
> .notifications {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -967,10 +984,10 @@ export default Vue.extend({
|
||||
> header {
|
||||
position: relative;
|
||||
line-height: 32px;
|
||||
background: #eee;
|
||||
|
||||
> .handle {
|
||||
padding: 0 8px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
> .remove {
|
||||
|
@ -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>
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<sequential-entrance class="sqadhkmv" ref="list" :direction="direction">
|
||||
<sequential-entrance class="sqadhkmv" ref="list" :direction="direction" :reversed="reversed">
|
||||
<template v-for="(item, i) in items">
|
||||
<slot :item="item" :i="i"></slot>
|
||||
<div class="separator" :key="item.id + '_date'" :data-index="i" v-if="i != items.length - 1 && new Date(item.createdAt).getDate() != new Date(items[i + 1].createdAt).getDate()">
|
||||
<div class="separator" :key="item.id + '_date'" v-if="i != items.length - 1 && new Date(item.createdAt).getDate() != new Date(items[i + 1].createdAt).getDate()">
|
||||
<p class="date">
|
||||
<span><fa class="icon" :icon="faAngleUp"/>{{ getDateText(item.createdAt) }}</span>
|
||||
<span>{{ getDateText(items[i + 1].createdAt) }}<fa class="icon" :icon="faAngleDown"/></span>
|
||||
@ -28,6 +28,11 @@ export default Vue.extend({
|
||||
direction: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
reversed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -133,6 +133,7 @@ export default Vue.extend({
|
||||
<style lang="scss" scoped>
|
||||
.zdjebgpv {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
> img,
|
||||
> .icon {
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -1,27 +1,27 @@
|
||||
<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" :data-index="i"></div>
|
||||
<span v-else-if="item.type === 'label'" class="label item" :key="i" :data-index="i">
|
||||
<div v-if="item === null" class="divider" :key="i"></div>
|
||||
<span v-else-if="item.type === 'label'" class="label item" :key="i">
|
||||
<span>{{ item.text }}</span>
|
||||
</span>
|
||||
<router-link v-else-if="item.type === 'link'" :to="item.to" @click.native="close()" :tabindex="i" class="_button item" :key="i" :data-index="i">
|
||||
<router-link v-else-if="item.type === 'link'" :to="item.to" @click.native="close()" :tabindex="i" class="_button item" :key="i">
|
||||
<fa v-if="item.icon" :icon="item.icon" fixed-width/>
|
||||
<mk-avatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
|
||||
<span>{{ item.text }}</span>
|
||||
<i v-if="item.indicate"><fa :icon="faCircle"/></i>
|
||||
</router-link>
|
||||
<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" @click="close()" :tabindex="i" class="_button item" :key="i" :data-index="i">
|
||||
<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" @click="close()" :tabindex="i" class="_button item" :key="i">
|
||||
<fa v-if="item.icon" :icon="item.icon" fixed-width/>
|
||||
<span>{{ item.text }}</span>
|
||||
<i v-if="item.indicate"><fa :icon="faCircle"/></i>
|
||||
</a>
|
||||
<button v-else-if="item.type === 'user'" @click="clicked(item.action)" :tabindex="i" class="_button item" :key="i" :data-index="i">
|
||||
<button v-else-if="item.type === 'user'" @click="clicked(item.action)" :tabindex="i" class="_button item" :key="i">
|
||||
<mk-avatar :user="item.user" class="avatar"/><mk-user-name :user="item.user"/>
|
||||
<i v-if="item.indicate"><fa :icon="faCircle"/></i>
|
||||
</button>
|
||||
<button v-else @click="clicked(item.action)" :tabindex="i" class="_button item" :key="i" :data-index="i">
|
||||
<button v-else @click="clicked(item.action)" :tabindex="i" class="_button item" :key="i">
|
||||
<fa v-if="item.icon" :icon="item.icon" fixed-width/>
|
||||
<mk-avatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
|
||||
<span>{{ item.text }}</span>
|
||||
@ -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,18 +102,18 @@ export default Vue.extend({
|
||||
},
|
||||
close() {
|
||||
this.$refs.popup.close();
|
||||
},
|
||||
focusUp() {
|
||||
focusPrev(document.activeElement);
|
||||
},
|
||||
focusDown() {
|
||||
focusNext(document.activeElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@keyframes blink {
|
||||
0% { opacity: 1; }
|
||||
30% { opacity: 1; }
|
||||
90% { opacity: 0; }
|
||||
}
|
||||
|
||||
.rrevdjwt {
|
||||
padding: 8px 0;
|
||||
|
||||
@ -125,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;
|
||||
|
@ -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))];
|
||||
}
|
||||
|
@ -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">
|
||||
@ -36,5 +36,10 @@ export default Vue.extend({
|
||||
::v-deep pre {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
::v-deep .title {
|
||||
text-align: center;
|
||||
border-bottom: solid 1px var(--divider);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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,21 +451,115 @@ 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) {
|
||||
if (!this.isMyNote) return;
|
||||
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: [{
|
||||
text: this.$t('unrenote'),
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,11 +1,14 @@
|
||||
<template>
|
||||
<div class="mk-notes" v-size="[{ max: 500 }]">
|
||||
<div class="empty" v-if="empty">{{ $t('noNotes') }}</div>
|
||||
<div class="empty" v-if="empty">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" alt=""/>
|
||||
<div>{{ $t('noNotes') }}</div>
|
||||
</div>
|
||||
|
||||
<mk-error v-if="error" @retry="init()"/>
|
||||
|
||||
<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note, i }">
|
||||
<x-note :note="note" :detail="detail" :key="note.id" :data-index="i"/>
|
||||
<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note }">
|
||||
<x-note :note="note" :detail="detail" :key="note.id"/>
|
||||
</x-list>
|
||||
|
||||
<footer v-if="more">
|
||||
@ -24,8 +27,6 @@ import i18n from '../i18n';
|
||||
import paging from '../scripts/paging';
|
||||
import XNote from './note.vue';
|
||||
import XList from './date-separated-list.vue';
|
||||
import getUserName from '../../misc/get-user-name';
|
||||
import getNoteSummary from '../../misc/get-note-summary';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n,
|
||||
@ -36,19 +37,6 @@ export default Vue.extend({
|
||||
|
||||
mixins: [
|
||||
paging({
|
||||
onPrepend: (self, note) => {
|
||||
// タブが非表示なら通知
|
||||
if (document.hidden) {
|
||||
if ('Notification' in window && Notification.permission === 'granted') {
|
||||
new Notification(getUserName(note.user), {
|
||||
body: getNoteSummary(note),
|
||||
icon: note.user.avatarUrl,
|
||||
tag: 'newNote'
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
before: (self) => {
|
||||
self.$emit('before');
|
||||
},
|
||||
@ -98,14 +86,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 {
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="mk-notifications">
|
||||
<div class="contents">
|
||||
<x-list class="notifications" :items="items" v-slot="{ item: notification, i }">
|
||||
<x-notification :notification="notification" :with-time="true" :full="true" class="notification" :key="notification.id" :data-index="i"/>
|
||||
<x-notification :notification="notification" :with-time="true" :full="true" class="notification" :key="notification.id"/>
|
||||
</x-list>
|
||||
|
||||
<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -13,7 +13,7 @@
|
||||
mode="out-in"
|
||||
appear
|
||||
>
|
||||
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :data-index="i" :tabindex="i + 1" :title="/^[a-z]+$/.test(reaction) ? $t('@.reactions.' + reaction) : reaction"><x-reaction-icon :reaction="reaction"/></button>
|
||||
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="/^[a-z]+$/.test(reaction) ? $t('@.reactions.' + reaction) : reaction"><x-reaction-icon :reaction="reaction"/></button>
|
||||
</transition-group>
|
||||
<input class="text" v-model="text" :placeholder="$t('enterEmoji')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
|
||||
</div>
|
||||
|
@ -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>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<transition-group
|
||||
<transition-group v-if="$store.state.device.animation"
|
||||
name="staggered-fade"
|
||||
tag="div"
|
||||
:css="false"
|
||||
@ -11,6 +11,9 @@
|
||||
>
|
||||
<slot></slot>
|
||||
</transition-group>
|
||||
<div v-else>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@ -27,27 +30,37 @@ export default Vue.extend({
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'down'
|
||||
},
|
||||
reversed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
i: 0,
|
||||
methods: {
|
||||
beforeEnter(el) {
|
||||
el.style.opacity = 0;
|
||||
el.style.transform = this.direction === 'down' ? 'translateY(-64px)' : 'translateY(64px)';
|
||||
let index = this.$options.i;
|
||||
const delay = this.delay * index;
|
||||
el.style.transition = [getComputedStyle(el).transition, `transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) ${delay}ms`, `opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1) ${delay}ms`].filter(x => x != '').join(',');
|
||||
this.$options.i++;
|
||||
},
|
||||
enter(el, done) {
|
||||
el.style.transition = [getComputedStyle(el).transition, 'transform 0.7s cubic-bezier(0.23, 1, 0.32, 1)', 'opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1)'].filter(x => x != '').join(',');
|
||||
setTimeout(() => {
|
||||
el.style.opacity = 1;
|
||||
el.style.transform = 'translateY(0px)';
|
||||
setTimeout(done, 700);
|
||||
}, this.delay * el.dataset.index)
|
||||
el.addEventListener('transitionend', () => {
|
||||
el.style.transition = '';
|
||||
this.$options.i--;
|
||||
done();
|
||||
}, { once: true });
|
||||
});
|
||||
},
|
||||
leave(el, done) {
|
||||
setTimeout(() => {
|
||||
el.style.opacity = 0;
|
||||
el.style.transform = this.direction === 'down' ? 'translateY(64px)' : 'translateY(-64px)';
|
||||
setTimeout(done, 700);
|
||||
}, this.delay * el.dataset.index)
|
||||
leave(el) {
|
||||
el.style.opacity = 0;
|
||||
el.style.transform = this.direction === 'down' ? 'translateY(64px)' : 'translateY(-64px)';
|
||||
},
|
||||
focus() {
|
||||
this.$slots.default[0].elm.focus();
|
||||
|
@ -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');
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
|
||||
<sequential-entrance class="users">
|
||||
<router-link v-for="(item, i) in items" class="user" :key="item.id" :data-index="i" :to="extract ? extract(item) : item | userPage">
|
||||
<router-link v-for="(item, i) in items" class="user" :key="item.id" :to="extract ? extract(item) : item | userPage">
|
||||
<mk-avatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true"/>
|
||||
<div class="body">
|
||||
<mk-user-name :user="extract ? extract(item) : item" class="name"/>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
<portal to="title">{{ $t('announcements') }}</portal>
|
||||
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="ruryvtyk" ref="list">
|
||||
<section class="_card announcement" v-for="(announcement, i) in items" :key="announcement.id" :data-index="i">
|
||||
<section class="_card announcement" v-for="(announcement, i) in items" :key="announcement.id">
|
||||
<div class="_title"><span v-if="$store.getters.isSignedIn && !announcement.isRead">🆕 </span>{{ announcement.title }}</div>
|
||||
<div class="_content">
|
||||
<mfm :text="announcement.text"/>
|
||||
|
134
src/client/pages/doc.vue
Normal file
134
src/client/pages/doc.vue
Normal 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
42
src/client/pages/docs.vue
Normal 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>
|
@ -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,
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="mk-follow-requests" ref="list">
|
||||
<div class="user _panel" v-for="(req, i) in items" :key="req.id" :data-index="i">
|
||||
<div class="user _panel" v-for="(req, i) in items" :key="req.id">
|
||||
<mk-avatar class="avatar" :user="req.follower"/>
|
||||
<div class="body">
|
||||
<div class="name">
|
||||
|
127
src/client/pages/index.home.tutorial.vue
Normal file
127
src/client/pages/index.home.tutorial.vue
Normal file
@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="_card tbkwesmv">
|
||||
<div class="_title"><fa :icon="faInfoCircle"/> {{ $t('_tutorial.title') }}</div>
|
||||
<div class="_content" v-if="tutorial === 0">
|
||||
<div>{{ $t('_tutorial.step1_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step1_2') }}</div>
|
||||
<div>{{ $t('_tutorial.step1_3') }}</div>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 1">
|
||||
<div>{{ $t('_tutorial.step2_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step2_2') }}</div>
|
||||
<router-link class="_link" to="/my/settings">{{ $t('editProfile') }}</router-link>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 2">
|
||||
<div>{{ $t('_tutorial.step3_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step3_2') }}</div>
|
||||
<div>{{ $t('_tutorial.step3_3') }}</div>
|
||||
<small>{{ $t('_tutorial.step3_4') }}</small>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 3">
|
||||
<div>{{ $t('_tutorial.step4_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step4_2') }}</div>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 4">
|
||||
<div>{{ $t('_tutorial.step5_1') }}</div>
|
||||
<i18n path="_tutorial.step5_2" tag="div">
|
||||
<router-link class="_link" place="featured" to="/featured">{{ $t('featured') }}</router-link>
|
||||
<router-link class="_link" place="explore" to="/explore">{{ $t('explore') }}</router-link>
|
||||
</i18n>
|
||||
<div>{{ $t('_tutorial.step5_3') }}</div>
|
||||
<small>{{ $t('_tutorial.step5_4') }}</small>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 5">
|
||||
<div>{{ $t('_tutorial.step6_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step6_2') }}</div>
|
||||
<div>{{ $t('_tutorial.step6_3') }}</div>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 6">
|
||||
<div>{{ $t('_tutorial.step7_1') }}</div>
|
||||
<i18n path="_tutorial.step7_2" tag="div">
|
||||
<router-link class="_link" place="help" to="/docs">{{ $t('help') }}</router-link>
|
||||
</i18n>
|
||||
<div>{{ $t('_tutorial.step7_3') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="_footer navigation">
|
||||
<div class="step">
|
||||
<button class="arrow _button" @click="tutorial--" :disabled="tutorial === 0">
|
||||
<fa :icon="faChevronLeft"/>
|
||||
</button>
|
||||
<span>{{ tutorial + 1 }} / 7</span>
|
||||
<button class="arrow _button" @click="tutorial++" :disabled="tutorial === 6">
|
||||
<fa :icon="faChevronRight"/>
|
||||
</button>
|
||||
</div>
|
||||
<mk-button class="ok" @click="tutorial = -1" primary v-if="tutorial === 6"><fa :icon="faCheck"/> {{ $t('gotIt') }}</mk-button>
|
||||
<mk-button class="ok" @click="tutorial++" primary v-else><fa :icon="faCheck"/> {{ $t('next') }}</mk-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faInfoCircle, faChevronLeft, faChevronRight, faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||
import MkButton from '../components/ui/button.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
MkButton,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
faInfoCircle, faChevronLeft, faChevronRight, faCheck
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
tutorial: {
|
||||
get() { return this.$store.state.settings.tutorial || 0; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'tutorial', value }); }
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tbkwesmv {
|
||||
> ._content {
|
||||
> small {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
> .navigation {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
|
||||
> .step {
|
||||
> .arrow {
|
||||
padding: 4px;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> span {
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
> .ok {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -13,6 +13,9 @@
|
||||
<fa :icon="menuOpened ? faAngleUp : faAngleDown" style="margin-left: 8px;"/>
|
||||
</button>
|
||||
</portal>
|
||||
|
||||
<x-tutorial class="tutorial" v-if="$store.state.settings.tutorial != -1"/>
|
||||
|
||||
<x-timeline ref="tl" :key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src" :src="src" :list="list" :antenna="antenna" @before="before()" @after="after()"/>
|
||||
</div>
|
||||
</template>
|
||||
@ -23,6 +26,7 @@ import { faAngleDown, faAngleUp, faHome, faShareAlt, faGlobe, faListUl, faSatell
|
||||
import { faComments } from '@fortawesome/free-regular-svg-icons';
|
||||
import Progress from '../scripts/loading';
|
||||
import XTimeline from '../components/timeline.vue';
|
||||
import XTutorial from './index.home.tutorial.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
metaInfo() {
|
||||
@ -32,7 +36,8 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
components: {
|
||||
XTimeline
|
||||
XTimeline,
|
||||
XTutorial,
|
||||
},
|
||||
|
||||
props: {
|
||||
@ -57,7 +62,7 @@ export default Vue.extend({
|
||||
return {
|
||||
't': this.focus
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
@ -172,11 +177,11 @@ export default Vue.extend({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@keyframes blink {
|
||||
0% { opacity: 1; }
|
||||
30% { opacity: 1; }
|
||||
90% { opacity: 0; }
|
||||
<style lang="scss" scoped>
|
||||
.mk-home {
|
||||
> .tutorial {
|
||||
margin-bottom: var(--margin);
|
||||
}
|
||||
}
|
||||
|
||||
._kjvfvyph_ {
|
||||
|
@ -9,7 +9,7 @@
|
||||
<mk-pagination :pagination="pagination" class="emojis" ref="emojis">
|
||||
<template #empty><span>{{ $t('noCustomEmojis') }}</span></template>
|
||||
<template #default="{items}">
|
||||
<div class="emoji" v-for="(emoji, i) in items" :key="emoji.id" :data-index="i" @click="selected = emoji" :class="{ selected: selected && (selected.id === emoji.id) }">
|
||||
<div class="emoji" v-for="(emoji, i) in items" :key="emoji.id" @click="selected = emoji" :class="{ selected: selected && (selected.id === emoji.id) }">
|
||||
<img :src="emoji.url" class="img" :alt="emoji.name"/>
|
||||
<div class="body">
|
||||
<span class="name">{{ emoji.name }}</span>
|
||||
@ -30,7 +30,7 @@
|
||||
<mk-pagination :pagination="remotePagination" class="emojis" ref="remoteEmojis">
|
||||
<template #empty><span>{{ $t('noCustomEmojis') }}</span></template>
|
||||
<template #default="{items}">
|
||||
<div class="emoji" v-for="(emoji, i) in items" :key="emoji.id" :data-index="i" @click="selectedRemote = emoji" :class="{ selected: selectedRemote && (selectedRemote.id === emoji.id) }">
|
||||
<div class="emoji" v-for="(emoji, i) in items" :key="emoji.id" @click="selectedRemote = emoji" :class="{ selected: selectedRemote && (selectedRemote.id === emoji.id) }">
|
||||
<img :src="emoji.url" class="img" :alt="emoji.name"/>
|
||||
<div class="body">
|
||||
<span class="name">{{ emoji.name }}</span>
|
||||
|
@ -18,7 +18,7 @@
|
||||
</div>
|
||||
<div class="_content">
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="instances" ref="instances" :key="host + state">
|
||||
<div class="instance" v-for="(instance, i) in items" :key="instance.id" :data-index="i" @click="info(instance)">
|
||||
<div class="instance" v-for="(instance, i) in items" :key="instance.id" @click="info(instance)">
|
||||
<div class="host"><fa :icon="faCircle" class="indicator" :class="getStatus(instance)"/><b>{{ instance.host }}</b></div>
|
||||
<div class="status">
|
||||
<span class="sub" v-if="instance.followersCount > 0"><fa :icon="faCaretDown" class="icon"/>Sub</span>
|
||||
|
@ -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,
|
||||
|
@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<div class="_content" style="max-height: 180px; overflow: auto;">
|
||||
<sequential-entrance :delay="15" v-if="jobs.length > 0">
|
||||
<div v-for="(job, i) in jobs" :key="job[0]" :data-index="i">
|
||||
<div v-for="(job, i) in jobs" :key="job[0]">
|
||||
<span>{{ job[0] }}</span>
|
||||
<span style="margin-left: 8px; opacity: 0.7;">({{ job[1] | number }} jobs)</span>
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@
|
||||
<div class="_title"><fa :icon="faUsers"/> {{ $t('users') }}</div>
|
||||
<div class="_content _list">
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="users" ref="users" :auto-margin="false">
|
||||
<button class="user _button _listItem" v-for="(user, i) in items" :key="user.id" :data-index="i" @click="show(user)">
|
||||
<button class="user _button _listItem" v-for="(user, i) in items" :key="user.id" @click="show(user)">
|
||||
<mk-avatar :user="user" class="avatar"/>
|
||||
<div class="body">
|
||||
<mk-user-name :user="user" class="name"/>
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
||||
@ -18,8 +19,8 @@
|
||||
<button class="more _button" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
|
||||
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
|
||||
</button>
|
||||
<x-list class="messages" :items="messages" v-slot="{ item: message, i }" direction="up">
|
||||
<x-message :message="message" :is-group="group != null" :key="message.id" :data-index="messages.length - i"/>
|
||||
<x-list class="messages" :items="messages" v-slot="{ item: message, i }" direction="up" reversed>
|
||||
<x-message :message="message" :is-group="group != null" :key="message.id"/>
|
||||
</x-list>
|
||||
</div>
|
||||
<footer>
|
||||
@ -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
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -3,12 +3,12 @@
|
||||
<portal to="icon"><fa :icon="faSatellite"/></portal>
|
||||
<portal to="title">{{ $t('manageAntennas') }}</portal>
|
||||
|
||||
<mk-button @click="create" primary class="add"><fa :icon="faPlus"/> {{ $t('createAntenna') }}</mk-button>
|
||||
<mk-button @click="create" primary class="add"><fa :icon="faPlus"/> {{ $t('add') }}</mk-button>
|
||||
|
||||
<x-antenna v-if="draft" :antenna="draft" @created="onAntennaCreated" style="margin-bottom: var(--margin);"/>
|
||||
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="antennas" ref="list">
|
||||
<x-antenna v-for="(antenna, i) in items" :key="antenna.id" :data-index="i" :antenna="antenna" @created="onAntennaDeleted"/>
|
||||
<x-antenna v-for="(antenna, i) in items" :key="antenna.id" :antenna="antenna" @created="onAntennaDeleted"/>
|
||||
</mk-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
214
src/client/pages/my-groups/group.vue
Normal file
214
src/client/pages/my-groups/group.vue
Normal 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>
|
122
src/client/pages/my-groups/index.vue
Normal file
122
src/client/pages/my-groups/index.vue
Normal 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>
|
@ -6,8 +6,8 @@
|
||||
<mk-button @click="create" primary class="add"><fa :icon="faPlus"/> {{ $t('createList') }}</mk-button>
|
||||
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="lists" ref="list">
|
||||
<div class="list _panel" v-for="(list, i) in items" :key="list.id" :data-index="i">
|
||||
<router-link :to="`/lists/${ list.id }`">{{ list.name }}</router-link>
|
||||
<div class="list _panel" v-for="(list, i) in items" :key="list.id">
|
||||
<router-link :to="`/my/lists/${ list.id }`">{{ list.name }}</router-link>
|
||||
</div>
|
||||
</mk-pagination>
|
||||
</div>
|
||||
@ -62,7 +62,7 @@ export default Vue.extend({
|
||||
<style lang="scss" scoped>
|
||||
.qkcjvfiv {
|
||||
> .add {
|
||||
margin: 0 auto 16px auto;
|
||||
margin: 0 auto var(--margin) auto;
|
||||
}
|
||||
|
||||
> .lists {
|
||||
|
@ -1,11 +1,23 @@
|
||||
<template>
|
||||
<div class="mk-list-page">
|
||||
<portal to="icon"><fa :icon="faListUl"/></portal>
|
||||
<portal to="title">{{ list.name }}</portal>
|
||||
|
||||
<transition name="zoom" mode="out-in">
|
||||
<div v-if="list" :key="list.id" class="_card list">
|
||||
<div class="_title">{{ list.name }}</div>
|
||||
<div v-if="list" class="_card">
|
||||
<div class="_content">
|
||||
<mk-button inline @click="renameList()">{{ $t('rename') }}</mk-button>
|
||||
<mk-button inline @click="deleteList()">{{ $t('delete') }}</mk-button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<transition name="zoom" mode="out-in">
|
||||
<div v-if="list" class="_card members">
|
||||
<div class="_title">{{ $t('members') }}</div>
|
||||
<div class="_content">
|
||||
<div class="users">
|
||||
<div class="user" v-for="(user, i) in users" :key="user.id" :data-index="i">
|
||||
<div class="user" v-for="user in users" :key="user.id">
|
||||
<mk-avatar :user="user" class="avatar"/>
|
||||
<div class="body">
|
||||
<mk-user-name :user="user" class="name"/>
|
||||
@ -18,8 +30,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="_footer">
|
||||
<mk-button inline @click="renameList()">{{ $t('renameList') }}</mk-button>
|
||||
<mk-button inline @click="deleteList()">{{ $t('deleteList') }}</mk-button>
|
||||
<mk-button inline @click="addUser()">{{ $t('addUser') }}</mk-button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
@ -28,10 +39,11 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faTimes, faListUl } from '@fortawesome/free-solid-svg-icons';
|
||||
import i18n from '../../i18n';
|
||||
import Progress from '../../scripts/loading';
|
||||
import MkButton from '../../components/ui/button.vue';
|
||||
import MkUserSelect from '../../components/user-select.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n,
|
||||
@ -50,7 +62,7 @@ export default Vue.extend({
|
||||
return {
|
||||
list: null,
|
||||
users: [],
|
||||
faTimes
|
||||
faTimes, faListUl
|
||||
};
|
||||
},
|
||||
|
||||
@ -78,6 +90,26 @@ export default Vue.extend({
|
||||
});
|
||||
},
|
||||
|
||||
addUser() {
|
||||
this.$root.new(MkUserSelect, {}).$once('selected', user => {
|
||||
this.$root.api('users/lists/push', {
|
||||
listId: this.list.id,
|
||||
userId: user.id
|
||||
}).then(() => {
|
||||
this.users.push(user);
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
iconOnly: true, autoClose: true
|
||||
});
|
||||
}).catch(e => {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: e
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
removeUser(user) {
|
||||
this.$root.api('users/lists/pull', {
|
||||
listId: this.list.id,
|
||||
@ -107,7 +139,7 @@ export default Vue.extend({
|
||||
async deleteList() {
|
||||
const { canceled } = await this.$root.dialog({
|
||||
type: 'warning',
|
||||
text: this.$t('deleteListConfirm', { list: this.list.name }),
|
||||
text: this.$t('removeAreYouSure', { x: this.list.name }),
|
||||
showCancelButton: true
|
||||
});
|
||||
if (canceled) return;
|
||||
@ -127,7 +159,7 @@ export default Vue.extend({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mk-list-page {
|
||||
> .list {
|
||||
> .members {
|
||||
> ._content {
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
|
54
src/client/pages/not-found.vue
Normal file
54
src/client/pages/not-found.vue
Normal 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>
|
@ -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>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="_title"><fa :icon="faCloud"/> {{ $t('drive') }}</div>
|
||||
<div class="_content">
|
||||
<mk-pagination :pagination="drivePagination" #default="{items}" class="drive" ref="drive">
|
||||
<div class="file" v-for="(file, i) in items" :key="file.id" :data-index="i" @click="selected = file" :class="{ selected: selected && (selected.id === file.id) }">
|
||||
<div class="file" v-for="(file, i) in items" :key="file.id" @click="selected = file" :class="{ selected: selected && (selected.id === file.id) }">
|
||||
<x-file-thumbnail class="thumbnail" :file="file" fit="cover"/>
|
||||
<div class="body">
|
||||
<p class="name">
|
||||
|
@ -10,14 +10,22 @@
|
||||
<mk-button primary :disabled="$store.state.settings.wallpaper == null" @click="delWallpaper()">{{ $t('removeWallpaper') }}</mk-button>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<mk-switch v-model="autoReload">
|
||||
{{ $t('autoReloadWhenDisconnected') }}
|
||||
</mk-switch>
|
||||
<mk-switch v-model="$store.state.i.autoWatch" @change="onChangeAutoWatch">
|
||||
{{ $t('auto-watch') }}<template #desc>{{ $t('auto-watch-desc') }}</template>
|
||||
{{ $t('autoNoteWatch') }}<template #desc>{{ $t('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>
|
||||
@ -52,6 +60,16 @@ export default Vue.extend({
|
||||
get() { return this.$store.state.settings.wallpaper; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'wallpaper', value }); }
|
||||
},
|
||||
|
||||
autoReload: {
|
||||
get() { return this.$store.state.device.autoReload; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'autoReload', value }); }
|
||||
},
|
||||
|
||||
reduceAnimation: {
|
||||
get() { return !this.$store.state.device.animation; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'animation', value: !value }); }
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -6,7 +6,7 @@
|
||||
<mk-pagination :pagination="mutingPagination" class="muting">
|
||||
<template #empty><span>{{ $t('noUsers') }}</span></template>
|
||||
<template #default="{items}">
|
||||
<div class="user" v-for="(mute, i) in items" :key="mute.id" :data-index="i">
|
||||
<div class="user" v-for="(mute, i) in items" :key="mute.id">
|
||||
<router-link class="name" :to="mute.mutee | userPage">
|
||||
<mk-acct :user="mute.mutee"/>
|
||||
</router-link>
|
||||
@ -19,7 +19,7 @@
|
||||
<mk-pagination :pagination="blockingPagination" class="blocking">
|
||||
<template #empty><span>{{ $t('noUsers') }}</span></template>
|
||||
<template #default="{items}">
|
||||
<div class="user" v-for="(block, i) in items" :key="block.id" :data-index="i">
|
||||
<div class="user" v-for="(block, i) in items" :key="block.id">
|
||||
<router-link class="name" :to="block.blockee | userPage">
|
||||
<mk-acct :user="block.blockee"/>
|
||||
</router-link>
|
||||
|
@ -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 }); }
|
||||
|
85
src/client/pages/share.vue
Normal file
85
src/client/pages/share.vue
Normal 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>
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="mk-following-or-followers" ref="list">
|
||||
<div class="user _panel" v-for="(user, i) in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" :data-index="i">
|
||||
<div class="user _panel" v-for="(user, i) in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id">
|
||||
<mk-avatar class="avatar" :user="user"/>
|
||||
<div class="body">
|
||||
<div class="name">
|
||||
|
@ -4,7 +4,7 @@
|
||||
<portal to="avatar" v-if="user"><mk-avatar class="avatar" :user="user" :disable-preview="true"/></portal>
|
||||
|
||||
<div class="remote-caution _panel" v-if="user.host != null"><fa :icon="faExclamationTriangle" style="margin-right: 8px;"/>{{ $t('remoteUserCaution') }}<a :href="user.url" rel="nofollow noopener" target="_blank">{{ $t('showOnRemote') }}</a></div>
|
||||
<transition name="zoom" mode="out-in" appear>
|
||||
<transition :name="$store.state.device.animation ? 'zoom' : ''" mode="out-in" appear>
|
||||
<div class="profile _panel" :key="user.id">
|
||||
<div class="banner-container" :style="style">
|
||||
<div class="banner" ref="banner" :style="style"></div>
|
||||
@ -83,7 +83,7 @@
|
||||
<router-view :user="user"></router-view>
|
||||
<template v-if="$route.name == 'user'">
|
||||
<sequential-entrance class="pins">
|
||||
<x-note v-for="(note, i) in user.pinnedNotes" class="note" :note="note" :key="note.id" :data-index="i" :detail="true" :pinned="true"/>
|
||||
<x-note v-for="(note, i) in user.pinnedNotes" class="note" :note="note" :key="note.id" :detail="true" :pinned="true"/>
|
||||
</sequential-entrance>
|
||||
<mk-container :body-togglable="true" class="content">
|
||||
<template #header><fa :icon="faImage"/>{{ $t('images') }}</template>
|
||||
@ -269,10 +269,11 @@ export default Vue.extend({
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
padding: 4px 6px;
|
||||
padding: 4px 8px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
font-size: 12px;
|
||||
font-size: 0.7em;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
> .actions {
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
23
src/client/scripts/focus.ts
Normal file
23
src/client/scripts/focus.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,8 @@ export default (opts) => ({
|
||||
fetching: true,
|
||||
moreFetching: false,
|
||||
inited: false,
|
||||
more: false
|
||||
more: false,
|
||||
backed: false,
|
||||
};
|
||||
},
|
||||
|
||||
@ -78,6 +79,7 @@ export default (opts) => ({
|
||||
async fetchMore() {
|
||||
if (!this.more || this.moreFetching || this.items.length === 0) return;
|
||||
this.moreFetching = true;
|
||||
this.backed = true;
|
||||
let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params;
|
||||
if (params && params.then) params = await params;
|
||||
const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint;
|
||||
|
@ -5,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,
|
||||
@ -22,12 +24,14 @@ const defaultDeviceSettings = {
|
||||
loadRawImages: false,
|
||||
alwaysShowNsfw: false,
|
||||
useOsDefaultEmojis: false,
|
||||
autoReload: false,
|
||||
accounts: [],
|
||||
recentEmojis: [],
|
||||
visibility: 'public',
|
||||
localOnly: false,
|
||||
themes: [],
|
||||
theme: 'light',
|
||||
animation: true,
|
||||
};
|
||||
|
||||
export default (os: MiOS) => new Vuex.Store({
|
||||
|
@ -301,6 +301,37 @@ a {
|
||||
}
|
||||
}
|
||||
|
||||
._frame {
|
||||
position: relative;
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: var(--radius);
|
||||
margin: var(--margin);
|
||||
|
||||
> ._title {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
border-bottom: solid 1px var(--divider);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> ._content {
|
||||
padding: 16px;
|
||||
|
||||
& + ._content {
|
||||
border-top: solid 1px var(--divider);
|
||||
}
|
||||
}
|
||||
|
||||
> ._footer {
|
||||
border-top: solid 1px var(--divider);
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
._link {
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
.zoom-enter-active, .zoom-leave-active {
|
||||
transition: opacity 0.5s, transform 0.5s !important;
|
||||
}
|
||||
@ -339,3 +370,22 @@ a {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes jump {
|
||||
0% { transform: translateY(0); }
|
||||
25% { transform: translateY(-16px); }
|
||||
50% { transform: translateY(0); }
|
||||
75% { transform: translateY(-8px); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0% { opacity: 1; }
|
||||
30% { opacity: 1; }
|
||||
90% { opacity: 0; }
|
||||
}
|
||||
|
@ -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}`);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -1,3 +0,0 @@
|
||||
# About Misskey
|
||||
|
||||
Misskey is a mini blog SNS.
|
@ -1,3 +0,0 @@
|
||||
# Misskeyについて
|
||||
|
||||
MisskeyはミニブログSNSです。
|
@ -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')
|
@ -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
|
@ -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>
|
||||
|
@ -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
|
@ -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
|
@ -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 => {
|
||||
|
@ -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();
|
||||
|
@ -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':
|
||||
|
@ -15,9 +15,9 @@ import { apLogger } from '../logger';
|
||||
import { DriveFile } from '../../../models/entities/drive-file';
|
||||
import { deliverQuestionUpdate } from '../../../services/note/polls/update';
|
||||
import { extractDbHost, toPuny } from '../../../misc/convert-host';
|
||||
import { Notes, Emojis, Polls } from '../../../models';
|
||||
import { Notes, Emojis, Polls, MessagingMessages } from '../../../models';
|
||||
import { Note } from '../../../models/entities/note';
|
||||
import { IObject, getOneApId, getApId, validPost, ICreate, isCreate, IPost } from '../type';
|
||||
import { 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,23 +112,19 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
let isTalk = note._misskey_talk && visibility === 'specified';
|
||||
|
||||
const apHashtags = await extractHashtags(note.tag);
|
||||
|
||||
// 添付ファイル
|
||||
@ -153,7 +149,18 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
||||
} else {
|
||||
return x;
|
||||
}
|
||||
}).catch(e => {
|
||||
}).catch(async e => {
|
||||
// トークだったらinReplyToのエラーは無視
|
||||
const uri = getApId(note.inReplyTo);
|
||||
if (uri.startsWith(config.url + '/')) {
|
||||
const id = uri.split('/').pop();
|
||||
const talk = await MessagingMessages.findOne(id);
|
||||
if (talk) {
|
||||
isTalk = true;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${e.statusCode || e}`);
|
||||
throw e;
|
||||
})
|
||||
@ -250,7 +257,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
||||
if (actor.uri) updatePerson(actor.uri);
|
||||
}
|
||||
|
||||
if (note._misskey_talk && visibility === 'specified') {
|
||||
if (isTalk) {
|
||||
for (const recipient of visibleUsers) {
|
||||
await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null, object.id);
|
||||
return null;
|
||||
|
@ -1,10 +1,12 @@
|
||||
import config from '../../../config';
|
||||
import { ILocalUser } from '../../../models/entities/user';
|
||||
import { NoteReaction } from '../../../models/entities/note-reaction';
|
||||
import { Note } from '../../../models/entities/note';
|
||||
|
||||
export default (user: ILocalUser, note: Note, reaction: string) => ({
|
||||
export const renderLike = (noteReaction: NoteReaction, note: Note) => ({
|
||||
type: 'Like',
|
||||
actor: `${config.url}/users/${user.id}`,
|
||||
object: note.uri ? note.uri : `${config.url}/notes/${note.id}`,
|
||||
_misskey_reaction: reaction
|
||||
id: `${config.url}/likes/${noteReaction.id}`,
|
||||
actor: `${config.url}/users/${noteReaction.userId}`,
|
||||
object: note.uri ? note.uri : `${config.url}/notes/${noteReaction.noteId}`,
|
||||
content: noteReaction.reaction,
|
||||
_misskey_reaction: noteReaction.reaction
|
||||
});
|
||||
|
@ -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';
|
||||
|
@ -13,10 +13,11 @@ import Following from './activitypub/following';
|
||||
import Featured from './activitypub/featured';
|
||||
import { inbox as processInbox } from '../queue';
|
||||
import { isSelfHost } from '../misc/convert-host';
|
||||
import { Notes, Users, Emojis, UserKeypairs } from '../models';
|
||||
import { Notes, Users, Emojis, UserKeypairs, NoteReactions } from '../models';
|
||||
import { ILocalUser, User } from '../models/entities/user';
|
||||
import { In } from 'typeorm';
|
||||
import { ensure } from '../prelude/ensure';
|
||||
import { renderLike } from '../remote/activitypub/renderer/like';
|
||||
|
||||
// Init router
|
||||
const router = new Router();
|
||||
@ -202,4 +203,25 @@ router.get('/emojis/:emoji', async ctx => {
|
||||
setResponseType(ctx);
|
||||
});
|
||||
|
||||
// like
|
||||
router.get('/likes/:like', async ctx => {
|
||||
const reaction = await NoteReactions.findOne(ctx.params.like);
|
||||
|
||||
if (reaction == null) {
|
||||
ctx.status = 404;
|
||||
return;
|
||||
}
|
||||
|
||||
const note = await Notes.findOne(reaction.noteId);
|
||||
|
||||
if (note == null) {
|
||||
ctx.status = 404;
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.body = renderActivity(await renderLike(reaction, note));
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
setResponseType(ctx);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
@ -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();
|
||||
|
20
src/server/web/views/flush.pug
Normal file
20
src/server/web/views/flush.pug
Normal file
@ -0,0 +1,20 @@
|
||||
doctype html
|
||||
|
||||
html
|
||||
script.
|
||||
localStorage.removeItem('locale');
|
||||
|
||||
try {
|
||||
navigator.serviceWorker.controller.postMessage('clear');
|
||||
|
||||
navigator.serviceWorker.getRegistrations().then(registrations => {
|
||||
return Promise.all(registrations.map(registration => registration.unregister()));
|
||||
}).then(() => {
|
||||
location = '/';
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setTimeout(() => {
|
||||
location = '/';
|
||||
}, 10000)
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { publishNoteStream } from '../../stream';
|
||||
import watch from '../watch';
|
||||
import renderLike from '../../../remote/activitypub/renderer/like';
|
||||
import { renderLike } from '../../../remote/activitypub/renderer/like';
|
||||
import DeliverManager from '../../../remote/activitypub/deliver-manager';
|
||||
import { renderActivity } from '../../../remote/activitypub/renderer';
|
||||
import { IdentifiableError } from '../../../misc/identifiable-error';
|
||||
@ -38,7 +38,7 @@ export default async (user: User, note: Note, reaction?: string) => {
|
||||
}
|
||||
|
||||
// Create reaction
|
||||
await NoteReactions.save({
|
||||
const inserted = await NoteReactions.save({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
noteId: note.id,
|
||||
@ -94,7 +94,7 @@ export default async (user: User, note: Note, reaction?: string) => {
|
||||
|
||||
//#region 配信
|
||||
if (Users.isLocalUser(user) && !note.localOnly) {
|
||||
const content = renderActivity(renderLike(user, note, reaction));
|
||||
const content = renderActivity(renderLike(inserted, note));
|
||||
const dm = new DeliverManager(user, content);
|
||||
if (note.userHost !== null) {
|
||||
const reactee = await Users.findOne(note.userId)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { publishNoteStream } from '../../stream';
|
||||
import renderLike from '../../../remote/activitypub/renderer/like';
|
||||
import { renderLike } from '../../../remote/activitypub/renderer/like';
|
||||
import renderUndo from '../../../remote/activitypub/renderer/undo';
|
||||
import { renderActivity } from '../../../remote/activitypub/renderer';
|
||||
import DeliverManager from '../../../remote/activitypub/deliver-manager';
|
||||
@ -40,7 +40,7 @@ export default async (user: User, note: Note) => {
|
||||
|
||||
//#region 配信
|
||||
if (Users.isLocalUser(user) && !note.localOnly) {
|
||||
const content = renderActivity(renderUndo(renderLike(user, note, exist.reaction), user));
|
||||
const content = renderActivity(renderUndo(renderLike(exist, note), user));
|
||||
const dm = new DeliverManager(user, content);
|
||||
if (note.userHost !== null) {
|
||||
const reactee = await Users.findOne(note.userId)
|
||||
|
148
yarn.lock
148
yarn.lock
@ -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==
|
||||
|
Reference in New Issue
Block a user