Compare commits
	
		
			69 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					a43b0548ed | ||
| 
						 | 
					93e95f56f4 | ||
| 
						 | 
					cb0673b1ec | ||
| 
						 | 
					cd018db945 | ||
| 
						 | 
					50fe67b99b | ||
| 
						 | 
					1dba82aae5 | ||
| 
						 | 
					17c6d64750 | ||
| 
						 | 
					b4c04efa23 | ||
| 
						 | 
					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 | ||
| 
						 | 
					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.51.1",
 | 
			
		||||
	"clientVersion": "2.0.11802",
 | 
			
		||||
	"codename": "nighthike",
 | 
			
		||||
	"main": "./built/index.js",
 | 
			
		||||
	"private": true,
 | 
			
		||||
@@ -176,6 +176,7 @@
 | 
			
		||||
		"pug": "2.0.3",
 | 
			
		||||
		"punycode": "2.1.1",
 | 
			
		||||
		"qrcode": "1.3.2",
 | 
			
		||||
		"randomcolor": "0.5.3",
 | 
			
		||||
		"ratelimiter": "3.2.0",
 | 
			
		||||
		"recaptcha-promise": "0.1.3",
 | 
			
		||||
		"reconnecting-websocket": "4.1.10",
 | 
			
		||||
@@ -198,6 +199,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",
 | 
			
		||||
@@ -217,10 +219,10 @@
 | 
			
		||||
		"vue-i18n": "8.3.1",
 | 
			
		||||
		"vue-js-modal": "1.3.26",
 | 
			
		||||
		"vue-loader": "15.4.2",
 | 
			
		||||
		"vue-marquee-text-component": "1.1.0",
 | 
			
		||||
		"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
 | 
			
		||||
 
 | 
			
		||||
@@ -41,22 +41,22 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		add() {
 | 
			
		||||
			this.announcements.push({
 | 
			
		||||
			this.announcements.unshift({
 | 
			
		||||
				title: '',
 | 
			
		||||
				text: ''
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		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,21 +30,27 @@
 | 
			
		||||
			<!-- <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>
 | 
			
		||||
		</div>
 | 
			
		||||
	</nav>
 | 
			
		||||
	<main>
 | 
			
		||||
		<div v-if="page == 'dashboard'"><x-dashboard/></div>
 | 
			
		||||
		<div v-if="page == 'instance'"><x-instance/></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>
 | 
			
		||||
		<div v-if="page == 'hashtags'"><x-hashtags/></div>
 | 
			
		||||
		<div v-if="page == 'drive'"></div>
 | 
			
		||||
		<div v-if="page == 'update'"></div>
 | 
			
		||||
		<marquee-text v-if="instances.length > 0" class="instances" :repeat="10" :duration="20">
 | 
			
		||||
			<span v-for="instance in instances" class="instance"><b :style="{ background: instance.bg }">{{ instance.host }}</b>{{ instance.notesCount | number }}</span>
 | 
			
		||||
		</marquee-text>
 | 
			
		||||
		<div class="page">
 | 
			
		||||
			<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>
 | 
			
		||||
			<div v-if="page == 'hashtags'"><x-hashtags/></div>
 | 
			
		||||
			<div v-if="page == 'drive'"></div>
 | 
			
		||||
			<div v-if="page == 'update'"></div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</main>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -54,10 +61,15 @@ 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';
 | 
			
		||||
import MarqueeText from 'vue-marquee-text-component';
 | 
			
		||||
import randomColor from 'randomcolor';
 | 
			
		||||
 | 
			
		||||
// Detect the user agent
 | 
			
		||||
const ua = navigator.userAgent.toLowerCase();
 | 
			
		||||
@@ -68,10 +80,12 @@ export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XDashboard,
 | 
			
		||||
		XInstance,
 | 
			
		||||
		XModerators,
 | 
			
		||||
		XEmoji,
 | 
			
		||||
		XAnnouncements,
 | 
			
		||||
		XHashtags,
 | 
			
		||||
		XUsers
 | 
			
		||||
		XUsers,
 | 
			
		||||
		MarqueeText
 | 
			
		||||
	},
 | 
			
		||||
	provide: {
 | 
			
		||||
		isMobile
 | 
			
		||||
@@ -81,9 +95,26 @@ export default Vue.extend({
 | 
			
		||||
			page: 'dashboard',
 | 
			
		||||
			version,
 | 
			
		||||
			isMobile,
 | 
			
		||||
			navOpend: !isMobile
 | 
			
		||||
			navOpend: !isMobile,
 | 
			
		||||
			instances: [],
 | 
			
		||||
			faGrin,
 | 
			
		||||
			faArrowLeft,
 | 
			
		||||
			faHeadset
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	created() {
 | 
			
		||||
		this.$root.api('instances', {
 | 
			
		||||
			sort: '+notes'
 | 
			
		||||
		}).then(instances => {
 | 
			
		||||
			instances.forEach(i => {
 | 
			
		||||
				i.bg = randomColor({
 | 
			
		||||
					seed: i.host,
 | 
			
		||||
					luminosity: 'dark'
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
			this.instances = instances;
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		nav(page: string) {
 | 
			
		||||
			this.page = page;
 | 
			
		||||
@@ -92,7 +123,7 @@ export default Vue.extend({
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.mk-admin
 | 
			
		||||
	$headerHeight = 48px
 | 
			
		||||
 | 
			
		||||
@@ -253,7 +284,23 @@ export default Vue.extend({
 | 
			
		||||
	> main
 | 
			
		||||
		width 100%
 | 
			
		||||
		padding 0 0 0 250px
 | 
			
		||||
		max-width 1300px
 | 
			
		||||
 | 
			
		||||
		> .instances
 | 
			
		||||
			padding 8px
 | 
			
		||||
			background rgba(0, 0, 0, 0.7)
 | 
			
		||||
			color #fff
 | 
			
		||||
			font-size 14px
 | 
			
		||||
 | 
			
		||||
			>>> .instance
 | 
			
		||||
				margin 0 10px
 | 
			
		||||
 | 
			
		||||
				> b
 | 
			
		||||
					padding 0px 6px
 | 
			
		||||
					margin-right 4px
 | 
			
		||||
					border-radius 4px
 | 
			
		||||
 | 
			
		||||
		> .page
 | 
			
		||||
			max-width 1300px
 | 
			
		||||
 | 
			
		||||
	&.isMobile
 | 
			
		||||
		> main
 | 
			
		||||
 
 | 
			
		||||
@@ -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',
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,9 @@
 | 
			
		||||
	if (`${url.pathname}/`.startsWith('/admin/')) app = 'admin';
 | 
			
		||||
	//#endregion
 | 
			
		||||
 | 
			
		||||
	// Script version
 | 
			
		||||
	const ver = localStorage.getItem('v') || VERSION;
 | 
			
		||||
 | 
			
		||||
	//#region Detect the user language
 | 
			
		||||
	let lang = null;
 | 
			
		||||
 | 
			
		||||
@@ -67,7 +70,7 @@
 | 
			
		||||
 | 
			
		||||
	let locale = localStorage.getItem('locale');
 | 
			
		||||
	if (locale == null) {
 | 
			
		||||
		const locale = await fetch(`/assets/locales/${lang}.json`)
 | 
			
		||||
		const locale = await fetch(`/assets/locales/${lang}.json?ver=${ver}`)
 | 
			
		||||
			.then(response => response.json());
 | 
			
		||||
 | 
			
		||||
			localStorage.setItem('locale', JSON.stringify(locale));
 | 
			
		||||
@@ -98,9 +101,6 @@
 | 
			
		||||
		app = isMobile ? 'mobile' : 'desktop';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Script version
 | 
			
		||||
	const ver = localStorage.getItem('v') || VERSION;
 | 
			
		||||
 | 
			
		||||
	// Get salt query
 | 
			
		||||
	const salt = localStorage.getItem('salt')
 | 
			
		||||
		? `?salt=${localStorage.getItem('salt')}`
 | 
			
		||||
@@ -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,11 +95,18 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		del() {
 | 
			
		||||
			if (!window.confirm(this.$t('delete-confirm'))) return;
 | 
			
		||||
			this.$root.api('notes/delete', {
 | 
			
		||||
				noteId: this.note.id
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				this.destroyDom();
 | 
			
		||||
			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();
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
@@ -105,7 +114,10 @@ export default Vue.extend({
 | 
			
		||||
			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
 | 
			
		||||
 | 
			
		||||
	> *
 | 
			
		||||
		flex 1
 | 
			
		||||
	&:not(.noGrow)
 | 
			
		||||
		display flex
 | 
			
		||||
 | 
			
		||||
		&:not(:last-child)
 | 
			
		||||
			margin-right 16px
 | 
			
		||||
		> *
 | 
			
		||||
			flex 1
 | 
			
		||||
 | 
			
		||||
	> *: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,28 +15,33 @@
 | 
			
		||||
			<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')">
 | 
			
		||||
		<textarea :class="{ with: (files.length != 0 || poll) }"
 | 
			
		||||
			ref="text" v-model="text" :disabled="posting"
 | 
			
		||||
			@keydown="onKeydown" @paste="onPaste" :placeholder="placeholder"
 | 
			
		||||
			v-autocomplete="'text'"
 | 
			
		||||
		></textarea>
 | 
			
		||||
		<div class="files" :class="{ with: poll }" v-show="files.length != 0">
 | 
			
		||||
			<x-draggable :list="files" :options="{ animation: 150 }">
 | 
			
		||||
				<div v-for="file in files" :key="file.id">
 | 
			
		||||
					<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div>
 | 
			
		||||
					<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" :title="$t('attach-cancel')" alt=""/>
 | 
			
		||||
				</div>
 | 
			
		||||
			</x-draggable>
 | 
			
		||||
			<p class="remain">{{ 4 - files.length }}/4</p>
 | 
			
		||||
		<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">
 | 
			
		||||
						<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>
 | 
			
		||||
				<p class="remain">{{ 4 - files.length }}/4</p>
 | 
			
		||||
			</div>
 | 
			
		||||
			<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="saveDraft()"/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="saveDraft()"/>
 | 
			
		||||
	</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,27 +516,108 @@ export default Vue.extend({
 | 
			
		||||
		> input
 | 
			
		||||
			margin-bottom 8px
 | 
			
		||||
 | 
			
		||||
		> textarea
 | 
			
		||||
			margin 0
 | 
			
		||||
			max-width 100%
 | 
			
		||||
			min-width 100%
 | 
			
		||||
			min-height 84px
 | 
			
		||||
		> .textarea
 | 
			
		||||
			> .emoji
 | 
			
		||||
				position absolute
 | 
			
		||||
				top 0
 | 
			
		||||
				right 0
 | 
			
		||||
				padding 10px
 | 
			
		||||
				font-size 18px
 | 
			
		||||
				color var(--text)
 | 
			
		||||
				opacity 0.5
 | 
			
		||||
 | 
			
		||||
			&:hover
 | 
			
		||||
				& + *
 | 
			
		||||
				& + * + *
 | 
			
		||||
					border-color var(--primaryAlpha02)
 | 
			
		||||
					transition border-color .1s ease
 | 
			
		||||
				&:hover
 | 
			
		||||
					color var(--textHighlighted)
 | 
			
		||||
					opacity 1
 | 
			
		||||
 | 
			
		||||
			&:focus
 | 
			
		||||
				& + *
 | 
			
		||||
				& + * + *
 | 
			
		||||
					border-color var(--primaryAlpha05)
 | 
			
		||||
					transition border-color 0s ease
 | 
			
		||||
				&:active
 | 
			
		||||
					color var(--primary)
 | 
			
		||||
					opacity 1
 | 
			
		||||
 | 
			
		||||
			&.with
 | 
			
		||||
				border-bottom solid 1px var(--primaryAlpha01) !important
 | 
			
		||||
				border-radius 4px 4px 0 0
 | 
			
		||||
			> textarea
 | 
			
		||||
				margin 0
 | 
			
		||||
				max-width 100%
 | 
			
		||||
				min-width 100%
 | 
			
		||||
				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
 | 
			
		||||
 | 
			
		||||
			> .files
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 0
 | 
			
		||||
				background var(--desktopPostFormTextareaBg)
 | 
			
		||||
				border solid 1px var(--primaryAlpha01)
 | 
			
		||||
				border-top none
 | 
			
		||||
				border-radius 0 0 4px 4px
 | 
			
		||||
				transition border-color .3s ease
 | 
			
		||||
 | 
			
		||||
				&.with
 | 
			
		||||
					border-bottom solid 1px var(--primaryAlpha01) !important
 | 
			
		||||
					border-radius 0
 | 
			
		||||
 | 
			
		||||
				> .remain
 | 
			
		||||
					display block
 | 
			
		||||
					position absolute
 | 
			
		||||
					top 8px
 | 
			
		||||
					right 8px
 | 
			
		||||
					margin 0
 | 
			
		||||
					padding 0
 | 
			
		||||
					color var(--primaryAlpha04)
 | 
			
		||||
 | 
			
		||||
				> 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
 | 
			
		||||
							top -6px
 | 
			
		||||
							right -6px
 | 
			
		||||
							width 16px
 | 
			
		||||
							height 16px
 | 
			
		||||
							cursor pointer
 | 
			
		||||
 | 
			
		||||
			> .mk-poll-editor
 | 
			
		||||
				background var(--desktopPostFormTextareaBg)
 | 
			
		||||
				border solid 1px var(--primaryAlpha01)
 | 
			
		||||
				border-top none
 | 
			
		||||
				border-radius 0 0 4px 4px
 | 
			
		||||
				transition border-color .3s ease
 | 
			
		||||
 | 
			
		||||
		> .visibleUsers
 | 
			
		||||
			margin-bottom 8px
 | 
			
		||||
@@ -541,66 +640,6 @@ export default Vue.extend({
 | 
			
		||||
				margin-right 8px
 | 
			
		||||
				white-space nowrap
 | 
			
		||||
 | 
			
		||||
		> .files
 | 
			
		||||
			margin 0
 | 
			
		||||
			padding 0
 | 
			
		||||
			background var(--desktopPostFormTextareaBg)
 | 
			
		||||
			border solid 1px var(--primaryAlpha01)
 | 
			
		||||
			border-top none
 | 
			
		||||
			border-radius 0 0 4px 4px
 | 
			
		||||
			transition border-color .3s ease
 | 
			
		||||
 | 
			
		||||
			&.with
 | 
			
		||||
				border-bottom solid 1px var(--primaryAlpha01) !important
 | 
			
		||||
				border-radius 0
 | 
			
		||||
 | 
			
		||||
			> .remain
 | 
			
		||||
				display block
 | 
			
		||||
				position absolute
 | 
			
		||||
				top 8px
 | 
			
		||||
				right 8px
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 0
 | 
			
		||||
				color var(--primaryAlpha04)
 | 
			
		||||
 | 
			
		||||
			> 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
 | 
			
		||||
						top -6px
 | 
			
		||||
						right -6px
 | 
			
		||||
						width 16px
 | 
			
		||||
						height 16px
 | 
			
		||||
						cursor pointer
 | 
			
		||||
 | 
			
		||||
		> .mk-poll-editor
 | 
			
		||||
			background var(--desktopPostFormTextareaBg)
 | 
			
		||||
			border solid 1px var(--primaryAlpha01)
 | 
			
		||||
			border-top none
 | 
			
		||||
			border-radius 0 0 4px 4px
 | 
			
		||||
			transition border-color .3s ease
 | 
			
		||||
 | 
			
		||||
	> .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,13 +73,20 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		block() {
 | 
			
		||||
			if (!window.confirm(this.$t('block-confirm'))) return;
 | 
			
		||||
			this.$root.api('blocking/create', {
 | 
			
		||||
				userId: this.user.id
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				this.user.isBlocking = true;
 | 
			
		||||
			}, () => {
 | 
			
		||||
				alert('error');
 | 
			
		||||
			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(() => {
 | 
			
		||||
					this.user.isBlocking = true;
 | 
			
		||||
				}, () => {
 | 
			
		||||
					alert('error');
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
@@ -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,66 +187,114 @@ 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
 | 
			
		||||
			&:active
 | 
			
		||||
				color var(--primary)
 | 
			
		||||
				opacity 1
 | 
			
		||||
 | 
			
		||||
	> .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)
 | 
			
		||||
		> textarea
 | 
			
		||||
			display block
 | 
			
		||||
			width 100%
 | 
			
		||||
			max-width 100%
 | 
			
		||||
			min-width 100%
 | 
			
		||||
			padding 16px
 | 
			
		||||
			color var(--desktopPostFormTextareaFg)
 | 
			
		||||
			outline none
 | 
			
		||||
			background var(--desktopPostFormTextareaBg)
 | 
			
		||||
			border none
 | 
			
		||||
			border-bottom solid 1px var(--faceDivider)
 | 
			
		||||
 | 
			
		||||
		> [data-icon]
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
			&:focus
 | 
			
		||||
				& + .emoji
 | 
			
		||||
					opacity 0.7
 | 
			
		||||
 | 
			
		||||
	> textarea
 | 
			
		||||
		display block
 | 
			
		||||
		width 100%
 | 
			
		||||
		max-width 100%
 | 
			
		||||
		min-width 100%
 | 
			
		||||
		padding 16px
 | 
			
		||||
		margin-bottom 28px + 16px
 | 
			
		||||
		border none
 | 
			
		||||
		border-bottom solid 1px #eee
 | 
			
		||||
	> .files
 | 
			
		||||
		> div
 | 
			
		||||
			padding 4px
 | 
			
		||||
 | 
			
		||||
	> button
 | 
			
		||||
		display block
 | 
			
		||||
		position absolute
 | 
			
		||||
		bottom 8px
 | 
			
		||||
		right 8px
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 0 10px
 | 
			
		||||
		height 28px
 | 
			
		||||
		color var(--primaryForeground)
 | 
			
		||||
		background var(--primary) !important
 | 
			
		||||
		outline none
 | 
			
		||||
		border none
 | 
			
		||||
		border-radius 4px
 | 
			
		||||
		transition background 0.1s ease
 | 
			
		||||
		cursor pointer
 | 
			
		||||
			&:after
 | 
			
		||||
				content ""
 | 
			
		||||
				display block
 | 
			
		||||
				clear both
 | 
			
		||||
 | 
			
		||||
		&:hover
 | 
			
		||||
			background var(--primaryLighten10) !important
 | 
			
		||||
			> div
 | 
			
		||||
				float left
 | 
			
		||||
				border solid 4px transparent
 | 
			
		||||
				cursor move
 | 
			
		||||
 | 
			
		||||
		&:active
 | 
			
		||||
			background var(--primaryDarken10) !important
 | 
			
		||||
			transition background 0s ease
 | 
			
		||||
				&:hover > .remove
 | 
			
		||||
					display block
 | 
			
		||||
 | 
			
		||||
				> .img
 | 
			
		||||
					width 64px
 | 
			
		||||
					height 64px
 | 
			
		||||
					background-size cover
 | 
			
		||||
					background-position center center
 | 
			
		||||
 | 
			
		||||
				> .remove
 | 
			
		||||
					display none
 | 
			
		||||
					position absolute
 | 
			
		||||
					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)
 | 
			
		||||
			background var(--primary) !important
 | 
			
		||||
			outline none
 | 
			
		||||
			border none
 | 
			
		||||
			border-radius 4px
 | 
			
		||||
			transition background 0.1s ease
 | 
			
		||||
			cursor pointer
 | 
			
		||||
 | 
			
		||||
			&:hover
 | 
			
		||||
				background var(--primaryLighten10) !important
 | 
			
		||||
 | 
			
		||||
			&:active
 | 
			
		||||
				background var(--primaryDarken10) !important
 | 
			
		||||
				transition background 0s ease
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -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