Compare commits
	
		
			111 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 2448bf4e4e | ||
|   | 91e0fc8c62 | ||
|   | b4f86feddb | ||
|   | ccf8e44acc | ||
|   | 451acb77df | ||
|   | e2c6227f47 | ||
|   | ebd1c877ad | ||
|   | 498094b3c7 | ||
|   | 1cc183ecdb | ||
|   | e8948452fd | ||
|   | ade7e62836 | ||
|   | 395cfa6108 | ||
|   | b5ff2abdb9 | ||
|   | 229e85b2c5 | ||
|   | 37058e3480 | ||
|   | a1b82e9723 | ||
|   | db943df0c8 | ||
|   | ff8d300ea8 | ||
|   | 8b490b9b94 | ||
| ![greenkeeper[bot]](/assets/img/avatar_default.png)  | f83f8631ac | ||
|   | 1915ccabdd | ||
|   | 6fea2f52f1 | ||
|   | f77eaaa08a | ||
|   | 7c5bc03492 | ||
|   | 72a1af6cd4 | ||
| ![greenkeeper[bot]](/assets/img/avatar_default.png)  | 4bce6f14f3 | ||
| ![greenkeeper[bot]](/assets/img/avatar_default.png)  | a38ce86f87 | ||
| ![greenkeeper[bot]](/assets/img/avatar_default.png)  | f539491502 | ||
|   | d279f8e9ff | ||
|   | eaec936fa6 | ||
| ![greenkeeper[bot]](/assets/img/avatar_default.png)  | a0735b0e7a | ||
|   | 5b039a1bee | ||
|   | 921609cab1 | ||
|   | 199573ccee | ||
|   | 977200b7cd | ||
|   | 6abff253ea | ||
|   | ba64de334a | ||
|   | dc1d7fa9d7 | ||
|   | f42665d4bc | ||
|   | a5eb19c878 | ||
|   | 60fa8e13d6 | ||
|   | ecbaea463b | ||
|   | 814ddeb436 | ||
|   | d6466106e8 | ||
|   | 633f5384f9 | ||
|   | fa7989772c | ||
|   | 0e395612a6 | ||
|   | fb3f52f3ad | ||
|   | ba11c71d65 | ||
|   | bdc3081167 | ||
|   | 430efcf1b9 | ||
|   | 996450dd7c | ||
|   | fa779f0417 | ||
|   | 25cec6d28a | ||
|   | c5f8403cea | ||
|   | a9ae9a65c8 | ||
|   | 3698c679e2 | ||
|   | 881df20f1b | ||
|   | 7d269c0441 | ||
|   | ba38f64353 | ||
|   | db3ae303cb | ||
|   | 66f3a155e6 | ||
|   | 639b483e6c | ||
|   | 09843a409b | ||
|   | e894ed5a8b | ||
|   | d7808299fd | ||
|   | f92e0c16d2 | ||
|   | d94b3757be | ||
|   | 13e822cba6 | ||
|   | c57bf87f52 | ||
|   | 99fbd60265 | ||
|   | ea9b48db3c | ||
|   | c145c994a9 | ||
|   | d033998b56 | ||
|   | 3136c714bf | ||
|   | c0ee134f19 | ||
|   | d15ebe5732 | ||
|   | ef630195fa | ||
|   | e31921151e | ||
|   | f94992abbe | ||
|   | b00060c09c | ||
|   | f6217d96d2 | ||
|   | 3a6947c7ed | ||
|   | 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 | 
| @@ -45,7 +45,7 @@ Please see [Contribution guide](./CONTRIBUTING.md). | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D" alt="negao"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1?token-time=2145916800&token-hash=d6P5MWHHsCMxUuBAEPAoVc5wLUR19mIhqAq7Ma9h9rI%3D" alt="ne_moni"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/1?token-time=2145916800&token-hash=f03BFb4S2FUx9YEt87TnEmifb4h33OywGBW2akQVtQY%3D" alt="Melilot"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/1?token-time=2145916800&token-hash=DVrSdOqQq2dufgNgWZ3XMnEtl_ZAktr8Lhf2tbHKtoA%3D" alt="Axella"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/2?token-time=2145916800&token-hash=rwZ8qvbm_kpA4ib3kc07tVKupXeySpY5ATQFGxfL9v0%3D" alt="Axella"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td> | ||||
| <td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td> | ||||
| @@ -65,18 +65,16 @@ Please see [Contribution guide](./CONTRIBUTING.md). | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12931605/ead494101f364dffa90efe49e36fb494/1?token-time=2145916800&token-hash=NzSFPjIlodXyv41rwK61aZWVZWfI4surJaNj8vWKvqM%3D" alt="Reiju"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=UERBN4OyP7Nh5XwwdDg0N0IE5cD6_qUQMO81Z5Wizso%3D" alt="Hiratake"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D" alt="dansup"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4950409/28e7d016209243759d9316be2e21381d/2?token-time=2145916800&token-hash=LuEaDkchH3GQWUcTOhBQ8xfKQYF0s5FjlZRd7Yduia8%3D" alt="mikan54951"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D" alt="Takashi Shibuya"></td> | ||||
| </tr><tr> | ||||
| <td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=12931605">Reiju</a></td> | ||||
| <td><a href="https://www.patreon.com/hiratake">Hiratake</a></td> | ||||
| <td><a href="https://www.patreon.com/dansup">dansup</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=4950409">mikan54951</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td> | ||||
| </tr></table> | ||||
|  | ||||
| **Last updated:** Sun, 02 Sep 2018 00:05:05 UTC | ||||
| **Last updated:** Sun, 02 Sep 2018 05:30:06 UTC | ||||
| <!-- PATREON_END --> | ||||
|  | ||||
| :four_leaf_clover: Copyright | ||||
|   | ||||
| @@ -54,7 +54,7 @@ Please visit https://www.google.com/recaptcha/intro/ and generate keys. | ||||
|  | ||||
| *(optional)* Generating VAPID keys | ||||
| ---------------------------------------------------------------- | ||||
| If you want to enable ServiceWroker, you need to generate VAPID keys: | ||||
| If you want to enable ServiceWorker, you need to generate VAPID keys: | ||||
| Unless you have set your global node_modules location elsewhere, you need to run this in root. | ||||
|  | ||||
| ``` shell | ||||
|   | ||||
| @@ -87,6 +87,7 @@ common: | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" | ||||
|   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' | ||||
|   reversi: | ||||
|     drawn: "引き分け" | ||||
|     my-turn: "あなたのターンです" | ||||
| @@ -260,6 +261,8 @@ common/views/components/nav.vue: | ||||
|   develop: "開発者" | ||||
|   feedback: "フィードバック" | ||||
| common/views/components/note-menu.vue: | ||||
|   detail: "詳細" | ||||
|   copy-link: "リンクをコピー" | ||||
|   favorite: "お気に入り" | ||||
|   pin: "ピン留め" | ||||
|   delete: "削除" | ||||
| @@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue: | ||||
|   specified: "ダイレクト" | ||||
|   specified-desc: "指定したユーザーにのみ公開" | ||||
|   private: "非公開" | ||||
| common/views/components/trends.vue: | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "確認中" | ||||
|   no-broadcasts: "お知らせはありません" | ||||
| @@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue: | ||||
|   toggle: "表示を切り替え" | ||||
| common/views/widgets/hashtags.vue: | ||||
|   title: "ハッシュタグ" | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/server.vue: | ||||
|   title: "サーバー情報" | ||||
|   toggle: "表示を切り替え" | ||||
|   | ||||
| @@ -87,6 +87,7 @@ common: | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" | ||||
|   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' | ||||
|   reversi: | ||||
|     drawn: "引き分け" | ||||
|     my-turn: "あなたのターンです" | ||||
| @@ -260,6 +261,8 @@ common/views/components/nav.vue: | ||||
|   develop: "Entwickler" | ||||
|   feedback: "Feedback" | ||||
| common/views/components/note-menu.vue: | ||||
|   detail: "詳細" | ||||
|   copy-link: "リンクをコピー" | ||||
|   favorite: "Diese Anmerkung favorisieren" | ||||
|   pin: "An die Profilseite pinnen" | ||||
|   delete: "Löschen" | ||||
| @@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue: | ||||
|   specified: "Direkt" | ||||
|   specified-desc: "Poste nur für bestimmte Benutzer" | ||||
|   private: "Privat" | ||||
| common/views/components/trends.vue: | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "Laden" | ||||
|   no-broadcasts: "Keine Broadcasts" | ||||
| @@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue: | ||||
|   toggle: "表示を切り替え" | ||||
| common/views/widgets/hashtags.vue: | ||||
|   title: "ハッシュタグ" | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/server.vue: | ||||
|   title: "Serverinformationen" | ||||
|   toggle: "Sicht umschalten" | ||||
|   | ||||
| @@ -84,9 +84,10 @@ common: | ||||
|   my-token-regenerated: "Your token has been regenerated, so you will be signed out." | ||||
|   i-like-sushi: "I prefer sushi rather than pudding" | ||||
|   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" | ||||
|   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: | ||||
|     drawn: "Draw" | ||||
|     my-turn: "Your turn" | ||||
| @@ -260,6 +261,8 @@ common/views/components/nav.vue: | ||||
|   develop: "Developers" | ||||
|   feedback: "Feedback" | ||||
| common/views/components/note-menu.vue: | ||||
|   detail: "Details" | ||||
|   copy-link: "Copy link" | ||||
|   favorite: "Favorite this note" | ||||
|   pin: "Pin to your profile" | ||||
|   delete: "Delete" | ||||
| @@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue: | ||||
|   specified: "Direct" | ||||
|   specified-desc: "Post to specified users only" | ||||
|   private: "Private" | ||||
| common/views/components/trends.vue: | ||||
|   count: "{} users mentioned" | ||||
|   empty: "No popular hashtag trends" | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "Fetching" | ||||
|   no-broadcasts: "No announcements" | ||||
| @@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue: | ||||
|   toggle: "Toggle views" | ||||
| common/views/widgets/hashtags.vue: | ||||
|   title: "Hashtags" | ||||
|   count: "{} users mentioned" | ||||
|   empty: "No popular hashtag trends" | ||||
| common/views/widgets/server.vue: | ||||
|   title: "Server info" | ||||
|   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." | ||||
|   application-authorization: "Autorizaciones de la aplicación." | ||||
|   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!" | ||||
|   customization-tips: | ||||
|     title: "Consejos de personalización" | ||||
| @@ -58,7 +58,7 @@ common: | ||||
|     friday: "Viernes" | ||||
|     saturday: "Sábado" | ||||
|   reactions: | ||||
|     like: "いいね" | ||||
|     like: "Me gusta" | ||||
|     love: "amor" | ||||
|     laugh: "risa" | ||||
|     hmm: "hmm" | ||||
| @@ -84,9 +84,10 @@ common: | ||||
|   my-token-regenerated: "Tu token se ha regenerado vas a ser desconectado." | ||||
|   i-like-sushi: "Prefiero sushi a pudín" | ||||
|   show-reversi-board-labels: "Mostrar etiquetas de filas y columnas en Reversi" | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   use-contrast-reversi-stones: "Hacer el color de la piedra claro en Reversi" | ||||
|   verified-user: "Cuenta verificada" | ||||
|   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: | ||||
|     drawn: "Empatado" | ||||
|     my-turn: "Mi turno" | ||||
| @@ -170,9 +171,9 @@ common/views/components/games/reversi/reversi.vue: | ||||
| common/views/components/games/reversi/reversi.game.vue: | ||||
|   surrender: "Rendirse" | ||||
|   surrendered: "Por rendirse" | ||||
|   is-llotheo: "石の少ない方が勝ち(ロセオ)" | ||||
|   looped-map: "ループマップ" | ||||
|   can-put-everywhere: "どこでも置けるモード" | ||||
|   is-llotheo: "El último gana (Llotheo)" | ||||
|   looped-map: "Mapa en bucle" | ||||
|   can-put-everywhere: "Puedes colocar donde quieras" | ||||
| common/views/components/games/reversi/reversi.index.vue: | ||||
|   title: "Misskey Reversi" | ||||
|   sub-title: "¡Juega Reversi con tus amigos!" | ||||
| @@ -260,6 +261,8 @@ common/views/components/nav.vue: | ||||
|   develop: "Desarrolladores" | ||||
|   feedback: "Opiniones" | ||||
| common/views/components/note-menu.vue: | ||||
|   detail: "Detalles" | ||||
|   copy-link: "Copiar enlace" | ||||
|   favorite: "Me gusta esta nota" | ||||
|   pin: "Fijar en el perfil" | ||||
|   delete: "Borrar" | ||||
| @@ -288,10 +291,10 @@ common/views/components/signin.vue: | ||||
|   signin: "Entra" | ||||
|   or: "O" | ||||
|   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: | ||||
|   invitation-code: "招待コード" | ||||
|   invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。" | ||||
|   invitation-code: "Código de invitación" | ||||
|   invitation-info: "Si no tienes un código de invitación, por favor contacta un <a href=\"{}\">administrador</a>." | ||||
|   username: "Usuario" | ||||
|   checking: "Comprobando..." | ||||
|   available: "Disponible" | ||||
| @@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue: | ||||
|   specified: "Directo" | ||||
|   specified-desc: "Publica solo para los seguidores que quieras" | ||||
|   private: "Privada" | ||||
| common/views/components/trends.vue: | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "Recuperando" | ||||
|   no-broadcasts: "Sin emisión" | ||||
| @@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue: | ||||
|   toggle: "Alternar vistas" | ||||
| common/views/widgets/hashtags.vue: | ||||
|   title: "Etiquetas" | ||||
|   count: "{} usuarios mencionados" | ||||
|   empty: "Ninguna tendencia popular ahora" | ||||
| common/views/widgets/server.vue: | ||||
|   title: "Información del servidor" | ||||
|   toggle: "Alternar vistas" | ||||
| @@ -411,7 +415,7 @@ desktop: | ||||
|   uploading-avatar: "Cargando un nuevo avatar" | ||||
|   avatar-updated: "Avatar actualizado" | ||||
|   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: | ||||
|   total: "Negro ... Total" | ||||
|   notes: "Azul ... Notas" | ||||
| @@ -426,23 +430,23 @@ desktop/views/components/calendar.vue: | ||||
|   next: "Próximo mes" | ||||
|   go: "Click para navegar" | ||||
| desktop/views/components/charts.vue: | ||||
|   title: "チャート" | ||||
|   per-day: "1日ごと" | ||||
|   per-hour: "1時間ごと" | ||||
|   notes: "投稿" | ||||
|   users: "ユーザー" | ||||
|   drive: "ドライブ" | ||||
|   title: "Gráficos" | ||||
|   per-day: "por día" | ||||
|   per-hour: "por hora" | ||||
|   notes: "Publicaciones" | ||||
|   users: "Usuarios" | ||||
|   drive: "Unidad" | ||||
|   charts: | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "投稿の累計" | ||||
|     users: "ユーザーの増減" | ||||
|     users-total: "ユーザーの累計" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の累計" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の累計" | ||||
|     notes: "Número de publicaciones: aumentar/disminuir (Combinado)" | ||||
|     local-notes: "Número de publicaciones: aumentar/disminuir (Local)" | ||||
|     remote-notes: "Número de publicaciones: aumentar/disminuir (Remoto)" | ||||
|     notes-total: "Número de publicaciones: Acumulativo total" | ||||
|     users: "Número de usuarios: aumentar/disminuir" | ||||
|     users-total: "Número de usuarios: Acumulativo total" | ||||
|     drive: "Capacidad de almacenamiento usada: aumentar/disminuir" | ||||
|     drive-total: "Capacidad de almacenamiento usada: Acumulativa total" | ||||
|     drive-files: "Número de archivos almacenados: aumentar/disminuir" | ||||
|     drive-files-total: "Número de archivos almacenados: Acumulativo total" | ||||
| desktop/views/components/choose-file-from-drive-window.vue: | ||||
|   choose-file: "Escoger archivos" | ||||
|   upload: "Cargar archivos de tu dispositivo" | ||||
| @@ -463,7 +467,7 @@ desktop/views/components/drive-window.vue: | ||||
| desktop/views/components/drive.file.vue: | ||||
|   avatar: "Avatar" | ||||
|   banner: "Banner" | ||||
|   nsfw: "閲覧注意" | ||||
|   nsfw: "Ver más" | ||||
|   contextmenu: | ||||
|     rename: "Renombrar" | ||||
|     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')" | ||||
|   click-to-show: "Click para mostrar" | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
|   sensitive: "Este contenido no es apropiado para ver en el trabajo" | ||||
|   click-to-show: "Click para mostrar" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "Siguiendo" | ||||
|   follow: "Sigue" | ||||
|   request-pending: "Pendiente de aprobación" | ||||
|   follow-request: "フォロー申請" | ||||
|   follow-request: "Solicitud de seguir" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{} のフォロワー" | ||||
|   followers: "{} seguidores" | ||||
| desktop/views/components/followers.vue: | ||||
|   empty: "フォロワーはいないようです。" | ||||
|   empty: "Parece que no tienes seguidores aún." | ||||
| desktop/views/components/following-window.vue: | ||||
|   following: "{} のフォロー" | ||||
|   following: "Siguiendo {}" | ||||
| desktop/views/components/following.vue: | ||||
|   empty: "フォロー中のユーザーはいないようです。" | ||||
|   empty: "Parece que aún no sigues a nadie." | ||||
| desktop/views/components/friends-maker.vue: | ||||
|   title: "気になるユーザーをフォロー:" | ||||
|   empty: "おすすめのユーザーは見つかりませんでした。" | ||||
|   fetching: "読み込んでいます" | ||||
|   refresh: "もっと見る" | ||||
|   close: "閉じる" | ||||
|   title: "Usuarios recomendados:" | ||||
|   empty: "No se pudieron encontrar usuarios para recomendar" | ||||
|   fetching: "Cargando" | ||||
|   refresh: "Más" | ||||
|   close: "Cerrar" | ||||
| desktop/views/components/game-window.vue: | ||||
|   game: "リバーシ" | ||||
|   game: "Reversi" | ||||
| desktop/views/components/home.vue: | ||||
|   done: "完了" | ||||
|   done: "Listo" | ||||
|   add-widget: "Agregar accesorio:" | ||||
|   add: "Agregar" | ||||
| desktop/views/input-dialog.vue: | ||||
| @@ -565,8 +569,8 @@ desktop/views/components/notes.note.vue: | ||||
|   detail: "Mostrar detalles" | ||||
|   private: "Esta publicación es privada" | ||||
|   deleted: "Esta publicación ha sido borrada" | ||||
|   hide: "隠す" | ||||
|   see-more: "もっと見る" | ||||
|   hide: "Esconder" | ||||
|   see-more: "Ver más" | ||||
| desktop/views/components/notes.vue: | ||||
|   error: "Error al cargar." | ||||
|   retry: "Reintentar" | ||||
| @@ -602,7 +606,7 @@ desktop/views/components/post-form.vue: | ||||
|   geolocation-alert: "Tu dispositivo no tiene soporte de geolocalización." | ||||
|   error: "Error" | ||||
|   enter-username: "Por favor escribe un nombre de usuario..." | ||||
|   annotations: "内容への注釈 (オプション)" | ||||
|   annotations: "Anotaciones a la publicación (opcional)" | ||||
| desktop/views/components/post-form-window.vue: | ||||
|   note: "Nota nueva" | ||||
|   reply: "Responder" | ||||
| @@ -766,40 +770,40 @@ desktop/views/components/timeline.vue: | ||||
|   global: "グローバル" | ||||
|   list: "リスト" | ||||
| desktop/views/components/ui.header.vue: | ||||
|   welcome-back: "おかえりなさい、" | ||||
|   adjective: "さん" | ||||
|   welcome-back: "Bienvenido/a de vuelta," | ||||
|   adjective: "-san" | ||||
| desktop/views/components/ui.header.account.vue: | ||||
|   profile: "プロフィール" | ||||
|   drive: "ドライブ" | ||||
|   favorites: "お気に入り" | ||||
|   lists: "リスト" | ||||
|   follow-requests: "フォロー申請" | ||||
|   customize: "ホームのカスタマイズ" | ||||
|   admin: "管理" | ||||
|   settings: "設定" | ||||
|   signout: "サインアウト" | ||||
|   dark: "闇に飲まれる" | ||||
|   profile: "Tu perfil" | ||||
|   drive: "Unidad" | ||||
|   favorites: "Favoritos" | ||||
|   lists: "Listas" | ||||
|   follow-requests: "Solicitudes de seguimiento" | ||||
|   customize: "Personalizar la página de inicio" | ||||
|   admin: "Admin" | ||||
|   settings: "Configuraciones" | ||||
|   signout: "Desconectarse" | ||||
|   dark: "Sumergirse en la oscuridad" | ||||
| desktop/views/components/ui.header.nav.vue: | ||||
|   home: "ホーム" | ||||
|   deck: "デッキ" | ||||
|   messaging: "メッセージ" | ||||
|   game: "ゲーム" | ||||
|   home: "Inicio" | ||||
|   deck: "Cubierta" | ||||
|   messaging: "Mensajes" | ||||
|   game: "Juegos" | ||||
| desktop/views/components/ui.header.notifications.vue: | ||||
|   title: "通知" | ||||
|   title: "Notificaciones" | ||||
| desktop/views/components/ui.header.post.vue: | ||||
|   post: "新規投稿" | ||||
|   post: "Crear una publicación" | ||||
| desktop/views/components/ui.header.search.vue: | ||||
|   placeholder: "検索" | ||||
|   placeholder: "Buscar" | ||||
| desktop/views/components/received-follow-requests-window.vue: | ||||
|   title: "フォロー申請" | ||||
|   accept: "承認" | ||||
|   reject: "拒否" | ||||
|   title: "Solicitudes de seguidores" | ||||
|   accept: "Aceptar" | ||||
|   reject: "Rechazar" | ||||
| desktop/views/components/user-lists-window.vue: | ||||
|   title: "リスト" | ||||
|   create-list: "リストを作成" | ||||
|   list-name: "リスト名" | ||||
|   title: "Listas de usuario" | ||||
|   create-list: "Crear lista" | ||||
|   list-name: "Nombre de lista" | ||||
| desktop/views/components/user-preview.vue: | ||||
|   notes: "投稿" | ||||
|   notes: "Publicaciones" | ||||
|   following: "フォロー" | ||||
|   followers: "フォロワー" | ||||
| desktop/views/components/users-list.vue: | ||||
|   | ||||
| @@ -87,6 +87,7 @@ common: | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "Compte vérifié" | ||||
|   disable-animated-mfm: "Désactiver les textes animés dans les publications" | ||||
|   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' | ||||
|   reversi: | ||||
|     drawn: "Partie nulle" | ||||
|     my-turn: "C’est votre tour" | ||||
| @@ -260,6 +261,8 @@ common/views/components/nav.vue: | ||||
|   develop: "Développeur·se·s" | ||||
|   feedback: "Remarques" | ||||
| common/views/components/note-menu.vue: | ||||
|   detail: "詳細" | ||||
|   copy-link: "リンクをコピー" | ||||
|   favorite: "Mettre cette note en favoris" | ||||
|   pin: "Épingler sur votre profil" | ||||
|   delete: "Supprimer" | ||||
| @@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue: | ||||
|   specified: "Direct" | ||||
|   specified-desc: "Publier aux utilisateur·rice·s mentionné·e·s" | ||||
|   private: "Privé" | ||||
| common/views/components/trends.vue: | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "Récupération" | ||||
|   no-broadcasts: "Aucune annonce" | ||||
| @@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue: | ||||
|   toggle: "Basculer entre les vues" | ||||
| common/views/widgets/hashtags.vue: | ||||
|   title: "Étiquettes" | ||||
|   count: "{} utilisateur·rice·s mentionné·e·s" | ||||
|   empty: "Aucune tendance" | ||||
| common/views/widgets/server.vue: | ||||
|   title: "Informations sur le serveur" | ||||
|   toggle: "Afficher les vues" | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| const fs = require('fs'); | ||||
| 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 locales = langs.map(lang => ({ [lang]: loadLocale(lang) })); | ||||
|   | ||||
| @@ -87,6 +87,7 @@ common: | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" | ||||
|   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' | ||||
|   reversi: | ||||
|     drawn: "引き分け" | ||||
|     my-turn: "あなたのターンです" | ||||
| @@ -260,6 +261,8 @@ common/views/components/nav.vue: | ||||
|   develop: "開発者" | ||||
|   feedback: "フィードバック" | ||||
| common/views/components/note-menu.vue: | ||||
|   detail: "詳細" | ||||
|   copy-link: "リンクをコピー" | ||||
|   favorite: "お気に入り" | ||||
|   pin: "ピン留め" | ||||
|   delete: "削除" | ||||
| @@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue: | ||||
|   specified: "ダイレクト" | ||||
|   specified-desc: "指定したユーザーにのみ公開" | ||||
|   private: "非公開" | ||||
| common/views/components/trends.vue: | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "確認中" | ||||
|   no-broadcasts: "お知らせはありません" | ||||
| @@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue: | ||||
|   toggle: "表示を切り替え" | ||||
| common/views/widgets/hashtags.vue: | ||||
|   title: "ハッシュタグ" | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/server.vue: | ||||
|   title: "サーバー情報" | ||||
|   toggle: "表示を切り替え" | ||||
|   | ||||
| @@ -375,6 +375,10 @@ common/views/components/visibility-chooser.vue: | ||||
|   specified-desc: "指定したユーザーにのみ公開" | ||||
|   private: "非公開" | ||||
|  | ||||
| common/views/components/trends.vue: | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
|  | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "確認中" | ||||
|   no-broadcasts: "お知らせはありません" | ||||
| @@ -403,8 +407,6 @@ common/views/widgets/posts-monitor.vue: | ||||
|  | ||||
| common/views/widgets/hashtags.vue: | ||||
|   title: "ハッシュタグ" | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
|  | ||||
| common/views/widgets/server.vue: | ||||
|   title: "サーバー情報" | ||||
| @@ -988,6 +990,8 @@ desktop/views/pages/welcome.vue: | ||||
|   signin-button: "やってる" | ||||
|   signup-button: "やる" | ||||
|   timeline: "タイムライン" | ||||
|   announcements: "お知らせ" | ||||
|   photos: "最近の画像" | ||||
|   powered-by-misskey: "Powered by <b>Misskey</b>." | ||||
|  | ||||
| desktop/views/pages/drive.vue: | ||||
| @@ -1353,6 +1357,9 @@ mobile/views/pages/settings.vue: | ||||
|   post-style: "投稿の表示スタイル" | ||||
|   post-style-standard: "標準" | ||||
|   post-style-smart: "スマート" | ||||
|   notification-position: "通知の表示" | ||||
|   notification-position-bottom: "下" | ||||
|   notification-position-top: "上" | ||||
|   behavior: "動作" | ||||
|   fetch-on-scroll: "スクロールで自動読み込み" | ||||
|   disable-via-mobile: "「モバイルからの投稿」フラグを付けない" | ||||
|   | ||||
| @@ -87,6 +87,7 @@ common: | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストをつけんで!" | ||||
|   verified-user: "アメちゃん付きアカウント" | ||||
|   disable-animated-mfm: "投稿内のちょろちょろ動いてんのを止める" | ||||
|   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' | ||||
|   reversi: | ||||
|     drawn: "おあいこ" | ||||
|     my-turn: "あんさんのターンや" | ||||
| @@ -260,6 +261,8 @@ common/views/components/nav.vue: | ||||
|   develop: "開発者" | ||||
|   feedback: "フィードバック" | ||||
| common/views/components/note-menu.vue: | ||||
|   detail: "詳細" | ||||
|   copy-link: "リンクをコピー" | ||||
|   favorite: "お気に入り" | ||||
|   pin: "ピン留め" | ||||
|   delete: "ほかす" | ||||
| @@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue: | ||||
|   specified: "ダイレクト" | ||||
|   specified-desc: "指定したユーザーにのみ公開" | ||||
|   private: "非公開" | ||||
| common/views/components/trends.vue: | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "確認中" | ||||
|   no-broadcasts: "お知らせはあらへんで" | ||||
| @@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue: | ||||
|   toggle: "表示を切り替え" | ||||
| common/views/widgets/hashtags.vue: | ||||
|   title: "ハッシュタグ" | ||||
|   count: "{}人が投稿" | ||||
|   empty: "流行は自分で作るんや" | ||||
| common/views/widgets/server.vue: | ||||
|   title: "サーバー情報" | ||||
|   toggle: "表示を切り替え" | ||||
|   | ||||
| @@ -87,6 +87,7 @@ common: | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "게시물의 문자 애니메이션을 비활성화 할" | ||||
|   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' | ||||
|   reversi: | ||||
|     drawn: "무승부" | ||||
|     my-turn: "당신의 차례입니다" | ||||
| @@ -260,6 +261,8 @@ common/views/components/nav.vue: | ||||
|   develop: "開発者" | ||||
|   feedback: "フィードバック" | ||||
| common/views/components/note-menu.vue: | ||||
|   detail: "詳細" | ||||
|   copy-link: "リンクをコピー" | ||||
|   favorite: "お気に入り" | ||||
|   pin: "ピン留め" | ||||
|   delete: "削除" | ||||
| @@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue: | ||||
|   specified: "ダイレクト" | ||||
|   specified-desc: "指定したユーザーにのみ公開" | ||||
|   private: "非公開" | ||||
| common/views/components/trends.vue: | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "確認中" | ||||
|   no-broadcasts: "お知らせはありません" | ||||
| @@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue: | ||||
|   toggle: "表示を切り替え" | ||||
| common/views/widgets/hashtags.vue: | ||||
|   title: "ハッシュタグ" | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/server.vue: | ||||
|   title: "サーバー情報" | ||||
|   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: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "Wyłącz animowany tekst we wpisach" | ||||
|   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' | ||||
|   reversi: | ||||
|     drawn: "Remis" | ||||
|     my-turn: "Twoja kolej" | ||||
| @@ -260,6 +261,8 @@ common/views/components/nav.vue: | ||||
|   develop: "Autorzy" | ||||
|   feedback: "Podziel się opinią" | ||||
| common/views/components/note-menu.vue: | ||||
|   detail: "詳細" | ||||
|   copy-link: "リンクをコピー" | ||||
|   favorite: "Dodaj do ulubionych" | ||||
|   pin: "Przypnij do profilu" | ||||
|   delete: "Usuń" | ||||
| @@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue: | ||||
|   specified: "Bezpośredni" | ||||
|   specified-desc: "Tylko dla określonych użytkowników" | ||||
|   private: "Prywatny" | ||||
| common/views/components/trends.vue: | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "Sprawdzanie" | ||||
|   no-broadcasts: "Brak transmisji" | ||||
| @@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue: | ||||
|   toggle: "Przełącz widok" | ||||
| common/views/widgets/hashtags.vue: | ||||
|   title: "Hashtagi" | ||||
|   count: "Wspomniany przez {} użytkowników" | ||||
|   empty: "Brak popularnych hashtagów" | ||||
| common/views/widgets/server.vue: | ||||
|   title: "Informacje o serwerze" | ||||
|   toggle: "Przełącz widok" | ||||
|   | ||||
| @@ -87,6 +87,7 @@ common: | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "Conta verificada" | ||||
|   disable-animated-mfm: "Desativar texto animado nas publicações" | ||||
|   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' | ||||
|   reversi: | ||||
|     drawn: "Empatado" | ||||
|     my-turn: "Seu turno" | ||||
| @@ -260,6 +261,8 @@ common/views/components/nav.vue: | ||||
|   develop: "開発者" | ||||
|   feedback: "フィードバック" | ||||
| common/views/components/note-menu.vue: | ||||
|   detail: "詳細" | ||||
|   copy-link: "リンクをコピー" | ||||
|   favorite: "お気に入り" | ||||
|   pin: "ピン留め" | ||||
|   delete: "削除" | ||||
| @@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue: | ||||
|   specified: "ダイレクト" | ||||
|   specified-desc: "指定したユーザーにのみ公開" | ||||
|   private: "非公開" | ||||
| common/views/components/trends.vue: | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "確認中" | ||||
|   no-broadcasts: "お知らせはありません" | ||||
| @@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue: | ||||
|   toggle: "表示を切り替え" | ||||
| common/views/widgets/hashtags.vue: | ||||
|   title: "ハッシュタグ" | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/server.vue: | ||||
|   title: "サーバー情報" | ||||
|   toggle: "表示を切り替え" | ||||
|   | ||||
| @@ -87,6 +87,7 @@ common: | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" | ||||
|   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' | ||||
|   reversi: | ||||
|     drawn: "引き分け" | ||||
|     my-turn: "あなたのターンです" | ||||
| @@ -260,6 +261,8 @@ common/views/components/nav.vue: | ||||
|   develop: "開発者" | ||||
|   feedback: "フィードバック" | ||||
| common/views/components/note-menu.vue: | ||||
|   detail: "詳細" | ||||
|   copy-link: "リンクをコピー" | ||||
|   favorite: "お気に入り" | ||||
|   pin: "ピン留め" | ||||
|   delete: "削除" | ||||
| @@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue: | ||||
|   specified: "ダイレクト" | ||||
|   specified-desc: "指定したユーザーにのみ公開" | ||||
|   private: "非公開" | ||||
| common/views/components/trends.vue: | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "確認中" | ||||
|   no-broadcasts: "お知らせはありません" | ||||
| @@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue: | ||||
|   toggle: "表示を切り替え" | ||||
| common/views/widgets/hashtags.vue: | ||||
|   title: "ハッシュタグ" | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/server.vue: | ||||
|   title: "サーバー情報" | ||||
|   toggle: "表示を切り替え" | ||||
|   | ||||
| @@ -87,6 +87,7 @@ common: | ||||
|   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける" | ||||
|   verified-user: "公式アカウント" | ||||
|   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする" | ||||
|   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。' | ||||
|   reversi: | ||||
|     drawn: "引き分け" | ||||
|     my-turn: "あなたのターンです" | ||||
| @@ -260,6 +261,8 @@ common/views/components/nav.vue: | ||||
|   develop: "開発者" | ||||
|   feedback: "フィードバック" | ||||
| common/views/components/note-menu.vue: | ||||
|   detail: "詳細" | ||||
|   copy-link: "リンクをコピー" | ||||
|   favorite: "お気に入り" | ||||
|   pin: "ピン留め" | ||||
|   delete: "削除" | ||||
| @@ -337,6 +340,9 @@ common/views/components/visibility-chooser.vue: | ||||
|   specified: "ダイレクト" | ||||
|   specified-desc: "指定したユーザーにのみ公開" | ||||
|   private: "非公開" | ||||
| common/views/components/trends.vue: | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/broadcast.vue: | ||||
|   fetching: "確認中" | ||||
|   no-broadcasts: "お知らせはありません" | ||||
| @@ -360,8 +366,6 @@ common/views/widgets/posts-monitor.vue: | ||||
|   toggle: "表示を切り替え" | ||||
| common/views/widgets/hashtags.vue: | ||||
|   title: "ハッシュタグ" | ||||
|   count: "{}人が投稿" | ||||
|   empty: "トレンドなし" | ||||
| common/views/widgets/server.vue: | ||||
|   title: "サーバー情報" | ||||
|   toggle: "表示を切り替え" | ||||
|   | ||||
							
								
								
									
										12
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,8 +1,8 @@ | ||||
| { | ||||
| 	"name": "misskey", | ||||
| 	"author": "syuilo <i@syuilo.com>", | ||||
| 	"version": "8.21.1", | ||||
| 	"clientVersion": "1.0.9264", | ||||
| 	"version": "8.27.0", | ||||
| 	"clientVersion": "1.0.9378", | ||||
| 	"codename": "nighthike", | ||||
| 	"main": "./built/index.js", | ||||
| 	"private": true, | ||||
| @@ -161,7 +161,7 @@ | ||||
| 		"nan": "2.11.0", | ||||
| 		"nested-property": "0.0.7", | ||||
| 		"node-sass": "4.9.3", | ||||
| 		"node-sass-json-importer": "3.3.1", | ||||
| 		"node-sass-json-importer": "4.0.0", | ||||
| 		"nprogress": "0.2.0", | ||||
| 		"object-assign-deep": "0.4.0", | ||||
| 		"on-build-webpack": "0.1.0", | ||||
| @@ -194,7 +194,7 @@ | ||||
| 		"stylus": "0.54.5", | ||||
| 		"stylus-loader": "3.0.2", | ||||
| 		"summaly": "2.2.0", | ||||
| 		"systeminformation": "3.44.2", | ||||
| 		"systeminformation": "3.45.1", | ||||
| 		"syuilo-password-strength": "0.0.1", | ||||
| 		"textarea-caret": "3.1.0", | ||||
| 		"tmp": "0.0.33", | ||||
| @@ -210,7 +210,7 @@ | ||||
| 		"vue": "2.5.17", | ||||
| 		"vue-chartjs": "3.4.0", | ||||
| 		"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-loader": "15.4.1", | ||||
| 		"vue-router": "3.0.1", | ||||
| @@ -221,7 +221,7 @@ | ||||
| 		"vuex-persistedstate": "2.5.4", | ||||
| 		"web-push": "3.3.2", | ||||
| 		"webfinger.js": "2.6.6", | ||||
| 		"webpack": "4.17.1", | ||||
| 		"webpack": "4.17.2", | ||||
| 		"webpack-cli": "3.1.0", | ||||
| 		"websocket": "1.0.26", | ||||
| 		"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; | ||||
| } | ||||
| @@ -3,8 +3,10 @@ import MiOS from '../../../../../mios'; | ||||
|  | ||||
| export class ReversiGameStream extends Stream { | ||||
| 	constructor(os: MiOS, me, game) { | ||||
| 		super(os, 'games/reversi-game', { | ||||
| 			i: me ? me.token : null, | ||||
| 		super(os, 'games/reversi-game', me ? { | ||||
| 			i: me.token, | ||||
| 			game: game.id | ||||
| 		} : { | ||||
| 			game: game.id | ||||
| 		}); | ||||
| 	} | ||||
|   | ||||
| @@ -7,9 +7,9 @@ import MiOS from '../../../mios'; | ||||
|  */ | ||||
| export class LocalTimelineStream extends Stream { | ||||
| 	constructor(os: MiOS, me) { | ||||
| 		super(os, 'local-timeline', { | ||||
| 		super(os, 'local-timeline', me ? { | ||||
| 			i: me.token | ||||
| 		}); | ||||
| 		} : {}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,14 +1,20 @@ | ||||
| <template> | ||||
| <span class="mk-acct"> | ||||
| 	<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> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import { host } from '../../../config'; | ||||
| export default Vue.extend({ | ||||
| 	props: ['user'] | ||||
| 	props: ['user', 'detail'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			host | ||||
| 		}; | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| import trends from './trends.vue'; | ||||
| import analogClock from './analog-clock.vue'; | ||||
| import menu from './menu.vue'; | ||||
| import noteHeader from './note-header.vue'; | ||||
| @@ -40,6 +41,7 @@ import uiSelect from './ui/select.vue'; | ||||
| import formButton from './ui/form/button.vue'; | ||||
| import formRadio from './ui/form/radio.vue'; | ||||
|  | ||||
| Vue.component('mk-trends', trends); | ||||
| Vue.component('mk-analog-clock', analogClock); | ||||
| Vue.component('mk-menu', menu); | ||||
| Vue.component('mk-note-header', noteHeader); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="mk-menu"> | ||||
| <div class="onchrpzrvnoruiaenfcqvccjfuupzzwv"> | ||||
| 	<div class="backdrop" ref="backdrop" @click="close"></div> | ||||
| 	<div class="popover" :class="{ hukidasi }" ref="popover"> | ||||
| 		<template v-for="item in items"> | ||||
| @@ -119,9 +119,10 @@ export default Vue.extend({ | ||||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
|  | ||||
| $border-color = rgba(27, 31, 35, 0.15) | ||||
| root(isDark) | ||||
| 	$bg-color = isDark ? #2c303c : #fff | ||||
| 	$border-color = rgba(27, 31, 35, 0.15) | ||||
|  | ||||
| .mk-menu | ||||
| 	position initial | ||||
|  | ||||
| 	> .backdrop | ||||
| @@ -131,14 +132,14 @@ $border-color = rgba(27, 31, 35, 0.15) | ||||
| 		z-index 10000 | ||||
| 		width 100% | ||||
| 		height 100% | ||||
| 		background rgba(#000, 0.1) | ||||
| 		background rgba(#000, isDark ? 0.5 : 0.1) | ||||
| 		opacity 0 | ||||
|  | ||||
| 	> .popover | ||||
| 		position absolute | ||||
| 		z-index 10001 | ||||
| 		padding 8px 0 | ||||
| 		background #fff | ||||
| 		background $bg-color | ||||
| 		border 1px solid $border-color | ||||
| 		border-radius 4px | ||||
| 		box-shadow 0 3px 12px rgba(27, 31, 35, 0.15) | ||||
| @@ -172,12 +173,13 @@ $border-color = rgba(27, 31, 35, 0.15) | ||||
| 				border-top solid $balloon-size transparent | ||||
| 				border-left solid $balloon-size transparent | ||||
| 				border-right solid $balloon-size transparent | ||||
| 				border-bottom solid $balloon-size #fff | ||||
| 				border-bottom solid $balloon-size $bg-color | ||||
|  | ||||
| 		> button | ||||
| 			display block | ||||
| 			padding 8px 16px | ||||
| 			width 100% | ||||
| 			color isDark ? #d6dce2 : #111 | ||||
|  | ||||
| 			&:hover | ||||
| 				color $theme-color-foreground | ||||
| @@ -191,6 +193,12 @@ $border-color = rgba(27, 31, 35, 0.15) | ||||
| 		> div | ||||
| 			margin 8px 0 | ||||
| 			height 1px | ||||
| 			background #eee | ||||
| 			background isDark ? #1c2023 : #eee | ||||
|  | ||||
| .onchrpzrvnoruiaenfcqvccjfuupzzwv[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
| .onchrpzrvnoruiaenfcqvccjfuupzzwv:not([data-darkmode]) | ||||
| 	root(false) | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -78,7 +78,7 @@ export default Vue.extend({ | ||||
| 			cursor wait !important | ||||
|  | ||||
| 	> .avatar | ||||
| 		margin 16px auto 0 auto | ||||
| 		margin 0 auto 0 auto | ||||
| 		width 64px | ||||
| 		height 64px | ||||
| 		background #ddd | ||||
|   | ||||
							
								
								
									
										105
									
								
								src/client/app/common/views/components/trends.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/client/app/common/views/components/trends.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| <template> | ||||
| <div class="csqvmxybqbycalfhkxvyfrgbrdalkaoc"> | ||||
| 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 	<p class="empty" v-else-if="stats.length == 0">%fa:exclamation-circle%%i18n:@empty%</p> | ||||
| 	<!-- トランジションを有効にするとなぜかメモリリークする --> | ||||
| 	<!-- <transition-group v-else tag="div" name="chart"> --> | ||||
| 	<div> | ||||
| 		<div v-for="stat in stats" :key="stat.tag"> | ||||
| 			<div class="tag"> | ||||
| 				<router-link :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</router-link> | ||||
| 				<p>{{ '%i18n:@count%'.replace('{}', stat.usersCount) }}</p> | ||||
| 			</div> | ||||
| 			<x-chart class="chart" :src="stat.chart"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<!-- </transition-group> --> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import XChart from './trends.chart.vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XChart | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			stats: [], | ||||
| 			fetching: true, | ||||
| 			clock: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.fetch(); | ||||
| 		this.clock = setInterval(this.fetch, 1000 * 60); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		clearInterval(this.clock); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		fetch() { | ||||
| 			(this as any).api('hashtags/trend').then(stats => { | ||||
| 				this.stats = stats; | ||||
| 				this.fetching = false; | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| root(isDark) | ||||
| 	> .fetching | ||||
| 	> .empty | ||||
| 		margin 0 | ||||
| 		padding 16px | ||||
| 		text-align center | ||||
| 		color #aaa | ||||
|  | ||||
| 		> [data-fa] | ||||
| 			margin-right 4px | ||||
|  | ||||
| 	> div | ||||
| 		.chart-move | ||||
| 			transition transform 1s ease | ||||
|  | ||||
| 		> div | ||||
| 			display flex | ||||
| 			align-items center | ||||
| 			padding 14px 16px | ||||
|  | ||||
| 			&:not(:last-child) | ||||
| 				border-bottom solid 1px isDark ? #393f4f : #eee | ||||
|  | ||||
| 			> .tag | ||||
| 				flex 1 | ||||
| 				overflow hidden | ||||
| 				font-size 14px | ||||
| 				color isDark ? #9baec8 : #65727b | ||||
|  | ||||
| 				> a | ||||
| 					display block | ||||
| 					width 100% | ||||
| 					white-space nowrap | ||||
| 					overflow hidden | ||||
| 					text-overflow ellipsis | ||||
| 					color inherit | ||||
|  | ||||
| 				> p | ||||
| 					margin 0 | ||||
| 					font-size 75% | ||||
| 					opacity 0.7 | ||||
|  | ||||
| 			> .chart | ||||
| 				height 30px | ||||
|  | ||||
| .csqvmxybqbycalfhkxvyfrgbrdalkaoc[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
| .csqvmxybqbycalfhkxvyfrgbrdalkaoc:not([data-darkmode]) | ||||
| 	root(false) | ||||
|  | ||||
| </style> | ||||
| @@ -24,17 +24,32 @@ export default Vue.extend({ | ||||
|  | ||||
| root(isDark) | ||||
| 	margin 16px | ||||
| 	padding 16px | ||||
| 	color isDark ? #fff : #000 | ||||
| 	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) | ||||
|  | ||||
| 	> 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) | ||||
| 			padding 32px | ||||
|  | ||||
| 		&.fit-top | ||||
| 			padding-top 0 | ||||
|  | ||||
| 		> header | ||||
| 		font-weight normal | ||||
| 		font-size 24px | ||||
| 			margin-bottom 16px | ||||
| 			font-weight bold | ||||
| 			color isDark ? #fff : #444 | ||||
|  | ||||
| .ui-card[data-darkmode] | ||||
|   | ||||
| @@ -55,7 +55,7 @@ export default Vue.extend({ | ||||
|  | ||||
| root(isDark) | ||||
| 	display inline-block | ||||
| 	margin 32px 32px 32px 0 | ||||
| 	margin 0 32px 0 0 | ||||
| 	cursor pointer | ||||
| 	transition all 0.3s | ||||
|  | ||||
|   | ||||
| @@ -64,6 +64,12 @@ root(isDark) | ||||
| 	cursor pointer | ||||
| 	transition all 0.3s | ||||
|  | ||||
| 	&:first-child | ||||
| 		margin-top 0 | ||||
|  | ||||
| 	&:last-child | ||||
| 		margin-bottom 0 | ||||
|  | ||||
| 	> * | ||||
| 		user-select none | ||||
|  | ||||
| @@ -89,6 +95,7 @@ root(isDark) | ||||
|  | ||||
| 	> .button | ||||
| 		display inline-block | ||||
| 		flex-shrink 0 | ||||
| 		margin 3px 0 0 0 | ||||
| 		width 34px | ||||
| 		height 14px | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import { toUnicode as decodePunycode } from 'punycode'; | ||||
| export default Vue.extend({ | ||||
| 	props: ['url', 'target'], | ||||
| 	data() { | ||||
| @@ -27,11 +28,11 @@ export default Vue.extend({ | ||||
| 	created() { | ||||
| 		const url = new URL(this.url); | ||||
| 		this.schema = url.protocol; | ||||
| 		this.hostname = url.hostname; | ||||
| 		this.hostname = decodePunycode(url.hostname); | ||||
| 		this.port = url.port; | ||||
| 		this.pathname = url.pathname; | ||||
| 		this.query = url.search; | ||||
| 		this.hash = url.hash; | ||||
| 		this.pathname = decodeURIComponent(url.pathname); | ||||
| 		this.query = decodeURIComponent(url.search); | ||||
| 		this.hash = decodeURIComponent(url.hash); | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -31,15 +31,30 @@ export default Vue.extend({ | ||||
| 			default: undefined | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			fetching: true, | ||||
| 			notes: [] | ||||
| 			notes: [], | ||||
| 			connection: null, | ||||
| 			connectionId: null | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		this.fetch(); | ||||
|  | ||||
| 		this.connection = (this as any).os.streams.localTimelineStream.getConnection(); | ||||
| 		this.connectionId = (this as any).os.streams.localTimelineStream.use(); | ||||
|  | ||||
| 		this.connection.on('note', this.onNote); | ||||
| 	}, | ||||
|  | ||||
| 	beforeDestroy() { | ||||
| 		this.connection.off('note', this.onNote); | ||||
| 		(this as any).os.streams.localTimelineStream.dispose(this.connectionId); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		fetch(cb?) { | ||||
| 			this.fetching = true; | ||||
| @@ -48,14 +63,21 @@ export default Vue.extend({ | ||||
| 				local: true, | ||||
| 				reply: false, | ||||
| 				renote: false, | ||||
| 				media: false, | ||||
| 				poll: false, | ||||
| 				bot: false | ||||
| 				file: false, | ||||
| 				poll: false | ||||
| 			}).then(notes => { | ||||
| 				this.notes = notes; | ||||
| 				this.fetching = false; | ||||
| 			}); | ||||
| 		} | ||||
| 		}, | ||||
|  | ||||
| 		onNote(note) { | ||||
| 			if (note.replyId != null) return; | ||||
| 			if (note.renoteId != null) return; | ||||
| 			if (note.poll != null) return; | ||||
|  | ||||
| 			this.notes.unshift(note); | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -83,7 +83,7 @@ export default Vue.extend({ | ||||
| 						userId: this.user.id | ||||
| 					}); | ||||
| 				} else { | ||||
| 					if (this.user.isLocked && this.user.hasPendingFollowRequestFromYou) { | ||||
| 					if (this.user.hasPendingFollowRequestFromYou) { | ||||
| 						this.user = await (this as any).api('following/requests/cancel', { | ||||
| 							userId: this.user.id | ||||
| 						}); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="mkw-broadcast" | ||||
| <div class="anltbovirfeutcigvwgmgxipejaeozxi" | ||||
| 	:data-found="broadcasts.length != 0" | ||||
| 	:data-melt="props.design == 1" | ||||
| 	:data-mobile="platform == 'mobile'" | ||||
| @@ -25,7 +25,6 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import define from '../../../common/define-widget'; | ||||
| import { lang } from '../../../config'; | ||||
|  | ||||
| export default define({ | ||||
| 	name: 'broadcast', | ||||
| @@ -42,15 +41,7 @@ export default define({ | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		(this as any).os.getMeta().then(meta => { | ||||
| 			let broadcasts = []; | ||||
| 			if (meta.broadcasts) { | ||||
| 				meta.broadcasts.forEach(broadcast => { | ||||
| 					if (broadcast[lang]) { | ||||
| 						broadcasts.push(broadcast[lang]); | ||||
| 					} | ||||
| 				}); | ||||
| 			} | ||||
| 			this.broadcasts = broadcasts; | ||||
| 			this.broadcasts = meta.broadcasts; | ||||
| 			this.fetching = false; | ||||
| 		}); | ||||
| 	}, | ||||
| @@ -75,7 +66,7 @@ export default define({ | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .mkw-broadcast | ||||
| root(isDark) | ||||
| 	padding 10px | ||||
| 	border solid 1px #4078c0 | ||||
| 	border-radius 6px | ||||
| @@ -142,15 +133,11 @@ export default define({ | ||||
| 		z-index 1 | ||||
| 		margin 0 | ||||
| 		font-size 0.7em | ||||
| 		color #555 | ||||
| 		color isDark ? #fff : #555 | ||||
|  | ||||
| 		&.fetching | ||||
| 			text-align center | ||||
|  | ||||
| 		a | ||||
| 			color #555 | ||||
| 			text-decoration underline | ||||
|  | ||||
| 	> a | ||||
| 		display block | ||||
| 		font-size 0.7em | ||||
| @@ -159,4 +146,10 @@ export default define({ | ||||
| 		> p | ||||
| 			color #fff | ||||
|  | ||||
| .anltbovirfeutcigvwgmgxipejaeozxi[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
| .anltbovirfeutcigvwgmgxipejaeozxi:not([data-darkmode]) | ||||
| 	root(false) | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -4,20 +4,7 @@ | ||||
| 		<template slot="header">%fa:hashtag%%i18n:@title%</template> | ||||
|  | ||||
| 		<div class="mkw-hashtags--body" :data-mobile="platform == 'mobile'"> | ||||
| 			<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 			<p class="empty" v-else-if="stats.length == 0">%fa:exclamation-circle%%i18n:@empty%</p> | ||||
| 			<!-- トランジションを有効にするとなぜかメモリリークする --> | ||||
| 			<!-- <transition-group v-else tag="div" name="chart"> --> | ||||
| 			<div> | ||||
| 				<div v-for="stat in stats" :key="stat.tag"> | ||||
| 					<div class="tag"> | ||||
| 						<router-link :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</router-link> | ||||
| 						<p>{{ '%i18n:@count%'.replace('{}', stat.usersCount) }}</p> | ||||
| 					</div> | ||||
| 					<x-chart class="chart" :src="stat.chart"/> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<!-- </transition-group> --> | ||||
| 			<mk-trends/> | ||||
| 		</div> | ||||
| 	</mk-widget-container> | ||||
| </div> | ||||
| @@ -25,7 +12,6 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import define from '../../../common/define-widget'; | ||||
| import XChart from './hashtags.chart.vue'; | ||||
|  | ||||
| export default define({ | ||||
| 	name: 'hashtags', | ||||
| @@ -33,89 +19,11 @@ export default define({ | ||||
| 		compact: false | ||||
| 	}) | ||||
| }).extend({ | ||||
| 	components: { | ||||
| 		XChart | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			stats: [], | ||||
| 			fetching: true, | ||||
| 			clock: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.fetch(); | ||||
| 		this.clock = setInterval(this.fetch, 1000 * 60); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		clearInterval(this.clock); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		func() { | ||||
| 			this.props.compact = !this.props.compact; | ||||
| 			this.save(); | ||||
| 		}, | ||||
| 		fetch() { | ||||
| 			(this as any).api('hashtags/trend').then(stats => { | ||||
| 				this.stats = stats; | ||||
| 				this.fetching = false; | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| root(isDark) | ||||
| 	.mkw-hashtags--body | ||||
| 		> .fetching | ||||
| 		> .empty | ||||
| 			margin 0 | ||||
| 			padding 16px | ||||
| 			text-align center | ||||
| 			color #aaa | ||||
|  | ||||
| 			> [data-fa] | ||||
| 				margin-right 4px | ||||
|  | ||||
| 		> div | ||||
| 			.chart-move | ||||
| 				transition transform 1s ease | ||||
|  | ||||
| 			> div | ||||
| 				display flex | ||||
| 				align-items center | ||||
| 				padding 14px 16px | ||||
|  | ||||
| 				&:not(:last-child) | ||||
| 					border-bottom solid 1px isDark ? #393f4f : #eee | ||||
|  | ||||
| 				> .tag | ||||
| 					flex 1 | ||||
| 					overflow hidden | ||||
| 					font-size 14px | ||||
| 					color isDark ? #9baec8 : #65727b | ||||
|  | ||||
| 					> a | ||||
| 						display block | ||||
| 						width 100% | ||||
| 						white-space nowrap | ||||
| 						overflow hidden | ||||
| 						text-overflow ellipsis | ||||
| 						color inherit | ||||
|  | ||||
| 					> p | ||||
| 						margin 0 | ||||
| 						font-size 75% | ||||
| 						opacity 0.7 | ||||
|  | ||||
| 				> .chart | ||||
| 					height 30px | ||||
|  | ||||
| .mkw-hashtags[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
| .mkw-hashtags:not([data-darkmode]) | ||||
| 	root(false) | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -55,13 +55,15 @@ export default Vue.extend({ | ||||
| 	methods: { | ||||
| 		onFollow(user) { | ||||
| 			if (user.id == this.u.id) { | ||||
| 				this.user.isFollowing = user.isFollowing; | ||||
| 				this.u.isFollowing = user.isFollowing; | ||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		onUnfollow(user) { | ||||
| 			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 | ||||
| 					}); | ||||
| 				} else { | ||||
| 					if (this.u.isLocked && this.u.hasPendingFollowRequestFromYou) { | ||||
| 					if (this.u.hasPendingFollowRequestFromYou) { | ||||
| 						this.u = await (this as any).api('following/requests/cancel', { | ||||
| 							userId: this.u.id | ||||
| 						}); | ||||
|   | ||||
| @@ -42,8 +42,8 @@ | ||||
| 				<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"/> | ||||
| 			</div> | ||||
| 			<div class="media" v-if="p.media.length > 0"> | ||||
| 				<mk-media-list :media-list="p.media" :raw="true"/> | ||||
| 			<div class="files" v-if="p.files.length > 0"> | ||||
| 				<mk-media-list :media-list="p.files" :raw="true"/> | ||||
| 			</div> | ||||
| 			<mk-poll v-if="p.poll" :note="p"/> | ||||
| 			<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/> | ||||
| @@ -114,7 +114,7 @@ export default Vue.extend({ | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds.length == 0 && | ||||
| 				this.note.fileIds.length == 0 && | ||||
| 				this.note.poll == null); | ||||
| 		}, | ||||
| 		p(): any { | ||||
|   | ||||
| @@ -28,8 +28,8 @@ | ||||
| 						<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> | ||||
| 					</div> | ||||
| 					<div class="media" v-if="p.media.length > 0"> | ||||
| 						<mk-media-list :media-list="p.media"/> | ||||
| 					<div class="files" v-if="p.files.length > 0"> | ||||
| 						<mk-media-list :media-list="p.files"/> | ||||
| 					</div> | ||||
| 					<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> | ||||
| @@ -110,7 +110,7 @@ export default Vue.extend({ | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds.length == 0 && | ||||
| 				this.note.fileIds.length == 0 && | ||||
| 				this.note.poll == null); | ||||
| 		}, | ||||
|  | ||||
|   | ||||
| @@ -122,7 +122,7 @@ export default Vue.extend({ | ||||
| 		prepend(note, silent = false) { | ||||
| 			//#region 弾く | ||||
| 			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 (isMyNote && isPureRenote) { | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| 		<span class="icon" v-if="geo">%fa:map-marker-alt%</span> | ||||
| 		<span v-if="!reply">%i18n:@note%</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> | ||||
|  | ||||
| @@ -14,7 +14,7 @@ | ||||
| 			:reply="reply" | ||||
| 			@posted="onPosted" | ||||
| 			@change-uploadings="onChangeUploadings" | ||||
| 			@change-attached-media="onChangeMedia" | ||||
| 			@change-attached-files="onChangeFiles" | ||||
| 			@geo-attached="onGeoAttached" | ||||
| 			@geo-dettached="onGeoDettached"/> | ||||
| 	</div> | ||||
| @@ -29,7 +29,7 @@ export default Vue.extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			uploadings: [], | ||||
| 			media: [], | ||||
| 			files: [], | ||||
| 			geo: null | ||||
| 		}; | ||||
| 	}, | ||||
| @@ -42,8 +42,8 @@ export default Vue.extend({ | ||||
| 		onChangeUploadings(files) { | ||||
| 			this.uploadings = files; | ||||
| 		}, | ||||
| 		onChangeMedia(media) { | ||||
| 			this.media = media; | ||||
| 		onChangeFiles(files) { | ||||
| 			this.files = files; | ||||
| 		}, | ||||
| 		onGeoAttached(geo) { | ||||
| 			this.geo = geo; | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
| 			@keydown="onKeydown" @paste="onPaste" :placeholder="placeholder" | ||||
| 			v-autocomplete="'text'" | ||||
| 		></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 }"> | ||||
| 				<div v-for="file in files" :key="file.id"> | ||||
| 					<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div> | ||||
| @@ -35,7 +35,7 @@ | ||||
| 	<button class="upload" title="%i18n:@attach-media-from-local%" @click="chooseFile">%fa:upload%</button> | ||||
| 	<button class="drive" title="%i18n:@attach-media-from-drive%" @click="chooseFileFromDrive">%fa:cloud%</button> | ||||
| 	<button class="kao" title="%i18n:@insert-a-kao%" @click="kao">%fa:R smile%</button> | ||||
| 	<button class="poll" title="%i18n:@create-poll%" @click="poll = true">%fa:chart-pie%</button> | ||||
| 	<button class="poll" title="%i18n:@create-poll%" @click="poll = !poll">%fa:chart-pie%</button> | ||||
| 	<button class="poll" title="%i18n:@hide-contents%" @click="useCw = !useCw">%fa:eye-slash%</button> | ||||
| 	<button class="geo" title="%i18n:@attach-location-information%" @click="geo ? removeGeo() : setGeo()">%fa:map-marker-alt%</button> | ||||
| 	<button class="visibility" title="%i18n:@visibility%" @click="setVisibility" ref="visibilityButton"> | ||||
| @@ -188,7 +188,7 @@ export default Vue.extend({ | ||||
| 							(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) { | ||||
| 			this.files.push(driveFile); | ||||
| 			this.$emit('change-attached-media', this.files); | ||||
| 			this.$emit('change-attached-files', this.files); | ||||
| 		}, | ||||
|  | ||||
| 		detachMedia(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() { | ||||
| @@ -249,7 +249,7 @@ export default Vue.extend({ | ||||
| 			this.text = ''; | ||||
| 			this.files = []; | ||||
| 			this.poll = false; | ||||
| 			this.$emit('change-attached-media', this.files); | ||||
| 			this.$emit('change-attached-files', this.files); | ||||
| 		}, | ||||
|  | ||||
| 		onKeydown(e) { | ||||
| @@ -297,7 +297,7 @@ export default Vue.extend({ | ||||
| 			if (driveFile != null && driveFile != '') { | ||||
| 				const file = JSON.parse(driveFile); | ||||
| 				this.files.push(file); | ||||
| 				this.$emit('change-attached-media', this.files); | ||||
| 				this.$emit('change-attached-files', this.files); | ||||
| 				e.preventDefault(); | ||||
| 			} | ||||
| 			//#endregion | ||||
| @@ -354,7 +354,7 @@ export default Vue.extend({ | ||||
|  | ||||
| 			(this as any).api('notes/create', { | ||||
| 				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, | ||||
| 				renoteId: this.renote ? this.renote.id : undefined, | ||||
| 				poll: this.poll ? (this.$refs.poll as any).get() : undefined, | ||||
| @@ -514,7 +514,7 @@ root(isDark) | ||||
| 				margin-right 8px | ||||
| 				white-space nowrap | ||||
|  | ||||
| 		> .medias | ||||
| 		> .files | ||||
| 			margin 0 | ||||
| 			padding 0 | ||||
| 			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"/> | ||||
| 		<a class="rp" v-if="note.renoteId" :href="`/notes/${note.renoteId}`">RP: ...</a> | ||||
| 	</div> | ||||
| 	<details v-if="note.media.length > 0"> | ||||
| 		<summary>({{ '%i18n:@media-count%'.replace('{}', note.media.length) }})</summary> | ||||
| 		<mk-media-list :media-list="note.media"/> | ||||
| 	<details v-if="note.files.length > 0"> | ||||
| 		<summary>({{ '%i18n:@media-count%'.replace('{}', note.files.length) }})</summary> | ||||
| 		<mk-media-list :media-list="note.files"/> | ||||
| 	</details> | ||||
| 	<details v-if="note.poll"> | ||||
| 		<summary>%i18n:@poll%</summary> | ||||
|   | ||||
| @@ -0,0 +1,41 @@ | ||||
| <template> | ||||
| <div class="qldxjjsrseehkusjuoooapmsprvfrxyl mk-admin-card"> | ||||
| 	<header>%i18n:@announcements%</header> | ||||
| 	<textarea v-model="broadcasts"></textarea> | ||||
| 	<button class="ui" @click="save">%i18n:@save%</button> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from "vue"; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			broadcasts: '', | ||||
| 		}; | ||||
| 	}, | ||||
| 	created() { | ||||
| 		(this as any).os.getMeta().then(meta => { | ||||
| 			this.broadcasts = JSON.stringify(meta.broadcasts, null, '  '); | ||||
| 		}); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		save() { | ||||
| 			(this as any).api('admin/update-meta', { | ||||
| 				broadcasts: JSON.parse(this.broadcasts) | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
|  | ||||
| .qldxjjsrseehkusjuoooapmsprvfrxyl | ||||
| 	textarea | ||||
| 		width 100% | ||||
| 		min-height 300px | ||||
|  | ||||
| </style> | ||||
| @@ -4,6 +4,7 @@ | ||||
| 		<ul> | ||||
| 			<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li> | ||||
| 			<li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li> | ||||
| 			<li @click="nav('announcements')" :class="{ active: page == 'announcements' }">%fa:broadcast-tower .fw%%i18n:@announcements%</li> | ||||
| 			<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:@drive%</li> --> | ||||
| 			<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> --> | ||||
| 		</ul> | ||||
| @@ -13,6 +14,9 @@ | ||||
| 			<x-dashboard/> | ||||
| 			<x-charts/> | ||||
| 		</div> | ||||
| 		<div v-show="page == 'announcements'"> | ||||
| 			<x-announcements/> | ||||
| 		</div> | ||||
| 		<div v-if="page == 'users'"> | ||||
| 			<x-suspend-user/> | ||||
| 			<x-unsuspend-user/> | ||||
| @@ -28,6 +32,7 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from "vue"; | ||||
| import XDashboard from "./admin.dashboard.vue"; | ||||
| import XAnnouncements from "./admin.announcements.vue"; | ||||
| import XSuspendUser from "./admin.suspend-user.vue"; | ||||
| import XUnsuspendUser from "./admin.unsuspend-user.vue"; | ||||
| import XVerifyUser from "./admin.verify-user.vue"; | ||||
| @@ -37,6 +42,7 @@ import XCharts from "../../components/charts.vue"; | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XDashboard, | ||||
| 		XAnnouncements, | ||||
| 		XSuspendUser, | ||||
| 		XUnsuspendUser, | ||||
| 		XVerifyUser, | ||||
|   | ||||
| @@ -28,6 +28,7 @@ | ||||
| import Vue from 'vue'; | ||||
| import Menu from '../../../../common/views/components/menu.vue'; | ||||
| import contextmenu from '../../../api/contextmenu'; | ||||
| import { countIf } from '../../../../../../prelude/array'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| @@ -117,7 +118,7 @@ export default Vue.extend({ | ||||
| 		toggleActive() { | ||||
| 			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)); | ||||
| 			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; | ||||
| 		}, | ||||
|  | ||||
|   | ||||
| @@ -68,7 +68,7 @@ export default Vue.extend({ | ||||
| 				(this as any).api('notes/user-list-timeline', { | ||||
| 					listId: this.list.id, | ||||
| 					limit: fetchLimit + 1, | ||||
| 					mediaOnly: this.mediaOnly, | ||||
| 					withFiles: this.mediaOnly, | ||||
| 					includeMyRenotes: this.$store.state.settings.showMyRenotes, | ||||
| 					includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, | ||||
| 					includeLocalRenotes: this.$store.state.settings.showLocalRenotes | ||||
| @@ -90,7 +90,7 @@ export default Vue.extend({ | ||||
| 				listId: this.list.id, | ||||
| 				limit: fetchLimit + 1, | ||||
| 				untilId: (this.$refs.timeline as any).tail().id, | ||||
| 				mediaOnly: this.mediaOnly, | ||||
| 				withFiles: this.mediaOnly, | ||||
| 				includeMyRenotes: this.$store.state.settings.showMyRenotes, | ||||
| 				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, | ||||
| 				includeLocalRenotes: this.$store.state.settings.showLocalRenotes | ||||
| @@ -109,7 +109,7 @@ export default Vue.extend({ | ||||
| 			return promise; | ||||
| 		}, | ||||
| 		onNote(note) { | ||||
| 			if (this.mediaOnly && note.media.length == 0) return; | ||||
| 			if (this.mediaOnly && note.files.length == 0) return; | ||||
|  | ||||
| 			// Prepend a 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"/> | ||||
| 						<a class="rp" v-if="p.renote != null">RP:</a> | ||||
| 					</div> | ||||
| 					<div class="media" v-if="p.media.length > 0"> | ||||
| 						<mk-media-list :media-list="p.media"/> | ||||
| 					<div class="files" v-if="p.files.length > 0"> | ||||
| 						<mk-media-list :media-list="p.files"/> | ||||
| 					</div> | ||||
| 					<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> | ||||
| @@ -54,11 +54,11 @@ | ||||
| 	</article> | ||||
| </div> | ||||
| <div v-else class="srwrkujossgfuhrbnvqkybtzxpblgchi"> | ||||
| 	<div v-if="note.media.length > 0"> | ||||
| 		<mk-media-list :media-list="note.media"/> | ||||
| 	<div v-if="note.files.length > 0"> | ||||
| 		<mk-media-list :media-list="note.files"/> | ||||
| 	</div> | ||||
| 	<div v-if="note.renote && note.renote.media.length > 0"> | ||||
| 		<mk-media-list :media-list="note.renote.media"/> | ||||
| 	<div v-if="note.renote && note.renote.files.length > 0"> | ||||
| 		<mk-media-list :media-list="note.renote.files"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
| @@ -100,7 +100,7 @@ export default Vue.extend({ | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds.length == 0 && | ||||
| 				this.note.fileIds.length == 0 && | ||||
| 				this.note.poll == null); | ||||
| 		}, | ||||
|  | ||||
| @@ -371,7 +371,7 @@ root(isDark) | ||||
| 					.mk-url-preview | ||||
| 						margin-top 8px | ||||
|  | ||||
| 					> .media | ||||
| 					> .files | ||||
| 						> img | ||||
| 							display block | ||||
| 							max-width 100% | ||||
|   | ||||
| @@ -127,7 +127,7 @@ export default Vue.extend({ | ||||
| 		prepend(note, silent = false) { | ||||
| 			//#region 弾く | ||||
| 			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 (isMyNote && isPureRenote) { | ||||
|   | ||||
| @@ -96,7 +96,7 @@ export default Vue.extend({ | ||||
| 			(this.$refs.timeline as any).init(() => new Promise((res, rej) => { | ||||
| 				(this as any).api(this.endpoint, { | ||||
| 					limit: fetchLimit + 1, | ||||
| 					mediaOnly: this.mediaOnly, | ||||
| 					withFiles: this.mediaOnly, | ||||
| 					includeMyRenotes: this.$store.state.settings.showMyRenotes, | ||||
| 					includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, | ||||
| 					includeLocalRenotes: this.$store.state.settings.showLocalRenotes | ||||
| @@ -117,7 +117,7 @@ export default Vue.extend({ | ||||
|  | ||||
| 			const promise = (this as any).api(this.endpoint, { | ||||
| 				limit: fetchLimit + 1, | ||||
| 				mediaOnly: this.mediaOnly, | ||||
| 				withFiles: this.mediaOnly, | ||||
| 				untilId: (this.$refs.timeline as any).tail().id, | ||||
| 				includeMyRenotes: this.$store.state.settings.showMyRenotes, | ||||
| 				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, | ||||
| @@ -138,7 +138,7 @@ export default Vue.extend({ | ||||
| 		}, | ||||
|  | ||||
| 		onNote(note) { | ||||
| 			if (this.mediaOnly && note.media.length == 0) return; | ||||
| 			if (this.mediaOnly && note.files.length == 0) return; | ||||
|  | ||||
| 			// Prepend a note | ||||
| 			(this.$refs.timeline as any).prepend(note); | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
| 		<div class="title"> | ||||
| 			<p class="name">{{ user | userName }}</p> | ||||
| 			<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 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> | ||||
|   | ||||
| @@ -24,12 +24,12 @@ export default Vue.extend({ | ||||
| 	mounted() { | ||||
| 		(this as any).api('users/notes', { | ||||
| 			userId: this.user.id, | ||||
| 			withMedia: true, | ||||
| 			withFiles: true, | ||||
| 			limit: 9 | ||||
| 		}).then(notes => { | ||||
| 			notes.forEach(note => { | ||||
| 				note.media.forEach(media => { | ||||
| 					if (this.images.length < 9) this.images.push(media); | ||||
| 				note.files.forEach(file => { | ||||
| 					if (this.images.length < 9) this.images.push(file); | ||||
| 				}); | ||||
| 			}); | ||||
| 			this.fetching = false; | ||||
|   | ||||
| @@ -66,7 +66,7 @@ export default Vue.extend({ | ||||
| 					limit: fetchLimit + 1, | ||||
| 					untilDate: this.date ? this.date.getTime() : undefined, | ||||
| 					includeReplies: this.mode == 'with-replies', | ||||
| 					withMedia: this.mode == 'with-media' | ||||
| 					withFiles: this.mode == 'with-media' | ||||
| 				}).then(notes => { | ||||
| 					if (notes.length == fetchLimit + 1) { | ||||
| 						notes.pop(); | ||||
| @@ -86,7 +86,7 @@ export default Vue.extend({ | ||||
| 				userId: this.user.id, | ||||
| 				limit: fetchLimit + 1, | ||||
| 				includeReplies: this.mode == 'with-replies', | ||||
| 				withMedia: this.mode == 'with-media', | ||||
| 				withFiles: this.mode == 'with-media', | ||||
| 				untilId: (this.$refs.timeline as any).tail().id | ||||
| 			}); | ||||
|  | ||||
|   | ||||
| @@ -1,46 +1,83 @@ | ||||
| <template> | ||||
| <div class="mk-welcome"> | ||||
| 	<img ref="pointer" class="pointer" src="/assets/pointer.png" alt=""> | ||||
| 	<button @click="dark"> | ||||
| 		<template v-if="$store.state.device.darkmode">%fa:moon%</template> | ||||
| 		<template v-else>%fa:R moon%</template> | ||||
| 	</button> | ||||
|  | ||||
| 	<mk-forkit class="forkit"/> | ||||
|  | ||||
| 	<div class="body"> | ||||
| 		<div class="container"> | ||||
| 		<div class="main block"> | ||||
| 			<div> | ||||
| 				<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> | ||||
|  | ||||
| 				<div class="info"> | ||||
| 				<span><b>{{ host }}</b></span> | ||||
| 					<span><b>{{ host }}</b> - <span v-html="'%i18n:@powered-by-misskey%'"></span></span> | ||||
| 					<span class="stats" v-if="stats"> | ||||
| 						<span>%fa:user% {{ stats.originalUsersCount | number }}</span> | ||||
| 						<span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span> | ||||
| 					</span> | ||||
| 				</div> | ||||
| 			<main> | ||||
| 				<div class="about"> | ||||
| 					<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> | ||||
| 					<p class="powerd-by" v-if="name != 'Misskey'" v-html="'%i18n:@powered-by-misskey%'"></p> | ||||
|  | ||||
| 				<p class="desc" v-html="description || '%i18n:common.about%'"></p> | ||||
| 					<a ref="signup" @click="signup">📦 %i18n:@signup%</a> | ||||
|  | ||||
| 				<p class="sign"> | ||||
| 					<span class="signup" @click="signup">%i18n:@signup%</span> | ||||
| 					<span class="divider">|</span> | ||||
| 					<span class="signin" @click="signin">%i18n:@signin%</span> | ||||
| 				</p> | ||||
| 			</div> | ||||
| 				<div class="login"> | ||||
| 					<mk-signin/> | ||||
| 				</div> | ||||
| 			</main> | ||||
| 			<div class="hashtags"> | ||||
| 				<router-link v-for="tag in tags" :key="tag" :to="`/tags/${ tag }`" :title="tag">#{{ tag }}</router-link> | ||||
| 			</div> | ||||
| 			<mk-nav class="nav"/> | ||||
| 		</div> | ||||
| 		<mk-forkit class="forkit"/> | ||||
| 		<img src="assets/title.dark.svg" :alt="name"> | ||||
| 	</div> | ||||
| 	<div class="tl"> | ||||
| 		<mk-welcome-timeline :max="20"/> | ||||
| 		</div> | ||||
|  | ||||
| 	<modal name="signup" width="500px" height="auto" scrollable> | ||||
| 		<header :class="$style.signupFormHeader">%i18n:@signup%</header> | ||||
| 		<mk-signup :class="$style.signupForm"/> | ||||
| 		<div class="announcements block"> | ||||
| 			<header>%fa:broadcast-tower% %i18n:@announcements%</header> | ||||
| 			<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 class="nav block"> | ||||
| 			<div> | ||||
| 				<mk-nav class="nav"/> | ||||
| 			</div> | ||||
| 		</div> | ||||
|  | ||||
| 		<div class="side"> | ||||
| 			<div class="trends block"> | ||||
| 				<div> | ||||
| 					<mk-trends/> | ||||
| 				</div> | ||||
| 			</div> | ||||
|  | ||||
| 			<div class="tl block"> | ||||
| 				<header>%fa:comment-alt R% %i18n:@timeline%</header> | ||||
| 				<div> | ||||
| 					<mk-welcome-timeline class="tl" :max="20"/> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
|  | ||||
| 	<modal name="signup" :class="$store.state.device.darkmode ? 'modal-dark' : 'modal-light'" width="450px" height="auto" scrollable> | ||||
| 		<header class="formHeader">%i18n:@signup%</header> | ||||
| 		<mk-signup class="form"/> | ||||
| 	</modal> | ||||
|  | ||||
| 	<modal name="signin" :class="$store.state.device.darkmode ? 'modal-dark' : 'modal-light'" width="450px" height="auto" scrollable> | ||||
| 		<header class="formHeader">%i18n:@signin%</header> | ||||
| 		<mk-signin class="form"/> | ||||
| 	</modal> | ||||
| </div> | ||||
| </template> | ||||
| @@ -57,43 +94,46 @@ export default Vue.extend({ | ||||
| 			host, | ||||
| 			name: 'Misskey', | ||||
| 			description: '', | ||||
| 			pointerInterval: null, | ||||
| 			tags: [] | ||||
| 			announcements: [], | ||||
| 			photos: [] | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	created() { | ||||
| 		(this as any).os.getMeta().then(meta => { | ||||
| 			this.name = meta.name; | ||||
| 			this.description = meta.description; | ||||
| 			this.announcements = meta.broadcasts; | ||||
| 		}); | ||||
|  | ||||
| 		(this as any).api('stats').then(stats => { | ||||
| 			this.stats = stats; | ||||
| 		}); | ||||
|  | ||||
| 		(this as any).api('hashtags/trend').then(stats => { | ||||
| 			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); | ||||
| 		}); | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.point(); | ||||
| 		this.pointerInterval = setInterval(this.point, 100); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		clearInterval(this.pointerInterval); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		point() { | ||||
| 			const x = this.$refs.signup.getBoundingClientRect(); | ||||
| 			this.$refs.pointer.style.top = x.top + x.height + 'px'; | ||||
| 			this.$refs.pointer.style.left = x.left + 'px'; | ||||
| 		}, | ||||
| 		signup() { | ||||
| 			this.$modal.show('signup'); | ||||
| 		}, | ||||
|  | ||||
| 		signin() { | ||||
| 			this.$modal.show('signin'); | ||||
| 		}, | ||||
|  | ||||
| 		dark() { | ||||
| 			this.$store.commit('device/set', { | ||||
| 				key: 'darkmode', | ||||
| @@ -104,11 +144,40 @@ export default Vue.extend({ | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style> | ||||
| #wait { | ||||
| 	right: auto; | ||||
| 	left: 15px; | ||||
| } | ||||
| <style lang="stylus"> | ||||
| #wait | ||||
| 	right auto | ||||
| 	left 15px | ||||
|  | ||||
| .v--modal-overlay | ||||
| 	background rgba(0, 0, 0, 0.6) | ||||
|  | ||||
| .modal-light | ||||
| 	.v--modal-box | ||||
| 		color #777 | ||||
|  | ||||
| 		.formHeader | ||||
| 			border-bottom solid 1px #eee | ||||
|  | ||||
| .modal-dark | ||||
| 	.v--modal-box | ||||
| 		background #313543 | ||||
| 		color #fff | ||||
|  | ||||
| 		.formHeader | ||||
| 			border-bottom solid 1px rgba(#000, 0.2) | ||||
|  | ||||
| .modal-light | ||||
| .modal-dark | ||||
| 	.form | ||||
| 		padding 24px 48px 48px 48px | ||||
|  | ||||
| 	.formHeader | ||||
| 		text-align center | ||||
| 		padding 48px 0 12px 0 | ||||
| 		margin 0 48px | ||||
| 		font-size 1.5em | ||||
|  | ||||
| </style> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| @@ -117,146 +186,159 @@ export default Vue.extend({ | ||||
| root(isDark) | ||||
| 	display flex | ||||
| 	min-height 100vh | ||||
|  | ||||
| 	> .pointer | ||||
| 		display block | ||||
| 		position absolute | ||||
| 		z-index 1 | ||||
| 		top 0 | ||||
| 		right 0 | ||||
| 		width 180px | ||||
| 		margin 0 0 0 -180px | ||||
| 		transform rotateY(180deg) translateX(-10px) translateY(-48px) | ||||
| 		pointer-events none | ||||
|  | ||||
| 	> button | ||||
| 		position fixed | ||||
| 		z-index 1 | ||||
| 		top 0 | ||||
| 		left 0 | ||||
| 		padding 16px | ||||
| 		font-size 18px | ||||
| 		color #fff | ||||
|  | ||||
| 		display none // TODO | ||||
|  | ||||
| 	> .body | ||||
| 		flex 1 | ||||
| 		padding 64px 0 0 0 | ||||
| 		text-align center | ||||
| 		background #578394 | ||||
| 		background-position center | ||||
| 		background-size cover | ||||
|  | ||||
| 		&:before | ||||
| 			content '' | ||||
| 			display block | ||||
| 			position absolute | ||||
| 			top 0 | ||||
| 			left 0 | ||||
| 			right 0 | ||||
| 			bottom 0 | ||||
| 			background rgba(#000, 0.5) | ||||
| 	//background-color #00070F | ||||
| 	//background-image url('/assets/bg.jpg') | ||||
| 	//background-position center | ||||
| 	//background-size cover | ||||
|  | ||||
| 	> .forkit | ||||
| 		position absolute | ||||
| 		top 0 | ||||
| 		right 0 | ||||
|  | ||||
| 		> img | ||||
| 			position absolute | ||||
| 	> button | ||||
| 		position fixed | ||||
| 		z-index 1 | ||||
| 		bottom 16px | ||||
| 			right 16px | ||||
| 			width 150px | ||||
| 		left 16px | ||||
| 		padding 16px | ||||
| 		font-size 18px | ||||
| 		color isDark ? #fff : #444 | ||||
|  | ||||
| 		> .container | ||||
| 			$aboutWidth = 380px | ||||
| 			$loginWidth = 340px | ||||
| 			$width = $aboutWidth + $loginWidth | ||||
| 	> .body | ||||
| 		display grid | ||||
| 		grid-template-rows 1fr 1fr 64px | ||||
| 		grid-template-columns 1fr 1fr 350px | ||||
| 		gap 16px | ||||
| 		width 100% | ||||
| 		max-width 1200px | ||||
| 		height 100vh | ||||
| 		min-height 1000px | ||||
| 		margin 0 auto | ||||
| 		padding 64px | ||||
|  | ||||
| 		.block | ||||
| 			color isDark ? #fff : #444 | ||||
| 			background isDark ? #282C37 : #fff | ||||
| 			box-shadow 0 3px 8px rgba(0, 0, 0, 0.2) | ||||
| 			//border-radius 8px | ||||
| 			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 | ||||
| 			grid-row 1 | ||||
| 			grid-column 1 / 3 | ||||
| 			border-top solid 5px $theme-color | ||||
|  | ||||
| 			> div | ||||
| 				padding 32px | ||||
|  | ||||
| 				> h1 | ||||
| 					margin 0 | ||||
|  | ||||
| 					> img | ||||
| 						margin -8px 0 0 -16px | ||||
| 						max-width 280px | ||||
|  | ||||
| 				> .info | ||||
| 					margin 0 auto 16px auto | ||||
| 					width $width | ||||
| 					font-size 14px | ||||
| 				color #fff | ||||
|  | ||||
| 					> .stats | ||||
| 						margin-left 16px | ||||
| 						padding-left 16px | ||||
| 					border-left solid 1px #fff | ||||
| 						border-left solid 1px isDark ? #fff : #444 | ||||
|  | ||||
| 						> * | ||||
| 							margin-right 16px | ||||
|  | ||||
| 			> main | ||||
| 				display flex | ||||
| 				margin auto | ||||
| 				width $width | ||||
| 				border-radius 8px | ||||
| 				overflow hidden | ||||
| 				box-shadow 0 2px 8px rgba(#000, 0.3) | ||||
| 				> .sign | ||||
| 					font-size 120% | ||||
|  | ||||
| 				> .about | ||||
| 					width $aboutWidth | ||||
| 					color #444 | ||||
| 					background #fff | ||||
| 					> .divider | ||||
| 						margin 0 16px | ||||
|  | ||||
| 					> .signin | ||||
| 					> .signup | ||||
| 						cursor pointer | ||||
|  | ||||
| 						&:hover | ||||
| 							color $theme-color | ||||
|  | ||||
| 		> .announcements | ||||
| 			grid-row 2 | ||||
| 			grid-column 1 | ||||
|  | ||||
| 			> div | ||||
| 				padding 32px | ||||
|  | ||||
| 				> div | ||||
| 					padding 0 0 16px 0 | ||||
| 					margin 0 0 16px 0 | ||||
| 					border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05) | ||||
|  | ||||
| 					> h1 | ||||
| 						margin 0 0 16px 0 | ||||
| 						padding 32px 32px 0 32px | ||||
| 						color #444 | ||||
|  | ||||
| 						> img | ||||
| 							width 170px | ||||
| 							vertical-align bottom | ||||
|  | ||||
| 					> .powerd-by | ||||
| 						margin 16px | ||||
| 						opacity 0.7 | ||||
|  | ||||
| 					> .desc | ||||
| 						margin 0 | ||||
| 						padding 0 32px 16px 32px | ||||
| 						font-size 1.25em | ||||
|  | ||||
| 					> a | ||||
| 						display inline-block | ||||
| 						margin 0 0 32px 0 | ||||
| 						font-weight bold | ||||
| 		> .photos | ||||
| 			grid-row 2 | ||||
| 			grid-column 2 | ||||
|  | ||||
| 				> .login | ||||
| 					width $loginWidth | ||||
| 					padding 16px 32px 32px 32px | ||||
| 					background isDark ? #2e3440 : #f5f5f5 | ||||
| 			> div | ||||
| 				display grid | ||||
| 				grid-template-rows 1fr 1fr 1fr | ||||
| 				grid-template-columns 1fr 1fr | ||||
| 				gap 8px | ||||
| 				height 100% | ||||
| 				padding 16px | ||||
|  | ||||
| 			> .hashtags | ||||
| 				margin 16px auto | ||||
| 				width $width | ||||
| 				font-size 14px | ||||
| 				color #fff | ||||
| 				background rgba(#000, 0.3) | ||||
| 				border-radius 8px | ||||
|  | ||||
| 				> * | ||||
| 					display inline-block | ||||
| 					margin 14px | ||||
| 				> div | ||||
| 					//border-radius 4px | ||||
| 					background-position center center | ||||
| 					background-size cover | ||||
|  | ||||
| 		> .nav | ||||
| 				display block | ||||
| 				margin 16px 0 | ||||
| 			display flex | ||||
| 			justify-content center | ||||
| 			align-items center | ||||
| 			grid-row 3 | ||||
| 			grid-column 1 / 3 | ||||
| 			font-size 14px | ||||
| 				color #fff | ||||
|  | ||||
| 		> .side | ||||
| 			display grid | ||||
| 			grid-row 1 / 4 | ||||
| 			grid-column 3 | ||||
| 			grid-template-rows 1fr 350px | ||||
| 			grid-template-columns 1fr | ||||
| 			gap 16px | ||||
|  | ||||
| 			> .tl | ||||
| 		margin 0 | ||||
| 		width 410px | ||||
| 		height 100vh | ||||
| 		text-align left | ||||
| 		background isDark ? #313543 : #fff | ||||
|  | ||||
| 		> * | ||||
| 			max-height 100% | ||||
| 				grid-row 1 | ||||
| 				grid-column 1 | ||||
| 				overflow auto | ||||
|  | ||||
| 			> .trends | ||||
| 				grid-row 2 | ||||
| 				grid-column 1 | ||||
| 				padding 8px | ||||
|  | ||||
| .mk-welcome[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
| @@ -264,29 +346,3 @@ root(isDark) | ||||
| 	root(false) | ||||
|  | ||||
| </style> | ||||
|  | ||||
| <style lang="stylus" module> | ||||
| .signupForm | ||||
| 	padding 24px 48px 48px 48px | ||||
|  | ||||
| .signupFormHeader | ||||
| 	padding 48px 0 12px 0 | ||||
| 	margin: 0 48px | ||||
| 	font-size 1.5em | ||||
| 	color #777 | ||||
| 	border-bottom solid 1px #eee | ||||
|  | ||||
| .signinForm | ||||
| 	padding 24px 48px 48px 48px | ||||
|  | ||||
| .signinFormHeader | ||||
| 	padding 48px 0 12px 0 | ||||
| 	margin: 0 48px | ||||
| 	font-size 1.5em | ||||
| 	color #777 | ||||
| 	border-bottom solid 1px #eee | ||||
|  | ||||
| .nav | ||||
| 	a | ||||
| 		color #666 | ||||
| </style> | ||||
|   | ||||
| @@ -49,7 +49,7 @@ export default define({ | ||||
| 				offset: this.offset, | ||||
| 				renote: false, | ||||
| 				reply: false, | ||||
| 				media: false, | ||||
| 				file: false, | ||||
| 				poll: false | ||||
| 			}).then(notes => { | ||||
| 				const note = notes ? notes[0] : null; | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { EventEmitter } from 'eventemitter3'; | ||||
| import * as uuid from 'uuid'; | ||||
|  | ||||
| import initStore from './store'; | ||||
| import { apiUrl, swPublickey, version, lang, googleMapsApiKey } from './config'; | ||||
| import { apiUrl, version, lang } from './config'; | ||||
| import Progress from './common/scripts/loading'; | ||||
| import Connection from './common/scripts/streaming/stream'; | ||||
| import { HomeStreamManager } from './common/scripts/streaming/home'; | ||||
| @@ -230,13 +230,13 @@ export default class MiOS extends EventEmitter { | ||||
| 		//#region Init stream managers | ||||
| 		this.streams.serverStatsStream = new ServerStatsStreamManager(this); | ||||
| 		this.streams.notesStatsStream = new NotesStatsStreamManager(this); | ||||
| 		this.streams.localTimelineStream = new LocalTimelineStreamManager(this, this.store.state.i); | ||||
|  | ||||
| 		this.once('signedin', () => { | ||||
| 			// Init home stream manager | ||||
| 			this.stream = new HomeStreamManager(this, this.store.state.i); | ||||
|  | ||||
| 			// Init other stream manager | ||||
| 			this.streams.localTimelineStream = new LocalTimelineStreamManager(this, this.store.state.i); | ||||
| 			this.streams.hybridTimelineStream = new HybridTimelineStreamManager(this, this.store.state.i); | ||||
| 			this.streams.globalTimelineStream = new GlobalTimelineStreamManager(this, this.store.state.i); | ||||
| 			this.streams.driveStream = new DriveStreamManager(this, this.store.state.i); | ||||
| @@ -361,7 +361,7 @@ export default class MiOS extends EventEmitter { | ||||
|  | ||||
| 				// A public key your push server will use to send | ||||
| 				// messages to client apps via a push server. | ||||
| 				applicationServerKey: urlBase64ToUint8Array(swPublickey) | ||||
| 				applicationServerKey: urlBase64ToUint8Array(this.meta.data.swPublickey) | ||||
| 			}; | ||||
|  | ||||
| 			// Subscribe push notification | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| <template> | ||||
| <div class="mk-drive-file-chooser"> | ||||
| <div class="cdxzvcfawjxdyxsekbxbfgtplebnoneb"> | ||||
| 	<div class="body"> | ||||
| 		<header> | ||||
| 			<h1>%i18n:@select-file%<span class="count" v-if="files.length > 0">({{ files.length }})</span></h1> | ||||
| 			<button class="close" @click="cancel">%fa:times%</button> | ||||
| 			<button v-if="multiple" class="ok" @click="ok">%fa:check%</button> | ||||
| 		</header> | ||||
| 		<mk-drive ref="browser" | ||||
| 		<mk-drive class="drive" ref="browser" | ||||
| 			:select-file="true" | ||||
| 			:multiple="multiple" | ||||
| 			@change-selection="onChangeSelection" | ||||
| @@ -46,7 +46,7 @@ export default Vue.extend({ | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .mk-drive-file-chooser | ||||
| root(isDark) | ||||
| 	position fixed | ||||
| 	z-index 20000 | ||||
| 	top 0 | ||||
| @@ -59,10 +59,11 @@ export default Vue.extend({ | ||||
| 	> .body | ||||
| 		width 100% | ||||
| 		height 100% | ||||
| 		background #fff | ||||
| 		background isDark ? #282c37 : #fff | ||||
|  | ||||
| 		> header | ||||
| 			border-bottom solid 1px #eee | ||||
| 			border-bottom solid 1px isDark ? #1b1f29 : #eee | ||||
| 			color isDark ? #fff : #111 | ||||
|  | ||||
| 			> h1 | ||||
| 				margin 0 | ||||
| @@ -90,9 +91,15 @@ export default Vue.extend({ | ||||
| 				line-height 42px | ||||
| 				width 42px | ||||
|  | ||||
| 		> .mk-drive | ||||
| 		> .drive | ||||
| 			height calc(100% - 42px) | ||||
| 			overflow scroll | ||||
| 			-webkit-overflow-scrolling touch | ||||
|  | ||||
| .cdxzvcfawjxdyxsekbxbfgtplebnoneb[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
| .cdxzvcfawjxdyxsekbxbfgtplebnoneb:not([data-darkmode]) | ||||
| 	root(false) | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -67,7 +67,7 @@ | ||||
| import Vue from 'vue'; | ||||
| import * as EXIF from 'exif-js'; | ||||
| import * as hljs from 'highlight.js'; | ||||
| import gcd from '../../../common/scripts/gcd'; | ||||
| import { gcd } from '../../../../../prelude/math'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['file'], | ||||
|   | ||||
| @@ -48,12 +48,14 @@ export default Vue.extend({ | ||||
| 		onFollow(user) { | ||||
| 			if (user.id == this.u.id) { | ||||
| 				this.u.isFollowing = user.isFollowing; | ||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		onUnfollow(user) { | ||||
| 			if (user.id == this.u.id) { | ||||
| 				this.u.isFollowing = user.isFollowing; | ||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| @@ -66,7 +68,7 @@ export default Vue.extend({ | ||||
| 						userId: this.u.id | ||||
| 					}); | ||||
| 				} else { | ||||
| 					if (this.u.isLocked && this.u.hasPendingFollowRequestFromYou) { | ||||
| 					if (this.u.hasPendingFollowRequestFromYou) { | ||||
| 						this.u = await (this as any).api('following/requests/cancel', { | ||||
| 							userId: this.u.id | ||||
| 						}); | ||||
|   | ||||
| @@ -40,8 +40,8 @@ | ||||
| 				<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"/> | ||||
| 			</div> | ||||
| 			<div class="media" v-if="p.media.length > 0"> | ||||
| 				<mk-media-list :media-list="p.media" :raw="true"/> | ||||
| 			<div class="files" v-if="p.files.length > 0"> | ||||
| 				<mk-media-list :media-list="p.files" :raw="true"/> | ||||
| 			</div> | ||||
| 			<mk-poll v-if="p.poll" :note="p"/> | ||||
| 			<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/> | ||||
| @@ -113,7 +113,7 @@ export default Vue.extend({ | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds.length == 0 && | ||||
| 				this.note.fileIds.length == 0 && | ||||
| 				this.note.poll == null); | ||||
| 		}, | ||||
|  | ||||
| @@ -369,7 +369,7 @@ root(isDark) | ||||
| 			> .mk-url-preview | ||||
| 				margin-top 8px | ||||
|  | ||||
| 			> .media | ||||
| 			> .files | ||||
| 				> img | ||||
| 					display block | ||||
| 					max-width 100% | ||||
|   | ||||
| @@ -28,8 +28,8 @@ | ||||
| 						<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> | ||||
| 					</div> | ||||
| 					<div class="media" v-if="p.media.length > 0"> | ||||
| 						<mk-media-list :media-list="p.media"/> | ||||
| 					<div class="files" v-if="p.files.length > 0"> | ||||
| 						<mk-media-list :media-list="p.files"/> | ||||
| 					</div> | ||||
| 					<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/> | ||||
| 					<mk-url-preview v-for="url in urls" :url="url" :key="url"/> | ||||
| @@ -90,7 +90,7 @@ export default Vue.extend({ | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds.length == 0 && | ||||
| 				this.note.fileIds.length == 0 && | ||||
| 				this.note.poll == null); | ||||
| 		}, | ||||
|  | ||||
| @@ -414,7 +414,7 @@ root(isDark) | ||||
| 					.mk-url-preview | ||||
| 						margin-top 8px | ||||
|  | ||||
| 					> .media | ||||
| 					> .files | ||||
| 						> img | ||||
| 							display block | ||||
| 							max-width 100% | ||||
| @@ -471,10 +471,6 @@ root(isDark) | ||||
| 					&.reacted | ||||
| 						color $theme-color | ||||
|  | ||||
| 					&.menu | ||||
| 						@media (max-width 350px) | ||||
| 							display none | ||||
|  | ||||
| .note[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
|   | ||||
| @@ -125,7 +125,7 @@ export default Vue.extend({ | ||||
| 		prepend(note, silent = false) { | ||||
| 			//#region 弾く | ||||
| 			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 (isMyNote && isPureRenote) { | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| <template> | ||||
| <div class="mk-notify"> | ||||
| <div class="mk-notify" :class="pos"> | ||||
| 	<div> | ||||
| 		<mk-notification-preview :notification="notification"/> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| @@ -10,11 +12,16 @@ import * as anime from 'animejs'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['notification'], | ||||
| 	computed: { | ||||
| 		pos() { | ||||
| 			return this.$store.state.device.mobileNotificationPosition; | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.$nextTick(() => { | ||||
| 			anime({ | ||||
| 				targets: this.$el, | ||||
| 				bottom: '0px', | ||||
| 				[this.pos]: '0px', | ||||
| 				duration: 500, | ||||
| 				easing: 'easeOutQuad' | ||||
| 			}); | ||||
| @@ -22,7 +29,7 @@ export default Vue.extend({ | ||||
| 			setTimeout(() => { | ||||
| 				anime({ | ||||
| 					targets: this.$el, | ||||
| 					bottom: '-64px', | ||||
| 					[this.pos]: `-${this.$el.offsetHeight}px`, | ||||
| 					duration: 500, | ||||
| 					easing: 'easeOutQuad', | ||||
| 					complete: () => this.$destroy() | ||||
| @@ -35,15 +42,32 @@ export default Vue.extend({ | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .mk-notify | ||||
| 	$height = 78px | ||||
|  | ||||
| 	position fixed | ||||
| 	z-index 1024 | ||||
| 	bottom -64px | ||||
| 	left 0 | ||||
| 	right 0 | ||||
| 	width 100% | ||||
| 	height 64px | ||||
| 	max-width 500px | ||||
| 	height $height | ||||
| 	margin 0 auto | ||||
| 	padding 8px | ||||
| 	pointer-events none | ||||
| 	font-size 80% | ||||
|  | ||||
| 	&.bottom | ||||
| 		bottom -($height) | ||||
|  | ||||
| 	&.top | ||||
| 		top -($height) | ||||
|  | ||||
| 	> div | ||||
| 		height 100% | ||||
| 		-webkit-backdrop-filter blur(2px) | ||||
| 		backdrop-filter blur(2px) | ||||
| 		background-color rgba(#000, 0.5) | ||||
| 		border-radius 7px | ||||
| 		overflow hidden | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="ulveipglmagnxfgvitaxyszerjwiqmwl"> | ||||
| 	<div class="bg" ref="bg" @click="onBgClick"></div> | ||||
| 	<div class="main" ref="main" @click.self="onBgClick"> | ||||
| 	<div class="bg" ref="bg"></div> | ||||
| 	<div class="main" ref="main"> | ||||
| 		<mk-post-form ref="form" | ||||
| 			:reply="reply" | ||||
| 			:renote="renote" | ||||
| @@ -83,11 +83,6 @@ export default Vue.extend({ | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		onBgClick() { | ||||
| 			this.$emit('cancel'); | ||||
| 			this.close(); | ||||
| 		}, | ||||
|  | ||||
| 		onPosted() { | ||||
| 			this.$emit('posted'); | ||||
| 			this.close(); | ||||
|   | ||||
| @@ -200,12 +200,12 @@ export default Vue.extend({ | ||||
|  | ||||
| 		attachMedia(driveFile) { | ||||
| 			this.files.push(driveFile); | ||||
| 			this.$emit('change-attached-media', this.files); | ||||
| 			this.$emit('change-attached-files', this.files); | ||||
| 		}, | ||||
|  | ||||
| 		detachMedia(file) { | ||||
| 			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() { | ||||
| @@ -269,7 +269,7 @@ export default Vue.extend({ | ||||
| 			this.text = ''; | ||||
| 			this.files = []; | ||||
| 			this.poll = false; | ||||
| 			this.$emit('change-attached-media'); | ||||
| 			this.$emit('change-attached-files'); | ||||
| 		}, | ||||
|  | ||||
| 		post() { | ||||
| @@ -277,7 +277,7 @@ export default Vue.extend({ | ||||
| 			const viaMobile = this.$store.state.settings.disableViaMobile !== true; | ||||
| 			(this as any).api('notes/create', { | ||||
| 				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, | ||||
| 				renoteId: this.renote ? this.renote.id : 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"/> | ||||
| 		<a class="rp" v-if="note.renoteId">RP: ...</a> | ||||
| 	</div> | ||||
| 	<details v-if="note.media.length > 0"> | ||||
| 		<summary>({{ '%i18n:@media-count%'.replace('{}', note.media.length) }})</summary> | ||||
| 		<mk-media-list :media-list="note.media"/> | ||||
| 	<details v-if="note.files.length > 0"> | ||||
| 		<summary>({{ '%i18n:@media-count%'.replace('{}', note.files.length) }})</summary> | ||||
| 		<mk-media-list :media-list="note.files"/> | ||||
| 	</details> | ||||
| 	<details v-if="note.poll"> | ||||
| 		<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> | ||||
| 				</ul> | ||||
| 			</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> | ||||
| 		</div> | ||||
| 	</transition> | ||||
| @@ -46,23 +52,32 @@ import { lang } from '../../../config'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: ['isOpen'], | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			hasGameInvitation: false, | ||||
| 			connection: null, | ||||
| 			connectionId: null, | ||||
| 			aboutUrl: `/docs/${lang}/about` | ||||
| 			aboutUrl: `/docs/${lang}/about`, | ||||
| 			announcements: [] | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		hasUnreadNotification(): boolean { | ||||
| 			return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadNotification; | ||||
| 		}, | ||||
|  | ||||
| 		hasUnreadMessagingMessage(): boolean { | ||||
| 			return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadMessagingMessage; | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		(this as any).os.getMeta().then(meta => { | ||||
| 			this.announcements = meta.broadcasts; | ||||
| 		}); | ||||
|  | ||||
| 		if (this.$store.getters.isSignedIn) { | ||||
| 			this.connection = (this as any).os.stream.getConnection(); | ||||
| 			this.connectionId = (this as any).os.stream.use(); | ||||
| @@ -71,6 +86,7 @@ export default Vue.extend({ | ||||
| 			this.connection.on('reversi_no_invites', this.onReversiNoInvites); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	beforeDestroy() { | ||||
| 		if (this.$store.getters.isSignedIn) { | ||||
| 			this.connection.off('reversi_invited', this.onReversiInvited); | ||||
| @@ -78,18 +94,22 @@ export default Vue.extend({ | ||||
| 			(this as any).os.stream.dispose(this.connectionId); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		search() { | ||||
| 			const query = window.prompt('%i18n:@search%'); | ||||
| 			if (query == null || query == '') return; | ||||
| 			this.$router.push(`/search?q=${encodeURIComponent(query)}`); | ||||
| 		}, | ||||
|  | ||||
| 		onReversiInvited() { | ||||
| 			this.hasGameInvitation = true; | ||||
| 		}, | ||||
|  | ||||
| 		onReversiNoInvites() { | ||||
| 			this.hasGameInvitation = false; | ||||
| 		}, | ||||
|  | ||||
| 		dark() { | ||||
| 			this.$store.commit('device/set', { | ||||
| 				key: 'darkmode', | ||||
| @@ -204,6 +224,17 @@ root(isDark) | ||||
| 					color $color | ||||
| 					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 | ||||
| 		margin 0 0 8px 0 | ||||
| 		padding 1em 0 | ||||
|   | ||||
| @@ -41,7 +41,7 @@ export default Vue.extend({ | ||||
| 			(this.$refs.timeline as any).init(() => new Promise((res, rej) => { | ||||
| 				(this as any).api('users/notes', { | ||||
| 					userId: this.user.id, | ||||
| 					withMedia: this.withMedia, | ||||
| 					withFiles: this.withMedia, | ||||
| 					limit: fetchLimit + 1 | ||||
| 				}).then(notes => { | ||||
| 					if (notes.length == fetchLimit + 1) { | ||||
| @@ -62,7 +62,7 @@ export default Vue.extend({ | ||||
|  | ||||
| 			const promise = (this as any).api('users/notes', { | ||||
| 				userId: this.user.id, | ||||
| 				withMedia: this.withMedia, | ||||
| 				withFiles: this.withMedia, | ||||
| 				limit: fetchLimit + 1, | ||||
| 				untilId: (this.$refs.timeline as any).tail().id | ||||
| 			}); | ||||
|   | ||||
| @@ -10,46 +10,62 @@ | ||||
| 			<ui-card> | ||||
| 				<div slot="title">%fa:palette% %i18n:@design%</div> | ||||
|  | ||||
| 				<section> | ||||
| 					<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.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.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> | ||||
| 				</section> | ||||
|  | ||||
| 				<section> | ||||
| 					<header>%i18n:@timeline%</header> | ||||
| 					<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.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.showLocalRenotes" @change="onChangeShowLocalRenotes">%i18n:@show-local-renotes%</ui-switch> | ||||
| 					</div> | ||||
| 				</section> | ||||
|  | ||||
| 				<div> | ||||
| 					<div>%i18n:@post-style%</div> | ||||
| 				<section> | ||||
| 					<header>%i18n:@post-style%</header> | ||||
| 					<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> | ||||
| 				</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> | ||||
| 				<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.disableViaMobile" @change="onChangeDisableViaMobile">%i18n:@disable-via-mobile%</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="lightmode">%i18n:@i-am-under-limited-internet%</ui-switch> | ||||
| 				</section> | ||||
| 			</ui-card> | ||||
|  | ||||
| 			<ui-card> | ||||
| 				<div slot="title">%fa:volume-up% %i18n:@sound%</div> | ||||
|  | ||||
| 				<section> | ||||
| 					<ui-switch v-model="enableSounds">%i18n:@enable-sounds%</ui-switch> | ||||
| 				</section> | ||||
| 			</ui-card> | ||||
|  | ||||
| 			<ui-card> | ||||
| 				<div slot="title">%fa:language% %i18n:@lang%</div> | ||||
|  | ||||
| 				<section class="fit-top"> | ||||
| 					<ui-select v-model="lang" placeholder="%i18n:@auto%"> | ||||
| 						<optgroup label="%i18n:@recommended%"> | ||||
| 							<option value="">%i18n:@auto%</option> | ||||
| @@ -60,22 +76,26 @@ | ||||
| 						</optgroup> | ||||
| 					</ui-select> | ||||
| 					<span>%fa:info-circle% %i18n:@lang-tip%</span> | ||||
| 				</section> | ||||
| 			</ui-card> | ||||
|  | ||||
| 			<ui-card> | ||||
| 				<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> | ||||
| 						<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> | ||||
| 						<a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="$store.state.i.twitter">%i18n:@twitter-disconnect%</a> | ||||
| 					</p> | ||||
| 				</section> | ||||
| 			</ui-card> | ||||
|  | ||||
| 			<ui-card> | ||||
| 				<div slot="title">%fa:sync-alt% %i18n:@update%</div> | ||||
|  | ||||
| 				<section> | ||||
| 					<div>%i18n:@version% <i>{{ version }}</i></div> | ||||
| 					<template v-if="latestVersion !== undefined"> | ||||
| 						<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-else>%i18n:@check-for-updates%</template> | ||||
| 					</ui-button> | ||||
| 				</section> | ||||
| 			</ui-card> | ||||
| 		</div> | ||||
|  | ||||
| @@ -134,6 +155,11 @@ export default Vue.extend({ | ||||
| 			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: { | ||||
| 			get() { return this.$store.state.device.lightmode; }, | ||||
| 			set(value) { this.$store.commit('device/set', { key: 'lightmode', value }); } | ||||
| @@ -273,7 +299,7 @@ export default Vue.extend({ | ||||
| <style lang="stylus" scoped> | ||||
| root(isDark) | ||||
| 	margin 0 auto | ||||
| 	max-width 500px | ||||
| 	max-width 600px | ||||
| 	width 100% | ||||
|  | ||||
| 	> .signin-as | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| <ui-card> | ||||
| 	<div slot="title">%fa:user% %i18n:@title%</div> | ||||
|  | ||||
| 	<section class="fit-top"> | ||||
| 		<ui-form :disabled="saving"> | ||||
| 			<ui-input v-model="name" :max="30"> | ||||
| 				<span>%i18n:@name%</span> | ||||
| @@ -43,6 +44,7 @@ | ||||
|  | ||||
| 			<ui-button @click="save">%i18n:@save%</ui-button> | ||||
| 		</ui-form> | ||||
| 	</section> | ||||
| </ui-card> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -16,7 +16,7 @@ | ||||
| 				</div> | ||||
| 				<div class="title"> | ||||
| 					<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> | ||||
| 				</div> | ||||
| 				<div class="description"> | ||||
|   | ||||
| @@ -26,7 +26,7 @@ export default Vue.extend({ | ||||
| 	mounted() { | ||||
| 		(this as any).api('users/notes', { | ||||
| 			userId: this.user.id, | ||||
| 			withMedia: true, | ||||
| 			withFiles: true, | ||||
| 			limit: 6 | ||||
| 		}).then(notes => { | ||||
| 			notes.forEach(note => { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="welcome"> | ||||
| <div class="wgwfgvvimdjvhjfwxropcwksnzftjqes"> | ||||
| 	<div> | ||||
| 		<img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name"> | ||||
| 		<p class="host">{{ host }}</p> | ||||
| @@ -17,10 +17,19 @@ | ||||
| 		<div class="hashtags"> | ||||
| 			<router-link v-for="tag in tags" :key="tag" :to="`/tags/${ tag }`" :title="tag">#{{ tag }}</router-link> | ||||
| 		</div> | ||||
| 		<div class="photos"> | ||||
| 			<div v-for="photo in photos" :style="`background-image: url(${photo.thumbnailUrl})`"></div> | ||||
| 		</div> | ||||
| 		<div class="stats" v-if="stats"> | ||||
| 			<span>%fa:user% {{ stats.originalUsersCount | number }}</span> | ||||
| 			<span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span> | ||||
| 		</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> | ||||
| 			<small>{{ copyright }}</small> | ||||
| 		</footer> | ||||
| @@ -41,13 +50,16 @@ export default Vue.extend({ | ||||
| 			host, | ||||
| 			name: 'Misskey', | ||||
| 			description: '', | ||||
| 			tags: [] | ||||
| 			tags: [], | ||||
| 			photos: [], | ||||
| 			announcements: [] | ||||
| 		}; | ||||
| 	}, | ||||
| 	created() { | ||||
| 		(this as any).os.getMeta().then(meta => { | ||||
| 			this.name = meta.name; | ||||
| 			this.description = meta.description; | ||||
| 			this.announcements = meta.broadcasts; | ||||
| 		}); | ||||
|  | ||||
| 		(this as any).api('stats').then(stats => { | ||||
| @@ -57,12 +69,26 @@ export default Vue.extend({ | ||||
| 		(this as any).api('hashtags/trend').then(stats => { | ||||
| 			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> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .welcome | ||||
| root(isDark) | ||||
| 	text-align center | ||||
| 	//background #fff | ||||
|  | ||||
| @@ -145,6 +171,19 @@ export default Vue.extend({ | ||||
| 			> * | ||||
| 				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 | ||||
| 			margin 16px 0 | ||||
| 			padding 8px | ||||
| @@ -156,6 +195,20 @@ export default Vue.extend({ | ||||
| 			> * | ||||
| 				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 | ||||
| 			text-align center | ||||
| 			color #444 | ||||
| @@ -165,4 +218,10 @@ export default Vue.extend({ | ||||
| 				margin 16px 0 0 0 | ||||
| 				opacity 0.7 | ||||
|  | ||||
| .wgwfgvvimdjvhjfwxropcwksnzftjqes[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
| .wgwfgvvimdjvhjfwxropcwksnzftjqes:not([data-darkmode]) | ||||
| 	root(false) | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -43,7 +43,8 @@ const defaultDeviceSettings = { | ||||
| 	debug: false, | ||||
| 	lightmode: false, | ||||
| 	loadRawImages: false, | ||||
| 	postStyle: 'standard' | ||||
| 	postStyle: 'standard', | ||||
| 	mobileNotificationPosition: 'bottom' | ||||
| }; | ||||
|  | ||||
| export default (os: MiOS) => new Vuex.Store({ | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import { URL } from 'url'; | ||||
| import * as yaml from 'js-yaml'; | ||||
| import { Source, Mixin } from './types'; | ||||
| import isUrl = require('is-url'); | ||||
| const pkg = require('../../package.json'); | ||||
|  | ||||
| /** | ||||
|  * Path of configuration directory | ||||
| @@ -43,6 +44,7 @@ export default function load() { | ||||
| 	mixin.stats_url = `${mixin.scheme}://${mixin.host}/stats`; | ||||
| 	mixin.status_url = `${mixin.scheme}://${mixin.host}/status`; | ||||
| 	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.remoteDriveCapacityMb == null) config.remoteDriveCapacityMb = 8; | ||||
|   | ||||
| @@ -114,6 +114,7 @@ export type Mixin = { | ||||
| 	status_url: string; | ||||
| 	dev_url: string; | ||||
| 	drive_url: string; | ||||
| 	user_agent: string; | ||||
| }; | ||||
|  | ||||
| export type Config = Source & Mixin; | ||||
|   | ||||
| @@ -4,6 +4,12 @@ import config from '../config'; | ||||
| const index = { | ||||
| 	settings: { | ||||
| 		analysis: { | ||||
| 			normalizer: { | ||||
| 				lowercase_normalizer: { | ||||
| 					type: 'custom', | ||||
| 					filter: ['lowercase'] | ||||
| 				} | ||||
| 			}, | ||||
| 			analyzer: { | ||||
| 				bigram: { | ||||
| 					tokenizer: 'bigram_tokenizer' | ||||
| @@ -24,7 +30,8 @@ const index = { | ||||
| 				text: { | ||||
| 					type: 'text', | ||||
| 					index: true, | ||||
| 					analyzer: 'bigram' | ||||
| 					analyzer: 'bigram', | ||||
| 					normalizer: 'lowercase_normalizer' | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
| @@ -33,19 +33,19 @@ props: | ||||
|       ja-JP: "投稿の本文" | ||||
|       en-US: "The text of this note" | ||||
|  | ||||
|   mediaIds: | ||||
|   fileIds: | ||||
|     type: "id(DriveFile)[]" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja-JP: "添付されているメディアのID (なければレスポンスでは空配列)" | ||||
|       en-US: "The IDs of the attached media (empty array for response if no media is attached)" | ||||
|       ja-JP: "添付されているファイルのID (なければレスポンスでは空配列)" | ||||
|       en-US: "The IDs of the attached files (empty array for response if no files is attached)" | ||||
|  | ||||
|   media: | ||||
|   files: | ||||
|     type: "entity(DriveFile)[]" | ||||
|     optional: true | ||||
|     desc: | ||||
|       ja-JP: "添付されているメディア" | ||||
|       en-US: "The attached media" | ||||
|       ja-JP: "添付されているファイル" | ||||
|       en-US: "The attached files" | ||||
|  | ||||
|   userId: | ||||
|     type: "id(User)" | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import { count, countIf } from "../../prelude/array"; | ||||
|  | ||||
| // MISSKEY REVERSI ENGINE | ||||
|  | ||||
| /** | ||||
| @@ -88,8 +90,8 @@ export default class Reversi { | ||||
| 		//#endregion | ||||
|  | ||||
| 		// ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある | ||||
| 		if (this.canPutSomewhere(BLACK).length == 0) { | ||||
| 			if (this.canPutSomewhere(WHITE).length == 0) { | ||||
| 		if (!this.canPutSomewhere(BLACK)) { | ||||
| 			if (!this.canPutSomewhere(WHITE)) { | ||||
| 				this.turn = null; | ||||
| 			} else { | ||||
| 				this.turn = WHITE; | ||||
| @@ -101,14 +103,14 @@ export default class Reversi { | ||||
| 	 * 黒石の数 | ||||
| 	 */ | ||||
| 	public get blackCount() { | ||||
| 		return this.board.filter(x => x === BLACK).length; | ||||
| 		return count(BLACK, this.board); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 白石の数 | ||||
| 	 */ | ||||
| 	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() { | ||||
| 		// ターン計算 | ||||
| 		if (this.canPutSomewhere(!this.prevColor).length > 0) { | ||||
| 		if (this.canPutSomewhere(!this.prevColor)) { | ||||
| 			this.turn = !this.prevColor; | ||||
| 		} else if (this.canPutSomewhere(this.prevColor).length > 0) { | ||||
| 		} else if (this.canPutSomewhere(this.prevColor)) { | ||||
| 			this.turn = this.prevColor; | ||||
| 		} else { | ||||
| 			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)); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 打つことができる場所があるかどうかを取得します | ||||
| 	 */ | ||||
| 	public canPutSomewhere(color: Color): boolean { | ||||
| 		return this.puttablePlaces(color).length > 0; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 指定のマスに石を打つことができるかどうかを取得します | ||||
| 	 * @param color 自分の色 | ||||
|   | ||||
| @@ -4,10 +4,7 @@ const { JSDOM } = jsdom; | ||||
| import config from '../config'; | ||||
| import { INote } from '../models/note'; | ||||
| import { TextElement } from './parse'; | ||||
|  | ||||
| function intersperse<T>(sep: T, xs: T[]): T[] { | ||||
| 	return [].concat(...xs.map(x => [sep, x])).slice(1); | ||||
| } | ||||
| import { intersperse } from '../prelude/array'; | ||||
|  | ||||
| const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers: INote['mentionedRemoteUsers']) => void } = { | ||||
| 	bold({ document }, { bold }) { | ||||
|   | ||||
| @@ -16,9 +16,9 @@ const summarize = (note: any): string => { | ||||
| 	// 本文 | ||||
| 	summary += note.text ? note.text : ''; | ||||
|  | ||||
| 	// メディアが添付されているとき | ||||
| 	if (note.media.length != 0) { | ||||
| 		summary += ` (${note.media.length}つのメディア)`; | ||||
| 	// ファイルが添付されているとき | ||||
| 	if (note.files.length != 0) { | ||||
| 		summary += ` (${note.files.length}つのファイル)`; | ||||
| 	} | ||||
|  | ||||
| 	// 投票が添付されているとき | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { INote } from '../models/note'; | ||||
|  | ||||
| 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をすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await Note.find({ mediaIds: d._id }) | ||||
| 		await Note.find({ fileIds: d._id }) | ||||
| 	).map(x => deleteNote(x))); | ||||
|  | ||||
| 	// このDriveFileを添付しているMessagingMessageをすべて削除 | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import { IUser, pack as packUser } from './user'; | ||||
| import { pack as packApp } from './app'; | ||||
| import PollVote, { deletePollVote } from './poll-vote'; | ||||
| 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 NoteReaction from './note-reaction'; | ||||
| import Favorite, { deleteFavorite } from './favorite'; | ||||
| @@ -17,9 +17,20 @@ const Note = db.get<INote>('notes'); | ||||
| Note.createIndex('uri', { sparse: true, unique: true }); | ||||
| Note.createIndex('userId'); | ||||
| Note.createIndex('tagsLower'); | ||||
| Note.createIndex('_files.contentType'); | ||||
| Note.createIndex({ | ||||
| 	createdAt: -1 | ||||
| }); | ||||
|  | ||||
| // 後方互換性のため | ||||
| Note.update({}, { | ||||
| 	$rename: { | ||||
| 		mediaIds: 'fileIds' | ||||
| 	} | ||||
| }, { | ||||
| 	multi: true | ||||
| }); | ||||
|  | ||||
| export default Note; | ||||
|  | ||||
| export function isValidText(text: string): boolean { | ||||
| @@ -34,7 +45,7 @@ export type INote = { | ||||
| 	_id: mongo.ObjectID; | ||||
| 	createdAt: Date; | ||||
| 	deletedAt: Date; | ||||
| 	mediaIds: mongo.ObjectID[]; | ||||
| 	fileIds: mongo.ObjectID[]; | ||||
| 	replyId: mongo.ObjectID; | ||||
| 	renoteId: mongo.ObjectID; | ||||
| 	poll: { | ||||
| @@ -92,6 +103,7 @@ export type INote = { | ||||
| 		inbox?: string; | ||||
| 	}; | ||||
| 	_replyIds?: mongo.ObjectID[]; | ||||
| 	_files?: IDriveFile[]; | ||||
| }; | ||||
|  | ||||
| /** | ||||
| @@ -271,11 +283,15 @@ export const pack = async ( | ||||
| 		_note.app = packApp(_note.appId); | ||||
| 	} | ||||
|  | ||||
| 	// Populate media | ||||
| 	_note.media = hide ? [] : Promise.all(_note.mediaIds.map((fileId: mongo.ObjectID) => | ||||
| 	// Populate files | ||||
| 	_note.files = hide ? [] : Promise.all(_note.fileIds.map((fileId: mongo.ObjectID) => | ||||
| 		packFile(fileId) | ||||
| 	)); | ||||
|  | ||||
| 	// 後方互換性のため | ||||
| 	_note.mediaIds = _note.fileIds; | ||||
| 	_note.media = _note.files; | ||||
|  | ||||
| 	// When requested a detailed note data | ||||
| 	if (opts.detail) { | ||||
| 		//#region 重いので廃止 | ||||
| @@ -344,7 +360,7 @@ export const pack = async ( | ||||
| 	} | ||||
|  | ||||
| 	if (hide) { | ||||
| 		_note.mediaIds = []; | ||||
| 		_note.fileIds = []; | ||||
| 		_note.text = null; | ||||
| 		_note.poll = null; | ||||
| 		_note.cw = null; | ||||
|   | ||||
| @@ -432,10 +432,10 @@ export const pack = ( | ||||
| 				followerId: _user.id, | ||||
| 				followeeId: meId | ||||
| 			}), | ||||
| 			_user.isLocked ? FollowRequest.findOne({ | ||||
| 			FollowRequest.findOne({ | ||||
| 				followerId: meId, | ||||
| 				followeeId: _user.id | ||||
| 			}) : Promise.resolve(null), | ||||
| 			}), | ||||
| 			FollowRequest.findOne({ | ||||
| 				followerId: _user.id, | ||||
| 				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 | ||||
|  | ||||
| 	// 添付メディア | ||||
| 	// 添付ファイル | ||||
| 	// TODO: attachmentは必ずしもImageではない | ||||
| 	// TODO: attachmentは必ずしも配列ではない | ||||
| 	// Noteがsensitiveなら添付もsensitiveにする | ||||
| 	const media = note.attachment | ||||
| 	const files = note.attachment | ||||
| 		.map(attach => attach.sensitive = note.sensitive) | ||||
| 		? 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, { | ||||
| 		createdAt: new Date(note.published), | ||||
| 		media, | ||||
| 		files: files, | ||||
| 		reply, | ||||
| 		renote: undefined, | ||||
| 		cw: note.summary, | ||||
|   | ||||
| @@ -8,8 +8,8 @@ import User from '../../../models/user'; | ||||
| import toHtml from '../misc/get-note-html'; | ||||
|  | ||||
| export default async function renderNote(note: INote, dive = true): Promise<any> { | ||||
| 	const promisedFiles: Promise<IDriveFile[]> = note.mediaIds | ||||
| 		? DriveFile.find({ _id: { $in: note.mediaIds } }) | ||||
| 	const promisedFiles: Promise<IDriveFile[]> = note.fileIds | ||||
| 		? DriveFile.find({ _id: { $in: note.fileIds } }) | ||||
| 		: Promise.resolve([]); | ||||
|  | ||||
| 	let inReplyTo; | ||||
|   | ||||
| @@ -27,6 +27,7 @@ export default (user: ILocalUser, url: string, object: any) => new Promise((reso | ||||
| 		method: 'POST', | ||||
| 		path: pathname + search, | ||||
| 		headers: { | ||||
| 			'User-Agent': config.user_agent, | ||||
| 			'Content-Type': 'application/activity+json', | ||||
| 			'Digest': `SHA-256=${hash}` | ||||
| 		} | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import * as request from 'request-promise-native'; | ||||
| import * as debug from 'debug'; | ||||
| import { IObject } from './type'; | ||||
| //import config from '../../config'; | ||||
| import config from '../../config'; | ||||
|  | ||||
| const log = debug('misskey:activitypub:resolver'); | ||||
|  | ||||
| @@ -51,6 +51,7 @@ export default class Resolver { | ||||
| 		const object = await request({ | ||||
| 			url: value, | ||||
| 			headers: { | ||||
| 				'User-Agent': config.user_agent, | ||||
| 				Accept: 'application/activity+json, application/ld+json' | ||||
| 			}, | ||||
| 			json: true | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import { setResponseType } from '../activitypub'; | ||||
|  | ||||
| import Note from '../../models/note'; | ||||
| import renderNote from '../../remote/activitypub/renderer/note'; | ||||
| import { countIf } from '../../prelude/array'; | ||||
|  | ||||
| export default async (ctx: Router.IRouterContext) => { | ||||
| 	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'; | ||||
|  | ||||
| 	// 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; | ||||
| 		return; | ||||
| 	} | ||||
| @@ -58,7 +59,7 @@ export default async (ctx: Router.IRouterContext) => { | ||||
| 				$or: [{ | ||||
| 					text: { $ne: null } | ||||
| 				}, { | ||||
| 					mediaIds: { $ne: [] } | ||||
| 					fileIds: { $ne: [] } | ||||
| 				}] | ||||
| 			}] | ||||
| 		} as any; | ||||
|   | ||||
| @@ -11,11 +11,17 @@ export const meta = { | ||||
| 	requireAdmin: true, | ||||
|  | ||||
| 	params: { | ||||
| 		broadcasts: $.arr($.obj()).optional.nullable.note({ | ||||
| 			desc: { | ||||
| 				'ja-JP': 'ブロードキャスト' | ||||
| 			} | ||||
| 		}), | ||||
|  | ||||
| 		disableRegistration: $.bool.optional.nullable.note({ | ||||
| 			desc: { | ||||
| 				'ja-JP': '招待制か否か' | ||||
| 			} | ||||
| 		}), | ||||
| 		}) | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| @@ -25,6 +31,10 @@ export default (params: any) => new Promise(async (res, rej) => { | ||||
|  | ||||
| 	const set = {} as any; | ||||
|  | ||||
| 	if (ps.broadcasts) { | ||||
| 		set.broadcasts = ps.broadcasts; | ||||
| 	} | ||||
|  | ||||
| 	if (typeof ps.disableRegistration === 'boolean') { | ||||
| 		set.disableRegistration = ps.disableRegistration; | ||||
| 	} | ||||
|   | ||||
| @@ -74,7 +74,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = | ||||
| 		createdAt: new Date(), | ||||
| 		fileId: file ? file._id : undefined, | ||||
| 		recipientId: recipient._id, | ||||
| 		text: text ? text : undefined, | ||||
| 		text: text ? text.trim() : undefined, | ||||
| 		userId: user._id, | ||||
| 		isRead: false | ||||
| 	}); | ||||
|   | ||||
| @@ -33,6 +33,7 @@ export default () => new Promise(async (res, rej) => { | ||||
| 		}, | ||||
| 		broadcasts: meta.broadcasts, | ||||
| 		disableRegistration: meta.disableRegistration, | ||||
| 		driveCapacityPerLocalUserMb: config.localDriveCapacityMb, | ||||
| 		recaptchaSitekey: config.recaptcha ? config.recaptcha.site_key : null, | ||||
| 		swPublickey: config.sw ? config.sw.public_key : null | ||||
| 	}); | ||||
|   | ||||
| @@ -1,51 +1,65 @@ | ||||
| /** | ||||
|  * Module dependencies | ||||
|  */ | ||||
| import $ from 'cafy'; import ID from '../../../misc/cafy-id'; | ||||
| 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) => { | ||||
| 	// Get 'local' parameter | ||||
| 	const [local, localErr] = $.bool.optional.get(params.local); | ||||
| 	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'); | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) throw psErr; | ||||
|  | ||||
| 	// Check if both of sinceId and untilId is specified | ||||
| 	if (sinceId && untilId) { | ||||
| 	if (ps.sinceId && ps.untilId) { | ||||
| 		return rej('cannot set sinceId and untilId'); | ||||
| 	} | ||||
|  | ||||
| @@ -56,35 +70,37 @@ export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	const query = { | ||||
| 		visibility: 'public' | ||||
| 	} as any; | ||||
| 	if (sinceId) { | ||||
| 	if (ps.sinceId) { | ||||
| 		sort._id = 1; | ||||
| 		query._id = { | ||||
| 			$gt: sinceId | ||||
| 			$gt: ps.sinceId | ||||
| 		}; | ||||
| 	} else if (untilId) { | ||||
| 	} else if (ps.untilId) { | ||||
| 		query._id = { | ||||
| 			$lt: untilId | ||||
| 			$lt: ps.untilId | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	if (local) { | ||||
| 	if (ps.local) { | ||||
| 		query['_user.host'] = null; | ||||
| 	} | ||||
|  | ||||
| 	if (reply != undefined) { | ||||
| 		query.replyId = reply ? { $exists: true, $ne: null } : null; | ||||
| 	if (ps.reply != undefined) { | ||||
| 		query.replyId = ps.reply ? { $exists: true, $ne: null } : null; | ||||
| 	} | ||||
|  | ||||
| 	if (renote != undefined) { | ||||
| 		query.renoteId = renote ? { $exists: true, $ne: null } : null; | ||||
| 	if (ps.renote != undefined) { | ||||
| 		query.renoteId = ps.renote ? { $exists: true, $ne: null } : null; | ||||
| 	} | ||||
|  | ||||
| 	if (media != undefined) { | ||||
| 		query.mediaIds = media ? { $exists: true, $ne: null } : []; | ||||
| 	const withFiles = ps.withFiles != undefined ? ps.withFiles : ps.media; | ||||
|  | ||||
| 	if (withFiles) { | ||||
| 		query.fileIds = withFiles ? { $exists: true, $ne: null } : []; | ||||
| 	} | ||||
|  | ||||
| 	if (poll != undefined) { | ||||
| 		query.poll = poll ? { $exists: true, $ne: null } : null; | ||||
| 	if (ps.poll != undefined) { | ||||
| 		query.poll = ps.poll ? { $exists: true, $ne: null } : null; | ||||
| 	} | ||||
|  | ||||
| 	// TODO | ||||
| @@ -95,7 +111,7 @@ export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	// Issue query | ||||
| 	const notes = await Note | ||||
| 		.find(query, { | ||||
| 			limit: limit, | ||||
| 			limit: ps.limit, | ||||
| 			sort: sort | ||||
| 		}); | ||||
|  | ||||
|   | ||||
| @@ -71,9 +71,15 @@ export const meta = { | ||||
| 			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({ | ||||
| 			desc: { | ||||
| 				'ja-JP': '添付するメディア' | ||||
| 				'ja-JP': '添付するファイル (このパラメータは廃止予定です。代わりに fileIds を使ってください。)' | ||||
| 			} | ||||
| 		}), | ||||
|  | ||||
| @@ -124,15 +130,16 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| 		// forEach だと途中でエラーなどがあっても return できないので | ||||
| 		// 敢えて for を使っています。 | ||||
| 		for (const mediaId of ps.mediaIds) { | ||||
| 		for (const fileId of fileIds) { | ||||
| 			// Fetch file | ||||
| 			// SELECT _id | ||||
| 			const entity = await DriveFile.findOne({ | ||||
| 				_id: mediaId, | ||||
| 				_id: fileId, | ||||
| 				'metadata.userId': user._id | ||||
| 			}); | ||||
|  | ||||
| @@ -155,7 +162,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( | ||||
|  | ||||
| 		if (renote == null) { | ||||
| 			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'); | ||||
| 		} | ||||
| 	} | ||||
| @@ -176,7 +183,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( | ||||
| 		} | ||||
|  | ||||
| 		// 返信対象が引用でないRenoteだったらエラー | ||||
| 		if (reply.renoteId && !reply.text && !reply.mediaIds) { | ||||
| 		if (reply.renoteId && !reply.text && !reply.fileIds) { | ||||
| 			return rej('cannot reply to renote'); | ||||
| 		} | ||||
| 	} | ||||
| @@ -191,13 +198,13 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( | ||||
|  | ||||
| 	// テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー | ||||
| 	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, { | ||||
| 		createdAt: new Date(), | ||||
| 		media: files, | ||||
| 		files: files, | ||||
| 		poll: ps.poll, | ||||
| 		text: ps.text, | ||||
| 		reply, | ||||
|   | ||||
| @@ -3,40 +3,50 @@ import Note from '../../../../models/note'; | ||||
| import Mute from '../../../../models/mute'; | ||||
| import { pack } from '../../../../models/note'; | ||||
| 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) => { | ||||
| 	// Get 'limit' parameter | ||||
| 	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); | ||||
| 	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'; | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) throw psErr; | ||||
|  | ||||
| 	// 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'; | ||||
| 	} | ||||
|  | ||||
| 	// Get 'mediaOnly' parameter | ||||
| 	const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly); | ||||
| 	if (mediaOnlyErr) throw 'invalid mediaOnly param'; | ||||
|  | ||||
| 	// ミュートしているユーザーを取得 | ||||
| 	const mutedUserIds = user ? (await Mute.find({ | ||||
| 		muterId: user._id | ||||
| @@ -68,27 +78,29 @@ export default async (params: any, user: ILocalUser) => { | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	if (mediaOnly) { | ||||
| 		query.mediaIds = { $exists: true, $ne: [] }; | ||||
| 	const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; | ||||
|  | ||||
| 	if (withFiles) { | ||||
| 		query.fileIds = { $exists: true, $ne: [] }; | ||||
| 	} | ||||
|  | ||||
| 	if (sinceId) { | ||||
| 	if (ps.sinceId) { | ||||
| 		sort._id = 1; | ||||
| 		query._id = { | ||||
| 			$gt: sinceId | ||||
| 			$gt: ps.sinceId | ||||
| 		}; | ||||
| 	} else if (untilId) { | ||||
| 	} else if (ps.untilId) { | ||||
| 		query._id = { | ||||
| 			$lt: untilId | ||||
| 			$lt: ps.untilId | ||||
| 		}; | ||||
| 	} else if (sinceDate) { | ||||
| 	} else if (ps.sinceDate) { | ||||
| 		sort._id = 1; | ||||
| 		query.createdAt = { | ||||
| 			$gt: new Date(sinceDate) | ||||
| 			$gt: new Date(ps.sinceDate) | ||||
| 		}; | ||||
| 	} else if (untilDate) { | ||||
| 	} else if (ps.untilDate) { | ||||
| 		query.createdAt = { | ||||
| 			$lt: new Date(untilDate) | ||||
| 			$lt: new Date(ps.untilDate) | ||||
| 		}; | ||||
| 	} | ||||
| 	//#endregion | ||||
| @@ -96,7 +108,7 @@ export default async (params: any, user: ILocalUser) => { | ||||
| 	// Issue query | ||||
| 	const timeline = await Note | ||||
| 		.find(query, { | ||||
| 			limit: limit, | ||||
| 			limit: ps.limit, | ||||
| 			sort: sort | ||||
| 		}); | ||||
|  | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user