Compare commits
	
		
			118 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 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 | ||
|   | 0fb528ddf8 | ||
|   | 14c03f226d | ||
|   | 4f0d844b43 | ||
|   | b93395fc4c | ||
|   | 34eacb7e2d | ||
|   | 0177023ead | ||
|   | a4678e45de | ||
|   | f24869625e | ||
|   | 01beb705a2 | ||
|   | ce28c70c35 | ||
|   | 5e0f5c31e7 | ||
|   | b28dd4be52 | ||
|   | 291beb45fc | ||
|   | ffb345ccb5 | ||
|   | d2abe2cd81 | ||
|   | acffc3e522 | ||
|   | 0962e62b8c | ||
|   | 91ebd310b7 | ||
|   | 2974c74b4e | ||
|   | 3d24112d2d | ||
|   | 4a977cd523 | ||
|   | 4b1886990f | ||
|   | 57a63d38aa | ||
|   | 3efffbcf22 | ||
|   | 15eaebe522 | ||
|   | eee98358ac | ||
|   | 795fc5e7bc | ||
|   | 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 | 
| @@ -47,13 +47,13 @@ Please run `node cli/migration/5.0.0` before launch. | ||||
|  | ||||
| オセロがリバーシに変更されました。 | ||||
|  | ||||
| Othello is now Reversi. | ||||
| Othello is rename to Reversi. | ||||
|  | ||||
| ### Migration | ||||
|  | ||||
| MongoDBの、`othelloGames`と`othelloMatchings`コレクションをそれぞれ`reversiGames`と`reversiMatchings`にリネームしてください。 | ||||
|  | ||||
| You need to rename `othelloGames` and `othelloMatchings` MongoDB collections to `reversiGames` and `reversiMatchings`. | ||||
| Please rename `othelloGames` and `othelloMatchings` MongoDB collections to `reversiGames` and `reversiMatchings` respectively. | ||||
|  | ||||
| 3.0.0 | ||||
| ----- | ||||
|   | ||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -42,17 +42,15 @@ Please see [Contribution guide](./CONTRIBUTING.md). | ||||
| ---------------------------------------------------------------- | ||||
| <!-- PATREON_START --> | ||||
| <table><tr> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D" alt="39ff"></td> | ||||
| <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> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=zwSu01tOtn5xTUucDZHuPsCxF2HBEMVs9ROJKTlEV_o%3D" alt="nemu"></td> | ||||
| </tr><tr> | ||||
| <td><a href="https://www.patreon.com/user?u=12378075">39ff</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=12731202">negao</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> | ||||
| @@ -67,20 +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> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12959468/c249e15aebec4424b5c0f427173671b6/1?token-time=2145916800&token-hash=lubpCEdxAkxPlpR2O6bvZ7BIh8Q4nGf-U_mE1qpjVAQ%3D" alt="fujishan"></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> | ||||
| <td><a href="https://www.patreon.com/fujishan">fujishan</a></td> | ||||
| </tr></table> | ||||
|  | ||||
| **Last updated:** Sun, 26 Aug 2018 08:55:06 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: "サーバー情報" | ||||
| @@ -1353,6 +1355,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.20.0", | ||||
| 	"clientVersion": "1.0.9240", | ||||
| 	"version": "8.26.0", | ||||
| 	"clientVersion": "1.0.9358", | ||||
| 	"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.0", | ||||
| 		"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", | ||||
|   | ||||
| @@ -80,7 +80,7 @@ export default Vue.extend({ | ||||
| 		accepted() { | ||||
| 			this.state = 'accepted'; | ||||
| 			if (this.session.app.callbackUrl) { | ||||
| 				location.href = this.session.app.callbackUrl + '?token=' + this.session.token; | ||||
| 				location.href = `${this.session.app.callbackUrl}?token=${this.session.token}`; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -94,7 +94,7 @@ | ||||
|  | ||||
| 	// Get salt query | ||||
| 	const salt = localStorage.getItem('salt') | ||||
| 		? '?salt=' + localStorage.getItem('salt') | ||||
| 		? `?salt=${localStorage.getItem('salt')}` | ||||
| 		: ''; | ||||
|  | ||||
| 	// Load an app script | ||||
|   | ||||
| @@ -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 | ||||
| 		}); | ||||
| 		} : {}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -44,11 +44,11 @@ export default class Connection extends EventEmitter { | ||||
|  | ||||
| 		const query = params | ||||
| 			? Object.keys(params) | ||||
| 				.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k])) | ||||
| 				.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`) | ||||
| 				.join('&') | ||||
| 			: null; | ||||
|  | ||||
| 		this.socket = new ReconnectingWebsocket(`${wsUrl}/${endpoint}${query ? '?' + query : ''}`); | ||||
| 		this.socket = new ReconnectingWebsocket(`${wsUrl}/${endpoint}${query ? `?${query}` : ''}`); | ||||
| 		this.socket.addEventListener('open', this.onOpen); | ||||
| 		this.socket.addEventListener('close', this.onClose); | ||||
| 		this.socket.addEventListener('message', this.onMessage); | ||||
|   | ||||
| @@ -125,7 +125,7 @@ export default Vue.extend({ | ||||
| 			} | ||||
|  | ||||
| 			if (this.type == 'user') { | ||||
| 				const cacheKey = 'autocomplete:user:' + this.q; | ||||
| 				const cacheKey = `autocomplete:user:${this.q}`; | ||||
| 				const cache = sessionStorage.getItem(cacheKey); | ||||
| 				if (cache) { | ||||
| 					const users = JSON.parse(cache); | ||||
| @@ -148,7 +148,7 @@ export default Vue.extend({ | ||||
| 					this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]'); | ||||
| 					this.fetching = false; | ||||
| 				} else { | ||||
| 					const cacheKey = 'autocomplete:hashtag:' + this.q; | ||||
| 					const cacheKey = `autocomplete:hashtag:${this.q}`; | ||||
| 					const cache = sessionStorage.getItem(cacheKey); | ||||
| 					if (cache) { | ||||
| 						const hashtags = JSON.parse(cache); | ||||
|   | ||||
| @@ -57,7 +57,7 @@ export default Vue.extend({ | ||||
| 		} | ||||
|  | ||||
| 		// Check internet connection | ||||
| 		fetch('https://google.com?rand=' + Math.random(), { | ||||
| 		fetch(`https://google.com?rand=${Math.random()}`, { | ||||
| 			mode: 'no-cors' | ||||
| 		}).then(() => { | ||||
| 			this.internet = true; | ||||
|   | ||||
| @@ -159,11 +159,9 @@ export default Vue.extend({ | ||||
| 				canPutEverywhere: this.game.settings.canPutEverywhere, | ||||
| 				loopedBoard: this.game.settings.loopedBoard | ||||
| 			}); | ||||
| 			this.logs.forEach((log, i) => { | ||||
| 				if (i < v) { | ||||
| 					this.o.put(log.color, log.pos); | ||||
| 				} | ||||
| 			}); | ||||
| 			for (const log of this.logs.slice(0, v)) { | ||||
| 				this.o.put(log.color, log.pos); | ||||
| 			} | ||||
| 			this.$forceUpdate(); | ||||
| 		} | ||||
| 	}, | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -205,17 +205,8 @@ export default Vue.component('misskey-flavored-markdown', { | ||||
| 			} | ||||
| 		})); | ||||
|  | ||||
| 		const _els = []; | ||||
| 		els.forEach((el, i) => { | ||||
| 			if (el.tag == 'br') { | ||||
| 				if (!['div', 'pre'].includes(els[i - 1].tag)) { | ||||
| 					_els.push(el); | ||||
| 				} | ||||
| 			} else { | ||||
| 				_els.push(el); | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		// el.tag === 'br' のとき i !== 0 が保証されるため、短絡評価により els[i - 1] は配列外参照しない | ||||
| 		const _els = els.filter((el, i) => !(el.tag === 'br' && ['div', 'pre'].includes(els[i - 1].tag))); | ||||
| 		return createElement('span', _els); | ||||
| 	} | ||||
| }); | ||||
|   | ||||
| @@ -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,19 +24,34 @@ 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) | ||||
|  | ||||
| 	@media (min-width 500px) | ||||
| 		padding 32px | ||||
|  | ||||
| 	> header | ||||
| 		font-weight normal | ||||
| 		font-size 24px | ||||
| 		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 | ||||
| 			margin-bottom 16px | ||||
| 			font-weight bold | ||||
| 			color isDark ? #fff : #444 | ||||
|  | ||||
| .ui-card[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -170,7 +170,7 @@ export default Vue.extend({ | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		fetch('/url?url=' + encodeURIComponent(this.url)).then(res => { | ||||
| 		fetch(`/url?url=${encodeURIComponent(this.url)}`).then(res => { | ||||
| 			res.json().then(info => { | ||||
| 				if (info.url == null) return; | ||||
| 				this.title = info.title; | ||||
|   | ||||
| @@ -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; | ||||
| @@ -49,13 +64,20 @@ export default Vue.extend({ | ||||
| 				reply: false, | ||||
| 				renote: false, | ||||
| 				media: false, | ||||
| 				poll: false, | ||||
| 				bot: 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> | ||||
|   | ||||
| @@ -191,7 +191,7 @@ class Autocomplete { | ||||
| 			const acct = renderAcct(value); | ||||
|  | ||||
| 			// 挿入 | ||||
| 			this.text = trimmedBefore + '@' + acct + ' ' + after; | ||||
| 			this.text = `${trimmedBefore}@${acct} ${after}`; | ||||
|  | ||||
| 			// キャレットを戻す | ||||
| 			this.vm.$nextTick(() => { | ||||
| @@ -207,7 +207,7 @@ class Autocomplete { | ||||
| 			const after = source.substr(caret); | ||||
|  | ||||
| 			// 挿入 | ||||
| 			this.text = trimmedBefore + '#' + value + ' ' + after; | ||||
| 			this.text = `${trimmedBefore}#${value} ${after}`; | ||||
|  | ||||
| 			// キャレットを戻す | ||||
| 			this.vm.$nextTick(() => { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| Vue.filter('notePage', note => { | ||||
| 	return '/notes/' + note.id; | ||||
| 	return `/notes/${note.id}`; | ||||
| }); | ||||
|   | ||||
| @@ -11,5 +11,5 @@ Vue.filter('userName', user => { | ||||
| }); | ||||
|  | ||||
| Vue.filter('userPage', (user, path?) => { | ||||
| 	return '/@' + Vue.filter('acct')(user) + (path ? '/' + path : ''); | ||||
| 	return `/@${Vue.filter('acct')(user)}${(path ? `/${path}` : '')}`; | ||||
| }); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <div class="syxhndwprovvuqhmyvveewmbqayniwkv" v-if="!fetching" :data-darkmode="$store.state.device.darkmode"> | ||||
| 	<div class="signed-in-as" v-html="'%i18n:@signed-in-as%'.replace('{}', '<b>' + myName + '</b>')"></div> | ||||
| 	<div class="signed-in-as" v-html="'%i18n:@signed-in-as%'.replace('{}', `<b>${myName}`)"></div> | ||||
|  | ||||
| 	<main> | ||||
| 		<div class="banner" :style="bannerStyle"></div> | ||||
| @@ -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> | ||||
|   | ||||
| @@ -163,7 +163,7 @@ export default Vue.extend({ | ||||
| 							}); | ||||
| 							break; | ||||
| 						default: | ||||
| 							alert('%i18n:@unhandled-error% ' + err); | ||||
| 							alert(`%i18n:@unhandled-error% ${err}`); | ||||
| 					} | ||||
| 				}); | ||||
| 			} | ||||
|   | ||||
| @@ -323,7 +323,7 @@ export default Vue.extend({ | ||||
| 							}); | ||||
| 							break; | ||||
| 						default: | ||||
| 							alert('%i18n:@unhandled-error% ' + err); | ||||
| 							alert(`%i18n:@unhandled-error% ${err}`); | ||||
| 					} | ||||
| 				}); | ||||
| 			} | ||||
| @@ -404,7 +404,7 @@ export default Vue.extend({ | ||||
| 					folder: folder | ||||
| 				}); | ||||
| 			} else { | ||||
| 				window.open(url + '/i/drive/folder/' + folder.id, | ||||
| 				window.open(`${url}/i/drive/folder/${folder.id}`, | ||||
| 					'drive_window', | ||||
| 					'height=500, width=800'); | ||||
| 			} | ||||
|   | ||||
| @@ -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 | ||||
| 						}); | ||||
|   | ||||
| @@ -48,7 +48,7 @@ export default Vue.extend({ | ||||
| 			const mouseY = e.clientY - rect.top; | ||||
| 			const xp = mouseX / this.$el.offsetWidth * 100; | ||||
| 			const yp = mouseY / this.$el.offsetHeight * 100; | ||||
| 			this.$el.style.backgroundPosition = xp + '% ' + yp + '%'; | ||||
| 			this.$el.style.backgroundPosition = `${xp}% ${yp}%`; | ||||
| 			this.$el.style.backgroundImage = `url("${this.image.url}")`; | ||||
| 		}, | ||||
|  | ||||
|   | ||||
| @@ -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"> | ||||
| @@ -110,9 +110,9 @@ export default Vue.extend({ | ||||
| 	computed: { | ||||
| 		draftId(): string { | ||||
| 			return this.renote | ||||
| 				? 'renote:' + this.renote.id | ||||
| 				? `renote:${this.renote.id}` | ||||
| 				: this.reply | ||||
| 					? 'reply:' + this.reply.id | ||||
| 					? `reply:${this.reply.id}` | ||||
| 					: 'note'; | ||||
| 		}, | ||||
|  | ||||
| @@ -313,7 +313,7 @@ export default Vue.extend({ | ||||
| 				this.geo = pos.coords; | ||||
| 				this.$emit('geo-attached', this.geo); | ||||
| 			}, err => { | ||||
| 				alert('%i18n:@error%: ' + err.message); | ||||
| 				alert(`%i18n:@error%: ${err.message}`); | ||||
| 			}, { | ||||
| 					enableHighAccuracy: true | ||||
| 				}); | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -31,7 +31,7 @@ export default Vue.extend({ | ||||
| 			const title = folder.name + ' | %i18n:@title%'; | ||||
|  | ||||
| 			// Rewrite URL | ||||
| 			history.pushState(null, title, '/i/drive/folder/' + folder.id); | ||||
| 			history.pushState(null, title, `/i/drive/folder/${folder.id}`); | ||||
|  | ||||
| 			document.title = title; | ||||
| 		} | ||||
|   | ||||
| @@ -16,10 +16,10 @@ export default Vue.extend({ | ||||
| 	methods: { | ||||
| 		nav(game, actualNav) { | ||||
| 			if (actualNav) { | ||||
| 				this.$router.push('/reversi/' + game.id); | ||||
| 				this.$router.push(`/reversi/${game.id}`); | ||||
| 			} else { | ||||
| 				// TODO: https://github.com/vuejs/vue-router/issues/703 | ||||
| 				this.$router.push('/reversi/' + game.id); | ||||
| 				this.$router.push(`/reversi/${game.id}`); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -46,7 +46,7 @@ export default Vue.extend({ | ||||
| 				this.user = user; | ||||
| 				this.fetching = false; | ||||
|  | ||||
| 				document.title = 'メッセージ: ' + getUserName(this.user); | ||||
| 				document.title = `メッセージ: ${getUserName(this.user)}`; | ||||
|  | ||||
| 				Progress.done(); | ||||
| 			}); | ||||
|   | ||||
| @@ -1,46 +1,60 @@ | ||||
| <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"> | ||||
| 			<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> | ||||
| 				</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> | ||||
|  | ||||
| 			<p class="desc" v-html="description || '%i18n:common.about%'"></p> | ||||
|  | ||||
| 			<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="broadcasts block"> | ||||
| 			<div v-for="broadcast in broadcasts"> | ||||
| 				<h1 v-html="broadcast.title"></h1> | ||||
| 				<div v-html="broadcast.text"></div> | ||||
| 			</div> | ||||
| 		</div> | ||||
|  | ||||
| 		<div class="nav block"> | ||||
| 			<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 class="side"> | ||||
| 			<mk-trends class="trends block"/> | ||||
|  | ||||
| 			<mk-welcome-timeline class="tl block" :max="20"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
|  | ||||
| 	<modal name="signup" width="500px" height="auto" scrollable> | ||||
| 		<header :class="$style.signupFormHeader">%i18n:@signup%</header> | ||||
| 		<mk-signup :class="$style.signupForm"/> | ||||
| 	<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,37 +71,22 @@ export default Vue.extend({ | ||||
| 			host, | ||||
| 			name: 'Misskey', | ||||
| 			description: '', | ||||
| 			pointerInterval: null, | ||||
| 			tags: [] | ||||
| 			broadcasts: [] | ||||
| 		}; | ||||
| 	}, | ||||
| 	created() { | ||||
| 		(this as any).os.getMeta().then(meta => { | ||||
| 			this.name = meta.name; | ||||
| 			this.description = meta.description; | ||||
| 			this.broadcasts = 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); | ||||
| 		}); | ||||
| 	}, | ||||
| 	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'); | ||||
| 		}, | ||||
| @@ -104,11 +103,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,122 +145,87 @@ export default Vue.extend({ | ||||
| root(isDark) | ||||
| 	display flex | ||||
| 	min-height 100vh | ||||
| 	//background-color #00070F | ||||
| 	//background-image url('/assets/bg.jpg') | ||||
| 	//background-position center | ||||
| 	//background-size cover | ||||
|  | ||||
| 	> .pointer | ||||
| 		display block | ||||
| 	> .forkit | ||||
| 		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 | ||||
| 		bottom 16px | ||||
| 		left 16px | ||||
| 		padding 16px | ||||
| 		font-size 18px | ||||
| 		color #fff | ||||
|  | ||||
| 		display none // TODO | ||||
| 		color isDark ? #fff : #444 | ||||
|  | ||||
| 	> .body | ||||
| 		flex 1 | ||||
| 		padding 64px 0 0 0 | ||||
| 		text-align center | ||||
| 		background #578394 | ||||
| 		background-position center | ||||
| 		background-size cover | ||||
| 		display grid | ||||
| 		grid-template-rows 0.5fr 0.5fr 64px | ||||
| 		grid-template-columns 1fr 350px | ||||
| 		gap 16px | ||||
| 		width 100% | ||||
| 		max-width 1200px | ||||
| 		height 100vh | ||||
| 		min-height 800px | ||||
| 		margin 0 auto | ||||
| 		padding 64px | ||||
|  | ||||
| 		&:before | ||||
| 			content '' | ||||
| 			display block | ||||
| 			position absolute | ||||
| 			top 0 | ||||
| 			left 0 | ||||
| 			right 0 | ||||
| 			bottom 0 | ||||
| 			background rgba(#000, 0.5) | ||||
| 		.block | ||||
| 			color isDark ? #fff : #444 | ||||
| 			background isDark ? #313543 : #fff | ||||
| 			box-shadow 0 3px 8px rgba(0, 0, 0, 0.2) | ||||
| 			//border-radius 8px | ||||
| 			overflow auto | ||||
|  | ||||
| 		> .forkit | ||||
| 			position absolute | ||||
| 			top 0 | ||||
| 			right 0 | ||||
| 		> .main | ||||
| 			grid-row 1 | ||||
| 			grid-column 1 | ||||
| 			padding 32px | ||||
| 			border-top solid 5px $theme-color | ||||
|  | ||||
| 		> img | ||||
| 			position absolute | ||||
| 			bottom 16px | ||||
| 			right 16px | ||||
| 			width 150px | ||||
| 			> h1 | ||||
| 				margin 0 | ||||
|  | ||||
| 		> .container | ||||
| 			$aboutWidth = 380px | ||||
| 			$loginWidth = 340px | ||||
| 			$width = $aboutWidth + $loginWidth | ||||
| 				> 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 | ||||
|  | ||||
| 					> h1 | ||||
| 						margin 0 0 16px 0 | ||||
| 						padding 32px 32px 0 32px | ||||
| 						color #444 | ||||
| 				> .signin | ||||
| 				> .signup | ||||
| 					cursor pointer | ||||
|  | ||||
| 						> img | ||||
| 							width 170px | ||||
| 							vertical-align bottom | ||||
|  | ||||
| 					> .powerd-by | ||||
| 						margin 16px | ||||
| 						opacity 0.7 | ||||
|  | ||||
| 					> .desc | ||||
| 						margin 0 | ||||
| 						padding 0 32px 16px 32px | ||||
|  | ||||
| 					> a | ||||
| 						display inline-block | ||||
| 						margin 0 0 32px 0 | ||||
| 						font-weight bold | ||||
|  | ||||
| 				> .login | ||||
| 					width $loginWidth | ||||
| 					padding 16px 32px 32px 32px | ||||
| 					background isDark ? #2e3440 : #f5f5f5 | ||||
| 					&:hover | ||||
| 						color $theme-color | ||||
|  | ||||
| 			> .hashtags | ||||
| 				margin 16px auto | ||||
| 				width $width | ||||
| 				font-size 14px | ||||
| 				color #fff | ||||
| 				background rgba(#000, 0.3) | ||||
| 				border-radius 8px | ||||
|  | ||||
| @@ -240,22 +233,47 @@ root(isDark) | ||||
| 					display inline-block | ||||
| 					margin 14px | ||||
|  | ||||
| 			> .nav | ||||
| 				display block | ||||
| 				margin 16px 0 | ||||
| 				font-size 14px | ||||
| 				color #fff | ||||
| 		> .broadcasts | ||||
| 			grid-row 2 | ||||
| 			grid-column 1 | ||||
| 			padding 32px | ||||
|  | ||||
| 	> .tl | ||||
| 		margin 0 | ||||
| 		width 410px | ||||
| 		height 100vh | ||||
| 		text-align left | ||||
| 		background isDark ? #313543 : #fff | ||||
| 			> div | ||||
| 				padding 0 0 16px 0 | ||||
| 				margin 0 0 16px 0 | ||||
| 				border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05) | ||||
|  | ||||
| 		> * | ||||
| 			max-height 100% | ||||
| 			overflow auto | ||||
| 				> h1 | ||||
| 					margin 0 | ||||
| 					font-size 1.5em | ||||
|  | ||||
| 		> .nav | ||||
| 			display flex | ||||
| 			justify-content center | ||||
| 			align-items center | ||||
| 			grid-row 3 | ||||
| 			grid-column 1 | ||||
| 			font-size 14px | ||||
|  | ||||
| 		> .side | ||||
| 			display grid | ||||
| 			grid-row 1 / 4 | ||||
| 			grid-column 2 | ||||
| 			grid-template-rows 1fr 350px | ||||
| 			grid-template-columns 1fr | ||||
| 			gap 16px | ||||
|  | ||||
| 			> .tl | ||||
| 				grid-row 1 | ||||
| 				grid-column 1 | ||||
| 				text-align left | ||||
| 				max-height 100% | ||||
| 				overflow auto | ||||
|  | ||||
| 			> .trends | ||||
| 				grid-row 2 | ||||
| 				grid-column 1 | ||||
| 				padding 8px | ||||
|  | ||||
| .mk-welcome[data-darkmode] | ||||
| 	root(true) | ||||
| @@ -264,29 +282,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> | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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 | ||||
| 						}); | ||||
|   | ||||
| @@ -471,10 +471,6 @@ root(isDark) | ||||
| 					&.reacted | ||||
| 						color $theme-color | ||||
|  | ||||
| 					&.menu | ||||
| 						@media (max-width 350px) | ||||
| 							display none | ||||
|  | ||||
| .note[data-darkmode] | ||||
| 	root(true) | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| <template> | ||||
| <div class="mk-notify"> | ||||
| 	<mk-notification-preview :notification="notification"/> | ||||
| <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 | ||||
| 	-webkit-backdrop-filter blur(2px) | ||||
| 	backdrop-filter blur(2px) | ||||
| 	background-color rgba(#000, 0.5) | ||||
| 	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(); | ||||
|   | ||||
| @@ -105,9 +105,9 @@ export default Vue.extend({ | ||||
| 	computed: { | ||||
| 		draftId(): string { | ||||
| 			return this.renote | ||||
| 				? 'renote:' + this.renote.id | ||||
| 				? `renote:${this.renote.id}` | ||||
| 				: this.reply | ||||
| 					? 'reply:' + this.reply.id | ||||
| 					? `reply:${this.reply.id}` | ||||
| 					: 'note'; | ||||
| 		}, | ||||
|  | ||||
| @@ -229,7 +229,7 @@ export default Vue.extend({ | ||||
| 			navigator.geolocation.getCurrentPosition(pos => { | ||||
| 				this.geo = pos.coords; | ||||
| 			}, err => { | ||||
| 				alert('%i18n:@error%: ' + err.message); | ||||
| 				alert(`%i18n:@error%: ${err.message}`); | ||||
| 			}, { | ||||
| 					enableHighAccuracy: true | ||||
| 				}); | ||||
|   | ||||
| @@ -82,7 +82,7 @@ export default Vue.extend({ | ||||
| 		search() { | ||||
| 			const query = window.prompt('%i18n:@search%'); | ||||
| 			if (query == null || query == '') return; | ||||
| 			this.$router.push('/search?q=' + encodeURIComponent(query)); | ||||
| 			this.$router.push(`/search?q=${encodeURIComponent(query)}`); | ||||
| 		}, | ||||
| 		onReversiInvited() { | ||||
| 			this.hasGameInvitation = true; | ||||
|   | ||||
| @@ -80,7 +80,7 @@ export default Vue.extend({ | ||||
|  | ||||
| 			if (!silent) { | ||||
| 				// Rewrite URL | ||||
| 				history.pushState(null, title, '/i/drive/folder/' + folder.id); | ||||
| 				history.pushState(null, title, `/i/drive/folder/${folder.id}`); | ||||
| 			} | ||||
|  | ||||
| 			document.title = title; | ||||
| @@ -93,7 +93,7 @@ export default Vue.extend({ | ||||
|  | ||||
| 			if (!silent) { | ||||
| 				// Rewrite URL | ||||
| 				history.pushState(null, title, '/i/drive/file/' + file.id); | ||||
| 				history.pushState(null, title, `/i/drive/file/${file.id}`); | ||||
| 			} | ||||
|  | ||||
| 			document.title = title; | ||||
|   | ||||
| @@ -49,7 +49,7 @@ export default Vue.extend({ | ||||
| 				this.user = user; | ||||
| 				this.fetching = false; | ||||
|  | ||||
| 				document.title = '%i18n:@followers-of%'.replace('{}', this.name) + ' | ' + (this as any).os.instanceName; | ||||
| 				document.title = `${'%i18n:@followers-of%'.replace('{}', this.name)} | ${(this as any).os.instanceName}`; | ||||
| 			}); | ||||
| 		}, | ||||
| 		onLoaded() { | ||||
|   | ||||
| @@ -48,7 +48,7 @@ export default Vue.extend({ | ||||
| 				this.user = user; | ||||
| 				this.fetching = false; | ||||
|  | ||||
| 				document.title = '%i18n:@followers-of%'.replace('{}', this.name) + ' | ' + (this as any).os.instanceName; | ||||
| 				document.title = `${'%i18n:@followers-of%'.replace('{}', this.name)} | ${(this as any).os.instanceName}`; | ||||
| 			}); | ||||
| 		}, | ||||
| 		onLoaded() { | ||||
|   | ||||
| @@ -16,10 +16,10 @@ export default Vue.extend({ | ||||
| 	methods: { | ||||
| 		nav(game, actualNav) { | ||||
| 			if (actualNav) { | ||||
| 				this.$router.push('/reversi/' + game.id); | ||||
| 				this.$router.push(`/reversi/${game.id}`); | ||||
| 			} else { | ||||
| 				// TODO: https://github.com/vuejs/vue-router/issues/703 | ||||
| 				this.$router.push('/reversi/' + game.id); | ||||
| 				this.$router.push(`/reversi/${game.id}`); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| <mk-ui> | ||||
| 	<span slot="header">%fa:cog%%i18n:@settings%</span> | ||||
| 	<main :data-darkmode="$store.state.device.darkmode"> | ||||
| 		<div class="signin-as" v-html="'%i18n:@signed-in-as%'.replace('{}', '<b>' + name + '</b>')"></div> | ||||
| 		<div class="signin-as" v-html="'%i18n:@signed-in-as%'.replace('{}', `<b>${name}</b>`)"></div> | ||||
|  | ||||
| 		<div> | ||||
| 			<x-profile/> | ||||
| @@ -10,80 +10,101 @@ | ||||
| 			<ui-card> | ||||
| 				<div slot="title">%fa:palette% %i18n:@design%</div> | ||||
|  | ||||
| 				<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> | ||||
| 					<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> | ||||
|  | ||||
| 				<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> | ||||
| 					<header>%i18n:@timeline%</header> | ||||
| 					<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> | ||||
| 				<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-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> | ||||
|  | ||||
| 				<ui-switch v-model="enableSounds">%i18n:@enable-sounds%</ui-switch> | ||||
| 				<section> | ||||
| 					<ui-switch v-model="enableSounds">%i18n:@enable-sounds%</ui-switch> | ||||
| 				</section> | ||||
| 			</ui-card> | ||||
|  | ||||
| 			<ui-card> | ||||
| 				<div slot="title">%fa:language% %i18n:@lang%</div> | ||||
|  | ||||
| 				<ui-select v-model="lang" placeholder="%i18n:@auto%"> | ||||
| 					<optgroup label="%i18n:@recommended%"> | ||||
| 						<option value="">%i18n:@auto%</option> | ||||
| 					</optgroup> | ||||
| 				<section class="fit-top"> | ||||
| 					<ui-select v-model="lang" placeholder="%i18n:@auto%"> | ||||
| 						<optgroup label="%i18n:@recommended%"> | ||||
| 							<option value="">%i18n:@auto%</option> | ||||
| 						</optgroup> | ||||
|  | ||||
| 					<optgroup label="%i18n:@specify-language%"> | ||||
| 						<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option> | ||||
| 					</optgroup> | ||||
| 				</ui-select> | ||||
| 				<span>%fa:info-circle% %i18n:@lang-tip%</span> | ||||
| 						<optgroup label="%i18n:@specify-language%"> | ||||
| 							<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option> | ||||
| 						</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> | ||||
|  | ||||
| 				<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> | ||||
| 					<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> | ||||
|  | ||||
| 				<div>%i18n:@version% <i>{{ version }}</i></div> | ||||
| 				<template v-if="latestVersion !== undefined"> | ||||
| 					<div>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></div> | ||||
| 				</template> | ||||
| 				<ui-button @click="checkForUpdate" :disabled="checkingForUpdate"> | ||||
| 					<template v-if="checkingForUpdate">%i18n:@update-checking%<mk-ellipsis/></template> | ||||
| 					<template v-else>%i18n:@check-for-updates%</template> | ||||
| 				</ui-button> | ||||
| 				<section> | ||||
| 					<div>%i18n:@version% <i>{{ version }}</i></div> | ||||
| 					<template v-if="latestVersion !== undefined"> | ||||
| 						<div>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></div> | ||||
| 					</template> | ||||
| 					<ui-button @click="checkForUpdate" :disabled="checkingForUpdate"> | ||||
| 						<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,47 +2,49 @@ | ||||
| <ui-card> | ||||
| 	<div slot="title">%fa:user% %i18n:@title%</div> | ||||
|  | ||||
| 	<ui-form :disabled="saving"> | ||||
| 		<ui-input v-model="name" :max="30"> | ||||
| 			<span>%i18n:@name%</span> | ||||
| 		</ui-input> | ||||
| 	<section class="fit-top"> | ||||
| 		<ui-form :disabled="saving"> | ||||
| 			<ui-input v-model="name" :max="30"> | ||||
| 				<span>%i18n:@name%</span> | ||||
| 			</ui-input> | ||||
|  | ||||
| 		<ui-input v-model="username" readonly> | ||||
| 			<span>%i18n:@account%</span> | ||||
| 			<span slot="prefix">@</span> | ||||
| 			<span slot="suffix">@{{ host }}</span> | ||||
| 		</ui-input> | ||||
| 			<ui-input v-model="username" readonly> | ||||
| 				<span>%i18n:@account%</span> | ||||
| 				<span slot="prefix">@</span> | ||||
| 				<span slot="suffix">@{{ host }}</span> | ||||
| 			</ui-input> | ||||
|  | ||||
| 		<ui-input v-model="location"> | ||||
| 			<span>%i18n:@location%</span> | ||||
| 			<span slot="prefix">%fa:map-marker-alt%</span> | ||||
| 		</ui-input> | ||||
| 			<ui-input v-model="location"> | ||||
| 				<span>%i18n:@location%</span> | ||||
| 				<span slot="prefix">%fa:map-marker-alt%</span> | ||||
| 			</ui-input> | ||||
|  | ||||
| 		<ui-input v-model="birthday" type="date"> | ||||
| 			<span>%i18n:@birthday%</span> | ||||
| 			<span slot="prefix">%fa:birthday-cake%</span> | ||||
| 		</ui-input> | ||||
| 			<ui-input v-model="birthday" type="date"> | ||||
| 				<span>%i18n:@birthday%</span> | ||||
| 				<span slot="prefix">%fa:birthday-cake%</span> | ||||
| 			</ui-input> | ||||
|  | ||||
| 		<ui-textarea v-model="description" :max="500"> | ||||
| 			<span>%i18n:@description%</span> | ||||
| 		</ui-textarea> | ||||
| 			<ui-textarea v-model="description" :max="500"> | ||||
| 				<span>%i18n:@description%</span> | ||||
| 			</ui-textarea> | ||||
|  | ||||
| 		<ui-input type="file" @change="onAvatarChange"> | ||||
| 			<span>%i18n:@avatar%</span> | ||||
| 			<span slot="icon">%fa:image%</span> | ||||
| 			<span slot="text" v-if="avatarUploading">%i18n:@uploading%<mk-ellipsis/></span> | ||||
| 		</ui-input> | ||||
| 			<ui-input type="file" @change="onAvatarChange"> | ||||
| 				<span>%i18n:@avatar%</span> | ||||
| 				<span slot="icon">%fa:image%</span> | ||||
| 				<span slot="text" v-if="avatarUploading">%i18n:@uploading%<mk-ellipsis/></span> | ||||
| 			</ui-input> | ||||
|  | ||||
| 		<ui-input type="file" @change="onBannerChange"> | ||||
| 			<span>%i18n:@banner%</span> | ||||
| 			<span slot="icon">%fa:image%</span> | ||||
| 			<span slot="text" v-if="bannerUploading">%i18n:@uploading%<mk-ellipsis/></span> | ||||
| 		</ui-input> | ||||
| 			<ui-input type="file" @change="onBannerChange"> | ||||
| 				<span>%i18n:@banner%</span> | ||||
| 				<span slot="icon">%fa:image%</span> | ||||
| 				<span slot="text" v-if="bannerUploading">%i18n:@uploading%<mk-ellipsis/></span> | ||||
| 			</ui-input> | ||||
|  | ||||
| 		<ui-switch v-model="isCat">%i18n:@is-cat%</ui-switch> | ||||
| 			<ui-switch v-model="isCat">%i18n:@is-cat%</ui-switch> | ||||
|  | ||||
| 		<ui-button @click="save">%i18n:@save%</ui-button> | ||||
| 	</ui-form> | ||||
| 			<ui-button @click="save">%i18n:@save%</ui-button> | ||||
| 		</ui-form> | ||||
| 	</section> | ||||
| </ui-card> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -43,7 +43,7 @@ export default Vue.extend({ | ||||
| 					title | ||||
| 				}); | ||||
|  | ||||
| 				this.$router.push('/i/lists/' + list.id); | ||||
| 				this.$router.push(`/i/lists/${list.id}`); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -107,7 +107,7 @@ export default Vue.extend({ | ||||
| 				this.fetching = false; | ||||
|  | ||||
| 				Progress.done(); | ||||
| 				document.title = Vue.filter('userName')(this.user) + ' | ' + (this as any).os.instanceName; | ||||
| 				document.title = `${Vue.filter('userName')(this.user)} | ${(this as any).os.instanceName}`; | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -16,7 +16,7 @@ export default function() { | ||||
| 	}); | ||||
|  | ||||
| 	ev.on('requestNotesStatsLog', id => { | ||||
| 		ev.emit('notesStatsLog:' + id, log.toArray()); | ||||
| 		ev.emit(`notesStatsLog:${id}`, log.toArray()); | ||||
| 	}); | ||||
|  | ||||
| 	process.on('exit', code => { | ||||
|   | ||||
| @@ -16,7 +16,7 @@ export default function() { | ||||
| 	const log = new Deque<any>(); | ||||
|  | ||||
| 	ev.on('requestServerStatsLog', x => { | ||||
| 		ev.emit('serverStatsLog:' + x.id, log.toArray().slice(0, x.length || 50)); | ||||
| 		ev.emit(`serverStatsLog:${x.id}`, log.toArray().slice(0, x.length || 50)); | ||||
| 	}); | ||||
|  | ||||
| 	async function tick() { | ||||
|   | ||||
| @@ -205,13 +205,7 @@ export default class Reversi { | ||||
| 	 * 打つことができる場所を取得します | ||||
| 	 */ | ||||
| 	public canPutSomewhere(color: Color): number[] { | ||||
| 		const result: number[] = []; | ||||
|  | ||||
| 		this.board.forEach((x, i) => { | ||||
| 			if (this.canPut(color, i)) result.push(i); | ||||
| 		}); | ||||
|  | ||||
| 		return result; | ||||
| 		return Array.from(this.board.keys()).filter(i => this.canPut(color, i)); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -20,7 +20,6 @@ import Logger from './misc/logger'; | ||||
| import ProgressBar from './misc/cli/progressbar'; | ||||
| import EnvironmentInfo from './misc/environmentInfo'; | ||||
| import MachineInfo from './misc/machineInfo'; | ||||
| import DependencyInfo from './misc/dependencyInfo'; | ||||
| import serverStats from './daemons/server-stats'; | ||||
| import notesStats from './daemons/notes-stats'; | ||||
| import loadConfig from './config/load'; | ||||
| @@ -116,7 +115,6 @@ async function init(): Promise<Config> { | ||||
| 	new Logger('Deps').info(`Node.js ${process.version}`); | ||||
| 	MachineInfo.show(); | ||||
| 	EnvironmentInfo.show(); | ||||
| 	new DependencyInfo().showAll(); | ||||
|  | ||||
| 	const configLogger = new Logger('Config'); | ||||
| 	let config; | ||||
|   | ||||
| @@ -33,26 +33,27 @@ export default function(html: string): string { | ||||
|  | ||||
| 			case 'a': | ||||
| 				const txt = getText(node); | ||||
| 				const rel = node.attrs.find((x: any) => x.name == 'rel'); | ||||
| 				const href = node.attrs.find((x: any) => x.name == 'href'); | ||||
|  | ||||
| 				// ハッシュタグ / hrefがない / txtがURL | ||||
| 				if ((rel && rel.value.match('tag') !== null) || !href || href.value == txt) { | ||||
| 					text += txt; | ||||
| 				// メンション | ||||
| 				if (txt.startsWith('@')) { | ||||
| 				} else if (txt.startsWith('@')) { | ||||
| 					const part = txt.split('@'); | ||||
|  | ||||
| 					if (part.length == 2) { | ||||
| 						//#region ホスト名部分が省略されているので復元する | ||||
| 						const href = new URL(node.attrs.find((x: any) => x.name == 'href').value); | ||||
| 						const acct = txt + '@' + href.hostname; | ||||
| 						const acct = `${txt}@${(new URL(href.value)).hostname}`; | ||||
| 						text += acct; | ||||
| 						break; | ||||
| 						//#endregion | ||||
| 					} else if (part.length == 3) { | ||||
| 						text += txt; | ||||
| 						break; | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if (node.childNodes) { | ||||
| 					node.childNodes.forEach((n: any) => analyze(n)); | ||||
| 				// その他 | ||||
| 				} else { | ||||
| 					text += `[${txt}](${href.value})`; | ||||
| 				} | ||||
| 				break; | ||||
|  | ||||
|   | ||||
| @@ -44,8 +44,8 @@ const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers: | ||||
|  | ||||
| 	hashtag({ document }, { hashtag }) { | ||||
| 		const a = document.createElement('a'); | ||||
| 		a.href = config.url + '/tags/' + hashtag; | ||||
| 		a.textContent = '#' + hashtag; | ||||
| 		a.href = `${config.url}/tags/${hashtag}`; | ||||
| 		a.textContent = `#${hashtag}`; | ||||
| 		a.setAttribute('rel', 'tag'); | ||||
| 		document.body.appendChild(a); | ||||
| 	}, | ||||
|   | ||||
| @@ -1,32 +0,0 @@ | ||||
| import Logger from './logger'; | ||||
| import { execSync } from 'child_process'; | ||||
|  | ||||
| export default class { | ||||
| 	private logger: Logger; | ||||
|  | ||||
| 	constructor() { | ||||
| 		this.logger = new Logger('Deps'); | ||||
| 	} | ||||
|  | ||||
| 	public showAll(): void { | ||||
| 		this.show('MongoDB', 'mongo --version', x => x.match(/^MongoDB shell version:? v(.*)\r?\n/)); | ||||
| 		this.show('Redis', 'redis-server --version', x => x.match(/v=([0-9\.]*)/)); | ||||
| 	} | ||||
|  | ||||
| 	public show(serviceName: string, command: string, transform: (x: string) => RegExpMatchArray): void { | ||||
| 		try { | ||||
| 			// ステータス0以外のときにexecSyncはstderrをコンソール上に出力してしまうので | ||||
| 			// プロセスからのstderrをすべて無視するように stdio オプションをセット | ||||
| 			const x = execSync(command, { stdio: ['pipe', 'pipe', 'ignore'] }); | ||||
| 			const ver = transform(x.toString()); | ||||
| 			if (ver != null) { | ||||
| 				this.logger.succ(`${serviceName} ${ver[1]} found`); | ||||
| 			} else { | ||||
| 				this.logger.warn(`${serviceName} not found`); | ||||
| 				this.logger.warn(`Regexp used for version check of ${serviceName} is probably messed up`); | ||||
| 			} | ||||
| 		} catch (e) { | ||||
| 			this.logger.warn(`${serviceName} not found`); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -26,7 +26,7 @@ export const replacement = (match: string, key: string) => { | ||||
| 				arg == 'B' ? 'fab' : | ||||
| 				''; | ||||
| 		} else if (arg.startsWith('.')) { | ||||
| 			classes.push('fa-' + arg.substr(1)); | ||||
| 			classes.push(`fa-${arg.substr(1)}`); | ||||
| 		} else if (arg.startsWith('-')) { | ||||
| 			transform = arg.substr(1).split('|').join(' '); | ||||
| 		} else { | ||||
|   | ||||
| @@ -193,5 +193,10 @@ export const pack = ( | ||||
| 		*/ | ||||
| 	} | ||||
|  | ||||
| 	delete _target.withoutChunks; | ||||
| 	delete _target.storage; | ||||
| 	delete _target.storageProps; | ||||
| 	delete _target.isRemote; | ||||
|  | ||||
| 	resolve(_target); | ||||
| }); | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -5,7 +5,7 @@ const httpSignature = require('http-signature'); | ||||
| import parseAcct from '../../../misc/acct/parse'; | ||||
| import User, { IRemoteUser } from '../../../models/user'; | ||||
| import perform from '../../../remote/activitypub/perform'; | ||||
| import { resolvePerson } from '../../../remote/activitypub/models/person'; | ||||
| import { resolvePerson, updatePerson } from '../../../remote/activitypub/models/person'; | ||||
| import { toUnicode } from 'punycode'; | ||||
| import { URL } from 'url'; | ||||
|  | ||||
| @@ -44,11 +44,6 @@ export default async (job: bq.Job, done: any): Promise<void> => { | ||||
| 		} | ||||
|  | ||||
| 		user = await User.findOne({ usernameLower: username, host: host.toLowerCase() }) as IRemoteUser; | ||||
|  | ||||
| 		// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する | ||||
| 		if (user === null) { | ||||
| 			user = await resolvePerson(activity.actor) as IRemoteUser; | ||||
| 		} | ||||
| 	} else { | ||||
| 		// アクティビティ内のホストの検証 | ||||
| 		const host = toUnicode(new URL(signature.keyId).hostname.toLowerCase()); | ||||
| @@ -64,11 +59,26 @@ export default async (job: bq.Job, done: any): Promise<void> => { | ||||
| 			host: { $ne: null }, | ||||
| 			'publicKey.id': signature.keyId | ||||
| 		}) as IRemoteUser; | ||||
| 	} | ||||
|  | ||||
| 		// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する | ||||
| 		if (user === null) { | ||||
| 			user = await resolvePerson(activity.actor) as IRemoteUser; | ||||
| 	// Update activityの場合は、ここで署名検証/更新処理まで実施して終了 | ||||
| 	if (activity.type === 'Update') { | ||||
| 		if (activity.object && activity.object.type === 'Person') { | ||||
| 			if (user == null) { | ||||
| 				console.warn('Update activity received, but user not registed.'); | ||||
| 			} else if (!httpSignature.verifySignature(signature, user.publicKey.publicKeyPem)) { | ||||
| 				console.warn('Update activity received, but signature verification failed.'); | ||||
| 			} else { | ||||
| 				updatePerson(activity.actor, null, activity.object); | ||||
| 			} | ||||
| 		} | ||||
| 		done(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する | ||||
| 	if (user === null) { | ||||
| 		user = await resolvePerson(activity.actor) as IRemoteUser; | ||||
| 	} | ||||
|  | ||||
| 	if (user === null) { | ||||
|   | ||||
| @@ -139,6 +139,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU | ||||
| 			avatarId: null, | ||||
| 			bannerId: null, | ||||
| 			createdAt: Date.parse(person.published) || null, | ||||
| 			updatedAt: new Date(), | ||||
| 			description: htmlToMFM(person.summary), | ||||
| 			followersCount, | ||||
| 			followingCount, | ||||
| @@ -215,10 +216,12 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU | ||||
|  | ||||
| /** | ||||
|  * Personの情報を更新します。 | ||||
|  * | ||||
|  * Misskeyに対象のPersonが登録されていなければ無視します。 | ||||
|  * @param uri URI of Person | ||||
|  * @param resolver Resolver | ||||
|  * @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します) | ||||
|  */ | ||||
| export async function updatePerson(uri: string, resolver?: Resolver): Promise<void> { | ||||
| export async function updatePerson(uri: string, resolver?: Resolver, hint?: object): Promise<void> { | ||||
| 	if (typeof uri !== 'string') throw 'uri is not string'; | ||||
|  | ||||
| 	// URIがこのサーバーを指しているならスキップ | ||||
| @@ -236,7 +239,7 @@ export async function updatePerson(uri: string, resolver?: Resolver): Promise<vo | ||||
|  | ||||
| 	if (resolver == null) resolver = new Resolver(); | ||||
|  | ||||
| 	const object = await resolver.resolve(uri) as any; | ||||
| 	const object = hint || await resolver.resolve(uri) as any; | ||||
|  | ||||
| 	const err = validatePerson(object, uri); | ||||
|  | ||||
| @@ -290,7 +293,14 @@ export async function updatePerson(uri: string, resolver?: Resolver): Promise<vo | ||||
| 			name: person.name, | ||||
| 			url: person.url, | ||||
| 			endpoints: person.endpoints, | ||||
| 			isCat: (person as any).isCat === true ? true : false | ||||
| 			isBot: object.type == 'Service', | ||||
| 			isCat: (person as any).isCat === true ? true : false, | ||||
| 			isLocked: person.manuallyApprovesFollowers, | ||||
| 			createdAt: Date.parse(person.published) || null, | ||||
| 			publicKey: { | ||||
| 				id: person.publicKey.id, | ||||
| 				publicKeyPem: person.publicKey.publicKeyPem | ||||
| 			}, | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -3,5 +3,5 @@ import config from '../../../config'; | ||||
| export default (tag: string) => ({ | ||||
| 	type: 'Hashtag', | ||||
| 	href: `${config.url}/tags/${encodeURIComponent(tag)}`, | ||||
| 	name: '#' + tag | ||||
| 	name: `#${tag}` | ||||
| }); | ||||
|   | ||||
							
								
								
									
										4
									
								
								src/remote/activitypub/renderer/tombstone.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/remote/activitypub/renderer/tombstone.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| export default (id: string) => ({ | ||||
| 	id, | ||||
| 	type: 'Tombstone' | ||||
| }); | ||||
							
								
								
									
										14
									
								
								src/remote/activitypub/renderer/update.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/remote/activitypub/renderer/update.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| import config from '../../../config'; | ||||
| import { ILocalUser } from '../../../models/user'; | ||||
|  | ||||
| export default (object: any, user: ILocalUser) => { | ||||
| 	const activity = { | ||||
| 		id: `${config.url}/users/${user._id}#updates/${new Date().getTime()}`, | ||||
| 		actor: `${config.url}/users/${user._id}`, | ||||
| 		type: 'Update', | ||||
| 		to: [ 'https://www.w3.org/ns/activitystreams#Public' ], | ||||
| 		object | ||||
| 	} as any; | ||||
|  | ||||
| 	return activity; | ||||
| }; | ||||
| @@ -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 | ||||
|   | ||||
| @@ -22,7 +22,7 @@ const router = new Router(); | ||||
| function inbox(ctx: Router.IRouterContext) { | ||||
| 	let signature; | ||||
|  | ||||
| 	ctx.req.headers.authorization = 'Signature ' + ctx.req.headers.signature; | ||||
| 	ctx.req.headers.authorization = `Signature ${ctx.req.headers.signature}`; | ||||
|  | ||||
| 	try { | ||||
| 		signature = httpSignature.parseRequest(ctx.req, { 'headers': [] }); | ||||
|   | ||||
| @@ -79,7 +79,7 @@ const files = glob.sync('**/*.js', { | ||||
| }); | ||||
|  | ||||
| const endpoints: IEndpoint[] = files.map(f => { | ||||
| 	const ep = require('./endpoints/' + f); | ||||
| 	const ep = require(`./endpoints/${f}`); | ||||
|  | ||||
| 	return { | ||||
| 		name: f.replace('.js', ''), | ||||
|   | ||||
| @@ -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,7 +31,11 @@ export default (params: any) => new Promise(async (res, rej) => { | ||||
|  | ||||
| 	const set = {} as any; | ||||
|  | ||||
| 	if (ps.disableRegistration === true || ps.disableRegistration === false) { | ||||
| 	if (ps.broadcasts) { | ||||
| 		set.broadcasts = ps.broadcasts; | ||||
| 	} | ||||
|  | ||||
| 	if (typeof ps.disableRegistration === 'boolean') { | ||||
| 		set.disableRegistration = ps.disableRegistration; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import DriveFile from '../../../../models/drive-file'; | ||||
| import acceptAllFollowRequests from '../../../../services/following/requests/accept-all'; | ||||
| import { IApp } from '../../../../models/app'; | ||||
| import config from '../../../../config'; | ||||
| import { publishToFollowers } from '../../../../services/i/update'; | ||||
|  | ||||
| export const meta = { | ||||
| 	desc: { | ||||
| @@ -144,4 +145,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a | ||||
| 	if (user.isLocked && isLocked === false) { | ||||
| 		acceptAllFollowRequests(user); | ||||
| 	} | ||||
|  | ||||
| 	// フォロワーにUpdateを配信 | ||||
| 	publishToFollowers(user._id); | ||||
| }); | ||||
|   | ||||
| @@ -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 | ||||
| 	}); | ||||
|   | ||||
							
								
								
									
										43
									
								
								src/server/api/endpoints/users/lists/delete.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/server/api/endpoints/users/lists/delete.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| import $ from 'cafy'; | ||||
| import ID from '../../../../../misc/cafy-id'; | ||||
| import UserList, { deleteUserList } from '../../../../../models/user-list'; | ||||
| import { ILocalUser } from '../../../../../models/user'; | ||||
| import getParams from '../../../get-params'; | ||||
|  | ||||
| export const meta = { | ||||
| 	desc: { | ||||
| 		'ja-JP': '指定したユーザーリストを削除します。', | ||||
| 		'en-US': 'Delete a user list' | ||||
| 	}, | ||||
|  | ||||
| 	requireCredential: true, | ||||
|  | ||||
| 	kind: 'account-write', | ||||
|  | ||||
| 	params: { | ||||
| 		listId: $.type(ID).note({ | ||||
| 			desc: { | ||||
| 				'ja-JP': '対象となるユーザーリストのID', | ||||
| 				'en-US': 'ID of target user list' | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const userList = await UserList.findOne({ | ||||
| 		_id: ps.listId, | ||||
| 		userId: user._id | ||||
| 	}); | ||||
|  | ||||
| 	if (userList == null) { | ||||
| 		return rej('list not found'); | ||||
| 	} | ||||
|  | ||||
| 	deleteUserList(userList); | ||||
|  | ||||
| 	res(); | ||||
| }); | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user