Compare commits
81 Commits
Author | SHA1 | Date | |
---|---|---|---|
2448bf4e4e | |||
91e0fc8c62 | |||
b4f86feddb | |||
ccf8e44acc | |||
451acb77df | |||
e2c6227f47 | |||
ebd1c877ad | |||
498094b3c7 | |||
1cc183ecdb | |||
e8948452fd | |||
ade7e62836 | |||
395cfa6108 | |||
b5ff2abdb9 | |||
229e85b2c5 | |||
37058e3480 | |||
a1b82e9723 | |||
db943df0c8 | |||
ff8d300ea8 | |||
8b490b9b94 | |||
f83f8631ac | |||
1915ccabdd | |||
6fea2f52f1 | |||
f77eaaa08a | |||
7c5bc03492 | |||
72a1af6cd4 | |||
4bce6f14f3 | |||
a38ce86f87 | |||
f539491502 | |||
d279f8e9ff | |||
eaec936fa6 | |||
a0735b0e7a | |||
5b039a1bee | |||
60fa8e13d6 | |||
ecbaea463b | |||
814ddeb436 | |||
d6466106e8 | |||
633f5384f9 | |||
fa7989772c | |||
0e395612a6 | |||
fb3f52f3ad | |||
ba11c71d65 | |||
bdc3081167 | |||
430efcf1b9 | |||
996450dd7c | |||
fa779f0417 | |||
25cec6d28a | |||
c5f8403cea | |||
881df20f1b | |||
7d269c0441 | |||
639b483e6c | |||
e894ed5a8b | |||
d7808299fd | |||
f92e0c16d2 | |||
a951c337b8 | |||
db3efb3791 | |||
5f9a9867eb | |||
059a8e07d2 | |||
cf82f56e66 | |||
2778bd14d4 | |||
5b0446739c | |||
55f235d0ac | |||
4ec44c68e9 | |||
e6952d499a | |||
e0b82f827b | |||
0bccb17e82 | |||
b251b8c6a9 | |||
c2a62f632b | |||
37b5afa1a3 | |||
b80d0a3b12 | |||
9f60688d37 | |||
ca0ea9e57c | |||
a77a7e8112 | |||
b26ea2edc0 | |||
02b99dfd76 | |||
af02b0f115 | |||
9b3c379678 | |||
a423fd7695 | |||
de6e1d8c9b | |||
d9db3e8629 | |||
ac1c81b7d6 | |||
49b2eec534 |
@ -87,6 +87,7 @@ common:
|
|||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||||
verified-user: "公式アカウント"
|
verified-user: "公式アカウント"
|
||||||
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
||||||
|
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "引き分け"
|
drawn: "引き分け"
|
||||||
my-turn: "あなたのターンです"
|
my-turn: "あなたのターンです"
|
||||||
@ -260,6 +261,8 @@ common/views/components/nav.vue:
|
|||||||
develop: "開発者"
|
develop: "開発者"
|
||||||
feedback: "フィードバック"
|
feedback: "フィードバック"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
|
detail: "詳細"
|
||||||
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue:
|
|||||||
specified: "ダイレクト"
|
specified: "ダイレクト"
|
||||||
specified-desc: "指定したユーザーにのみ公開"
|
specified-desc: "指定したユーザーにのみ公開"
|
||||||
private: "非公開"
|
private: "非公開"
|
||||||
|
common/views/components/trends.vue:
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "確認中"
|
fetching: "確認中"
|
||||||
no-broadcasts: "お知らせはありません"
|
no-broadcasts: "お知らせはありません"
|
||||||
@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
count: "{}人が投稿"
|
|
||||||
empty: "トレンドなし"
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
@ -87,6 +87,7 @@ common:
|
|||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||||
verified-user: "公式アカウント"
|
verified-user: "公式アカウント"
|
||||||
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
||||||
|
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "引き分け"
|
drawn: "引き分け"
|
||||||
my-turn: "あなたのターンです"
|
my-turn: "あなたのターンです"
|
||||||
@ -260,6 +261,8 @@ common/views/components/nav.vue:
|
|||||||
develop: "Entwickler"
|
develop: "Entwickler"
|
||||||
feedback: "Feedback"
|
feedback: "Feedback"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
|
detail: "詳細"
|
||||||
|
copy-link: "リンクをコピー"
|
||||||
favorite: "Diese Anmerkung favorisieren"
|
favorite: "Diese Anmerkung favorisieren"
|
||||||
pin: "An die Profilseite pinnen"
|
pin: "An die Profilseite pinnen"
|
||||||
delete: "Löschen"
|
delete: "Löschen"
|
||||||
@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue:
|
|||||||
specified: "Direkt"
|
specified: "Direkt"
|
||||||
specified-desc: "Poste nur für bestimmte Benutzer"
|
specified-desc: "Poste nur für bestimmte Benutzer"
|
||||||
private: "Privat"
|
private: "Privat"
|
||||||
|
common/views/components/trends.vue:
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "Laden"
|
fetching: "Laden"
|
||||||
no-broadcasts: "Keine Broadcasts"
|
no-broadcasts: "Keine Broadcasts"
|
||||||
@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
count: "{}人が投稿"
|
|
||||||
empty: "トレンドなし"
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "Serverinformationen"
|
title: "Serverinformationen"
|
||||||
toggle: "Sicht umschalten"
|
toggle: "Sicht umschalten"
|
||||||
|
@ -84,9 +84,10 @@ common:
|
|||||||
my-token-regenerated: "Your token has been regenerated, so you will be signed out."
|
my-token-regenerated: "Your token has been regenerated, so you will be signed out."
|
||||||
i-like-sushi: "I prefer sushi rather than pudding"
|
i-like-sushi: "I prefer sushi rather than pudding"
|
||||||
show-reversi-board-labels: "Show row and column labels in Reversi"
|
show-reversi-board-labels: "Show row and column labels in Reversi"
|
||||||
use-contrast-reversi-stones: "Make the stone color clear"
|
use-contrast-reversi-stones: "Make the stone color clear in reversi"
|
||||||
verified-user: "Verified account"
|
verified-user: "Verified account"
|
||||||
disable-animated-mfm: "Disable animated texts in a post"
|
disable-animated-mfm: "Disable animated texts in a post"
|
||||||
|
do-not-use-in-production: 'As this is for development, do not use this in production.'
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "Draw"
|
drawn: "Draw"
|
||||||
my-turn: "Your turn"
|
my-turn: "Your turn"
|
||||||
@ -260,6 +261,8 @@ common/views/components/nav.vue:
|
|||||||
develop: "Developers"
|
develop: "Developers"
|
||||||
feedback: "Feedback"
|
feedback: "Feedback"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
|
detail: "Details"
|
||||||
|
copy-link: "Copy link"
|
||||||
favorite: "Favorite this note"
|
favorite: "Favorite this note"
|
||||||
pin: "Pin to your profile"
|
pin: "Pin to your profile"
|
||||||
delete: "Delete"
|
delete: "Delete"
|
||||||
@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue:
|
|||||||
specified: "Direct"
|
specified: "Direct"
|
||||||
specified-desc: "Post to specified users only"
|
specified-desc: "Post to specified users only"
|
||||||
private: "Private"
|
private: "Private"
|
||||||
|
common/views/components/trends.vue:
|
||||||
|
count: "{} users mentioned"
|
||||||
|
empty: "No popular hashtag trends"
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "Fetching"
|
fetching: "Fetching"
|
||||||
no-broadcasts: "No announcements"
|
no-broadcasts: "No announcements"
|
||||||
@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "Toggle views"
|
toggle: "Toggle views"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "Hashtags"
|
title: "Hashtags"
|
||||||
count: "{} users mentioned"
|
|
||||||
empty: "No popular hashtag trends"
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "Server info"
|
title: "Server info"
|
||||||
toggle: "Toggle views"
|
toggle: "Toggle views"
|
||||||
|
@ -11,7 +11,7 @@ common:
|
|||||||
warning: "<strong>Misskey no tiene anuncios publicitarios.</strong> Sin embargo, algunas características podrían no estar disponibles si el bloqueador de publicidad está habilitado."
|
warning: "<strong>Misskey no tiene anuncios publicitarios.</strong> Sin embargo, algunas características podrían no estar disponibles si el bloqueador de publicidad está habilitado."
|
||||||
application-authorization: "Autorizaciones de la aplicación."
|
application-authorization: "Autorizaciones de la aplicación."
|
||||||
close: "Cerrar"
|
close: "Cerrar"
|
||||||
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
|
do-not-copy-paste: "Por favor no copies código aquí. Tu cuenta puede resultar comprometida."
|
||||||
got-it: "¡Listo!"
|
got-it: "¡Listo!"
|
||||||
customization-tips:
|
customization-tips:
|
||||||
title: "Consejos de personalización"
|
title: "Consejos de personalización"
|
||||||
@ -58,7 +58,7 @@ common:
|
|||||||
friday: "Viernes"
|
friday: "Viernes"
|
||||||
saturday: "Sábado"
|
saturday: "Sábado"
|
||||||
reactions:
|
reactions:
|
||||||
like: "いいね"
|
like: "Me gusta"
|
||||||
love: "amor"
|
love: "amor"
|
||||||
laugh: "risa"
|
laugh: "risa"
|
||||||
hmm: "hmm"
|
hmm: "hmm"
|
||||||
@ -84,9 +84,10 @@ common:
|
|||||||
my-token-regenerated: "Tu token se ha regenerado vas a ser desconectado."
|
my-token-regenerated: "Tu token se ha regenerado vas a ser desconectado."
|
||||||
i-like-sushi: "Prefiero sushi a pudín"
|
i-like-sushi: "Prefiero sushi a pudín"
|
||||||
show-reversi-board-labels: "Mostrar etiquetas de filas y columnas en Reversi"
|
show-reversi-board-labels: "Mostrar etiquetas de filas y columnas en Reversi"
|
||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
use-contrast-reversi-stones: "Hacer el color de la piedra claro en Reversi"
|
||||||
verified-user: "公式アカウント"
|
verified-user: "Cuenta verificada"
|
||||||
disable-animated-mfm: "Desactivar texto animado en una publicación"
|
disable-animated-mfm: "Desactivar texto animado en una publicación"
|
||||||
|
do-not-use-in-production: 'Esto está en desarrollo, no usarlo para producción.'
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "Empatado"
|
drawn: "Empatado"
|
||||||
my-turn: "Mi turno"
|
my-turn: "Mi turno"
|
||||||
@ -170,9 +171,9 @@ common/views/components/games/reversi/reversi.vue:
|
|||||||
common/views/components/games/reversi/reversi.game.vue:
|
common/views/components/games/reversi/reversi.game.vue:
|
||||||
surrender: "Rendirse"
|
surrender: "Rendirse"
|
||||||
surrendered: "Por rendirse"
|
surrendered: "Por rendirse"
|
||||||
is-llotheo: "石の少ない方が勝ち(ロセオ)"
|
is-llotheo: "El último gana (Llotheo)"
|
||||||
looped-map: "ループマップ"
|
looped-map: "Mapa en bucle"
|
||||||
can-put-everywhere: "どこでも置けるモード"
|
can-put-everywhere: "Puedes colocar donde quieras"
|
||||||
common/views/components/games/reversi/reversi.index.vue:
|
common/views/components/games/reversi/reversi.index.vue:
|
||||||
title: "Misskey Reversi"
|
title: "Misskey Reversi"
|
||||||
sub-title: "¡Juega Reversi con tus amigos!"
|
sub-title: "¡Juega Reversi con tus amigos!"
|
||||||
@ -260,6 +261,8 @@ common/views/components/nav.vue:
|
|||||||
develop: "Desarrolladores"
|
develop: "Desarrolladores"
|
||||||
feedback: "Opiniones"
|
feedback: "Opiniones"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
|
detail: "Detalles"
|
||||||
|
copy-link: "Copiar enlace"
|
||||||
favorite: "Me gusta esta nota"
|
favorite: "Me gusta esta nota"
|
||||||
pin: "Fijar en el perfil"
|
pin: "Fijar en el perfil"
|
||||||
delete: "Borrar"
|
delete: "Borrar"
|
||||||
@ -288,10 +291,10 @@ common/views/components/signin.vue:
|
|||||||
signin: "Entra"
|
signin: "Entra"
|
||||||
or: "O"
|
or: "O"
|
||||||
signin-with-twitter: "Ingresar con Twitter"
|
signin-with-twitter: "Ingresar con Twitter"
|
||||||
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
|
login-failed: "Autenticación fallida. Asegúrate de haber usado el nombre de usuario y contraseña correctos."
|
||||||
common/views/components/signup.vue:
|
common/views/components/signup.vue:
|
||||||
invitation-code: "招待コード"
|
invitation-code: "Código de invitación"
|
||||||
invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
|
invitation-info: "Si no tienes un código de invitación, por favor contacta un <a href=\"{}\">administrador</a>."
|
||||||
username: "Usuario"
|
username: "Usuario"
|
||||||
checking: "Comprobando..."
|
checking: "Comprobando..."
|
||||||
available: "Disponible"
|
available: "Disponible"
|
||||||
@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue:
|
|||||||
specified: "Directo"
|
specified: "Directo"
|
||||||
specified-desc: "Publica solo para los seguidores que quieras"
|
specified-desc: "Publica solo para los seguidores que quieras"
|
||||||
private: "Privada"
|
private: "Privada"
|
||||||
|
common/views/components/trends.vue:
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "Recuperando"
|
fetching: "Recuperando"
|
||||||
no-broadcasts: "Sin emisión"
|
no-broadcasts: "Sin emisión"
|
||||||
@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "Alternar vistas"
|
toggle: "Alternar vistas"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "Etiquetas"
|
title: "Etiquetas"
|
||||||
count: "{} usuarios mencionados"
|
|
||||||
empty: "Ninguna tendencia popular ahora"
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "Información del servidor"
|
title: "Información del servidor"
|
||||||
toggle: "Alternar vistas"
|
toggle: "Alternar vistas"
|
||||||
@ -411,7 +415,7 @@ desktop:
|
|||||||
uploading-avatar: "Cargando un nuevo avatar"
|
uploading-avatar: "Cargando un nuevo avatar"
|
||||||
avatar-updated: "Avatar actualizado"
|
avatar-updated: "Avatar actualizado"
|
||||||
choose-avatar: "Escoge una imagen de avatar"
|
choose-avatar: "Escoge una imagen de avatar"
|
||||||
invalid-filetype: "この形式のファイルはサポートされていません"
|
invalid-filetype: "Este tipo de archivo no es compatible aquí"
|
||||||
desktop/views/components/activity.chart.vue:
|
desktop/views/components/activity.chart.vue:
|
||||||
total: "Negro ... Total"
|
total: "Negro ... Total"
|
||||||
notes: "Azul ... Notas"
|
notes: "Azul ... Notas"
|
||||||
@ -426,23 +430,23 @@ desktop/views/components/calendar.vue:
|
|||||||
next: "Próximo mes"
|
next: "Próximo mes"
|
||||||
go: "Click para navegar"
|
go: "Click para navegar"
|
||||||
desktop/views/components/charts.vue:
|
desktop/views/components/charts.vue:
|
||||||
title: "チャート"
|
title: "Gráficos"
|
||||||
per-day: "1日ごと"
|
per-day: "por día"
|
||||||
per-hour: "1時間ごと"
|
per-hour: "por hora"
|
||||||
notes: "投稿"
|
notes: "Publicaciones"
|
||||||
users: "ユーザー"
|
users: "Usuarios"
|
||||||
drive: "ドライブ"
|
drive: "Unidad"
|
||||||
charts:
|
charts:
|
||||||
notes: "投稿の増減 (統合)"
|
notes: "Número de publicaciones: aumentar/disminuir (Combinado)"
|
||||||
local-notes: "投稿の増減 (ローカル)"
|
local-notes: "Número de publicaciones: aumentar/disminuir (Local)"
|
||||||
remote-notes: "投稿の増減 (リモート)"
|
remote-notes: "Número de publicaciones: aumentar/disminuir (Remoto)"
|
||||||
notes-total: "投稿の累計"
|
notes-total: "Número de publicaciones: Acumulativo total"
|
||||||
users: "ユーザーの増減"
|
users: "Número de usuarios: aumentar/disminuir"
|
||||||
users-total: "ユーザーの累計"
|
users-total: "Número de usuarios: Acumulativo total"
|
||||||
drive: "ドライブ使用量の増減"
|
drive: "Capacidad de almacenamiento usada: aumentar/disminuir"
|
||||||
drive-total: "ドライブ使用量の累計"
|
drive-total: "Capacidad de almacenamiento usada: Acumulativa total"
|
||||||
drive-files: "ドライブのファイル数の増減"
|
drive-files: "Número de archivos almacenados: aumentar/disminuir"
|
||||||
drive-files-total: "ドライブのファイル数の累計"
|
drive-files-total: "Número de archivos almacenados: Acumulativo total"
|
||||||
desktop/views/components/choose-file-from-drive-window.vue:
|
desktop/views/components/choose-file-from-drive-window.vue:
|
||||||
choose-file: "Escoger archivos"
|
choose-file: "Escoger archivos"
|
||||||
upload: "Cargar archivos de tu dispositivo"
|
upload: "Cargar archivos de tu dispositivo"
|
||||||
@ -463,7 +467,7 @@ desktop/views/components/drive-window.vue:
|
|||||||
desktop/views/components/drive.file.vue:
|
desktop/views/components/drive.file.vue:
|
||||||
avatar: "Avatar"
|
avatar: "Avatar"
|
||||||
banner: "Banner"
|
banner: "Banner"
|
||||||
nsfw: "閲覧注意"
|
nsfw: "Ver más"
|
||||||
contextmenu:
|
contextmenu:
|
||||||
rename: "Renombrar"
|
rename: "Renombrar"
|
||||||
mark-as-sensitive: "Marcar como 'sensible'"
|
mark-as-sensitive: "Marcar como 'sensible'"
|
||||||
@ -515,31 +519,31 @@ desktop/views/components/media-image.vue:
|
|||||||
sensitive: "El contenido es NSFW (no seguro para ver en el trabajo, 'not safe for work')"
|
sensitive: "El contenido es NSFW (no seguro para ver en el trabajo, 'not safe for work')"
|
||||||
click-to-show: "Click para mostrar"
|
click-to-show: "Click para mostrar"
|
||||||
desktop/views/components/media-video.vue:
|
desktop/views/components/media-video.vue:
|
||||||
sensitive: "閲覧注意"
|
sensitive: "Este contenido no es apropiado para ver en el trabajo"
|
||||||
click-to-show: "クリックして表示"
|
click-to-show: "Click para mostrar"
|
||||||
desktop/views/components/follow-button.vue:
|
desktop/views/components/follow-button.vue:
|
||||||
following: "Siguiendo"
|
following: "Siguiendo"
|
||||||
follow: "Sigue"
|
follow: "Sigue"
|
||||||
request-pending: "Pendiente de aprobación"
|
request-pending: "Pendiente de aprobación"
|
||||||
follow-request: "フォロー申請"
|
follow-request: "Solicitud de seguir"
|
||||||
desktop/views/components/followers-window.vue:
|
desktop/views/components/followers-window.vue:
|
||||||
followers: "{} のフォロワー"
|
followers: "{} seguidores"
|
||||||
desktop/views/components/followers.vue:
|
desktop/views/components/followers.vue:
|
||||||
empty: "フォロワーはいないようです。"
|
empty: "Parece que no tienes seguidores aún."
|
||||||
desktop/views/components/following-window.vue:
|
desktop/views/components/following-window.vue:
|
||||||
following: "{} のフォロー"
|
following: "Siguiendo {}"
|
||||||
desktop/views/components/following.vue:
|
desktop/views/components/following.vue:
|
||||||
empty: "フォロー中のユーザーはいないようです。"
|
empty: "Parece que aún no sigues a nadie."
|
||||||
desktop/views/components/friends-maker.vue:
|
desktop/views/components/friends-maker.vue:
|
||||||
title: "気になるユーザーをフォロー:"
|
title: "Usuarios recomendados:"
|
||||||
empty: "おすすめのユーザーは見つかりませんでした。"
|
empty: "No se pudieron encontrar usuarios para recomendar"
|
||||||
fetching: "読み込んでいます"
|
fetching: "Cargando"
|
||||||
refresh: "もっと見る"
|
refresh: "Más"
|
||||||
close: "閉じる"
|
close: "Cerrar"
|
||||||
desktop/views/components/game-window.vue:
|
desktop/views/components/game-window.vue:
|
||||||
game: "リバーシ"
|
game: "Reversi"
|
||||||
desktop/views/components/home.vue:
|
desktop/views/components/home.vue:
|
||||||
done: "完了"
|
done: "Listo"
|
||||||
add-widget: "Agregar accesorio:"
|
add-widget: "Agregar accesorio:"
|
||||||
add: "Agregar"
|
add: "Agregar"
|
||||||
desktop/views/input-dialog.vue:
|
desktop/views/input-dialog.vue:
|
||||||
@ -565,8 +569,8 @@ desktop/views/components/notes.note.vue:
|
|||||||
detail: "Mostrar detalles"
|
detail: "Mostrar detalles"
|
||||||
private: "Esta publicación es privada"
|
private: "Esta publicación es privada"
|
||||||
deleted: "Esta publicación ha sido borrada"
|
deleted: "Esta publicación ha sido borrada"
|
||||||
hide: "隠す"
|
hide: "Esconder"
|
||||||
see-more: "もっと見る"
|
see-more: "Ver más"
|
||||||
desktop/views/components/notes.vue:
|
desktop/views/components/notes.vue:
|
||||||
error: "Error al cargar."
|
error: "Error al cargar."
|
||||||
retry: "Reintentar"
|
retry: "Reintentar"
|
||||||
@ -602,7 +606,7 @@ desktop/views/components/post-form.vue:
|
|||||||
geolocation-alert: "Tu dispositivo no tiene soporte de geolocalización."
|
geolocation-alert: "Tu dispositivo no tiene soporte de geolocalización."
|
||||||
error: "Error"
|
error: "Error"
|
||||||
enter-username: "Por favor escribe un nombre de usuario..."
|
enter-username: "Por favor escribe un nombre de usuario..."
|
||||||
annotations: "内容への注釈 (オプション)"
|
annotations: "Anotaciones a la publicación (opcional)"
|
||||||
desktop/views/components/post-form-window.vue:
|
desktop/views/components/post-form-window.vue:
|
||||||
note: "Nota nueva"
|
note: "Nota nueva"
|
||||||
reply: "Responder"
|
reply: "Responder"
|
||||||
@ -766,40 +770,40 @@ desktop/views/components/timeline.vue:
|
|||||||
global: "グローバル"
|
global: "グローバル"
|
||||||
list: "リスト"
|
list: "リスト"
|
||||||
desktop/views/components/ui.header.vue:
|
desktop/views/components/ui.header.vue:
|
||||||
welcome-back: "おかえりなさい、"
|
welcome-back: "Bienvenido/a de vuelta,"
|
||||||
adjective: "さん"
|
adjective: "-san"
|
||||||
desktop/views/components/ui.header.account.vue:
|
desktop/views/components/ui.header.account.vue:
|
||||||
profile: "プロフィール"
|
profile: "Tu perfil"
|
||||||
drive: "ドライブ"
|
drive: "Unidad"
|
||||||
favorites: "お気に入り"
|
favorites: "Favoritos"
|
||||||
lists: "リスト"
|
lists: "Listas"
|
||||||
follow-requests: "フォロー申請"
|
follow-requests: "Solicitudes de seguimiento"
|
||||||
customize: "ホームのカスタマイズ"
|
customize: "Personalizar la página de inicio"
|
||||||
admin: "管理"
|
admin: "Admin"
|
||||||
settings: "設定"
|
settings: "Configuraciones"
|
||||||
signout: "サインアウト"
|
signout: "Desconectarse"
|
||||||
dark: "闇に飲まれる"
|
dark: "Sumergirse en la oscuridad"
|
||||||
desktop/views/components/ui.header.nav.vue:
|
desktop/views/components/ui.header.nav.vue:
|
||||||
home: "ホーム"
|
home: "Inicio"
|
||||||
deck: "デッキ"
|
deck: "Cubierta"
|
||||||
messaging: "メッセージ"
|
messaging: "Mensajes"
|
||||||
game: "ゲーム"
|
game: "Juegos"
|
||||||
desktop/views/components/ui.header.notifications.vue:
|
desktop/views/components/ui.header.notifications.vue:
|
||||||
title: "通知"
|
title: "Notificaciones"
|
||||||
desktop/views/components/ui.header.post.vue:
|
desktop/views/components/ui.header.post.vue:
|
||||||
post: "新規投稿"
|
post: "Crear una publicación"
|
||||||
desktop/views/components/ui.header.search.vue:
|
desktop/views/components/ui.header.search.vue:
|
||||||
placeholder: "検索"
|
placeholder: "Buscar"
|
||||||
desktop/views/components/received-follow-requests-window.vue:
|
desktop/views/components/received-follow-requests-window.vue:
|
||||||
title: "フォロー申請"
|
title: "Solicitudes de seguidores"
|
||||||
accept: "承認"
|
accept: "Aceptar"
|
||||||
reject: "拒否"
|
reject: "Rechazar"
|
||||||
desktop/views/components/user-lists-window.vue:
|
desktop/views/components/user-lists-window.vue:
|
||||||
title: "リスト"
|
title: "Listas de usuario"
|
||||||
create-list: "リストを作成"
|
create-list: "Crear lista"
|
||||||
list-name: "リスト名"
|
list-name: "Nombre de lista"
|
||||||
desktop/views/components/user-preview.vue:
|
desktop/views/components/user-preview.vue:
|
||||||
notes: "投稿"
|
notes: "Publicaciones"
|
||||||
following: "フォロー"
|
following: "フォロー"
|
||||||
followers: "フォロワー"
|
followers: "フォロワー"
|
||||||
desktop/views/components/users-list.vue:
|
desktop/views/components/users-list.vue:
|
||||||
|
@ -87,6 +87,7 @@ common:
|
|||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||||
verified-user: "Compte vérifié"
|
verified-user: "Compte vérifié"
|
||||||
disable-animated-mfm: "Désactiver les textes animés dans les publications"
|
disable-animated-mfm: "Désactiver les textes animés dans les publications"
|
||||||
|
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "Partie nulle"
|
drawn: "Partie nulle"
|
||||||
my-turn: "C’est votre tour"
|
my-turn: "C’est votre tour"
|
||||||
@ -260,6 +261,8 @@ common/views/components/nav.vue:
|
|||||||
develop: "Développeur·se·s"
|
develop: "Développeur·se·s"
|
||||||
feedback: "Remarques"
|
feedback: "Remarques"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
|
detail: "詳細"
|
||||||
|
copy-link: "リンクをコピー"
|
||||||
favorite: "Mettre cette note en favoris"
|
favorite: "Mettre cette note en favoris"
|
||||||
pin: "Épingler sur votre profil"
|
pin: "Épingler sur votre profil"
|
||||||
delete: "Supprimer"
|
delete: "Supprimer"
|
||||||
@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue:
|
|||||||
specified: "Direct"
|
specified: "Direct"
|
||||||
specified-desc: "Publier aux utilisateur·rice·s mentionné·e·s"
|
specified-desc: "Publier aux utilisateur·rice·s mentionné·e·s"
|
||||||
private: "Privé"
|
private: "Privé"
|
||||||
|
common/views/components/trends.vue:
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "Récupération"
|
fetching: "Récupération"
|
||||||
no-broadcasts: "Aucune annonce"
|
no-broadcasts: "Aucune annonce"
|
||||||
@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "Basculer entre les vues"
|
toggle: "Basculer entre les vues"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "Étiquettes"
|
title: "Étiquettes"
|
||||||
count: "{} utilisateur·rice·s mentionné·e·s"
|
|
||||||
empty: "Aucune tendance"
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "Informations sur le serveur"
|
title: "Informations sur le serveur"
|
||||||
toggle: "Afficher les vues"
|
toggle: "Afficher les vues"
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
|
|
||||||
const langs = ['de-DE', 'en-US', 'fr-FR', 'ja-JP', 'ja-KS', 'pl-PL', 'es-ES'];
|
const langs = ['de-DE', 'en-US', 'fr-FR', 'ja-JP', 'ja-KS', 'pl-PL', 'es-ES', 'nl-NL'];
|
||||||
|
|
||||||
const loadLocale = lang => yaml.safeLoad(fs.readFileSync(`${__dirname}/${lang}.yml`, 'utf-8'));
|
const loadLocale = lang => yaml.safeLoad(fs.readFileSync(`${__dirname}/${lang}.yml`, 'utf-8'));
|
||||||
const locales = langs.map(lang => ({ [lang]: loadLocale(lang) }));
|
const locales = langs.map(lang => ({ [lang]: loadLocale(lang) }));
|
||||||
|
@ -87,6 +87,7 @@ common:
|
|||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||||
verified-user: "公式アカウント"
|
verified-user: "公式アカウント"
|
||||||
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
||||||
|
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "引き分け"
|
drawn: "引き分け"
|
||||||
my-turn: "あなたのターンです"
|
my-turn: "あなたのターンです"
|
||||||
@ -260,6 +261,8 @@ common/views/components/nav.vue:
|
|||||||
develop: "開発者"
|
develop: "開発者"
|
||||||
feedback: "フィードバック"
|
feedback: "フィードバック"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
|
detail: "詳細"
|
||||||
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue:
|
|||||||
specified: "ダイレクト"
|
specified: "ダイレクト"
|
||||||
specified-desc: "指定したユーザーにのみ公開"
|
specified-desc: "指定したユーザーにのみ公開"
|
||||||
private: "非公開"
|
private: "非公開"
|
||||||
|
common/views/components/trends.vue:
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "確認中"
|
fetching: "確認中"
|
||||||
no-broadcasts: "お知らせはありません"
|
no-broadcasts: "お知らせはありません"
|
||||||
@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
count: "{}人が投稿"
|
|
||||||
empty: "トレンドなし"
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
@ -990,6 +990,8 @@ desktop/views/pages/welcome.vue:
|
|||||||
signin-button: "やってる"
|
signin-button: "やってる"
|
||||||
signup-button: "やる"
|
signup-button: "やる"
|
||||||
timeline: "タイムライン"
|
timeline: "タイムライン"
|
||||||
|
announcements: "お知らせ"
|
||||||
|
photos: "最近の画像"
|
||||||
powered-by-misskey: "Powered by <b>Misskey</b>."
|
powered-by-misskey: "Powered by <b>Misskey</b>."
|
||||||
|
|
||||||
desktop/views/pages/drive.vue:
|
desktop/views/pages/drive.vue:
|
||||||
@ -1355,6 +1357,9 @@ mobile/views/pages/settings.vue:
|
|||||||
post-style: "投稿の表示スタイル"
|
post-style: "投稿の表示スタイル"
|
||||||
post-style-standard: "標準"
|
post-style-standard: "標準"
|
||||||
post-style-smart: "スマート"
|
post-style-smart: "スマート"
|
||||||
|
notification-position: "通知の表示"
|
||||||
|
notification-position-bottom: "下"
|
||||||
|
notification-position-top: "上"
|
||||||
behavior: "動作"
|
behavior: "動作"
|
||||||
fetch-on-scroll: "スクロールで自動読み込み"
|
fetch-on-scroll: "スクロールで自動読み込み"
|
||||||
disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
|
disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
|
||||||
|
@ -87,6 +87,7 @@ common:
|
|||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストをつけんで!"
|
use-contrast-reversi-stones: "リバーシのアイコンにコントラストをつけんで!"
|
||||||
verified-user: "アメちゃん付きアカウント"
|
verified-user: "アメちゃん付きアカウント"
|
||||||
disable-animated-mfm: "投稿内のちょろちょろ動いてんのを止める"
|
disable-animated-mfm: "投稿内のちょろちょろ動いてんのを止める"
|
||||||
|
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "おあいこ"
|
drawn: "おあいこ"
|
||||||
my-turn: "あんさんのターンや"
|
my-turn: "あんさんのターンや"
|
||||||
@ -260,6 +261,8 @@ common/views/components/nav.vue:
|
|||||||
develop: "開発者"
|
develop: "開発者"
|
||||||
feedback: "フィードバック"
|
feedback: "フィードバック"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
|
detail: "詳細"
|
||||||
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
delete: "ほかす"
|
delete: "ほかす"
|
||||||
@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue:
|
|||||||
specified: "ダイレクト"
|
specified: "ダイレクト"
|
||||||
specified-desc: "指定したユーザーにのみ公開"
|
specified-desc: "指定したユーザーにのみ公開"
|
||||||
private: "非公開"
|
private: "非公開"
|
||||||
|
common/views/components/trends.vue:
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "確認中"
|
fetching: "確認中"
|
||||||
no-broadcasts: "お知らせはあらへんで"
|
no-broadcasts: "お知らせはあらへんで"
|
||||||
@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
count: "{}人が投稿"
|
|
||||||
empty: "流行は自分で作るんや"
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
@ -87,6 +87,7 @@ common:
|
|||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||||
verified-user: "公式アカウント"
|
verified-user: "公式アカウント"
|
||||||
disable-animated-mfm: "게시물의 문자 애니메이션을 비활성화 할"
|
disable-animated-mfm: "게시물의 문자 애니메이션을 비활성화 할"
|
||||||
|
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "무승부"
|
drawn: "무승부"
|
||||||
my-turn: "당신의 차례입니다"
|
my-turn: "당신의 차례입니다"
|
||||||
@ -260,6 +261,8 @@ common/views/components/nav.vue:
|
|||||||
develop: "開発者"
|
develop: "開発者"
|
||||||
feedback: "フィードバック"
|
feedback: "フィードバック"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
|
detail: "詳細"
|
||||||
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue:
|
|||||||
specified: "ダイレクト"
|
specified: "ダイレクト"
|
||||||
specified-desc: "指定したユーザーにのみ公開"
|
specified-desc: "指定したユーザーにのみ公開"
|
||||||
private: "非公開"
|
private: "非公開"
|
||||||
|
common/views/components/trends.vue:
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "確認中"
|
fetching: "確認中"
|
||||||
no-broadcasts: "お知らせはありません"
|
no-broadcasts: "お知らせはありません"
|
||||||
@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
count: "{}人が投稿"
|
|
||||||
empty: "トレンドなし"
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
1241
locales/nl-NL.yml
Normal file
1241
locales/nl-NL.yml
Normal file
File diff suppressed because it is too large
Load Diff
1241
locales/no-NO.yml
Normal file
1241
locales/no-NO.yml
Normal file
File diff suppressed because it is too large
Load Diff
@ -87,6 +87,7 @@ common:
|
|||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||||
verified-user: "公式アカウント"
|
verified-user: "公式アカウント"
|
||||||
disable-animated-mfm: "Wyłącz animowany tekst we wpisach"
|
disable-animated-mfm: "Wyłącz animowany tekst we wpisach"
|
||||||
|
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "Remis"
|
drawn: "Remis"
|
||||||
my-turn: "Twoja kolej"
|
my-turn: "Twoja kolej"
|
||||||
@ -260,6 +261,8 @@ common/views/components/nav.vue:
|
|||||||
develop: "Autorzy"
|
develop: "Autorzy"
|
||||||
feedback: "Podziel się opinią"
|
feedback: "Podziel się opinią"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
|
detail: "詳細"
|
||||||
|
copy-link: "リンクをコピー"
|
||||||
favorite: "Dodaj do ulubionych"
|
favorite: "Dodaj do ulubionych"
|
||||||
pin: "Przypnij do profilu"
|
pin: "Przypnij do profilu"
|
||||||
delete: "Usuń"
|
delete: "Usuń"
|
||||||
@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue:
|
|||||||
specified: "Bezpośredni"
|
specified: "Bezpośredni"
|
||||||
specified-desc: "Tylko dla określonych użytkowników"
|
specified-desc: "Tylko dla określonych użytkowników"
|
||||||
private: "Prywatny"
|
private: "Prywatny"
|
||||||
|
common/views/components/trends.vue:
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "Sprawdzanie"
|
fetching: "Sprawdzanie"
|
||||||
no-broadcasts: "Brak transmisji"
|
no-broadcasts: "Brak transmisji"
|
||||||
@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "Przełącz widok"
|
toggle: "Przełącz widok"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "Hashtagi"
|
title: "Hashtagi"
|
||||||
count: "Wspomniany przez {} użytkowników"
|
|
||||||
empty: "Brak popularnych hashtagów"
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "Informacje o serwerze"
|
title: "Informacje o serwerze"
|
||||||
toggle: "Przełącz widok"
|
toggle: "Przełącz widok"
|
||||||
|
@ -87,6 +87,7 @@ common:
|
|||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||||
verified-user: "Conta verificada"
|
verified-user: "Conta verificada"
|
||||||
disable-animated-mfm: "Desativar texto animado nas publicações"
|
disable-animated-mfm: "Desativar texto animado nas publicações"
|
||||||
|
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "Empatado"
|
drawn: "Empatado"
|
||||||
my-turn: "Seu turno"
|
my-turn: "Seu turno"
|
||||||
@ -260,6 +261,8 @@ common/views/components/nav.vue:
|
|||||||
develop: "開発者"
|
develop: "開発者"
|
||||||
feedback: "フィードバック"
|
feedback: "フィードバック"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
|
detail: "詳細"
|
||||||
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue:
|
|||||||
specified: "ダイレクト"
|
specified: "ダイレクト"
|
||||||
specified-desc: "指定したユーザーにのみ公開"
|
specified-desc: "指定したユーザーにのみ公開"
|
||||||
private: "非公開"
|
private: "非公開"
|
||||||
|
common/views/components/trends.vue:
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "確認中"
|
fetching: "確認中"
|
||||||
no-broadcasts: "お知らせはありません"
|
no-broadcasts: "お知らせはありません"
|
||||||
@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
count: "{}人が投稿"
|
|
||||||
empty: "トレンドなし"
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
@ -87,6 +87,7 @@ common:
|
|||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||||
verified-user: "公式アカウント"
|
verified-user: "公式アカウント"
|
||||||
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
||||||
|
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "引き分け"
|
drawn: "引き分け"
|
||||||
my-turn: "あなたのターンです"
|
my-turn: "あなたのターンです"
|
||||||
@ -260,6 +261,8 @@ common/views/components/nav.vue:
|
|||||||
develop: "開発者"
|
develop: "開発者"
|
||||||
feedback: "フィードバック"
|
feedback: "フィードバック"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
|
detail: "詳細"
|
||||||
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue:
|
|||||||
specified: "ダイレクト"
|
specified: "ダイレクト"
|
||||||
specified-desc: "指定したユーザーにのみ公開"
|
specified-desc: "指定したユーザーにのみ公開"
|
||||||
private: "非公開"
|
private: "非公開"
|
||||||
|
common/views/components/trends.vue:
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "確認中"
|
fetching: "確認中"
|
||||||
no-broadcasts: "お知らせはありません"
|
no-broadcasts: "お知らせはありません"
|
||||||
@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
count: "{}人が投稿"
|
|
||||||
empty: "トレンドなし"
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
@ -87,6 +87,7 @@ common:
|
|||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||||
verified-user: "公式アカウント"
|
verified-user: "公式アカウント"
|
||||||
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
||||||
|
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "引き分け"
|
drawn: "引き分け"
|
||||||
my-turn: "あなたのターンです"
|
my-turn: "あなたのターンです"
|
||||||
@ -260,6 +261,8 @@ common/views/components/nav.vue:
|
|||||||
develop: "開発者"
|
develop: "開発者"
|
||||||
feedback: "フィードバック"
|
feedback: "フィードバック"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
|
detail: "詳細"
|
||||||
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue:
|
|||||||
specified: "ダイレクト"
|
specified: "ダイレクト"
|
||||||
specified-desc: "指定したユーザーにのみ公開"
|
specified-desc: "指定したユーザーにのみ公開"
|
||||||
private: "非公開"
|
private: "非公開"
|
||||||
|
common/views/components/trends.vue:
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "確認中"
|
fetching: "確認中"
|
||||||
no-broadcasts: "お知らせはありません"
|
no-broadcasts: "お知らせはありません"
|
||||||
@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
count: "{}人が投稿"
|
|
||||||
empty: "トレンドなし"
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
12
package.json
12
package.json
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "8.25.0",
|
"version": "8.27.0",
|
||||||
"clientVersion": "1.0.9297",
|
"clientVersion": "1.0.9378",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
@ -161,7 +161,7 @@
|
|||||||
"nan": "2.11.0",
|
"nan": "2.11.0",
|
||||||
"nested-property": "0.0.7",
|
"nested-property": "0.0.7",
|
||||||
"node-sass": "4.9.3",
|
"node-sass": "4.9.3",
|
||||||
"node-sass-json-importer": "3.3.1",
|
"node-sass-json-importer": "4.0.0",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"object-assign-deep": "0.4.0",
|
"object-assign-deep": "0.4.0",
|
||||||
"on-build-webpack": "0.1.0",
|
"on-build-webpack": "0.1.0",
|
||||||
@ -194,7 +194,7 @@
|
|||||||
"stylus": "0.54.5",
|
"stylus": "0.54.5",
|
||||||
"stylus-loader": "3.0.2",
|
"stylus-loader": "3.0.2",
|
||||||
"summaly": "2.2.0",
|
"summaly": "2.2.0",
|
||||||
"systeminformation": "3.44.2",
|
"systeminformation": "3.45.1",
|
||||||
"syuilo-password-strength": "0.0.1",
|
"syuilo-password-strength": "0.0.1",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"tmp": "0.0.33",
|
"tmp": "0.0.33",
|
||||||
@ -210,7 +210,7 @@
|
|||||||
"vue": "2.5.17",
|
"vue": "2.5.17",
|
||||||
"vue-chartjs": "3.4.0",
|
"vue-chartjs": "3.4.0",
|
||||||
"vue-cropperjs": "2.2.1",
|
"vue-cropperjs": "2.2.1",
|
||||||
"vue-js-modal": "1.3.25",
|
"vue-js-modal": "1.3.26",
|
||||||
"vue-json-tree-view": "2.1.4",
|
"vue-json-tree-view": "2.1.4",
|
||||||
"vue-loader": "15.4.1",
|
"vue-loader": "15.4.1",
|
||||||
"vue-router": "3.0.1",
|
"vue-router": "3.0.1",
|
||||||
@ -221,7 +221,7 @@
|
|||||||
"vuex-persistedstate": "2.5.4",
|
"vuex-persistedstate": "2.5.4",
|
||||||
"web-push": "3.3.2",
|
"web-push": "3.3.2",
|
||||||
"webfinger.js": "2.6.6",
|
"webfinger.js": "2.6.6",
|
||||||
"webpack": "4.17.1",
|
"webpack": "4.17.2",
|
||||||
"webpack-cli": "3.1.0",
|
"webpack-cli": "3.1.0",
|
||||||
"websocket": "1.0.26",
|
"websocket": "1.0.26",
|
||||||
"ws": "6.0.0",
|
"ws": "6.0.0",
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
const gcd = (a, b) => !b ? a : gcd(b, a % b);
|
|
||||||
export default gcd;
|
|
@ -1,53 +0,0 @@
|
|||||||
export default function(qs: string) {
|
|
||||||
const q = {
|
|
||||||
text: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
qs.split(' ').forEach(x => {
|
|
||||||
if (/^([a-z_]+?):(.+?)$/.test(x)) {
|
|
||||||
const [key, value] = x.split(':');
|
|
||||||
switch (key) {
|
|
||||||
case 'user':
|
|
||||||
q['includeUserUsernames'] = value.split(',');
|
|
||||||
break;
|
|
||||||
case 'exclude_user':
|
|
||||||
q['excludeUserUsernames'] = value.split(',');
|
|
||||||
break;
|
|
||||||
case 'follow':
|
|
||||||
q['following'] = value == 'null' ? null : value == 'true';
|
|
||||||
break;
|
|
||||||
case 'reply':
|
|
||||||
q['reply'] = value == 'null' ? null : value == 'true';
|
|
||||||
break;
|
|
||||||
case 'renote':
|
|
||||||
q['renote'] = value == 'null' ? null : value == 'true';
|
|
||||||
break;
|
|
||||||
case 'media':
|
|
||||||
q['media'] = value == 'null' ? null : value == 'true';
|
|
||||||
break;
|
|
||||||
case 'poll':
|
|
||||||
q['poll'] = value == 'null' ? null : value == 'true';
|
|
||||||
break;
|
|
||||||
case 'until':
|
|
||||||
case 'since':
|
|
||||||
// YYYY-MM-DD
|
|
||||||
if (/^[0-9]+\-[0-9]+\-[0-9]+$/) {
|
|
||||||
const [yyyy, mm, dd] = value.split('-');
|
|
||||||
q[`${key}_date`] = (new Date(parseInt(yyyy, 10), parseInt(mm, 10) - 1, parseInt(dd, 10))).getTime();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
q[key] = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
q.text += x + ' ';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (q.text) {
|
|
||||||
q.text = q.text.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
return q;
|
|
||||||
}
|
|
@ -1,14 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="mk-acct">
|
<span class="mk-acct">
|
||||||
<span class="name">@{{ user.username }}</span>
|
<span class="name">@{{ user.username }}</span>
|
||||||
<span class="host" v-if="user.host">@{{ user.host }}</span>
|
<span class="host" v-if="user.host || detail">@{{ user.host || host }}</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import { host } from '../../../config';
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['user']
|
props: ['user', 'detail'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
host
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -24,17 +24,32 @@ export default Vue.extend({
|
|||||||
|
|
||||||
root(isDark)
|
root(isDark)
|
||||||
margin 16px
|
margin 16px
|
||||||
padding 16px
|
|
||||||
color isDark ? #fff : #000
|
color isDark ? #fff : #000
|
||||||
background isDark ? #282C37 : #fff
|
background isDark ? #282C37 : #fff
|
||||||
box-shadow 0 3px 1px -2px rgba(#000, 0.2), 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12)
|
box-shadow 0 3px 1px -2px rgba(#000, 0.2), 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12)
|
||||||
|
|
||||||
|
> header
|
||||||
|
padding 16px
|
||||||
|
font-weight bold
|
||||||
|
font-size 20px
|
||||||
|
color isDark ? #fff : #444
|
||||||
|
|
||||||
|
@media (min-width 500px)
|
||||||
|
padding 24px 32px
|
||||||
|
|
||||||
|
> section
|
||||||
|
padding 20px 16px
|
||||||
|
border-top solid 1px isDark ? rgba(#000, 0.3) : rgba(#000, 0.1)
|
||||||
|
|
||||||
@media (min-width 500px)
|
@media (min-width 500px)
|
||||||
padding 32px
|
padding 32px
|
||||||
|
|
||||||
|
&.fit-top
|
||||||
|
padding-top 0
|
||||||
|
|
||||||
> header
|
> header
|
||||||
font-weight normal
|
margin-bottom 16px
|
||||||
font-size 24px
|
font-weight bold
|
||||||
color isDark ? #fff : #444
|
color isDark ? #fff : #444
|
||||||
|
|
||||||
.ui-card[data-darkmode]
|
.ui-card[data-darkmode]
|
||||||
|
@ -55,7 +55,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
root(isDark)
|
root(isDark)
|
||||||
display inline-block
|
display inline-block
|
||||||
margin 32px 32px 32px 0
|
margin 0 32px 0 0
|
||||||
cursor pointer
|
cursor pointer
|
||||||
transition all 0.3s
|
transition all 0.3s
|
||||||
|
|
||||||
|
@ -64,6 +64,12 @@ root(isDark)
|
|||||||
cursor pointer
|
cursor pointer
|
||||||
transition all 0.3s
|
transition all 0.3s
|
||||||
|
|
||||||
|
&:first-child
|
||||||
|
margin-top 0
|
||||||
|
|
||||||
|
&:last-child
|
||||||
|
margin-bottom 0
|
||||||
|
|
||||||
> *
|
> *
|
||||||
user-select none
|
user-select none
|
||||||
|
|
||||||
@ -89,6 +95,7 @@ root(isDark)
|
|||||||
|
|
||||||
> .button
|
> .button
|
||||||
display inline-block
|
display inline-block
|
||||||
|
flex-shrink 0
|
||||||
margin 3px 0 0 0
|
margin 3px 0 0 0
|
||||||
width 34px
|
width 34px
|
||||||
height 14px
|
height 14px
|
||||||
|
@ -63,7 +63,7 @@ export default Vue.extend({
|
|||||||
local: true,
|
local: true,
|
||||||
reply: false,
|
reply: false,
|
||||||
renote: false,
|
renote: false,
|
||||||
media: false,
|
file: false,
|
||||||
poll: false
|
poll: false
|
||||||
}).then(notes => {
|
}).then(notes => {
|
||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
|
@ -83,7 +83,7 @@ export default Vue.extend({
|
|||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (this.user.isLocked && this.user.hasPendingFollowRequestFromYou) {
|
if (this.user.hasPendingFollowRequestFromYou) {
|
||||||
this.user = await (this as any).api('following/requests/cancel', {
|
this.user = await (this as any).api('following/requests/cancel', {
|
||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
});
|
});
|
||||||
|
@ -55,13 +55,15 @@ export default Vue.extend({
|
|||||||
methods: {
|
methods: {
|
||||||
onFollow(user) {
|
onFollow(user) {
|
||||||
if (user.id == this.u.id) {
|
if (user.id == this.u.id) {
|
||||||
this.user.isFollowing = user.isFollowing;
|
this.u.isFollowing = user.isFollowing;
|
||||||
|
this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onUnfollow(user) {
|
onUnfollow(user) {
|
||||||
if (user.id == this.u.id) {
|
if (user.id == this.u.id) {
|
||||||
this.user.isFollowing = user.isFollowing;
|
this.u.isFollowing = user.isFollowing;
|
||||||
|
this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -74,7 +76,7 @@ export default Vue.extend({
|
|||||||
userId: this.u.id
|
userId: this.u.id
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (this.u.isLocked && this.u.hasPendingFollowRequestFromYou) {
|
if (this.u.hasPendingFollowRequestFromYou) {
|
||||||
this.u = await (this as any).api('following/requests/cancel', {
|
this.u = await (this as any).api('following/requests/cancel', {
|
||||||
userId: this.u.id
|
userId: this.u.id
|
||||||
});
|
});
|
||||||
|
@ -42,8 +42,8 @@
|
|||||||
<span v-if="p.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
|
<span v-if="p.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
|
||||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="media" v-if="p.media.length > 0">
|
<div class="files" v-if="p.files.length > 0">
|
||||||
<mk-media-list :media-list="p.media" :raw="true"/>
|
<mk-media-list :media-list="p.files" :raw="true"/>
|
||||||
</div>
|
</div>
|
||||||
<mk-poll v-if="p.poll" :note="p"/>
|
<mk-poll v-if="p.poll" :note="p"/>
|
||||||
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
|
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
|
||||||
@ -114,7 +114,7 @@ export default Vue.extend({
|
|||||||
isRenote(): boolean {
|
isRenote(): boolean {
|
||||||
return (this.note.renote &&
|
return (this.note.renote &&
|
||||||
this.note.text == null &&
|
this.note.text == null &&
|
||||||
this.note.mediaIds.length == 0 &&
|
this.note.fileIds.length == 0 &&
|
||||||
this.note.poll == null);
|
this.note.poll == null);
|
||||||
},
|
},
|
||||||
p(): any {
|
p(): any {
|
||||||
|
@ -28,8 +28,8 @@
|
|||||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/>
|
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/>
|
||||||
<a class="rp" v-if="p.renote">RP:</a>
|
<a class="rp" v-if="p.renote">RP:</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="media" v-if="p.media.length > 0">
|
<div class="files" v-if="p.files.length > 0">
|
||||||
<mk-media-list :media-list="p.media"/>
|
<mk-media-list :media-list="p.files"/>
|
||||||
</div>
|
</div>
|
||||||
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
||||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
|
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
|
||||||
@ -110,7 +110,7 @@ export default Vue.extend({
|
|||||||
isRenote(): boolean {
|
isRenote(): boolean {
|
||||||
return (this.note.renote &&
|
return (this.note.renote &&
|
||||||
this.note.text == null &&
|
this.note.text == null &&
|
||||||
this.note.mediaIds.length == 0 &&
|
this.note.fileIds.length == 0 &&
|
||||||
this.note.poll == null);
|
this.note.poll == null);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ export default Vue.extend({
|
|||||||
prepend(note, silent = false) {
|
prepend(note, silent = false) {
|
||||||
//#region 弾く
|
//#region 弾く
|
||||||
const isMyNote = note.userId == this.$store.state.i.id;
|
const isMyNote = note.userId == this.$store.state.i.id;
|
||||||
const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null;
|
const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
||||||
|
|
||||||
if (this.$store.state.settings.showMyRenotes === false) {
|
if (this.$store.state.settings.showMyRenotes === false) {
|
||||||
if (isMyNote && isPureRenote) {
|
if (isMyNote && isPureRenote) {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<span class="icon" v-if="geo">%fa:map-marker-alt%</span>
|
<span class="icon" v-if="geo">%fa:map-marker-alt%</span>
|
||||||
<span v-if="!reply">%i18n:@note%</span>
|
<span v-if="!reply">%i18n:@note%</span>
|
||||||
<span v-if="reply">%i18n:@reply%</span>
|
<span v-if="reply">%i18n:@reply%</span>
|
||||||
<span class="count" v-if="media.length != 0">{{ '%i18n:@attaches%'.replace('{}', media.length) }}</span>
|
<span class="count" v-if="files.length != 0">{{ '%i18n:@attaches%'.replace('{}', files.length) }}</span>
|
||||||
<span class="count" v-if="uploadings.length != 0">{{ '%i18n:@uploading-media%'.replace('{}', uploadings.length) }}<mk-ellipsis/></span>
|
<span class="count" v-if="uploadings.length != 0">{{ '%i18n:@uploading-media%'.replace('{}', uploadings.length) }}<mk-ellipsis/></span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@ -14,7 +14,7 @@
|
|||||||
:reply="reply"
|
:reply="reply"
|
||||||
@posted="onPosted"
|
@posted="onPosted"
|
||||||
@change-uploadings="onChangeUploadings"
|
@change-uploadings="onChangeUploadings"
|
||||||
@change-attached-media="onChangeMedia"
|
@change-attached-files="onChangeFiles"
|
||||||
@geo-attached="onGeoAttached"
|
@geo-attached="onGeoAttached"
|
||||||
@geo-dettached="onGeoDettached"/>
|
@geo-dettached="onGeoDettached"/>
|
||||||
</div>
|
</div>
|
||||||
@ -29,7 +29,7 @@ export default Vue.extend({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
uploadings: [],
|
uploadings: [],
|
||||||
media: [],
|
files: [],
|
||||||
geo: null
|
geo: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -42,8 +42,8 @@ export default Vue.extend({
|
|||||||
onChangeUploadings(files) {
|
onChangeUploadings(files) {
|
||||||
this.uploadings = files;
|
this.uploadings = files;
|
||||||
},
|
},
|
||||||
onChangeMedia(media) {
|
onChangeFiles(files) {
|
||||||
this.media = media;
|
this.files = files;
|
||||||
},
|
},
|
||||||
onGeoAttached(geo) {
|
onGeoAttached(geo) {
|
||||||
this.geo = geo;
|
this.geo = geo;
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
@keydown="onKeydown" @paste="onPaste" :placeholder="placeholder"
|
@keydown="onKeydown" @paste="onPaste" :placeholder="placeholder"
|
||||||
v-autocomplete="'text'"
|
v-autocomplete="'text'"
|
||||||
></textarea>
|
></textarea>
|
||||||
<div class="medias" :class="{ with: poll }" v-show="files.length != 0">
|
<div class="files" :class="{ with: poll }" v-show="files.length != 0">
|
||||||
<x-draggable :list="files" :options="{ animation: 150 }">
|
<x-draggable :list="files" :options="{ animation: 150 }">
|
||||||
<div v-for="file in files" :key="file.id">
|
<div v-for="file in files" :key="file.id">
|
||||||
<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div>
|
<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div>
|
||||||
@ -188,7 +188,7 @@ export default Vue.extend({
|
|||||||
(this.$refs.poll as any).set(draft.data.poll);
|
(this.$refs.poll as any).set(draft.data.poll);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.$emit('change-attached-media', this.files);
|
this.$emit('change-attached-files', this.files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,12 +225,12 @@ export default Vue.extend({
|
|||||||
|
|
||||||
attachMedia(driveFile) {
|
attachMedia(driveFile) {
|
||||||
this.files.push(driveFile);
|
this.files.push(driveFile);
|
||||||
this.$emit('change-attached-media', this.files);
|
this.$emit('change-attached-files', this.files);
|
||||||
},
|
},
|
||||||
|
|
||||||
detachMedia(id) {
|
detachMedia(id) {
|
||||||
this.files = this.files.filter(x => x.id != id);
|
this.files = this.files.filter(x => x.id != id);
|
||||||
this.$emit('change-attached-media', this.files);
|
this.$emit('change-attached-files', this.files);
|
||||||
},
|
},
|
||||||
|
|
||||||
onChangeFile() {
|
onChangeFile() {
|
||||||
@ -249,7 +249,7 @@ export default Vue.extend({
|
|||||||
this.text = '';
|
this.text = '';
|
||||||
this.files = [];
|
this.files = [];
|
||||||
this.poll = false;
|
this.poll = false;
|
||||||
this.$emit('change-attached-media', this.files);
|
this.$emit('change-attached-files', this.files);
|
||||||
},
|
},
|
||||||
|
|
||||||
onKeydown(e) {
|
onKeydown(e) {
|
||||||
@ -297,7 +297,7 @@ export default Vue.extend({
|
|||||||
if (driveFile != null && driveFile != '') {
|
if (driveFile != null && driveFile != '') {
|
||||||
const file = JSON.parse(driveFile);
|
const file = JSON.parse(driveFile);
|
||||||
this.files.push(file);
|
this.files.push(file);
|
||||||
this.$emit('change-attached-media', this.files);
|
this.$emit('change-attached-files', this.files);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
@ -354,7 +354,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
(this as any).api('notes/create', {
|
(this as any).api('notes/create', {
|
||||||
text: this.text == '' ? undefined : this.text,
|
text: this.text == '' ? undefined : this.text,
|
||||||
mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
|
fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
|
||||||
replyId: this.reply ? this.reply.id : undefined,
|
replyId: this.reply ? this.reply.id : undefined,
|
||||||
renoteId: this.renote ? this.renote.id : undefined,
|
renoteId: this.renote ? this.renote.id : undefined,
|
||||||
poll: this.poll ? (this.$refs.poll as any).get() : undefined,
|
poll: this.poll ? (this.$refs.poll as any).get() : undefined,
|
||||||
@ -514,7 +514,7 @@ root(isDark)
|
|||||||
margin-right 8px
|
margin-right 8px
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
|
|
||||||
> .medias
|
> .files
|
||||||
margin 0
|
margin 0
|
||||||
padding 0
|
padding 0
|
||||||
background isDark ? #181b23 : lighten($theme-color, 98%)
|
background isDark ? #181b23 : lighten($theme-color, 98%)
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
|
<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
|
||||||
<a class="rp" v-if="note.renoteId" :href="`/notes/${note.renoteId}`">RP: ...</a>
|
<a class="rp" v-if="note.renoteId" :href="`/notes/${note.renoteId}`">RP: ...</a>
|
||||||
</div>
|
</div>
|
||||||
<details v-if="note.media.length > 0">
|
<details v-if="note.files.length > 0">
|
||||||
<summary>({{ '%i18n:@media-count%'.replace('{}', note.media.length) }})</summary>
|
<summary>({{ '%i18n:@media-count%'.replace('{}', note.files.length) }})</summary>
|
||||||
<mk-media-list :media-list="note.media"/>
|
<mk-media-list :media-list="note.files"/>
|
||||||
</details>
|
</details>
|
||||||
<details v-if="note.poll">
|
<details v-if="note.poll">
|
||||||
<summary>%i18n:@poll%</summary>
|
<summary>%i18n:@poll%</summary>
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Menu from '../../../../common/views/components/menu.vue';
|
import Menu from '../../../../common/views/components/menu.vue';
|
||||||
import contextmenu from '../../../api/contextmenu';
|
import contextmenu from '../../../api/contextmenu';
|
||||||
|
import { countIf } from '../../../../../../prelude/array';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: {
|
props: {
|
||||||
@ -117,7 +118,7 @@ export default Vue.extend({
|
|||||||
toggleActive() {
|
toggleActive() {
|
||||||
if (!this.isStacked) return;
|
if (!this.isStacked) return;
|
||||||
const vms = this.$store.state.settings.deck.layout.find(ids => ids.indexOf(this.column.id) != -1).map(id => this.getColumnVm(id));
|
const vms = this.$store.state.settings.deck.layout.find(ids => ids.indexOf(this.column.id) != -1).map(id => this.getColumnVm(id));
|
||||||
if (this.active && vms.filter(vm => vm.$el.classList.contains('active')).length == 1) return;
|
if (this.active && countIf(vm => vm.$el.classList.contains('active'), vms) == 1) return;
|
||||||
this.active = !this.active;
|
this.active = !this.active;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ export default Vue.extend({
|
|||||||
(this as any).api('notes/user-list-timeline', {
|
(this as any).api('notes/user-list-timeline', {
|
||||||
listId: this.list.id,
|
listId: this.list.id,
|
||||||
limit: fetchLimit + 1,
|
limit: fetchLimit + 1,
|
||||||
mediaOnly: this.mediaOnly,
|
withFiles: this.mediaOnly,
|
||||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||||
@ -90,7 +90,7 @@ export default Vue.extend({
|
|||||||
listId: this.list.id,
|
listId: this.list.id,
|
||||||
limit: fetchLimit + 1,
|
limit: fetchLimit + 1,
|
||||||
untilId: (this.$refs.timeline as any).tail().id,
|
untilId: (this.$refs.timeline as any).tail().id,
|
||||||
mediaOnly: this.mediaOnly,
|
withFiles: this.mediaOnly,
|
||||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||||
@ -109,7 +109,7 @@ export default Vue.extend({
|
|||||||
return promise;
|
return promise;
|
||||||
},
|
},
|
||||||
onNote(note) {
|
onNote(note) {
|
||||||
if (this.mediaOnly && note.media.length == 0) return;
|
if (this.mediaOnly && note.files.length == 0) return;
|
||||||
|
|
||||||
// Prepend a note
|
// Prepend a note
|
||||||
(this.$refs.timeline as any).prepend(note);
|
(this.$refs.timeline as any).prepend(note);
|
||||||
|
@ -28,8 +28,8 @@
|
|||||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
||||||
<a class="rp" v-if="p.renote != null">RP:</a>
|
<a class="rp" v-if="p.renote != null">RP:</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="media" v-if="p.media.length > 0">
|
<div class="files" v-if="p.files.length > 0">
|
||||||
<mk-media-list :media-list="p.media"/>
|
<mk-media-list :media-list="p.files"/>
|
||||||
</div>
|
</div>
|
||||||
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
||||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
||||||
@ -54,11 +54,11 @@
|
|||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="srwrkujossgfuhrbnvqkybtzxpblgchi">
|
<div v-else class="srwrkujossgfuhrbnvqkybtzxpblgchi">
|
||||||
<div v-if="note.media.length > 0">
|
<div v-if="note.files.length > 0">
|
||||||
<mk-media-list :media-list="note.media"/>
|
<mk-media-list :media-list="note.files"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="note.renote && note.renote.media.length > 0">
|
<div v-if="note.renote && note.renote.files.length > 0">
|
||||||
<mk-media-list :media-list="note.renote.media"/>
|
<mk-media-list :media-list="note.renote.files"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -100,7 +100,7 @@ export default Vue.extend({
|
|||||||
isRenote(): boolean {
|
isRenote(): boolean {
|
||||||
return (this.note.renote &&
|
return (this.note.renote &&
|
||||||
this.note.text == null &&
|
this.note.text == null &&
|
||||||
this.note.mediaIds.length == 0 &&
|
this.note.fileIds.length == 0 &&
|
||||||
this.note.poll == null);
|
this.note.poll == null);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -371,7 +371,7 @@ root(isDark)
|
|||||||
.mk-url-preview
|
.mk-url-preview
|
||||||
margin-top 8px
|
margin-top 8px
|
||||||
|
|
||||||
> .media
|
> .files
|
||||||
> img
|
> img
|
||||||
display block
|
display block
|
||||||
max-width 100%
|
max-width 100%
|
||||||
|
@ -127,7 +127,7 @@ export default Vue.extend({
|
|||||||
prepend(note, silent = false) {
|
prepend(note, silent = false) {
|
||||||
//#region 弾く
|
//#region 弾く
|
||||||
const isMyNote = note.userId == this.$store.state.i.id;
|
const isMyNote = note.userId == this.$store.state.i.id;
|
||||||
const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null;
|
const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
||||||
|
|
||||||
if (this.$store.state.settings.showMyRenotes === false) {
|
if (this.$store.state.settings.showMyRenotes === false) {
|
||||||
if (isMyNote && isPureRenote) {
|
if (isMyNote && isPureRenote) {
|
||||||
|
@ -96,7 +96,7 @@ export default Vue.extend({
|
|||||||
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
|
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
|
||||||
(this as any).api(this.endpoint, {
|
(this as any).api(this.endpoint, {
|
||||||
limit: fetchLimit + 1,
|
limit: fetchLimit + 1,
|
||||||
mediaOnly: this.mediaOnly,
|
withFiles: this.mediaOnly,
|
||||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||||
@ -117,7 +117,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
const promise = (this as any).api(this.endpoint, {
|
const promise = (this as any).api(this.endpoint, {
|
||||||
limit: fetchLimit + 1,
|
limit: fetchLimit + 1,
|
||||||
mediaOnly: this.mediaOnly,
|
withFiles: this.mediaOnly,
|
||||||
untilId: (this.$refs.timeline as any).tail().id,
|
untilId: (this.$refs.timeline as any).tail().id,
|
||||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||||
@ -138,7 +138,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onNote(note) {
|
onNote(note) {
|
||||||
if (this.mediaOnly && note.media.length == 0) return;
|
if (this.mediaOnly && note.files.length == 0) return;
|
||||||
|
|
||||||
// Prepend a note
|
// Prepend a note
|
||||||
(this.$refs.timeline as any).prepend(note);
|
(this.$refs.timeline as any).prepend(note);
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<div class="title">
|
<div class="title">
|
||||||
<p class="name">{{ user | userName }}</p>
|
<p class="name">{{ user | userName }}</p>
|
||||||
<div>
|
<div>
|
||||||
<span class="username"><mk-acct :user="user"/></span>
|
<span class="username"><mk-acct :user="user" :detail="true" /></span>
|
||||||
<span v-if="user.isBot" title="%i18n:@is-bot%">%fa:robot%</span>
|
<span v-if="user.isBot" title="%i18n:@is-bot%">%fa:robot%</span>
|
||||||
<span class="location" v-if="user.host === null && user.profile.location">%fa:map-marker% {{ user.profile.location }}</span>
|
<span class="location" v-if="user.host === null && user.profile.location">%fa:map-marker% {{ user.profile.location }}</span>
|
||||||
<span class="birthday" v-if="user.host === null && user.profile.birthday">%fa:birthday-cake% {{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)</span>
|
<span class="birthday" v-if="user.host === null && user.profile.birthday">%fa:birthday-cake% {{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)</span>
|
||||||
|
@ -24,12 +24,12 @@ export default Vue.extend({
|
|||||||
mounted() {
|
mounted() {
|
||||||
(this as any).api('users/notes', {
|
(this as any).api('users/notes', {
|
||||||
userId: this.user.id,
|
userId: this.user.id,
|
||||||
withMedia: true,
|
withFiles: true,
|
||||||
limit: 9
|
limit: 9
|
||||||
}).then(notes => {
|
}).then(notes => {
|
||||||
notes.forEach(note => {
|
notes.forEach(note => {
|
||||||
note.media.forEach(media => {
|
note.files.forEach(file => {
|
||||||
if (this.images.length < 9) this.images.push(media);
|
if (this.images.length < 9) this.images.push(file);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.fetching = false;
|
this.fetching = false;
|
||||||
|
@ -66,7 +66,7 @@ export default Vue.extend({
|
|||||||
limit: fetchLimit + 1,
|
limit: fetchLimit + 1,
|
||||||
untilDate: this.date ? this.date.getTime() : undefined,
|
untilDate: this.date ? this.date.getTime() : undefined,
|
||||||
includeReplies: this.mode == 'with-replies',
|
includeReplies: this.mode == 'with-replies',
|
||||||
withMedia: this.mode == 'with-media'
|
withFiles: this.mode == 'with-media'
|
||||||
}).then(notes => {
|
}).then(notes => {
|
||||||
if (notes.length == fetchLimit + 1) {
|
if (notes.length == fetchLimit + 1) {
|
||||||
notes.pop();
|
notes.pop();
|
||||||
@ -86,7 +86,7 @@ export default Vue.extend({
|
|||||||
userId: this.user.id,
|
userId: this.user.id,
|
||||||
limit: fetchLimit + 1,
|
limit: fetchLimit + 1,
|
||||||
includeReplies: this.mode == 'with-replies',
|
includeReplies: this.mode == 'with-replies',
|
||||||
withMedia: this.mode == 'with-media',
|
withFiles: this.mode == 'with-media',
|
||||||
untilId: (this.$refs.timeline as any).tail().id
|
untilId: (this.$refs.timeline as any).tail().id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="main block">
|
<div class="main block">
|
||||||
|
<div>
|
||||||
<h1 v-if="name != 'Misskey'">{{ name }}</h1>
|
<h1 v-if="name != 'Misskey'">{{ name }}</h1>
|
||||||
<h1 v-else><img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name"></h1>
|
<h1 v-else><img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name"></h1>
|
||||||
|
|
||||||
@ -28,22 +29,44 @@
|
|||||||
<span class="signin" @click="signin">%i18n:@signin%</span>
|
<span class="signin" @click="signin">%i18n:@signin%</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="broadcasts block">
|
<div class="announcements block">
|
||||||
<div v-for="broadcast in broadcasts">
|
<header>%fa:broadcast-tower% %i18n:@announcements%</header>
|
||||||
<h1 v-html="broadcast.title"></h1>
|
<div>
|
||||||
<div v-html="broadcast.text"></div>
|
<div v-for="announcement in announcements">
|
||||||
|
<h1 v-html="announcement.title"></h1>
|
||||||
|
<div v-html="announcement.text"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="photos block">
|
||||||
|
<header>%fa:images% %i18n:@photos%</header>
|
||||||
|
<div>
|
||||||
|
<div v-for="photo in photos" :style="`background-image: url(${photo.thumbnailUrl})`"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav block">
|
<div class="nav block">
|
||||||
|
<div>
|
||||||
<mk-nav class="nav"/>
|
<mk-nav class="nav"/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="side">
|
<div class="side">
|
||||||
<mk-trends class="trends block"/>
|
<div class="trends block">
|
||||||
|
<div>
|
||||||
|
<mk-trends/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<mk-welcome-timeline class="tl block" :max="20"/>
|
<div class="tl block">
|
||||||
|
<header>%fa:comment-alt R% %i18n:@timeline%</header>
|
||||||
|
<div>
|
||||||
|
<mk-welcome-timeline class="tl" :max="20"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -71,28 +94,46 @@ export default Vue.extend({
|
|||||||
host,
|
host,
|
||||||
name: 'Misskey',
|
name: 'Misskey',
|
||||||
description: '',
|
description: '',
|
||||||
broadcasts: []
|
announcements: [],
|
||||||
|
photos: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
(this as any).os.getMeta().then(meta => {
|
(this as any).os.getMeta().then(meta => {
|
||||||
this.name = meta.name;
|
this.name = meta.name;
|
||||||
this.description = meta.description;
|
this.description = meta.description;
|
||||||
this.broadcasts = meta.broadcasts;
|
this.announcements = meta.broadcasts;
|
||||||
});
|
});
|
||||||
|
|
||||||
(this as any).api('stats').then(stats => {
|
(this as any).api('stats').then(stats => {
|
||||||
this.stats = stats;
|
this.stats = stats;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const image = [
|
||||||
|
'image/jpeg',
|
||||||
|
'image/png',
|
||||||
|
'image/gif'
|
||||||
|
];
|
||||||
|
|
||||||
|
(this as any).api('notes/local-timeline', {
|
||||||
|
fileType: image,
|
||||||
|
limit: 6
|
||||||
|
}).then(notes => {
|
||||||
|
const files = [].concat(...notes.map(n => n.files));
|
||||||
|
this.photos = files.filter(f => image.includes(f.type)).slice(0, 6);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
signup() {
|
signup() {
|
||||||
this.$modal.show('signup');
|
this.$modal.show('signup');
|
||||||
},
|
},
|
||||||
|
|
||||||
signin() {
|
signin() {
|
||||||
this.$modal.show('signin');
|
this.$modal.show('signin');
|
||||||
},
|
},
|
||||||
|
|
||||||
dark() {
|
dark() {
|
||||||
this.$store.commit('device/set', {
|
this.$store.commit('device/set', {
|
||||||
key: 'darkmode',
|
key: 'darkmode',
|
||||||
@ -166,29 +207,46 @@ root(isDark)
|
|||||||
|
|
||||||
> .body
|
> .body
|
||||||
display grid
|
display grid
|
||||||
grid-template-rows 0.5fr 0.5fr 64px
|
grid-template-rows 1fr 1fr 64px
|
||||||
grid-template-columns 1fr 350px
|
grid-template-columns 1fr 1fr 350px
|
||||||
gap 16px
|
gap 16px
|
||||||
width 100%
|
width 100%
|
||||||
max-width 1200px
|
max-width 1200px
|
||||||
height 100vh
|
height 100vh
|
||||||
min-height 800px
|
min-height 1000px
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
padding 64px
|
padding 64px
|
||||||
|
|
||||||
.block
|
.block
|
||||||
color isDark ? #fff : #444
|
color isDark ? #fff : #444
|
||||||
background isDark ? #313543 : #fff
|
background isDark ? #282C37 : #fff
|
||||||
box-shadow 0 3px 8px rgba(0, 0, 0, 0.2)
|
box-shadow 0 3px 8px rgba(0, 0, 0, 0.2)
|
||||||
//border-radius 8px
|
//border-radius 8px
|
||||||
overflow auto
|
overflow auto
|
||||||
|
|
||||||
|
> header
|
||||||
|
z-index 1
|
||||||
|
padding 0 16px
|
||||||
|
line-height 48px
|
||||||
|
background isDark ? #313543 : #fff
|
||||||
|
|
||||||
|
if !isDark
|
||||||
|
box-shadow 0 1px 0px rgba(0, 0, 0, 0.1)
|
||||||
|
|
||||||
|
& + div
|
||||||
|
max-height calc(100% - 48px)
|
||||||
|
|
||||||
|
> div
|
||||||
|
overflow auto
|
||||||
|
|
||||||
> .main
|
> .main
|
||||||
grid-row 1
|
grid-row 1
|
||||||
grid-column 1
|
grid-column 1 / 3
|
||||||
padding 32px
|
|
||||||
border-top solid 5px $theme-color
|
border-top solid 5px $theme-color
|
||||||
|
|
||||||
|
> div
|
||||||
|
padding 32px
|
||||||
|
|
||||||
> h1
|
> h1
|
||||||
margin 0
|
margin 0
|
||||||
|
|
||||||
@ -222,20 +280,11 @@ root(isDark)
|
|||||||
&:hover
|
&:hover
|
||||||
color $theme-color
|
color $theme-color
|
||||||
|
|
||||||
> .hashtags
|
> .announcements
|
||||||
margin 16px auto
|
|
||||||
width $width
|
|
||||||
font-size 14px
|
|
||||||
background rgba(#000, 0.3)
|
|
||||||
border-radius 8px
|
|
||||||
|
|
||||||
> *
|
|
||||||
display inline-block
|
|
||||||
margin 14px
|
|
||||||
|
|
||||||
> .broadcasts
|
|
||||||
grid-row 2
|
grid-row 2
|
||||||
grid-column 1
|
grid-column 1
|
||||||
|
|
||||||
|
> div
|
||||||
padding 32px
|
padding 32px
|
||||||
|
|
||||||
> div
|
> div
|
||||||
@ -245,20 +294,37 @@ root(isDark)
|
|||||||
|
|
||||||
> h1
|
> h1
|
||||||
margin 0
|
margin 0
|
||||||
font-size 1.5em
|
font-size 1.25em
|
||||||
|
|
||||||
|
> .photos
|
||||||
|
grid-row 2
|
||||||
|
grid-column 2
|
||||||
|
|
||||||
|
> div
|
||||||
|
display grid
|
||||||
|
grid-template-rows 1fr 1fr 1fr
|
||||||
|
grid-template-columns 1fr 1fr
|
||||||
|
gap 8px
|
||||||
|
height 100%
|
||||||
|
padding 16px
|
||||||
|
|
||||||
|
> div
|
||||||
|
//border-radius 4px
|
||||||
|
background-position center center
|
||||||
|
background-size cover
|
||||||
|
|
||||||
> .nav
|
> .nav
|
||||||
display flex
|
display flex
|
||||||
justify-content center
|
justify-content center
|
||||||
align-items center
|
align-items center
|
||||||
grid-row 3
|
grid-row 3
|
||||||
grid-column 1
|
grid-column 1 / 3
|
||||||
font-size 14px
|
font-size 14px
|
||||||
|
|
||||||
> .side
|
> .side
|
||||||
display grid
|
display grid
|
||||||
grid-row 1 / 4
|
grid-row 1 / 4
|
||||||
grid-column 2
|
grid-column 3
|
||||||
grid-template-rows 1fr 350px
|
grid-template-rows 1fr 350px
|
||||||
grid-template-columns 1fr
|
grid-template-columns 1fr
|
||||||
gap 16px
|
gap 16px
|
||||||
@ -266,8 +332,6 @@ root(isDark)
|
|||||||
> .tl
|
> .tl
|
||||||
grid-row 1
|
grid-row 1
|
||||||
grid-column 1
|
grid-column 1
|
||||||
text-align left
|
|
||||||
max-height 100%
|
|
||||||
overflow auto
|
overflow auto
|
||||||
|
|
||||||
> .trends
|
> .trends
|
||||||
|
@ -49,7 +49,7 @@ export default define({
|
|||||||
offset: this.offset,
|
offset: this.offset,
|
||||||
renote: false,
|
renote: false,
|
||||||
reply: false,
|
reply: false,
|
||||||
media: false,
|
file: false,
|
||||||
poll: false
|
poll: false
|
||||||
}).then(notes => {
|
}).then(notes => {
|
||||||
const note = notes ? notes[0] : null;
|
const note = notes ? notes[0] : null;
|
||||||
|
@ -67,7 +67,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import * as EXIF from 'exif-js';
|
import * as EXIF from 'exif-js';
|
||||||
import * as hljs from 'highlight.js';
|
import * as hljs from 'highlight.js';
|
||||||
import gcd from '../../../common/scripts/gcd';
|
import { gcd } from '../../../../../prelude/math';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['file'],
|
props: ['file'],
|
||||||
|
@ -48,12 +48,14 @@ export default Vue.extend({
|
|||||||
onFollow(user) {
|
onFollow(user) {
|
||||||
if (user.id == this.u.id) {
|
if (user.id == this.u.id) {
|
||||||
this.u.isFollowing = user.isFollowing;
|
this.u.isFollowing = user.isFollowing;
|
||||||
|
this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onUnfollow(user) {
|
onUnfollow(user) {
|
||||||
if (user.id == this.u.id) {
|
if (user.id == this.u.id) {
|
||||||
this.u.isFollowing = user.isFollowing;
|
this.u.isFollowing = user.isFollowing;
|
||||||
|
this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -66,7 +68,7 @@ export default Vue.extend({
|
|||||||
userId: this.u.id
|
userId: this.u.id
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (this.u.isLocked && this.u.hasPendingFollowRequestFromYou) {
|
if (this.u.hasPendingFollowRequestFromYou) {
|
||||||
this.u = await (this as any).api('following/requests/cancel', {
|
this.u = await (this as any).api('following/requests/cancel', {
|
||||||
userId: this.u.id
|
userId: this.u.id
|
||||||
});
|
});
|
||||||
|
@ -40,8 +40,8 @@
|
|||||||
<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
|
<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
|
||||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="media" v-if="p.media.length > 0">
|
<div class="files" v-if="p.files.length > 0">
|
||||||
<mk-media-list :media-list="p.media" :raw="true"/>
|
<mk-media-list :media-list="p.files" :raw="true"/>
|
||||||
</div>
|
</div>
|
||||||
<mk-poll v-if="p.poll" :note="p"/>
|
<mk-poll v-if="p.poll" :note="p"/>
|
||||||
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
|
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
|
||||||
@ -113,7 +113,7 @@ export default Vue.extend({
|
|||||||
isRenote(): boolean {
|
isRenote(): boolean {
|
||||||
return (this.note.renote &&
|
return (this.note.renote &&
|
||||||
this.note.text == null &&
|
this.note.text == null &&
|
||||||
this.note.mediaIds.length == 0 &&
|
this.note.fileIds.length == 0 &&
|
||||||
this.note.poll == null);
|
this.note.poll == null);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -369,7 +369,7 @@ root(isDark)
|
|||||||
> .mk-url-preview
|
> .mk-url-preview
|
||||||
margin-top 8px
|
margin-top 8px
|
||||||
|
|
||||||
> .media
|
> .files
|
||||||
> img
|
> img
|
||||||
display block
|
display block
|
||||||
max-width 100%
|
max-width 100%
|
||||||
|
@ -28,8 +28,8 @@
|
|||||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/>
|
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/>
|
||||||
<a class="rp" v-if="p.renote != null">RP:</a>
|
<a class="rp" v-if="p.renote != null">RP:</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="media" v-if="p.media.length > 0">
|
<div class="files" v-if="p.files.length > 0">
|
||||||
<mk-media-list :media-list="p.media"/>
|
<mk-media-list :media-list="p.files"/>
|
||||||
</div>
|
</div>
|
||||||
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
||||||
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
|
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
|
||||||
@ -90,7 +90,7 @@ export default Vue.extend({
|
|||||||
isRenote(): boolean {
|
isRenote(): boolean {
|
||||||
return (this.note.renote &&
|
return (this.note.renote &&
|
||||||
this.note.text == null &&
|
this.note.text == null &&
|
||||||
this.note.mediaIds.length == 0 &&
|
this.note.fileIds.length == 0 &&
|
||||||
this.note.poll == null);
|
this.note.poll == null);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -414,7 +414,7 @@ root(isDark)
|
|||||||
.mk-url-preview
|
.mk-url-preview
|
||||||
margin-top 8px
|
margin-top 8px
|
||||||
|
|
||||||
> .media
|
> .files
|
||||||
> img
|
> img
|
||||||
display block
|
display block
|
||||||
max-width 100%
|
max-width 100%
|
||||||
|
@ -125,7 +125,7 @@ export default Vue.extend({
|
|||||||
prepend(note, silent = false) {
|
prepend(note, silent = false) {
|
||||||
//#region 弾く
|
//#region 弾く
|
||||||
const isMyNote = note.userId == this.$store.state.i.id;
|
const isMyNote = note.userId == this.$store.state.i.id;
|
||||||
const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null;
|
const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
||||||
|
|
||||||
if (this.$store.state.settings.showMyRenotes === false) {
|
if (this.$store.state.settings.showMyRenotes === false) {
|
||||||
if (isMyNote && isPureRenote) {
|
if (isMyNote && isPureRenote) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-notify">
|
<div class="mk-notify" :class="pos">
|
||||||
<div>
|
<div>
|
||||||
<mk-notification-preview :notification="notification"/>
|
<mk-notification-preview :notification="notification"/>
|
||||||
</div>
|
</div>
|
||||||
@ -12,11 +12,16 @@ import * as anime from 'animejs';
|
|||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['notification'],
|
props: ['notification'],
|
||||||
|
computed: {
|
||||||
|
pos() {
|
||||||
|
return this.$store.state.device.mobileNotificationPosition;
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
anime({
|
anime({
|
||||||
targets: this.$el,
|
targets: this.$el,
|
||||||
bottom: '0px',
|
[this.pos]: '0px',
|
||||||
duration: 500,
|
duration: 500,
|
||||||
easing: 'easeOutQuad'
|
easing: 'easeOutQuad'
|
||||||
});
|
});
|
||||||
@ -24,7 +29,7 @@ export default Vue.extend({
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
anime({
|
anime({
|
||||||
targets: this.$el,
|
targets: this.$el,
|
||||||
bottom: '-72px',
|
[this.pos]: `-${this.$el.offsetHeight}px`,
|
||||||
duration: 500,
|
duration: 500,
|
||||||
easing: 'easeOutQuad',
|
easing: 'easeOutQuad',
|
||||||
complete: () => this.$destroy()
|
complete: () => this.$destroy()
|
||||||
@ -37,24 +42,32 @@ export default Vue.extend({
|
|||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.mk-notify
|
.mk-notify
|
||||||
|
$height = 78px
|
||||||
|
|
||||||
position fixed
|
position fixed
|
||||||
z-index 1024
|
z-index 1024
|
||||||
bottom -72px
|
|
||||||
left 0
|
left 0
|
||||||
right 0
|
right 0
|
||||||
width 100%
|
width 100%
|
||||||
max-width 500px
|
max-width 500px
|
||||||
height 72px
|
height $height
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
padding 8px
|
padding 8px
|
||||||
pointer-events none
|
pointer-events none
|
||||||
font-size 80%
|
font-size 80%
|
||||||
|
|
||||||
|
&.bottom
|
||||||
|
bottom -($height)
|
||||||
|
|
||||||
|
&.top
|
||||||
|
top -($height)
|
||||||
|
|
||||||
> div
|
> div
|
||||||
height 100%
|
height 100%
|
||||||
-webkit-backdrop-filter blur(2px)
|
-webkit-backdrop-filter blur(2px)
|
||||||
backdrop-filter blur(2px)
|
backdrop-filter blur(2px)
|
||||||
background-color rgba(#000, 0.5)
|
background-color rgba(#000, 0.5)
|
||||||
border-radius 6px
|
border-radius 7px
|
||||||
|
overflow hidden
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
|
<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
|
||||||
<div class="bg" ref="bg" @click="onBgClick"></div>
|
<div class="bg" ref="bg"></div>
|
||||||
<div class="main" ref="main" @click.self="onBgClick">
|
<div class="main" ref="main">
|
||||||
<mk-post-form ref="form"
|
<mk-post-form ref="form"
|
||||||
:reply="reply"
|
:reply="reply"
|
||||||
:renote="renote"
|
:renote="renote"
|
||||||
@ -83,11 +83,6 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onBgClick() {
|
|
||||||
this.$emit('cancel');
|
|
||||||
this.close();
|
|
||||||
},
|
|
||||||
|
|
||||||
onPosted() {
|
onPosted() {
|
||||||
this.$emit('posted');
|
this.$emit('posted');
|
||||||
this.close();
|
this.close();
|
||||||
|
@ -200,12 +200,12 @@ export default Vue.extend({
|
|||||||
|
|
||||||
attachMedia(driveFile) {
|
attachMedia(driveFile) {
|
||||||
this.files.push(driveFile);
|
this.files.push(driveFile);
|
||||||
this.$emit('change-attached-media', this.files);
|
this.$emit('change-attached-files', this.files);
|
||||||
},
|
},
|
||||||
|
|
||||||
detachMedia(file) {
|
detachMedia(file) {
|
||||||
this.files = this.files.filter(x => x.id != file.id);
|
this.files = this.files.filter(x => x.id != file.id);
|
||||||
this.$emit('change-attached-media', this.files);
|
this.$emit('change-attached-files', this.files);
|
||||||
},
|
},
|
||||||
|
|
||||||
onChangeFile() {
|
onChangeFile() {
|
||||||
@ -269,7 +269,7 @@ export default Vue.extend({
|
|||||||
this.text = '';
|
this.text = '';
|
||||||
this.files = [];
|
this.files = [];
|
||||||
this.poll = false;
|
this.poll = false;
|
||||||
this.$emit('change-attached-media');
|
this.$emit('change-attached-files');
|
||||||
},
|
},
|
||||||
|
|
||||||
post() {
|
post() {
|
||||||
@ -277,7 +277,7 @@ export default Vue.extend({
|
|||||||
const viaMobile = this.$store.state.settings.disableViaMobile !== true;
|
const viaMobile = this.$store.state.settings.disableViaMobile !== true;
|
||||||
(this as any).api('notes/create', {
|
(this as any).api('notes/create', {
|
||||||
text: this.text == '' ? undefined : this.text,
|
text: this.text == '' ? undefined : this.text,
|
||||||
mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
|
fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
|
||||||
replyId: this.reply ? this.reply.id : undefined,
|
replyId: this.reply ? this.reply.id : undefined,
|
||||||
renoteId: this.renote ? this.renote.id : undefined,
|
renoteId: this.renote ? this.renote.id : undefined,
|
||||||
poll: this.poll ? (this.$refs.poll as any).get() : undefined,
|
poll: this.poll ? (this.$refs.poll as any).get() : undefined,
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
|
<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
|
||||||
<a class="rp" v-if="note.renoteId">RP: ...</a>
|
<a class="rp" v-if="note.renoteId">RP: ...</a>
|
||||||
</div>
|
</div>
|
||||||
<details v-if="note.media.length > 0">
|
<details v-if="note.files.length > 0">
|
||||||
<summary>({{ '%i18n:@media-count%'.replace('{}', note.media.length) }})</summary>
|
<summary>({{ '%i18n:@media-count%'.replace('{}', note.files.length) }})</summary>
|
||||||
<mk-media-list :media-list="note.media"/>
|
<mk-media-list :media-list="note.files"/>
|
||||||
</details>
|
</details>
|
||||||
<details v-if="note.poll">
|
<details v-if="note.poll">
|
||||||
<summary>%i18n:@poll%</summary>
|
<summary>%i18n:@poll%</summary>
|
||||||
|
@ -34,6 +34,12 @@
|
|||||||
<li @click="dark"><p><template v-if="$store.state.device.darkmode">%fa:moon%</template><template v-else>%fa:R moon%</template><span>%i18n:@darkmode%</span></p></li>
|
<li @click="dark"><p><template v-if="$store.state.device.darkmode">%fa:moon%</template><template v-else>%fa:R moon%</template><span>%i18n:@darkmode%</span></p></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="announcements" v-if="announcements.length > 0">
|
||||||
|
<article v-for="announcement in announcements">
|
||||||
|
<span v-html="announcement.title" class="title"></span>
|
||||||
|
<div v-html="announcement.text"></div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
<a :href="aboutUrl"><p class="about">%i18n:@about%</p></a>
|
<a :href="aboutUrl"><p class="about">%i18n:@about%</p></a>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
@ -46,23 +52,32 @@ import { lang } from '../../../config';
|
|||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['isOpen'],
|
props: ['isOpen'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
hasGameInvitation: false,
|
hasGameInvitation: false,
|
||||||
connection: null,
|
connection: null,
|
||||||
connectionId: null,
|
connectionId: null,
|
||||||
aboutUrl: `/docs/${lang}/about`
|
aboutUrl: `/docs/${lang}/about`,
|
||||||
|
announcements: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
hasUnreadNotification(): boolean {
|
hasUnreadNotification(): boolean {
|
||||||
return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadNotification;
|
return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadNotification;
|
||||||
},
|
},
|
||||||
|
|
||||||
hasUnreadMessagingMessage(): boolean {
|
hasUnreadMessagingMessage(): boolean {
|
||||||
return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadMessagingMessage;
|
return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadMessagingMessage;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
(this as any).os.getMeta().then(meta => {
|
||||||
|
this.announcements = meta.broadcasts;
|
||||||
|
});
|
||||||
|
|
||||||
if (this.$store.getters.isSignedIn) {
|
if (this.$store.getters.isSignedIn) {
|
||||||
this.connection = (this as any).os.stream.getConnection();
|
this.connection = (this as any).os.stream.getConnection();
|
||||||
this.connectionId = (this as any).os.stream.use();
|
this.connectionId = (this as any).os.stream.use();
|
||||||
@ -71,6 +86,7 @@ export default Vue.extend({
|
|||||||
this.connection.on('reversi_no_invites', this.onReversiNoInvites);
|
this.connection.on('reversi_no_invites', this.onReversiNoInvites);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.$store.getters.isSignedIn) {
|
if (this.$store.getters.isSignedIn) {
|
||||||
this.connection.off('reversi_invited', this.onReversiInvited);
|
this.connection.off('reversi_invited', this.onReversiInvited);
|
||||||
@ -78,18 +94,22 @@ export default Vue.extend({
|
|||||||
(this as any).os.stream.dispose(this.connectionId);
|
(this as any).os.stream.dispose(this.connectionId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
search() {
|
search() {
|
||||||
const query = window.prompt('%i18n:@search%');
|
const query = window.prompt('%i18n:@search%');
|
||||||
if (query == null || query == '') return;
|
if (query == null || query == '') return;
|
||||||
this.$router.push(`/search?q=${encodeURIComponent(query)}`);
|
this.$router.push(`/search?q=${encodeURIComponent(query)}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
onReversiInvited() {
|
onReversiInvited() {
|
||||||
this.hasGameInvitation = true;
|
this.hasGameInvitation = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
onReversiNoInvites() {
|
onReversiNoInvites() {
|
||||||
this.hasGameInvitation = false;
|
this.hasGameInvitation = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
dark() {
|
dark() {
|
||||||
this.$store.commit('device/set', {
|
this.$store.commit('device/set', {
|
||||||
key: 'darkmode',
|
key: 'darkmode',
|
||||||
@ -204,6 +224,17 @@ root(isDark)
|
|||||||
color $color
|
color $color
|
||||||
opacity 0.5
|
opacity 0.5
|
||||||
|
|
||||||
|
.announcements
|
||||||
|
> article
|
||||||
|
background isDark ? rgba(30, 129, 216, 0.2) : rgba(155, 196, 232, 0.2)
|
||||||
|
color isDark ? #fff : #3f4967
|
||||||
|
padding 16px
|
||||||
|
margin 8px 0
|
||||||
|
font-size 12px
|
||||||
|
|
||||||
|
> .title
|
||||||
|
font-weight bold
|
||||||
|
|
||||||
.about
|
.about
|
||||||
margin 0 0 8px 0
|
margin 0 0 8px 0
|
||||||
padding 1em 0
|
padding 1em 0
|
||||||
|
@ -41,7 +41,7 @@ export default Vue.extend({
|
|||||||
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
|
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
|
||||||
(this as any).api('users/notes', {
|
(this as any).api('users/notes', {
|
||||||
userId: this.user.id,
|
userId: this.user.id,
|
||||||
withMedia: this.withMedia,
|
withFiles: this.withMedia,
|
||||||
limit: fetchLimit + 1
|
limit: fetchLimit + 1
|
||||||
}).then(notes => {
|
}).then(notes => {
|
||||||
if (notes.length == fetchLimit + 1) {
|
if (notes.length == fetchLimit + 1) {
|
||||||
@ -62,7 +62,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
const promise = (this as any).api('users/notes', {
|
const promise = (this as any).api('users/notes', {
|
||||||
userId: this.user.id,
|
userId: this.user.id,
|
||||||
withMedia: this.withMedia,
|
withFiles: this.withMedia,
|
||||||
limit: fetchLimit + 1,
|
limit: fetchLimit + 1,
|
||||||
untilId: (this.$refs.timeline as any).tail().id
|
untilId: (this.$refs.timeline as any).tail().id
|
||||||
});
|
});
|
||||||
|
@ -10,46 +10,62 @@
|
|||||||
<ui-card>
|
<ui-card>
|
||||||
<div slot="title">%fa:palette% %i18n:@design%</div>
|
<div slot="title">%fa:palette% %i18n:@design%</div>
|
||||||
|
|
||||||
|
<section>
|
||||||
<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
|
<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
|
||||||
<ui-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons">%i18n:@circle-icons%</ui-switch>
|
<ui-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons">%i18n:@circle-icons%</ui-switch>
|
||||||
<ui-switch v-model="$store.state.settings.iLikeSushi" @change="onChangeILikeSushi">%i18n:common.i-like-sushi%</ui-switch>
|
<ui-switch v-model="$store.state.settings.iLikeSushi" @change="onChangeILikeSushi">%i18n:common.i-like-sushi%</ui-switch>
|
||||||
<ui-switch v-model="$store.state.settings.disableAnimatedMfm" @change="onChangeDisableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch>
|
<ui-switch v-model="$store.state.settings.disableAnimatedMfm" @change="onChangeDisableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch>
|
||||||
<ui-switch v-model="$store.state.settings.games.reversi.showBoardLabels" @change="onChangeReversiBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch>
|
<ui-switch v-model="$store.state.settings.games.reversi.showBoardLabels" @change="onChangeReversiBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch>
|
||||||
<ui-switch v-model="$store.state.settings.games.reversi.useContrastStones" @change="onChangeUseContrastReversiStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
|
<ui-switch v-model="$store.state.settings.games.reversi.useContrastStones" @change="onChangeUseContrastReversiStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<header>%i18n:@timeline%</header>
|
||||||
<div>
|
<div>
|
||||||
<div>%i18n:@timeline%</div>
|
|
||||||
<ui-switch v-model="$store.state.settings.showReplyTarget" @change="onChangeShowReplyTarget">%i18n:@show-reply-target%</ui-switch>
|
<ui-switch v-model="$store.state.settings.showReplyTarget" @change="onChangeShowReplyTarget">%i18n:@show-reply-target%</ui-switch>
|
||||||
<ui-switch v-model="$store.state.settings.showMyRenotes" @change="onChangeShowMyRenotes">%i18n:@show-my-renotes%</ui-switch>
|
<ui-switch v-model="$store.state.settings.showMyRenotes" @change="onChangeShowMyRenotes">%i18n:@show-my-renotes%</ui-switch>
|
||||||
<ui-switch v-model="$store.state.settings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes">%i18n:@show-renoted-my-notes%</ui-switch>
|
<ui-switch v-model="$store.state.settings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes">%i18n:@show-renoted-my-notes%</ui-switch>
|
||||||
<ui-switch v-model="$store.state.settings.showLocalRenotes" @change="onChangeShowLocalRenotes">%i18n:@show-local-renotes%</ui-switch>
|
<ui-switch v-model="$store.state.settings.showLocalRenotes" @change="onChangeShowLocalRenotes">%i18n:@show-local-renotes%</ui-switch>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div>
|
<section>
|
||||||
<div>%i18n:@post-style%</div>
|
<header>%i18n:@post-style%</header>
|
||||||
<ui-radio v-model="postStyle" value="standard">%i18n:@post-style-standard%</ui-radio>
|
<ui-radio v-model="postStyle" value="standard">%i18n:@post-style-standard%</ui-radio>
|
||||||
<ui-radio v-model="postStyle" value="smart">%i18n:@post-style-smart%</ui-radio>
|
<ui-radio v-model="postStyle" value="smart">%i18n:@post-style-smart%</ui-radio>
|
||||||
</div>
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<header>%i18n:@notification-position%</header>
|
||||||
|
<ui-radio v-model="mobileNotificationPosition" value="bottom">%i18n:@notification-position-bottom%</ui-radio>
|
||||||
|
<ui-radio v-model="mobileNotificationPosition" value="top">%i18n:@notification-position-top%</ui-radio>
|
||||||
|
</section>
|
||||||
</ui-card>
|
</ui-card>
|
||||||
|
|
||||||
<ui-card>
|
<ui-card>
|
||||||
<div slot="title">%fa:cog% %i18n:@behavior%</div>
|
<div slot="title">%fa:cog% %i18n:@behavior%</div>
|
||||||
|
|
||||||
|
<section>
|
||||||
<ui-switch v-model="$store.state.settings.fetchOnScroll" @change="onChangeFetchOnScroll">%i18n:@fetch-on-scroll%</ui-switch>
|
<ui-switch v-model="$store.state.settings.fetchOnScroll" @change="onChangeFetchOnScroll">%i18n:@fetch-on-scroll%</ui-switch>
|
||||||
<ui-switch v-model="$store.state.settings.disableViaMobile" @change="onChangeDisableViaMobile">%i18n:@disable-via-mobile%</ui-switch>
|
<ui-switch v-model="$store.state.settings.disableViaMobile" @change="onChangeDisableViaMobile">%i18n:@disable-via-mobile%</ui-switch>
|
||||||
<ui-switch v-model="loadRawImages">%i18n:@load-raw-images%</ui-switch>
|
<ui-switch v-model="loadRawImages">%i18n:@load-raw-images%</ui-switch>
|
||||||
<ui-switch v-model="$store.state.settings.loadRemoteMedia" @change="onChangeLoadRemoteMedia">%i18n:@load-remote-media%</ui-switch>
|
<ui-switch v-model="$store.state.settings.loadRemoteMedia" @change="onChangeLoadRemoteMedia">%i18n:@load-remote-media%</ui-switch>
|
||||||
<ui-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</ui-switch>
|
<ui-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</ui-switch>
|
||||||
|
</section>
|
||||||
</ui-card>
|
</ui-card>
|
||||||
|
|
||||||
<ui-card>
|
<ui-card>
|
||||||
<div slot="title">%fa:volume-up% %i18n:@sound%</div>
|
<div slot="title">%fa:volume-up% %i18n:@sound%</div>
|
||||||
|
|
||||||
|
<section>
|
||||||
<ui-switch v-model="enableSounds">%i18n:@enable-sounds%</ui-switch>
|
<ui-switch v-model="enableSounds">%i18n:@enable-sounds%</ui-switch>
|
||||||
|
</section>
|
||||||
</ui-card>
|
</ui-card>
|
||||||
|
|
||||||
<ui-card>
|
<ui-card>
|
||||||
<div slot="title">%fa:language% %i18n:@lang%</div>
|
<div slot="title">%fa:language% %i18n:@lang%</div>
|
||||||
|
|
||||||
|
<section class="fit-top">
|
||||||
<ui-select v-model="lang" placeholder="%i18n:@auto%">
|
<ui-select v-model="lang" placeholder="%i18n:@auto%">
|
||||||
<optgroup label="%i18n:@recommended%">
|
<optgroup label="%i18n:@recommended%">
|
||||||
<option value="">%i18n:@auto%</option>
|
<option value="">%i18n:@auto%</option>
|
||||||
@ -60,22 +76,26 @@
|
|||||||
</optgroup>
|
</optgroup>
|
||||||
</ui-select>
|
</ui-select>
|
||||||
<span>%fa:info-circle% %i18n:@lang-tip%</span>
|
<span>%fa:info-circle% %i18n:@lang-tip%</span>
|
||||||
|
</section>
|
||||||
</ui-card>
|
</ui-card>
|
||||||
|
|
||||||
<ui-card>
|
<ui-card>
|
||||||
<div slot="title">%fa:B twitter% %i18n:@twitter%</div>
|
<div slot="title">%fa:B twitter% %i18n:@twitter%</div>
|
||||||
|
|
||||||
|
<section>
|
||||||
<p class="account" v-if="$store.state.i.twitter"><a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p>
|
<p class="account" v-if="$store.state.i.twitter"><a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p>
|
||||||
<p>
|
<p>
|
||||||
<a :href="`${apiUrl}/connect/twitter`" target="_blank">{{ $store.state.i.twitter ? '%i18n:@twitter-reconnect%' : '%i18n:@twitter-connect%' }}</a>
|
<a :href="`${apiUrl}/connect/twitter`" target="_blank">{{ $store.state.i.twitter ? '%i18n:@twitter-reconnect%' : '%i18n:@twitter-connect%' }}</a>
|
||||||
<span v-if="$store.state.i.twitter"> or </span>
|
<span v-if="$store.state.i.twitter"> or </span>
|
||||||
<a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="$store.state.i.twitter">%i18n:@twitter-disconnect%</a>
|
<a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="$store.state.i.twitter">%i18n:@twitter-disconnect%</a>
|
||||||
</p>
|
</p>
|
||||||
|
</section>
|
||||||
</ui-card>
|
</ui-card>
|
||||||
|
|
||||||
<ui-card>
|
<ui-card>
|
||||||
<div slot="title">%fa:sync-alt% %i18n:@update%</div>
|
<div slot="title">%fa:sync-alt% %i18n:@update%</div>
|
||||||
|
|
||||||
|
<section>
|
||||||
<div>%i18n:@version% <i>{{ version }}</i></div>
|
<div>%i18n:@version% <i>{{ version }}</i></div>
|
||||||
<template v-if="latestVersion !== undefined">
|
<template v-if="latestVersion !== undefined">
|
||||||
<div>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></div>
|
<div>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></div>
|
||||||
@ -84,6 +104,7 @@
|
|||||||
<template v-if="checkingForUpdate">%i18n:@update-checking%<mk-ellipsis/></template>
|
<template v-if="checkingForUpdate">%i18n:@update-checking%<mk-ellipsis/></template>
|
||||||
<template v-else>%i18n:@check-for-updates%</template>
|
<template v-else>%i18n:@check-for-updates%</template>
|
||||||
</ui-button>
|
</ui-button>
|
||||||
|
</section>
|
||||||
</ui-card>
|
</ui-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -134,6 +155,11 @@ export default Vue.extend({
|
|||||||
set(value) { this.$store.commit('device/set', { key: 'postStyle', value }); }
|
set(value) { this.$store.commit('device/set', { key: 'postStyle', value }); }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mobileNotificationPosition: {
|
||||||
|
get() { return this.$store.state.device.mobileNotificationPosition; },
|
||||||
|
set(value) { this.$store.commit('device/set', { key: 'mobileNotificationPosition', value }); }
|
||||||
|
},
|
||||||
|
|
||||||
lightmode: {
|
lightmode: {
|
||||||
get() { return this.$store.state.device.lightmode; },
|
get() { return this.$store.state.device.lightmode; },
|
||||||
set(value) { this.$store.commit('device/set', { key: 'lightmode', value }); }
|
set(value) { this.$store.commit('device/set', { key: 'lightmode', value }); }
|
||||||
@ -273,7 +299,7 @@ export default Vue.extend({
|
|||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
root(isDark)
|
root(isDark)
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
max-width 500px
|
max-width 600px
|
||||||
width 100%
|
width 100%
|
||||||
|
|
||||||
> .signin-as
|
> .signin-as
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<ui-card>
|
<ui-card>
|
||||||
<div slot="title">%fa:user% %i18n:@title%</div>
|
<div slot="title">%fa:user% %i18n:@title%</div>
|
||||||
|
|
||||||
|
<section class="fit-top">
|
||||||
<ui-form :disabled="saving">
|
<ui-form :disabled="saving">
|
||||||
<ui-input v-model="name" :max="30">
|
<ui-input v-model="name" :max="30">
|
||||||
<span>%i18n:@name%</span>
|
<span>%i18n:@name%</span>
|
||||||
@ -43,6 +44,7 @@
|
|||||||
|
|
||||||
<ui-button @click="save">%i18n:@save%</ui-button>
|
<ui-button @click="save">%i18n:@save%</ui-button>
|
||||||
</ui-form>
|
</ui-form>
|
||||||
|
</section>
|
||||||
</ui-card>
|
</ui-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h1>{{ user | userName }}</h1>
|
<h1>{{ user | userName }}</h1>
|
||||||
<span class="username"><mk-acct :user="user"/></span>
|
<span class="username"><mk-acct :user="user" :detail="true" /></span>
|
||||||
<span class="followed" v-if="user.isFollowed">%i18n:@follows-you%</span>
|
<span class="followed" v-if="user.isFollowed">%i18n:@follows-you%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
|
@ -26,7 +26,7 @@ export default Vue.extend({
|
|||||||
mounted() {
|
mounted() {
|
||||||
(this as any).api('users/notes', {
|
(this as any).api('users/notes', {
|
||||||
userId: this.user.id,
|
userId: this.user.id,
|
||||||
withMedia: true,
|
withFiles: true,
|
||||||
limit: 6
|
limit: 6
|
||||||
}).then(notes => {
|
}).then(notes => {
|
||||||
notes.forEach(note => {
|
notes.forEach(note => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="welcome">
|
<div class="wgwfgvvimdjvhjfwxropcwksnzftjqes">
|
||||||
<div>
|
<div>
|
||||||
<img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name">
|
<img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name">
|
||||||
<p class="host">{{ host }}</p>
|
<p class="host">{{ host }}</p>
|
||||||
@ -17,10 +17,19 @@
|
|||||||
<div class="hashtags">
|
<div class="hashtags">
|
||||||
<router-link v-for="tag in tags" :key="tag" :to="`/tags/${ tag }`" :title="tag">#{{ tag }}</router-link>
|
<router-link v-for="tag in tags" :key="tag" :to="`/tags/${ tag }`" :title="tag">#{{ tag }}</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="photos">
|
||||||
|
<div v-for="photo in photos" :style="`background-image: url(${photo.thumbnailUrl})`"></div>
|
||||||
|
</div>
|
||||||
<div class="stats" v-if="stats">
|
<div class="stats" v-if="stats">
|
||||||
<span>%fa:user% {{ stats.originalUsersCount | number }}</span>
|
<span>%fa:user% {{ stats.originalUsersCount | number }}</span>
|
||||||
<span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span>
|
<span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="announcements" v-if="announcements && announcements.length > 0">
|
||||||
|
<article v-for="announcement in announcements">
|
||||||
|
<span class="title" v-html="announcement.title"></span>
|
||||||
|
<div v-html="announcement.text"></div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
<small>{{ copyright }}</small>
|
<small>{{ copyright }}</small>
|
||||||
</footer>
|
</footer>
|
||||||
@ -41,13 +50,16 @@ export default Vue.extend({
|
|||||||
host,
|
host,
|
||||||
name: 'Misskey',
|
name: 'Misskey',
|
||||||
description: '',
|
description: '',
|
||||||
tags: []
|
tags: [],
|
||||||
|
photos: [],
|
||||||
|
announcements: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
(this as any).os.getMeta().then(meta => {
|
(this as any).os.getMeta().then(meta => {
|
||||||
this.name = meta.name;
|
this.name = meta.name;
|
||||||
this.description = meta.description;
|
this.description = meta.description;
|
||||||
|
this.announcements = meta.broadcasts;
|
||||||
});
|
});
|
||||||
|
|
||||||
(this as any).api('stats').then(stats => {
|
(this as any).api('stats').then(stats => {
|
||||||
@ -57,12 +69,26 @@ export default Vue.extend({
|
|||||||
(this as any).api('hashtags/trend').then(stats => {
|
(this as any).api('hashtags/trend').then(stats => {
|
||||||
this.tags = stats.map(x => x.tag);
|
this.tags = stats.map(x => x.tag);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const image = [
|
||||||
|
'image/jpeg',
|
||||||
|
'image/png',
|
||||||
|
'image/gif'
|
||||||
|
];
|
||||||
|
|
||||||
|
(this as any).api('notes/local-timeline', {
|
||||||
|
fileType: image,
|
||||||
|
limit: 6
|
||||||
|
}).then(notes => {
|
||||||
|
const files = [].concat(...notes.map(n => n.files));
|
||||||
|
this.photos = files.filter(f => image.includes(f.type)).slice(0, 6);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.welcome
|
root(isDark)
|
||||||
text-align center
|
text-align center
|
||||||
//background #fff
|
//background #fff
|
||||||
|
|
||||||
@ -145,6 +171,19 @@ export default Vue.extend({
|
|||||||
> *
|
> *
|
||||||
margin 0 16px
|
margin 0 16px
|
||||||
|
|
||||||
|
> .photos
|
||||||
|
display grid
|
||||||
|
grid-template-rows 1fr 1fr 1fr
|
||||||
|
grid-template-columns 1fr 1fr
|
||||||
|
gap 8px
|
||||||
|
height 300px
|
||||||
|
margin-top 16px
|
||||||
|
|
||||||
|
> div
|
||||||
|
border-radius 4px
|
||||||
|
background-position center center
|
||||||
|
background-size cover
|
||||||
|
|
||||||
> .stats
|
> .stats
|
||||||
margin 16px 0
|
margin 16px 0
|
||||||
padding 8px
|
padding 8px
|
||||||
@ -156,6 +195,20 @@ export default Vue.extend({
|
|||||||
> *
|
> *
|
||||||
margin 0 8px
|
margin 0 8px
|
||||||
|
|
||||||
|
> .announcements
|
||||||
|
margin 16px 0
|
||||||
|
|
||||||
|
> article
|
||||||
|
background isDark ? rgba(30, 129, 216, 0.2) : rgba(155, 196, 232, 0.2)
|
||||||
|
border-radius 6px
|
||||||
|
color isDark ? #fff : #3f4967
|
||||||
|
padding 16px
|
||||||
|
margin 8px 0
|
||||||
|
font-size 12px
|
||||||
|
|
||||||
|
> .title
|
||||||
|
font-weight bold
|
||||||
|
|
||||||
> footer
|
> footer
|
||||||
text-align center
|
text-align center
|
||||||
color #444
|
color #444
|
||||||
@ -165,4 +218,10 @@ export default Vue.extend({
|
|||||||
margin 16px 0 0 0
|
margin 16px 0 0 0
|
||||||
opacity 0.7
|
opacity 0.7
|
||||||
|
|
||||||
|
.wgwfgvvimdjvhjfwxropcwksnzftjqes[data-darkmode]
|
||||||
|
root(true)
|
||||||
|
|
||||||
|
.wgwfgvvimdjvhjfwxropcwksnzftjqes:not([data-darkmode])
|
||||||
|
root(false)
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -43,7 +43,8 @@ const defaultDeviceSettings = {
|
|||||||
debug: false,
|
debug: false,
|
||||||
lightmode: false,
|
lightmode: false,
|
||||||
loadRawImages: false,
|
loadRawImages: false,
|
||||||
postStyle: 'standard'
|
postStyle: 'standard',
|
||||||
|
mobileNotificationPosition: 'bottom'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (os: MiOS) => new Vuex.Store({
|
export default (os: MiOS) => new Vuex.Store({
|
||||||
|
@ -7,6 +7,7 @@ import { URL } from 'url';
|
|||||||
import * as yaml from 'js-yaml';
|
import * as yaml from 'js-yaml';
|
||||||
import { Source, Mixin } from './types';
|
import { Source, Mixin } from './types';
|
||||||
import isUrl = require('is-url');
|
import isUrl = require('is-url');
|
||||||
|
const pkg = require('../../package.json');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path of configuration directory
|
* Path of configuration directory
|
||||||
@ -43,6 +44,7 @@ export default function load() {
|
|||||||
mixin.stats_url = `${mixin.scheme}://${mixin.host}/stats`;
|
mixin.stats_url = `${mixin.scheme}://${mixin.host}/stats`;
|
||||||
mixin.status_url = `${mixin.scheme}://${mixin.host}/status`;
|
mixin.status_url = `${mixin.scheme}://${mixin.host}/status`;
|
||||||
mixin.drive_url = `${mixin.scheme}://${mixin.host}/files`;
|
mixin.drive_url = `${mixin.scheme}://${mixin.host}/files`;
|
||||||
|
mixin.user_agent = `Misskey/${pkg.version} (${config.url})`;
|
||||||
|
|
||||||
if (config.localDriveCapacityMb == null) config.localDriveCapacityMb = 256;
|
if (config.localDriveCapacityMb == null) config.localDriveCapacityMb = 256;
|
||||||
if (config.remoteDriveCapacityMb == null) config.remoteDriveCapacityMb = 8;
|
if (config.remoteDriveCapacityMb == null) config.remoteDriveCapacityMb = 8;
|
||||||
|
@ -114,6 +114,7 @@ export type Mixin = {
|
|||||||
status_url: string;
|
status_url: string;
|
||||||
dev_url: string;
|
dev_url: string;
|
||||||
drive_url: string;
|
drive_url: string;
|
||||||
|
user_agent: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Config = Source & Mixin;
|
export type Config = Source & Mixin;
|
||||||
|
@ -4,6 +4,12 @@ import config from '../config';
|
|||||||
const index = {
|
const index = {
|
||||||
settings: {
|
settings: {
|
||||||
analysis: {
|
analysis: {
|
||||||
|
normalizer: {
|
||||||
|
lowercase_normalizer: {
|
||||||
|
type: 'custom',
|
||||||
|
filter: ['lowercase']
|
||||||
|
}
|
||||||
|
},
|
||||||
analyzer: {
|
analyzer: {
|
||||||
bigram: {
|
bigram: {
|
||||||
tokenizer: 'bigram_tokenizer'
|
tokenizer: 'bigram_tokenizer'
|
||||||
@ -24,7 +30,8 @@ const index = {
|
|||||||
text: {
|
text: {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
index: true,
|
index: true,
|
||||||
analyzer: 'bigram'
|
analyzer: 'bigram',
|
||||||
|
normalizer: 'lowercase_normalizer'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,19 +33,19 @@ props:
|
|||||||
ja-JP: "投稿の本文"
|
ja-JP: "投稿の本文"
|
||||||
en-US: "The text of this note"
|
en-US: "The text of this note"
|
||||||
|
|
||||||
mediaIds:
|
fileIds:
|
||||||
type: "id(DriveFile)[]"
|
type: "id(DriveFile)[]"
|
||||||
optional: true
|
optional: true
|
||||||
desc:
|
desc:
|
||||||
ja-JP: "添付されているメディアのID (なければレスポンスでは空配列)"
|
ja-JP: "添付されているファイルのID (なければレスポンスでは空配列)"
|
||||||
en-US: "The IDs of the attached media (empty array for response if no media is attached)"
|
en-US: "The IDs of the attached files (empty array for response if no files is attached)"
|
||||||
|
|
||||||
media:
|
files:
|
||||||
type: "entity(DriveFile)[]"
|
type: "entity(DriveFile)[]"
|
||||||
optional: true
|
optional: true
|
||||||
desc:
|
desc:
|
||||||
ja-JP: "添付されているメディア"
|
ja-JP: "添付されているファイル"
|
||||||
en-US: "The attached media"
|
en-US: "The attached files"
|
||||||
|
|
||||||
userId:
|
userId:
|
||||||
type: "id(User)"
|
type: "id(User)"
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { count, countIf } from "../../prelude/array";
|
||||||
|
|
||||||
// MISSKEY REVERSI ENGINE
|
// MISSKEY REVERSI ENGINE
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,8 +90,8 @@ export default class Reversi {
|
|||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある
|
// ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある
|
||||||
if (this.canPutSomewhere(BLACK).length == 0) {
|
if (!this.canPutSomewhere(BLACK)) {
|
||||||
if (this.canPutSomewhere(WHITE).length == 0) {
|
if (!this.canPutSomewhere(WHITE)) {
|
||||||
this.turn = null;
|
this.turn = null;
|
||||||
} else {
|
} else {
|
||||||
this.turn = WHITE;
|
this.turn = WHITE;
|
||||||
@ -101,14 +103,14 @@ export default class Reversi {
|
|||||||
* 黒石の数
|
* 黒石の数
|
||||||
*/
|
*/
|
||||||
public get blackCount() {
|
public get blackCount() {
|
||||||
return this.board.filter(x => x === BLACK).length;
|
return count(BLACK, this.board);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 白石の数
|
* 白石の数
|
||||||
*/
|
*/
|
||||||
public get whiteCount() {
|
public get whiteCount() {
|
||||||
return this.board.filter(x => x === WHITE).length;
|
return count(BLACK, this.board);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -170,9 +172,9 @@ export default class Reversi {
|
|||||||
|
|
||||||
private calcTurn() {
|
private calcTurn() {
|
||||||
// ターン計算
|
// ターン計算
|
||||||
if (this.canPutSomewhere(!this.prevColor).length > 0) {
|
if (this.canPutSomewhere(!this.prevColor)) {
|
||||||
this.turn = !this.prevColor;
|
this.turn = !this.prevColor;
|
||||||
} else if (this.canPutSomewhere(this.prevColor).length > 0) {
|
} else if (this.canPutSomewhere(this.prevColor)) {
|
||||||
this.turn = this.prevColor;
|
this.turn = this.prevColor;
|
||||||
} else {
|
} else {
|
||||||
this.turn = null;
|
this.turn = null;
|
||||||
@ -204,10 +206,17 @@ export default class Reversi {
|
|||||||
/**
|
/**
|
||||||
* 打つことができる場所を取得します
|
* 打つことができる場所を取得します
|
||||||
*/
|
*/
|
||||||
public canPutSomewhere(color: Color): number[] {
|
public puttablePlaces(color: Color): number[] {
|
||||||
return Array.from(this.board.keys()).filter(i => this.canPut(color, i));
|
return Array.from(this.board.keys()).filter(i => this.canPut(color, i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打つことができる場所があるかどうかを取得します
|
||||||
|
*/
|
||||||
|
public canPutSomewhere(color: Color): boolean {
|
||||||
|
return this.puttablePlaces(color).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 指定のマスに石を打つことができるかどうかを取得します
|
* 指定のマスに石を打つことができるかどうかを取得します
|
||||||
* @param color 自分の色
|
* @param color 自分の色
|
||||||
|
@ -4,10 +4,7 @@ const { JSDOM } = jsdom;
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { INote } from '../models/note';
|
import { INote } from '../models/note';
|
||||||
import { TextElement } from './parse';
|
import { TextElement } from './parse';
|
||||||
|
import { intersperse } from '../prelude/array';
|
||||||
function intersperse<T>(sep: T, xs: T[]): T[] {
|
|
||||||
return [].concat(...xs.map(x => [sep, x])).slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers: INote['mentionedRemoteUsers']) => void } = {
|
const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers: INote['mentionedRemoteUsers']) => void } = {
|
||||||
bold({ document }, { bold }) {
|
bold({ document }, { bold }) {
|
||||||
|
@ -16,9 +16,9 @@ const summarize = (note: any): string => {
|
|||||||
// 本文
|
// 本文
|
||||||
summary += note.text ? note.text : '';
|
summary += note.text ? note.text : '';
|
||||||
|
|
||||||
// メディアが添付されているとき
|
// ファイルが添付されているとき
|
||||||
if (note.media.length != 0) {
|
if (note.files.length != 0) {
|
||||||
summary += ` (${note.media.length}つのメディア)`;
|
summary += ` (${note.files.length}つのファイル)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 投票が添付されているとき
|
// 投票が添付されているとき
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { INote } from '../models/note';
|
import { INote } from '../models/note';
|
||||||
|
|
||||||
export default function(note: INote): boolean {
|
export default function(note: INote): boolean {
|
||||||
return note.renoteId != null && (note.text != null || note.poll != null || (note.mediaIds != null && note.mediaIds.length > 0));
|
return note.renoteId != null && (note.text != null || note.poll != null || (note.fileIds != null && note.fileIds.length > 0));
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ export async function deleteDriveFile(driveFile: string | mongo.ObjectID | IDriv
|
|||||||
|
|
||||||
// このDriveFileを添付しているNoteをすべて削除
|
// このDriveFileを添付しているNoteをすべて削除
|
||||||
await Promise.all((
|
await Promise.all((
|
||||||
await Note.find({ mediaIds: d._id })
|
await Note.find({ fileIds: d._id })
|
||||||
).map(x => deleteNote(x)));
|
).map(x => deleteNote(x)));
|
||||||
|
|
||||||
// このDriveFileを添付しているMessagingMessageをすべて削除
|
// このDriveFileを添付しているMessagingMessageをすべて削除
|
||||||
|
@ -6,7 +6,7 @@ import { IUser, pack as packUser } from './user';
|
|||||||
import { pack as packApp } from './app';
|
import { pack as packApp } from './app';
|
||||||
import PollVote, { deletePollVote } from './poll-vote';
|
import PollVote, { deletePollVote } from './poll-vote';
|
||||||
import Reaction, { deleteNoteReaction } from './note-reaction';
|
import Reaction, { deleteNoteReaction } from './note-reaction';
|
||||||
import { pack as packFile } from './drive-file';
|
import { pack as packFile, IDriveFile } from './drive-file';
|
||||||
import NoteWatching, { deleteNoteWatching } from './note-watching';
|
import NoteWatching, { deleteNoteWatching } from './note-watching';
|
||||||
import NoteReaction from './note-reaction';
|
import NoteReaction from './note-reaction';
|
||||||
import Favorite, { deleteFavorite } from './favorite';
|
import Favorite, { deleteFavorite } from './favorite';
|
||||||
@ -17,9 +17,20 @@ const Note = db.get<INote>('notes');
|
|||||||
Note.createIndex('uri', { sparse: true, unique: true });
|
Note.createIndex('uri', { sparse: true, unique: true });
|
||||||
Note.createIndex('userId');
|
Note.createIndex('userId');
|
||||||
Note.createIndex('tagsLower');
|
Note.createIndex('tagsLower');
|
||||||
|
Note.createIndex('_files.contentType');
|
||||||
Note.createIndex({
|
Note.createIndex({
|
||||||
createdAt: -1
|
createdAt: -1
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 後方互換性のため
|
||||||
|
Note.update({}, {
|
||||||
|
$rename: {
|
||||||
|
mediaIds: 'fileIds'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
multi: true
|
||||||
|
});
|
||||||
|
|
||||||
export default Note;
|
export default Note;
|
||||||
|
|
||||||
export function isValidText(text: string): boolean {
|
export function isValidText(text: string): boolean {
|
||||||
@ -34,7 +45,7 @@ export type INote = {
|
|||||||
_id: mongo.ObjectID;
|
_id: mongo.ObjectID;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
deletedAt: Date;
|
deletedAt: Date;
|
||||||
mediaIds: mongo.ObjectID[];
|
fileIds: mongo.ObjectID[];
|
||||||
replyId: mongo.ObjectID;
|
replyId: mongo.ObjectID;
|
||||||
renoteId: mongo.ObjectID;
|
renoteId: mongo.ObjectID;
|
||||||
poll: {
|
poll: {
|
||||||
@ -92,6 +103,7 @@ export type INote = {
|
|||||||
inbox?: string;
|
inbox?: string;
|
||||||
};
|
};
|
||||||
_replyIds?: mongo.ObjectID[];
|
_replyIds?: mongo.ObjectID[];
|
||||||
|
_files?: IDriveFile[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -271,11 +283,15 @@ export const pack = async (
|
|||||||
_note.app = packApp(_note.appId);
|
_note.app = packApp(_note.appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate media
|
// Populate files
|
||||||
_note.media = hide ? [] : Promise.all(_note.mediaIds.map((fileId: mongo.ObjectID) =>
|
_note.files = hide ? [] : Promise.all(_note.fileIds.map((fileId: mongo.ObjectID) =>
|
||||||
packFile(fileId)
|
packFile(fileId)
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// 後方互換性のため
|
||||||
|
_note.mediaIds = _note.fileIds;
|
||||||
|
_note.media = _note.files;
|
||||||
|
|
||||||
// When requested a detailed note data
|
// When requested a detailed note data
|
||||||
if (opts.detail) {
|
if (opts.detail) {
|
||||||
//#region 重いので廃止
|
//#region 重いので廃止
|
||||||
@ -344,7 +360,7 @@ export const pack = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hide) {
|
if (hide) {
|
||||||
_note.mediaIds = [];
|
_note.fileIds = [];
|
||||||
_note.text = null;
|
_note.text = null;
|
||||||
_note.poll = null;
|
_note.poll = null;
|
||||||
_note.cw = null;
|
_note.cw = null;
|
||||||
|
@ -432,10 +432,10 @@ export const pack = (
|
|||||||
followerId: _user.id,
|
followerId: _user.id,
|
||||||
followeeId: meId
|
followeeId: meId
|
||||||
}),
|
}),
|
||||||
_user.isLocked ? FollowRequest.findOne({
|
FollowRequest.findOne({
|
||||||
followerId: meId,
|
followerId: meId,
|
||||||
followeeId: _user.id
|
followeeId: _user.id
|
||||||
}) : Promise.resolve(null),
|
}),
|
||||||
FollowRequest.findOne({
|
FollowRequest.findOne({
|
||||||
followerId: _user.id,
|
followerId: _user.id,
|
||||||
followeeId: meId
|
followeeId: meId
|
||||||
|
3
src/prelude/README.md
Normal file
3
src/prelude/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Prelude
|
||||||
|
このディレクトリのコードはJavaScriptの表現能力を補うためのコードです。
|
||||||
|
Misskey固有の処理とは独立したコードの集まりですが、Misskeyのコードを読みやすくすることを目的としています。
|
11
src/prelude/array.ts
Normal file
11
src/prelude/array.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export function countIf<T>(f: (x: T) => boolean, xs: T[]): number {
|
||||||
|
return xs.filter(f).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function count<T>(x: T, xs: T[]): number {
|
||||||
|
return countIf(y => x === y, xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function intersperse<T>(sep: T, xs: T[]): T[] {
|
||||||
|
return [].concat(...xs.map(x => [sep, x])).slice(1);
|
||||||
|
}
|
3
src/prelude/math.ts
Normal file
3
src/prelude/math.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function gcd(a: number, b: number): number {
|
||||||
|
return b === 0 ? a : gcd(b, a % b);
|
||||||
|
}
|
@ -78,11 +78,11 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||||||
}
|
}
|
||||||
//#endergion
|
//#endergion
|
||||||
|
|
||||||
// 添付メディア
|
// 添付ファイル
|
||||||
// TODO: attachmentは必ずしもImageではない
|
// TODO: attachmentは必ずしもImageではない
|
||||||
// TODO: attachmentは必ずしも配列ではない
|
// TODO: attachmentは必ずしも配列ではない
|
||||||
// Noteがsensitiveなら添付もsensitiveにする
|
// Noteがsensitiveなら添付もsensitiveにする
|
||||||
const media = note.attachment
|
const files = note.attachment
|
||||||
.map(attach => attach.sensitive = note.sensitive)
|
.map(attach => attach.sensitive = note.sensitive)
|
||||||
? await Promise.all(note.attachment.map(x => resolveImage(actor, x)))
|
? await Promise.all(note.attachment.map(x => resolveImage(actor, x)))
|
||||||
: [];
|
: [];
|
||||||
@ -100,7 +100,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||||||
|
|
||||||
return await post(actor, {
|
return await post(actor, {
|
||||||
createdAt: new Date(note.published),
|
createdAt: new Date(note.published),
|
||||||
media,
|
files: files,
|
||||||
reply,
|
reply,
|
||||||
renote: undefined,
|
renote: undefined,
|
||||||
cw: note.summary,
|
cw: note.summary,
|
||||||
|
@ -8,8 +8,8 @@ import User from '../../../models/user';
|
|||||||
import toHtml from '../misc/get-note-html';
|
import toHtml from '../misc/get-note-html';
|
||||||
|
|
||||||
export default async function renderNote(note: INote, dive = true): Promise<any> {
|
export default async function renderNote(note: INote, dive = true): Promise<any> {
|
||||||
const promisedFiles: Promise<IDriveFile[]> = note.mediaIds
|
const promisedFiles: Promise<IDriveFile[]> = note.fileIds
|
||||||
? DriveFile.find({ _id: { $in: note.mediaIds } })
|
? DriveFile.find({ _id: { $in: note.fileIds } })
|
||||||
: Promise.resolve([]);
|
: Promise.resolve([]);
|
||||||
|
|
||||||
let inReplyTo;
|
let inReplyTo;
|
||||||
|
@ -27,6 +27,7 @@ export default (user: ILocalUser, url: string, object: any) => new Promise((reso
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: pathname + search,
|
path: pathname + search,
|
||||||
headers: {
|
headers: {
|
||||||
|
'User-Agent': config.user_agent,
|
||||||
'Content-Type': 'application/activity+json',
|
'Content-Type': 'application/activity+json',
|
||||||
'Digest': `SHA-256=${hash}`
|
'Digest': `SHA-256=${hash}`
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as request from 'request-promise-native';
|
import * as request from 'request-promise-native';
|
||||||
import * as debug from 'debug';
|
import * as debug from 'debug';
|
||||||
import { IObject } from './type';
|
import { IObject } from './type';
|
||||||
//import config from '../../config';
|
import config from '../../config';
|
||||||
|
|
||||||
const log = debug('misskey:activitypub:resolver');
|
const log = debug('misskey:activitypub:resolver');
|
||||||
|
|
||||||
@ -51,6 +51,7 @@ export default class Resolver {
|
|||||||
const object = await request({
|
const object = await request({
|
||||||
url: value,
|
url: value,
|
||||||
headers: {
|
headers: {
|
||||||
|
'User-Agent': config.user_agent,
|
||||||
Accept: 'application/activity+json, application/ld+json'
|
Accept: 'application/activity+json, application/ld+json'
|
||||||
},
|
},
|
||||||
json: true
|
json: true
|
||||||
|
@ -10,6 +10,7 @@ import { setResponseType } from '../activitypub';
|
|||||||
|
|
||||||
import Note from '../../models/note';
|
import Note from '../../models/note';
|
||||||
import renderNote from '../../remote/activitypub/renderer/note';
|
import renderNote from '../../remote/activitypub/renderer/note';
|
||||||
|
import { countIf } from '../../prelude/array';
|
||||||
|
|
||||||
export default async (ctx: Router.IRouterContext) => {
|
export default async (ctx: Router.IRouterContext) => {
|
||||||
const userId = new mongo.ObjectID(ctx.params.user);
|
const userId = new mongo.ObjectID(ctx.params.user);
|
||||||
@ -25,7 +26,7 @@ export default async (ctx: Router.IRouterContext) => {
|
|||||||
const page: boolean = ctx.request.query.page === 'true';
|
const page: boolean = ctx.request.query.page === 'true';
|
||||||
|
|
||||||
// Validate parameters
|
// Validate parameters
|
||||||
if (sinceIdErr || untilIdErr || pageErr || [sinceId, untilId].filter(x => x != null).length > 1) {
|
if (sinceIdErr || untilIdErr || pageErr || countIf(x => x != null, [sinceId, untilId]) > 1) {
|
||||||
ctx.status = 400;
|
ctx.status = 400;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -58,7 +59,7 @@ export default async (ctx: Router.IRouterContext) => {
|
|||||||
$or: [{
|
$or: [{
|
||||||
text: { $ne: null }
|
text: { $ne: null }
|
||||||
}, {
|
}, {
|
||||||
mediaIds: { $ne: [] }
|
fileIds: { $ne: [] }
|
||||||
}]
|
}]
|
||||||
}]
|
}]
|
||||||
} as any;
|
} as any;
|
||||||
|
@ -1,51 +1,65 @@
|
|||||||
/**
|
|
||||||
* Module dependencies
|
|
||||||
*/
|
|
||||||
import $ from 'cafy'; import ID from '../../../misc/cafy-id';
|
import $ from 'cafy'; import ID from '../../../misc/cafy-id';
|
||||||
import Note, { pack } from '../../../models/note';
|
import Note, { pack } from '../../../models/note';
|
||||||
|
import getParams from '../get-params';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '投稿を取得します。'
|
||||||
|
},
|
||||||
|
|
||||||
|
params: {
|
||||||
|
local: $.bool.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'ローカルの投稿に限定するか否か'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
reply: $.bool.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '返信に限定するか否か'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
renote: $.bool.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'Renoteに限定するか否か'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
withFiles: $.bool.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'ファイルが添付された投稿に限定するか否か'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
media: $.bool.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
poll: $.bool.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'アンケートが添付された投稿に限定するか否か'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
limit: $.num.optional.range(1, 100).note({
|
||||||
|
default: 10
|
||||||
|
}),
|
||||||
|
|
||||||
|
sinceId: $.type(ID).optional.note({}),
|
||||||
|
|
||||||
|
untilId: $.type(ID).optional.note({}),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all notes
|
|
||||||
*/
|
|
||||||
export default (params: any) => new Promise(async (res, rej) => {
|
export default (params: any) => new Promise(async (res, rej) => {
|
||||||
// Get 'local' parameter
|
const [ps, psErr] = getParams(meta, params);
|
||||||
const [local, localErr] = $.bool.optional.get(params.local);
|
if (psErr) throw psErr;
|
||||||
if (localErr) return rej('invalid local param');
|
|
||||||
|
|
||||||
// Get 'reply' parameter
|
|
||||||
const [reply, replyErr] = $.bool.optional.get(params.reply);
|
|
||||||
if (replyErr) return rej('invalid reply param');
|
|
||||||
|
|
||||||
// Get 'renote' parameter
|
|
||||||
const [renote, renoteErr] = $.bool.optional.get(params.renote);
|
|
||||||
if (renoteErr) return rej('invalid renote param');
|
|
||||||
|
|
||||||
// Get 'media' parameter
|
|
||||||
const [media, mediaErr] = $.bool.optional.get(params.media);
|
|
||||||
if (mediaErr) return rej('invalid media param');
|
|
||||||
|
|
||||||
// Get 'poll' parameter
|
|
||||||
const [poll, pollErr] = $.bool.optional.get(params.poll);
|
|
||||||
if (pollErr) return rej('invalid poll param');
|
|
||||||
|
|
||||||
// Get 'bot' parameter
|
|
||||||
//const [bot, botErr] = $.bool.optional.get(params.bot);
|
|
||||||
//if (botErr) return rej('invalid bot param');
|
|
||||||
|
|
||||||
// Get 'limit' parameter
|
|
||||||
const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
|
|
||||||
if (limitErr) return rej('invalid limit param');
|
|
||||||
|
|
||||||
// Get 'sinceId' parameter
|
|
||||||
const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
|
|
||||||
if (sinceIdErr) return rej('invalid sinceId param');
|
|
||||||
|
|
||||||
// Get 'untilId' parameter
|
|
||||||
const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
|
|
||||||
if (untilIdErr) return rej('invalid untilId param');
|
|
||||||
|
|
||||||
// Check if both of sinceId and untilId is specified
|
// Check if both of sinceId and untilId is specified
|
||||||
if (sinceId && untilId) {
|
if (ps.sinceId && ps.untilId) {
|
||||||
return rej('cannot set sinceId and untilId');
|
return rej('cannot set sinceId and untilId');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,35 +70,37 @@ export default (params: any) => new Promise(async (res, rej) => {
|
|||||||
const query = {
|
const query = {
|
||||||
visibility: 'public'
|
visibility: 'public'
|
||||||
} as any;
|
} as any;
|
||||||
if (sinceId) {
|
if (ps.sinceId) {
|
||||||
sort._id = 1;
|
sort._id = 1;
|
||||||
query._id = {
|
query._id = {
|
||||||
$gt: sinceId
|
$gt: ps.sinceId
|
||||||
};
|
};
|
||||||
} else if (untilId) {
|
} else if (ps.untilId) {
|
||||||
query._id = {
|
query._id = {
|
||||||
$lt: untilId
|
$lt: ps.untilId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (local) {
|
if (ps.local) {
|
||||||
query['_user.host'] = null;
|
query['_user.host'] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reply != undefined) {
|
if (ps.reply != undefined) {
|
||||||
query.replyId = reply ? { $exists: true, $ne: null } : null;
|
query.replyId = ps.reply ? { $exists: true, $ne: null } : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (renote != undefined) {
|
if (ps.renote != undefined) {
|
||||||
query.renoteId = renote ? { $exists: true, $ne: null } : null;
|
query.renoteId = ps.renote ? { $exists: true, $ne: null } : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media != undefined) {
|
const withFiles = ps.withFiles != undefined ? ps.withFiles : ps.media;
|
||||||
query.mediaIds = media ? { $exists: true, $ne: null } : [];
|
|
||||||
|
if (withFiles) {
|
||||||
|
query.fileIds = withFiles ? { $exists: true, $ne: null } : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (poll != undefined) {
|
if (ps.poll != undefined) {
|
||||||
query.poll = poll ? { $exists: true, $ne: null } : null;
|
query.poll = ps.poll ? { $exists: true, $ne: null } : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
@ -95,7 +111,7 @@ export default (params: any) => new Promise(async (res, rej) => {
|
|||||||
// Issue query
|
// Issue query
|
||||||
const notes = await Note
|
const notes = await Note
|
||||||
.find(query, {
|
.find(query, {
|
||||||
limit: limit,
|
limit: ps.limit,
|
||||||
sort: sort
|
sort: sort
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -71,9 +71,15 @@ export const meta = {
|
|||||||
ref: 'geo'
|
ref: 'geo'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
fileIds: $.arr($.type(ID)).optional.unique().range(1, 4).note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '添付するファイル'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
mediaIds: $.arr($.type(ID)).optional.unique().range(1, 4).note({
|
mediaIds: $.arr($.type(ID)).optional.unique().range(1, 4).note({
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': '添付するメディア'
|
'ja-JP': '添付するファイル (このパラメータは廃止予定です。代わりに fileIds を使ってください。)'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -124,15 +130,16 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
let files: IDriveFile[] = [];
|
let files: IDriveFile[] = [];
|
||||||
if (ps.mediaIds !== undefined) {
|
const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null;
|
||||||
|
if (fileIds != null) {
|
||||||
// Fetch files
|
// Fetch files
|
||||||
// forEach だと途中でエラーなどがあっても return できないので
|
// forEach だと途中でエラーなどがあっても return できないので
|
||||||
// 敢えて for を使っています。
|
// 敢えて for を使っています。
|
||||||
for (const mediaId of ps.mediaIds) {
|
for (const fileId of fileIds) {
|
||||||
// Fetch file
|
// Fetch file
|
||||||
// SELECT _id
|
// SELECT _id
|
||||||
const entity = await DriveFile.findOne({
|
const entity = await DriveFile.findOne({
|
||||||
_id: mediaId,
|
_id: fileId,
|
||||||
'metadata.userId': user._id
|
'metadata.userId': user._id
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -155,7 +162,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (
|
|||||||
|
|
||||||
if (renote == null) {
|
if (renote == null) {
|
||||||
return rej('renoteee is not found');
|
return rej('renoteee is not found');
|
||||||
} else if (renote.renoteId && !renote.text && !renote.mediaIds) {
|
} else if (renote.renoteId && !renote.text && !renote.fileIds) {
|
||||||
return rej('cannot renote to renote');
|
return rej('cannot renote to renote');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,7 +183,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 返信対象が引用でないRenoteだったらエラー
|
// 返信対象が引用でないRenoteだったらエラー
|
||||||
if (reply.renoteId && !reply.text && !reply.mediaIds) {
|
if (reply.renoteId && !reply.text && !reply.fileIds) {
|
||||||
return rej('cannot reply to renote');
|
return rej('cannot reply to renote');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,13 +198,13 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (
|
|||||||
|
|
||||||
// テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー
|
// テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー
|
||||||
if ((ps.text === undefined || ps.text === null) && files === null && renote === null && ps.poll === undefined) {
|
if ((ps.text === undefined || ps.text === null) && files === null && renote === null && ps.poll === undefined) {
|
||||||
return rej('text, mediaIds, renoteId or poll is required');
|
return rej('text, fileIds, renoteId or poll is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 投稿を作成
|
// 投稿を作成
|
||||||
const note = await create(user, {
|
const note = await create(user, {
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
media: files,
|
files: files,
|
||||||
poll: ps.poll,
|
poll: ps.poll,
|
||||||
text: ps.text,
|
text: ps.text,
|
||||||
reply,
|
reply,
|
||||||
|
@ -3,40 +3,50 @@ import Note from '../../../../models/note';
|
|||||||
import Mute from '../../../../models/mute';
|
import Mute from '../../../../models/mute';
|
||||||
import { pack } from '../../../../models/note';
|
import { pack } from '../../../../models/note';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
|
import getParams from '../../get-params';
|
||||||
|
import { countIf } from '../../../../prelude/array';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'グローバルタイムラインを取得します。'
|
||||||
|
},
|
||||||
|
|
||||||
|
params: {
|
||||||
|
withFiles: $.bool.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'ファイルが添付された投稿に限定するか否か'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
mediaOnly: $.bool.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
limit: $.num.optional.range(1, 100).note({
|
||||||
|
default: 10
|
||||||
|
}),
|
||||||
|
|
||||||
|
sinceId: $.type(ID).optional.note({}),
|
||||||
|
|
||||||
|
untilId: $.type(ID).optional.note({}),
|
||||||
|
|
||||||
|
sinceDate: $.num.optional.note({}),
|
||||||
|
|
||||||
|
untilDate: $.num.optional.note({}),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get timeline of global
|
|
||||||
*/
|
|
||||||
export default async (params: any, user: ILocalUser) => {
|
export default async (params: any, user: ILocalUser) => {
|
||||||
// Get 'limit' parameter
|
const [ps, psErr] = getParams(meta, params);
|
||||||
const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
|
if (psErr) throw psErr;
|
||||||
if (limitErr) throw 'invalid limit param';
|
|
||||||
|
|
||||||
// Get 'sinceId' parameter
|
|
||||||
const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
|
|
||||||
if (sinceIdErr) throw 'invalid sinceId param';
|
|
||||||
|
|
||||||
// Get 'untilId' parameter
|
|
||||||
const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
|
|
||||||
if (untilIdErr) throw 'invalid untilId param';
|
|
||||||
|
|
||||||
// Get 'sinceDate' parameter
|
|
||||||
const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
|
|
||||||
if (sinceDateErr) throw 'invalid sinceDate param';
|
|
||||||
|
|
||||||
// Get 'untilDate' parameter
|
|
||||||
const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
|
|
||||||
if (untilDateErr) throw 'invalid untilDate param';
|
|
||||||
|
|
||||||
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
|
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
|
||||||
if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) {
|
if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
|
||||||
throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
|
throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get 'mediaOnly' parameter
|
|
||||||
const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly);
|
|
||||||
if (mediaOnlyErr) throw 'invalid mediaOnly param';
|
|
||||||
|
|
||||||
// ミュートしているユーザーを取得
|
// ミュートしているユーザーを取得
|
||||||
const mutedUserIds = user ? (await Mute.find({
|
const mutedUserIds = user ? (await Mute.find({
|
||||||
muterId: user._id
|
muterId: user._id
|
||||||
@ -68,27 +78,29 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaOnly) {
|
const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
|
||||||
query.mediaIds = { $exists: true, $ne: [] };
|
|
||||||
|
if (withFiles) {
|
||||||
|
query.fileIds = { $exists: true, $ne: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sinceId) {
|
if (ps.sinceId) {
|
||||||
sort._id = 1;
|
sort._id = 1;
|
||||||
query._id = {
|
query._id = {
|
||||||
$gt: sinceId
|
$gt: ps.sinceId
|
||||||
};
|
};
|
||||||
} else if (untilId) {
|
} else if (ps.untilId) {
|
||||||
query._id = {
|
query._id = {
|
||||||
$lt: untilId
|
$lt: ps.untilId
|
||||||
};
|
};
|
||||||
} else if (sinceDate) {
|
} else if (ps.sinceDate) {
|
||||||
sort._id = 1;
|
sort._id = 1;
|
||||||
query.createdAt = {
|
query.createdAt = {
|
||||||
$gt: new Date(sinceDate)
|
$gt: new Date(ps.sinceDate)
|
||||||
};
|
};
|
||||||
} else if (untilDate) {
|
} else if (ps.untilDate) {
|
||||||
query.createdAt = {
|
query.createdAt = {
|
||||||
$lt: new Date(untilDate)
|
$lt: new Date(ps.untilDate)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
@ -96,7 +108,7 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
// Issue query
|
// Issue query
|
||||||
const timeline = await Note
|
const timeline = await Note
|
||||||
.find(query, {
|
.find(query, {
|
||||||
limit: limit,
|
limit: ps.limit,
|
||||||
sort: sort
|
sort: sort
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5,10 +5,9 @@ import { getFriends } from '../../common/get-friends';
|
|||||||
import { pack } from '../../../../models/note';
|
import { pack } from '../../../../models/note';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
|
import { countIf } from '../../../../prelude/array';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
name: 'notes/hybrid-timeline',
|
|
||||||
|
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': 'ハイブリッドタイムラインを取得します。'
|
'ja-JP': 'ハイブリッドタイムラインを取得します。'
|
||||||
},
|
},
|
||||||
@ -66,9 +65,15 @@ export const meta = {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
withFiles: $.bool.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
mediaOnly: $.bool.optional.note({
|
mediaOnly: $.bool.optional.note({
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します'
|
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -82,7 +87,7 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
if (psErr) throw psErr;
|
if (psErr) throw psErr;
|
||||||
|
|
||||||
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
|
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
|
||||||
if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) {
|
if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
|
||||||
throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
|
throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +169,7 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
}, {
|
}, {
|
||||||
text: { $ne: null }
|
text: { $ne: null }
|
||||||
}, {
|
}, {
|
||||||
mediaIds: { $ne: [] }
|
fileIds: { $ne: [] }
|
||||||
}, {
|
}, {
|
||||||
poll: { $ne: null }
|
poll: { $ne: null }
|
||||||
}]
|
}]
|
||||||
@ -180,7 +185,7 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
}, {
|
}, {
|
||||||
text: { $ne: null }
|
text: { $ne: null }
|
||||||
}, {
|
}, {
|
||||||
mediaIds: { $ne: [] }
|
fileIds: { $ne: [] }
|
||||||
}, {
|
}, {
|
||||||
poll: { $ne: null }
|
poll: { $ne: null }
|
||||||
}]
|
}]
|
||||||
@ -196,16 +201,16 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
}, {
|
}, {
|
||||||
text: { $ne: null }
|
text: { $ne: null }
|
||||||
}, {
|
}, {
|
||||||
mediaIds: { $ne: [] }
|
fileIds: { $ne: [] }
|
||||||
}, {
|
}, {
|
||||||
poll: { $ne: null }
|
poll: { $ne: null }
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.mediaOnly) {
|
if (ps.withFiles || ps.mediaOnly) {
|
||||||
query.$and.push({
|
query.$and.push({
|
||||||
mediaIds: { $exists: true, $ne: [] }
|
fileIds: { $exists: true, $ne: [] }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,40 +3,56 @@ import Note from '../../../../models/note';
|
|||||||
import Mute from '../../../../models/mute';
|
import Mute from '../../../../models/mute';
|
||||||
import { pack } from '../../../../models/note';
|
import { pack } from '../../../../models/note';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
|
import getParams from '../../get-params';
|
||||||
|
import { countIf } from '../../../../prelude/array';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'ローカルタイムラインを取得します。'
|
||||||
|
},
|
||||||
|
|
||||||
|
params: {
|
||||||
|
withFiles: $.bool.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'ファイルが添付された投稿に限定するか否か'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
mediaOnly: $.bool.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
fileType: $.arr($.str).optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '指定された種類のファイルが添付された投稿のみを取得します'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
limit: $.num.optional.range(1, 100).note({
|
||||||
|
default: 10
|
||||||
|
}),
|
||||||
|
|
||||||
|
sinceId: $.type(ID).optional.note({}),
|
||||||
|
|
||||||
|
untilId: $.type(ID).optional.note({}),
|
||||||
|
|
||||||
|
sinceDate: $.num.optional.note({}),
|
||||||
|
|
||||||
|
untilDate: $.num.optional.note({}),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get timeline of local
|
|
||||||
*/
|
|
||||||
export default async (params: any, user: ILocalUser) => {
|
export default async (params: any, user: ILocalUser) => {
|
||||||
// Get 'limit' parameter
|
const [ps, psErr] = getParams(meta, params);
|
||||||
const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
|
if (psErr) throw psErr;
|
||||||
if (limitErr) throw 'invalid limit param';
|
|
||||||
|
|
||||||
// Get 'sinceId' parameter
|
|
||||||
const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
|
|
||||||
if (sinceIdErr) throw 'invalid sinceId param';
|
|
||||||
|
|
||||||
// Get 'untilId' parameter
|
|
||||||
const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
|
|
||||||
if (untilIdErr) throw 'invalid untilId param';
|
|
||||||
|
|
||||||
// Get 'sinceDate' parameter
|
|
||||||
const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
|
|
||||||
if (sinceDateErr) throw 'invalid sinceDate param';
|
|
||||||
|
|
||||||
// Get 'untilDate' parameter
|
|
||||||
const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
|
|
||||||
if (untilDateErr) throw 'invalid untilDate param';
|
|
||||||
|
|
||||||
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
|
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
|
||||||
if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) {
|
if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
|
||||||
throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
|
throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get 'mediaOnly' parameter
|
|
||||||
const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly);
|
|
||||||
if (mediaOnlyErr) throw 'invalid mediaOnly param';
|
|
||||||
|
|
||||||
// ミュートしているユーザーを取得
|
// ミュートしているユーザーを取得
|
||||||
const mutedUserIds = user ? (await Mute.find({
|
const mutedUserIds = user ? (await Mute.find({
|
||||||
muterId: user._id
|
muterId: user._id
|
||||||
@ -69,27 +85,37 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaOnly) {
|
const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
|
||||||
query.mediaIds = { $exists: true, $ne: [] };
|
|
||||||
|
if (withFiles) {
|
||||||
|
query.fileIds = { $exists: true, $ne: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sinceId) {
|
if (ps.fileType) {
|
||||||
|
query.fileIds = { $exists: true, $ne: [] };
|
||||||
|
|
||||||
|
query['_files.contentType'] = {
|
||||||
|
$in: ps.fileType
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.sinceId) {
|
||||||
sort._id = 1;
|
sort._id = 1;
|
||||||
query._id = {
|
query._id = {
|
||||||
$gt: sinceId
|
$gt: ps.sinceId
|
||||||
};
|
};
|
||||||
} else if (untilId) {
|
} else if (ps.untilId) {
|
||||||
query._id = {
|
query._id = {
|
||||||
$lt: untilId
|
$lt: ps.untilId
|
||||||
};
|
};
|
||||||
} else if (sinceDate) {
|
} else if (ps.sinceDate) {
|
||||||
sort._id = 1;
|
sort._id = 1;
|
||||||
query.createdAt = {
|
query.createdAt = {
|
||||||
$gt: new Date(sinceDate)
|
$gt: new Date(ps.sinceDate)
|
||||||
};
|
};
|
||||||
} else if (untilDate) {
|
} else if (ps.untilDate) {
|
||||||
query.createdAt = {
|
query.createdAt = {
|
||||||
$lt: new Date(untilDate)
|
$lt: new Date(ps.untilDate)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
@ -97,7 +123,7 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
// Issue query
|
// Issue query
|
||||||
const timeline = await Note
|
const timeline = await Note
|
||||||
.find(query, {
|
.find(query, {
|
||||||
limit: limit,
|
limit: ps.limit,
|
||||||
sort: sort
|
sort: sort
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -4,119 +4,152 @@ import User, { ILocalUser } from '../../../../models/user';
|
|||||||
import Mute from '../../../../models/mute';
|
import Mute from '../../../../models/mute';
|
||||||
import { getFriendIds } from '../../common/get-friends';
|
import { getFriendIds } from '../../common/get-friends';
|
||||||
import { pack } from '../../../../models/note';
|
import { pack } from '../../../../models/note';
|
||||||
|
import getParams from '../../get-params';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '指定されたタグが付けられた投稿を取得します。'
|
||||||
|
},
|
||||||
|
|
||||||
|
params: {
|
||||||
|
tag: $.str.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'タグ'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
includeUserIds: $.arr($.type(ID)).optional.note({
|
||||||
|
default: []
|
||||||
|
}),
|
||||||
|
|
||||||
|
excludeUserIds: $.arr($.type(ID)).optional.note({
|
||||||
|
default: []
|
||||||
|
}),
|
||||||
|
|
||||||
|
includeUserUsernames: $.arr($.str).optional.note({
|
||||||
|
default: []
|
||||||
|
}),
|
||||||
|
|
||||||
|
excludeUserUsernames: $.arr($.str).optional.note({
|
||||||
|
default: []
|
||||||
|
}),
|
||||||
|
|
||||||
|
following: $.bool.optional.nullable.note({
|
||||||
|
default: null
|
||||||
|
}),
|
||||||
|
|
||||||
|
mute: $.str.optional.note({
|
||||||
|
default: 'mute_all'
|
||||||
|
}),
|
||||||
|
|
||||||
|
reply: $.bool.optional.nullable.note({
|
||||||
|
default: null,
|
||||||
|
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '返信に限定するか否か'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
renote: $.bool.optional.nullable.note({
|
||||||
|
default: null,
|
||||||
|
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'Renoteに限定するか否か'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
withFiles: $.bool.optional.nullable.note({
|
||||||
|
default: null,
|
||||||
|
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'ファイルが添付された投稿に限定するか否か'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
media: $.bool.optional.nullable.note({
|
||||||
|
default: null,
|
||||||
|
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
poll: $.bool.optional.nullable.note({
|
||||||
|
default: null,
|
||||||
|
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'アンケートが添付された投稿に限定するか否か'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
sinceDate: $.num.optional.note({
|
||||||
|
}),
|
||||||
|
|
||||||
|
untilDate: $.num.optional.note({
|
||||||
|
}),
|
||||||
|
|
||||||
|
offset: $.num.optional.min(0).note({
|
||||||
|
default: 0
|
||||||
|
}),
|
||||||
|
|
||||||
|
limit: $.num.optional.range(1, 30).note({
|
||||||
|
default: 10
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Search notes by tag
|
|
||||||
*/
|
|
||||||
export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
|
export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
|
||||||
// Get 'tag' parameter
|
const [ps, psErr] = getParams(meta, params);
|
||||||
const [tag, tagError] = $.str.get(params.tag);
|
if (psErr) throw psErr;
|
||||||
if (tagError) return rej('invalid tag param');
|
|
||||||
|
|
||||||
// Get 'includeUserIds' parameter
|
if (ps.includeUserUsernames != null) {
|
||||||
const [includeUserIds = [], includeUserIdsErr] = $.arr($.type(ID)).optional.get(params.includeUserIds);
|
const ids = (await Promise.all(ps.includeUserUsernames.map(async (username) => {
|
||||||
if (includeUserIdsErr) return rej('invalid includeUserIds param');
|
|
||||||
|
|
||||||
// Get 'excludeUserIds' parameter
|
|
||||||
const [excludeUserIds = [], excludeUserIdsErr] = $.arr($.type(ID)).optional.get(params.excludeUserIds);
|
|
||||||
if (excludeUserIdsErr) return rej('invalid excludeUserIds param');
|
|
||||||
|
|
||||||
// Get 'includeUserUsernames' parameter
|
|
||||||
const [includeUserUsernames = [], includeUserUsernamesErr] = $.arr($.str).optional.get(params.includeUserUsernames);
|
|
||||||
if (includeUserUsernamesErr) return rej('invalid includeUserUsernames param');
|
|
||||||
|
|
||||||
// Get 'excludeUserUsernames' parameter
|
|
||||||
const [excludeUserUsernames = [], excludeUserUsernamesErr] = $.arr($.str).optional.get(params.excludeUserUsernames);
|
|
||||||
if (excludeUserUsernamesErr) return rej('invalid excludeUserUsernames param');
|
|
||||||
|
|
||||||
// Get 'following' parameter
|
|
||||||
const [following = null, followingErr] = $.bool.optional.nullable.get(params.following);
|
|
||||||
if (followingErr) return rej('invalid following param');
|
|
||||||
|
|
||||||
// Get 'mute' parameter
|
|
||||||
const [mute = 'mute_all', muteErr] = $.str.optional.get(params.mute);
|
|
||||||
if (muteErr) return rej('invalid mute param');
|
|
||||||
|
|
||||||
// Get 'reply' parameter
|
|
||||||
const [reply = null, replyErr] = $.bool.optional.nullable.get(params.reply);
|
|
||||||
if (replyErr) return rej('invalid reply param');
|
|
||||||
|
|
||||||
// Get 'renote' parameter
|
|
||||||
const [renote = null, renoteErr] = $.bool.optional.nullable.get(params.renote);
|
|
||||||
if (renoteErr) return rej('invalid renote param');
|
|
||||||
|
|
||||||
// Get 'media' parameter
|
|
||||||
const [media = null, mediaErr] = $.bool.optional.nullable.get(params.media);
|
|
||||||
if (mediaErr) return rej('invalid media param');
|
|
||||||
|
|
||||||
// Get 'poll' parameter
|
|
||||||
const [poll = null, pollErr] = $.bool.optional.nullable.get(params.poll);
|
|
||||||
if (pollErr) return rej('invalid poll param');
|
|
||||||
|
|
||||||
// Get 'sinceDate' parameter
|
|
||||||
const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
|
|
||||||
if (sinceDateErr) throw 'invalid sinceDate param';
|
|
||||||
|
|
||||||
// Get 'untilDate' parameter
|
|
||||||
const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
|
|
||||||
if (untilDateErr) throw 'invalid untilDate param';
|
|
||||||
|
|
||||||
// Get 'offset' parameter
|
|
||||||
const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
|
|
||||||
if (offsetErr) return rej('invalid offset param');
|
|
||||||
|
|
||||||
// Get 'limit' parameter
|
|
||||||
const [limit = 10, limitErr] = $.num.optional.range(1, 30).get(params.limit);
|
|
||||||
if (limitErr) return rej('invalid limit param');
|
|
||||||
|
|
||||||
if (includeUserUsernames != null) {
|
|
||||||
const ids = (await Promise.all(includeUserUsernames.map(async (username) => {
|
|
||||||
const _user = await User.findOne({
|
const _user = await User.findOne({
|
||||||
usernameLower: username.toLowerCase()
|
usernameLower: username.toLowerCase()
|
||||||
});
|
});
|
||||||
return _user ? _user._id : null;
|
return _user ? _user._id : null;
|
||||||
}))).filter(id => id != null);
|
}))).filter(id => id != null);
|
||||||
|
|
||||||
ids.forEach(id => includeUserIds.push(id));
|
ids.forEach(id => ps.includeUserIds.push(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (excludeUserUsernames != null) {
|
if (ps.excludeUserUsernames != null) {
|
||||||
const ids = (await Promise.all(excludeUserUsernames.map(async (username) => {
|
const ids = (await Promise.all(ps.excludeUserUsernames.map(async (username) => {
|
||||||
const _user = await User.findOne({
|
const _user = await User.findOne({
|
||||||
usernameLower: username.toLowerCase()
|
usernameLower: username.toLowerCase()
|
||||||
});
|
});
|
||||||
return _user ? _user._id : null;
|
return _user ? _user._id : null;
|
||||||
}))).filter(id => id != null);
|
}))).filter(id => id != null);
|
||||||
|
|
||||||
ids.forEach(id => excludeUserIds.push(id));
|
ids.forEach(id => ps.excludeUserIds.push(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
let q: any = {
|
let q: any = {
|
||||||
$and: [{
|
$and: [{
|
||||||
tagsLower: tag.toLowerCase()
|
tagsLower: ps.tag.toLowerCase()
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
|
|
||||||
const push = (x: any) => q.$and.push(x);
|
const push = (x: any) => q.$and.push(x);
|
||||||
|
|
||||||
if (includeUserIds && includeUserIds.length != 0) {
|
if (ps.includeUserIds && ps.includeUserIds.length != 0) {
|
||||||
push({
|
push({
|
||||||
userId: {
|
userId: {
|
||||||
$in: includeUserIds
|
$in: ps.includeUserIds
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (excludeUserIds && excludeUserIds.length != 0) {
|
} else if (ps.excludeUserIds && ps.excludeUserIds.length != 0) {
|
||||||
push({
|
push({
|
||||||
userId: {
|
userId: {
|
||||||
$nin: excludeUserIds
|
$nin: ps.excludeUserIds
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (following != null && me != null) {
|
if (ps.following != null && me != null) {
|
||||||
const ids = await getFriendIds(me._id, false);
|
const ids = await getFriendIds(me._id, false);
|
||||||
push({
|
push({
|
||||||
userId: following ? {
|
userId: ps.following ? {
|
||||||
$in: ids
|
$in: ids
|
||||||
} : {
|
} : {
|
||||||
$nin: ids.concat(me._id)
|
$nin: ids.concat(me._id)
|
||||||
@ -131,7 +164,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
|||||||
});
|
});
|
||||||
const mutedUserIds = mutes.map(m => m.muteeId);
|
const mutedUserIds = mutes.map(m => m.muteeId);
|
||||||
|
|
||||||
switch (mute) {
|
switch (ps.mute) {
|
||||||
case 'mute_all':
|
case 'mute_all':
|
||||||
push({
|
push({
|
||||||
userId: {
|
userId: {
|
||||||
@ -202,8 +235,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reply != null) {
|
if (ps.reply != null) {
|
||||||
if (reply) {
|
if (ps.reply) {
|
||||||
push({
|
push({
|
||||||
replyId: {
|
replyId: {
|
||||||
$exists: true,
|
$exists: true,
|
||||||
@ -223,8 +256,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (renote != null) {
|
if (ps.renote != null) {
|
||||||
if (renote) {
|
if (ps.renote) {
|
||||||
push({
|
push({
|
||||||
renoteId: {
|
renoteId: {
|
||||||
$exists: true,
|
$exists: true,
|
||||||
@ -244,10 +277,12 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media != null) {
|
const withFiles = ps.withFiles != null ? ps.withFiles : ps.media;
|
||||||
if (media) {
|
|
||||||
|
if (withFiles != null) {
|
||||||
|
if (withFiles) {
|
||||||
push({
|
push({
|
||||||
mediaIds: {
|
fileIds: {
|
||||||
$exists: true,
|
$exists: true,
|
||||||
$ne: null
|
$ne: null
|
||||||
}
|
}
|
||||||
@ -255,18 +290,18 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
|||||||
} else {
|
} else {
|
||||||
push({
|
push({
|
||||||
$or: [{
|
$or: [{
|
||||||
mediaIds: {
|
fileIds: {
|
||||||
$exists: false
|
$exists: false
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
mediaIds: null
|
fileIds: null
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (poll != null) {
|
if (ps.poll != null) {
|
||||||
if (poll) {
|
if (ps.poll) {
|
||||||
push({
|
push({
|
||||||
poll: {
|
poll: {
|
||||||
$exists: true,
|
$exists: true,
|
||||||
@ -286,18 +321,18 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sinceDate) {
|
if (ps.sinceDate) {
|
||||||
push({
|
push({
|
||||||
createdAt: {
|
createdAt: {
|
||||||
$gt: new Date(sinceDate)
|
$gt: new Date(ps.sinceDate)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (untilDate) {
|
if (ps.untilDate) {
|
||||||
push({
|
push({
|
||||||
createdAt: {
|
createdAt: {
|
||||||
$lt: new Date(untilDate)
|
$lt: new Date(ps.untilDate)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -312,8 +347,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
|||||||
sort: {
|
sort: {
|
||||||
_id: -1
|
_id: -1
|
||||||
},
|
},
|
||||||
limit: limit,
|
limit: ps.limit,
|
||||||
skip: offset
|
skip: ps.offset
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
|
@ -5,6 +5,7 @@ import { getFriends } from '../../common/get-friends';
|
|||||||
import { pack } from '../../../../models/note';
|
import { pack } from '../../../../models/note';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
|
import { countIf } from '../../../../prelude/array';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
@ -67,9 +68,15 @@ export const meta = {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
withFiles: $.bool.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
mediaOnly: $.bool.optional.note({
|
mediaOnly: $.bool.optional.note({
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します'
|
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -80,7 +87,7 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
if (psErr) throw psErr;
|
if (psErr) throw psErr;
|
||||||
|
|
||||||
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
|
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
|
||||||
if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) {
|
if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
|
||||||
throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
|
throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +161,7 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
}, {
|
}, {
|
||||||
text: { $ne: null }
|
text: { $ne: null }
|
||||||
}, {
|
}, {
|
||||||
mediaIds: { $ne: [] }
|
fileIds: { $ne: [] }
|
||||||
}, {
|
}, {
|
||||||
poll: { $ne: null }
|
poll: { $ne: null }
|
||||||
}]
|
}]
|
||||||
@ -170,7 +177,7 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
}, {
|
}, {
|
||||||
text: { $ne: null }
|
text: { $ne: null }
|
||||||
}, {
|
}, {
|
||||||
mediaIds: { $ne: [] }
|
fileIds: { $ne: [] }
|
||||||
}, {
|
}, {
|
||||||
poll: { $ne: null }
|
poll: { $ne: null }
|
||||||
}]
|
}]
|
||||||
@ -186,16 +193,18 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
}, {
|
}, {
|
||||||
text: { $ne: null }
|
text: { $ne: null }
|
||||||
}, {
|
}, {
|
||||||
mediaIds: { $ne: [] }
|
fileIds: { $ne: [] }
|
||||||
}, {
|
}, {
|
||||||
poll: { $ne: null }
|
poll: { $ne: null }
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.mediaOnly) {
|
const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
|
||||||
|
|
||||||
|
if (withFiles) {
|
||||||
query.$and.push({
|
query.$and.push({
|
||||||
mediaIds: { $exists: true, $ne: [] }
|
fileIds: { $exists: true, $ne: [] }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (media != undefined) {
|
if (media != undefined) {
|
||||||
query.mediaIds = media ? { $exists: true, $ne: null } : null;
|
query.fileIds = media ? { $exists: true, $ne: null } : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (poll != undefined) {
|
if (poll != undefined) {
|
||||||
|
@ -73,9 +73,15 @@ export const meta = {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
withFiles: $.bool.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
mediaOnly: $.bool.optional.note({
|
mediaOnly: $.bool.optional.note({
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します'
|
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -160,7 +166,7 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
}, {
|
}, {
|
||||||
text: { $ne: null }
|
text: { $ne: null }
|
||||||
}, {
|
}, {
|
||||||
mediaIds: { $ne: [] }
|
fileIds: { $ne: [] }
|
||||||
}, {
|
}, {
|
||||||
poll: { $ne: null }
|
poll: { $ne: null }
|
||||||
}]
|
}]
|
||||||
@ -176,7 +182,7 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
}, {
|
}, {
|
||||||
text: { $ne: null }
|
text: { $ne: null }
|
||||||
}, {
|
}, {
|
||||||
mediaIds: { $ne: [] }
|
fileIds: { $ne: [] }
|
||||||
}, {
|
}, {
|
||||||
poll: { $ne: null }
|
poll: { $ne: null }
|
||||||
}]
|
}]
|
||||||
@ -192,16 +198,18 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
}, {
|
}, {
|
||||||
text: { $ne: null }
|
text: { $ne: null }
|
||||||
}, {
|
}, {
|
||||||
mediaIds: { $ne: [] }
|
fileIds: { $ne: [] }
|
||||||
}, {
|
}, {
|
||||||
poll: { $ne: null }
|
poll: { $ne: null }
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.mediaOnly) {
|
const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
|
||||||
|
|
||||||
|
if (withFiles) {
|
||||||
query.$and.push({
|
query.$and.push({
|
||||||
mediaIds: { $exists: true, $ne: [] }
|
fileIds: { $exists: true, $ne: [] }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,63 +2,122 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
|||||||
import getHostLower from '../../common/get-host-lower';
|
import getHostLower from '../../common/get-host-lower';
|
||||||
import Note, { pack } from '../../../../models/note';
|
import Note, { pack } from '../../../../models/note';
|
||||||
import User, { ILocalUser } from '../../../../models/user';
|
import User, { ILocalUser } from '../../../../models/user';
|
||||||
|
import getParams from '../../get-params';
|
||||||
|
import { countIf } from '../../../../prelude/array';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '指定したユーザーのタイムラインを取得します。'
|
||||||
|
},
|
||||||
|
|
||||||
|
params: {
|
||||||
|
userId: $.type(ID).optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'ユーザーID'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
username: $.str.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'ユーザー名'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
host: $.str.optional.note({
|
||||||
|
}),
|
||||||
|
|
||||||
|
includeReplies: $.bool.optional.note({
|
||||||
|
default: true,
|
||||||
|
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'リプライを含めるか否か'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
limit: $.num.optional.range(1, 100).note({
|
||||||
|
default: 10,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '最大数'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
sinceId: $.type(ID).optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
untilId: $.type(ID).optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
sinceDate: $.num.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
untilDate: $.num.optional.note({
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
includeMyRenotes: $.bool.optional.note({
|
||||||
|
default: true,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '自分の行ったRenoteを含めるかどうか'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
includeRenotedMyNotes: $.bool.optional.note({
|
||||||
|
default: true,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'Renoteされた自分の投稿を含めるかどうか'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
includeLocalRenotes: $.bool.optional.note({
|
||||||
|
default: true,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'Renoteされたローカルの投稿を含めるかどうか'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
withFiles: $.bool.optional.note({
|
||||||
|
default: false,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
mediaOnly: $.bool.optional.note({
|
||||||
|
default: false,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get notes of a user
|
|
||||||
*/
|
|
||||||
export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
|
export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
|
||||||
// Get 'userId' parameter
|
const [ps, psErr] = getParams(meta, params);
|
||||||
const [userId, userIdErr] = $.type(ID).optional.get(params.userId);
|
if (psErr) throw psErr;
|
||||||
if (userIdErr) return rej('invalid userId param');
|
|
||||||
|
|
||||||
// Get 'username' parameter
|
if (ps.userId === undefined && ps.username === undefined) {
|
||||||
const [username, usernameErr] = $.str.optional.get(params.username);
|
|
||||||
if (usernameErr) return rej('invalid username param');
|
|
||||||
|
|
||||||
if (userId === undefined && username === undefined) {
|
|
||||||
return rej('userId or username is required');
|
return rej('userId or username is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get 'host' parameter
|
|
||||||
const [host, hostErr] = $.str.optional.get(params.host);
|
|
||||||
if (hostErr) return rej('invalid host param');
|
|
||||||
|
|
||||||
// Get 'includeReplies' parameter
|
|
||||||
const [includeReplies = true, includeRepliesErr] = $.bool.optional.get(params.includeReplies);
|
|
||||||
if (includeRepliesErr) return rej('invalid includeReplies param');
|
|
||||||
|
|
||||||
// Get 'withMedia' parameter
|
|
||||||
const [withMedia = false, withMediaErr] = $.bool.optional.get(params.withMedia);
|
|
||||||
if (withMediaErr) return rej('invalid withMedia param');
|
|
||||||
|
|
||||||
// Get 'limit' parameter
|
|
||||||
const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
|
|
||||||
if (limitErr) return rej('invalid limit param');
|
|
||||||
|
|
||||||
// Get 'sinceId' parameter
|
|
||||||
const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
|
|
||||||
if (sinceIdErr) return rej('invalid sinceId param');
|
|
||||||
|
|
||||||
// Get 'untilId' parameter
|
|
||||||
const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
|
|
||||||
if (untilIdErr) return rej('invalid untilId param');
|
|
||||||
|
|
||||||
// Get 'sinceDate' parameter
|
|
||||||
const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
|
|
||||||
if (sinceDateErr) throw 'invalid sinceDate param';
|
|
||||||
|
|
||||||
// Get 'untilDate' parameter
|
|
||||||
const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
|
|
||||||
if (untilDateErr) throw 'invalid untilDate param';
|
|
||||||
|
|
||||||
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
|
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
|
||||||
if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) {
|
if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
|
||||||
throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
|
throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
|
||||||
}
|
}
|
||||||
|
|
||||||
const q = userId !== undefined
|
const q = ps.userId !== undefined
|
||||||
? { _id: userId }
|
? { _id: ps.userId }
|
||||||
: { usernameLower: username.toLowerCase(), host: getHostLower(host) } ;
|
: { usernameLower: ps.username.toLowerCase(), host: getHostLower(ps.host) } ;
|
||||||
|
|
||||||
// Lookup user
|
// Lookup user
|
||||||
const user = await User.findOne(q, {
|
const user = await User.findOne(q, {
|
||||||
@ -80,32 +139,34 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
|||||||
userId: user._id
|
userId: user._id
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
if (sinceId) {
|
if (ps.sinceId) {
|
||||||
sort._id = 1;
|
sort._id = 1;
|
||||||
query._id = {
|
query._id = {
|
||||||
$gt: sinceId
|
$gt: ps.sinceId
|
||||||
};
|
};
|
||||||
} else if (untilId) {
|
} else if (ps.untilId) {
|
||||||
query._id = {
|
query._id = {
|
||||||
$lt: untilId
|
$lt: ps.untilId
|
||||||
};
|
};
|
||||||
} else if (sinceDate) {
|
} else if (ps.sinceDate) {
|
||||||
sort._id = 1;
|
sort._id = 1;
|
||||||
query.createdAt = {
|
query.createdAt = {
|
||||||
$gt: new Date(sinceDate)
|
$gt: new Date(ps.sinceDate)
|
||||||
};
|
};
|
||||||
} else if (untilDate) {
|
} else if (ps.untilDate) {
|
||||||
query.createdAt = {
|
query.createdAt = {
|
||||||
$lt: new Date(untilDate)
|
$lt: new Date(ps.untilDate)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!includeReplies) {
|
if (!ps.includeReplies) {
|
||||||
query.replyId = null;
|
query.replyId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (withMedia) {
|
const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
|
||||||
query.mediaIds = {
|
|
||||||
|
if (withFiles) {
|
||||||
|
query.fileIds = {
|
||||||
$exists: true,
|
$exists: true,
|
||||||
$ne: []
|
$ne: []
|
||||||
};
|
};
|
||||||
@ -115,12 +176,10 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
|||||||
// Issue query
|
// Issue query
|
||||||
const notes = await Note
|
const notes = await Note
|
||||||
.find(query, {
|
.find(query, {
|
||||||
limit: limit,
|
limit: ps.limit,
|
||||||
sort: sort
|
sort: sort
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
res(await Promise.all(notes.map(async (note) =>
|
res(await Promise.all(notes.map(note => pack(note, me))));
|
||||||
await pack(note, me)
|
|
||||||
)));
|
|
||||||
});
|
});
|
||||||
|
@ -34,7 +34,12 @@ export default async (url: string, user: IUser, folderId: mongodb.ObjectID = nul
|
|||||||
// write content at URL to temp file
|
// write content at URL to temp file
|
||||||
await new Promise((res, rej) => {
|
await new Promise((res, rej) => {
|
||||||
const writable = fs.createWriteStream(path);
|
const writable = fs.createWriteStream(path);
|
||||||
request(url)
|
request({
|
||||||
|
url,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': config.user_agent
|
||||||
|
}
|
||||||
|
})
|
||||||
.on('error', rej)
|
.on('error', rej)
|
||||||
.on('end', () => {
|
.on('end', () => {
|
||||||
writable.close();
|
writable.close();
|
||||||
|
@ -11,7 +11,7 @@ import { deliver } from '../../queue';
|
|||||||
import createFollowRequest from './requests/create';
|
import createFollowRequest from './requests/create';
|
||||||
|
|
||||||
export default async function(follower: IUser, followee: IUser) {
|
export default async function(follower: IUser, followee: IUser) {
|
||||||
if (followee.isLocked) {
|
if (followee.isLocked || isLocalUser(follower) && isRemoteUser(followee)) {
|
||||||
await createFollowRequest(follower, followee);
|
await createFollowRequest(follower, followee);
|
||||||
} else {
|
} else {
|
||||||
const following = await Following.insert({
|
const following = await Following.insert({
|
||||||
@ -72,11 +72,6 @@ export default async function(follower: IUser, followee: IUser) {
|
|||||||
notify(followee._id, follower._id, 'follow');
|
notify(followee._id, follower._id, 'follow');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLocalUser(follower) && isRemoteUser(followee)) {
|
|
||||||
const content = pack(renderFollow(follower, followee));
|
|
||||||
deliver(follower, content, followee.inbox);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRemoteUser(follower) && isLocalUser(followee)) {
|
if (isRemoteUser(follower) && isLocalUser(followee)) {
|
||||||
const content = pack(renderAccept(renderFollow(follower, followee)));
|
const content = pack(renderAccept(renderFollow(follower, followee)));
|
||||||
deliver(followee, content, follower.inbox);
|
deliver(followee, content, follower.inbox);
|
||||||
|
@ -75,4 +75,6 @@ export default async function(followee: IUser, follower: IUser) {
|
|||||||
packUser(followee, followee, {
|
packUser(followee, followee, {
|
||||||
detail: true
|
detail: true
|
||||||
}).then(packed => publishUserStream(followee._id, 'meUpdated', packed));
|
}).then(packed => publishUserStream(followee._id, 'meUpdated', packed));
|
||||||
|
|
||||||
|
packUser(followee, follower).then(packed => publishUserStream(follower._id, 'follow', packed));
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,6 @@ import { deliver } from '../../../queue';
|
|||||||
import FollowRequest from '../../../models/follow-request';
|
import FollowRequest from '../../../models/follow-request';
|
||||||
|
|
||||||
export default async function(follower: IUser, followee: IUser) {
|
export default async function(follower: IUser, followee: IUser) {
|
||||||
if (!followee.isLocked) throw '対象のアカウントは鍵アカウントではありません';
|
|
||||||
|
|
||||||
await FollowRequest.insert({
|
await FollowRequest.insert({
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
followerId: follower._id,
|
followerId: follower._id,
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import User, { IUser, isRemoteUser, ILocalUser } from '../../../models/user';
|
import User, { IUser, isRemoteUser, ILocalUser, pack as packUser } from '../../../models/user';
|
||||||
import FollowRequest from '../../../models/follow-request';
|
import FollowRequest from '../../../models/follow-request';
|
||||||
import pack from '../../../remote/activitypub/renderer';
|
import pack from '../../../remote/activitypub/renderer';
|
||||||
import renderFollow from '../../../remote/activitypub/renderer/follow';
|
import renderFollow from '../../../remote/activitypub/renderer/follow';
|
||||||
import renderReject from '../../../remote/activitypub/renderer/reject';
|
import renderReject from '../../../remote/activitypub/renderer/reject';
|
||||||
import { deliver } from '../../../queue';
|
import { deliver } from '../../../queue';
|
||||||
|
import { publishUserStream } from '../../../stream';
|
||||||
|
|
||||||
export default async function(followee: IUser, follower: IUser) {
|
export default async function(followee: IUser, follower: IUser) {
|
||||||
if (isRemoteUser(follower)) {
|
if (isRemoteUser(follower)) {
|
||||||
@ -21,4 +22,6 @@ export default async function(followee: IUser, follower: IUser) {
|
|||||||
pendingReceivedFollowRequestsCount: -1
|
pendingReceivedFollowRequestsCount: -1
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
packUser(followee, follower).then(packed => publishUserStream(follower._id, 'unfollow', packed));
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ type Option = {
|
|||||||
text?: string;
|
text?: string;
|
||||||
reply?: INote;
|
reply?: INote;
|
||||||
renote?: INote;
|
renote?: INote;
|
||||||
media?: IDriveFile[];
|
files?: IDriveFile[];
|
||||||
geo?: any;
|
geo?: any;
|
||||||
poll?: any;
|
poll?: any;
|
||||||
viaMobile?: boolean;
|
viaMobile?: boolean;
|
||||||
@ -135,7 +135,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
|||||||
|
|
||||||
const mentionedUsers = await extractMentionedUsers(tokens);
|
const mentionedUsers = await extractMentionedUsers(tokens);
|
||||||
|
|
||||||
const note = await insertNote(user, data, tokens, tags, mentionedUsers);
|
const note = await insertNote(user, data, tags, mentionedUsers);
|
||||||
|
|
||||||
res(note);
|
res(note);
|
||||||
|
|
||||||
@ -309,10 +309,10 @@ async function publish(user: IUser, note: INote, noteObj: any, reply: INote, ren
|
|||||||
publishToUserLists(note, noteObj);
|
publishToUserLists(note, noteObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function insertNote(user: IUser, data: Option, tokens: ReturnType<typeof parse>, tags: string[], mentionedUsers: IUser[]) {
|
async function insertNote(user: IUser, data: Option, tags: string[], mentionedUsers: IUser[]) {
|
||||||
const insert: any = {
|
const insert: any = {
|
||||||
createdAt: data.createdAt,
|
createdAt: data.createdAt,
|
||||||
mediaIds: data.media ? data.media.map(file => file._id) : [],
|
fileIds: data.files ? data.files.map(file => file._id) : [],
|
||||||
replyId: data.reply ? data.reply._id : null,
|
replyId: data.reply ? data.reply._id : null,
|
||||||
renoteId: data.renote ? data.renote._id : null,
|
renoteId: data.renote ? data.renote._id : null,
|
||||||
text: data.text,
|
text: data.text,
|
||||||
@ -347,7 +347,8 @@ async function insertNote(user: IUser, data: Option, tokens: ReturnType<typeof p
|
|||||||
_user: {
|
_user: {
|
||||||
host: user.host,
|
host: user.host,
|
||||||
inbox: isRemoteUser(user) ? user.inbox : undefined
|
inbox: isRemoteUser(user) ? user.inbox : undefined
|
||||||
}
|
},
|
||||||
|
_files: data.files ? data.files : []
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.uri != null) insert.uri = data.uri;
|
if (data.uri != null) insert.uri = data.uri;
|
||||||
|
@ -23,7 +23,7 @@ export default async function(user: IUser, note: INote) {
|
|||||||
deletedAt: new Date(),
|
deletedAt: new Date(),
|
||||||
text: null,
|
text: null,
|
||||||
tags: [],
|
tags: [],
|
||||||
mediaIds: [],
|
fileIds: [],
|
||||||
poll: null,
|
poll: null,
|
||||||
geo: null
|
geo: null
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user