Compare commits
	
		
			61 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 152dd74abf | ||
|   | 0985f7f609 | ||
|   | 56d571c0f0 | ||
|   | dc9a19b9c7 | ||
|   | 88a2c7715a | ||
|   | 2fa8cb1b73 | ||
|   | 5f8a66fdb9 | ||
|   | 57320a94f9 | ||
|   | 89f045d624 | ||
|   | 1a77dea7ed | ||
|   | d063d59a91 | ||
|   | 90429b787c | ||
|   | 7a2ef04ec3 | ||
|   | 76a9ea8d3d | ||
|   | 0a05a2d060 | ||
|   | a7e2ee3b0c | ||
|   | 40efa90dd5 | ||
|   | 4ca0a22bfc | ||
|   | 20a943b193 | ||
|   | 552df8737d | ||
|   | 860f622d79 | ||
|   | e76bf5707a | ||
|   | bf37a72f59 | ||
|   | 840ad75830 | ||
|   | 4c7dd7228f | ||
|   | 46a51addad | ||
|   | 0a5fe37025 | ||
|   | 00bb403497 | ||
|   | 11afa8140c | ||
|   | 850396e9da | ||
|   | 5ee75be49e | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 879116a20c | ||
|   | e509b1f488 | ||
|   | 468ff7037f | ||
|   | df23504ccf | ||
|   | 66e3cb8eda | ||
|   | 6ddd2389dc | ||
|   | 402efb8c50 | ||
|   | 7b6eae0ce4 | ||
|   | 26ce9725ce | ||
|   | ebfaa18f12 | ||
|   | cc81d41a05 | ||
|   | 212176ee5c | ||
|   | a63ec05e41 | ||
|   | 0dcb527bf3 | ||
|   | 54710f17fc | ||
|   | e58a6593c0 | ||
|   | 62132570e1 | ||
|   | 9f0b8ba2f8 | ||
|   | adbe0fbcd1 | ||
|   | 7896242f57 | ||
|   | 4a6722b9e9 | ||
|   | 7c9fb5228b | ||
|   | 81805b01cc | ||
|   | 50824a7245 | ||
|   | 6f2953f3a7 | ||
|   | dd3f007582 | ||
|   | a4b2b093fc | ||
|   | 0fbf56219f | ||
|   | 0acacf7a8e | ||
|   | c84500d914 | 
							
								
								
									
										126
									
								
								docs/setup.fr.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								docs/setup.fr.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| Guide d'installation et de configuration de Misskey | ||||
| ================================================================ | ||||
|  | ||||
| Nous vous remerçions de l'intrêt que vous manifestez pour l'installation de votre propre instance Misskey ! | ||||
| Ce guide décrit les étapes à suivre afin d'installer et de configurer une instance Misskey. | ||||
|  | ||||
| [La version en japonnais est également disponible sur - 日本語版もあります](./setup.ja.md) | ||||
|  | ||||
| ---------------------------------------------------------------- | ||||
|  | ||||
| *1.* Création de l'utilisateur Misskey | ||||
| ---------------------------------------------------------------- | ||||
| Lancer misskey en tant qu'utilisateur est une mauvaise idée, nous avons besoin de créer un utilisateur dédié. | ||||
| Sur Debian, à titre d'exemple : | ||||
|  | ||||
| ``` | ||||
| adduser --disabled-password --disabled-login misskey | ||||
| ``` | ||||
|  | ||||
| *2.* Installation des dépendances | ||||
| ---------------------------------------------------------------- | ||||
| Installez les paquets suivants : | ||||
|  | ||||
| #### Dépendences :package: | ||||
| * **[Node.js](https://nodejs.org/en/)** >= 10.0.0 | ||||
| * **[MongoDB](https://www.mongodb.com/)** >= 3.6 | ||||
|  | ||||
| ##### Optionnels | ||||
| * [Redis](https://redis.io/) | ||||
|   * Redis est optionnel mais nous vous recommandons vivement de l'installer | ||||
| * [Elasticsearch](https://www.elastic.co/) - requis pour pouvoir activer la fonctionnalité de recherche | ||||
|  | ||||
| *3.* Paramètrage de MongoDB | ||||
| ---------------------------------------------------------------- | ||||
| En mode root : | ||||
| 1. `mongo` Accédez au shell de mango | ||||
| 2. `use misskey` Utilisez la base de données misskey | ||||
| 3. `db.users.save( {dummy:"dummy"} )` Write dummy data to initialize the db. | ||||
| 4. `db.createUser( { user: "misskey", pwd: "<password>", roles: [ { role: "readWrite", db: "misskey" } ] } )` Créez l'utilisateur misskey. | ||||
| 5. `exit` Vous avez terminé ! | ||||
|  | ||||
| *4.* Installation de Misskey | ||||
| ---------------------------------------------------------------- | ||||
| 1. `su - misskey` Basculez vers l'utilisateur misskey. | ||||
| 2. `git clone -b master git://github.com/syuilo/misskey.git` Clonez la branche master du dépôt misskey. | ||||
| 3. `cd misskey` Accédez au dossier misskey. | ||||
| 4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Télécharge la [version la plus récente](https://github.com/syuilo/misskey/releases/latest) | ||||
| 5. `npm install` Installez les dépendances de misskey. | ||||
|  | ||||
| *(optionnel)* Génération des clés VAPID | ||||
| ---------------------------------------------------------------- | ||||
| Si vous désirez activer ServiceWorker, vous devez générer les clés VAPID : | ||||
| Unless you have set your global node_modules location elsewhere, vous devez lancer ceci en mode root. | ||||
|  | ||||
| ``` shell | ||||
| npm install web-push -g | ||||
| web-push generate-vapid-keys | ||||
| ``` | ||||
|  | ||||
| *5.* Création du fichier de configuration | ||||
| ---------------------------------------------------------------- | ||||
| 1. `cp .config/example.yml .config/default.yml` Copiez le fichier `.config/example.yml` et renommez-le `default.yml`. | ||||
| 2. Editez le fichier `default.yml` | ||||
|  | ||||
| *6.* Construction de Misskey | ||||
| ---------------------------------------------------------------- | ||||
|  | ||||
| Construisez Misskey comme ceci : | ||||
|  | ||||
| `npm run build` | ||||
|  | ||||
| Si vous êtes sous Debian, vous serez amené à installer les paquets `build-essential`, `python`. | ||||
|  | ||||
| Si vous rencontrez des erreurs concernant certains modules, utilisez node-gyp: | ||||
|  | ||||
| 1. `npm install -g node-gyp` | ||||
| 2. `node-gyp configure` | ||||
| 3. `node-gyp build` | ||||
| 4. `npm run build` | ||||
|  | ||||
| *7.* C'est tout. | ||||
| ---------------------------------------------------------------- | ||||
| Excellent ! Maintenant, vous avez un environnement prêt pour lancer Misskey | ||||
|  | ||||
| ### Lancement conventionnel | ||||
| Lancez tout simplement `npm start`. Bonne chance et amusez-vous bien ! | ||||
|  | ||||
| ### Démarrage avec systemd | ||||
|  | ||||
| 1. Créez une service systemd sur : `/etc/systemd/system/misskey.service` | ||||
| 2. Editez-le puis copiez et coller ceci dans le fichier : | ||||
|  | ||||
| ``` | ||||
| [Unit] | ||||
| Description=Misskey daemon | ||||
|  | ||||
| [Service] | ||||
| Type=simple | ||||
| User=misskey | ||||
| ExecStart=/usr/bin/npm start | ||||
| WorkingDirectory=/home/misskey/misskey | ||||
| TimeoutSec=60 | ||||
| StandardOutput=syslog | ||||
| StandardError=syslog | ||||
| SyslogIdentifier=misskey | ||||
| Restart=always | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
| ``` | ||||
|  | ||||
| 3. `systemctl daemon-reload ; systemctl enable misskey` Redémarre systemd et active le service misskey. | ||||
| 4. `systemctl start misskey` Démarre le service misskey. | ||||
|  | ||||
| Vous pouvez vérifier si le service a démarré en utilisant la commande `systemctl status misskey`. | ||||
|  | ||||
| ### Méthode de mise à jour vers la plus récente version de Misskey | ||||
| 1. `git fetch` | ||||
| 2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` | ||||
| 3. `npm install` | ||||
| 4. `npm run build` | ||||
| 5. Consultez [ChangeLog](../CHANGELOG.md) pour les information de migration. | ||||
|  | ||||
| ---------------------------------------------------------------- | ||||
|  | ||||
| Si vous rencontrez des difficultés ou avez d'autres questions, n'hésitez pas à nous contacter ! | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "アンケートを破棄" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "リアクションを選択" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "カスタム絵文字" | ||||
|   people: "人" | ||||
|   animals-and-nature: "動物&自然" | ||||
|   food-and-drink: "食べ物&飲み物" | ||||
|   activity: "アクティビティ" | ||||
|   travel-and-places: "場所" | ||||
|   objects: "物" | ||||
|   symbols: "記号" | ||||
|   flags: "旗" | ||||
| common/views/components/signin.vue: | ||||
|   username: "ユーザー名" | ||||
|   password: "パスワード" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "フォロー申請" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{} のフォロワー" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしているユーザーはいません" | ||||
|   no-blocked-users: "ブロックしているユーザーはいません" | ||||
|   word-mute: "ワードミュート" | ||||
|   muted-words: "ミュートされたキーワード" | ||||
|   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
|   save: "保存" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1161,7 +1169,7 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| mobile/views/components/follow-button.vue: | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "Diese Abstimmung löschen" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "Wähle eine Reaktion aus" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "カスタム絵文字" | ||||
|   people: "人" | ||||
|   animals-and-nature: "動物&自然" | ||||
|   food-and-drink: "食べ物&飲み物" | ||||
|   activity: "アクティビティ" | ||||
|   travel-and-places: "場所" | ||||
|   objects: "物" | ||||
|   symbols: "記号" | ||||
|   flags: "旗" | ||||
| common/views/components/signin.vue: | ||||
|   username: "Benutzername" | ||||
|   password: "Passwort" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "Folge ich" | ||||
|   follow: "Folgen" | ||||
|   request-pending: "Ausstehend" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "Follower-Anfragen" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{} のフォロワー" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしているユーザーはいません" | ||||
|   no-blocked-users: "ブロックしているユーザーはいません" | ||||
|   word-mute: "ワードミュート" | ||||
|   muted-words: "ミュートされたキーワード" | ||||
|   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
|   save: "保存" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1161,7 +1169,7 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| mobile/views/components/follow-button.vue: | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "Discard the poll" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "Send a reaction" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "Custom Emoji" | ||||
|   people: "People" | ||||
|   animals-and-nature: "Animals & Nature" | ||||
|   food-and-drink: "Food & drink" | ||||
|   activity: "Activity" | ||||
|   travel-and-places: "Travel & Places" | ||||
|   objects: "Objects" | ||||
|   symbols: "Symbols" | ||||
|   flags: "Flags" | ||||
| common/views/components/signin.vue: | ||||
|   username: "Username" | ||||
|   password: "Password" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "The content is NSFW" | ||||
|   click-to-show: "Click to show" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "Following" | ||||
|   follow: "Follow" | ||||
|   request-pending: "Pending follow request" | ||||
|   follow-processing: "Processing follow" | ||||
|   follow-request: "Follow request" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{}'s followers" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "Blocking" | ||||
|   no-muted-users: "No muted users" | ||||
|   no-blocked-users: "No blocked users" | ||||
|   word-mute: "Word mute" | ||||
|   muted-words: "Muted keywords" | ||||
|   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
|   save: "Save" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "Change password" | ||||
|   enter-current-password: "Enter the current password" | ||||
| @@ -1161,11 +1169,11 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "The content is NSFW" | ||||
|   click-to-show: "Click to show" | ||||
| mobile/views/components/follow-button.vue: | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "Following" | ||||
|   follow: "Follow" | ||||
|   request-pending: "Pending follow request" | ||||
|   follow-processing: "Processing follow" | ||||
|   request-pending: "Pending" | ||||
|   follow-processing: "Processing" | ||||
|   follow-request: "Follow request" | ||||
| mobile/views/components/friends-maker.vue: | ||||
|   title: "Let's follow them" | ||||
|   | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "Cancelar la encuesta" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "Escoge una reacción" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "カスタム絵文字" | ||||
|   people: "人" | ||||
|   animals-and-nature: "動物&自然" | ||||
|   food-and-drink: "食べ物&飲み物" | ||||
|   activity: "アクティビティ" | ||||
|   travel-and-places: "場所" | ||||
|   objects: "物" | ||||
|   symbols: "記号" | ||||
|   flags: "旗" | ||||
| common/views/components/signin.vue: | ||||
|   username: "Usuario" | ||||
|   password: "Contraseña" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   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-processing: "フォロー処理中" | ||||
|   follow-request: "Solicitud de seguir" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{} seguidores" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしているユーザーはいません" | ||||
|   no-blocked-users: "ブロックしているユーザーはいません" | ||||
|   word-mute: "ワードミュート" | ||||
|   muted-words: "ミュートされたキーワード" | ||||
|   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
|   save: "保存" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1161,7 +1169,7 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| mobile/views/components/follow-button.vue: | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   | ||||
| @@ -71,7 +71,7 @@ common: | ||||
|     friday: "Vendredi" | ||||
|     saturday: "Samedi" | ||||
|   reactions: | ||||
|     like: "J'aime" | ||||
|     like: "Bien" | ||||
|     love: "Adore" | ||||
|     laugh: "Rire" | ||||
|     hmm: "Hmm … ?" | ||||
| @@ -85,8 +85,8 @@ common: | ||||
|     public: "Public" | ||||
|     home: "Principal" | ||||
|     home-desc: "Publier sur le fil principal uniquement" | ||||
|     followers: "Abonnés·es" | ||||
|     followers-desc: "Publier à vos abonnés·es uniquement" | ||||
|     followers: "Abonné·e·s" | ||||
|     followers-desc: "Publier à vos abonné·e·s uniquement" | ||||
|     specified: "Direct" | ||||
|     specified-desc: "Publier uniquement aux utilisateurs·rices mentionnés·es" | ||||
|     private: "Privé" | ||||
| @@ -99,7 +99,7 @@ common: | ||||
|     f: "En attente de vos écrits" | ||||
|   search: "Recherche" | ||||
|   delete: "Supprimer" | ||||
|   loading: "Chargement" | ||||
|   loading: "Chargement en cours …" | ||||
|   ok: "OK" | ||||
|   update-available-title: "Mise à jour disponible" | ||||
|   update-available: "Une nouvelle version de Misskey est disponible ({newer}, version actuelle: {current}). Veuillez recharger la page pour appliquer la mise à jour." | ||||
| @@ -117,8 +117,8 @@ common: | ||||
|   this-setting-is-this-device-only: "Uniquement sur cet appareil" | ||||
|   use-os-default-emojis: "Utiliser les émojis standards du système" | ||||
|   do-not-use-in-production: 'Il s’agit d’une version de développement. Ne pas utiliser dans un environnement de production.' | ||||
|   is-remote-user: "Ces informations utilisateur ont été copiées." | ||||
|   is-remote-post: "Ceci est une publication distante" | ||||
|   is-remote-user: "Ces informations appartiennent à un·e utilisateur·rice distant·e." | ||||
|   is-remote-post: "Ceci est une publication distante." | ||||
|   view-on-remote: "Consulter le profil complet" | ||||
|   error: | ||||
|     title: 'Une erreur est survenue' | ||||
| @@ -151,7 +151,7 @@ common: | ||||
|     notifications: "Notifications" | ||||
|     users: "Utilisateur·rice·s" | ||||
|     polls: "Sondages" | ||||
|     post-form: "Formulaire de publication" | ||||
|     post-form: "Champs de publication" | ||||
|     server: "Info sur le serveur" | ||||
|     donation: "Dons" | ||||
|     nav: "Navigation" | ||||
| @@ -166,7 +166,7 @@ auth/views/form.vue: | ||||
|   account-write: "Modifications des informations du compte :" | ||||
|   note-write: "Publier." | ||||
|   like-write: "Réagir aux publications." | ||||
|   following-write: "S'abonner et se désabonner." | ||||
|   following-write: "S’abonner et se désabonner." | ||||
|   drive-read: "Lire votre Drive" | ||||
|   drive-write: "Téléverser/supprimer des fichiers dans votre Drive." | ||||
|   notification-read: "Lire vos notifications." | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "Annuler ce sondage" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "Choisissez votre réaction" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "Émoji personnalisé" | ||||
|   people: "Personnes" | ||||
|   animals-and-nature: "Animaux et nature" | ||||
|   food-and-drink: "Nourriture et boisson" | ||||
|   activity: "Activités" | ||||
|   travel-and-places: "Lieux et voyages" | ||||
|   objects: "Objets" | ||||
|   symbols: "Symboles" | ||||
|   flags: "Drapeaux" | ||||
| common/views/components/signin.vue: | ||||
|   username: "Nom d'utilisateur·rice" | ||||
|   password: "Mot de passe" | ||||
| @@ -491,8 +501,8 @@ common/views/pages/follow.vue: | ||||
|   following: "Suit" | ||||
|   follow: "Suivre" | ||||
|   request-pending: "Demande d'abonnement en attente" | ||||
|   follow-processing: "En cours d’abonnement" | ||||
|   follow-request: "Demande d'abonnement" | ||||
|   follow-processing: "Demande en attente" | ||||
|   follow-request: "Demande d’abonnement" | ||||
| desktop: | ||||
|   banner-crop-title: "Découpez la partie qui apparaitra comme bannière" | ||||
|   banner: "Bannière" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "Le contenu est NSFW" | ||||
|   click-to-show: "Cliquer pour afficher" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "Abonné·e" | ||||
|   follow: "Suivre" | ||||
|   request-pending: "En attente d'approbation" | ||||
|   follow-processing: "Continuer l’abonnement" | ||||
|   follow-request: "Demande d'abonnement" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{} abonné·e·s" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "En cours blocage" | ||||
|   no-muted-users: "Aucun utilisateur·rice n’est mis·e en sourdine" | ||||
|   no-blocked-users: "Aucun utilisateur·rice n’est bloqué·e" | ||||
|   word-mute: "Filtre de mots" | ||||
|   muted-words: "Mots masqués" | ||||
|   muted-words-description: "Description des mots mis en sourdine" | ||||
|   save: "Enregistrer" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "Modifier le mot de passe" | ||||
|   enter-current-password: "Entrez votre mot de passe actuel" | ||||
| @@ -1067,7 +1075,7 @@ desktop/views/pages/user-list.users.vue: | ||||
| desktop/views/pages/user/user.followers-you-know.vue: | ||||
|   title: "Abonné·e·s que vous connaissez" | ||||
|   loading: "Chargement en cours" | ||||
|   no-users: "Pas d'utilisateurs" | ||||
|   no-users: "Aucun abonné connu" | ||||
| desktop/views/pages/user/user.friends.vue: | ||||
|   title: "Mentions fréquentes" | ||||
|   loading: "Chargement en cours" | ||||
| @@ -1079,8 +1087,8 @@ desktop/views/pages/user/user.photos.vue: | ||||
| desktop/views/pages/user/user.profile.vue: | ||||
|   follows-you: "Vous suit" | ||||
|   stalk: "Traquer" | ||||
|   stalking: "ストーキングしています" | ||||
|   unstalk: "ストーク解除" | ||||
|   stalking: "Entrain de poursuivre" | ||||
|   unstalk: "Cesser la poursuite" | ||||
|   mute: "Mettre en sourdine" | ||||
|   muted: "Muting" | ||||
|   unmute: "Enlever la sourdine" | ||||
| @@ -1161,12 +1169,12 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "Le contenu est NSFW" | ||||
|   click-to-show: "Cliquer pour afficher" | ||||
| mobile/views/components/follow-button.vue: | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "Abonné·e" | ||||
|   follow: "Suivre" | ||||
|   request-pending: "En attente d'approbation" | ||||
|   follow-processing: "En cours d’abonnement" | ||||
|   follow-request: "Demande d'abonnement" | ||||
|   follow: "S’abonner" | ||||
|   request-pending: "Demande en attente" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "フォロー申請" | ||||
| mobile/views/components/friends-maker.vue: | ||||
|   title: "Abonnez-vous aux utilisateurs" | ||||
|   empty: "Impossible de trouver des utilisateurs·trices à recommander." | ||||
| @@ -1219,7 +1227,7 @@ mobile/views/components/ui.header.vue: | ||||
| mobile/views/components/ui.nav.vue: | ||||
|   timeline: "Fil d'actualité" | ||||
|   notifications: "Notifications" | ||||
|   follow-requests: "Demandes d'abonnement" | ||||
|   follow-requests: "Demandes d’abonnement" | ||||
|   search: "Rechercher" | ||||
|   favorites: "Favoris" | ||||
|   user-lists: "Listes" | ||||
| @@ -1267,8 +1275,8 @@ mobile/views/pages/widgets/activity.vue: | ||||
| mobile/views/pages/share.vue: | ||||
|   share-with: "Partager avec {name}" | ||||
| mobile/views/pages/received-follow-requests.vue: | ||||
|   title: "Demandes d'abonnement" | ||||
|   accept: "Approuver" | ||||
|   title: "Demandes d’abonnement" | ||||
|   accept: "Accepter" | ||||
|   reject: "Refuser" | ||||
| mobile/views/pages/note.vue: | ||||
|   title: "Post" | ||||
| @@ -1363,7 +1371,7 @@ mobile/views/pages/user/home.vue: | ||||
|   followers-you-know: "Abonné·e·s que vous connaissez" | ||||
|   last-used-at: "Dernière connexion il y a" | ||||
| mobile/views/pages/user/home.followers-you-know.vue: | ||||
|   no-users: "Pas d'utilisateurs" | ||||
|   no-users: "Aucun utilisateur connu" | ||||
| mobile/views/pages/user/home.friends.vue: | ||||
|   no-users: "Pass d'utilisateurs" | ||||
| mobile/views/pages/user/home.notes.vue: | ||||
|   | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "アンケートを破棄" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "リアクションを選択" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "カスタム絵文字" | ||||
|   people: "人" | ||||
|   animals-and-nature: "動物&自然" | ||||
|   food-and-drink: "食べ物&飲み物" | ||||
|   activity: "アクティビティ" | ||||
|   travel-and-places: "場所" | ||||
|   objects: "物" | ||||
|   symbols: "記号" | ||||
|   flags: "旗" | ||||
| common/views/components/signin.vue: | ||||
|   username: "ユーザー名" | ||||
|   password: "パスワード" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "フォロー申請" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{} のフォロワー" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしているユーザーはいません" | ||||
|   no-blocked-users: "ブロックしているユーザーはいません" | ||||
|   word-mute: "ワードミュート" | ||||
|   muted-words: "ミュートされたキーワード" | ||||
|   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
|   save: "保存" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1161,7 +1169,7 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| mobile/views/components/follow-button.vue: | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   | ||||
| @@ -379,6 +379,17 @@ common/views/components/poll-editor.vue: | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "リアクションを選択" | ||||
|  | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "カスタム絵文字" | ||||
|   people: "人" | ||||
|   animals-and-nature: "動物&自然" | ||||
|   food-and-drink: "食べ物&飲み物" | ||||
|   activity: "アクティビティ" | ||||
|   travel-and-places: "場所" | ||||
|   objects: "物" | ||||
|   symbols: "記号" | ||||
|   flags: "旗" | ||||
|  | ||||
| common/views/components/signin.vue: | ||||
|   username: "ユーザー名" | ||||
|   password: "パスワード" | ||||
| @@ -657,13 +668,6 @@ desktop/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
|  | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "フォロー申請" | ||||
|  | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{} のフォロワー" | ||||
|  | ||||
| @@ -1030,6 +1034,7 @@ admin/views/index.vue: | ||||
|   dashboard: "ダッシュボード" | ||||
|   instance: "インスタンス" | ||||
|   emoji: "カスタム絵文字" | ||||
|   moderators: "モデレーター" | ||||
|   users: "ユーザー" | ||||
|   update: "更新" | ||||
|   announcements: "お知らせ" | ||||
| @@ -1129,6 +1134,12 @@ admin/views/users.vue: | ||||
|   unverify: "公式アカウントを解除する" | ||||
|   unverified: "公式アカウントを解除しました" | ||||
|  | ||||
| admin/views/moderators.vue: | ||||
|   add-moderator: | ||||
|     title: "モデレーターの登録" | ||||
|     add: "登録" | ||||
|     added: "モデレーターを登録しました" | ||||
|  | ||||
| admin/views/emoji.vue: | ||||
|   add-emoji: | ||||
|     title: "絵文字の登録" | ||||
| @@ -1325,7 +1336,7 @@ mobile/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
|  | ||||
| mobile/views/components/follow-button.vue: | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "アンケートをほかそ" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "リアクション、どれにするんや?" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "カスタム絵文字" | ||||
|   people: "人" | ||||
|   animals-and-nature: "動物&自然" | ||||
|   food-and-drink: "食べ物&飲み物" | ||||
|   activity: "アクティビティ" | ||||
|   travel-and-places: "場所" | ||||
|   objects: "物" | ||||
|   symbols: "記号" | ||||
|   flags: "旗" | ||||
| common/views/components/signin.vue: | ||||
|   username: "ユーザー名" | ||||
|   password: "パスワード" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "ちょっと見せられへんわ" | ||||
|   click-to-show: "クリックして見せるで" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "フォローしとる" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォローの許し待っとる" | ||||
|   follow-processing: "今フォロー処理やっとる‥" | ||||
|   follow-request: "フォロー許してくれや!言うてみる" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{} のフォロワー" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしとるユーザーはおらんで" | ||||
|   no-blocked-users: "ブロックしとるユーザーはおらんで" | ||||
|   word-mute: "ワードミュート" | ||||
|   muted-words: "ミュートされたキーワード" | ||||
|   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
|   save: "保存" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "パスワード変える" | ||||
|   enter-current-password: "今のパスワードを入れてや" | ||||
| @@ -1161,12 +1169,12 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "ちょっと見せられへんわ" | ||||
|   click-to-show: "押してみ、見せたるわ" | ||||
| mobile/views/components/follow-button.vue: | ||||
|   following: "フォローしとる" | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォローの許し待っとる" | ||||
|   follow-processing: "今フォロー処理やっとる‥" | ||||
|   follow-request: "フォロー許してくれや!言うてみる" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "フォロー申請" | ||||
| mobile/views/components/friends-maker.vue: | ||||
|   title: "おもろそうやな" | ||||
|   empty: "おすすめのユーザーはおらん。" | ||||
|   | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "アンケートを破棄" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "リアクションを選択" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "カスタム絵文字" | ||||
|   people: "人" | ||||
|   animals-and-nature: "動物&自然" | ||||
|   food-and-drink: "食べ物&飲み物" | ||||
|   activity: "アクティビティ" | ||||
|   travel-and-places: "場所" | ||||
|   objects: "物" | ||||
|   symbols: "記号" | ||||
|   flags: "旗" | ||||
| common/views/components/signin.vue: | ||||
|   username: "ユーザー名" | ||||
|   password: "パスワード" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "フォロー申請" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{} のフォロワー" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしているユーザーはいません" | ||||
|   no-blocked-users: "ブロックしているユーザーはいません" | ||||
|   word-mute: "ワードミュート" | ||||
|   muted-words: "ミュートされたキーワード" | ||||
|   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
|   save: "保存" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1161,7 +1169,7 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| mobile/views/components/follow-button.vue: | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "Deze peiling vernietigen" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "Kies een reactie" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "カスタム絵文字" | ||||
|   people: "人" | ||||
|   animals-and-nature: "動物&自然" | ||||
|   food-and-drink: "食べ物&飲み物" | ||||
|   activity: "アクティビティ" | ||||
|   travel-and-places: "場所" | ||||
|   objects: "物" | ||||
|   symbols: "記号" | ||||
|   flags: "旗" | ||||
| common/views/components/signin.vue: | ||||
|   username: "Gebruikersnaam" | ||||
|   password: "Wachtwoord" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "Volgen" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "フォロー申請" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "Volgers van {}" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしているユーザーはいません" | ||||
|   no-blocked-users: "ブロックしているユーザーはいません" | ||||
|   word-mute: "ワードミュート" | ||||
|   muted-words: "ミュートされたキーワード" | ||||
|   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
|   save: "保存" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1161,9 +1169,9 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| mobile/views/components/follow-button.vue: | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "Volgen" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "フォロー申請" | ||||
|   | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "アンケートを破棄" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "リアクションを選択" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "カスタム絵文字" | ||||
|   people: "人" | ||||
|   animals-and-nature: "動物&自然" | ||||
|   food-and-drink: "食べ物&飲み物" | ||||
|   activity: "アクティビティ" | ||||
|   travel-and-places: "場所" | ||||
|   objects: "物" | ||||
|   symbols: "記号" | ||||
|   flags: "旗" | ||||
| common/views/components/signin.vue: | ||||
|   username: "Brukernavn" | ||||
|   password: "Passord" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "Innholdet er NSFW" | ||||
|   click-to-show: "クリックして表示" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "Følger" | ||||
|   follow: "Følg" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "フォロー申請" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{} のフォロワー" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしているユーザーはいません" | ||||
|   no-blocked-users: "ブロックしているユーザーはいません" | ||||
|   word-mute: "ワードミュート" | ||||
|   muted-words: "ミュートされたキーワード" | ||||
|   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
|   save: "保存" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1161,9 +1169,9 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "Innholdet er NSFW" | ||||
|   click-to-show: "クリックして表示" | ||||
| mobile/views/components/follow-button.vue: | ||||
|   following: "Følger" | ||||
|   follow: "Følg" | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "フォロー申請" | ||||
|   | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "Usuń tę ankietę" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "Wybierz reakcję" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "カスタム絵文字" | ||||
|   people: "人" | ||||
|   animals-and-nature: "動物&自然" | ||||
|   food-and-drink: "食べ物&飲み物" | ||||
|   activity: "アクティビティ" | ||||
|   travel-and-places: "場所" | ||||
|   objects: "物" | ||||
|   symbols: "記号" | ||||
|   flags: "旗" | ||||
| common/views/components/signin.vue: | ||||
|   username: "Nazwa użytkownika" | ||||
|   password: "Hasło" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "To jest zawartość NSFW" | ||||
|   click-to-show: "Naciśnij aby wyświetlić" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "Śledzisz" | ||||
|   follow: "Śledź" | ||||
|   request-pending: "Oczekiwanie na pozwolenie" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "Poproś o śledzenie" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "Śledzący" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしているユーザーはいません" | ||||
|   no-blocked-users: "ブロックしているユーザーはいません" | ||||
|   word-mute: "ワードミュート" | ||||
|   muted-words: "ミュートされたキーワード" | ||||
|   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
|   save: "保存" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1161,12 +1169,12 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "To jest zawartość NSFW" | ||||
|   click-to-show: "Naciśnij aby wyświetlić" | ||||
| mobile/views/components/follow-button.vue: | ||||
|   following: "Śledzisz" | ||||
|   follow: "Śledź" | ||||
|   request-pending: "Oczekiwanie na pozwolenie" | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "Poproś o śledzenie" | ||||
|   follow-request: "フォロー申請" | ||||
| mobile/views/components/friends-maker.vue: | ||||
|   title: "Zacznij śledzić ludzi takich jak Ty" | ||||
|   empty: "Nie znaleziono podobnych użytkowników." | ||||
|   | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "アンケートを破棄" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "リアクションを選択" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "カスタム絵文字" | ||||
|   people: "人" | ||||
|   animals-and-nature: "動物&自然" | ||||
|   food-and-drink: "食べ物&飲み物" | ||||
|   activity: "アクティビティ" | ||||
|   travel-and-places: "場所" | ||||
|   objects: "物" | ||||
|   symbols: "記号" | ||||
|   flags: "旗" | ||||
| common/views/components/signin.vue: | ||||
|   username: "ユーザー名" | ||||
|   password: "パスワード" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "フォロー申請" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{} のフォロワー" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしているユーザーはいません" | ||||
|   no-blocked-users: "ブロックしているユーザーはいません" | ||||
|   word-mute: "ワードミュート" | ||||
|   muted-words: "ミュートされたキーワード" | ||||
|   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
|   save: "保存" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1161,7 +1169,7 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| mobile/views/components/follow-button.vue: | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "アンケートを破棄" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "リアクションを選択" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "カスタム絵文字" | ||||
|   people: "人" | ||||
|   animals-and-nature: "動物&自然" | ||||
|   food-and-drink: "食べ物&飲み物" | ||||
|   activity: "アクティビティ" | ||||
|   travel-and-places: "場所" | ||||
|   objects: "物" | ||||
|   symbols: "記号" | ||||
|   flags: "旗" | ||||
| common/views/components/signin.vue: | ||||
|   username: "ユーザー名" | ||||
|   password: "パスワード" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "フォロー申請" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{} のフォロワー" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしているユーザーはいません" | ||||
|   no-blocked-users: "ブロックしているユーザーはいません" | ||||
|   word-mute: "ワードミュート" | ||||
|   muted-words: "ミュートされたキーワード" | ||||
|   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
|   save: "保存" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1161,7 +1169,7 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| mobile/views/components/follow-button.vue: | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   | ||||
| @@ -344,6 +344,16 @@ common/views/components/poll-editor.vue: | ||||
|   destroy: "アンケートを破棄" | ||||
| common/views/components/reaction-picker.vue: | ||||
|   choose-reaction: "リアクションを選択" | ||||
| common/views/components/emoji-picker.vue: | ||||
|   custom-emoji: "カスタム絵文字" | ||||
|   people: "人" | ||||
|   animals-and-nature: "動物&自然" | ||||
|   food-and-drink: "食べ物&飲み物" | ||||
|   activity: "アクティビティ" | ||||
|   travel-and-places: "場所" | ||||
|   objects: "物" | ||||
|   symbols: "記号" | ||||
|   flags: "旗" | ||||
| common/views/components/signin.vue: | ||||
|   username: "ユーザー名" | ||||
|   password: "パスワード" | ||||
| @@ -588,12 +598,6 @@ desktop/views/components/media-image.vue: | ||||
| desktop/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| desktop/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   follow-processing: "フォロー処理中" | ||||
|   follow-request: "フォロー申請" | ||||
| desktop/views/components/followers-window.vue: | ||||
|   followers: "{} のフォロワー" | ||||
| desktop/views/components/followers.vue: | ||||
| @@ -829,6 +833,10 @@ common/views/components/mute-and-block.vue: | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしているユーザーはいません" | ||||
|   no-blocked-users: "ブロックしているユーザーはいません" | ||||
|   word-mute: "ワードミュート" | ||||
|   muted-words: "ミュートされたキーワード" | ||||
|   muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" | ||||
|   save: "保存" | ||||
| common/views/components/password-settings.vue: | ||||
|   reset: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1161,7 +1169,7 @@ mobile/views/components/media-image.vue: | ||||
| mobile/views/components/media-video.vue: | ||||
|   sensitive: "閲覧注意" | ||||
|   click-to-show: "クリックして表示" | ||||
| mobile/views/components/follow-button.vue: | ||||
| common/views/components/follow-button.vue: | ||||
|   following: "フォロー中" | ||||
|   follow: "フォロー" | ||||
|   request-pending: "フォロー許可待ち" | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| { | ||||
| 	"name": "misskey", | ||||
| 	"author": "syuilo <i@syuilo.com>", | ||||
| 	"version": "10.48.0", | ||||
| 	"clientVersion": "2.0.11733", | ||||
| 	"version": "10.50.0", | ||||
| 	"clientVersion": "2.0.11794", | ||||
| 	"codename": "nighthike", | ||||
| 	"main": "./built/index.js", | ||||
| 	"private": true, | ||||
| @@ -198,6 +198,7 @@ | ||||
| 		"summaly": "2.2.0", | ||||
| 		"systeminformation": "3.47.0", | ||||
| 		"syuilo-password-strength": "0.0.1", | ||||
| 		"terser-webpack-plugin": "1.1.0", | ||||
| 		"textarea-caret": "3.1.0", | ||||
| 		"tinycolor2": "1.4.1", | ||||
| 		"tmp": "0.0.33", | ||||
| @@ -220,7 +221,6 @@ | ||||
| 		"vue-router": "3.0.1", | ||||
| 		"vue-style-loader": "4.1.2", | ||||
| 		"vue-svg-inline-loader": "1.2.1", | ||||
| 		"vue-sweetalert2": "1.5.7", | ||||
| 		"vue-template-compiler": "2.5.17", | ||||
| 		"vuedraggable": "2.16.0", | ||||
| 		"vuewordcloud": "18.7.11", | ||||
|   | ||||
| @@ -66,19 +66,6 @@ export default abstract class Chart<T> { | ||||
| 		} else { | ||||
| 			this.collection.createIndex({ span: -1, date: -1 }, { unique: true }); | ||||
| 		} | ||||
|  | ||||
| 		//#region 後方互換性のため | ||||
| 		this.collection.find({ span: 'day' }, { fields: { _id: true, date: true } }).then(logs => { | ||||
| 			logs.forEach(log => { | ||||
| 				this.collection.update({ _id: log._id }, { $set: { date: utc(log.date).hour(0).toDate() } }); | ||||
| 			}); | ||||
| 		}); | ||||
| 		this.collection.find({ span: 'hour' }, { fields: { _id: true, date: true } }).then(logs => { | ||||
| 			logs.forEach(log => { | ||||
| 				this.collection.update({ _id: log._id }, { $set: { date: utc(log.date).toDate() } }); | ||||
| 			}); | ||||
| 		}); | ||||
| 		//#endregion | ||||
| 	} | ||||
|  | ||||
| 	@autobind | ||||
|   | ||||
| @@ -48,15 +48,15 @@ export default Vue.extend({ | ||||
| 		}, | ||||
|  | ||||
| 		remove(i) { | ||||
| 			this.$swal({ | ||||
| 			this.$root.alert({ | ||||
| 				type: 'warning', | ||||
| 				text: this.$t('_remove.are-you-sure').replace('$1', this.announcements.find((_, j) => j == i).title), | ||||
| 				showCancelButton: true | ||||
| 			}).then(res => { | ||||
| 				if (!res.value) return; | ||||
| 				if (!res) return; | ||||
| 				this.announcements = this.announcements.filter((_, j) => j !== i); | ||||
| 				this.save(true); | ||||
| 				this.$swal({ | ||||
| 				this.$root.alert({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('_remove.removed') | ||||
| 				}); | ||||
| @@ -68,13 +68,13 @@ export default Vue.extend({ | ||||
| 				broadcasts: this.announcements | ||||
| 			}).then(() => { | ||||
| 				if (!silent) { | ||||
| 					this.$swal({ | ||||
| 					this.$root.alert({ | ||||
| 						type: 'success', | ||||
| 						text: this.$t('saved') | ||||
| 					}); | ||||
| 				} | ||||
| 			}).catch(e => { | ||||
| 				this.$swal({ | ||||
| 				this.$root.alert({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}); | ||||
|   | ||||
| @@ -3,17 +3,17 @@ | ||||
| 	<table> | ||||
| 		<thead> | ||||
| 			<tr> | ||||
| 				<th><fa icon="exchange-alt"/> In/Out</th> | ||||
| 				<th><fa :icon="faExchangeAlt"/> In/Out</th> | ||||
| 				<th><fa :icon="faBolt"/> Activity</th> | ||||
| 				<th><fa icon="server"/> Host</th> | ||||
| 				<th><fa icon="bolt"/> Activity</th> | ||||
| 				<th><fa icon="user"/> Actor</th> | ||||
| 			</tr> | ||||
| 		</thead> | ||||
| 		<tbody> | ||||
| 			<tr v-for="log in logs" :key="log.id"> | ||||
| 				<td :class="log.direction">{{ log.direction == 'in' ? '<' : '>' }} {{ log.direction }}</td> | ||||
| 				<td>{{ log.host }}</td> | ||||
| 				<td>{{ log.activity }}</td> | ||||
| 				<td>{{ log.host }}</td> | ||||
| 				<td>@{{ log.actor }}</td> | ||||
| 			</tr> | ||||
| 		</tbody> | ||||
| @@ -23,12 +23,14 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import { faBolt, faExchangeAlt } from '@fortawesome/free-solid-svg-icons'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			logs: [], | ||||
| 			connection: null | ||||
| 			connection: null, | ||||
| 			faBolt, faExchangeAlt | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
|   | ||||
| @@ -37,7 +37,7 @@ | ||||
| 		</div> | ||||
| 		<div> | ||||
| 			<div> | ||||
| 				<div><fa icon="database"/></div> | ||||
| 				<div><fa :icon="faDatabase"/></div> | ||||
| 				<div> | ||||
| 					<span>{{ $t('drive') }}</span> | ||||
| 					<b>{{ stats.driveUsageLocal | bytes }}</b> | ||||
| @@ -83,9 +83,11 @@ import i18n from '../../i18n'; | ||||
| import XCpuMemory from "./cpu-memory.vue"; | ||||
| import XCharts from "./charts.vue"; | ||||
| import XApLog from "./ap-log.vue"; | ||||
| import { faDatabase } from '@fortawesome/free-solid-svg-icons'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('admin/views/dashboard.vue'), | ||||
|  | ||||
| 	components: { | ||||
| 		XCpuMemory, | ||||
| 		XCharts, | ||||
| @@ -96,7 +98,8 @@ export default Vue.extend({ | ||||
| 		return { | ||||
| 			stats: null, | ||||
| 			connection: null, | ||||
| 			meta: null | ||||
| 			meta: null, | ||||
| 			faDatabase | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
| 	</ui-card> | ||||
|  | ||||
| 	<ui-card> | ||||
| 		<div slot="title"><fa :icon="['far', 'grin']"/> {{ $t('emojis.title') }}</div> | ||||
| 		<div slot="title"><fa :icon="faGrin"/> {{ $t('emojis.title') }}</div> | ||||
| 		<section v-for="emoji in emojis"> | ||||
| 			<img :src="emoji.url" :alt="emoji.name" style="width: 64px;"/> | ||||
| 			<ui-horizon-group inputs> | ||||
| @@ -50,6 +50,7 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../i18n'; | ||||
| import { faGrin } from '@fortawesome/free-regular-svg-icons'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('admin/views/emoji.vue'), | ||||
| @@ -58,7 +59,8 @@ export default Vue.extend({ | ||||
| 			name: '', | ||||
| 			url: '', | ||||
| 			aliases: '', | ||||
| 			emojis: [] | ||||
| 			emojis: [], | ||||
| 			faGrin | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| @@ -73,13 +75,13 @@ export default Vue.extend({ | ||||
| 				url: this.url, | ||||
| 				aliases: this.aliases.split(' ').filter(x => x.length > 0) | ||||
| 			}).then(() => { | ||||
| 				this.$swal({ | ||||
| 				this.$root.alert({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('add-emoji.added') | ||||
| 				}); | ||||
| 				this.fetchEmojis(); | ||||
| 			}).catch(e => { | ||||
| 				this.$swal({ | ||||
| 				this.$root.alert({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}); | ||||
| @@ -101,12 +103,12 @@ export default Vue.extend({ | ||||
| 				url: emoji.url, | ||||
| 				aliases: emoji.aliases.split(' ').filter(x => x.length > 0) | ||||
| 			}).then(() => { | ||||
| 				this.$swal({ | ||||
| 				this.$root.alert({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('updated') | ||||
| 				}); | ||||
| 			}).catch(e => { | ||||
| 				this.$swal({ | ||||
| 				this.$root.alert({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}); | ||||
| @@ -114,23 +116,23 @@ export default Vue.extend({ | ||||
| 		}, | ||||
|  | ||||
| 		removeEmoji(emoji) { | ||||
| 			this.$swal({ | ||||
| 			this.$root.alert({ | ||||
| 				type: 'warning', | ||||
| 				text: this.$t('remove-emoji.are-you-sure').replace('$1', emoji.name), | ||||
| 				showCancelButton: true | ||||
| 			}).then(res => { | ||||
| 				if (!res.value) return; | ||||
| 				if (!res) return; | ||||
|  | ||||
| 				this.$root.api('admin/emoji/remove', { | ||||
| 					id: emoji.id | ||||
| 				}).then(() => { | ||||
| 					this.$swal({ | ||||
| 					this.$root.alert({ | ||||
| 						type: 'success', | ||||
| 						text: this.$t('remove-emoji.removed') | ||||
| 					}); | ||||
| 					this.fetchEmojis(); | ||||
| 				}).catch(e => { | ||||
| 					this.$swal({ | ||||
| 					this.$root.alert({ | ||||
| 						type: 'error', | ||||
| 						text: e | ||||
| 					}); | ||||
|   | ||||
| @@ -20,8 +20,9 @@ | ||||
| 		<ul> | ||||
| 			<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }"><fa icon="home" fixed-width/>{{ $t('dashboard') }}</li> | ||||
| 			<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li> | ||||
| 			<li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li> | ||||
| 			<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li> | ||||
| 			<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="['far', 'grin']" fixed-width/>{{ $t('emoji') }}</li> | ||||
| 			<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</li> | ||||
| 			<li @click="nav('announcements')" :class="{ active: page == 'announcements' }"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</li> | ||||
| 			<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }"><fa icon="hashtag" fixed-width/>{{ $t('hashtags') }}</li> | ||||
|  | ||||
| @@ -29,7 +30,7 @@ | ||||
| 			<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">{{ $t('update') }}</li> --> | ||||
| 		</ul> | ||||
| 		<div class="back-to-misskey"> | ||||
| 			<a href="/"><fa icon="arrow-left"/> {{ $t('back-to-misskey') }}</a> | ||||
| 			<a href="/"><fa :icon="faArrowLeft"/> {{ $t('back-to-misskey') }}</a> | ||||
| 		</div> | ||||
| 		<div class="version"> | ||||
| 			<small>Misskey {{ version }}</small> | ||||
| @@ -38,6 +39,7 @@ | ||||
| 	<main> | ||||
| 		<div v-if="page == 'dashboard'"><x-dashboard/></div> | ||||
| 		<div v-if="page == 'instance'"><x-instance/></div> | ||||
| 		<div v-if="page == 'moderators'"><x-moderators/></div> | ||||
| 		<div v-if="page == 'users'"><x-users/></div> | ||||
| 		<div v-if="page == 'emoji'"><x-emoji/></div> | ||||
| 		<div v-if="page == 'announcements'"><x-announcements/></div> | ||||
| @@ -54,10 +56,13 @@ import i18n from '../../i18n'; | ||||
| import { version } from '../../config'; | ||||
| import XDashboard from "./dashboard.vue"; | ||||
| import XInstance from "./instance.vue"; | ||||
| import XModerators from "./moderators.vue"; | ||||
| import XEmoji from "./emoji.vue"; | ||||
| import XAnnouncements from "./announcements.vue"; | ||||
| import XHashtags from "./hashtags.vue"; | ||||
| import XUsers from "./users.vue"; | ||||
| import { faHeadset, faArrowLeft } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faGrin } from '@fortawesome/free-regular-svg-icons'; | ||||
|  | ||||
| // Detect the user agent | ||||
| const ua = navigator.userAgent.toLowerCase(); | ||||
| @@ -68,6 +73,7 @@ export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XDashboard, | ||||
| 		XInstance, | ||||
| 		XModerators, | ||||
| 		XEmoji, | ||||
| 		XAnnouncements, | ||||
| 		XHashtags, | ||||
| @@ -81,7 +87,10 @@ export default Vue.extend({ | ||||
| 			page: 'dashboard', | ||||
| 			version, | ||||
| 			isMobile, | ||||
| 			navOpend: !isMobile | ||||
| 			navOpend: !isMobile, | ||||
| 			faGrin, | ||||
| 			faArrowLeft, | ||||
| 			faHeadset | ||||
| 		}; | ||||
| 	}, | ||||
| 	methods: { | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
| 			<ui-input v-model="languages"><i slot="icon"><fa icon="language"/></i>{{ $t('languages') }}<span slot="desc">{{ $t('languages-desc') }}</span></ui-input> | ||||
| 		</section> | ||||
| 		<section class="fit-bottom"> | ||||
| 			<header><fa icon="headset"/> {{ $t('maintainer-config') }}</header> | ||||
| 			<header><fa :icon="faHeadset"/> {{ $t('maintainer-config') }}</header> | ||||
| 			<ui-input v-model="maintainerName">{{ $t('maintainer-name') }}</ui-input> | ||||
| 			<ui-input v-model="maintainerEmail" type="email"><i slot="icon"><fa :icon="['far', 'envelope']"/></i>{{ $t('maintainer-email') }}</ui-input> | ||||
| 		</section> | ||||
| @@ -24,14 +24,14 @@ | ||||
| 			<ui-input v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles">{{ $t('remote-drive-capacity-mb') }}<span slot="suffix">MB</span><span slot="desc">{{ $t('mb') }}</span></ui-input> | ||||
| 		</section> | ||||
| 		<section class="fit-bottom"> | ||||
| 			<header><fa icon="shield-alt"/> {{ $t('recaptcha-config') }}</header> | ||||
| 			<header><fa :icon="faShieldAlt"/> {{ $t('recaptcha-config') }}</header> | ||||
| 			<ui-switch v-model="enableRecaptcha">{{ $t('enable-recaptcha') }}</ui-switch> | ||||
| 			<ui-info>{{ $t('recaptcha-info') }}</ui-info> | ||||
| 			<ui-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><i slot="icon"><fa icon="key"/></i>{{ $t('recaptcha-site-key') }}</ui-input> | ||||
| 			<ui-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><i slot="icon"><fa icon="key"/></i>{{ $t('recaptcha-secret-key') }}</ui-input> | ||||
| 		</section> | ||||
| 		<section> | ||||
| 			<header><fa icon="ghost"/> {{ $t('proxy-account-config') }}</header> | ||||
| 			<header><fa :icon="faGhost"/> {{ $t('proxy-account-config') }}</header> | ||||
| 			<ui-info>{{ $t('proxy-account-info') }}</ui-info> | ||||
| 			<ui-input v-model="proxyAccount"><span slot="prefix">@</span>{{ $t('proxy-account-username') }}<span slot="desc">{{ $t('proxy-account-username-desc') }}</span></ui-input> | ||||
| 			<ui-info warn>{{ $t('proxy-account-warn') }}</ui-info> | ||||
| @@ -84,9 +84,11 @@ import Vue from 'vue'; | ||||
| import i18n from '../../i18n'; | ||||
| import { host } from '../../config'; | ||||
| import { toUnicode } from 'punycode'; | ||||
| import { faHeadset, faShieldAlt, faGhost } from '@fortawesome/free-solid-svg-icons'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('admin/views/instance.vue'), | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			host: toUnicode(host), | ||||
| @@ -113,6 +115,7 @@ export default Vue.extend({ | ||||
| 			githubClientSecret: null, | ||||
| 			proxyAccount: null, | ||||
| 			inviteCode: null, | ||||
| 			faHeadset, faShieldAlt, faGhost | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| @@ -146,7 +149,7 @@ export default Vue.extend({ | ||||
| 			this.$root.api('admin/invite').then(x => { | ||||
| 				this.inviteCode = x.code; | ||||
| 			}).catch(e => { | ||||
| 				this.$swal({ | ||||
| 				this.$root.alert({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}); | ||||
| @@ -178,12 +181,12 @@ export default Vue.extend({ | ||||
| 				githubClientId: this.githubClientId, | ||||
| 				githubClientSecret: this.githubClientSecret, | ||||
| 			}).then(() => { | ||||
| 				this.$swal({ | ||||
| 				this.$root.alert({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('saved') | ||||
| 				}); | ||||
| 			}).catch(e => { | ||||
| 				this.$swal({ | ||||
| 				this.$root.alert({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				}); | ||||
|   | ||||
							
								
								
									
										61
									
								
								src/client/app/admin/views/moderators.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/client/app/admin/views/moderators.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| <template> | ||||
| <div class="jnhmugbb"> | ||||
| 	<ui-card> | ||||
| 		<div slot="title"><fa icon="plus"/> {{ $t('add-moderator.title') }}</div> | ||||
| 		<section class="fit-top"> | ||||
| 			<ui-input v-model="username" type="text"> | ||||
| 				<span slot="prefix">@</span> | ||||
| 			</ui-input> | ||||
| 			<ui-button @click="add" :disabled="adding">{{ $t('add-moderator.add') }}</ui-button> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../i18n'; | ||||
| import parseAcct from "../../../../misc/acct/parse"; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('admin/views/moderators.vue'), | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			username: '', | ||||
| 			adding: false | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		async add() { | ||||
| 			this.adding = true; | ||||
|  | ||||
| 			const process = async () => { | ||||
| 				const user = await this.$root.api('users/show', parseAcct(this.username)); | ||||
| 				await this.$root.api('admin/moderators/add', { userId: user.id }); | ||||
| 				this.$root.alert({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('add-moderator.added') | ||||
| 				}); | ||||
| 			}; | ||||
|  | ||||
| 			await process().catch(e => { | ||||
| 				this.$root.alert({ | ||||
| 					type: 'error', | ||||
| 					text: e.toString() | ||||
| 				}); | ||||
| 			}); | ||||
|  | ||||
| 			this.adding = false; | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .jnhmugbb | ||||
| 	@media (min-width 500px) | ||||
| 		padding 16px | ||||
|  | ||||
| </style> | ||||
| @@ -49,6 +49,7 @@ import parseAcct from "../../../../misc/acct/parse"; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('admin/views/users.vue'), | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			verifyUsername: null, | ||||
| @@ -67,13 +68,19 @@ export default Vue.extend({ | ||||
| 			this.verifying = true; | ||||
|  | ||||
| 			const process = async () => { | ||||
| 				const user = await this.$root.os.api('users/show', parseAcct(this.verifyUsername)); | ||||
| 				await this.$root.os.api('admin/verify-user', { userId: user.id }); | ||||
| 				//this.$root.os.apis.dialog({ text: this.$t('verified') }); | ||||
| 				const user = await this.$root.api('users/show', parseAcct(this.verifyUsername)); | ||||
| 				await this.$root.api('admin/verify-user', { userId: user.id }); | ||||
| 				this.$root.alert({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('verified') | ||||
| 				}); | ||||
| 			}; | ||||
|  | ||||
| 			await process().catch(e => { | ||||
| 				//this.$root.os.apis.dialog({ text: `Failed: ${e}` }); | ||||
| 				this.$root.alert({ | ||||
| 					type: 'error', | ||||
| 					text: e.toString() | ||||
| 				}); | ||||
| 			}); | ||||
|  | ||||
| 			this.verifying = false; | ||||
| @@ -83,13 +90,19 @@ export default Vue.extend({ | ||||
| 			this.unverifying = true; | ||||
|  | ||||
| 			const process = async () => { | ||||
| 				const user = await this.$root.os.api('users/show', parseAcct(this.unverifyUsername)); | ||||
| 				await this.$root.os.api('admin/unverify-user', { userId: user.id }); | ||||
| 				//this.$root.os.apis.dialog({ text: this.$t('unverified') }); | ||||
| 				const user = await this.$root.api('users/show', parseAcct(this.unverifyUsername)); | ||||
| 				await this.$root.api('admin/unverify-user', { userId: user.id }); | ||||
| 				this.$root.alert({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('unverified') | ||||
| 				}); | ||||
| 			}; | ||||
|  | ||||
| 			await process().catch(e => { | ||||
| 				//this.$root.os.apis.dialog({ text: `Failed: ${e}` }); | ||||
| 				this.$root.alert({ | ||||
| 					type: 'error', | ||||
| 					text: e.toString() | ||||
| 				}); | ||||
| 			}); | ||||
|  | ||||
| 			this.unverifying = false; | ||||
| @@ -99,13 +112,19 @@ export default Vue.extend({ | ||||
| 			this.suspending = true; | ||||
|  | ||||
| 			const process = async () => { | ||||
| 				const user = await this.$root.os.api('users/show', parseAcct(this.suspendUsername)); | ||||
| 				await this.$root.os.api('admin/suspend-user', { userId: user.id }); | ||||
| 				//this.$root.os.apis.dialog({ text: this.$t('suspended') }); | ||||
| 				const user = await this.$root.api('users/show', parseAcct(this.suspendUsername)); | ||||
| 				await this.$root.api('admin/suspend-user', { userId: user.id }); | ||||
| 				this.$root.alert({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('suspended') | ||||
| 				}); | ||||
| 			}; | ||||
|  | ||||
| 			await process().catch(e => { | ||||
| 				//this.$root.os.apis.dialog({ text: `Failed: ${e}` }); | ||||
| 				this.$root.alert({ | ||||
| 					type: 'error', | ||||
| 					text: e.toString() | ||||
| 				}); | ||||
| 			}); | ||||
|  | ||||
| 			this.suspending = false; | ||||
| @@ -115,13 +134,19 @@ export default Vue.extend({ | ||||
| 			this.unsuspending = true; | ||||
|  | ||||
| 			const process = async () => { | ||||
| 				const user = await this.$root.os.api('users/show', parseAcct(this.unsuspendUsername)); | ||||
| 				await this.$root.os.api('admin/unsuspend-user', { userId: user.id }); | ||||
| 				//this.$root.os.apis.dialog({ text: this.$t('unsuspended') }); | ||||
| 				const user = await this.$root.api('users/show', parseAcct(this.unsuspendUsername)); | ||||
| 				await this.$root.api('admin/unsuspend-user', { userId: user.id }); | ||||
| 				this.$root.alert({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('unsuspended') | ||||
| 				}); | ||||
| 			}; | ||||
|  | ||||
| 			await process().catch(e => { | ||||
| 				//this.$root.os.apis.dialog({ text: `Failed: ${e}` }); | ||||
| 				this.$root.alert({ | ||||
| 					type: 'error', | ||||
| 					text: e.toString() | ||||
| 				}); | ||||
| 			}); | ||||
|  | ||||
| 			this.unsuspending = false; | ||||
|   | ||||
| @@ -123,29 +123,3 @@ pre | ||||
|  | ||||
| [data-icon] | ||||
| 	display inline-block | ||||
|  | ||||
| .swal2-container | ||||
| 	z-index 10000 !important | ||||
|  | ||||
| 	&.swal2-shown | ||||
| 		background-color rgba(0, 0, 0, 0.5) !important | ||||
|  | ||||
| .swal2-popup | ||||
| 	background var(--face) !important | ||||
|  | ||||
| .swal2-content | ||||
| 	color var(--text) !important | ||||
|  | ||||
| .swal2-confirm | ||||
| 	background-color var(--primary) !important | ||||
| 	border-left-color var(--primary) !important | ||||
| 	border-right-color var(--primary) !important | ||||
| 	color var(--primaryForeground) !important | ||||
|  | ||||
| 	&:hover | ||||
| 		background-image none !important | ||||
| 		background-color var(--primaryDarken5) !important | ||||
|  | ||||
| 	&:active | ||||
| 		background-image none !important | ||||
| 		background-color var(--primaryDarken5) !important | ||||
|   | ||||
| @@ -9,14 +9,11 @@ import './style.styl'; | ||||
|  | ||||
| import init from '../init'; | ||||
| import Index from './views/index.vue'; | ||||
| import * as config from '../config'; | ||||
|  | ||||
| /** | ||||
|  * init | ||||
|  */ | ||||
| init(launch => { | ||||
| 	document.title = `${config.name} | %i18n:common.application-authorization%`; | ||||
|  | ||||
| 	// Init router | ||||
| 	const router = new VueRouter({ | ||||
| 		mode: 'history', | ||||
|   | ||||
| @@ -146,6 +146,8 @@ | ||||
| 	function refresh() { | ||||
| 		localStorage.setItem('shouldFlush', 'false'); | ||||
|  | ||||
| 		localStorage.removeItem('locale'); | ||||
|  | ||||
| 		// Random | ||||
| 		localStorage.setItem('salt', Math.random().toString().substr(2, 8)); | ||||
|  | ||||
|   | ||||
| @@ -66,7 +66,7 @@ export default function<T extends object>(data: { | ||||
|  | ||||
| 				this.bakeProps(); | ||||
|  | ||||
| 				(this as any).api('i/update_widget', { | ||||
| 				this.$root.api('i/update_widget', { | ||||
| 					id: this.id, | ||||
| 					data: this.props | ||||
| 				}); | ||||
|   | ||||
| @@ -22,7 +22,7 @@ export default async function($root: any, force = false, silent = false) { | ||||
| 		} | ||||
|  | ||||
| 		if (!silent) { | ||||
| 			$root.$dialog({ | ||||
| 			$root.alert({ | ||||
| 				title: $root.$t('@.update-available-title'), | ||||
| 				text: $root.$t('@.update-available', { newer, current }) | ||||
| 			}); | ||||
|   | ||||
| @@ -4,12 +4,9 @@ export default ($root: any) => { | ||||
| 	require('fuckadblock'); | ||||
|  | ||||
| 	function adBlockDetected() { | ||||
| 		$root.$dialog({ | ||||
| 		$root.alert({ | ||||
| 			title: $root.$t('@.adblock.detected'), | ||||
| 			text: $root.$t('@.adblock.warning'), | ||||
| 			actins: [{ | ||||
| 				text: 'OK' | ||||
| 			}] | ||||
| 			text: $root.$t('@.adblock.warning') | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import { sum } from '../../../../prelude/array'; | ||||
| import shouldMuteNote from './should-mute-note'; | ||||
| import MkNoteMenu from '../views/components/note-menu.vue'; | ||||
| import MkReactionPicker from '../views/components/reaction-picker.vue'; | ||||
| import Ok from '../views/components/ok.vue'; | ||||
|  | ||||
| function focus(el, fn) { | ||||
| 	const target = fn(el); | ||||
| @@ -142,7 +141,10 @@ export default (opts: Opts = {}) => ({ | ||||
| 			this.$root.api('notes/favorites/create', { | ||||
| 				noteId: this.appearNote.id | ||||
| 			}).then(() => { | ||||
| 				this.$root.new(Ok); | ||||
| 				this.$root.alert({ | ||||
| 					type: 'success', | ||||
| 					splash: true | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
|   | ||||
							
								
								
									
										197
									
								
								src/client/app/common/views/components/alert.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								src/client/app/common/views/components/alert.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,197 @@ | ||||
| <template> | ||||
| <div class="felqjxyj" :class="{ splash }"> | ||||
| 	<div class="bg" ref="bg" @click="onBgClick"></div> | ||||
| 	<div class="main" ref="main"> | ||||
| 		<div class="icon" :class="type"><fa :icon="icon"/></div> | ||||
| 		<header v-if="title" v-html="title"></header> | ||||
| 		<div class="body" v-if="text" v-html="text"></div> | ||||
| 		<ui-horizon-group no-grow class="buttons" v-if="!splash"> | ||||
| 			<ui-button @click="ok" primary autofocus>OK</ui-button> | ||||
| 			<ui-button @click="cancel" v-if="showCancelButton">Cancel</ui-button> | ||||
| 		</ui-horizon-group> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import * as anime from 'animejs'; | ||||
| import { faTimesCircle, faQuestionCircle } from '@fortawesome/free-regular-svg-icons'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		type: { | ||||
| 			type: String, | ||||
| 			required: false, | ||||
| 			default: 'info' | ||||
| 		}, | ||||
| 		title: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 		}, | ||||
| 		text: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 		}, | ||||
| 		showCancelButton: { | ||||
| 			type: Boolean, | ||||
| 			default: false | ||||
| 		}, | ||||
| 		splash: { | ||||
| 			type: Boolean, | ||||
| 			default: false | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		icon(): any { | ||||
| 			switch (this.type) { | ||||
| 				case 'success': return 'check'; | ||||
| 				case 'error': return faTimesCircle; | ||||
| 				case 'warning': return 'exclamation-triangle'; | ||||
| 				case 'info': return 'info-circle'; | ||||
| 				case 'question': return faQuestionCircle; | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		this.$nextTick(() => { | ||||
| 			(this.$refs.bg as any).style.pointerEvents = 'auto'; | ||||
| 			anime({ | ||||
| 				targets: this.$refs.bg, | ||||
| 				opacity: 1, | ||||
| 				duration: 100, | ||||
| 				easing: 'linear' | ||||
| 			}); | ||||
|  | ||||
| 			anime({ | ||||
| 				targets: this.$refs.main, | ||||
| 				opacity: 1, | ||||
| 				scale: [1.2, 1], | ||||
| 				duration: 300, | ||||
| 				easing: [0, 0.5, 0.5, 1] | ||||
| 			}); | ||||
|  | ||||
| 			if (this.splash) { | ||||
| 				setTimeout(() => { | ||||
| 					this.close(); | ||||
| 				}, 1000); | ||||
| 			} | ||||
| 		}); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		ok() { | ||||
| 			this.$emit('ok'); | ||||
| 			this.close(); | ||||
| 		}, | ||||
|  | ||||
| 		cancel() { | ||||
| 			this.$emit('cancel'); | ||||
| 			this.close(); | ||||
| 		}, | ||||
|  | ||||
| 		close() { | ||||
| 			(this.$refs.bg as any).style.pointerEvents = 'none'; | ||||
| 			anime({ | ||||
| 				targets: this.$refs.bg, | ||||
| 				opacity: 0, | ||||
| 				duration: 300, | ||||
| 				easing: 'linear' | ||||
| 			}); | ||||
|  | ||||
| 			(this.$refs.main as any).style.pointerEvents = 'none'; | ||||
| 			anime({ | ||||
| 				targets: this.$refs.main, | ||||
| 				opacity: 0, | ||||
| 				scale: 0.8, | ||||
| 				duration: 300, | ||||
| 				easing: [0, 0.5, 0.5, 1], | ||||
| 				complete: () => this.destroyDom() | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		onBgClick() { | ||||
| 			this.cancel(); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .felqjxyj | ||||
| 	display flex | ||||
| 	align-items center | ||||
| 	justify-content center | ||||
| 	position fixed | ||||
| 	z-index 30000 | ||||
| 	top 0 | ||||
| 	left 0 | ||||
| 	width 100% | ||||
| 	height 100% | ||||
|  | ||||
| 	&.splash | ||||
| 		&, * | ||||
| 			pointer-events none !important | ||||
|  | ||||
| 		> .main | ||||
| 			min-width 0 | ||||
| 			width initial | ||||
|  | ||||
| 	> .bg | ||||
| 		display block | ||||
| 		position fixed | ||||
| 		top 0 | ||||
| 		left 0 | ||||
| 		width 100% | ||||
| 		height 100% | ||||
| 		background rgba(#000, 0.7) | ||||
| 		opacity 0 | ||||
| 		pointer-events none | ||||
|  | ||||
| 	> .main | ||||
| 		display block | ||||
| 		position fixed | ||||
| 		margin auto | ||||
| 		padding 32px | ||||
| 		min-width 320px | ||||
| 		max-width 480px | ||||
| 		width calc(100% - 32px) | ||||
| 		text-align center | ||||
| 		background var(--face) | ||||
| 		border-radius 8px | ||||
| 		color var(--faceText) | ||||
| 		opacity 0 | ||||
|  | ||||
| 		> .icon | ||||
| 			font-size 32px | ||||
|  | ||||
| 			&.success | ||||
| 				color #37ec92 | ||||
|  | ||||
| 			&.error | ||||
| 				color #ec4137 | ||||
|  | ||||
| 			&.warning | ||||
| 				color #ecb637 | ||||
|  | ||||
| 			> * | ||||
| 				display block | ||||
| 				margin 0 auto | ||||
|  | ||||
| 		> header | ||||
| 			margin 16px 0 8px 0 | ||||
| 			font-weight bold | ||||
| 			font-size 20px | ||||
|  | ||||
| 			& + .body | ||||
| 				margin-top 8px | ||||
|  | ||||
| 		> .body | ||||
| 			margin 16px 0 | ||||
|  | ||||
| 		> .buttons | ||||
| 			margin-top 16px | ||||
|  | ||||
| </style> | ||||
							
								
								
									
										202
									
								
								src/client/app/common/views/components/emoji-picker.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								src/client/app/common/views/components/emoji-picker.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
| <template> | ||||
| <div class="prlncendiewqqkrevzeruhndoakghvtx"> | ||||
| 	<header> | ||||
| 		<button v-for="category in categories" | ||||
| 			:title="category.text" | ||||
| 			@click="go(category.ref)" | ||||
| 			:class="{ active: category.isActive }" | ||||
| 		> | ||||
| 			<fa :icon="category.icon" fixed-width/> | ||||
| 		</button> | ||||
| 	</header> | ||||
| 	<div class="emojis" ref="emojis" @scroll.passive="onScroll"> | ||||
| 		<section v-for="category in categories" :ref="category.ref"> | ||||
| 			<header><fa :icon="category.icon" fixed-width/> {{ category.text }}</header> | ||||
| 			<div v-if="category.name"> | ||||
| 				<button v-for="emoji in Object.entries(lib).filter(([k, v]) => v.category === category.name)" | ||||
| 					:title="emoji[0]" | ||||
| 					@click="chosen(emoji[1].char)" | ||||
| 				> | ||||
| 					<mk-emoji :emoji="emoji[1].char"/> | ||||
| 				</button> | ||||
| 			</div> | ||||
| 			<div v-else> | ||||
| 				<button v-for="emoji in customEmojis" | ||||
| 					:title="emoji.name" | ||||
| 					@click="chosen(`:${emoji.name}:`)" | ||||
| 				> | ||||
| 					<img :src="emoji.url" :alt="emoji.name"/> | ||||
| 				</button> | ||||
| 			</div> | ||||
| 		</section> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../../i18n'; | ||||
| import { lib } from 'emojilib'; | ||||
| import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faHeart, faFlag } from '@fortawesome/free-regular-svg-icons'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('common/views/components/emoji-picker.vue'), | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			lib, | ||||
| 			customEmojis: [], | ||||
| 			categories: [{ | ||||
| 				ref: 'customEmojiSection', | ||||
| 				text: this.$t('custom-emoji'), | ||||
| 				icon: faAsterisk, | ||||
| 				isActive: true | ||||
| 			}, { | ||||
| 				name: 'people', | ||||
| 				ref: 'peopleSection', | ||||
| 				text: this.$t('people'), | ||||
| 				icon: ['far', 'laugh'], | ||||
| 				isActive: false | ||||
| 			}, { | ||||
| 				name: 'animals_and_nature', | ||||
| 				ref: 'animalsAndNatureSection', | ||||
| 				text: this.$t('animals-and-nature'), | ||||
| 				icon: faLeaf, | ||||
| 				isActive: false | ||||
| 			}, { | ||||
| 				name: 'food_and_drink', | ||||
| 				ref: 'foodAndDrinkSection', | ||||
| 				text: this.$t('food-and-drink'), | ||||
| 				icon: faUtensils, | ||||
| 				isActive: false | ||||
| 			}, { | ||||
| 				name: 'activity', | ||||
| 				ref: 'activitySection', | ||||
| 				text: this.$t('activity'), | ||||
| 				icon: faFutbol, | ||||
| 				isActive: false | ||||
| 			}, { | ||||
| 				name: 'travel_and_places', | ||||
| 				ref: 'travelAndPlacesSection', | ||||
| 				text: this.$t('travel-and-places'), | ||||
| 				icon: faCity, | ||||
| 				isActive: false | ||||
| 			}, { | ||||
| 				name: 'objects', | ||||
| 				ref: 'objectsSection', | ||||
| 				text: this.$t('objects'), | ||||
| 				icon: faDice, | ||||
| 				isActive: false | ||||
| 			}, { | ||||
| 				name: 'symbols', | ||||
| 				ref: 'symbolsSection', | ||||
| 				text: this.$t('symbols'), | ||||
| 				icon: faHeart, | ||||
| 				isActive: false | ||||
| 			}, { | ||||
| 				name: 'flags', | ||||
| 				ref: 'flagsSection', | ||||
| 				text: this.$t('flags'), | ||||
| 				icon: faFlag, | ||||
| 				isActive: false | ||||
| 			}] | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	created() { | ||||
| 		this.customEmojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || []; | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		go(ref) { | ||||
| 			this.$refs.emojis.scrollTop = this.$refs[ref][0].offsetTop; | ||||
| 		}, | ||||
|  | ||||
| 		onScroll(e) { | ||||
| 			const section = this.categories.forEach(x => { | ||||
| 				const top = e.target.scrollTop; | ||||
| 				const el = this.$refs[x.ref][0]; | ||||
| 				x.isActive = el.offsetTop <= top && el.offsetTop + el.offsetHeight > top; | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		chosen(emoji) { | ||||
| 			this.$emit('chosen', emoji); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .prlncendiewqqkrevzeruhndoakghvtx | ||||
| 	width 350px | ||||
| 	background var(--face) | ||||
|  | ||||
| 	> header | ||||
| 		display flex | ||||
|  | ||||
| 		> button | ||||
| 			flex 1 | ||||
| 			padding 10px 0 | ||||
| 			font-size 16px | ||||
| 			color var(--text) | ||||
| 			transition color 0.2s ease | ||||
|  | ||||
| 			&:hover | ||||
| 				color var(--textHighlighted) | ||||
| 				transition color 0s | ||||
|  | ||||
| 			&.active | ||||
| 				color var(--primary) | ||||
| 				transition color 0s | ||||
|  | ||||
| 	> .emojis | ||||
| 		height 300px | ||||
| 		overflow-y auto | ||||
| 		overflow-x hidden | ||||
|  | ||||
| 		> section | ||||
| 			> header | ||||
| 				position sticky | ||||
| 				top 0 | ||||
| 				left 0 | ||||
| 				z-index 1 | ||||
| 				padding 8px | ||||
| 				background var(--faceHeader) | ||||
| 				color var(--text) | ||||
| 				font-size 12px | ||||
|  | ||||
| 			> div | ||||
| 				display grid | ||||
| 				grid-template-columns 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr | ||||
| 				gap 4px | ||||
| 				padding 8px | ||||
|  | ||||
| 				> button | ||||
| 					padding 0 | ||||
| 					width 100% | ||||
|  | ||||
| 					&:before | ||||
| 						content '' | ||||
| 						display block | ||||
| 						width 1px | ||||
| 						height 0 | ||||
| 						padding-bottom 100% | ||||
|  | ||||
| 					&:hover | ||||
| 						> * | ||||
| 							transform scale(1.2) | ||||
| 							transition transform 0s | ||||
|  | ||||
| 					> * | ||||
| 						position absolute | ||||
| 						top 0 | ||||
| 						left 0 | ||||
| 						width 100% | ||||
| 						height 100% | ||||
| 						font-size 28px | ||||
| 						transition transform 0.2s ease | ||||
| 						pointer-events none | ||||
|  | ||||
| </style> | ||||
| @@ -22,7 +22,7 @@ export default Vue.extend({ | ||||
| 		}, | ||||
| 		customEmojis: { | ||||
| 			required: false, | ||||
| 			default: [] | ||||
| 			default: () => [] | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
|   | ||||
							
								
								
									
										184
									
								
								src/client/app/common/views/components/follow-button.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								src/client/app/common/views/components/follow-button.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,184 @@ | ||||
| <template> | ||||
| <button class="wfliddvnhxvyusikowhxozkyxyenqxqr" | ||||
| 	:class="{ wait, block, mini, active: isFollowing || hasPendingFollowRequestFromYou }" | ||||
| 	@click="onClick" | ||||
| 	:disabled="wait" | ||||
| > | ||||
| 	<template v-if="!wait"> | ||||
| 		<fa :icon="iconAndText[0]"/> <template v-if="!mini">{{ iconAndText[1] }}</template> | ||||
| 	</template> | ||||
| 	<template v-else><fa icon="spinner" pulse fixed-width/></template> | ||||
| </button> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../../i18n'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('common/views/components/follow-button.vue'), | ||||
|  | ||||
| 	props: { | ||||
| 		user: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		block: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 		mini: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			isFollowing: this.user.isFollowing, | ||||
| 			hasPendingFollowRequestFromYou: this.user.hasPendingFollowRequestFromYou, | ||||
| 			wait: false, | ||||
| 			connection: null | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		iconAndText(): any[] { | ||||
| 			return ( | ||||
| 				(this.hasPendingFollowRequestFromYou && this.user.isLocked) ? ['hourglass-half', this.$t('request-pending')] : | ||||
| 				(this.hasPendingFollowRequestFromYou && !this.user.isLocked) ? ['hourglass-start', this.$t('follow-processing')] : | ||||
| 				(this.isFollowing) ? ['minus', this.$t('following')] : | ||||
| 				(!this.isFollowing && this.user.isLocked) ? ['plus', this.$t('follow-request')] : | ||||
| 				(!this.isFollowing && !this.user.isLocked) ? ['plus', this.$t('follow')] : | ||||
| 				[] | ||||
| 			); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		this.connection = this.$root.stream.useSharedConnection('main'); | ||||
|  | ||||
| 		this.connection.on('follow', this.onFollowChange); | ||||
| 		this.connection.on('unfollow', this.onFollowChange); | ||||
| 	}, | ||||
|  | ||||
| 	beforeDestroy() { | ||||
| 		this.connection.dispose(); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		onFollowChange(user) { | ||||
| 			if (user.id == this.user.id) { | ||||
| 				this.isFollowing = user.isFollowing; | ||||
| 				this.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		async onClick() { | ||||
| 			this.wait = true; | ||||
|  | ||||
| 			try { | ||||
| 				if (this.isFollowing) { | ||||
| 					await this.$root.api('following/delete', { | ||||
| 						userId: this.user.id | ||||
| 					}); | ||||
| 				} else { | ||||
| 					if (this.hasPendingFollowRequestFromYou) { | ||||
| 						await this.$root.api('following/requests/cancel', { | ||||
| 							userId: this.user.id | ||||
| 						}); | ||||
| 					} else if (this.user.isLocked) { | ||||
| 						await this.$root.api('following/create', { | ||||
| 							userId: this.user.id | ||||
| 						}); | ||||
| 						this.hasPendingFollowRequestFromYou = true; | ||||
| 					} else { | ||||
| 						await this.$root.api('following/create', { | ||||
| 							userId: this.user.id | ||||
| 						}); | ||||
| 						this.hasPendingFollowRequestFromYou = true; | ||||
| 					} | ||||
| 				} | ||||
| 			} catch (e) { | ||||
| 				console.error(e); | ||||
| 			} finally { | ||||
| 				this.wait = false; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .wfliddvnhxvyusikowhxozkyxyenqxqr | ||||
| 	display block | ||||
| 	user-select none | ||||
| 	cursor pointer | ||||
| 	padding 0 16px | ||||
| 	margin 0 | ||||
| 	min-width 100px | ||||
| 	line-height 36px | ||||
| 	font-size 14px | ||||
| 	font-weight bold | ||||
| 	color var(--primary) | ||||
| 	background transparent | ||||
| 	outline none | ||||
| 	border solid 1px var(--primary) | ||||
| 	border-radius 36px | ||||
|  | ||||
| 	&.mini | ||||
| 		padding 0 | ||||
| 		min-width 0 | ||||
| 		width 32px | ||||
| 		height 32px | ||||
| 		font-size 16px | ||||
| 		border-radius 4px | ||||
| 		line-height 32px | ||||
|  | ||||
| 		&:focus | ||||
| 			&:after | ||||
| 				border-radius 8px | ||||
|  | ||||
| 	&.block | ||||
| 		width 100% | ||||
|  | ||||
| 	&:focus | ||||
| 		&:after | ||||
| 			content "" | ||||
| 			pointer-events none | ||||
| 			position absolute | ||||
| 			top -5px | ||||
| 			right -5px | ||||
| 			bottom -5px | ||||
| 			left -5px | ||||
| 			border 2px solid var(--primaryAlpha03) | ||||
| 			border-radius 36px | ||||
|  | ||||
| 	&:hover | ||||
| 		background var(--primaryAlpha01) | ||||
|  | ||||
| 	&:active | ||||
| 		background var(--primaryAlpha02) | ||||
|  | ||||
| 	&.active | ||||
| 		color var(--primaryForeground) | ||||
| 		background var(--primary) | ||||
|  | ||||
| 		&:hover | ||||
| 			background var(--primaryLighten10) | ||||
| 			border-color var(--primaryLighten10) | ||||
|  | ||||
| 		&:active | ||||
| 			background var(--primaryDarken10) | ||||
| 			border-color var(--primaryDarken10) | ||||
|  | ||||
| 	&.wait | ||||
| 		cursor wait !important | ||||
| 		opacity 0.7 | ||||
|  | ||||
| 	* | ||||
| 		pointer-events none | ||||
|  | ||||
| </style> | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <div class="mk-github-setting"> | ||||
| 	<p>{{ $t('description') }}<a :href="`${docsUrl}/link-to-github`" target="_blank">{{ $t('detail') }}</a></p> | ||||
| 	<p>{{ $t('description') }}</p> | ||||
| 	<p class="account" v-if="$store.state.i.github" :title="`GitHub ID: ${$store.state.i.github.id}`">{{ $t('connected-to') }}: <a :href="`https://github.com/${$store.state.i.github.login}`" target="_blank">@{{ $store.state.i.github.login }}</a></p> | ||||
| 	<p> | ||||
| 		<a :href="`${apiUrl}/connect/github`" target="_blank" @click.prevent="connect">{{ $store.state.i.github ? this.$t('reconnect') : this.$t('connect') }}</a> | ||||
| @@ -14,15 +14,14 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../../i18n'; | ||||
| import { apiUrl, docsUrl } from '../../../config'; | ||||
| import { apiUrl } from '../../../config'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('common/views/components/github-setting.vue'), | ||||
| 	data() { | ||||
| 		return { | ||||
| 			form: null, | ||||
| 			apiUrl, | ||||
| 			docsUrl | ||||
| 			apiUrl | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
|   | ||||
| @@ -1,13 +1,8 @@ | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| import muteAndBlock from './mute-and-block.vue'; | ||||
| import followButton from './follow-button.vue'; | ||||
| import error from './error.vue'; | ||||
| import apiSettings from './api-settings.vue'; | ||||
| import passwordSettings from './password-settings.vue'; | ||||
| import driveSettings from './drive-settings.vue'; | ||||
| import profileEditor from './profile-editor.vue'; | ||||
| import noteSkeleton from './note-skeleton.vue'; | ||||
| import theme from './theme.vue'; | ||||
| import instance from './instance.vue'; | ||||
| import cwButton from './cw-button.vue'; | ||||
| import tagCloud from './tag-cloud.vue'; | ||||
| @@ -27,7 +22,6 @@ import pollEditor from './poll-editor.vue'; | ||||
| import reactionIcon from './reaction-icon.vue'; | ||||
| import reactionsViewer from './reactions-viewer.vue'; | ||||
| import time from './time.vue'; | ||||
| import timer from './timer.vue'; | ||||
| import mediaList from './media-list.vue'; | ||||
| import uploader from './uploader.vue'; | ||||
| import streamIndicator from './stream-indicator.vue'; | ||||
| @@ -51,14 +45,9 @@ import uiInfo from './ui/info.vue'; | ||||
| import formButton from './ui/form/button.vue'; | ||||
| import formRadio from './ui/form/radio.vue'; | ||||
|  | ||||
| Vue.component('mk-mute-and-block', muteAndBlock); | ||||
| Vue.component('mk-follow-button', followButton); | ||||
| Vue.component('mk-error', error); | ||||
| Vue.component('mk-api-settings', apiSettings); | ||||
| Vue.component('mk-password-settings', passwordSettings); | ||||
| Vue.component('mk-drive-settings', driveSettings); | ||||
| Vue.component('mk-profile-editor', profileEditor); | ||||
| Vue.component('mk-note-skeleton', noteSkeleton); | ||||
| Vue.component('mk-theme', theme); | ||||
| Vue.component('mk-instance', instance); | ||||
| Vue.component('mk-cw-button', cwButton); | ||||
| Vue.component('mk-tag-cloud', tagCloud); | ||||
| @@ -78,7 +67,6 @@ Vue.component('mk-poll-editor', pollEditor); | ||||
| Vue.component('mk-reaction-icon', reactionIcon); | ||||
| Vue.component('mk-reactions-viewer', reactionsViewer); | ||||
| Vue.component('mk-time', time); | ||||
| Vue.component('mk-timer', timer); | ||||
| Vue.component('mk-media-list', mediaList); | ||||
| Vue.component('mk-uploader', uploader); | ||||
| Vue.component('mk-stream-indicator', streamIndicator); | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| 		<p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>{{ $t('empty') }}</p> | ||||
| 		<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa icon="flag"/>{{ $t('no-history') }}</p> | ||||
| 		<button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages"> | ||||
| 			<template v-if="fetchingMoreMessages"><fa icon="spinner .pulse" fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }} | ||||
| 			<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }} | ||||
| 		</button> | ||||
| 		<template v-for="(message, i) in _messages"> | ||||
| 			<x-message :message="message" :key="message.id"/> | ||||
| @@ -20,7 +20,7 @@ | ||||
| 	<footer> | ||||
| 		<transition name="fade"> | ||||
| 			<div class="new-message" v-show="showIndicator"> | ||||
| 				<button @click="onIndicatorClick"><i><fa icon="arrow-circle-down"/></i>{{ $t('new-message') }}</button> | ||||
| 				<button @click="onIndicatorClick"><i><fa :icon="faArrowCircleDown"/></i>{{ $t('new-message') }}</button> | ||||
| 			</div> | ||||
| 		</transition> | ||||
| 		<x-form :user="user" ref="form"/> | ||||
| @@ -34,6 +34,7 @@ import i18n from '../../../i18n'; | ||||
| import XMessage from './messaging-room.message.vue'; | ||||
| import XForm from './messaging-room.form.vue'; | ||||
| import { url } from '../../../config'; | ||||
| import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('common/views/components/messaging-room.vue'), | ||||
| @@ -52,7 +53,8 @@ export default Vue.extend({ | ||||
| 			existMoreMessages: false, | ||||
| 			connection: null, | ||||
| 			showIndicator: false, | ||||
| 			timer: null | ||||
| 			timer: null, | ||||
| 			faArrowCircleDown | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
|   | ||||
| @@ -45,7 +45,7 @@ | ||||
| 		</template> | ||||
| 	</div> | ||||
| 	<p class="no-history" v-if="!fetching && messages.length == 0">{{ $t('no-history') }}</p> | ||||
| 	<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 	<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,6 @@ import Vue from 'vue'; | ||||
| import i18n from '../../../i18n'; | ||||
| import { url } from '../../../config'; | ||||
| import copyToClipboard from '../../../common/scripts/copy-to-clipboard'; | ||||
| import Ok from './ok.vue'; | ||||
| import { concat, intersperse } from '../../../../../prelude/array'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| @@ -56,7 +55,7 @@ export default Vue.extend({ | ||||
| 							} | ||||
| 					] : [] | ||||
| 				], [ | ||||
| 					this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin ? [{ | ||||
| 					this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin || this.$store.state.i.isModerator ? [{ | ||||
| 						icon: ['far', 'trash-alt'], | ||||
| 						text: this.$t('delete'), | ||||
| 						action: this.del | ||||
| @@ -79,7 +78,10 @@ export default Vue.extend({ | ||||
| 			this.$root.api('i/pin', { | ||||
| 				noteId: this.note.id | ||||
| 			}).then(() => { | ||||
| 				this.$root.new(Ok); | ||||
| 				this.$root.alert({ | ||||
| 					type: 'success', | ||||
| 					splash: true | ||||
| 				}); | ||||
| 				this.destroyDom(); | ||||
| 			}); | ||||
| 		}, | ||||
| @@ -93,19 +95,29 @@ export default Vue.extend({ | ||||
| 		}, | ||||
|  | ||||
| 		del() { | ||||
| 			if (!window.confirm(this.$t('delete-confirm'))) return; | ||||
| 			this.$root.alert({ | ||||
| 				type: 'warning', | ||||
| 				text: this.$t('delete-confirm'), | ||||
| 				showCancelButton: true | ||||
| 			}).then(res => { | ||||
| 				if (!res) return; | ||||
|  | ||||
| 				this.$root.api('notes/delete', { | ||||
| 					noteId: this.note.id | ||||
| 				}).then(() => { | ||||
| 					this.destroyDom(); | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		favorite() { | ||||
| 			this.$root.api('notes/favorites/create', { | ||||
| 				noteId: this.note.id | ||||
| 			}).then(() => { | ||||
| 				this.$root.new(Ok); | ||||
| 				this.$root.alert({ | ||||
| 					type: 'success', | ||||
| 					splash: true | ||||
| 				}); | ||||
| 				this.destroyDom(); | ||||
| 			}); | ||||
| 		}, | ||||
| @@ -114,7 +126,10 @@ export default Vue.extend({ | ||||
| 			this.$root.api('notes/favorites/delete', { | ||||
| 				noteId: this.note.id | ||||
| 			}).then(() => { | ||||
| 				this.$root.new(Ok); | ||||
| 				this.$root.alert({ | ||||
| 					type: 'success', | ||||
| 					splash: true | ||||
| 				}); | ||||
| 				this.destroyDom(); | ||||
| 			}); | ||||
| 		}, | ||||
|   | ||||
| @@ -1,175 +0,0 @@ | ||||
| <template> | ||||
| <div class="yvbkymdqeusiqucuuloahhiqflzinufs"> | ||||
| 	<div class="bg" ref="bg"></div> | ||||
| 	<div class="body" ref="body"> | ||||
| 		<div class="icon"> | ||||
| 			<div class="circle left"></div> | ||||
| 			<span class="check tip"></span> | ||||
| 			<span class="check long"></span> | ||||
| 			<div class="ring"></div> | ||||
| 			<div class="fix"></div> | ||||
| 			<div class="circle right"></div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import * as anime from 'animejs'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	mounted() { | ||||
| 		this.$nextTick(() => { | ||||
| 			anime({ | ||||
| 				targets: this.$refs.bg, | ||||
| 				opacity: 1, | ||||
| 				duration: 300, | ||||
| 				easing: 'linear' | ||||
| 			}); | ||||
|  | ||||
| 			anime({ | ||||
| 				targets: this.$refs.body, | ||||
| 				opacity: 1, | ||||
| 				scale: [1.2, 1], | ||||
| 				duration: 300, | ||||
| 				easing: [0, 0.5, 0.5, 1] | ||||
| 			}); | ||||
| 		}); | ||||
|  | ||||
| 		setTimeout(() => { | ||||
| 			anime({ | ||||
| 				targets: this.$refs.bg, | ||||
| 				opacity: 0, | ||||
| 				duration: 300, | ||||
| 				easing: 'linear' | ||||
| 			}); | ||||
|  | ||||
| 			anime({ | ||||
| 				targets: this.$refs.body, | ||||
| 				opacity: 0, | ||||
| 				scale: 0.8, | ||||
| 				duration: 300, | ||||
| 				easing: [0.5, 0, 1, 0.5], | ||||
| 				complete: () => this.destroyDom() | ||||
| 			}); | ||||
| 		}, 1250); | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .yvbkymdqeusiqucuuloahhiqflzinufs | ||||
| 	pointer-events none | ||||
|  | ||||
| 	> .bg | ||||
| 		display block | ||||
| 		position fixed | ||||
| 		z-index 10000 | ||||
| 		top 0 | ||||
| 		left 0 | ||||
| 		width 100% | ||||
| 		height 100% | ||||
| 		background rgba(#000, 0.7) | ||||
| 		opacity 0 | ||||
|  | ||||
| 	> .body | ||||
| 		position fixed | ||||
| 		z-index 10000 | ||||
| 		top 0 | ||||
| 		right 0 | ||||
| 		left 0 | ||||
| 		bottom 0 | ||||
| 		margin auto | ||||
| 		width 150px | ||||
| 		height 150px | ||||
| 		background var(--face) | ||||
| 		border-radius 8px | ||||
| 		opacity 0 | ||||
|  | ||||
| 		> .icon | ||||
| 			display flex | ||||
| 			justify-content center | ||||
| 			position absolute | ||||
| 			top 0 | ||||
| 			right 0 | ||||
| 			left 0 | ||||
| 			bottom 0 | ||||
| 			width 5em | ||||
| 			height 5em | ||||
| 			margin auto | ||||
| 			border .25em solid transparent | ||||
| 			border-radius 50% | ||||
| 			line-height 5em | ||||
| 			cursor default | ||||
| 			box-sizing content-box | ||||
| 			user-select none | ||||
| 			zoom normal | ||||
| 			border-color #a5dc86 | ||||
|  | ||||
| 			> .circle | ||||
| 				position absolute | ||||
| 				width 3.75em | ||||
| 				height 7.5em | ||||
| 				transform rotate(45deg) | ||||
| 				border-radius 50% | ||||
| 				background var(--face) | ||||
|  | ||||
| 				&.left | ||||
| 					top -.4375em | ||||
| 					left -2.0635em | ||||
| 					transform rotate(-45deg) | ||||
| 					transform-origin 3.75em 3.75em | ||||
| 					border-radius 7.5em 0 0 7.5em | ||||
|  | ||||
| 				&.right | ||||
| 					top -.6875em | ||||
| 					left 1.875em | ||||
| 					transform rotate(-45deg) | ||||
| 					transform-origin 0 3.75em | ||||
| 					border-radius 0 7.5em 7.5em 0 | ||||
| 					animation swal2-rotate-success-circular-line 4.25s ease-in | ||||
|  | ||||
| 			> .check | ||||
| 				display block | ||||
| 				position absolute | ||||
| 				height .3125em | ||||
| 				border-radius .125em | ||||
| 				background-color #a5dc86 | ||||
| 				z-index 2 | ||||
|  | ||||
| 				&.tip | ||||
| 					top 2.875em | ||||
| 					left .875em | ||||
| 					width 1.5625em | ||||
| 					transform rotate(45deg) | ||||
| 					animation swal2-animate-success-line-tip .75s | ||||
|  | ||||
| 				&.long | ||||
| 					top 2.375em | ||||
| 					right .5em | ||||
| 					width 2.9375em | ||||
| 					transform rotate(-45deg) | ||||
| 					animation swal2-animate-success-line-long .75s | ||||
|  | ||||
| 			> .fix | ||||
| 				position absolute | ||||
| 				top .5em | ||||
| 				left 1.625em | ||||
| 				width .4375em | ||||
| 				height 5.625em | ||||
| 				transform rotate(-45deg) | ||||
| 				z-index 1 | ||||
| 				background var(--face) | ||||
|  | ||||
| 			> .ring | ||||
| 				position absolute | ||||
| 				top -.25em | ||||
| 				left -.25em | ||||
| 				width 100% | ||||
| 				height 100% | ||||
| 				border .25em solid rgba(165,220,134,.3) | ||||
| 				border-radius 50% | ||||
| 				z-index 2 | ||||
| 				box-sizing content-box | ||||
| </style> | ||||
| @@ -25,12 +25,9 @@ export default Vue.extend({ | ||||
| 						type: 'password' | ||||
| 					}).then(newPassword2 => { | ||||
| 						if (newPassword !== newPassword2) { | ||||
| 							this.$dialog({ | ||||
| 							this.$root.alert({ | ||||
| 								title: null, | ||||
| 								text: this.$t('not-match'), | ||||
| 								actions: [{ | ||||
| 									text: 'OK' | ||||
| 								}] | ||||
| 								text: this.$t('not-match') | ||||
| 							}); | ||||
| 							return; | ||||
| 						} | ||||
|   | ||||
| @@ -193,7 +193,7 @@ export default Vue.extend({ | ||||
| 				this.$store.state.i.bannerUrl = i.bannerUrl; | ||||
|  | ||||
| 				if (notify) { | ||||
| 					this.$swal({ | ||||
| 					this.$root.alert({ | ||||
| 						type: 'success', | ||||
| 						text: this.$t('saved') | ||||
| 					}); | ||||
| @@ -223,6 +223,5 @@ export default Vue.extend({ | ||||
| 			width 72px | ||||
| 			height 72px | ||||
| 			margin auto | ||||
| 			box-shadow 0 0 16px rgba(0, 0, 0, 0.5) | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
| 			<span>{{ $t('username') }}</span> | ||||
| 			<span slot="prefix">@</span> | ||||
| 			<span slot="suffix">@{{ host }}</span> | ||||
| 			<p slot="desc" v-if="usernameState == 'wait'" style="color:#999"><fa icon="spinner .pulse" fixed-width/> {{ $t('checking') }}</p> | ||||
| 			<p slot="desc" v-if="usernameState == 'wait'" style="color:#999"><fa icon="spinner" pulse fixed-width/> {{ $t('checking') }}</p> | ||||
| 			<p slot="desc" v-if="usernameState == 'ok'" style="color:#3CB7B5"><fa icon="check" fixed-width/> {{ $t('available') }}</p> | ||||
| 			<p slot="desc" v-if="usernameState == 'unavailable'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> {{ $t('unavailable') }}</p> | ||||
| 			<p slot="desc" v-if="usernameState == 'error'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> {{ $t('error') }}</p> | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| <template> | ||||
| <div class="mk-stream-indicator"> | ||||
| 	<p v-if="stream.state == 'initializing'"> | ||||
| 		<fa icon="spinner .pulse"/> | ||||
| 		<fa icon="spinner" pulse/> | ||||
| 		<span>{{ $t('connecting') }}<mk-ellipsis/></span> | ||||
| 	</p> | ||||
| 	<p v-if="stream.state == 'reconnecting'"> | ||||
| 		<fa icon="spinner .pulse"/> | ||||
| 		<fa icon="spinner" pulse/> | ||||
| 		<span>{{ $t('reconnecting') }}<mk-ellipsis/></span> | ||||
| 	</p> | ||||
| 	<p v-if="stream.state == 'connected'"> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <div class="jtivnzhfwquxpsfidertopbmwmchmnmo"> | ||||
| 	<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 	<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 	<p class="empty" v-else-if="tags.length == 0"><fa icon="exclamation-circle"/>{{ $t('empty') }}</p> | ||||
| 	<div v-else> | ||||
| 		<vue-word-cloud | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="nicnklzforebnpfgasiypmpdaaglujqm"> | ||||
| 	<label> | ||||
| 		<span>{{ $t('light-theme') }}</span> | ||||
| 		<span><fa :icon="faSun"/> {{ $t('light-theme') }}</span> | ||||
| 		<ui-select v-model="light" :placeholder="$t('light-theme')"> | ||||
| 			<optgroup :label="$t('light-themes')"> | ||||
| 				<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option> | ||||
| @@ -13,7 +13,7 @@ | ||||
| 	</label> | ||||
|  | ||||
| 	<label> | ||||
| 		<span>{{ $t('dark-theme') }}</span> | ||||
| 		<span><fa :icon="faMoon"/> {{ $t('dark-theme') }}</span> | ||||
| 		<ui-select v-model="dark" :placeholder="$t('dark-theme')"> | ||||
| 			<optgroup :label="$t('dark-themes')"> | ||||
| 				<option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option> | ||||
| @@ -104,6 +104,7 @@ import { Chrome } from 'vue-color'; | ||||
| import * as uuid from 'uuid'; | ||||
| import * as tinycolor from 'tinycolor2'; | ||||
| import * as JSON5 from 'json5'; | ||||
| import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons'; | ||||
|  | ||||
| // 後方互換性のため | ||||
| function convertOldThemedefinition(t) { | ||||
| @@ -135,7 +136,8 @@ export default Vue.extend({ | ||||
| 			myThemeDesc: '', | ||||
| 			myThemePrimary: lightTheme.vars.primary, | ||||
| 			myThemeSecondary: lightTheme.vars.secondary, | ||||
| 			myThemeText: lightTheme.vars.text | ||||
| 			myThemeText: lightTheme.vars.text, | ||||
| 			faMoon, faSun | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| @@ -221,7 +223,7 @@ export default Vue.extend({ | ||||
| 			try { | ||||
| 				theme = JSON5.parse(code); | ||||
| 			} catch (e) { | ||||
| 				this.$swal({ | ||||
| 				this.$root.alert({ | ||||
| 					type: 'error', | ||||
| 					text: this.$t('invalid-theme') | ||||
| 				}); | ||||
| @@ -234,7 +236,7 @@ export default Vue.extend({ | ||||
| 			} | ||||
|  | ||||
| 			if (theme.id == null) { | ||||
| 				this.$swal({ | ||||
| 				this.$root.alert({ | ||||
| 					type: 'error', | ||||
| 					text: this.$t('invalid-theme') | ||||
| 				}); | ||||
| @@ -242,7 +244,7 @@ export default Vue.extend({ | ||||
| 			} | ||||
|  | ||||
| 			if (this.$store.state.device.themes.some(t => t.id == theme.id)) { | ||||
| 				this.$swal({ | ||||
| 				this.$root.alert({ | ||||
| 					type: 'info', | ||||
| 					text: this.$t('already-installed') | ||||
| 				}); | ||||
| @@ -254,7 +256,7 @@ export default Vue.extend({ | ||||
| 				key: 'themes', value: themes | ||||
| 			}); | ||||
|  | ||||
| 			this.$swal({ | ||||
| 			this.$root.alert({ | ||||
| 				type: 'success', | ||||
| 				text: this.$t('installed').replace('{}', theme.name) | ||||
| 			}); | ||||
| @@ -267,7 +269,7 @@ export default Vue.extend({ | ||||
| 				key: 'themes', value: themes | ||||
| 			}); | ||||
|  | ||||
| 			this.$swal({ | ||||
| 			this.$root.alert({ | ||||
| 				type: 'info', | ||||
| 				text: this.$t('uninstalled').replace('{}', theme.name) | ||||
| 			}); | ||||
| @@ -304,7 +306,7 @@ export default Vue.extend({ | ||||
| 			const theme = this.myTheme; | ||||
|  | ||||
| 			if (theme.name == null || theme.name.trim() == '') { | ||||
| 				this.$swal({ | ||||
| 				this.$root.alert({ | ||||
| 					type: 'warning', | ||||
| 					text: this.$t('theme-name-required') | ||||
| 				}); | ||||
| @@ -318,7 +320,7 @@ export default Vue.extend({ | ||||
| 				key: 'themes', value: themes | ||||
| 			}); | ||||
|  | ||||
| 			this.$swal({ | ||||
| 			this.$root.alert({ | ||||
| 				type: 'success', | ||||
| 				text: this.$t('saved') | ||||
| 			}); | ||||
|   | ||||
| @@ -1,49 +0,0 @@ | ||||
| <template> | ||||
| <time class="mk-time"> | ||||
| 	{{ hh }}:{{ mm }}:{{ ss }} | ||||
| </time> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		time: { | ||||
| 			type: [Date, String], | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			tickId: null, | ||||
| 			hh: null, | ||||
| 			mm: null, | ||||
| 			ss: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		_time(): Date { | ||||
| 			return typeof this.time == 'string' ? new Date(this.time) : this.time; | ||||
| 		} | ||||
| 	}, | ||||
| 	created() { | ||||
| 		this.tick(); | ||||
| 		this.tickId = setInterval(this.tick, 1000); | ||||
| 	}, | ||||
| 	destroyed() { | ||||
| 		clearInterval(this.tickId); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		tick() { | ||||
| 			const now = new Date().getTime(); | ||||
| 			const start = this._time.getTime(); | ||||
| 			const ago = Math.floor((now - start) / 1000); | ||||
|  | ||||
| 			this.hh = Math.floor(ago / (60 * 60)).toString().padStart(2, '0'); | ||||
| 			this.mm = Math.floor(ago / 60).toString().padStart(2, '0'); | ||||
| 			this.ss = (ago % 60).toString().padStart(2, '0'); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <div class="csqvmxybqbycalfhkxvyfrgbrdalkaoc"> | ||||
| 	<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 	<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 	<p class="empty" v-else-if="stats.length == 0"><fa icon="exclamation-circle"/>{{ $t('empty') }}</p> | ||||
| 	<!-- トランジションを有効にするとなぜかメモリリークする --> | ||||
| 	<transition-group v-else tag="div" name="chart"> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <div class="mk-twitter-setting"> | ||||
| 	<p>{{ $t('description') }}<a :href="`${docsUrl}/link-to-twitter`" target="_blank">{{ $t('detail') }}</a></p> | ||||
| 	<p>{{ $t('description') }}</p> | ||||
| 	<p class="account" v-if="$store.state.i.twitter" :title="`Twitter ID: ${$store.state.i.twitter.userId}`">{{ $t('connected-to') }}: <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" @click.prevent="connect">{{ $store.state.i.twitter ? this.$t('reconnect') : this.$t('connect') }}</a> | ||||
| @@ -14,15 +14,14 @@ | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../../i18n'; | ||||
| import { apiUrl, docsUrl } from '../../../config'; | ||||
| import { apiUrl } from '../../../config'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('common/views/components/twitter-setting.vue'), | ||||
| 	data() { | ||||
| 		return { | ||||
| 			form: null, | ||||
| 			apiUrl, | ||||
| 			docsUrl | ||||
| 			apiUrl | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
|   | ||||
| @@ -38,12 +38,24 @@ export default Vue.extend({ | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		} | ||||
| 		}, | ||||
| 		autofocus: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			styl: 'fill' | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		if (this.autofocus) { | ||||
| 			this.$nextTick(() => { | ||||
| 				this.$el.focus(); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| @@ -57,6 +69,7 @@ export default Vue.extend({ | ||||
| 	text-align center | ||||
| 	font-weight normal | ||||
| 	font-size 16px | ||||
| 	line-height 24px | ||||
| 	border none | ||||
| 	border-radius 6px | ||||
| 	outline none | ||||
| @@ -85,6 +98,7 @@ export default Vue.extend({ | ||||
| 	&.inline | ||||
| 		display inline-block | ||||
| 		width auto | ||||
| 		min-width 100px | ||||
|  | ||||
| 	&.primary | ||||
| 		font-weight bold | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="pfzekjfwkwvadvlujpdnnxfggqgqjoze" :class="{ inputs }"> | ||||
| <div class="vnxwkwuf" :class="{ inputs, noGrow }"> | ||||
| 	<slot></slot> | ||||
| </div> | ||||
| </template> | ||||
| @@ -15,21 +15,27 @@ export default Vue.extend({ | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 		noGrow: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .pfzekjfwkwvadvlujpdnnxfggqgqjoze | ||||
| 	display flex | ||||
|  | ||||
| .vnxwkwuf | ||||
| 	&.inputs | ||||
| 		margin 32px 0 | ||||
|  | ||||
| 	&:not(.noGrow) | ||||
| 		display flex | ||||
|  | ||||
| 		> * | ||||
| 			flex 1 | ||||
|  | ||||
| 		&:not(:last-child) | ||||
| 	> *:not(:last-child) | ||||
| 		margin-right 16px | ||||
| </style> | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| 	<ol v-if="uploads.length > 0"> | ||||
| 		<li v-for="ctx in uploads" :key="ctx.id"> | ||||
| 			<div class="img" :style="{ backgroundImage: `url(${ ctx.img })` }"></div> | ||||
| 			<p class="name"><fa icon="spinner .pulse"/>{{ ctx.name }}</p> | ||||
| 			<p class="name"><fa icon="spinner" pulse/>{{ ctx.name }}</p> | ||||
| 			<p class="status"> | ||||
| 				<span class="initing" v-if="ctx.progress == undefined">{{ $t('waiting') }}<mk-ellipsis/></span> | ||||
| 				<span class="kb" v-if="ctx.progress != undefined">{{ String(Math.floor(ctx.progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span> | ||||
| @@ -57,17 +57,11 @@ export default Vue.extend({ | ||||
| 						return; | ||||
| 					} | ||||
|  | ||||
| 					// Upload if the file didn't exist yet | ||||
| 					const buf = new Uint8Array(e.target.result); | ||||
| 					let bin = ''; | ||||
| 					// We use for-of loop instead of apply() to avoid RangeError | ||||
| 					// SEE: https://stackoverflow.com/questions/9267899/arraybuffer-to-base64-encoded-string | ||||
| 					for (const byte of buf) bin += String.fromCharCode(byte); | ||||
| 					const ctx = { | ||||
| 						id: id, | ||||
| 						name: file.name || 'untitled', | ||||
| 						progress: undefined, | ||||
| 						img: 'data:*/*;base64,' + btoa(bin) | ||||
| 						img: window.URL.createObjectURL(file) | ||||
| 					}; | ||||
|  | ||||
| 					this.uploads.push(ctx); | ||||
|   | ||||
| @@ -25,7 +25,7 @@ | ||||
| 			<template v-else-if="!user.isFollowing && user.isLocked"><fa icon="plus"/> {{ $t('follow-request') }}</template> | ||||
| 			<template v-else-if="!user.isFollowing && !user.isLocked"><fa icon="plus"/> {{ $t('follow') }}</template> | ||||
| 		</template> | ||||
| 		<template v-else><fa icon="spinner .pulse" fixed-width/></template> | ||||
| 		<template v-else><fa icon="spinner" pulse fixed-width/></template> | ||||
| 	</button> | ||||
| </div> | ||||
| </template> | ||||
|   | ||||
| @@ -3,9 +3,15 @@ | ||||
| 	<mk-widget-container :show-header="props.design == 0" :naked="props.design == 2"> | ||||
| 		<template slot="header"><fa icon="camera"/>{{ $t('title') }}</template> | ||||
|  | ||||
| 		<p :class="$style.fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 		<p :class="$style.fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 		<div :class="$style.stream" v-if="!fetching && images.length > 0"> | ||||
| 			<div v-for="image in images" :class="$style.img" :style="`background-image: url(${image.thumbnailUrl || image.url})`"></div> | ||||
| 			<div v-for="image in images" | ||||
| 				:class="$style.img" | ||||
| 				:style="`background-image: url(${image.thumbnailUrl || image.url})`" | ||||
| 				draggable="true" | ||||
| 				@dragstart="onDragstart(image, $event)" | ||||
| 				@dragend="onDragend" | ||||
| 			></div> | ||||
| 		</div> | ||||
| 		<p :class="$style.empty" v-if="!fetching && images.length == 0">{{ $t('no-photos') }}</p> | ||||
| 	</mk-widget-container> | ||||
| @@ -31,6 +37,7 @@ export default define({ | ||||
| 			connection: null | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		this.connection = this.$root.stream.useSharedConnection('main'); | ||||
|  | ||||
| @@ -44,9 +51,11 @@ export default define({ | ||||
| 			this.fetching = false; | ||||
| 		}); | ||||
| 	}, | ||||
|  | ||||
| 	beforeDestroy() { | ||||
| 		this.connection.dispose(); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		onDriveFileCreated(file) { | ||||
| 			if (/^image\/.+$/.test(file.type)) { | ||||
| @@ -54,6 +63,7 @@ export default define({ | ||||
| 				if (this.images.length > 9) this.images.pop(); | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		func() { | ||||
| 			if (this.props.design == 2) { | ||||
| 				this.props.design = 0; | ||||
| @@ -62,7 +72,16 @@ export default define({ | ||||
| 			} | ||||
|  | ||||
| 			this.save(); | ||||
| 		} | ||||
| 		}, | ||||
|  | ||||
| 		onDragstart(file, e) { | ||||
| 			e.dataTransfer.effectAllowed = 'move'; | ||||
| 			e.dataTransfer.setData('mk_drive_file', JSON.stringify(file)); | ||||
| 		}, | ||||
|  | ||||
| 		onDragend(e) { | ||||
| 			this.browser.isDragSource = false; | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| 		<button slot="func" title="設定" @click="setting"><fa icon="cog"/></button> | ||||
|  | ||||
| 		<div class="mkw-rss--body" :data-mobile="platform == 'mobile'"> | ||||
| 			<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 			<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 			<div class="feed" v-else> | ||||
| 				<a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a> | ||||
| 			</div> | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| <div class="memory"> | ||||
| 	<x-pie class="pie" :value="usage"/> | ||||
| 	<div> | ||||
| 		<p><fa icon="flask"/>Memory</p> | ||||
| 		<p><fa icon="memory"/>Memory</p> | ||||
| 		<p>Total: {{ total | bytes(1) }}</p> | ||||
| 		<p>Used: {{ used | bytes(1) }}</p> | ||||
| 		<p>Free: {{ free | bytes(1) }}</p> | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| 		<template slot="header"><fa icon="server"/>{{ $t('title') }}</template> | ||||
| 		<button slot="func" @click="toggle" :title="$t('toggle')"><fa icon="sort"/></button> | ||||
|  | ||||
| 		<p :class="$style.fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 		<p :class="$style.fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 		<template v-if="!fetching"> | ||||
| 			<x-cpu-memory v-show="props.view == 0" :connection="connection"/> | ||||
| 			<x-cpu v-show="props.view == 1" :connection="connection" :meta="meta"/> | ||||
|   | ||||
| @@ -8,12 +8,9 @@ export default ($root: any) => { | ||||
|  | ||||
| 		const regex = RegExp('\.(jpg|jpeg|png|gif|webp|bmp|tiff)$'); | ||||
| 		if (!regex.test(file.name) ) { | ||||
| 			$root.$dialog({ | ||||
| 			$root.alert({ | ||||
| 				title: '%fa:info-circle% %i18n:desktop.invalid-filetype%', | ||||
| 				text: null, | ||||
| 				actions: [{ | ||||
| 					text: '%i18n:common.got-it%' | ||||
| 				}] | ||||
| 				text: null | ||||
| 			}); | ||||
| 			return reject('invalid-filetype'); | ||||
| 		} | ||||
| @@ -90,12 +87,9 @@ export default ($root: any) => { | ||||
| 				value: i.avatarUrl | ||||
| 			}); | ||||
|  | ||||
| 			$root.$dialog({ | ||||
| 			$root.alert({ | ||||
| 				title: '%fa:info-circle% %i18n:desktop.avatar-updated%', | ||||
| 				text: null, | ||||
| 				actions: [{ | ||||
| 					text: '%i18n:common.got-it%' | ||||
| 				}] | ||||
| 				text: null | ||||
| 			}); | ||||
|  | ||||
| 			return i; | ||||
|   | ||||
| @@ -10,10 +10,7 @@ export default ($root: any) => { | ||||
| 		if (!regex.test(file.name) ) { | ||||
| 			$root.dialog({ | ||||
| 				title: '%fa:info-circle% %i18n:desktop.invalid-filetype%', | ||||
| 				text: null, | ||||
| 				actions: [{ | ||||
| 					text: '%i18n:common.got-it%' | ||||
| 				}] | ||||
| 				text: null | ||||
| 			}); | ||||
| 			return reject('invalid-filetype'); | ||||
| 		} | ||||
| @@ -90,12 +87,9 @@ export default ($root: any) => { | ||||
| 				value: i.bannerUrl | ||||
| 			}); | ||||
|  | ||||
| 			$root.$dialog({ | ||||
| 			$root.alert({ | ||||
| 				title: '%fa:info-circle% %i18n:desktop.banner-updated%', | ||||
| 				text: null, | ||||
| 				actions: [{ | ||||
| 					text: '%i18n:common.got-it%' | ||||
| 				}] | ||||
| 				text: null | ||||
| 			}); | ||||
|  | ||||
| 			return i; | ||||
|   | ||||
| @@ -34,7 +34,6 @@ import PostFormWindow from './views/components/post-form-window.vue'; | ||||
| import RenoteFormWindow from './views/components/renote-form-window.vue'; | ||||
| import MkChooseFileFromDriveWindow from './views/components/choose-file-from-drive-window.vue'; | ||||
| import MkChooseFolderFromDriveWindow from './views/components/choose-folder-from-drive-window.vue'; | ||||
| import Dialog from './views/components/dialog.vue'; | ||||
| import InputDialog from './views/components/input-dialog.vue'; | ||||
| import Notification from './views/components/ui-notification.vue'; | ||||
|  | ||||
| @@ -114,21 +113,6 @@ init(async (launch) => { | ||||
| 				}); | ||||
| 			}, | ||||
|  | ||||
| 			$dialog(opts) { | ||||
| 				return new Promise<string>((res, rej) => { | ||||
| 					const o = opts || {}; | ||||
| 					const d = this.$root.new(Dialog, { | ||||
| 						title: o.title, | ||||
| 						text: o.text, | ||||
| 						modal: o.modal, | ||||
| 						buttons: o.actions | ||||
| 					}); | ||||
| 					d.$once('clicked', id => { | ||||
| 						res(id); | ||||
| 					}); | ||||
| 				}); | ||||
| 			}, | ||||
|  | ||||
| 			$input(opts) { | ||||
| 				return new Promise<string>((res, rej) => { | ||||
| 					const o = opts || {}; | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| 		<template slot="header"><fa icon="chart-bar"/>{{ $t('title') }}</template> | ||||
| 		<button slot="func" :title="$t('toggle')" @click="toggle"><fa icon="sort"/></button> | ||||
|  | ||||
| 		<p :class="$style.fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 		<p :class="$style.fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 		<template v-else> | ||||
| 			<x-calendar v-show="view == 0" :data="[].concat(activity)"/> | ||||
| 			<x-chart v-show="view == 1" :data="[].concat(activity)"/> | ||||
|   | ||||
| @@ -1,168 +0,0 @@ | ||||
| <template> | ||||
| <div class="mk-dialog"> | ||||
| 	<div class="bg" ref="bg" @click="onBgClick"></div> | ||||
| 	<div class="main" ref="main"> | ||||
| 		<header v-html="title" :class="$style.header"></header> | ||||
| 		<div class="body" v-html="text"></div> | ||||
| 		<div class="buttons"> | ||||
| 			<button v-for="button in buttons" @click="click(button)">{{ button.text }}</button> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import * as anime from 'animejs'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		title: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 		}, | ||||
| 		text: { | ||||
| 			type: String, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		buttons: { | ||||
| 			type: Array, | ||||
| 			default: () => { | ||||
| 				return [{ | ||||
| 					text: 'OK' | ||||
| 				}]; | ||||
| 			} | ||||
| 		}, | ||||
| 		modal: { | ||||
| 			type: Boolean, | ||||
| 			default: false | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.$nextTick(() => { | ||||
| 			(this.$refs.bg as any).style.pointerEvents = 'auto'; | ||||
| 			anime({ | ||||
| 				targets: this.$refs.bg, | ||||
| 				opacity: 1, | ||||
| 				duration: 100, | ||||
| 				easing: 'linear' | ||||
| 			}); | ||||
|  | ||||
| 			anime({ | ||||
| 				targets: this.$refs.main, | ||||
| 				opacity: 1, | ||||
| 				scale: [1.2, 1], | ||||
| 				duration: 300, | ||||
| 				easing: [0, 0.5, 0.5, 1] | ||||
| 			}); | ||||
| 		}); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		click(button) { | ||||
| 			this.$emit('clicked', button.id); | ||||
| 			this.close(); | ||||
| 		}, | ||||
| 		close() { | ||||
| 			(this.$refs.bg as any).style.pointerEvents = 'none'; | ||||
| 			anime({ | ||||
| 				targets: this.$refs.bg, | ||||
| 				opacity: 0, | ||||
| 				duration: 300, | ||||
| 				easing: 'linear' | ||||
| 			}); | ||||
|  | ||||
| 			(this.$refs.main as any).style.pointerEvents = 'none'; | ||||
| 			anime({ | ||||
| 				targets: this.$refs.main, | ||||
| 				opacity: 0, | ||||
| 				scale: 0.8, | ||||
| 				duration: 300, | ||||
| 				easing: [ 0.5, -0.5, 1, 0.5 ], | ||||
| 				complete: () => this.destroyDom() | ||||
| 			}); | ||||
| 		}, | ||||
| 		onBgClick() { | ||||
| 			if (!this.modal) { | ||||
| 				this.close(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .mk-dialog | ||||
| 	> .bg | ||||
| 		display block | ||||
| 		position fixed | ||||
| 		z-index 8192 | ||||
| 		top 0 | ||||
| 		left 0 | ||||
| 		width 100% | ||||
| 		height 100% | ||||
| 		background rgba(#000, 0.7) | ||||
| 		opacity 0 | ||||
| 		pointer-events none | ||||
|  | ||||
| 	> .main | ||||
| 		display block | ||||
| 		position fixed | ||||
| 		z-index 8192 | ||||
| 		top 20% | ||||
| 		left 0 | ||||
| 		right 0 | ||||
| 		margin 0 auto 0 auto | ||||
| 		padding 32px 42px | ||||
| 		width 480px | ||||
| 		background #fff | ||||
| 		opacity 0 | ||||
|  | ||||
| 		> .body | ||||
| 			margin 1em 0 | ||||
| 			color #888 | ||||
|  | ||||
| 		> .buttons | ||||
| 			> button | ||||
| 				display inline-block | ||||
| 				float right | ||||
| 				margin 0 | ||||
| 				padding 10px 10px | ||||
| 				font-size 1.1em | ||||
| 				font-weight normal | ||||
| 				text-decoration none | ||||
| 				color #888 | ||||
| 				background transparent | ||||
| 				outline none | ||||
| 				border none | ||||
| 				border-radius 0 | ||||
| 				cursor pointer | ||||
| 				transition color 0.1s ease | ||||
|  | ||||
| 				i | ||||
| 					margin 0 0.375em | ||||
|  | ||||
| 				&:hover | ||||
| 					color var(--primary) | ||||
|  | ||||
| 				&:active | ||||
| 					color var(--primaryDarken10) | ||||
| 					transition color 0s ease | ||||
|  | ||||
| </style> | ||||
|  | ||||
| <style lang="stylus" module> | ||||
|  | ||||
|  | ||||
| .header | ||||
| 	margin 1em 0 | ||||
| 	color var(--primary) | ||||
| 	// color #43A4EC | ||||
| 	font-weight bold | ||||
|  | ||||
| 	&:empty | ||||
| 		display none | ||||
|  | ||||
| 	> i | ||||
| 		margin-right 0.5em | ||||
|  | ||||
| </style> | ||||
| @@ -170,12 +170,9 @@ export default Vue.extend({ | ||||
|  | ||||
| 		copyUrl() { | ||||
| 			copyToClipboard(this.file.url); | ||||
| 			this.$dialog({ | ||||
| 			this.$root.alert({ | ||||
| 				title: this.$t('contextmenu.copied'), | ||||
| 				text: this.$t('contextmenu.copied-url-to-clipboard'), | ||||
| 				actions: [{ | ||||
| 					text: this.$t('@.ok') | ||||
| 				}] | ||||
| 				text: this.$t('contextmenu.copied-url-to-clipboard') | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
|   | ||||
| @@ -155,12 +155,9 @@ export default Vue.extend({ | ||||
| 				}).catch(err => { | ||||
| 					switch (err) { | ||||
| 						case 'detected-circular-definition': | ||||
| 							this.$dialog({ | ||||
| 							this.$root.alert({ | ||||
| 								title: this.$t('unable-to-process'), | ||||
| 								text: this.$t('circular-reference-detected'), | ||||
| 								actions: [{ | ||||
| 									text: this.$t('@.ok') | ||||
| 								}] | ||||
| 								text: this.$t('circular-reference-detected') | ||||
| 							}); | ||||
| 							break; | ||||
| 						default: | ||||
|   | ||||
| @@ -313,12 +313,9 @@ export default Vue.extend({ | ||||
| 				}).catch(err => { | ||||
| 					switch (err) { | ||||
| 						case 'detected-circular-definition': | ||||
| 							this.$dialog({ | ||||
| 							this.$root.alert({ | ||||
| 								title: this.$t('unable-to-process'), | ||||
| 								text: this.$t('circular-reference-detected'), | ||||
| 								actions: [{ | ||||
| 									text: this.$t('@.ok') | ||||
| 								}] | ||||
| 								text: this.$t('circular-reference-detected') | ||||
| 							}); | ||||
| 							break; | ||||
| 						default: | ||||
| @@ -343,12 +340,9 @@ export default Vue.extend({ | ||||
| 					folderId: this.folder ? this.folder.id : undefined | ||||
| 				}); | ||||
|  | ||||
| 				this.$dialog({ | ||||
| 				this.$root.alert({ | ||||
| 					title: this.$t('url-upload-requested'), | ||||
| 					text: this.$t('may-take-time'), | ||||
| 					actions: [{ | ||||
| 						text: this.$t('@.ok') | ||||
| 					}] | ||||
| 					text: this.$t('may-take-time') | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
|   | ||||
| @@ -0,0 +1,84 @@ | ||||
| <template> | ||||
| <div class="gcafiosrssbtbnbzqupfmglvzgiaipyv"> | ||||
| 	<x-picker @chosen="chosen"/> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import contains from '../../../common/scripts/contains'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XPicker: () => import('../../../common/views/components/emoji-picker.vue').then(m => m.default) | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 		x: { | ||||
| 			type: Number, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		y: { | ||||
| 			type: Number, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		this.$nextTick(() => { | ||||
| 			const width = this.$el.offsetWidth; | ||||
| 			const height = this.$el.offsetHeight; | ||||
|  | ||||
| 			let x = this.x; | ||||
| 			let y = this.y; | ||||
|  | ||||
| 			if (x + width - window.pageXOffset > window.innerWidth) { | ||||
| 				x = window.innerWidth - width + window.pageXOffset; | ||||
| 			} | ||||
|  | ||||
| 			if (y + height - window.pageYOffset > window.innerHeight) { | ||||
| 				y = window.innerHeight - height + window.pageYOffset; | ||||
| 			} | ||||
|  | ||||
| 			this.$el.style.left = x + 'px'; | ||||
| 			this.$el.style.top = y + 'px'; | ||||
|  | ||||
| 			Array.from(document.querySelectorAll('body *')).forEach(el => { | ||||
| 				el.addEventListener('mousedown', this.onMousedown); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		onMousedown(e) { | ||||
| 			e.preventDefault(); | ||||
| 			if (!contains(this.$el, e.target) && (this.$el != e.target)) this.close(); | ||||
| 			return false; | ||||
| 		}, | ||||
|  | ||||
| 		chosen(emoji) { | ||||
| 			this.$emit('chosen', emoji); | ||||
| 			this.close(); | ||||
| 		}, | ||||
|  | ||||
| 		close() { | ||||
| 			Array.from(document.querySelectorAll('body *')).forEach(el => { | ||||
| 				el.removeEventListener('mousedown', this.onMousedown); | ||||
| 			}); | ||||
|  | ||||
| 			this.$emit('closed'); | ||||
| 			this.destroyDom(); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .gcafiosrssbtbnbzqupfmglvzgiaipyv | ||||
| 	position fixed | ||||
| 	top 0 | ||||
| 	left 0 | ||||
| 	z-index 3000 | ||||
| 	box-shadow 0 2px 12px 0 rgba(0, 0, 0, 0.3) | ||||
|  | ||||
| </style> | ||||
| @@ -1,157 +0,0 @@ | ||||
| <template> | ||||
| <button class="mk-follow-button" | ||||
| 	:class="{ wait, active: u.isFollowing || u.hasPendingFollowRequestFromYou, big: size == 'big' }" | ||||
| 	@click="onClick" | ||||
| 	:disabled="wait" | ||||
| > | ||||
| 	<template v-if="!wait"> | ||||
| 		<template v-if="u.hasPendingFollowRequestFromYou && u.isLocked"><fa icon="hourglass-half"/><template v-if="size == 'big'"> {{ $t('request-pending') }}</template></template> | ||||
| 		<template v-else-if="u.hasPendingFollowRequestFromYou && !u.isLocked"><fa icon="hourglass-start"/><template v-if="size == 'big'"> {{ $t('follow-processing') }}</template></template> | ||||
| 		<template v-else-if="u.isFollowing"><fa icon="minus"/><template v-if="size == 'big'"> {{ $t('following') }}</template></template> | ||||
| 		<template v-else-if="!u.isFollowing && u.isLocked"><fa icon="plus"/><template v-if="size == 'big'"> {{ $t('follow-request') }}</template></template> | ||||
| 		<template v-else-if="!u.isFollowing && !u.isLocked"><fa icon="plus"/><template v-if="size == 'big'"> {{ $t('follow') }}</template></template> | ||||
| 	</template> | ||||
| 	<template v-else><fa icon="spinner .pulse" fixed-width/></template> | ||||
| </button> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../../i18n'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('desktop/views/components/follow-button.vue'), | ||||
| 	props: { | ||||
| 		user: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		size: { | ||||
| 			type: String, | ||||
| 			default: 'compact' | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			u: this.user, | ||||
| 			wait: false, | ||||
| 			connection: null | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		this.connection = this.$root.stream.useSharedConnection('main'); | ||||
| 		this.connection.on('follow', this.onFollowChange); | ||||
| 		this.connection.on('unfollow', this.onFollowChange); | ||||
| 	}, | ||||
|  | ||||
| 	beforeDestroy() { | ||||
| 		this.connection.dispose(); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		onFollowChange(user) { | ||||
| 			if (user.id == this.u.id) { | ||||
| 				this.u.isFollowing = user.isFollowing; | ||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 				this.$forceUpdate(); | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		async onClick() { | ||||
| 			this.wait = true; | ||||
|  | ||||
| 			try { | ||||
| 				if (this.u.isFollowing) { | ||||
| 					this.u = await this.$root.api('following/delete', { | ||||
| 						userId: this.u.id | ||||
| 					}); | ||||
| 				} else { | ||||
| 					if (this.u.hasPendingFollowRequestFromYou) { | ||||
| 						this.u = await this.$root.api('following/requests/cancel', { | ||||
| 							userId: this.u.id | ||||
| 						}); | ||||
| 					} else if (this.u.isLocked) { | ||||
| 						this.u = await this.$root.api('following/create', { | ||||
| 							userId: this.u.id | ||||
| 						}); | ||||
| 					} else { | ||||
| 						this.u = await this.$root.api('following/create', { | ||||
| 							userId: this.user.id | ||||
| 						}); | ||||
| 					} | ||||
| 				} | ||||
| 			} catch (e) { | ||||
| 				console.error(e); | ||||
| 			} finally { | ||||
| 				this.wait = false; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .mk-follow-button | ||||
| 	display block | ||||
| 	cursor pointer | ||||
| 	padding 0 | ||||
| 	margin 0 | ||||
| 	width 32px | ||||
| 	height 32px | ||||
| 	font-size 1em | ||||
| 	outline none | ||||
| 	border-radius 4px | ||||
|  | ||||
| 	* | ||||
| 		pointer-events none | ||||
|  | ||||
| 	&:focus | ||||
| 		&:after | ||||
| 			content "" | ||||
| 			pointer-events none | ||||
| 			position absolute | ||||
| 			top -5px | ||||
| 			right -5px | ||||
| 			bottom -5px | ||||
| 			left -5px | ||||
| 			border 2px solid var(--primaryAlpha03) | ||||
| 			border-radius 8px | ||||
|  | ||||
| 	&:not(.active) | ||||
| 		color var(--primary) | ||||
| 		border solid 1px var(--primary) | ||||
|  | ||||
| 		&:hover | ||||
| 			background var(--primaryAlpha03) | ||||
|  | ||||
| 		&:active | ||||
| 			background var(--primaryAlpha05) | ||||
|  | ||||
| 	&.active | ||||
| 		color var(--primaryForeground) | ||||
| 		background var(--primary) | ||||
| 		border solid 1px var(--primary) | ||||
|  | ||||
| 		&:not(:disabled) | ||||
| 			font-weight bold | ||||
|  | ||||
| 		&:hover:not(:disabled) | ||||
| 			background var(--primaryLighten5) | ||||
| 			border-color var(--primaryLighten5) | ||||
|  | ||||
| 		&:active:not(:disabled) | ||||
| 			background var(--primaryDarken5) | ||||
| 			border-color var(--primaryDarken5) | ||||
|  | ||||
| 	&.wait | ||||
| 		cursor wait !important | ||||
| 		opacity 0.7 | ||||
|  | ||||
| 	&.big | ||||
| 		width 100% | ||||
| 		height 38px | ||||
| 		line-height 38px | ||||
|  | ||||
| </style> | ||||
| @@ -11,7 +11,7 @@ | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<p class="empty" v-if="!fetching && users.length == 0">{{ $t('empty') }}</p> | ||||
| 	<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('fetching') }}<mk-ellipsis/></p> | ||||
| 	<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('fetching') }}<mk-ellipsis/></p> | ||||
| 	<a class="refresh" @click="refresh">{{ $t('refresh') }}</a> | ||||
| 	<button class="close" @click="destroyDom()" :title="$t('title')"><fa icon="times"/></button> | ||||
| </div> | ||||
|   | ||||
| @@ -186,12 +186,9 @@ export default Vue.extend({ | ||||
|  | ||||
| 	methods: { | ||||
| 		hint() { | ||||
| 			this.$dialog({ | ||||
| 			this.$root.alert({ | ||||
| 				title: this.$t('@.customization-tips.title'), | ||||
| 				text: this.$t('@.customization-tips.paragraph'), | ||||
| 				actions: [{ | ||||
| 					text: this.$t('@.customization-tips.gotit') | ||||
| 				}] | ||||
| 				text: this.$t('@.customization-tips.paragraph') | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
|   | ||||
| @@ -14,10 +14,8 @@ import mediaVideo from './media-video.vue'; | ||||
| import notifications from './notifications.vue'; | ||||
| import noteForm from './post-form.vue'; | ||||
| import renoteForm from './renote-form.vue'; | ||||
| import followButton from './follow-button.vue'; | ||||
| import notePreview from './note-preview.vue'; | ||||
| import noteDetail from './note-detail.vue'; | ||||
| import settings from './settings.vue'; | ||||
| import calendar from './calendar.vue'; | ||||
| import activity from './activity.vue'; | ||||
| import friendsMaker from './friends-maker.vue'; | ||||
| @@ -39,10 +37,8 @@ Vue.component('mk-media-video', mediaVideo); | ||||
| Vue.component('mk-notifications', notifications); | ||||
| Vue.component('mk-post-form', noteForm); | ||||
| Vue.component('mk-renote-form', renoteForm); | ||||
| Vue.component('mk-follow-button', followButton); | ||||
| Vue.component('mk-note-preview', notePreview); | ||||
| Vue.component('mk-note-detail', noteDetail); | ||||
| Vue.component('mk-settings', settings); | ||||
| Vue.component('mk-calendar', calendar); | ||||
| Vue.component('mk-activity', activity); | ||||
| Vue.component('mk-friends-maker', friendsMaker); | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| 		:disabled="conversationFetching" | ||||
| 	> | ||||
| 		<template v-if="!conversationFetching"><fa icon="ellipsis-v"/></template> | ||||
| 		<template v-if="conversationFetching"><fa icon="spinner .pulse"/></template> | ||||
| 		<template v-if="conversationFetching"><fa icon="spinner" pulse/></template> | ||||
| 	</button> | ||||
| 	<div class="conversation"> | ||||
| 		<x-sub v-for="note in conversation" :key="note.id" :note="note"/> | ||||
|   | ||||
| @@ -26,7 +26,7 @@ | ||||
| 	<footer v-if="more"> | ||||
| 		<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> | ||||
| 			<template v-if="!moreFetching">{{ $t('@.load-more') }}</template> | ||||
| 			<template v-if="moreFetching"><fa icon="spinner .pulse" fixed-width/></template> | ||||
| 			<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template> | ||||
| 		</button> | ||||
| 	</footer> | ||||
| </div> | ||||
|   | ||||
| @@ -105,7 +105,7 @@ | ||||
| 		</component> | ||||
| 	</div> | ||||
| 	<button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications"> | ||||
| 		<template v-if="fetchingMoreNotifications"><fa icon="spinner .pulse" fixed-width/></template>{{ fetchingMoreNotifications ? $t('@.loading') : $t('@.load-more') }} | ||||
| 		<template v-if="fetchingMoreNotifications"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreNotifications ? $t('@.loading') : $t('@.load-more') }} | ||||
| 	</button> | ||||
| 	<p class="empty" v-if="notifications.length == 0 && !fetching">{{ $t('empty') }}</p> | ||||
| </div> | ||||
|   | ||||
| @@ -15,11 +15,15 @@ | ||||
| 			<a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" :title="$t('click-to-tagging')">#{{ tag }}</a> | ||||
| 		</div> | ||||
| 		<input v-show="useCw" v-model="cw" :placeholder="$t('annotations')"> | ||||
| 		<div class="textarea"> | ||||
| 			<textarea :class="{ with: (files.length != 0 || poll) }" | ||||
| 				ref="text" v-model="text" :disabled="posting" | ||||
| 				@keydown="onKeydown" @paste="onPaste" :placeholder="placeholder" | ||||
| 				v-autocomplete="'text'" | ||||
| 			></textarea> | ||||
| 			<button class="emoji" @click="emoji" ref="emoji"> | ||||
| 				<fa :icon="['far', 'laugh']"/> | ||||
| 			</button> | ||||
| 			<div class="files" :class="{ with: poll }" v-show="files.length != 0"> | ||||
| 				<x-draggable :list="files" :options="{ animation: 150 }"> | ||||
| 					<div v-for="file in files" :key="file.id"> | ||||
| @@ -31,12 +35,13 @@ | ||||
| 			</div> | ||||
| 			<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="saveDraft()"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/> | ||||
| 	<button class="upload" :title="$t('attach-media-from-local')" @click="chooseFile"><fa icon="upload"/></button> | ||||
| 	<button class="drive" :title="$t('attach-media-from-drive')" @click="chooseFileFromDrive"><fa icon="cloud"/></button> | ||||
| 	<button class="kao" :title="$t('insert-a-kao')" @click="kao"><fa :icon="['far', 'smile']"/></button> | ||||
| 	<button class="poll" :title="$t('create-poll')" @click="poll = !poll"><fa icon="chart-pie"/></button> | ||||
| 	<button class="cw" :title="$t('hide-contents')" @click="useCw = !useCw"><fa icon="eye-slash"/></button> | ||||
| 	<button class="cw" :title="$t('hide-contents')" @click="useCw = !useCw"><fa :icon="['far', 'eye-slash']"/></button> | ||||
| 	<button class="geo" :title="$t('attach-location-information')" @click="geo ? removeGeo() : setGeo()"><fa icon="map-marker-alt"/></button> | ||||
| 	<button class="visibility" :title="$t('visibility')" @click="setVisibility" ref="visibilityButton"> | ||||
| 		<span v-if="visibility === 'public'"><fa icon="globe"/></span> | ||||
| @@ -377,6 +382,19 @@ export default Vue.extend({ | ||||
| 			this.visibleUsers = erase(user, this.visibleUsers); | ||||
| 		}, | ||||
|  | ||||
| 		async emoji() { | ||||
| 			const Picker = await import('./emoji-picker-dialog.vue').then(m => m.default); | ||||
| 			const button = this.$refs.emoji; | ||||
| 			const rect = button.getBoundingClientRect(); | ||||
| 			const vm = this.$root.new(Picker, { | ||||
| 				x: button.offsetWidth + rect.left + window.pageXOffset, | ||||
| 				y: rect.top + window.pageYOffset | ||||
| 			}); | ||||
| 			vm.$once('chosen', emoji => { | ||||
| 				insertTextAtCursor(this.$refs.text, emoji); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		post() { | ||||
| 			this.posting = true; | ||||
|  | ||||
| @@ -469,7 +487,7 @@ export default Vue.extend({ | ||||
|  | ||||
| 	> .content | ||||
| 		> input | ||||
| 		> textarea | ||||
| 		> .textarea > textarea | ||||
| 			display block | ||||
| 			width 100% | ||||
| 			padding 12px | ||||
| @@ -498,6 +516,24 @@ export default Vue.extend({ | ||||
| 		> input | ||||
| 			margin-bottom 8px | ||||
|  | ||||
| 		> .textarea | ||||
| 			> .emoji | ||||
| 				position absolute | ||||
| 				top 0 | ||||
| 				right 0 | ||||
| 				padding 10px | ||||
| 				font-size 18px | ||||
| 				color var(--text) | ||||
| 				opacity 0.5 | ||||
|  | ||||
| 				&:hover | ||||
| 					color var(--textHighlighted) | ||||
| 					opacity 1 | ||||
|  | ||||
| 				&:active | ||||
| 					color var(--primary) | ||||
| 					opacity 1 | ||||
|  | ||||
| 			> textarea | ||||
| 				margin 0 | ||||
| 				max-width 100% | ||||
| @@ -505,42 +541,24 @@ export default Vue.extend({ | ||||
| 				min-height 84px | ||||
|  | ||||
| 				&:hover | ||||
| 				& + * | ||||
| 					& + * + * | ||||
| 					& + * + * + * | ||||
| 						border-color var(--primaryAlpha02) | ||||
| 						transition border-color .1s ease | ||||
|  | ||||
| 				&:focus | ||||
| 				& + * | ||||
| 					& + * + * | ||||
| 					& + * + * + * | ||||
| 						border-color var(--primaryAlpha05) | ||||
| 						transition border-color 0s ease | ||||
|  | ||||
| 					& + .emoji | ||||
| 						opacity 0.7 | ||||
|  | ||||
| 				&.with | ||||
| 					border-bottom solid 1px var(--primaryAlpha01) !important | ||||
| 					border-radius 4px 4px 0 0 | ||||
|  | ||||
| 		> .visibleUsers | ||||
| 			margin-bottom 8px | ||||
| 			font-size 14px | ||||
|  | ||||
| 			> span | ||||
| 				margin-right 16px | ||||
| 				color var(--primary) | ||||
|  | ||||
| 		> .hashtags | ||||
| 			margin 0 0 8px 0 | ||||
| 			overflow hidden | ||||
| 			white-space nowrap | ||||
| 			font-size 14px | ||||
|  | ||||
| 			> b | ||||
| 				color var(--primary) | ||||
|  | ||||
| 			> * | ||||
| 				margin-right 8px | ||||
| 				white-space nowrap | ||||
|  | ||||
| 			> .files | ||||
| 				margin 0 | ||||
| 				padding 0 | ||||
| @@ -601,6 +619,27 @@ export default Vue.extend({ | ||||
| 				border-radius 0 0 4px 4px | ||||
| 				transition border-color .3s ease | ||||
|  | ||||
| 		> .visibleUsers | ||||
| 			margin-bottom 8px | ||||
| 			font-size 14px | ||||
|  | ||||
| 			> span | ||||
| 				margin-right 16px | ||||
| 				color var(--primary) | ||||
|  | ||||
| 		> .hashtags | ||||
| 			margin 0 0 8px 0 | ||||
| 			overflow hidden | ||||
| 			white-space nowrap | ||||
| 			font-size 14px | ||||
|  | ||||
| 			> b | ||||
| 				color var(--primary) | ||||
|  | ||||
| 			> * | ||||
| 				margin-right 8px | ||||
| 				white-space nowrap | ||||
|  | ||||
| 	> .mk-uploader | ||||
| 		margin 8px 0 0 0 | ||||
| 		padding 8px | ||||
|   | ||||
| @@ -1,15 +1,21 @@ | ||||
| <template> | ||||
| <mk-window ref="window" is-modal width="700px" height="550px" @closed="destroyDom"> | ||||
| 	<span slot="header" :class="$style.header"><fa icon="cog"/>{{ $t('settings') }}</span> | ||||
| 	<mk-settings :initial-page="initialPage" @done="close"/> | ||||
| 	<x-settings :initial-page="initialPage" @done="close"/> | ||||
| </mk-window> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../../i18n'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('desktop/views/components/settings-window.vue'), | ||||
|  | ||||
| 	components: { | ||||
| 		XSettings: () => import('./settings.vue').then(m => m.default) | ||||
| 	}, | ||||
|  | ||||
| 	props: { | ||||
| 		initialPage: { | ||||
| 			type: String, | ||||
|   | ||||
| @@ -15,7 +15,7 @@ | ||||
| 	</div> | ||||
| 	<div class="pages"> | ||||
| 		<div class="profile" v-show="page == 'profile'"> | ||||
| 			<mk-profile-editor/> | ||||
| 			<x-profile-editor/> | ||||
|  | ||||
| 			<ui-card> | ||||
| 				<div slot="title"><fa :icon="['fab', 'twitter']"/> {{ $t('twitter') }}</div> | ||||
| @@ -36,7 +36,7 @@ | ||||
| 			<div slot="title"><fa icon="palette"/> {{ $t('theme') }}</div> | ||||
|  | ||||
| 			<section> | ||||
| 				<mk-theme/> | ||||
| 				<x-theme/> | ||||
| 			</section> | ||||
| 		</ui-card> | ||||
|  | ||||
| @@ -194,7 +194,7 @@ | ||||
| 		</ui-card> | ||||
|  | ||||
| 		<div class="drive" v-if="page == 'drive'"> | ||||
| 			<mk-drive-settings/> | ||||
| 			<x-drive-settings/> | ||||
| 		</div> | ||||
|  | ||||
| 		<ui-card class="hashtags" v-show="page == 'hashtags'"> | ||||
| @@ -205,7 +205,7 @@ | ||||
| 		</ui-card> | ||||
|  | ||||
| 		<div class="muteAndBlock" v-show="page == 'muteAndBlock'"> | ||||
| 			<mk-mute-and-block/> | ||||
| 			<x-mute-and-block/> | ||||
| 		</div> | ||||
|  | ||||
| 		<ui-card class="apps" v-show="page == 'apps'"> | ||||
| @@ -218,7 +218,7 @@ | ||||
| 		<ui-card class="password" v-show="page == 'security'"> | ||||
| 			<div slot="title"><fa icon="unlock-alt"/> {{ $t('password') }}</div> | ||||
| 			<section> | ||||
| 				<mk-password-settings/> | ||||
| 				<x-password-settings/> | ||||
| 			</section> | ||||
| 		</ui-card> | ||||
|  | ||||
| @@ -237,7 +237,7 @@ | ||||
| 		</ui-card> | ||||
|  | ||||
| 		<div class="api" v-show="page == 'api'"> | ||||
| 			<mk-api-settings/> | ||||
| 			<x-api-settings/> | ||||
| 		</div> | ||||
|  | ||||
| 		<ui-card class="other" v-show="page == 'other'"> | ||||
| @@ -301,7 +301,13 @@ export default Vue.extend({ | ||||
| 		X2fa, | ||||
| 		XApps, | ||||
| 		XSignins, | ||||
| 		XTags | ||||
| 		XTags, | ||||
| 		XTheme: () => import('../../../common/views/components/theme.vue').then(m => m.default), | ||||
| 		XDriveSettings: () => import('../../../common/views/components/drive-settings.vue').then(m => m.default), | ||||
| 		XMuteAndBlock: () => import('../../../common/views/components/mute-and-block.vue').then(m => m.default), | ||||
| 		XPasswordSettings: () => import('../../../common/views/components/password-settings.vue').then(m => m.default), | ||||
| 		XProfileEditor: () => import('../../../common/views/components/profile-editor.vue').then(m => m.default), | ||||
| 		XApiSettings: () => import('../../../common/views/components/api-settings.vue').then(m => m.default), | ||||
| 	}, | ||||
| 	props: { | ||||
| 		initialPage: { | ||||
| @@ -543,12 +549,12 @@ export default Vue.extend({ | ||||
| 				this.checkingForUpdate = false; | ||||
| 				this.latestVersion = newer; | ||||
| 				if (newer == null) { | ||||
| 					this.$dialog({ | ||||
| 					this.$root.alert({ | ||||
| 						title: this.$t('no-updates'), | ||||
| 						text: this.$t('no-updates-desc') | ||||
| 					}); | ||||
| 				} else { | ||||
| 					this.$dialog({ | ||||
| 					this.$root.alert({ | ||||
| 						title: this.$t('update-available'), | ||||
| 						text: this.$t('update-available-desc') | ||||
| 					}); | ||||
| @@ -557,7 +563,7 @@ export default Vue.extend({ | ||||
| 		}, | ||||
| 		clean() { | ||||
| 			localStorage.clear(); | ||||
| 			this.$dialog({ | ||||
| 			this.$root.alert({ | ||||
| 				title: this.$t('cache-cleared'), | ||||
| 				text: this.$t('cache-cleared-desc') | ||||
| 			}); | ||||
|   | ||||
| @@ -58,7 +58,7 @@ | ||||
| 						<i><fa icon="angle-right"/></i> | ||||
| 					</p> | ||||
| 				</li> | ||||
| 				<li v-if="$store.state.i.isAdmin"> | ||||
| 				<li v-if="$store.state.i.isAdmin || $store.state.i.isModerator"> | ||||
| 					<a href="/admin"> | ||||
| 						<i><fa icon="terminal"/></i> | ||||
| 						<span>{{ $t('admin') }}</span> | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| <div class="zvdbznxvfixtmujpsigoccczftvpiwqh"> | ||||
| 	<div class="banner" :style="bannerStyle"></div> | ||||
| 	<mk-avatar class="avatar" :user="user" :disable-preview="true"/> | ||||
| 	<mk-follow-button :user="user" class="follow"/> | ||||
| 	<mk-follow-button :user="user" class="follow" mini/> | ||||
| 	<div class="body"> | ||||
| 		<router-link :to="user | userPage" class="name">{{ user | userName }}</router-link> | ||||
| 		<span class="username">@{{ user | acct }}</span> | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
| 				<p>{{ $t('followers') }}</p><span>{{ u.followersCount }}</span> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<mk-follow-button v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u"/> | ||||
| 		<mk-follow-button class="follow-button" v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u" mini/> | ||||
| 	</template> | ||||
| </div> | ||||
| </template> | ||||
| @@ -154,7 +154,7 @@ export default Vue.extend({ | ||||
| 				font-size 1em | ||||
| 				color var(--primary) | ||||
|  | ||||
| 	> .mk-follow-button | ||||
| 	> .follow-button | ||||
| 		position absolute | ||||
| 		top 92px | ||||
| 		right 8px | ||||
|   | ||||
| @@ -31,7 +31,7 @@ | ||||
| 	<footer v-if="more"> | ||||
| 		<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> | ||||
| 			<template v-if="!moreFetching">{{ $t('@.load-more') }}</template> | ||||
| 			<template v-if="moreFetching"><fa icon="spinner .pulse" fixed-width/></template> | ||||
| 			<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template> | ||||
| 		</button> | ||||
| 	</footer> | ||||
| </div> | ||||
|   | ||||
| @@ -17,7 +17,7 @@ | ||||
| 		</template> | ||||
| 	</component> | ||||
| 	<button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications"> | ||||
| 		<template v-if="fetchingMoreNotifications"><fa icon="spinner .pulse" fixed-width/></template>{{ fetchingMoreNotifications ? this.$t('@.loading') : this.$t('@.load-more') }} | ||||
| 		<template v-if="fetchingMoreNotifications"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreNotifications ? this.$t('@.loading') : this.$t('@.load-more') }} | ||||
| 	</button> | ||||
| 	<p class="empty" v-if="notifications.length == 0 && !fetching">{{ $t('empty') }}</p> | ||||
| </div> | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
| 		<header :style="bannerStyle"> | ||||
| 			<div> | ||||
| 				<button class="menu" @click="menu" ref="menu"><fa icon="ellipsis-h"/></button> | ||||
| 				<mk-follow-button v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" class="follow"/> | ||||
| 				<mk-follow-button v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" class="follow" mini/> | ||||
| 				<mk-avatar class="avatar" :user="user" :disable-preview="true"/> | ||||
| 				<span class="name">{{ user | userName }}</span> | ||||
| 				<span class="acct">@{{ user | acct }}</span> | ||||
| @@ -87,7 +87,6 @@ import XNotes from './deck.notes.vue'; | ||||
| import XNote from '../../components/note.vue'; | ||||
| import Menu from '../../../../common/views/components/menu.vue'; | ||||
| import MkUserListsWindow from '../../components/user-lists-window.vue'; | ||||
| import Ok from '../../../../common/views/components/ok.vue'; | ||||
| import { concat } from '../../../../../../prelude/array'; | ||||
| import * as ApexCharts from 'apexcharts'; | ||||
|  | ||||
| @@ -155,7 +154,8 @@ export default Vue.extend({ | ||||
| 			this.$root.api('users/notes', { | ||||
| 				userId: this.user.id, | ||||
| 				fileType: image, | ||||
| 				limit: 9 | ||||
| 				limit: 9, | ||||
| 				untilDate: new Date().getTime() + 1000 * 86400 * 365 | ||||
| 			}).then(notes => { | ||||
| 				notes.forEach(note => { | ||||
| 					note.files.forEach(file => { | ||||
| @@ -254,6 +254,7 @@ export default Vue.extend({ | ||||
| 				this.$root.api('users/notes', { | ||||
| 					userId: this.user.id, | ||||
| 					limit: fetchLimit + 1, | ||||
| 					untilDate: new Date().getTime() + 1000 * 86400 * 365, | ||||
| 					withFiles: this.withFiles, | ||||
| 					includeMyRenotes: this.$store.state.settings.showMyRenotes, | ||||
| 					includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, | ||||
| @@ -274,7 +275,7 @@ export default Vue.extend({ | ||||
| 			const promise = this.$root.api('users/notes', { | ||||
| 				userId: this.user.id, | ||||
| 				limit: fetchLimit + 1, | ||||
| 				untilId: (this.$refs.timeline as any).tail().id, | ||||
| 				untilDate: new Date((this.$refs.timeline as any).tail().createdAt).getTime(), | ||||
| 				withFiles: this.withFiles, | ||||
| 				includeMyRenotes: this.$store.state.settings.showMyRenotes, | ||||
| 				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, | ||||
| @@ -306,7 +307,10 @@ export default Vue.extend({ | ||||
| 							listId: list.id, | ||||
| 							userId: this.user.id | ||||
| 						}); | ||||
| 						this.$root.new(Ok); | ||||
| 						this.$root.alert({ | ||||
| 							type: 'success', | ||||
| 							splash: true | ||||
| 						}); | ||||
| 					}); | ||||
| 				} | ||||
| 			}]; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="vahgrswmbzfdlmomxnqftuueyvwaafth"> | ||||
| 	<p class="title"><fa icon="users"/>{{ $t('title') }}</p> | ||||
| 	<p class="initializing" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('loading') }}<mk-ellipsis/></p> | ||||
| 	<p class="initializing" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('loading') }}<mk-ellipsis/></p> | ||||
| 	<div v-if="!fetching && users.length > 0"> | ||||
| 	<router-link v-for="user in users" :to="user | userPage" :key="user.id"> | ||||
| 		<img :src="user.avatarUrl" :alt="user | userName" v-user-preview="user.id"/> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="hozptpaliadatkehcmcayizwzwwctpbc"> | ||||
| 	<p class="title"><fa icon="users"/>{{ $t('title') }}</p> | ||||
| 	<p class="initializing" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('loading') }}<mk-ellipsis/></p> | ||||
| 	<p class="initializing" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('loading') }}<mk-ellipsis/></p> | ||||
| 	<template v-if="!fetching && users.length != 0"> | ||||
| 		<div class="user" v-for="friend in users"> | ||||
| 			<mk-avatar class="avatar" :user="friend"/> | ||||
| @@ -9,7 +9,7 @@ | ||||
| 				<router-link class="name" :to="friend | userPage" v-user-preview="friend.id">{{ friend.name }}</router-link> | ||||
| 				<p class="username">@{{ friend | acct }}</p> | ||||
| 			</div> | ||||
| 			<mk-follow-button :user="friend"/> | ||||
| 			<mk-follow-button class="follow-button" :user="friend"/> | ||||
| 		</div> | ||||
| 	</template> | ||||
| 	<p class="empty" v-if="!fetching && users.length == 0">{{ $t('no-users') }}</p> | ||||
| @@ -110,7 +110,7 @@ export default Vue.extend({ | ||||
| 				color var(--text) | ||||
| 				opacity 0.7 | ||||
|  | ||||
| 		> .mk-follow-button | ||||
| 		> .follow-button | ||||
| 			position absolute | ||||
| 			top 16px | ||||
| 			right 16px | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="dzsuvbsrrrwobdxifudxuefculdfiaxd"> | ||||
| 	<p class="title"><fa icon="camera"/>{{ $t('title') }}</p> | ||||
| 	<p class="initializing" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('loading') }}<mk-ellipsis/></p> | ||||
| 	<p class="initializing" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('loading') }}<mk-ellipsis/></p> | ||||
| 	<div class="stream" v-if="!fetching && images.length > 0"> | ||||
| 		<div v-for="image in images" class="img" | ||||
| 			:style="`background-image: url(${image.thumbnailUrl})`" | ||||
| @@ -27,7 +27,8 @@ export default Vue.extend({ | ||||
| 		this.$root.api('users/notes', { | ||||
| 			userId: this.user.id, | ||||
| 			withFiles: true, | ||||
| 			limit: 9 | ||||
| 			limit: 9, | ||||
| 			untilDate: new Date().getTime() + 1000 * 86400 * 365 | ||||
| 		}).then(notes => { | ||||
| 			notes.forEach(note => { | ||||
| 				note.files.forEach(file => { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="profile" v-if="$store.getters.isSignedIn"> | ||||
| 	<div class="friend-form" v-if="$store.state.i.id != user.id"> | ||||
| 		<mk-follow-button :user="user" size="big"/> | ||||
| 		<mk-follow-button :user="user" block/> | ||||
| 		<p class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</p> | ||||
| 		<p class="stalk" v-if="user.isFollowing"> | ||||
| 			<span v-if="user.isStalking">{{ $t('stalking') }} <a @click="unstalk"><fa icon="meh"/> {{ $t('unstalk') }}</a></span> | ||||
| @@ -11,11 +11,11 @@ | ||||
| 	<div class="action-form"> | ||||
| 		<ui-button @click="user.isMuted ? unmute() : mute()" v-if="$store.state.i.id != user.id"> | ||||
| 			<span v-if="user.isMuted"><fa icon="eye"/> {{ $t('unmute') }}</span> | ||||
| 			<span v-else><fa icon="eye-slash"/> {{ $t('mute') }}</span> | ||||
| 			<span v-else><fa :icon="['far', 'eye-slash']"/> {{ $t('mute') }}</span> | ||||
| 		</ui-button> | ||||
| 		<ui-button @click="user.isBlocking ? unblock() : block()" v-if="$store.state.i.id != user.id"> | ||||
| 			<span v-if="user.isBlocking"><fa icon="user"/> {{ $t('unblock') }}</span> | ||||
| 			<span v-else><fa icon="user-slash"/> {{ $t('block') }}</span> | ||||
| 			<span v-if="user.isBlocking"><fa icon="ban"/> {{ $t('unblock') }}</span> | ||||
| 			<span v-else><fa icon="ban"/> {{ $t('block') }}</span> | ||||
| 		</ui-button> | ||||
| 		<ui-button @click="list"><fa icon="list"/> {{ $t('push-to-a-list') }}</ui-button> | ||||
| 	</div> | ||||
| @@ -73,7 +73,13 @@ export default Vue.extend({ | ||||
| 		}, | ||||
|  | ||||
| 		block() { | ||||
| 			if (!window.confirm(this.$t('block-confirm'))) return; | ||||
| 			this.$root.alert({ | ||||
| 				type: 'warning', | ||||
| 				text: this.$t('block-confirm'), | ||||
| 				showCancelButton: true | ||||
| 			}).then(res => { | ||||
| 				if (!res) return; | ||||
|  | ||||
| 				this.$root.api('blocking/create', { | ||||
| 					userId: this.user.id | ||||
| 				}).then(() => { | ||||
| @@ -81,6 +87,7 @@ export default Vue.extend({ | ||||
| 				}, () => { | ||||
| 					alert('error'); | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		unblock() { | ||||
| @@ -101,7 +108,7 @@ export default Vue.extend({ | ||||
| 					listId: list.id, | ||||
| 					userId: this.user.id | ||||
| 				}); | ||||
| 				this.$dialog({ | ||||
| 				this.$root.alert({ | ||||
| 					title: 'Done!', | ||||
| 					text: this.$t('list-pushed').replace('{user}', this.user.name).replace('{list}', list.title) | ||||
| 				}); | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| 	<header> | ||||
| 		<span :data-active="mode == 'default'" @click="mode = 'default'"><fa :icon="['far', 'comment-alt']"/> {{ $t('default') }}</span> | ||||
| 		<span :data-active="mode == 'with-replies'" @click="mode = 'with-replies'"><fa icon="comments"/> {{ $t('with-replies') }}</span> | ||||
| 		<span :data-active="mode == 'with-media'" @click="mode = 'with-media'"><fa icon="images"/> {{ $t('with-media') }}</span> | ||||
| 		<span :data-active="mode == 'with-media'" @click="mode = 'with-media'"><fa :icon="['far', 'images']"/> {{ $t('with-media') }}</span> | ||||
| 	</header> | ||||
| 	<mk-notes ref="timeline" :more="existMore ? more : null"> | ||||
| 		<p class="empty" slot="empty"><fa :icon="['far', 'comments']"/>{{ $t('empty') }}</p> | ||||
| @@ -63,7 +63,7 @@ export default Vue.extend({ | ||||
| 				this.$root.api('users/notes', { | ||||
| 					userId: this.user.id, | ||||
| 					limit: fetchLimit + 1, | ||||
| 					untilDate: this.date ? this.date.getTime() : undefined, | ||||
| 					untilDate: this.date ? this.date.getTime() : new Date().getTime() + 1000 * 86400 * 365, | ||||
| 					includeReplies: this.mode == 'with-replies', | ||||
| 					withFiles: this.mode == 'with-media' | ||||
| 				}).then(notes => { | ||||
| @@ -86,7 +86,7 @@ export default Vue.extend({ | ||||
| 				limit: fetchLimit + 1, | ||||
| 				includeReplies: this.mode == 'with-replies', | ||||
| 				withFiles: this.mode == 'with-media', | ||||
| 				untilId: (this.$refs.timeline as any).tail().id | ||||
| 				untilDate: new Date((this.$refs.timeline as any).tail().createdAt).getTime() | ||||
| 			}); | ||||
|  | ||||
| 			promise.then(notes => { | ||||
|   | ||||
| @@ -50,7 +50,7 @@ | ||||
| 			</div> | ||||
|  | ||||
| 			<div class="photos block"> | ||||
| 				<header><fa icon="images"/> {{ $t('photos') }}</header> | ||||
| 				<header><fa :icon="['far', 'images']"/> {{ $t('photos') }}</header> | ||||
| 				<div> | ||||
| 					<div v-for="photo in photos" :style="`background-image: url(${photo.thumbnailUrl})`"></div> | ||||
| 				</div> | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
| 				<mk-poll :note="poll"/> | ||||
| 			</div> | ||||
| 			<p class="empty" v-if="!fetching && poll == null">{{ $t('nothing') }}</p> | ||||
| 			<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 			<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 		</div> | ||||
| 	</mk-widget-container> | ||||
| </div> | ||||
|   | ||||
| @@ -1,16 +1,51 @@ | ||||
| <template> | ||||
| <div class="mkw-post-form"> | ||||
| 	<template v-if="props.design == 0"> | ||||
| 		<p class="title"><fa icon="pencil-alt"/>{{ $t('title') }}</p> | ||||
| 	</template> | ||||
| 	<textarea :disabled="posting" v-model="text" @keydown="onKeydown" :placeholder="placeholder"></textarea> | ||||
| 	<button @click="post" :disabled="posting">{{ $t('note') }}</button> | ||||
| <div> | ||||
| 	<mk-widget-container :show-header="props.design == 0"> | ||||
| 		<template slot="header"><fa icon="pencil-alt"/>{{ $t('title') }}</template> | ||||
|  | ||||
| 		<div class="lhcuptdmcdkfwmipgazeawoiuxpzaclc-body" | ||||
| 			@dragover.stop="onDragover" | ||||
| 			@drop.stop="onDrop" | ||||
| 		> | ||||
| 			<div class="textarea"> | ||||
| 				<textarea | ||||
| 					:disabled="posting" | ||||
| 					v-model="text" | ||||
| 					@keydown="onKeydown" | ||||
| 					@paste="onPaste" | ||||
| 					:placeholder="placeholder" | ||||
| 					ref="text" | ||||
| 					v-autocomplete="'text'" | ||||
| 				></textarea> | ||||
| 				<button class="emoji" @click="emoji" ref="emoji"> | ||||
| 					<fa :icon="['far', 'laugh']"/> | ||||
| 				</button> | ||||
| 			</div> | ||||
| 			<div class="files" v-show="files.length != 0"> | ||||
| 				<x-draggable :list="files" :options="{ animation: 150 }"> | ||||
| 					<div v-for="file in files" :key="file.id"> | ||||
| 						<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div> | ||||
| 						<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" :title="$t('attach-cancel')" alt=""/> | ||||
| 					</div> | ||||
| 				</x-draggable> | ||||
| 			</div> | ||||
| 			<input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/> | ||||
| 			<mk-uploader ref="uploader" @uploaded="attachMedia"/> | ||||
| 			<footer> | ||||
| 				<button @click="chooseFile"><fa icon="upload"/></button> | ||||
| 				<button @click="chooseFileFromDrive"><fa icon="cloud"/></button> | ||||
| 				<button @click="post" :disabled="posting" class="post">{{ $t('note') }}</button> | ||||
| 			</footer> | ||||
| 		</div> | ||||
| 	</mk-widget-container> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import define from '../../../common/define-widget'; | ||||
| import i18n from '../../../i18n'; | ||||
| import insertTextAtCursor from 'insert-text-at-cursor'; | ||||
| import * as XDraggable from 'vuedraggable'; | ||||
|  | ||||
| export default define({ | ||||
| 	name: 'post-form', | ||||
| @@ -19,12 +54,19 @@ export default define({ | ||||
| 	}) | ||||
| }).extend({ | ||||
| 	i18n: i18n('desktop/views/widgets/post-form.vue'), | ||||
|  | ||||
| 	components: { | ||||
| 		XDraggable | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			posting: false, | ||||
| 			text: '' | ||||
| 			text: '', | ||||
| 			files: [], | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	computed: { | ||||
| 		placeholder(): string { | ||||
| 			const xs = [ | ||||
| @@ -38,6 +80,7 @@ export default define({ | ||||
| 			return xs[Math.floor(Math.random() * xs.length)]; | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		func() { | ||||
| 			if (this.props.design == 1) { | ||||
| @@ -47,14 +90,95 @@ export default define({ | ||||
| 			} | ||||
| 			this.save(); | ||||
| 		}, | ||||
|  | ||||
| 		chooseFile() { | ||||
| 			(this.$refs.file as any).click(); | ||||
| 		}, | ||||
|  | ||||
| 		chooseFileFromDrive() { | ||||
| 			this.$chooseDriveFile({ | ||||
| 				multiple: true | ||||
| 			}).then(files => { | ||||
| 				files.forEach(this.attachMedia); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		attachMedia(driveFile) { | ||||
| 			this.files.push(driveFile); | ||||
| 			this.$emit('change-attached-files', this.files); | ||||
| 		}, | ||||
|  | ||||
| 		detachMedia(id) { | ||||
| 			this.files = this.files.filter(x => x.id != id); | ||||
| 			this.$emit('change-attached-files', this.files); | ||||
| 		}, | ||||
|  | ||||
| 		onKeydown(e) { | ||||
| 			if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && !this.posting && this.text) this.post(); | ||||
| 		}, | ||||
|  | ||||
| 		onPaste(e) { | ||||
| 			Array.from(e.clipboardData.items).forEach((item: any) => { | ||||
| 				if (item.kind == 'file') { | ||||
| 					this.upload(item.getAsFile()); | ||||
| 				} | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		onChangeFile() { | ||||
| 			Array.from((this.$refs.file as any).files).forEach(this.upload); | ||||
| 		}, | ||||
|  | ||||
| 		upload(file) { | ||||
| 			(this.$refs.uploader as any).upload(file); | ||||
| 		}, | ||||
|  | ||||
| 		onDragover(e) { | ||||
| 			const isFile = e.dataTransfer.items[0].kind == 'file'; | ||||
| 			const isDriveFile = e.dataTransfer.types[0] == 'mk_drive_file'; | ||||
| 			if (isFile || isDriveFile) { | ||||
| 				e.preventDefault(); | ||||
| 				e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		onDrop(e): void { | ||||
| 			// ファイルだったら | ||||
| 			if (e.dataTransfer.files.length > 0) { | ||||
| 				e.preventDefault(); | ||||
| 				Array.from(e.dataTransfer.files).forEach(this.upload); | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			//#region ドライブのファイル | ||||
| 			const driveFile = e.dataTransfer.getData('mk_drive_file'); | ||||
| 			if (driveFile != null && driveFile != '') { | ||||
| 				const file = JSON.parse(driveFile); | ||||
| 				this.files.push(file); | ||||
| 				e.preventDefault(); | ||||
| 			} | ||||
| 			//#endregion | ||||
| 		}, | ||||
|  | ||||
| 		async emoji() { | ||||
| 			const Picker = await import('../components/emoji-picker-dialog.vue').then(m => m.default); | ||||
| 			const button = this.$refs.emoji; | ||||
| 			const rect = button.getBoundingClientRect(); | ||||
| 			const vm = this.$root.new(Picker, { | ||||
| 				x: button.offsetWidth + rect.left + window.pageXOffset, | ||||
| 				y: rect.top + window.pageYOffset | ||||
| 			}); | ||||
| 			vm.$once('chosen', emoji => { | ||||
| 				insertTextAtCursor(this.$refs.text, emoji); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		post() { | ||||
| 			this.posting = true; | ||||
|  | ||||
| 			this.$root.api('notes/create', { | ||||
| 				text: this.text | ||||
| 				text: this.text == '' ? undefined : this.text, | ||||
| 				fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, | ||||
| 			}).then(data => { | ||||
| 				this.clear(); | ||||
| 			}).catch(err => { | ||||
| @@ -63,34 +187,34 @@ export default define({ | ||||
| 				this.posting = false; | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		clear() { | ||||
| 			this.text = ''; | ||||
| 			this.files = []; | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .lhcuptdmcdkfwmipgazeawoiuxpzaclc-body | ||||
| 	> .textarea | ||||
| 		> .emoji | ||||
| 			position absolute | ||||
| 			top 0 | ||||
| 			right 0 | ||||
| 			padding 10px | ||||
| 			font-size 18px | ||||
| 			color var(--text) | ||||
| 			opacity 0.5 | ||||
|  | ||||
| 			&:hover | ||||
| 				color var(--textHighlighted) | ||||
| 				opacity 1 | ||||
|  | ||||
| .mkw-post-form | ||||
| 	background #fff | ||||
| 	overflow hidden | ||||
| 	border solid 1px rgba(#000, 0.075) | ||||
| 	border-radius 6px | ||||
|  | ||||
| 	> .title | ||||
| 		z-index 1 | ||||
| 		margin 0 | ||||
| 		padding 0 16px | ||||
| 		line-height 42px | ||||
| 		font-size 0.9em | ||||
| 		font-weight bold | ||||
| 		color #888 | ||||
| 		box-shadow 0 1px rgba(#000, 0.07) | ||||
|  | ||||
| 		> [data-icon] | ||||
| 			margin-right 4px | ||||
| 			&:active | ||||
| 				color var(--primary) | ||||
| 				opacity 1 | ||||
|  | ||||
| 		> textarea | ||||
| 			display block | ||||
| @@ -98,16 +222,64 @@ export default define({ | ||||
| 			max-width 100% | ||||
| 			min-width 100% | ||||
| 			padding 16px | ||||
| 		margin-bottom 28px + 16px | ||||
| 			color var(--desktopPostFormTextareaFg) | ||||
| 			outline none | ||||
| 			background var(--desktopPostFormTextareaBg) | ||||
| 			border none | ||||
| 		border-bottom solid 1px #eee | ||||
| 			border-bottom solid 1px var(--faceDivider) | ||||
|  | ||||
| 	> button | ||||
| 			&:focus | ||||
| 				& + .emoji | ||||
| 					opacity 0.7 | ||||
|  | ||||
| 	> .files | ||||
| 		> div | ||||
| 			padding 4px | ||||
|  | ||||
| 			&:after | ||||
| 				content "" | ||||
| 				display block | ||||
| 				clear both | ||||
|  | ||||
| 			> div | ||||
| 				float left | ||||
| 				border solid 4px transparent | ||||
| 				cursor move | ||||
|  | ||||
| 				&:hover > .remove | ||||
| 					display block | ||||
|  | ||||
| 				> .img | ||||
| 					width 64px | ||||
| 					height 64px | ||||
| 					background-size cover | ||||
| 					background-position center center | ||||
|  | ||||
| 				> .remove | ||||
| 					display none | ||||
| 					position absolute | ||||
| 		bottom 8px | ||||
| 		right 8px | ||||
| 		margin 0 | ||||
| 					top -6px | ||||
| 					right -6px | ||||
| 					width 16px | ||||
| 					height 16px | ||||
| 					cursor pointer | ||||
|  | ||||
| 	> input[type=file] | ||||
| 		display none | ||||
|  | ||||
| 	> footer | ||||
| 		display flex | ||||
| 		padding 8px | ||||
|  | ||||
| 		> button:not(.post) | ||||
| 			color var(--text) | ||||
|  | ||||
| 			&:hover | ||||
| 				color var(--textHighlighted) | ||||
|  | ||||
| 		> .post | ||||
| 			display block | ||||
| 			margin 0 0 0 auto | ||||
| 			padding 0 10px | ||||
| 			height 28px | ||||
| 			color var(--primaryForeground) | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| 		<button slot="func" :title="$t('title')" @click="fetch"><fa icon="sync"/></button> | ||||
|  | ||||
| 		<div class="mkw-trends--body"> | ||||
| 			<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 			<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 			<div class="note" v-else-if="note != null"> | ||||
| 				<p class="text"><router-link :to="note | notePage">{{ note.text }}</router-link></p> | ||||
| 				<p class="author">―<router-link :to="note.user | userPage">@{{ note.user | acct }}</router-link></p> | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| 		<button slot="func" :title="$t('title')" @click="refresh"><fa icon="sync"/></button> | ||||
|  | ||||
| 		<div class="mkw-users--body"> | ||||
| 			<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 			<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> | ||||
| 			<template v-else-if="users.length != 0"> | ||||
| 				<div class="user" v-for="_user in users"> | ||||
| 					<mk-avatar class="avatar" :user="_user"/> | ||||
| @@ -114,11 +114,6 @@ export default define({ | ||||
| 					color var(--text) | ||||
| 					opacity 0.7 | ||||
|  | ||||
| 			> .mk-follow-button | ||||
| 				position absolute | ||||
| 				top 16px | ||||
| 				right 16px | ||||
|  | ||||
| 		> .empty | ||||
| 			margin 0 | ||||
| 			padding 16px | ||||
|   | ||||
| @@ -7,7 +7,6 @@ import Vuex from 'vuex'; | ||||
| import VueRouter from 'vue-router'; | ||||
| import VAnimateCss from 'v-animate-css'; | ||||
| import VModal from 'vue-js-modal'; | ||||
| import VueSweetalert2 from 'vue-sweetalert2'; | ||||
| import VueI18n from 'vue-i18n'; | ||||
|  | ||||
| import VueHotkey from './common/hotkey'; | ||||
| @@ -16,6 +15,7 @@ import checkForUpdate from './common/scripts/check-for-update'; | ||||
| import MiOS from './mios'; | ||||
| import { clientVersion as version, codename, lang } from './config'; | ||||
| import { builtinThemes, lightTheme, applyTheme } from './theme'; | ||||
| import Alert from './common/views/components/alert.vue'; | ||||
|  | ||||
| if (localStorage.getItem('theme') == null) { | ||||
| 	applyTheme(lightTheme); | ||||
| @@ -25,47 +25,126 @@ if (localStorage.getItem('theme') == null) { | ||||
| import { library } from '@fortawesome/fontawesome-svg-core'; | ||||
| import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; | ||||
|  | ||||
| /* なぜか動かない | ||||
| import faRetweet from '@fortawesome/free-solid-svg-icons/faRetweet'; | ||||
| import faPlus from '@fortawesome/free-solid-svg-icons/faPlus'; | ||||
| import faUser from '@fortawesome/free-solid-svg-icons/faUser'; | ||||
| import faCog from '@fortawesome/free-solid-svg-icons/faCog'; | ||||
| import faCheck from '@fortawesome/free-solid-svg-icons/faCheck'; | ||||
| import faStar from '@fortawesome/free-solid-svg-icons/faStar'; | ||||
| import faReply from '@fortawesome/free-solid-svg-icons/faReply'; | ||||
| import faEllipsisH from '@fortawesome/free-solid-svg-icons/faEllipsisH'; | ||||
| import faQuoteLeft from '@fortawesome/free-solid-svg-icons/faQuoteLeft'; | ||||
| import faQuoteRight from '@fortawesome/free-solid-svg-icons/faQuoteRight'; | ||||
| import faAngleUp from '@fortawesome/free-solid-svg-icons/faAngleUp'; | ||||
| import faAngleDown from '@fortawesome/free-solid-svg-icons/faAngleDown'; | ||||
| import faAt from '@fortawesome/free-solid-svg-icons/faAt'; | ||||
| import faHashtag from '@fortawesome/free-solid-svg-icons/faHashtag'; | ||||
| import faHome from '@fortawesome/free-solid-svg-icons/faHome'; | ||||
| import faGlobe from '@fortawesome/free-solid-svg-icons/faGlobe'; | ||||
| import faCircle from '@fortawesome/free-solid-svg-icons/faCircle'; | ||||
| import faList from '@fortawesome/free-solid-svg-icons/faList'; | ||||
| import faHeart from '@fortawesome/free-solid-svg-icons/faHeart'; | ||||
| import faUnlock from '@fortawesome/free-solid-svg-icons/faUnlock'; | ||||
| import faRssSquare from '@fortawesome/free-solid-svg-icons/faRssSquare'; | ||||
| import faSort from '@fortawesome/free-solid-svg-icons/faSort'; | ||||
| import faChartPie from '@fortawesome/free-solid-svg-icons/faChartPie'; | ||||
| import faChartBar from '@fortawesome/free-solid-svg-icons/faChartBar'; | ||||
| import faPencilAlt from '@fortawesome/free-solid-svg-icons/faPencilAlt'; | ||||
| import faColumns from '@fortawesome/free-solid-svg-icons/faColumns'; | ||||
| import faComments from '@fortawesome/free-solid-svg-icons/faComments'; | ||||
| import faGamepad from '@fortawesome/free-solid-svg-icons/faGamepad'; | ||||
| import faCloud from '@fortawesome/free-solid-svg-icons/faCloud'; | ||||
| import faPowerOff from '@fortawesome/free-solid-svg-icons/faPowerOff'; | ||||
| import faChevronCircleLeft from '@fortawesome/free-solid-svg-icons/faChevronCircleLeft'; | ||||
| import faChevronCircleRight from '@fortawesome/free-solid-svg-icons/faChevronCircleRight'; | ||||
| import faShareAlt from '@fortawesome/free-solid-svg-icons/faShareAlt'; | ||||
| import faTimes from '@fortawesome/free-solid-svg-icons/faTimes'; | ||||
| import faThumbtack from '@fortawesome/free-solid-svg-icons/faThumbtack'; | ||||
| import faSearch from '@fortawesome/free-solid-svg-icons/faSearch'; | ||||
| import { | ||||
| 	faRetweet, | ||||
| 	faPlus, | ||||
| 	faUser, | ||||
| 	faCog, | ||||
| 	faCheck, | ||||
| 	faStar, | ||||
| 	faReply, | ||||
| 	faEllipsisH, | ||||
| 	faQuoteLeft, | ||||
| 	faQuoteRight, | ||||
| 	faAngleUp, | ||||
| 	faAngleDown, | ||||
| 	faAt, | ||||
| 	faHashtag, | ||||
| 	faHome, | ||||
| 	faGlobe, | ||||
| 	faCircle, | ||||
| 	faList, | ||||
| 	faHeart, | ||||
| 	faUnlock, | ||||
| 	faRssSquare, | ||||
| 	faSort, | ||||
| 	faChartPie, | ||||
| 	faChartBar, | ||||
| 	faPencilAlt, | ||||
| 	faColumns, | ||||
| 	faComments, | ||||
| 	faGamepad, | ||||
| 	faCloud, | ||||
| 	faPowerOff, | ||||
| 	faChevronCircleLeft, | ||||
| 	faChevronCircleRight, | ||||
| 	faShareAlt, | ||||
| 	faTimes, | ||||
| 	faThumbtack, | ||||
| 	faSearch, | ||||
| 	faAngleRight, | ||||
| 	faWrench, | ||||
| 	faTerminal, | ||||
| 	faMoon, | ||||
| 	faPalette, | ||||
| 	faSlidersH, | ||||
| 	faDesktop, | ||||
| 	faVolumeUp, | ||||
| 	faLanguage, | ||||
| 	faInfoCircle, | ||||
| 	faExclamationTriangle, | ||||
| 	faKey, | ||||
| 	faBan, | ||||
| 	faCogs, | ||||
| 	faUnlockAlt, | ||||
| 	faPuzzlePiece, | ||||
| 	faMobileAlt, | ||||
| 	faSignInAlt, | ||||
| 	faSyncAlt, | ||||
| 	faPaperPlane, | ||||
| 	faUpload, | ||||
| 	faMapMarkerAlt, | ||||
| 	faEnvelope, | ||||
| 	faLock, | ||||
| 	faFolderOpen, | ||||
| 	faBirthdayCake, | ||||
| 	faImage, | ||||
| 	faEye, | ||||
| 	faDownload, | ||||
| 	faFileImport, | ||||
| 	faLink, | ||||
| 	faArrowRight, | ||||
| 	faICursor, | ||||
| 	faCaretRight, | ||||
| 	faReplyAll, | ||||
| 	faCamera, | ||||
| 	faMinus, | ||||
| 	faCaretDown, | ||||
| 	faCalculator, | ||||
| 	faUsers, | ||||
| 	faBars, | ||||
| 	faFileImage, | ||||
| 	faPollH, | ||||
| 	faFolder, | ||||
| 	faMicrochip, | ||||
| 	faMemory, | ||||
| 	faServer, | ||||
| 	faExclamationCircle, | ||||
| 	faSpinner, | ||||
| 	faBroadcastTower, | ||||
| 	faChartLine, | ||||
| 	faEllipsisV, | ||||
| 	faStickyNote, | ||||
| 	faUserPlus, | ||||
| 	faExternalLinkSquareAlt, | ||||
| 	faSync, | ||||
| } from '@fortawesome/free-solid-svg-icons'; | ||||
|  | ||||
| import farBell from '@fortawesome/free-regular-svg-icons/faBell'; | ||||
| import farEnvelope from '@fortawesome/free-regular-svg-icons/faEnvelope'; | ||||
| import farComments from '@fortawesome/free-regular-svg-icons/faComments'; | ||||
| import { | ||||
| 	faBell as farBell, | ||||
| 	faEnvelope as farEnvelope, | ||||
| 	faComments as farComments, | ||||
| 	faTrashAlt as farTrashAlt, | ||||
| 	faWindowRestore as farWindowRestore, | ||||
| 	faFolder as farFolder, | ||||
| 	faLaugh as farLaugh, | ||||
| 	faSmile as farSmile, | ||||
| 	faEyeSlash as farEyeSlash, | ||||
| 	faFolderOpen as farFolderOpen, | ||||
| 	faSave as farSave, | ||||
| 	faImages as farImages, | ||||
| 	faChartBar as farChartBar, | ||||
| 	faCommentAlt as farCommentAlt, | ||||
| 	faClock as farClock, | ||||
| 	faCalendarAlt as farCalendarAlt, | ||||
| 	faHdd as farHdd, | ||||
| } from '@fortawesome/free-regular-svg-icons'; | ||||
|  | ||||
| import { | ||||
| 	faTwitter as fabTwitter, | ||||
| 	faGithub as fabGithub, | ||||
| } from '@fortawesome/free-brands-svg-icons'; | ||||
| import i18n from './i18n'; | ||||
|  | ||||
| library.add( | ||||
| 	faRetweet, | ||||
| @@ -104,16 +183,84 @@ library.add( | ||||
| 	faTimes, | ||||
| 	faThumbtack, | ||||
| 	faSearch, | ||||
| 	faAngleRight, | ||||
| 	faWrench, | ||||
| 	faTerminal, | ||||
| 	faMoon, | ||||
| 	faPalette, | ||||
| 	faSlidersH, | ||||
| 	faDesktop, | ||||
| 	faVolumeUp, | ||||
| 	faLanguage, | ||||
| 	faInfoCircle, | ||||
| 	faExclamationTriangle, | ||||
| 	faKey, | ||||
| 	faBan, | ||||
| 	faCogs, | ||||
| 	faUnlockAlt, | ||||
| 	faPuzzlePiece, | ||||
| 	faMobileAlt, | ||||
| 	faSignInAlt, | ||||
| 	faSyncAlt, | ||||
| 	faPaperPlane, | ||||
| 	faUpload, | ||||
| 	faMapMarkerAlt, | ||||
| 	faEnvelope, | ||||
| 	faLock, | ||||
| 	faFolderOpen, | ||||
| 	faBirthdayCake, | ||||
| 	faImage, | ||||
| 	faEye, | ||||
| 	faDownload, | ||||
| 	faFileImport, | ||||
| 	faLink, | ||||
| 	faArrowRight, | ||||
| 	faICursor, | ||||
| 	faCaretRight, | ||||
| 	faReplyAll, | ||||
| 	faCamera, | ||||
| 	faMinus, | ||||
| 	faCaretDown, | ||||
| 	faCalculator, | ||||
| 	faUsers, | ||||
| 	faBars, | ||||
| 	faFileImage, | ||||
| 	faPollH, | ||||
| 	faFolder, | ||||
| 	faMicrochip, | ||||
| 	faMemory, | ||||
| 	faServer, | ||||
| 	faExclamationCircle, | ||||
| 	faSpinner, | ||||
| 	faBroadcastTower, | ||||
| 	faChartLine, | ||||
| 	faEllipsisV, | ||||
| 	faStickyNote, | ||||
| 	faUserPlus, | ||||
| 	faExternalLinkSquareAlt, | ||||
| 	faSync, | ||||
|  | ||||
| 	farBell, | ||||
| 	farEnvelope, | ||||
| 	farComments, | ||||
| 	farTrashAlt, | ||||
| 	farWindowRestore, | ||||
| 	farFolder, | ||||
| 	farLaugh, | ||||
| 	farSmile, | ||||
| 	farEyeSlash, | ||||
| 	farFolderOpen, | ||||
| 	farSave, | ||||
| 	farImages, | ||||
| 	farChartBar, | ||||
| 	farCommentAlt, | ||||
| 	farClock, | ||||
| 	farCalendarAlt, | ||||
| 	farHdd, | ||||
|  | ||||
| 	fabTwitter, | ||||
| 	fabGithub | ||||
| ); | ||||
| */ | ||||
|  | ||||
| import { fas } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { far } from '@fortawesome/free-regular-svg-icons'; | ||||
|  | ||||
| library.add(fas, far); | ||||
| //#endregion | ||||
|  | ||||
| Vue.use(Vuex); | ||||
| @@ -121,7 +268,6 @@ Vue.use(VueRouter); | ||||
| Vue.use(VAnimateCss); | ||||
| Vue.use(VModal); | ||||
| Vue.use(VueHotkey); | ||||
| Vue.use(VueSweetalert2); | ||||
| Vue.use(VueI18n); | ||||
|  | ||||
| Vue.component('fa', FontAwesomeIcon); | ||||
| @@ -269,13 +415,7 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS]) => void, | ||||
| 			}, { passive: true }); | ||||
|  | ||||
| 			const app = new Vue({ | ||||
| 				i18n: new VueI18n({ | ||||
| 					sync: false, | ||||
| 					locale: lang, | ||||
| 					messages: { | ||||
| 						[lang]: {} | ||||
| 					} | ||||
| 				}), | ||||
| 				i18n: i18n(), | ||||
| 				store: os.store, | ||||
| 				data() { | ||||
| 					return { | ||||
| @@ -299,6 +439,13 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS]) => void, | ||||
| 						document.body.appendChild(x.$el); | ||||
| 						return x; | ||||
| 					}, | ||||
| 					alert(opts) { | ||||
| 						return new Promise((res) => { | ||||
| 							const vm = this.new(Alert, opts); | ||||
| 							vm.$once('ok', () => res(true)); | ||||
| 							vm.$once('cancel', () => res(false)); | ||||
| 						}); | ||||
| 					} | ||||
| 				}, | ||||
| 				router, | ||||
| 				render: createEl => createEl(App) | ||||
|   | ||||
| @@ -172,7 +172,7 @@ export default class MiOS extends EventEmitter { | ||||
| 			callback(); | ||||
|  | ||||
| 			// Init service worker | ||||
| 			if (this.shouldRegisterSw) this.registerSw(); | ||||
| 			//if (this.shouldRegisterSw) this.registerSw(); | ||||
| 		}; | ||||
|  | ||||
| 		// キャッシュがあったとき | ||||
| @@ -365,7 +365,7 @@ export default class MiOS extends EventEmitter { | ||||
| 		}); | ||||
|  | ||||
| 		// The path of service worker script | ||||
| 		const sw = `/sw.${version}.${lang}.js`; | ||||
| 		const sw = `/sw.${version}.js`; | ||||
|  | ||||
| 		// Register service worker | ||||
| 		navigator.serviceWorker.register(sw).then(registration => { | ||||
|   | ||||
| @@ -27,7 +27,6 @@ import MkFollowing from './views/pages/following.vue'; | ||||
| import MkFavorites from './views/pages/favorites.vue'; | ||||
| import MkUserLists from './views/pages/user-lists.vue'; | ||||
| import MkUserList from './views/pages/user-list.vue'; | ||||
| import MkSettings from './views/pages/settings.vue'; | ||||
| import MkReversi from './views/pages/games/reversi.vue'; | ||||
| import MkTag from './views/pages/tag.vue'; | ||||
| import MkShare from './views/pages/share.vue'; | ||||
| @@ -36,7 +35,6 @@ import MkFollow from '../common/views/pages/follow.vue'; | ||||
| import PostForm from './views/components/post-form-dialog.vue'; | ||||
| import FileChooser from './views/components/drive-file-chooser.vue'; | ||||
| import FolderChooser from './views/components/drive-folder-chooser.vue'; | ||||
| import Dialog from './views/components/dialog.vue'; | ||||
|  | ||||
| /** | ||||
|  * init | ||||
| @@ -100,21 +98,6 @@ init((launch) => { | ||||
| 				}); | ||||
| 			}, | ||||
|  | ||||
| 			$dialog(opts) { | ||||
| 				return new Promise<string>((res, rej) => { | ||||
| 					const o = opts || {}; | ||||
| 					const d = this.$root.new(Dialog, { | ||||
| 						title: o.title, | ||||
| 						text: o.text, | ||||
| 						modal: o.modal, | ||||
| 						buttons: o.actions | ||||
| 					}); | ||||
| 					d.$once('clicked', id => { | ||||
| 						res(id); | ||||
| 					}); | ||||
| 				}); | ||||
| 			}, | ||||
|  | ||||
| 			$notify(message) { | ||||
| 				alert(message); | ||||
| 			} | ||||
| @@ -137,7 +120,7 @@ init((launch) => { | ||||
| 		routes: [ | ||||
| 			{ path: '/', name: 'index', component: MkIndex }, | ||||
| 			{ path: '/signup', name: 'signup', component: MkSignup }, | ||||
| 			{ path: '/i/settings', name: 'settings', component: MkSettings }, | ||||
| 			{ path: '/i/settings', name: 'settings', component: () => import('./views/pages/settings.vue').then(m => m.default) }, | ||||
| 			{ path: '/i/notifications', name: 'notifications', component: MkNotifications }, | ||||
| 			{ path: '/i/favorites', name: 'favorites', component: MkFavorites }, | ||||
| 			{ path: '/i/lists', name: 'user-lists', component: MkUserLists }, | ||||
| @@ -154,7 +137,7 @@ init((launch) => { | ||||
| 			{ path: '/tags/:tag', component: MkTag }, | ||||
| 			{ path: '/share', component: MkShare }, | ||||
| 			{ path: '/reversi/:game?', name: 'reversi', component: MkReversi }, | ||||
| 			{ path: '/@:user', component: MkUser }, | ||||
| 			{ path: '/@:user', component: () => import('./views/pages/user.vue').then(m => m.default) }, | ||||
| 			{ path: '/@:user/followers', component: MkFollowers }, | ||||
| 			{ path: '/@:user/following', component: MkFollowing }, | ||||
| 			{ path: '/notes/:note', component: MkNote }, | ||||
|   | ||||
| @@ -1,167 +0,0 @@ | ||||
| <template> | ||||
| <div class="mk-dialog"> | ||||
| 	<div class="bg" ref="bg" @click="onBgClick"></div> | ||||
| 	<div class="main" ref="main"> | ||||
| 		<header v-html="title" :class="$style.header"></header> | ||||
| 		<div class="body" v-html="text"></div> | ||||
| 		<div class="buttons"> | ||||
| 			<button v-for="button in buttons" @click="click(button)">{{ button.text }}</button> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import * as anime from 'animejs'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		title: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 		}, | ||||
| 		text: { | ||||
| 			type: String, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		buttons: { | ||||
| 			type: Array, | ||||
| 			default: () => { | ||||
| 				return [{ | ||||
| 					text: 'OK' | ||||
| 				}]; | ||||
| 			} | ||||
| 		}, | ||||
| 		modal: { | ||||
| 			type: Boolean, | ||||
| 			default: false | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.$nextTick(() => { | ||||
| 			(this.$refs.bg as any).style.pointerEvents = 'auto'; | ||||
| 			anime({ | ||||
| 				targets: this.$refs.bg, | ||||
| 				opacity: 1, | ||||
| 				duration: 100, | ||||
| 				easing: 'linear' | ||||
| 			}); | ||||
|  | ||||
| 			anime({ | ||||
| 				targets: this.$refs.main, | ||||
| 				opacity: 1, | ||||
| 				scale: [1.2, 1], | ||||
| 				duration: 300, | ||||
| 				easing: [0, 0.5, 0.5, 1] | ||||
| 			}); | ||||
| 		}); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		click(button) { | ||||
| 			this.$emit('clicked', button.id); | ||||
| 			this.close(); | ||||
| 		}, | ||||
| 		close() { | ||||
| 			(this.$refs.bg as any).style.pointerEvents = 'none'; | ||||
| 			anime({ | ||||
| 				targets: this.$refs.bg, | ||||
| 				opacity: 0, | ||||
| 				duration: 300, | ||||
| 				easing: 'linear' | ||||
| 			}); | ||||
|  | ||||
| 			(this.$refs.main as any).style.pointerEvents = 'none'; | ||||
| 			anime({ | ||||
| 				targets: this.$refs.main, | ||||
| 				opacity: 0, | ||||
| 				scale: 0.8, | ||||
| 				duration: 300, | ||||
| 				easing: [ 0.5, -0.5, 1, 0.5 ], | ||||
| 				complete: () => this.destroyDom() | ||||
| 			}); | ||||
| 		}, | ||||
| 		onBgClick() { | ||||
| 			if (!this.modal) { | ||||
| 				this.close(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .mk-dialog | ||||
| 	> .bg | ||||
| 		display block | ||||
| 		position fixed | ||||
| 		z-index 8192 | ||||
| 		top 0 | ||||
| 		left 0 | ||||
| 		width 100% | ||||
| 		height 100% | ||||
| 		background rgba(#000, 0.7) | ||||
| 		opacity 0 | ||||
| 		pointer-events none | ||||
|  | ||||
| 	> .main | ||||
| 		display block | ||||
| 		position fixed | ||||
| 		z-index 8192 | ||||
| 		top 20% | ||||
| 		left 0 | ||||
| 		right 0 | ||||
| 		margin 0 auto 0 auto | ||||
| 		padding 16px | ||||
| 		width calc(100% - 32px) | ||||
| 		max-width 300px | ||||
| 		background #fff | ||||
| 		opacity 0 | ||||
|  | ||||
| 		> .body | ||||
| 			margin 1em 0 | ||||
| 			color #888 | ||||
|  | ||||
| 		> .buttons | ||||
| 			> button | ||||
| 				display inline-block | ||||
| 				float right | ||||
| 				margin 0 | ||||
| 				padding 0 10px | ||||
| 				font-size 1.1em | ||||
| 				font-weight normal | ||||
| 				text-decoration none | ||||
| 				color #888 | ||||
| 				background transparent | ||||
| 				outline none | ||||
| 				border none | ||||
| 				border-radius 0 | ||||
| 				cursor pointer | ||||
| 				transition color 0.1s ease | ||||
|  | ||||
| 				i | ||||
| 					margin 0 0.375em | ||||
|  | ||||
| 				&:hover | ||||
| 					color var(--primary) | ||||
|  | ||||
| 				&:active | ||||
| 					color var(--primaryDarken10) | ||||
| 					transition color 0s ease | ||||
|  | ||||
| </style> | ||||
|  | ||||
| <style lang="stylus" module> | ||||
| .header | ||||
| 	margin 0 0 1em 0 | ||||
| 	color var(--primary) | ||||
| 	// color #43A4EC | ||||
| 	font-weight bold | ||||
|  | ||||
| 	&:empty | ||||
| 		display none | ||||
|  | ||||
| 	> i | ||||
| 		margin-right 0.5em | ||||
|  | ||||
| </style> | ||||
| @@ -31,7 +31,7 @@ | ||||
| 			<span class="created-at" @click="showCreatedAt"><fa :icon="['far', 'clock']"/><mk-time :time="file.createdAt"/></span> | ||||
| 			<template v-if="file.isSensitive"> | ||||
| 				<span class="separator"></span> | ||||
| 				<span class="nsfw"><fa icon="eye-slash"/> {{ $t('nsfw') }}</span> | ||||
| 				<span class="nsfw"><fa :icon="['far', 'eye-slash']"/> {{ $t('nsfw') }}</span> | ||||
| 			</template> | ||||
| 		</div> | ||||
| 	</div> | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user