Merge branch 'develop'
This commit is contained in:
		
						commit
						c26168f22e
					
				
					 97 changed files with 3292 additions and 2006 deletions
				
			
		| 
						 | 
					@ -54,6 +54,14 @@ mongodb:
 | 
				
			||||||
8. master ブランチに戻す
 | 
					8. master ブランチに戻す
 | 
				
			||||||
9. enjoy
 | 
					9. enjoy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					11.18.0 (2019/05/21)
 | 
				
			||||||
 | 
					--------------------
 | 
				
			||||||
 | 
					### ✨Improvements
 | 
				
			||||||
 | 
					* デザインの調整
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🐛Fixes
 | 
				
			||||||
 | 
					* 投稿の削除が連合しない問題を修正
 | 
				
			||||||
 | 
					
 | 
				
			||||||
11.17.1 (2019/05/20)
 | 
					11.17.1 (2019/05/20)
 | 
				
			||||||
--------------------
 | 
					--------------------
 | 
				
			||||||
### 🐛Fixes
 | 
					### 🐛Fixes
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -128,6 +128,20 @@ query.andWhere(new Brackets(qb => {
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Not `null` in TypeORM
 | 
				
			||||||
 | 
					```ts
 | 
				
			||||||
 | 
					const foo = await Foos.findOne({
 | 
				
			||||||
 | 
						bar: Not(null)
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					のようなクエリ(`bar`が`null`ではない)は期待通りに動作しない。
 | 
				
			||||||
 | 
					次のようにします:
 | 
				
			||||||
 | 
					```ts
 | 
				
			||||||
 | 
					const foo = await Foos.findOne({
 | 
				
			||||||
 | 
						bar: Not(IsNull())
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### `null` in SQL
 | 
					### `null` in SQL
 | 
				
			||||||
SQLを発行する際、パラメータが`null`になる可能性のある場合はSQL文を出し分けなければならない
 | 
					SQLを発行する際、パラメータが`null`になる可能性のある場合はSQL文を出し分けなければならない
 | 
				
			||||||
例えば
 | 
					例えば
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -251,6 +251,7 @@ common:
 | 
				
			||||||
  my-token-regenerated: "Váš token byl regenerován, proto budete odhlášen/a."
 | 
					  my-token-regenerated: "Váš token byl regenerován, proto budete odhlášen/a."
 | 
				
			||||||
  hide-password: "Skrýt heslo"
 | 
					  hide-password: "Skrýt heslo"
 | 
				
			||||||
  show-password: "Zobrazit heslo"
 | 
					  show-password: "Zobrazit heslo"
 | 
				
			||||||
 | 
					  enter-username: "Zadejte uživatelské jméno"
 | 
				
			||||||
  do-not-use-in-production: "Tohle je vývojářský build. Nepoužívejte v produkci."
 | 
					  do-not-use-in-production: "Tohle je vývojářský build. Nepoužívejte v produkci."
 | 
				
			||||||
  user-suspended: "Tomuto uživateli byl pozastaven účet."
 | 
					  user-suspended: "Tomuto uživateli byl pozastaven účet."
 | 
				
			||||||
  is-remote-user: "Informace o tomto uživateli nemusí být kompletní."
 | 
					  is-remote-user: "Informace o tomto uživateli nemusí být kompletní."
 | 
				
			||||||
| 
						 | 
					@ -414,14 +415,12 @@ common/views/components/messaging.vue:
 | 
				
			||||||
  search-user: "Najít uživatele"
 | 
					  search-user: "Najít uživatele"
 | 
				
			||||||
  you: "Vy"
 | 
					  you: "Vy"
 | 
				
			||||||
  no-history: "Žádná historie"
 | 
					  no-history: "Žádná historie"
 | 
				
			||||||
 | 
					  user: "Uživatel"
 | 
				
			||||||
common/views/components/messaging-room.vue:
 | 
					common/views/components/messaging-room.vue:
 | 
				
			||||||
  empty: "Žádné zprávy"
 | 
					 | 
				
			||||||
  new-message: "Máte novou zprávu"
 | 
					  new-message: "Máte novou zprávu"
 | 
				
			||||||
  only-one-file-attached: "Jenom JEDEN soubor může být přiložen ke zprávě."
 | 
					 | 
				
			||||||
common/views/components/messaging-room.form.vue:
 | 
					common/views/components/messaging-room.form.vue:
 | 
				
			||||||
  send: "Odeslat"
 | 
					  send: "Odeslat"
 | 
				
			||||||
  attach-from-local: "Přiložit soubory z Vašeho zařízení"
 | 
					  attach-from-local: "Přiložit soubory z Vašeho zařízení"
 | 
				
			||||||
  only-one-file-attached: "Jenom JEDEN soubor může být přiložen ke zprávě."
 | 
					 | 
				
			||||||
common/views/components/messaging-room.message.vue:
 | 
					common/views/components/messaging-room.message.vue:
 | 
				
			||||||
  is-read: "Přečtené"
 | 
					  is-read: "Přečtené"
 | 
				
			||||||
  deleted: "Tato zpráva byla odstraněna"
 | 
					  deleted: "Tato zpráva byla odstraněna"
 | 
				
			||||||
| 
						 | 
					@ -617,8 +616,15 @@ common/views/components/user-list-editor.vue:
 | 
				
			||||||
  remove-user: "Odebrat z tohoto seznamu"
 | 
					  remove-user: "Odebrat z tohoto seznamu"
 | 
				
			||||||
  delete-are-you-sure: "Smazat seznam \"$1\"?"
 | 
					  delete-are-you-sure: "Smazat seznam \"$1\"?"
 | 
				
			||||||
  deleted: "Smazáno"
 | 
					  deleted: "Smazáno"
 | 
				
			||||||
 | 
					  add-user: "Přidat uživatele"
 | 
				
			||||||
 | 
					common/views/components/user-group-editor.vue:
 | 
				
			||||||
 | 
					  deleted: "Smazáno"
 | 
				
			||||||
 | 
					  invite: "Pozvat"
 | 
				
			||||||
common/views/components/user-lists.vue:
 | 
					common/views/components/user-lists.vue:
 | 
				
			||||||
 | 
					  user-lists: "Seznamy"
 | 
				
			||||||
  list-name: "Název seznamu"
 | 
					  list-name: "Název seznamu"
 | 
				
			||||||
 | 
					common/views/components/user-groups.vue:
 | 
				
			||||||
 | 
					  invites: "Pozvat"
 | 
				
			||||||
common/views/widgets/broadcast.vue:
 | 
					common/views/widgets/broadcast.vue:
 | 
				
			||||||
  fetching: "Načítám"
 | 
					  fetching: "Načítám"
 | 
				
			||||||
  next: "Další"
 | 
					  next: "Další"
 | 
				
			||||||
| 
						 | 
					@ -853,11 +859,6 @@ desktop/views/components/ui.header.post.vue:
 | 
				
			||||||
  post: "Nový příspěvek"
 | 
					  post: "Nový příspěvek"
 | 
				
			||||||
desktop/views/components/ui.header.search.vue:
 | 
					desktop/views/components/ui.header.search.vue:
 | 
				
			||||||
  placeholder: "Vyhledávání"
 | 
					  placeholder: "Vyhledávání"
 | 
				
			||||||
desktop/views/components/received-follow-requests-window.vue:
 | 
					 | 
				
			||||||
  accept: "Přijmout"
 | 
					 | 
				
			||||||
  reject: "Odmítnout"
 | 
					 | 
				
			||||||
desktop/views/components/user-lists-window.vue:
 | 
					 | 
				
			||||||
  title: "Seznamy uživatelů"
 | 
					 | 
				
			||||||
desktop/views/components/user-preview.vue:
 | 
					desktop/views/components/user-preview.vue:
 | 
				
			||||||
  notes: "Příspěvky"
 | 
					  notes: "Příspěvky"
 | 
				
			||||||
desktop/views/components/users-list.vue:
 | 
					desktop/views/components/users-list.vue:
 | 
				
			||||||
| 
						 | 
					@ -902,8 +903,7 @@ admin/views/instance.vue:
 | 
				
			||||||
  recaptcha-config: "nastavení služby reCAPTCHA"
 | 
					  recaptcha-config: "nastavení služby reCAPTCHA"
 | 
				
			||||||
  recaptcha-info: "reCAPTCHA token je povinný. Můžete jej získat na https://www.google.com/recaptcha/intro/"
 | 
					  recaptcha-info: "reCAPTCHA token je povinný. Můžete jej získat na https://www.google.com/recaptcha/intro/"
 | 
				
			||||||
  enable-recaptcha: "povolit reCAPTCHA"
 | 
					  enable-recaptcha: "povolit reCAPTCHA"
 | 
				
			||||||
  recaptcha-site-key: "reCAPTCHA klíč stránky (site key)"
 | 
					  recaptcha-preview: "Náhled"
 | 
				
			||||||
  recaptcha-secret-key: "reCAPTCHA tajný klíč"
 | 
					 | 
				
			||||||
  twitter-integration-config: "Nastavení spojení s Twitterem"
 | 
					  twitter-integration-config: "Nastavení spojení s Twitterem"
 | 
				
			||||||
  twitter-integration-info: "The callback URL is set on {url}."
 | 
					  twitter-integration-info: "The callback URL is set on {url}."
 | 
				
			||||||
  enable-twitter-integration: "Povolit připojení k Twitteru"
 | 
					  enable-twitter-integration: "Povolit připojení k Twitteru"
 | 
				
			||||||
| 
						 | 
					@ -1159,8 +1159,6 @@ mobile/views/pages/drive.vue:
 | 
				
			||||||
  contextmenu:
 | 
					  contextmenu:
 | 
				
			||||||
    upload: "Nahrát soubor"
 | 
					    upload: "Nahrát soubor"
 | 
				
			||||||
    create-folder: "Vytvořit složku"
 | 
					    create-folder: "Vytvořit složku"
 | 
				
			||||||
mobile/views/pages/user-lists.vue:
 | 
					 | 
				
			||||||
  title: "Seznamy"
 | 
					 | 
				
			||||||
mobile/views/pages/signup.vue:
 | 
					mobile/views/pages/signup.vue:
 | 
				
			||||||
  lets-start: "Váš účet je připraven! 📦"
 | 
					  lets-start: "Váš účet je připraven! 📦"
 | 
				
			||||||
mobile/views/pages/home.vue:
 | 
					mobile/views/pages/home.vue:
 | 
				
			||||||
| 
						 | 
					@ -1177,9 +1175,6 @@ mobile/views/pages/widgets/activity.vue:
 | 
				
			||||||
  activity: "Aktivita"
 | 
					  activity: "Aktivita"
 | 
				
			||||||
mobile/views/pages/share.vue:
 | 
					mobile/views/pages/share.vue:
 | 
				
			||||||
  share-with: "Sdílet na {name}"
 | 
					  share-with: "Sdílet na {name}"
 | 
				
			||||||
mobile/views/pages/received-follow-requests.vue:
 | 
					 | 
				
			||||||
  accept: "Přijmout"
 | 
					 | 
				
			||||||
  reject: "Odmítnout"
 | 
					 | 
				
			||||||
mobile/views/pages/note.vue:
 | 
					mobile/views/pages/note.vue:
 | 
				
			||||||
  prev: "Předchozí příspěvěk"
 | 
					  prev: "Předchozí příspěvěk"
 | 
				
			||||||
  next: "Následující příspěvek"
 | 
					  next: "Následující příspěvek"
 | 
				
			||||||
| 
						 | 
					@ -1212,6 +1207,7 @@ deck/deck.user-column.vue:
 | 
				
			||||||
dev/views/new-app.vue:
 | 
					dev/views/new-app.vue:
 | 
				
			||||||
  app-name-desc: "Jméno vaší aplikace"
 | 
					  app-name-desc: "Jméno vaší aplikace"
 | 
				
			||||||
pages:
 | 
					pages:
 | 
				
			||||||
 | 
					  like: "Lajk"
 | 
				
			||||||
  title: "Titulek"
 | 
					  title: "Titulek"
 | 
				
			||||||
  blocks:
 | 
					  blocks:
 | 
				
			||||||
    post: "Formulář pro psaní"
 | 
					    post: "Formulář pro psaní"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1896
									
								
								locales/da-DK.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1896
									
								
								locales/da-DK.yml
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
					@ -281,9 +281,7 @@ common/views/components/messaging.vue:
 | 
				
			||||||
  you: "Du"
 | 
					  you: "Du"
 | 
				
			||||||
  no-history: "Keine Chronik"
 | 
					  no-history: "Keine Chronik"
 | 
				
			||||||
common/views/components/messaging-room.vue:
 | 
					common/views/components/messaging-room.vue:
 | 
				
			||||||
  empty: "Keine Unterhaltungen"
 | 
					 | 
				
			||||||
  no-history: "Keine weitere Chronik vorhanden"
 | 
					  no-history: "Keine weitere Chronik vorhanden"
 | 
				
			||||||
  resize-form: "Ziehen um die Größe zu verändern"
 | 
					 | 
				
			||||||
  new-message: "Neue Nachricht"
 | 
					  new-message: "Neue Nachricht"
 | 
				
			||||||
common/views/components/messaging-room.form.vue:
 | 
					common/views/components/messaging-room.form.vue:
 | 
				
			||||||
  input-message-here: "Nachricht hier eingeben"
 | 
					  input-message-here: "Nachricht hier eingeben"
 | 
				
			||||||
| 
						 | 
					@ -386,6 +384,12 @@ common/views/components/profile-editor.vue:
 | 
				
			||||||
  export-targets:
 | 
					  export-targets:
 | 
				
			||||||
    user-lists: "Listen"
 | 
					    user-lists: "Listen"
 | 
				
			||||||
  enter-password: "Bitte Passwort eingeben"
 | 
					  enter-password: "Bitte Passwort eingeben"
 | 
				
			||||||
 | 
					common/views/components/user-group-editor.vue:
 | 
				
			||||||
 | 
					  invite: "Einladen"
 | 
				
			||||||
 | 
					common/views/components/user-lists.vue:
 | 
				
			||||||
 | 
					  user-lists: "Listen"
 | 
				
			||||||
 | 
					common/views/components/user-groups.vue:
 | 
				
			||||||
 | 
					  invites: "Einladen"
 | 
				
			||||||
common/views/widgets/broadcast.vue:
 | 
					common/views/widgets/broadcast.vue:
 | 
				
			||||||
  fetching: "Laden"
 | 
					  fetching: "Laden"
 | 
				
			||||||
  no-broadcasts: "Keine Broadcasts"
 | 
					  no-broadcasts: "Keine Broadcasts"
 | 
				
			||||||
| 
						 | 
					@ -583,8 +587,6 @@ desktop/views/components/ui.header.post.vue:
 | 
				
			||||||
  post: "Einen neuen Post erstellen"
 | 
					  post: "Einen neuen Post erstellen"
 | 
				
			||||||
desktop/views/components/ui.header.search.vue:
 | 
					desktop/views/components/ui.header.search.vue:
 | 
				
			||||||
  placeholder: "Suchen"
 | 
					  placeholder: "Suchen"
 | 
				
			||||||
desktop/views/components/user-lists-window.vue:
 | 
					 | 
				
			||||||
  title: "Listen"
 | 
					 | 
				
			||||||
desktop/views/components/users-list.vue:
 | 
					desktop/views/components/users-list.vue:
 | 
				
			||||||
  fetching: "Lade…"
 | 
					  fetching: "Lade…"
 | 
				
			||||||
admin/views/dashboard.vue:
 | 
					admin/views/dashboard.vue:
 | 
				
			||||||
| 
						 | 
					@ -592,6 +594,7 @@ admin/views/dashboard.vue:
 | 
				
			||||||
admin/views/abuse.vue:
 | 
					admin/views/abuse.vue:
 | 
				
			||||||
  remove-report: "Löschen"
 | 
					  remove-report: "Löschen"
 | 
				
			||||||
admin/views/instance.vue:
 | 
					admin/views/instance.vue:
 | 
				
			||||||
 | 
					  recaptcha-preview: "Vorschau"
 | 
				
			||||||
  invite: "Einladen"
 | 
					  invite: "Einladen"
 | 
				
			||||||
  save: "Speichern"
 | 
					  save: "Speichern"
 | 
				
			||||||
admin/views/charts.vue:
 | 
					admin/views/charts.vue:
 | 
				
			||||||
| 
						 | 
					@ -677,8 +680,6 @@ mobile/views/pages/drive.vue:
 | 
				
			||||||
  contextmenu:
 | 
					  contextmenu:
 | 
				
			||||||
    upload: "Eine Datei hochladen"
 | 
					    upload: "Eine Datei hochladen"
 | 
				
			||||||
    create-folder: "Ein Verzeichnis erstellen"
 | 
					    create-folder: "Ein Verzeichnis erstellen"
 | 
				
			||||||
mobile/views/pages/user-lists.vue:
 | 
					 | 
				
			||||||
  title: "Listen"
 | 
					 | 
				
			||||||
mobile/views/pages/home.vue:
 | 
					mobile/views/pages/home.vue:
 | 
				
			||||||
  home: "Home"
 | 
					  home: "Home"
 | 
				
			||||||
  local: "Lokal"
 | 
					  local: "Lokal"
 | 
				
			||||||
| 
						 | 
					@ -732,6 +733,7 @@ dev/views/new-app.vue:
 | 
				
			||||||
  authority-desc: "Nur die hier eingetragenen Berechtigungen, werden per API zur Verfügung stehen."
 | 
					  authority-desc: "Nur die hier eingetragenen Berechtigungen, werden per API zur Verfügung stehen."
 | 
				
			||||||
  authority-warning: "Dies kann auch nach dem erstellen der Anwendung geändert werden, allerdings werden dann alle bisher generierten Token ungültig."
 | 
					  authority-warning: "Dies kann auch nach dem erstellen der Anwendung geändert werden, allerdings werden dann alle bisher generierten Token ungültig."
 | 
				
			||||||
pages:
 | 
					pages:
 | 
				
			||||||
 | 
					  like: "Gefällt mir"
 | 
				
			||||||
  blocks:
 | 
					  blocks:
 | 
				
			||||||
    post: "\"Neuer Beitrag\"-Formular"
 | 
					    post: "\"Neuer Beitrag\"-Formular"
 | 
				
			||||||
  script:
 | 
					  script:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -253,6 +253,7 @@ common:
 | 
				
			||||||
  my-token-regenerated: "Your token has been regenerated, so you will be signed out."
 | 
					  my-token-regenerated: "Your token has been regenerated, so you will be signed out."
 | 
				
			||||||
  hide-password: "Hide Password"
 | 
					  hide-password: "Hide Password"
 | 
				
			||||||
  show-password: "Show Password"
 | 
					  show-password: "Show Password"
 | 
				
			||||||
 | 
					  enter-username: "Enter user name"
 | 
				
			||||||
  do-not-use-in-production: "This is a development build. Do not use in production."
 | 
					  do-not-use-in-production: "This is a development build. Do not use in production."
 | 
				
			||||||
  user-suspended: "This user has been suspended."
 | 
					  user-suspended: "This user has been suspended."
 | 
				
			||||||
  is-remote-user: "The information about this user may not be entirely complete."
 | 
					  is-remote-user: "The information about this user may not be entirely complete."
 | 
				
			||||||
| 
						 | 
					@ -448,18 +449,15 @@ common/views/components/messaging.vue:
 | 
				
			||||||
  search-user: "Find a user"
 | 
					  search-user: "Find a user"
 | 
				
			||||||
  you: "You"
 | 
					  you: "You"
 | 
				
			||||||
  no-history: "Without history"
 | 
					  no-history: "Without history"
 | 
				
			||||||
 | 
					  user: "User"
 | 
				
			||||||
common/views/components/messaging-room.vue:
 | 
					common/views/components/messaging-room.vue:
 | 
				
			||||||
  empty: "No conversations"
 | 
					 | 
				
			||||||
  no-history: "There is no further history"
 | 
					  no-history: "There is no further history"
 | 
				
			||||||
  resize-form: "Drag to resize"
 | 
					 | 
				
			||||||
  new-message: "New message"
 | 
					  new-message: "New message"
 | 
				
			||||||
  only-one-file-attached: "Only ONE file can be attached to a message."
 | 
					 | 
				
			||||||
common/views/components/messaging-room.form.vue:
 | 
					common/views/components/messaging-room.form.vue:
 | 
				
			||||||
  input-message-here: "Enter message here"
 | 
					  input-message-here: "Enter message here"
 | 
				
			||||||
  send: "Send"
 | 
					  send: "Send"
 | 
				
			||||||
  attach-from-local: "Attach files from your device"
 | 
					  attach-from-local: "Attach files from your device"
 | 
				
			||||||
  attach-from-drive: "Attach files from your Drive"
 | 
					  attach-from-drive: "Attach files from your Drive"
 | 
				
			||||||
  only-one-file-attached: "Only one file can be attached to the message."
 | 
					 | 
				
			||||||
common/views/components/messaging-room.message.vue:
 | 
					common/views/components/messaging-room.message.vue:
 | 
				
			||||||
  is-read: "Read"
 | 
					  is-read: "Read"
 | 
				
			||||||
  deleted: "This message has been deleted"
 | 
					  deleted: "This message has been deleted"
 | 
				
			||||||
| 
						 | 
					@ -472,6 +470,7 @@ common/views/components/nav.vue:
 | 
				
			||||||
  repository: "Repository"
 | 
					  repository: "Repository"
 | 
				
			||||||
  develop: "Developers"
 | 
					  develop: "Developers"
 | 
				
			||||||
  feedback: "Feedback"
 | 
					  feedback: "Feedback"
 | 
				
			||||||
 | 
					  tos: "Terms Of Service"
 | 
				
			||||||
common/views/components/note-menu.vue:
 | 
					common/views/components/note-menu.vue:
 | 
				
			||||||
  mention: "Mention"
 | 
					  mention: "Mention"
 | 
				
			||||||
  detail: "Details"
 | 
					  detail: "Details"
 | 
				
			||||||
| 
						 | 
					@ -584,6 +583,8 @@ common/views/components/signup.vue:
 | 
				
			||||||
  password-matched: "OK"
 | 
					  password-matched: "OK"
 | 
				
			||||||
  password-not-matched: "Doesn't match"
 | 
					  password-not-matched: "Doesn't match"
 | 
				
			||||||
  recaptcha: "Verification"
 | 
					  recaptcha: "Verification"
 | 
				
			||||||
 | 
					  agree-to: "Accept {0}."
 | 
				
			||||||
 | 
					  tos: "Terms Of Service"
 | 
				
			||||||
  create: "Create an Account"
 | 
					  create: "Create an Account"
 | 
				
			||||||
  some-error: "An attempt at account creation has failed for some reason. Please try again."
 | 
					  some-error: "An attempt at account creation has failed for some reason. Please try again."
 | 
				
			||||||
common/views/components/special-message.vue:
 | 
					common/views/components/special-message.vue:
 | 
				
			||||||
| 
						 | 
					@ -691,9 +692,16 @@ common/views/components/user-list-editor.vue:
 | 
				
			||||||
  remove-user: "Remove from this list"
 | 
					  remove-user: "Remove from this list"
 | 
				
			||||||
  delete-are-you-sure: "Delete list \"$1\"?"
 | 
					  delete-are-you-sure: "Delete list \"$1\"?"
 | 
				
			||||||
  deleted: "Deleted successfully"
 | 
					  deleted: "Deleted successfully"
 | 
				
			||||||
 | 
					  add-user: "Add a user"
 | 
				
			||||||
 | 
					common/views/components/user-group-editor.vue:
 | 
				
			||||||
 | 
					  deleted: "Deleted"
 | 
				
			||||||
 | 
					  invite: "Invite"
 | 
				
			||||||
common/views/components/user-lists.vue:
 | 
					common/views/components/user-lists.vue:
 | 
				
			||||||
 | 
					  user-lists: "Lists"
 | 
				
			||||||
  create-list: "Create a list"
 | 
					  create-list: "Create a list"
 | 
				
			||||||
  list-name: "List name"
 | 
					  list-name: "List name"
 | 
				
			||||||
 | 
					common/views/components/user-groups.vue:
 | 
				
			||||||
 | 
					  invites: "Invite"
 | 
				
			||||||
common/views/widgets/broadcast.vue:
 | 
					common/views/widgets/broadcast.vue:
 | 
				
			||||||
  fetching: "Checking"
 | 
					  fetching: "Checking"
 | 
				
			||||||
  no-broadcasts: "No announcements"
 | 
					  no-broadcasts: "No announcements"
 | 
				
			||||||
| 
						 | 
					@ -755,6 +763,8 @@ common/views/pages/follow.vue:
 | 
				
			||||||
  request-pending: "Pending follow request"
 | 
					  request-pending: "Pending follow request"
 | 
				
			||||||
  follow-processing: "Processing follow"
 | 
					  follow-processing: "Processing follow"
 | 
				
			||||||
  follow-request: "Follow request"
 | 
					  follow-request: "Follow request"
 | 
				
			||||||
 | 
					common/views/pages/follow-requests.vue:
 | 
				
			||||||
 | 
					  received-follow-requests: "Follow requests"
 | 
				
			||||||
desktop:
 | 
					desktop:
 | 
				
			||||||
  banner-crop-title: "Crop the part that appears as a banner"
 | 
					  banner-crop-title: "Crop the part that appears as a banner"
 | 
				
			||||||
  banner: "Banner"
 | 
					  banner: "Banner"
 | 
				
			||||||
| 
						 | 
					@ -1036,12 +1046,6 @@ desktop/views/components/ui.header.post.vue:
 | 
				
			||||||
  post: "Compose new Post"
 | 
					  post: "Compose new Post"
 | 
				
			||||||
desktop/views/components/ui.header.search.vue:
 | 
					desktop/views/components/ui.header.search.vue:
 | 
				
			||||||
  placeholder: "Search"
 | 
					  placeholder: "Search"
 | 
				
			||||||
desktop/views/components/received-follow-requests-window.vue:
 | 
					 | 
				
			||||||
  title: "Follow requests"
 | 
					 | 
				
			||||||
  accept: "Accept"
 | 
					 | 
				
			||||||
  reject: "Reject"
 | 
					 | 
				
			||||||
desktop/views/components/user-lists-window.vue:
 | 
					 | 
				
			||||||
  title: "User lists"
 | 
					 | 
				
			||||||
desktop/views/components/user-preview.vue:
 | 
					desktop/views/components/user-preview.vue:
 | 
				
			||||||
  notes: "Posts"
 | 
					  notes: "Posts"
 | 
				
			||||||
  following: "Following"
 | 
					  following: "Following"
 | 
				
			||||||
| 
						 | 
					@ -1089,14 +1093,21 @@ admin/views/instance.vue:
 | 
				
			||||||
  instance-name: "Instance name"
 | 
					  instance-name: "Instance name"
 | 
				
			||||||
  instance-description: "Instance description"
 | 
					  instance-description: "Instance description"
 | 
				
			||||||
  host: "Host"
 | 
					  host: "Host"
 | 
				
			||||||
 | 
					  icon-url: "URL of the icon"
 | 
				
			||||||
 | 
					  logo-url: "URL of the logo"
 | 
				
			||||||
  banner-url: "Banner image URL"
 | 
					  banner-url: "Banner image URL"
 | 
				
			||||||
  error-image-url: "Error image URL"
 | 
					  error-image-url: "Error image URL"
 | 
				
			||||||
  languages: "Language of this instance"
 | 
					  languages: "Language of this instance"
 | 
				
			||||||
  languages-desc: "You can add more than one, separated by spaces."
 | 
					  languages-desc: "You can add more than one, separated by spaces."
 | 
				
			||||||
 | 
					  tos-url: "Terms of Service URL"
 | 
				
			||||||
 | 
					  repository-url: "Repository URL"
 | 
				
			||||||
 | 
					  feedback-url: "URL for feedback"
 | 
				
			||||||
  maintainer-config: "Administrator information"
 | 
					  maintainer-config: "Administrator information"
 | 
				
			||||||
  maintainer-name: "Administrator name"
 | 
					  maintainer-name: "Administrator name"
 | 
				
			||||||
  maintainer-email: "Contact Administrator"
 | 
					  maintainer-email: "Contact Administrator"
 | 
				
			||||||
 | 
					  advanced-config: "Other settings"
 | 
				
			||||||
  drive-config: "Drive settings"
 | 
					  drive-config: "Drive settings"
 | 
				
			||||||
 | 
					  object-storage-base-url: "URL"
 | 
				
			||||||
  object-storage-endpoint: "Endpoint"
 | 
					  object-storage-endpoint: "Endpoint"
 | 
				
			||||||
  cache-remote-files: "Cache remote files"
 | 
					  cache-remote-files: "Cache remote files"
 | 
				
			||||||
  cache-remote-files-desc: "Without this parameter, all remote files are linked to their host server directly. This will be an effective solution to save your server storage, however make remote files invisible to users who set direct-link disabled, since no thumbnail will be generated, increase traffic. It is recommended that this parameter set enabled."
 | 
					  cache-remote-files-desc: "Without this parameter, all remote files are linked to their host server directly. This will be an effective solution to save your server storage, however make remote files invisible to users who set direct-link disabled, since no thumbnail will be generated, increase traffic. It is recommended that this parameter set enabled."
 | 
				
			||||||
| 
						 | 
					@ -1106,8 +1117,7 @@ admin/views/instance.vue:
 | 
				
			||||||
  recaptcha-config: "the reCAPTCHA settings"
 | 
					  recaptcha-config: "the reCAPTCHA settings"
 | 
				
			||||||
  recaptcha-info: "reCAPTCHA token is required. Please get it on https://www.google.com/recaptcha/intro/"
 | 
					  recaptcha-info: "reCAPTCHA token is required. Please get it on https://www.google.com/recaptcha/intro/"
 | 
				
			||||||
  enable-recaptcha: "enable reCAPTCHA"
 | 
					  enable-recaptcha: "enable reCAPTCHA"
 | 
				
			||||||
  recaptcha-site-key: "reCAPTCHA site key"
 | 
					  recaptcha-preview: "Preview"
 | 
				
			||||||
  recaptcha-secret-key: "reCAPTCHA secret key"
 | 
					 | 
				
			||||||
  twitter-integration-config: "Settings of connecting to Twitter"
 | 
					  twitter-integration-config: "Settings of connecting to Twitter"
 | 
				
			||||||
  twitter-integration-info: "The callback URL is set on {url}."
 | 
					  twitter-integration-info: "The callback URL is set on {url}."
 | 
				
			||||||
  enable-twitter-integration: "Enable connection to Twitter"
 | 
					  enable-twitter-integration: "Enable connection to Twitter"
 | 
				
			||||||
| 
						 | 
					@ -1514,8 +1524,6 @@ mobile/views/pages/drive.vue:
 | 
				
			||||||
    rename-folder: "Rename folder"
 | 
					    rename-folder: "Rename folder"
 | 
				
			||||||
    move-folder: "Move this folder"
 | 
					    move-folder: "Move this folder"
 | 
				
			||||||
    delete-folder: "Delete this folder"
 | 
					    delete-folder: "Delete this folder"
 | 
				
			||||||
mobile/views/pages/user-lists.vue:
 | 
					 | 
				
			||||||
  title: "Lists"
 | 
					 | 
				
			||||||
mobile/views/pages/signup.vue:
 | 
					mobile/views/pages/signup.vue:
 | 
				
			||||||
  lets-start: "Your account is now ready! 📦"
 | 
					  lets-start: "Your account is now ready! 📦"
 | 
				
			||||||
mobile/views/pages/followers.vue:
 | 
					mobile/views/pages/followers.vue:
 | 
				
			||||||
| 
						 | 
					@ -1540,10 +1548,6 @@ mobile/views/pages/widgets/activity.vue:
 | 
				
			||||||
  activity: "Activity"
 | 
					  activity: "Activity"
 | 
				
			||||||
mobile/views/pages/share.vue:
 | 
					mobile/views/pages/share.vue:
 | 
				
			||||||
  share-with: "Share on {name}"
 | 
					  share-with: "Share on {name}"
 | 
				
			||||||
mobile/views/pages/received-follow-requests.vue:
 | 
					 | 
				
			||||||
  title: "Follow requests"
 | 
					 | 
				
			||||||
  accept: "Accept"
 | 
					 | 
				
			||||||
  reject: "Reject"
 | 
					 | 
				
			||||||
mobile/views/pages/note.vue:
 | 
					mobile/views/pages/note.vue:
 | 
				
			||||||
  title: "Post"
 | 
					  title: "Post"
 | 
				
			||||||
  prev: "Previous note"
 | 
					  prev: "Previous note"
 | 
				
			||||||
| 
						 | 
					@ -1649,6 +1653,7 @@ pages:
 | 
				
			||||||
  edit-this-page: "Edit this page"
 | 
					  edit-this-page: "Edit this page"
 | 
				
			||||||
  view-source: "View Source"
 | 
					  view-source: "View Source"
 | 
				
			||||||
  view-page: "View page"
 | 
					  view-page: "View page"
 | 
				
			||||||
 | 
					  like: "Like"
 | 
				
			||||||
  inspector: "Inspector"
 | 
					  inspector: "Inspector"
 | 
				
			||||||
  content: "Page block"
 | 
					  content: "Page block"
 | 
				
			||||||
  variables: "Variables"
 | 
					  variables: "Variables"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -395,17 +395,13 @@ common/views/components/messaging.vue:
 | 
				
			||||||
  you: "Tu"
 | 
					  you: "Tu"
 | 
				
			||||||
  no-history: "Sin historial"
 | 
					  no-history: "Sin historial"
 | 
				
			||||||
common/views/components/messaging-room.vue:
 | 
					common/views/components/messaging-room.vue:
 | 
				
			||||||
  empty: "Sin conversaciones"
 | 
					 | 
				
			||||||
  no-history: "El historial se ha acabado"
 | 
					  no-history: "El historial se ha acabado"
 | 
				
			||||||
  resize-form: "Arrastra para redimensionar"
 | 
					 | 
				
			||||||
  new-message: "Nuevo mensaje"
 | 
					  new-message: "Nuevo mensaje"
 | 
				
			||||||
  only-one-file-attached: "Un único archivo se puede conectar al mensaje"
 | 
					 | 
				
			||||||
common/views/components/messaging-room.form.vue:
 | 
					common/views/components/messaging-room.form.vue:
 | 
				
			||||||
  input-message-here: "Escribe el mensaje aquí"
 | 
					  input-message-here: "Escribe el mensaje aquí"
 | 
				
			||||||
  send: "Enviar"
 | 
					  send: "Enviar"
 | 
				
			||||||
  attach-from-local: "Adjunta ficheros desde tu PC"
 | 
					  attach-from-local: "Adjunta ficheros desde tu PC"
 | 
				
			||||||
  attach-from-drive: "Adjunta ficheros desde tu disco"
 | 
					  attach-from-drive: "Adjunta ficheros desde tu disco"
 | 
				
			||||||
  only-one-file-attached: "Un único archivo se puede conectar al mensaje"
 | 
					 | 
				
			||||||
common/views/components/messaging-room.message.vue:
 | 
					common/views/components/messaging-room.message.vue:
 | 
				
			||||||
  is-read: "Leer"
 | 
					  is-read: "Leer"
 | 
				
			||||||
  deleted: "El mensaje se ha borrado"
 | 
					  deleted: "El mensaje se ha borrado"
 | 
				
			||||||
| 
						 | 
					@ -587,8 +583,13 @@ common/views/components/user-list-editor.vue:
 | 
				
			||||||
  rename: "Cambiar el nombre de la lista"
 | 
					  rename: "Cambiar el nombre de la lista"
 | 
				
			||||||
  delete: "Eliminar lista"
 | 
					  delete: "Eliminar lista"
 | 
				
			||||||
  remove-user: "Eliminar de la lista"
 | 
					  remove-user: "Eliminar de la lista"
 | 
				
			||||||
 | 
					common/views/components/user-group-editor.vue:
 | 
				
			||||||
 | 
					  invite: "Invitar"
 | 
				
			||||||
common/views/components/user-lists.vue:
 | 
					common/views/components/user-lists.vue:
 | 
				
			||||||
 | 
					  user-lists: "Listas"
 | 
				
			||||||
  list-name: "Nombre de lista"
 | 
					  list-name: "Nombre de lista"
 | 
				
			||||||
 | 
					common/views/components/user-groups.vue:
 | 
				
			||||||
 | 
					  invites: "Invitar"
 | 
				
			||||||
common/views/widgets/broadcast.vue:
 | 
					common/views/widgets/broadcast.vue:
 | 
				
			||||||
  fetching: "Recuperando"
 | 
					  fetching: "Recuperando"
 | 
				
			||||||
  no-broadcasts: "Sin emisión"
 | 
					  no-broadcasts: "Sin emisión"
 | 
				
			||||||
| 
						 | 
					@ -647,6 +648,8 @@ common/views/pages/follow.vue:
 | 
				
			||||||
  request-pending: "Solicitud pendiente"
 | 
					  request-pending: "Solicitud pendiente"
 | 
				
			||||||
  follow-processing: "Solicitud en proceso"
 | 
					  follow-processing: "Solicitud en proceso"
 | 
				
			||||||
  follow-request: "Solicitar suscripción"
 | 
					  follow-request: "Solicitar suscripción"
 | 
				
			||||||
 | 
					common/views/pages/follow-requests.vue:
 | 
				
			||||||
 | 
					  received-follow-requests: "Solicitudes de seguimiento"
 | 
				
			||||||
desktop:
 | 
					desktop:
 | 
				
			||||||
  banner-crop-title: "Corta la parte que aparece como un banner"
 | 
					  banner-crop-title: "Corta la parte que aparece como un banner"
 | 
				
			||||||
  banner: "Banner"
 | 
					  banner: "Banner"
 | 
				
			||||||
| 
						 | 
					@ -890,12 +893,6 @@ desktop/views/components/ui.header.post.vue:
 | 
				
			||||||
  post: "Crear una publicación"
 | 
					  post: "Crear una publicación"
 | 
				
			||||||
desktop/views/components/ui.header.search.vue:
 | 
					desktop/views/components/ui.header.search.vue:
 | 
				
			||||||
  placeholder: "Buscar"
 | 
					  placeholder: "Buscar"
 | 
				
			||||||
desktop/views/components/received-follow-requests-window.vue:
 | 
					 | 
				
			||||||
  title: "Solicitudes de seguidores"
 | 
					 | 
				
			||||||
  accept: "Aceptar"
 | 
					 | 
				
			||||||
  reject: "Rechazar"
 | 
					 | 
				
			||||||
desktop/views/components/user-lists-window.vue:
 | 
					 | 
				
			||||||
  title: "Listas de usuario"
 | 
					 | 
				
			||||||
desktop/views/components/user-preview.vue:
 | 
					desktop/views/components/user-preview.vue:
 | 
				
			||||||
  notes: "Publicaciones"
 | 
					  notes: "Publicaciones"
 | 
				
			||||||
  following: "Sigue"
 | 
					  following: "Sigue"
 | 
				
			||||||
| 
						 | 
					@ -944,7 +941,7 @@ admin/views/instance.vue:
 | 
				
			||||||
  maintainer-email: "Contactar con el administrador"
 | 
					  maintainer-email: "Contactar con el administrador"
 | 
				
			||||||
  drive-config: "Ajustes del Drive"
 | 
					  drive-config: "Ajustes del Drive"
 | 
				
			||||||
  cache-remote-files: "Mantener en cache los archivos remotos"
 | 
					  cache-remote-files: "Mantener en cache los archivos remotos"
 | 
				
			||||||
  recaptcha-secret-key: "clave secreta reCAPTCHA"
 | 
					  recaptcha-preview: "Vista previa"
 | 
				
			||||||
  invite: "Invitar"
 | 
					  invite: "Invitar"
 | 
				
			||||||
  save: "Guardar"
 | 
					  save: "Guardar"
 | 
				
			||||||
  saved: "Guardado"
 | 
					  saved: "Guardado"
 | 
				
			||||||
| 
						 | 
					@ -1079,8 +1076,6 @@ mobile/views/pages/drive.vue:
 | 
				
			||||||
  contextmenu:
 | 
					  contextmenu:
 | 
				
			||||||
    upload: "Subir fichero"
 | 
					    upload: "Subir fichero"
 | 
				
			||||||
    create-folder: "Crear una carpeta"
 | 
					    create-folder: "Crear una carpeta"
 | 
				
			||||||
mobile/views/pages/user-lists.vue:
 | 
					 | 
				
			||||||
  title: "Listas"
 | 
					 | 
				
			||||||
mobile/views/pages/home.vue:
 | 
					mobile/views/pages/home.vue:
 | 
				
			||||||
  home: "Inicio"
 | 
					  home: "Inicio"
 | 
				
			||||||
  local: "Local"
 | 
					  local: "Local"
 | 
				
			||||||
| 
						 | 
					@ -1091,10 +1086,6 @@ mobile/views/pages/widgets.vue:
 | 
				
			||||||
  customization-tips: "Consejos de personalización"
 | 
					  customization-tips: "Consejos de personalización"
 | 
				
			||||||
mobile/views/pages/widgets/activity.vue:
 | 
					mobile/views/pages/widgets/activity.vue:
 | 
				
			||||||
  activity: "Actividad"
 | 
					  activity: "Actividad"
 | 
				
			||||||
mobile/views/pages/received-follow-requests.vue:
 | 
					 | 
				
			||||||
  title: "Solicitudes de seguimiento"
 | 
					 | 
				
			||||||
  accept: "Aceptar"
 | 
					 | 
				
			||||||
  reject: "Rechazar"
 | 
					 | 
				
			||||||
mobile/views/pages/games/reversi.vue:
 | 
					mobile/views/pages/games/reversi.vue:
 | 
				
			||||||
  reversi: "Reversi"
 | 
					  reversi: "Reversi"
 | 
				
			||||||
mobile/views/pages/search.vue:
 | 
					mobile/views/pages/search.vue:
 | 
				
			||||||
| 
						 | 
					@ -1114,6 +1105,7 @@ deck:
 | 
				
			||||||
deck/deck.user-column.vue:
 | 
					deck/deck.user-column.vue:
 | 
				
			||||||
  activity: "Actividad"
 | 
					  activity: "Actividad"
 | 
				
			||||||
pages:
 | 
					pages:
 | 
				
			||||||
 | 
					  like: "Me gusta"
 | 
				
			||||||
  blocks:
 | 
					  blocks:
 | 
				
			||||||
    post: "Formulario"
 | 
					    post: "Formulario"
 | 
				
			||||||
  script:
 | 
					  script:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -243,6 +243,7 @@ common:
 | 
				
			||||||
  my-token-regenerated: "Votre jeton vient d’être généré, vous allez maintenant être déconnecté."
 | 
					  my-token-regenerated: "Votre jeton vient d’être généré, vous allez maintenant être déconnecté."
 | 
				
			||||||
  hide-password: "Masquer le mot de passe"
 | 
					  hide-password: "Masquer le mot de passe"
 | 
				
			||||||
  show-password: "Afficher le mot de passe"
 | 
					  show-password: "Afficher le mot de passe"
 | 
				
			||||||
 | 
					  enter-username: "Saisir un nom d'utilisateur"
 | 
				
			||||||
  do-not-use-in-production: "Il s’agit d’une version de développement. Ne pas utiliser dans un environnement de production."
 | 
					  do-not-use-in-production: "Il s’agit d’une version de développement. Ne pas utiliser dans un environnement de production."
 | 
				
			||||||
  user-suspended: "Cet·te utilisateur·trice a été suspendu·e"
 | 
					  user-suspended: "Cet·te utilisateur·trice a été suspendu·e"
 | 
				
			||||||
  is-remote-user: "Les informations à propos de ce compte peuvent être incomplètes."
 | 
					  is-remote-user: "Les informations à propos de ce compte peuvent être incomplètes."
 | 
				
			||||||
| 
						 | 
					@ -436,18 +437,15 @@ common/views/components/messaging.vue:
 | 
				
			||||||
  search-user: "Trouver un utilisateur"
 | 
					  search-user: "Trouver un utilisateur"
 | 
				
			||||||
  you: "Vous"
 | 
					  you: "Vous"
 | 
				
			||||||
  no-history: "Pas d'historique"
 | 
					  no-history: "Pas d'historique"
 | 
				
			||||||
 | 
					  user: "Utilisateur·rice·s"
 | 
				
			||||||
common/views/components/messaging-room.vue:
 | 
					common/views/components/messaging-room.vue:
 | 
				
			||||||
  empty: "Pas de conversations"
 | 
					 | 
				
			||||||
  no-history: "Aucun historique"
 | 
					  no-history: "Aucun historique"
 | 
				
			||||||
  resize-form: "Faites glisser pour redimensionner"
 | 
					 | 
				
			||||||
  new-message: "Nouveau message"
 | 
					  new-message: "Nouveau message"
 | 
				
			||||||
  only-one-file-attached: "Un seul fichier peut être joint au message"
 | 
					 | 
				
			||||||
common/views/components/messaging-room.form.vue:
 | 
					common/views/components/messaging-room.form.vue:
 | 
				
			||||||
  input-message-here: "Tapez ici votre message"
 | 
					  input-message-here: "Tapez ici votre message"
 | 
				
			||||||
  send: "Envoyer"
 | 
					  send: "Envoyer"
 | 
				
			||||||
  attach-from-local: "Joindre un fichier depuis votre ordinateur"
 | 
					  attach-from-local: "Joindre un fichier depuis votre ordinateur"
 | 
				
			||||||
  attach-from-drive: "Joindre un fichier depuis votre Drive"
 | 
					  attach-from-drive: "Joindre un fichier depuis votre Drive"
 | 
				
			||||||
  only-one-file-attached: "Un seul fichier uniquement peut être joint au message"
 | 
					 | 
				
			||||||
common/views/components/messaging-room.message.vue:
 | 
					common/views/components/messaging-room.message.vue:
 | 
				
			||||||
  is-read: "Lu"
 | 
					  is-read: "Lu"
 | 
				
			||||||
  deleted: "Ce message a été supprimé"
 | 
					  deleted: "Ce message a été supprimé"
 | 
				
			||||||
| 
						 | 
					@ -675,9 +673,20 @@ common/views/components/user-list-editor.vue:
 | 
				
			||||||
  remove-user: "Retirer de cette liste"
 | 
					  remove-user: "Retirer de cette liste"
 | 
				
			||||||
  delete-are-you-sure: "Voulez-vous vraiment supprimer la liste « $1 » ?"
 | 
					  delete-are-you-sure: "Voulez-vous vraiment supprimer la liste « $1 » ?"
 | 
				
			||||||
  deleted: "Supprimé"
 | 
					  deleted: "Supprimé"
 | 
				
			||||||
 | 
					  add-user: "Ajouter un utilisateur"
 | 
				
			||||||
 | 
					common/views/components/user-group-editor.vue:
 | 
				
			||||||
 | 
					  rename: "Renommer le groupe"
 | 
				
			||||||
 | 
					  delete: "Supprimer le groupe"
 | 
				
			||||||
 | 
					  deleted: "Supprimé"
 | 
				
			||||||
 | 
					  invite: "Inviter"
 | 
				
			||||||
common/views/components/user-lists.vue:
 | 
					common/views/components/user-lists.vue:
 | 
				
			||||||
 | 
					  user-lists: "Listes"
 | 
				
			||||||
  create-list: "Créer une liste"
 | 
					  create-list: "Créer une liste"
 | 
				
			||||||
  list-name: "Nom de la liste"
 | 
					  list-name: "Nom de la liste"
 | 
				
			||||||
 | 
					common/views/components/user-groups.vue:
 | 
				
			||||||
 | 
					  create-group: "Créer un groupe"
 | 
				
			||||||
 | 
					  invites: "Inviter"
 | 
				
			||||||
 | 
					  reject-invite: "Refuser"
 | 
				
			||||||
common/views/widgets/broadcast.vue:
 | 
					common/views/widgets/broadcast.vue:
 | 
				
			||||||
  fetching: "Récupération"
 | 
					  fetching: "Récupération"
 | 
				
			||||||
  no-broadcasts: "Aucune annonce"
 | 
					  no-broadcasts: "Aucune annonce"
 | 
				
			||||||
| 
						 | 
					@ -738,6 +747,10 @@ common/views/pages/follow.vue:
 | 
				
			||||||
  request-pending: "Demande d’abonnement en attente"
 | 
					  request-pending: "Demande d’abonnement en attente"
 | 
				
			||||||
  follow-processing: "Demande en attente"
 | 
					  follow-processing: "Demande en attente"
 | 
				
			||||||
  follow-request: "Demande d’abonnement"
 | 
					  follow-request: "Demande d’abonnement"
 | 
				
			||||||
 | 
					common/views/pages/follow-requests.vue:
 | 
				
			||||||
 | 
					  received-follow-requests: "Demandes d’abonnement"
 | 
				
			||||||
 | 
					  accept: "Accepter"
 | 
				
			||||||
 | 
					  reject: "Refuser"
 | 
				
			||||||
desktop:
 | 
					desktop:
 | 
				
			||||||
  banner-crop-title: "Découpez la partie qui apparaîtra comme bannière"
 | 
					  banner-crop-title: "Découpez la partie qui apparaîtra comme bannière"
 | 
				
			||||||
  banner: "Bannière"
 | 
					  banner: "Bannière"
 | 
				
			||||||
| 
						 | 
					@ -1009,6 +1022,7 @@ desktop/views/components/ui.header.vue:
 | 
				
			||||||
desktop/views/components/ui.header.account.vue:
 | 
					desktop/views/components/ui.header.account.vue:
 | 
				
			||||||
  profile: "Votre profil"
 | 
					  profile: "Votre profil"
 | 
				
			||||||
  lists: "Listes"
 | 
					  lists: "Listes"
 | 
				
			||||||
 | 
					  groups: "Groupes"
 | 
				
			||||||
  follow-requests: "Demandes d’abonnement"
 | 
					  follow-requests: "Demandes d’abonnement"
 | 
				
			||||||
  admin: "Admin"
 | 
					  admin: "Admin"
 | 
				
			||||||
desktop/views/components/ui.header.nav.vue:
 | 
					desktop/views/components/ui.header.nav.vue:
 | 
				
			||||||
| 
						 | 
					@ -1019,12 +1033,6 @@ desktop/views/components/ui.header.post.vue:
 | 
				
			||||||
  post: "Rédiger une nouvelle publication"
 | 
					  post: "Rédiger une nouvelle publication"
 | 
				
			||||||
desktop/views/components/ui.header.search.vue:
 | 
					desktop/views/components/ui.header.search.vue:
 | 
				
			||||||
  placeholder: "Chercher"
 | 
					  placeholder: "Chercher"
 | 
				
			||||||
desktop/views/components/received-follow-requests-window.vue:
 | 
					 | 
				
			||||||
  title: "Demandes d’abonnement"
 | 
					 | 
				
			||||||
  accept: "Accepter"
 | 
					 | 
				
			||||||
  reject: "Refuser"
 | 
					 | 
				
			||||||
desktop/views/components/user-lists-window.vue:
 | 
					 | 
				
			||||||
  title: "Listes de l'utilisateur"
 | 
					 | 
				
			||||||
desktop/views/components/user-preview.vue:
 | 
					desktop/views/components/user-preview.vue:
 | 
				
			||||||
  notes: "Publications"
 | 
					  notes: "Publications"
 | 
				
			||||||
  following: "Abonné à"
 | 
					  following: "Abonné à"
 | 
				
			||||||
| 
						 | 
					@ -1083,8 +1091,12 @@ admin/views/instance.vue:
 | 
				
			||||||
  maintainer-config: "Informations de l’administrateur"
 | 
					  maintainer-config: "Informations de l’administrateur"
 | 
				
			||||||
  maintainer-name: "Nom de l’administrateur"
 | 
					  maintainer-name: "Nom de l’administrateur"
 | 
				
			||||||
  maintainer-email: "Contact administratif"
 | 
					  maintainer-email: "Contact administratif"
 | 
				
			||||||
 | 
					  advanced-config: "Autres réglages"
 | 
				
			||||||
  drive-config: "Paramètres du lecteur"
 | 
					  drive-config: "Paramètres du lecteur"
 | 
				
			||||||
 | 
					  object-storage-base-url: "URL"
 | 
				
			||||||
  object-storage-endpoint: "Point de terminaison"
 | 
					  object-storage-endpoint: "Point de terminaison"
 | 
				
			||||||
 | 
					  object-storage-access-key: "Clé d'accès"
 | 
				
			||||||
 | 
					  object-storage-secret-key: "Clé secrète"
 | 
				
			||||||
  cache-remote-files: "Mettre en cache des fichiers distants"
 | 
					  cache-remote-files: "Mettre en cache des fichiers distants"
 | 
				
			||||||
  local-drive-capacity-mb: "Volume du lecteur par utilisateur"
 | 
					  local-drive-capacity-mb: "Volume du lecteur par utilisateur"
 | 
				
			||||||
  remote-drive-capacity-mb: "Volume du lecteur par utilisateur distant"
 | 
					  remote-drive-capacity-mb: "Volume du lecteur par utilisateur distant"
 | 
				
			||||||
| 
						 | 
					@ -1092,8 +1104,8 @@ admin/views/instance.vue:
 | 
				
			||||||
  recaptcha-config: "Paramètres de reCAPTCHA"
 | 
					  recaptcha-config: "Paramètres de reCAPTCHA"
 | 
				
			||||||
  recaptcha-info: "Si activé, un jeton reCAPTCHA est requis. Vous pouvez en obtenir un sur https://www.google.com/recaptcha/intro/"
 | 
					  recaptcha-info: "Si activé, un jeton reCAPTCHA est requis. Vous pouvez en obtenir un sur https://www.google.com/recaptcha/intro/"
 | 
				
			||||||
  enable-recaptcha: "Activation de reCAPTCHA"
 | 
					  enable-recaptcha: "Activation de reCAPTCHA"
 | 
				
			||||||
  recaptcha-site-key: "Clé reCAPTCHA du site"
 | 
					  recaptcha-secret-key: "Clé secrète"
 | 
				
			||||||
  recaptcha-secret-key: "Clé secrète reCAPTCHA"
 | 
					  recaptcha-preview: "Prévisualisation"
 | 
				
			||||||
  twitter-integration-config: "Paramètres de connexion à Twitter"
 | 
					  twitter-integration-config: "Paramètres de connexion à Twitter"
 | 
				
			||||||
  twitter-integration-info: "L'URL de callback est {url}."
 | 
					  twitter-integration-info: "L'URL de callback est {url}."
 | 
				
			||||||
  enable-twitter-integration: "Activer la connexion à Twitter"
 | 
					  enable-twitter-integration: "Activer la connexion à Twitter"
 | 
				
			||||||
| 
						 | 
					@ -1483,8 +1495,6 @@ mobile/views/pages/drive.vue:
 | 
				
			||||||
    rename-folder: "Renommer le dossier"
 | 
					    rename-folder: "Renommer le dossier"
 | 
				
			||||||
    move-folder: "Déplacer ce dossier"
 | 
					    move-folder: "Déplacer ce dossier"
 | 
				
			||||||
    delete-folder: "Supprimer ce dossier"
 | 
					    delete-folder: "Supprimer ce dossier"
 | 
				
			||||||
mobile/views/pages/user-lists.vue:
 | 
					 | 
				
			||||||
  title: "Listes"
 | 
					 | 
				
			||||||
mobile/views/pages/signup.vue:
 | 
					mobile/views/pages/signup.vue:
 | 
				
			||||||
  lets-start: "Votre compte est prêt ! 📦"
 | 
					  lets-start: "Votre compte est prêt ! 📦"
 | 
				
			||||||
mobile/views/pages/followers.vue:
 | 
					mobile/views/pages/followers.vue:
 | 
				
			||||||
| 
						 | 
					@ -1508,10 +1518,6 @@ mobile/views/pages/widgets/activity.vue:
 | 
				
			||||||
  activity: "Activité"
 | 
					  activity: "Activité"
 | 
				
			||||||
mobile/views/pages/share.vue:
 | 
					mobile/views/pages/share.vue:
 | 
				
			||||||
  share-with: "Partager avec {name}"
 | 
					  share-with: "Partager avec {name}"
 | 
				
			||||||
mobile/views/pages/received-follow-requests.vue:
 | 
					 | 
				
			||||||
  title: "Demandes d’abonnement"
 | 
					 | 
				
			||||||
  accept: "Accepter"
 | 
					 | 
				
			||||||
  reject: "Refuser"
 | 
					 | 
				
			||||||
mobile/views/pages/note.vue:
 | 
					mobile/views/pages/note.vue:
 | 
				
			||||||
  title: "Publication"
 | 
					  title: "Publication"
 | 
				
			||||||
  prev: "Note précédente"
 | 
					  prev: "Note précédente"
 | 
				
			||||||
| 
						 | 
					@ -1608,6 +1614,7 @@ pages:
 | 
				
			||||||
  edit-this-page: "Éditer cette page"
 | 
					  edit-this-page: "Éditer cette page"
 | 
				
			||||||
  view-source: "Afficher la source"
 | 
					  view-source: "Afficher la source"
 | 
				
			||||||
  view-page: "Afficher la page"
 | 
					  view-page: "Afficher la page"
 | 
				
			||||||
 | 
					  like: "Bien"
 | 
				
			||||||
  inspector: "Inspecteur"
 | 
					  inspector: "Inspecteur"
 | 
				
			||||||
  content: "Bloc de page"
 | 
					  content: "Bloc de page"
 | 
				
			||||||
  variables: "Variables"
 | 
					  variables: "Variables"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@ const merge = (...args) => args.reduce((a, c) => ({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const languages = [
 | 
					const languages = [
 | 
				
			||||||
	'cs-CZ',
 | 
						'cs-CZ',
 | 
				
			||||||
 | 
						'da-DK',
 | 
				
			||||||
	'de-DE',
 | 
						'de-DE',
 | 
				
			||||||
	'en-US',
 | 
						'en-US',
 | 
				
			||||||
	'es-ES',
 | 
						'es-ES',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -121,6 +121,7 @@ common:
 | 
				
			||||||
  update-available-title: "更新があんで"
 | 
					  update-available-title: "更新があんで"
 | 
				
			||||||
  update-available: "Misskeyの新しいバージョンがあんで({newer}。現在{current}をつこてるわ)。ページを再度読み込みしたると更新が適用されるわ。"
 | 
					  update-available: "Misskeyの新しいバージョンがあんで({newer}。現在{current}をつこてるわ)。ページを再度読み込みしたると更新が適用されるわ。"
 | 
				
			||||||
  my-token-regenerated: "あんさんのトークンが更新されたらしいわ。すまんがとりあえずサインアウトすんで。"
 | 
					  my-token-regenerated: "あんさんのトークンが更新されたらしいわ。すまんがとりあえずサインアウトすんで。"
 | 
				
			||||||
 | 
					  enter-username: "ユーザー名を入力してや"
 | 
				
			||||||
  do-not-use-in-production: "開発ビルドや。本番環境で使わんといて!知らんで!"
 | 
					  do-not-use-in-production: "開発ビルドや。本番環境で使わんといて!知らんで!"
 | 
				
			||||||
  is-remote-post: "この投稿情報はコピーです。"
 | 
					  is-remote-post: "この投稿情報はコピーです。"
 | 
				
			||||||
  view-on-remote: "ちゃんとした情報見せてや!"
 | 
					  view-on-remote: "ちゃんとした情報見せてや!"
 | 
				
			||||||
| 
						 | 
					@ -294,18 +295,15 @@ common/views/components/messaging.vue:
 | 
				
			||||||
  search-user: "ユーザーを探す"
 | 
					  search-user: "ユーザーを探す"
 | 
				
			||||||
  you: "あんさん"
 | 
					  you: "あんさん"
 | 
				
			||||||
  no-history: "履歴はあらへんで"
 | 
					  no-history: "履歴はあらへんで"
 | 
				
			||||||
 | 
					  user: "ユーザー"
 | 
				
			||||||
common/views/components/messaging-room.vue:
 | 
					common/views/components/messaging-room.vue:
 | 
				
			||||||
  empty: "このユーザーと話したことはあらへんで"
 | 
					 | 
				
			||||||
  no-history: "これより過去の履歴はあらへんで"
 | 
					  no-history: "これより過去の履歴はあらへんで"
 | 
				
			||||||
  resize-form: "ドラッグしてフォームの広さを調整"
 | 
					 | 
				
			||||||
  new-message: "新しいメッセージがあるで"
 | 
					  new-message: "新しいメッセージがあるで"
 | 
				
			||||||
  only-one-file-attached: "メッセージに添付できんのはひとつのファイルのみやで"
 | 
					 | 
				
			||||||
common/views/components/messaging-room.form.vue:
 | 
					common/views/components/messaging-room.form.vue:
 | 
				
			||||||
  input-message-here: "ここにメッセージ書いてや"
 | 
					  input-message-here: "ここにメッセージ書いてや"
 | 
				
			||||||
  send: "送信"
 | 
					  send: "送信"
 | 
				
			||||||
  attach-from-local: "PCからファイルを添付する"
 | 
					  attach-from-local: "PCからファイルを添付する"
 | 
				
			||||||
  attach-from-drive: "ドライブからファイルを添付する"
 | 
					  attach-from-drive: "ドライブからファイルを添付する"
 | 
				
			||||||
  only-one-file-attached: "メッセージに添付できんのはひとつのファイルのみやで"
 | 
					 | 
				
			||||||
common/views/components/messaging-room.message.vue:
 | 
					common/views/components/messaging-room.message.vue:
 | 
				
			||||||
  is-read: "既読"
 | 
					  is-read: "既読"
 | 
				
			||||||
  deleted: "このメッセージは削除されたわ"
 | 
					  deleted: "このメッセージは削除されたわ"
 | 
				
			||||||
| 
						 | 
					@ -476,8 +474,14 @@ common/views/components/profile-editor.vue:
 | 
				
			||||||
  enter-password: "パスワードを入れてや"
 | 
					  enter-password: "パスワードを入れてや"
 | 
				
			||||||
common/views/components/user-list-editor.vue:
 | 
					common/views/components/user-list-editor.vue:
 | 
				
			||||||
  users: "ユーザー"
 | 
					  users: "ユーザー"
 | 
				
			||||||
 | 
					  add-user: "ユーザー増やす"
 | 
				
			||||||
 | 
					common/views/components/user-group-editor.vue:
 | 
				
			||||||
 | 
					  invite: "招待"
 | 
				
			||||||
common/views/components/user-lists.vue:
 | 
					common/views/components/user-lists.vue:
 | 
				
			||||||
 | 
					  user-lists: "リスト"
 | 
				
			||||||
  list-name: "リスト名"
 | 
					  list-name: "リスト名"
 | 
				
			||||||
 | 
					common/views/components/user-groups.vue:
 | 
				
			||||||
 | 
					  invites: "招待"
 | 
				
			||||||
common/views/widgets/broadcast.vue:
 | 
					common/views/widgets/broadcast.vue:
 | 
				
			||||||
  fetching: "見てみるわ…"
 | 
					  fetching: "見てみるわ…"
 | 
				
			||||||
  no-broadcasts: "お知らせはあらへんで"
 | 
					  no-broadcasts: "お知らせはあらへんで"
 | 
				
			||||||
| 
						 | 
					@ -536,6 +540,8 @@ common/views/pages/follow.vue:
 | 
				
			||||||
  request-pending: "フォローの許し待っとる"
 | 
					  request-pending: "フォローの許し待っとる"
 | 
				
			||||||
  follow-processing: "今フォロー処理やっとる‥"
 | 
					  follow-processing: "今フォロー処理やっとる‥"
 | 
				
			||||||
  follow-request: "フォロー許してくれや!言うてみる"
 | 
					  follow-request: "フォロー許してくれや!言うてみる"
 | 
				
			||||||
 | 
					common/views/pages/follow-requests.vue:
 | 
				
			||||||
 | 
					  received-follow-requests: "フォロー許してくれや!言うてみる"
 | 
				
			||||||
desktop:
 | 
					desktop:
 | 
				
			||||||
  banner-crop-title: "どこバナーとして出す?"
 | 
					  banner-crop-title: "どこバナーとして出す?"
 | 
				
			||||||
  banner: "バナー"
 | 
					  banner: "バナー"
 | 
				
			||||||
| 
						 | 
					@ -809,12 +815,6 @@ desktop/views/components/ui.header.post.vue:
 | 
				
			||||||
  post: "新規投稿"
 | 
					  post: "新規投稿"
 | 
				
			||||||
desktop/views/components/ui.header.search.vue:
 | 
					desktop/views/components/ui.header.search.vue:
 | 
				
			||||||
  placeholder: "検索"
 | 
					  placeholder: "検索"
 | 
				
			||||||
desktop/views/components/received-follow-requests-window.vue:
 | 
					 | 
				
			||||||
  title: "フォロー許してくれや!言うてみる"
 | 
					 | 
				
			||||||
  accept: "許す"
 | 
					 | 
				
			||||||
  reject: "許さん"
 | 
					 | 
				
			||||||
desktop/views/components/user-lists-window.vue:
 | 
					 | 
				
			||||||
  title: "リスト"
 | 
					 | 
				
			||||||
desktop/views/components/user-preview.vue:
 | 
					desktop/views/components/user-preview.vue:
 | 
				
			||||||
  notes: "投稿"
 | 
					  notes: "投稿"
 | 
				
			||||||
  following: "フォロー"
 | 
					  following: "フォロー"
 | 
				
			||||||
| 
						 | 
					@ -869,8 +869,7 @@ admin/views/instance.vue:
 | 
				
			||||||
  recaptcha-config: "reCAPTCHAの設定"
 | 
					  recaptcha-config: "reCAPTCHAの設定"
 | 
				
			||||||
  recaptcha-info: "reCAPTCHAを有効にするにはreCAPTCHAトークンが要るで。https://www.google.com/recaptcha/intro/ にアクセスしてトークンを取得してな。"
 | 
					  recaptcha-info: "reCAPTCHAを有効にするにはreCAPTCHAトークンが要るで。https://www.google.com/recaptcha/intro/ にアクセスしてトークンを取得してな。"
 | 
				
			||||||
  enable-recaptcha: "reCAPTCHAを有効にする"
 | 
					  enable-recaptcha: "reCAPTCHAを有効にする"
 | 
				
			||||||
  recaptcha-site-key: "reCAPTCHA site key"
 | 
					  recaptcha-preview: "試してみる"
 | 
				
			||||||
  recaptcha-secret-key: "reCAPTCHA secret key"
 | 
					 | 
				
			||||||
  twitter-integration-config: "Twitter連携の設定"
 | 
					  twitter-integration-config: "Twitter連携の設定"
 | 
				
			||||||
  twitter-integration-info: "コールバックURLは {url} に設定してや。"
 | 
					  twitter-integration-info: "コールバックURLは {url} に設定してや。"
 | 
				
			||||||
  enable-twitter-integration: "Twitter連携を有効にする"
 | 
					  enable-twitter-integration: "Twitter連携を有効にする"
 | 
				
			||||||
| 
						 | 
					@ -1148,8 +1147,6 @@ mobile/views/pages/drive.vue:
 | 
				
			||||||
  contextmenu:
 | 
					  contextmenu:
 | 
				
			||||||
    upload: "ファイル上げる"
 | 
					    upload: "ファイル上げる"
 | 
				
			||||||
    create-folder: "フォルダー作る"
 | 
					    create-folder: "フォルダー作る"
 | 
				
			||||||
mobile/views/pages/user-lists.vue:
 | 
					 | 
				
			||||||
  title: "リスト"
 | 
					 | 
				
			||||||
mobile/views/pages/signup.vue:
 | 
					mobile/views/pages/signup.vue:
 | 
				
			||||||
  lets-start: "📦 始めようや"
 | 
					  lets-start: "📦 始めようや"
 | 
				
			||||||
mobile/views/pages/followers.vue:
 | 
					mobile/views/pages/followers.vue:
 | 
				
			||||||
| 
						 | 
					@ -1172,10 +1169,6 @@ mobile/views/pages/widgets/activity.vue:
 | 
				
			||||||
  activity: "やっとること"
 | 
					  activity: "やっとること"
 | 
				
			||||||
mobile/views/pages/share.vue:
 | 
					mobile/views/pages/share.vue:
 | 
				
			||||||
  share-with: "{name}で共有"
 | 
					  share-with: "{name}で共有"
 | 
				
			||||||
mobile/views/pages/received-follow-requests.vue:
 | 
					 | 
				
			||||||
  title: "フォロー許してくれや!"
 | 
					 | 
				
			||||||
  accept: "許す"
 | 
					 | 
				
			||||||
  reject: "許さん"
 | 
					 | 
				
			||||||
mobile/views/pages/note.vue:
 | 
					mobile/views/pages/note.vue:
 | 
				
			||||||
  title: "投稿"
 | 
					  title: "投稿"
 | 
				
			||||||
  prev: "前のやつ"
 | 
					  prev: "前のやつ"
 | 
				
			||||||
| 
						 | 
					@ -1260,6 +1253,7 @@ dev/views/new-app.vue:
 | 
				
			||||||
  authority-desc: "ここにチェックした機能しかAPIからアクセスできひんから気ぃつけてな"
 | 
					  authority-desc: "ここにチェックした機能しかAPIからアクセスできひんから気ぃつけてな"
 | 
				
			||||||
  authority-warning: "アプリ作った後でも変えれるけど、新しいやつ追加したらそん時関連付いてるユーザーキーは全部ほかされるで。"
 | 
					  authority-warning: "アプリ作った後でも変えれるけど、新しいやつ追加したらそん時関連付いてるユーザーキーは全部ほかされるで。"
 | 
				
			||||||
pages:
 | 
					pages:
 | 
				
			||||||
 | 
					  like: "ええやん"
 | 
				
			||||||
  blocks:
 | 
					  blocks:
 | 
				
			||||||
    image: "画像"
 | 
					    image: "画像"
 | 
				
			||||||
    post: "投稿フォーム"
 | 
					    post: "投稿フォーム"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -253,6 +253,7 @@ common:
 | 
				
			||||||
  my-token-regenerated: "당신의 토큰이 업데이트되었으므로 로그아웃합니다."
 | 
					  my-token-regenerated: "당신의 토큰이 업데이트되었으므로 로그아웃합니다."
 | 
				
			||||||
  hide-password: "비밀번호 숨기기"
 | 
					  hide-password: "비밀번호 숨기기"
 | 
				
			||||||
  show-password: "비밀번호 표시"
 | 
					  show-password: "비밀번호 표시"
 | 
				
			||||||
 | 
					  enter-username: "사용자명을 입력하여 주십시오"
 | 
				
			||||||
  do-not-use-in-production: "이것은 개발 빌드입니다. 프로덕션 환경에서 사용하지 마십시오."
 | 
					  do-not-use-in-production: "이것은 개발 빌드입니다. 프로덕션 환경에서 사용하지 마십시오."
 | 
				
			||||||
  user-suspended: "이 사용자는 정지된 상태입니다."
 | 
					  user-suspended: "이 사용자는 정지된 상태입니다."
 | 
				
			||||||
  is-remote-user: "이 사용자 정보는 정확하지 않을 수 있습니다."
 | 
					  is-remote-user: "이 사용자 정보는 정확하지 않을 수 있습니다."
 | 
				
			||||||
| 
						 | 
					@ -448,18 +449,23 @@ common/views/components/messaging.vue:
 | 
				
			||||||
  search-user: "사용자 찾기"
 | 
					  search-user: "사용자 찾기"
 | 
				
			||||||
  you: "당신"
 | 
					  you: "당신"
 | 
				
			||||||
  no-history: "기록이 없습니다"
 | 
					  no-history: "기록이 없습니다"
 | 
				
			||||||
 | 
					  user: "사용자"
 | 
				
			||||||
 | 
					  group: "그룹"
 | 
				
			||||||
 | 
					  start-with-user: "사용자와 대화 시작"
 | 
				
			||||||
 | 
					  start-with-group: "그룹과 대화 시작"
 | 
				
			||||||
 | 
					  select-group: "그룹을 선택하여 주십시오"
 | 
				
			||||||
common/views/components/messaging-room.vue:
 | 
					common/views/components/messaging-room.vue:
 | 
				
			||||||
  empty: "이 사용자와 대화한 적이 없습니다"
 | 
					  not-talked-user: "이 사용자와의 대화가 없습니다"
 | 
				
			||||||
 | 
					  not-talked-group: "이 그룹과의 대화가 없습니다"
 | 
				
			||||||
  no-history: "이것보다 과거의 기록이 없습니다"
 | 
					  no-history: "이것보다 과거의 기록이 없습니다"
 | 
				
			||||||
  resize-form: "드래그하여 폼의 크기 조절"
 | 
					 | 
				
			||||||
  new-message: "새 메시지가 있습니다"
 | 
					  new-message: "새 메시지가 있습니다"
 | 
				
			||||||
  only-one-file-attached: "메시지에는 하나의 파일만 첨부할 수 있습니다"
 | 
					  only-one-file-attached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
 | 
				
			||||||
common/views/components/messaging-room.form.vue:
 | 
					common/views/components/messaging-room.form.vue:
 | 
				
			||||||
  input-message-here: "여기에 메시지를 입력하세요"
 | 
					  input-message-here: "여기에 메시지를 입력하세요"
 | 
				
			||||||
  send: "전송"
 | 
					  send: "전송"
 | 
				
			||||||
  attach-from-local: "PC에서 파일 첨부"
 | 
					  attach-from-local: "PC에서 파일 첨부"
 | 
				
			||||||
  attach-from-drive: "드라이브에서 파일 첨부"
 | 
					  attach-from-drive: "드라이브에서 파일 첨부"
 | 
				
			||||||
  only-one-file-attached: "메시지에는 하나의 파일만 첨부할 수 있습니다"
 | 
					  only-one-file-attached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
 | 
				
			||||||
common/views/components/messaging-room.message.vue:
 | 
					common/views/components/messaging-room.message.vue:
 | 
				
			||||||
  is-read: "읽음"
 | 
					  is-read: "읽음"
 | 
				
			||||||
  deleted: "이 메시지는 삭제되었습니다"
 | 
					  deleted: "이 메시지는 삭제되었습니다"
 | 
				
			||||||
| 
						 | 
					@ -585,6 +591,7 @@ common/views/components/signup.vue:
 | 
				
			||||||
  password-matched: "확인되었습니다"
 | 
					  password-matched: "확인되었습니다"
 | 
				
			||||||
  password-not-matched: "일치하지 않습니다"
 | 
					  password-not-matched: "일치하지 않습니다"
 | 
				
			||||||
  recaptcha: "자동 가입 방지"
 | 
					  recaptcha: "자동 가입 방지"
 | 
				
			||||||
 | 
					  agree-to: "{0}에 동의합니다."
 | 
				
			||||||
  tos: "이용 약관"
 | 
					  tos: "이용 약관"
 | 
				
			||||||
  create: "계정 만들기"
 | 
					  create: "계정 만들기"
 | 
				
			||||||
  some-error: "알 수 없는 이유로 계정 만들기에 실패했습니다. 다시 한번 시도해 주세요."
 | 
					  some-error: "알 수 없는 이유로 계정 만들기에 실패했습니다. 다시 한번 시도해 주세요."
 | 
				
			||||||
| 
						 | 
					@ -693,9 +700,29 @@ common/views/components/user-list-editor.vue:
 | 
				
			||||||
  remove-user: "이 리스트에서 제거"
 | 
					  remove-user: "이 리스트에서 제거"
 | 
				
			||||||
  delete-are-you-sure: "리스트 \"$1\"을 삭제하시겠습니까?"
 | 
					  delete-are-you-sure: "리스트 \"$1\"을 삭제하시겠습니까?"
 | 
				
			||||||
  deleted: "삭제하였습니다"
 | 
					  deleted: "삭제하였습니다"
 | 
				
			||||||
 | 
					  add-user: "사용자 추가"
 | 
				
			||||||
 | 
					common/views/components/user-group-editor.vue:
 | 
				
			||||||
 | 
					  users: "멤버"
 | 
				
			||||||
 | 
					  rename: "그룹명을 변경"
 | 
				
			||||||
 | 
					  delete: "그룹을 삭제"
 | 
				
			||||||
 | 
					  remove-user: "이 그룹에서 삭제"
 | 
				
			||||||
 | 
					  delete-are-you-sure: "그룹 「$1」을 삭제하시겠습니까?"
 | 
				
			||||||
 | 
					  deleted: "삭제하였습니다"
 | 
				
			||||||
 | 
					  invite: "초대"
 | 
				
			||||||
 | 
					  invited: "초대를 보냈습니다"
 | 
				
			||||||
common/views/components/user-lists.vue:
 | 
					common/views/components/user-lists.vue:
 | 
				
			||||||
 | 
					  user-lists: "리스트"
 | 
				
			||||||
  create-list: "리스트 만들기"
 | 
					  create-list: "리스트 만들기"
 | 
				
			||||||
  list-name: "리스트 이름"
 | 
					  list-name: "리스트 이름"
 | 
				
			||||||
 | 
					common/views/components/user-groups.vue:
 | 
				
			||||||
 | 
					  user-groups: "그룹"
 | 
				
			||||||
 | 
					  create-group: "그룹 만들기"
 | 
				
			||||||
 | 
					  group-name: "그룹명"
 | 
				
			||||||
 | 
					  owned-groups: "자신의 그룹"
 | 
				
			||||||
 | 
					  joined-groups: "참여중인 그룹"
 | 
				
			||||||
 | 
					  invites: "초대"
 | 
				
			||||||
 | 
					  accept-invite: "참여"
 | 
				
			||||||
 | 
					  reject-invite: "거부"
 | 
				
			||||||
common/views/widgets/broadcast.vue:
 | 
					common/views/widgets/broadcast.vue:
 | 
				
			||||||
  fetching: "확인중"
 | 
					  fetching: "확인중"
 | 
				
			||||||
  no-broadcasts: "공지사항이 없습니다"
 | 
					  no-broadcasts: "공지사항이 없습니다"
 | 
				
			||||||
| 
						 | 
					@ -757,6 +784,10 @@ common/views/pages/follow.vue:
 | 
				
			||||||
  request-pending: "팔로우 허가 대기중"
 | 
					  request-pending: "팔로우 허가 대기중"
 | 
				
			||||||
  follow-processing: "팔로우 처리중"
 | 
					  follow-processing: "팔로우 처리중"
 | 
				
			||||||
  follow-request: "팔로우 요청"
 | 
					  follow-request: "팔로우 요청"
 | 
				
			||||||
 | 
					common/views/pages/follow-requests.vue:
 | 
				
			||||||
 | 
					  received-follow-requests: "팔로우 요청"
 | 
				
			||||||
 | 
					  accept: "승인"
 | 
				
			||||||
 | 
					  reject: "거부"
 | 
				
			||||||
desktop:
 | 
					desktop:
 | 
				
			||||||
  banner-crop-title: "배너로 표시할 부분을 선택"
 | 
					  banner-crop-title: "배너로 표시할 부분을 선택"
 | 
				
			||||||
  banner: "배너"
 | 
					  banner: "배너"
 | 
				
			||||||
| 
						 | 
					@ -1028,6 +1059,7 @@ desktop/views/components/ui.header.vue:
 | 
				
			||||||
desktop/views/components/ui.header.account.vue:
 | 
					desktop/views/components/ui.header.account.vue:
 | 
				
			||||||
  profile: "프로필"
 | 
					  profile: "프로필"
 | 
				
			||||||
  lists: "리스트"
 | 
					  lists: "리스트"
 | 
				
			||||||
 | 
					  groups: "그룹"
 | 
				
			||||||
  follow-requests: "팔로우 요청"
 | 
					  follow-requests: "팔로우 요청"
 | 
				
			||||||
  admin: "관리"
 | 
					  admin: "관리"
 | 
				
			||||||
desktop/views/components/ui.header.nav.vue:
 | 
					desktop/views/components/ui.header.nav.vue:
 | 
				
			||||||
| 
						 | 
					@ -1038,12 +1070,6 @@ desktop/views/components/ui.header.post.vue:
 | 
				
			||||||
  post: "새 글"
 | 
					  post: "새 글"
 | 
				
			||||||
desktop/views/components/ui.header.search.vue:
 | 
					desktop/views/components/ui.header.search.vue:
 | 
				
			||||||
  placeholder: "검색"
 | 
					  placeholder: "검색"
 | 
				
			||||||
desktop/views/components/received-follow-requests-window.vue:
 | 
					 | 
				
			||||||
  title: "팔로우 요청"
 | 
					 | 
				
			||||||
  accept: "승인"
 | 
					 | 
				
			||||||
  reject: "거부"
 | 
					 | 
				
			||||||
desktop/views/components/user-lists-window.vue:
 | 
					 | 
				
			||||||
  title: "리스트"
 | 
					 | 
				
			||||||
desktop/views/components/user-preview.vue:
 | 
					desktop/views/components/user-preview.vue:
 | 
				
			||||||
  notes: "글"
 | 
					  notes: "글"
 | 
				
			||||||
  following: "팔로잉"
 | 
					  following: "팔로잉"
 | 
				
			||||||
| 
						 | 
					@ -1103,8 +1129,22 @@ admin/views/instance.vue:
 | 
				
			||||||
  maintainer-config: "관리자 정보"
 | 
					  maintainer-config: "관리자 정보"
 | 
				
			||||||
  maintainer-name: "관리자 이름"
 | 
					  maintainer-name: "관리자 이름"
 | 
				
			||||||
  maintainer-email: "관리자 연락처"
 | 
					  maintainer-email: "관리자 연락처"
 | 
				
			||||||
 | 
					  advanced-config: "그 외 설정"
 | 
				
			||||||
 | 
					  note-and-tl: "글과 타임라인"
 | 
				
			||||||
  drive-config: "드라이브 설정"
 | 
					  drive-config: "드라이브 설정"
 | 
				
			||||||
 | 
					  use-object-storage: "오브젝트 스토리지를 사용"
 | 
				
			||||||
 | 
					  object-storage-base-url: "URL"
 | 
				
			||||||
 | 
					  object-storage-bucket: "버킷 이름"
 | 
				
			||||||
 | 
					  object-storage-prefix: "프리픽스"
 | 
				
			||||||
  object-storage-endpoint: "엔드포인트"
 | 
					  object-storage-endpoint: "엔드포인트"
 | 
				
			||||||
 | 
					  object-storage-region: "리전"
 | 
				
			||||||
 | 
					  object-storage-port: "포트"
 | 
				
			||||||
 | 
					  object-storage-access-key: "액세스 키"
 | 
				
			||||||
 | 
					  object-storage-secret-key: "시크릿 키"
 | 
				
			||||||
 | 
					  object-storage-use-ssl: "SSL 사용"
 | 
				
			||||||
 | 
					  object-storage-s3-info: "Amazon S3를 오브젝트 스토리지로 사용하는 경우의 「엔드포인트」와 「리전」의 설정값에 대해서는 {0}을 확인하여 주십시오."
 | 
				
			||||||
 | 
					  object-storage-s3-info-here: "이곳"
 | 
				
			||||||
 | 
					  object-storage-gcs-info: "Google Cloud Storage를 오브젝트 스토리지로 사용하는 경우, 「엔드포인트」는 storage.googleapis.com 으로 설정하고, 「리전」 란은 비웁니다."
 | 
				
			||||||
  cache-remote-files: "원격 파일을 캐시"
 | 
					  cache-remote-files: "원격 파일을 캐시"
 | 
				
			||||||
  cache-remote-files-desc: "이 설정을 해지하면 원격 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라 서버의 저장 공간을 절약할 수 있지만, 프라이버시 설정에서 직접 링크를 무효로 설정한 사용자에게는 파일이 보이지 않거나, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다. 일반적으로 이 설정을 ON으로 두는 것을 추천합니다."
 | 
					  cache-remote-files-desc: "이 설정을 해지하면 원격 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라 서버의 저장 공간을 절약할 수 있지만, 프라이버시 설정에서 직접 링크를 무효로 설정한 사용자에게는 파일이 보이지 않거나, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다. 일반적으로 이 설정을 ON으로 두는 것을 추천합니다."
 | 
				
			||||||
  local-drive-capacity-mb: "로컬 사용자 한 명당 드라이브 용량"
 | 
					  local-drive-capacity-mb: "로컬 사용자 한 명당 드라이브 용량"
 | 
				
			||||||
| 
						 | 
					@ -1113,8 +1153,12 @@ admin/views/instance.vue:
 | 
				
			||||||
  recaptcha-config: "reCAPCHA 설정"
 | 
					  recaptcha-config: "reCAPCHA 설정"
 | 
				
			||||||
  recaptcha-info: "reCAPCHA를 사용하도록 설정하는 경우 reCAPCHA 토큰을 확보해야 합니다. https://www.google.com/recaptcha/intro/ 에 접속하여 토큰을 가져와주십시오."
 | 
					  recaptcha-info: "reCAPCHA를 사용하도록 설정하는 경우 reCAPCHA 토큰을 확보해야 합니다. https://www.google.com/recaptcha/intro/ 에 접속하여 토큰을 가져와주십시오."
 | 
				
			||||||
  enable-recaptcha: "reCAPCHA 활성화"
 | 
					  enable-recaptcha: "reCAPCHA 활성화"
 | 
				
			||||||
  recaptcha-site-key: "reCAPTCHA site key"
 | 
					  recaptcha-site-key: "사이트 키"
 | 
				
			||||||
  recaptcha-secret-key: "reCAPTCHA secret key"
 | 
					  recaptcha-secret-key: "시크릿 키"
 | 
				
			||||||
 | 
					  recaptcha-preview: "미리보기"
 | 
				
			||||||
 | 
					  hidden-tags: "숨긴 해시태그"
 | 
				
			||||||
 | 
					  hidden-tags-info: "집계에서 제외할 해시태그를 줄 바꿈으로 구분하여 기술합니다."
 | 
				
			||||||
 | 
					  external-service-integration-config: "외부 서비스 연계"
 | 
				
			||||||
  twitter-integration-config: "Twitter 연동 설정"
 | 
					  twitter-integration-config: "Twitter 연동 설정"
 | 
				
			||||||
  twitter-integration-info: "콜백 URL은 {url} 로 설정됩니다."
 | 
					  twitter-integration-info: "콜백 URL은 {url} 로 설정됩니다."
 | 
				
			||||||
  enable-twitter-integration: "트위터 연동 활성화"
 | 
					  enable-twitter-integration: "트위터 연동 활성화"
 | 
				
			||||||
| 
						 | 
					@ -1146,6 +1190,7 @@ admin/views/instance.vue:
 | 
				
			||||||
  save: "저장"
 | 
					  save: "저장"
 | 
				
			||||||
  saved: "저장하였습니다"
 | 
					  saved: "저장하였습니다"
 | 
				
			||||||
  pinned-users: "고정된 사용자"
 | 
					  pinned-users: "고정된 사용자"
 | 
				
			||||||
 | 
					  pinned-users-info: "고정해두고 싶은 사용자를 줄바꿈으로 구분하여 기술합니다."
 | 
				
			||||||
  email-config: "메일 서버 설정"
 | 
					  email-config: "메일 서버 설정"
 | 
				
			||||||
  email-config-info: "메일 주소 확인 혹은 비밀번호 재설정에 사용 됩니다."
 | 
					  email-config-info: "메일 주소 확인 혹은 비밀번호 재설정에 사용 됩니다."
 | 
				
			||||||
  enable-email: "메일 발신 활성화"
 | 
					  enable-email: "메일 발신 활성화"
 | 
				
			||||||
| 
						 | 
					@ -1231,6 +1276,8 @@ admin/views/users.vue:
 | 
				
			||||||
  unsilence-confirm: "침묵 해제하시겠습니까?"
 | 
					  unsilence-confirm: "침묵 해제하시겠습니까?"
 | 
				
			||||||
  update-remote-user: "원격 사용자 정보 갱신"
 | 
					  update-remote-user: "원격 사용자 정보 갱신"
 | 
				
			||||||
  remote-user-updated: "원격 사용자 정보를 갱신하였습니다"
 | 
					  remote-user-updated: "원격 사용자 정보를 갱신하였습니다"
 | 
				
			||||||
 | 
					  delete-all-files: "모든 파일 삭제"
 | 
				
			||||||
 | 
					  delete-all-files-confirm: "모든 파일을 삭제하시겠습니까?"
 | 
				
			||||||
  users:
 | 
					  users:
 | 
				
			||||||
    title: "사용자"
 | 
					    title: "사용자"
 | 
				
			||||||
    sort:
 | 
					    sort:
 | 
				
			||||||
| 
						 | 
					@ -1306,6 +1353,7 @@ admin/views/federation.vue:
 | 
				
			||||||
  latest-request-received-at: "마지막으로 요청을 받은 시간"
 | 
					  latest-request-received-at: "마지막으로 요청을 받은 시간"
 | 
				
			||||||
  remove-all-following: "모든 팔로잉 해제"
 | 
					  remove-all-following: "모든 팔로잉 해제"
 | 
				
			||||||
  remove-all-following-info: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행하십시오."
 | 
					  remove-all-following-info: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행하십시오."
 | 
				
			||||||
 | 
					  delete-all-files: "파일을 모두 삭제"
 | 
				
			||||||
  block: "차단"
 | 
					  block: "차단"
 | 
				
			||||||
  marked-as-closed: "폐쇄된 것으로 표시"
 | 
					  marked-as-closed: "폐쇄된 것으로 표시"
 | 
				
			||||||
  lookup: "조회"
 | 
					  lookup: "조회"
 | 
				
			||||||
| 
						 | 
					@ -1353,6 +1401,7 @@ admin/views/federation.vue:
 | 
				
			||||||
    hour: "1시간마다"
 | 
					    hour: "1시간마다"
 | 
				
			||||||
    day: "1일마다"
 | 
					    day: "1일마다"
 | 
				
			||||||
  blocked-hosts: "차단"
 | 
					  blocked-hosts: "차단"
 | 
				
			||||||
 | 
					  blocked-hosts-info: "차단할 호스트를 줄바꿈으로 구분하여 기술합니다."
 | 
				
			||||||
desktop/views/pages/welcome.vue:
 | 
					desktop/views/pages/welcome.vue:
 | 
				
			||||||
  about: "자세히..."
 | 
					  about: "자세히..."
 | 
				
			||||||
  timeline: "타임라인"
 | 
					  timeline: "타임라인"
 | 
				
			||||||
| 
						 | 
					@ -1509,6 +1558,7 @@ mobile/views/components/ui.nav.vue:
 | 
				
			||||||
  follow-requests: "팔로우 요청"
 | 
					  follow-requests: "팔로우 요청"
 | 
				
			||||||
  search: "검색"
 | 
					  search: "검색"
 | 
				
			||||||
  user-lists: "리스트"
 | 
					  user-lists: "리스트"
 | 
				
			||||||
 | 
					  user-groups: "그룹"
 | 
				
			||||||
  widgets: "위젯"
 | 
					  widgets: "위젯"
 | 
				
			||||||
  game: "게임"
 | 
					  game: "게임"
 | 
				
			||||||
  admin: "관리"
 | 
					  admin: "관리"
 | 
				
			||||||
| 
						 | 
					@ -1521,8 +1571,6 @@ mobile/views/pages/drive.vue:
 | 
				
			||||||
    rename-folder: "폴더 이름 바꾸기"
 | 
					    rename-folder: "폴더 이름 바꾸기"
 | 
				
			||||||
    move-folder: "이 폴더를 이동"
 | 
					    move-folder: "이 폴더를 이동"
 | 
				
			||||||
    delete-folder: "이 폴더를 삭제"
 | 
					    delete-folder: "이 폴더를 삭제"
 | 
				
			||||||
mobile/views/pages/user-lists.vue:
 | 
					 | 
				
			||||||
  title: "리스트"
 | 
					 | 
				
			||||||
mobile/views/pages/signup.vue:
 | 
					mobile/views/pages/signup.vue:
 | 
				
			||||||
  lets-start: "📦 이제 시작해도 됩니다"
 | 
					  lets-start: "📦 이제 시작해도 됩니다"
 | 
				
			||||||
mobile/views/pages/followers.vue:
 | 
					mobile/views/pages/followers.vue:
 | 
				
			||||||
| 
						 | 
					@ -1547,10 +1595,6 @@ mobile/views/pages/widgets/activity.vue:
 | 
				
			||||||
  activity: "활동"
 | 
					  activity: "활동"
 | 
				
			||||||
mobile/views/pages/share.vue:
 | 
					mobile/views/pages/share.vue:
 | 
				
			||||||
  share-with: "{name}(으)로 공유"
 | 
					  share-with: "{name}(으)로 공유"
 | 
				
			||||||
mobile/views/pages/received-follow-requests.vue:
 | 
					 | 
				
			||||||
  title: "팔로우 요청"
 | 
					 | 
				
			||||||
  accept: "승인"
 | 
					 | 
				
			||||||
  reject: "거부"
 | 
					 | 
				
			||||||
mobile/views/pages/note.vue:
 | 
					mobile/views/pages/note.vue:
 | 
				
			||||||
  title: "글"
 | 
					  title: "글"
 | 
				
			||||||
  prev: "이전 글"
 | 
					  prev: "이전 글"
 | 
				
			||||||
| 
						 | 
					@ -1656,9 +1700,17 @@ pages:
 | 
				
			||||||
  edit-this-page: "이 페이지를 편집"
 | 
					  edit-this-page: "이 페이지를 편집"
 | 
				
			||||||
  view-source: "소스 보기"
 | 
					  view-source: "소스 보기"
 | 
				
			||||||
  view-page: "페이지 보기"
 | 
					  view-page: "페이지 보기"
 | 
				
			||||||
 | 
					  like: "좋아요"
 | 
				
			||||||
 | 
					  unlike: "좋아요 해제"
 | 
				
			||||||
 | 
					  liked-pages: "좋아요한 페이지"
 | 
				
			||||||
 | 
					  my-pages: "내 페이지"
 | 
				
			||||||
  inspector: "인스펙터"
 | 
					  inspector: "인스펙터"
 | 
				
			||||||
  content: "페이지 블록"
 | 
					  content: "페이지 블록"
 | 
				
			||||||
  variables: "변수"
 | 
					  variables: "변수"
 | 
				
			||||||
 | 
					  variables-info: "변수를 사용하면 동적인 페이지를 만들 수 있습니다. 텍스트에 <b>{ 변수명 }</b>을 적으면 그 위치에 변수의 값을 집어넣습니다. 예를 들자면 <b>Hello { thing } world!</b> 라는 텍스트가 있을 때, 변수(thing)의 값이 <b>ai</b>인 경우 텍스트는 <b>Hello ai world!</b>가 됩니다."
 | 
				
			||||||
 | 
					  variables-info2: "변수의 평가(값을 산출해내는 것)는 위에서부터 아래로 진행되므로 어떤 변수의 내부에서 자신보다 아래에 있는 변수를 참조할 수는 없습니다. 예를 들자면 위에서부터 <b>A, B, C</b>의 3개의 변수가 정의되어 있을 때, <b>C</b>의 내부에 <b>A</b>나 <b>B</b>를 참조할 수는 있지만, <b>A</b>의 내부에서 <b>B</b>나 <b>C</b>를 참조할 수는 없습니다."
 | 
				
			||||||
 | 
					  variables-info3: "사용자로부터 입력을 받으려면, 페이지에 「사용자 입력」 블록을 삽입하고 「변수명」에 입력받은 값을 저장하고 싶은 변수명을 설정합니다 (변수는 자동으로 생성됩니다). 그 변수를 사용하여 사용자 입력에 따라 동작할 수 있습니다."
 | 
				
			||||||
 | 
					  variables-info4: "함수를 사용하면 반복되는 작업을 손쉽게 처리할 수 있습니다. 함수를 만드시려면 「함수」 타입의 변수를 만듭니다. 함수에서 슬롯(인수)를 받도록 설정하면, 함수를 사용할 때 슬롯에 입력된 값을 함수 안에서 변수로써 이용할 수 있게 됩니다. 또한, AiScript 표준에는 함수를 인수로 받는 함수(고차함수)도 존재합니다. 함수를 미리 정의하는 것 외에, 이와 같은 고차함수를 즉석으로 설정할 수 있습니다."
 | 
				
			||||||
  more-details: "자세한 설명"
 | 
					  more-details: "자세한 설명"
 | 
				
			||||||
  title: "제목"
 | 
					  title: "제목"
 | 
				
			||||||
  url: "페이지 URL"
 | 
					  url: "페이지 URL"
 | 
				
			||||||
| 
						 | 
					@ -1739,6 +1791,8 @@ pages:
 | 
				
			||||||
      text: "텍스트"
 | 
					      text: "텍스트"
 | 
				
			||||||
      multiLineText: "텍스트 (여러줄)"
 | 
					      multiLineText: "텍스트 (여러줄)"
 | 
				
			||||||
      textList: "텍스트 목록"
 | 
					      textList: "텍스트 목록"
 | 
				
			||||||
 | 
					      _textList:
 | 
				
			||||||
 | 
					        info: "각각을 줄 바꿈으로 구분해주십시오"
 | 
				
			||||||
      strLen: "텍스트의 길이"
 | 
					      strLen: "텍스트의 길이"
 | 
				
			||||||
      _strLen:
 | 
					      _strLen:
 | 
				
			||||||
        arg1: "텍스트"
 | 
					        arg1: "텍스트"
 | 
				
			||||||
| 
						 | 
					@ -1828,40 +1882,57 @@ pages:
 | 
				
			||||||
      randomPick: "목록에서 임의로 선택"
 | 
					      randomPick: "목록에서 임의로 선택"
 | 
				
			||||||
      _randomPick:
 | 
					      _randomPick:
 | 
				
			||||||
        arg1: "리스트"
 | 
					        arg1: "리스트"
 | 
				
			||||||
 | 
					      dailyRandom: "랜덤 (하루동안 결과 유지)"
 | 
				
			||||||
      _dailyRandom:
 | 
					      _dailyRandom:
 | 
				
			||||||
        arg1: "확률"
 | 
					        arg1: "확률"
 | 
				
			||||||
 | 
					      dailyRannum: "난수 (하루동안 결과 유지)"
 | 
				
			||||||
      _dailyRannum:
 | 
					      _dailyRannum:
 | 
				
			||||||
        arg1: "최소"
 | 
					        arg1: "최소"
 | 
				
			||||||
        arg2: "최대"
 | 
					        arg2: "최대"
 | 
				
			||||||
 | 
					      dailyRandomPick: "목록에서 임의로 선택 (하루동안 결과 유지)"
 | 
				
			||||||
      _dailyRandomPick:
 | 
					      _dailyRandomPick:
 | 
				
			||||||
        arg1: "리스트"
 | 
					        arg1: "리스트"
 | 
				
			||||||
 | 
					      seedRandom: "무작위 (시드)"
 | 
				
			||||||
      _seedRandom:
 | 
					      _seedRandom:
 | 
				
			||||||
        arg1: "시드"
 | 
					        arg1: "시드"
 | 
				
			||||||
        arg2: "확률"
 | 
					        arg2: "확률"
 | 
				
			||||||
 | 
					      seedRannum: "난수 (시드)"
 | 
				
			||||||
      _seedRannum:
 | 
					      _seedRannum:
 | 
				
			||||||
        arg1: "시드"
 | 
					        arg1: "시드"
 | 
				
			||||||
        arg2: "최소"
 | 
					        arg2: "최소"
 | 
				
			||||||
        arg3: "최대"
 | 
					        arg3: "최대"
 | 
				
			||||||
 | 
					      seedRandomPick: "목록에서 무작위로 선택 (시드)"
 | 
				
			||||||
      _seedRandomPick:
 | 
					      _seedRandomPick:
 | 
				
			||||||
        arg1: "시드"
 | 
					        arg1: "시드"
 | 
				
			||||||
        arg2: "리스트"
 | 
					        arg2: "리스트"
 | 
				
			||||||
 | 
					      DRPWPM: "확률형 목록에서 임의로 선택 (하루동안 결과 유지)"
 | 
				
			||||||
      _DRPWPM:
 | 
					      _DRPWPM:
 | 
				
			||||||
        arg1: "텍스트 목록"
 | 
					        arg1: "텍스트 목록"
 | 
				
			||||||
 | 
					      pick: "목록에서 선택"
 | 
				
			||||||
      _pick:
 | 
					      _pick:
 | 
				
			||||||
        arg1: "리스트"
 | 
					        arg1: "리스트"
 | 
				
			||||||
 | 
					        arg2: "위치"
 | 
				
			||||||
      number: "수치"
 | 
					      number: "수치"
 | 
				
			||||||
      stringToNumber: "텍스트를 수치로"
 | 
					      stringToNumber: "텍스트를 수치로"
 | 
				
			||||||
      _stringToNumber:
 | 
					      _stringToNumber:
 | 
				
			||||||
        arg1: "텍스트"
 | 
					        arg1: "텍스트"
 | 
				
			||||||
 | 
					      numberToString: "수치를 텍스트로"
 | 
				
			||||||
      _numberToString:
 | 
					      _numberToString:
 | 
				
			||||||
        arg1: "수치"
 | 
					        arg1: "수치"
 | 
				
			||||||
 | 
					      splitStrByLine: "텍스트를 행 단위로 분할"
 | 
				
			||||||
      _splitStrByLine:
 | 
					      _splitStrByLine:
 | 
				
			||||||
        arg1: "텍스트"
 | 
					        arg1: "텍스트"
 | 
				
			||||||
      ref: "변수"
 | 
					      ref: "변수"
 | 
				
			||||||
      fn: "함수"
 | 
					      fn: "함수"
 | 
				
			||||||
      _fn:
 | 
					      _fn:
 | 
				
			||||||
        slots: "슬롯"
 | 
					        slots: "슬롯"
 | 
				
			||||||
 | 
					        slots-info: "각 슬롯을 줄 바꿈으로 구분하여 주십시오"
 | 
				
			||||||
        arg1: "출력"
 | 
					        arg1: "출력"
 | 
				
			||||||
 | 
					      for: "반복"
 | 
				
			||||||
 | 
					      _for:
 | 
				
			||||||
 | 
					        arg1: "횟수"
 | 
				
			||||||
 | 
					        arg2: "처리"
 | 
				
			||||||
 | 
					    typeError: "슬롯 {slot}은 \"{expect}\"를 사용할 수 있지만 \"{actual}이 들어있습니다!"
 | 
				
			||||||
    thereIsEmptySlot: "슬롯 {slot}이(가) 비었습니다!"
 | 
					    thereIsEmptySlot: "슬롯 {slot}이(가) 비었습니다!"
 | 
				
			||||||
    types:
 | 
					    types:
 | 
				
			||||||
      string: "텍스트"
 | 
					      string: "텍스트"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -104,10 +104,9 @@ common/views/components/messaging.vue:
 | 
				
			||||||
  search-user: "Gebruiker zoeken"
 | 
					  search-user: "Gebruiker zoeken"
 | 
				
			||||||
  you: "Jij"
 | 
					  you: "Jij"
 | 
				
			||||||
  no-history: "Geen geschiedenis"
 | 
					  no-history: "Geen geschiedenis"
 | 
				
			||||||
 | 
					  user: "Gebruiker"
 | 
				
			||||||
common/views/components/messaging-room.vue:
 | 
					common/views/components/messaging-room.vue:
 | 
				
			||||||
  empty: "Geen gesprekken"
 | 
					 | 
				
			||||||
  no-history: "Er is geen verdere geschiedenis"
 | 
					  no-history: "Er is geen verdere geschiedenis"
 | 
				
			||||||
  resize-form: "Versleep om grootte te wijzigen"
 | 
					 | 
				
			||||||
  new-message: "Nieuw bericht"
 | 
					  new-message: "Nieuw bericht"
 | 
				
			||||||
common/views/components/messaging-room.form.vue:
 | 
					common/views/components/messaging-room.form.vue:
 | 
				
			||||||
  input-message-here: "Voer hier je bericht in"
 | 
					  input-message-here: "Voer hier je bericht in"
 | 
				
			||||||
| 
						 | 
					@ -202,6 +201,9 @@ common/views/components/profile-editor.vue:
 | 
				
			||||||
  enter-password: "Voer het wachtwoord in"
 | 
					  enter-password: "Voer het wachtwoord in"
 | 
				
			||||||
common/views/components/user-list-editor.vue:
 | 
					common/views/components/user-list-editor.vue:
 | 
				
			||||||
  users: "Gebruiker"
 | 
					  users: "Gebruiker"
 | 
				
			||||||
 | 
					  add-user: "Gebruiker toevoegen"
 | 
				
			||||||
 | 
					common/views/components/user-lists.vue:
 | 
				
			||||||
 | 
					  user-lists: "Lijsten"
 | 
				
			||||||
common/views/widgets/broadcast.vue:
 | 
					common/views/widgets/broadcast.vue:
 | 
				
			||||||
  fetching: "Bezig met ophalen"
 | 
					  fetching: "Bezig met ophalen"
 | 
				
			||||||
  no-broadcasts: "Geen uitzendingen"
 | 
					  no-broadcasts: "Geen uitzendingen"
 | 
				
			||||||
| 
						 | 
					@ -401,8 +403,6 @@ desktop/views/components/ui.header.post.vue:
 | 
				
			||||||
  post: "Nieuw bericht opstellen"
 | 
					  post: "Nieuw bericht opstellen"
 | 
				
			||||||
desktop/views/components/ui.header.search.vue:
 | 
					desktop/views/components/ui.header.search.vue:
 | 
				
			||||||
  placeholder: "Zoeken"
 | 
					  placeholder: "Zoeken"
 | 
				
			||||||
desktop/views/components/user-lists-window.vue:
 | 
					 | 
				
			||||||
  title: "Lijsten"
 | 
					 | 
				
			||||||
desktop/views/components/user-preview.vue:
 | 
					desktop/views/components/user-preview.vue:
 | 
				
			||||||
  notes: "Berichten"
 | 
					  notes: "Berichten"
 | 
				
			||||||
  following: "Volgend"
 | 
					  following: "Volgend"
 | 
				
			||||||
| 
						 | 
					@ -558,8 +558,6 @@ mobile/views/pages/drive.vue:
 | 
				
			||||||
  contextmenu:
 | 
					  contextmenu:
 | 
				
			||||||
    upload: "Bestand uploaden"
 | 
					    upload: "Bestand uploaden"
 | 
				
			||||||
    create-folder: "Map creëren"
 | 
					    create-folder: "Map creëren"
 | 
				
			||||||
mobile/views/pages/user-lists.vue:
 | 
					 | 
				
			||||||
  title: "Lijsten"
 | 
					 | 
				
			||||||
mobile/views/pages/home.vue:
 | 
					mobile/views/pages/home.vue:
 | 
				
			||||||
  home: "Startpagina"
 | 
					  home: "Startpagina"
 | 
				
			||||||
  local: "Lokaal"
 | 
					  local: "Lokaal"
 | 
				
			||||||
| 
						 | 
					@ -618,6 +616,7 @@ docs:
 | 
				
			||||||
  edit-this-page-on-github: "Heb je een fout ontdekt of wil je bijdragen aan de documentatie? "
 | 
					  edit-this-page-on-github: "Heb je een fout ontdekt of wil je bijdragen aan de documentatie? "
 | 
				
			||||||
  edit-this-page-on-github-link: "Bewerk deze pagina op GitHub!"
 | 
					  edit-this-page-on-github-link: "Bewerk deze pagina op GitHub!"
 | 
				
			||||||
pages:
 | 
					pages:
 | 
				
			||||||
 | 
					  like: "Leuk"
 | 
				
			||||||
  blocks:
 | 
					  blocks:
 | 
				
			||||||
    image: "Afbeeldingen"
 | 
					    image: "Afbeeldingen"
 | 
				
			||||||
  script:
 | 
					  script:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,6 +130,7 @@ common/views/components/cw-button.vue:
 | 
				
			||||||
  hide: "Skjul"
 | 
					  hide: "Skjul"
 | 
				
			||||||
common/views/components/messaging.vue:
 | 
					common/views/components/messaging.vue:
 | 
				
			||||||
  you: "Du"
 | 
					  you: "Du"
 | 
				
			||||||
 | 
					  user: "Bruker"
 | 
				
			||||||
common/views/components/messaging-room.form.vue:
 | 
					common/views/components/messaging-room.form.vue:
 | 
				
			||||||
  send: "Send"
 | 
					  send: "Send"
 | 
				
			||||||
common/views/components/messaging-room.message.vue:
 | 
					common/views/components/messaging-room.message.vue:
 | 
				
			||||||
| 
						 | 
					@ -192,8 +193,13 @@ common/views/components/profile-editor.vue:
 | 
				
			||||||
    user-lists: "Lister"
 | 
					    user-lists: "Lister"
 | 
				
			||||||
common/views/components/user-list-editor.vue:
 | 
					common/views/components/user-list-editor.vue:
 | 
				
			||||||
  users: "Bruker"
 | 
					  users: "Bruker"
 | 
				
			||||||
 | 
					common/views/components/user-group-editor.vue:
 | 
				
			||||||
 | 
					  invite: "Inviter"
 | 
				
			||||||
common/views/components/user-lists.vue:
 | 
					common/views/components/user-lists.vue:
 | 
				
			||||||
 | 
					  user-lists: "Lister"
 | 
				
			||||||
  list-name: "Liste navn"
 | 
					  list-name: "Liste navn"
 | 
				
			||||||
 | 
					common/views/components/user-groups.vue:
 | 
				
			||||||
 | 
					  invites: "Inviter"
 | 
				
			||||||
common/views/widgets/broadcast.vue:
 | 
					common/views/widgets/broadcast.vue:
 | 
				
			||||||
  fetching: "Henter"
 | 
					  fetching: "Henter"
 | 
				
			||||||
  next: "Neste"
 | 
					  next: "Neste"
 | 
				
			||||||
| 
						 | 
					@ -307,11 +313,6 @@ desktop/views/components/ui.header.post.vue:
 | 
				
			||||||
  post: "Skriv nytt innlegg"
 | 
					  post: "Skriv nytt innlegg"
 | 
				
			||||||
desktop/views/components/ui.header.search.vue:
 | 
					desktop/views/components/ui.header.search.vue:
 | 
				
			||||||
  placeholder: "Søk"
 | 
					  placeholder: "Søk"
 | 
				
			||||||
desktop/views/components/received-follow-requests-window.vue:
 | 
					 | 
				
			||||||
  accept: "Godta"
 | 
					 | 
				
			||||||
  reject: "Avslå"
 | 
					 | 
				
			||||||
desktop/views/components/user-lists-window.vue:
 | 
					 | 
				
			||||||
  title: "Brukerlister"
 | 
					 | 
				
			||||||
desktop/views/components/user-preview.vue:
 | 
					desktop/views/components/user-preview.vue:
 | 
				
			||||||
  notes: "Innlegg"
 | 
					  notes: "Innlegg"
 | 
				
			||||||
  following: "Følger"
 | 
					  following: "Følger"
 | 
				
			||||||
| 
						 | 
					@ -448,17 +449,12 @@ mobile/views/components/ui.nav.vue:
 | 
				
			||||||
  user-lists: "Lister"
 | 
					  user-lists: "Lister"
 | 
				
			||||||
  game: "Spill"
 | 
					  game: "Spill"
 | 
				
			||||||
  admin: "Admin"
 | 
					  admin: "Admin"
 | 
				
			||||||
mobile/views/pages/user-lists.vue:
 | 
					 | 
				
			||||||
  title: "Lister"
 | 
					 | 
				
			||||||
mobile/views/pages/home.vue:
 | 
					mobile/views/pages/home.vue:
 | 
				
			||||||
  home: "Hjem"
 | 
					  home: "Hjem"
 | 
				
			||||||
  local: "Lokalt"
 | 
					  local: "Lokalt"
 | 
				
			||||||
  global: "Globalt"
 | 
					  global: "Globalt"
 | 
				
			||||||
mobile/views/pages/widgets.vue:
 | 
					mobile/views/pages/widgets.vue:
 | 
				
			||||||
  add-widget: "Legg til"
 | 
					  add-widget: "Legg til"
 | 
				
			||||||
mobile/views/pages/received-follow-requests.vue:
 | 
					 | 
				
			||||||
  accept: "Godta"
 | 
					 | 
				
			||||||
  reject: "Avslå"
 | 
					 | 
				
			||||||
mobile/views/pages/note.vue:
 | 
					mobile/views/pages/note.vue:
 | 
				
			||||||
  title: "Innlegg"
 | 
					  title: "Innlegg"
 | 
				
			||||||
  prev: "Forrige innlegg"
 | 
					  prev: "Forrige innlegg"
 | 
				
			||||||
| 
						 | 
					@ -490,6 +486,7 @@ deck/deck.user-column.vue:
 | 
				
			||||||
  followers: "Følgere"
 | 
					  followers: "Følgere"
 | 
				
			||||||
  images: "Bilder"
 | 
					  images: "Bilder"
 | 
				
			||||||
pages:
 | 
					pages:
 | 
				
			||||||
 | 
					  like: "Lik"
 | 
				
			||||||
  blocks:
 | 
					  blocks:
 | 
				
			||||||
    image: "Bilder"
 | 
					    image: "Bilder"
 | 
				
			||||||
  script:
 | 
					  script:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -168,6 +168,7 @@ common:
 | 
				
			||||||
  my-token-regenerated: "Twój token został wygenerowany. Zostaniesz wylogowany."
 | 
					  my-token-regenerated: "Twój token został wygenerowany. Zostaniesz wylogowany."
 | 
				
			||||||
  hide-password: "Ukryj hasło"
 | 
					  hide-password: "Ukryj hasło"
 | 
				
			||||||
  show-password: "Pokaż hasło"
 | 
					  show-password: "Pokaż hasło"
 | 
				
			||||||
 | 
					  enter-username: "Wprowadź nazwę użytkownika"
 | 
				
			||||||
  view-on-remote: "Dla dopełnienia, zobacz to zdalnie."
 | 
					  view-on-remote: "Dla dopełnienia, zobacz to zdalnie."
 | 
				
			||||||
  renoted-by: "{user} udostępnił(a)"
 | 
					  renoted-by: "{user} udostępnił(a)"
 | 
				
			||||||
  error:
 | 
					  error:
 | 
				
			||||||
| 
						 | 
					@ -323,18 +324,15 @@ common/views/components/messaging.vue:
 | 
				
			||||||
  search-user: "Znajdź użytkownika"
 | 
					  search-user: "Znajdź użytkownika"
 | 
				
			||||||
  you: "Ty"
 | 
					  you: "Ty"
 | 
				
			||||||
  no-history: "Brak historii"
 | 
					  no-history: "Brak historii"
 | 
				
			||||||
 | 
					  user: "Użytkownicy"
 | 
				
			||||||
common/views/components/messaging-room.vue:
 | 
					common/views/components/messaging-room.vue:
 | 
				
			||||||
  empty: "Brak konwersacji"
 | 
					 | 
				
			||||||
  no-history: "Brak dalszej historii"
 | 
					  no-history: "Brak dalszej historii"
 | 
				
			||||||
  resize-form: "Przeciągnij aby zmienić rozmiar"
 | 
					 | 
				
			||||||
  new-message: "Nowa wiadomość"
 | 
					  new-message: "Nowa wiadomość"
 | 
				
			||||||
  only-one-file-attached: "Tylko JEDEN plik może zostać dołączony do wiadomości"
 | 
					 | 
				
			||||||
common/views/components/messaging-room.form.vue:
 | 
					common/views/components/messaging-room.form.vue:
 | 
				
			||||||
  input-message-here: "Wprowadź wiadomość tutaj"
 | 
					  input-message-here: "Wprowadź wiadomość tutaj"
 | 
				
			||||||
  send: "Wyślij"
 | 
					  send: "Wyślij"
 | 
				
			||||||
  attach-from-local: "Załącz pliki z komputera"
 | 
					  attach-from-local: "Załącz pliki z komputera"
 | 
				
			||||||
  attach-from-drive: "Załącz pliki z dysku"
 | 
					  attach-from-drive: "Załącz pliki z dysku"
 | 
				
			||||||
  only-one-file-attached: "Tylko jeden plik może zostać dołączony do wiadomości"
 | 
					 | 
				
			||||||
common/views/components/messaging-room.message.vue:
 | 
					common/views/components/messaging-room.message.vue:
 | 
				
			||||||
  is-read: "Przeczytano"
 | 
					  is-read: "Przeczytano"
 | 
				
			||||||
  deleted: "Wiadomość została usunięta"
 | 
					  deleted: "Wiadomość została usunięta"
 | 
				
			||||||
| 
						 | 
					@ -511,8 +509,15 @@ common/views/components/user-list-editor.vue:
 | 
				
			||||||
  remove-user: "Usuń z tej listy"
 | 
					  remove-user: "Usuń z tej listy"
 | 
				
			||||||
  delete-are-you-sure: "Usunąć listę \"$1\"?"
 | 
					  delete-are-you-sure: "Usunąć listę \"$1\"?"
 | 
				
			||||||
  deleted: "Usunięto"
 | 
					  deleted: "Usunięto"
 | 
				
			||||||
 | 
					  add-user: "Dodaj użytkownika"
 | 
				
			||||||
 | 
					common/views/components/user-group-editor.vue:
 | 
				
			||||||
 | 
					  deleted: "Usunięto"
 | 
				
			||||||
 | 
					  invite: "Zaproś"
 | 
				
			||||||
common/views/components/user-lists.vue:
 | 
					common/views/components/user-lists.vue:
 | 
				
			||||||
 | 
					  user-lists: "Listy"
 | 
				
			||||||
  list-name: "Nazwa listy"
 | 
					  list-name: "Nazwa listy"
 | 
				
			||||||
 | 
					common/views/components/user-groups.vue:
 | 
				
			||||||
 | 
					  invites: "Zaproś"
 | 
				
			||||||
common/views/widgets/broadcast.vue:
 | 
					common/views/widgets/broadcast.vue:
 | 
				
			||||||
  fetching: "Sprawdzanie"
 | 
					  fetching: "Sprawdzanie"
 | 
				
			||||||
  no-broadcasts: "Brak transmisji"
 | 
					  no-broadcasts: "Brak transmisji"
 | 
				
			||||||
| 
						 | 
					@ -568,6 +573,8 @@ common/views/pages/follow.vue:
 | 
				
			||||||
  request-pending: "Oczekiwanie na pozwolenie"
 | 
					  request-pending: "Oczekiwanie na pozwolenie"
 | 
				
			||||||
  follow-processing: "Przetwarzanie śledzenia"
 | 
					  follow-processing: "Przetwarzanie śledzenia"
 | 
				
			||||||
  follow-request: "Poproś o śledzenie"
 | 
					  follow-request: "Poproś o śledzenie"
 | 
				
			||||||
 | 
					common/views/pages/follow-requests.vue:
 | 
				
			||||||
 | 
					  received-follow-requests: "Prośby o śledzenie"
 | 
				
			||||||
desktop:
 | 
					desktop:
 | 
				
			||||||
  banner: "Baner"
 | 
					  banner: "Baner"
 | 
				
			||||||
  uploading-banner: "Wysyłanie baneru"
 | 
					  uploading-banner: "Wysyłanie baneru"
 | 
				
			||||||
| 
						 | 
					@ -829,12 +836,6 @@ desktop/views/components/ui.header.post.vue:
 | 
				
			||||||
  post: "Utwórz nowy wpis"
 | 
					  post: "Utwórz nowy wpis"
 | 
				
			||||||
desktop/views/components/ui.header.search.vue:
 | 
					desktop/views/components/ui.header.search.vue:
 | 
				
			||||||
  placeholder: "Szukaj"
 | 
					  placeholder: "Szukaj"
 | 
				
			||||||
desktop/views/components/received-follow-requests-window.vue:
 | 
					 | 
				
			||||||
  title: "Prośby o śledzenie"
 | 
					 | 
				
			||||||
  accept: "Zatwierdź"
 | 
					 | 
				
			||||||
  reject: "Odmów"
 | 
					 | 
				
			||||||
desktop/views/components/user-lists-window.vue:
 | 
					 | 
				
			||||||
  title: "Listy"
 | 
					 | 
				
			||||||
desktop/views/components/user-preview.vue:
 | 
					desktop/views/components/user-preview.vue:
 | 
				
			||||||
  notes: "Wpisy"
 | 
					  notes: "Wpisy"
 | 
				
			||||||
  following: "Śledzeni"
 | 
					  following: "Śledzeni"
 | 
				
			||||||
| 
						 | 
					@ -866,6 +867,7 @@ admin/views/abuse.vue:
 | 
				
			||||||
  remove-report: "Usuń"
 | 
					  remove-report: "Usuń"
 | 
				
			||||||
admin/views/instance.vue:
 | 
					admin/views/instance.vue:
 | 
				
			||||||
  instance: "Instancja"
 | 
					  instance: "Instancja"
 | 
				
			||||||
 | 
					  recaptcha-preview: "Pokaż podgląd"
 | 
				
			||||||
  github-integration-client-id: "Client ID"
 | 
					  github-integration-client-id: "Client ID"
 | 
				
			||||||
  github-integration-client-secret: "Client Secret"
 | 
					  github-integration-client-secret: "Client Secret"
 | 
				
			||||||
  discord-integration-client-id: "Client ID"
 | 
					  discord-integration-client-id: "Client ID"
 | 
				
			||||||
| 
						 | 
					@ -1102,8 +1104,6 @@ mobile/views/pages/drive.vue:
 | 
				
			||||||
  contextmenu:
 | 
					  contextmenu:
 | 
				
			||||||
    upload: "Wyślij plik"
 | 
					    upload: "Wyślij plik"
 | 
				
			||||||
    create-folder: "Utwórz katalog"
 | 
					    create-folder: "Utwórz katalog"
 | 
				
			||||||
mobile/views/pages/user-lists.vue:
 | 
					 | 
				
			||||||
  title: "Listy"
 | 
					 | 
				
			||||||
mobile/views/pages/signup.vue:
 | 
					mobile/views/pages/signup.vue:
 | 
				
			||||||
  lets-start: "Rozpocznijmy! 📦"
 | 
					  lets-start: "Rozpocznijmy! 📦"
 | 
				
			||||||
mobile/views/pages/home.vue:
 | 
					mobile/views/pages/home.vue:
 | 
				
			||||||
| 
						 | 
					@ -1118,10 +1118,6 @@ mobile/views/pages/widgets.vue:
 | 
				
			||||||
  customization-tips: "Wskazówki o dostosowywaniu"
 | 
					  customization-tips: "Wskazówki o dostosowywaniu"
 | 
				
			||||||
mobile/views/pages/widgets/activity.vue:
 | 
					mobile/views/pages/widgets/activity.vue:
 | 
				
			||||||
  activity: "Aktywność"
 | 
					  activity: "Aktywność"
 | 
				
			||||||
mobile/views/pages/received-follow-requests.vue:
 | 
					 | 
				
			||||||
  title: "Prośby o śledzenie"
 | 
					 | 
				
			||||||
  accept: "Zatwierdź"
 | 
					 | 
				
			||||||
  reject: "Odmów"
 | 
					 | 
				
			||||||
mobile/views/pages/note.vue:
 | 
					mobile/views/pages/note.vue:
 | 
				
			||||||
  title: "Wpis"
 | 
					  title: "Wpis"
 | 
				
			||||||
  prev: "Poprzedni wpis"
 | 
					  prev: "Poprzedni wpis"
 | 
				
			||||||
| 
						 | 
					@ -1196,6 +1192,7 @@ dev/views/new-app.vue:
 | 
				
			||||||
  app-name: "Nazwa Aplikacji"
 | 
					  app-name: "Nazwa Aplikacji"
 | 
				
			||||||
  authority: "Uprawnienia"
 | 
					  authority: "Uprawnienia"
 | 
				
			||||||
pages:
 | 
					pages:
 | 
				
			||||||
 | 
					  like: "Lubię"
 | 
				
			||||||
  title: "Tytuł"
 | 
					  title: "Tytuł"
 | 
				
			||||||
  blocks:
 | 
					  blocks:
 | 
				
			||||||
    image: "Zdjęcia"
 | 
					    image: "Zdjęcia"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -160,6 +160,10 @@ common/views/components/profile-editor.vue:
 | 
				
			||||||
  name: "Nome"
 | 
					  name: "Nome"
 | 
				
			||||||
  export-targets:
 | 
					  export-targets:
 | 
				
			||||||
    following-list: "Seguindo"
 | 
					    following-list: "Seguindo"
 | 
				
			||||||
 | 
					common/views/components/user-group-editor.vue:
 | 
				
			||||||
 | 
					  invite: "Convidar"
 | 
				
			||||||
 | 
					common/views/components/user-groups.vue:
 | 
				
			||||||
 | 
					  invites: "Convidar"
 | 
				
			||||||
common/views/widgets/posts-monitor.vue:
 | 
					common/views/widgets/posts-monitor.vue:
 | 
				
			||||||
  title: "Gráfico de publicações"
 | 
					  title: "Gráfico de publicações"
 | 
				
			||||||
common/views/widgets/memo.vue:
 | 
					common/views/widgets/memo.vue:
 | 
				
			||||||
| 
						 | 
					@ -270,6 +274,7 @@ docs:
 | 
				
			||||||
dev/views/index.vue:
 | 
					dev/views/index.vue:
 | 
				
			||||||
  manage-apps: "Gerenciar aplicativos"
 | 
					  manage-apps: "Gerenciar aplicativos"
 | 
				
			||||||
pages:
 | 
					pages:
 | 
				
			||||||
 | 
					  like: "Curtir"
 | 
				
			||||||
  blocks:
 | 
					  blocks:
 | 
				
			||||||
    image: "Imagens"
 | 
					    image: "Imagens"
 | 
				
			||||||
    post: "Formulário de publicação"
 | 
					    post: "Formulário de publicação"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -147,6 +147,7 @@ mobile/views/components/sub-note-content.vue:
 | 
				
			||||||
mobile/views/pages/widgets.vue:
 | 
					mobile/views/pages/widgets.vue:
 | 
				
			||||||
  customization-tips: "Советы по настройке"
 | 
					  customization-tips: "Советы по настройке"
 | 
				
			||||||
pages:
 | 
					pages:
 | 
				
			||||||
 | 
					  like: "Нравится"
 | 
				
			||||||
  script:
 | 
					  script:
 | 
				
			||||||
    categories:
 | 
					    categories:
 | 
				
			||||||
      random: "Случайно"
 | 
					      random: "Случайно"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -253,6 +253,7 @@ common:
 | 
				
			||||||
  my-token-regenerated: "您的 Token 已被重置, 您将自动登出。"
 | 
					  my-token-regenerated: "您的 Token 已被重置, 您将自动登出。"
 | 
				
			||||||
  hide-password: "隐藏密码"
 | 
					  hide-password: "隐藏密码"
 | 
				
			||||||
  show-password: "显示密码"
 | 
					  show-password: "显示密码"
 | 
				
			||||||
 | 
					  enter-username: "请输入用户名"
 | 
				
			||||||
  do-not-use-in-production: "这是一个开发者测试版. 请勿在生产环境中使用."
 | 
					  do-not-use-in-production: "这是一个开发者测试版. 请勿在生产环境中使用."
 | 
				
			||||||
  user-suspended: "该用户已被冻结。"
 | 
					  user-suspended: "该用户已被冻结。"
 | 
				
			||||||
  is-remote-user: "此用户信息可能不准确。"
 | 
					  is-remote-user: "此用户信息可能不准确。"
 | 
				
			||||||
| 
						 | 
					@ -448,18 +449,22 @@ common/views/components/messaging.vue:
 | 
				
			||||||
  search-user: "查找用户"
 | 
					  search-user: "查找用户"
 | 
				
			||||||
  you: "您"
 | 
					  you: "您"
 | 
				
			||||||
  no-history: "没有历史记录"
 | 
					  no-history: "没有历史记录"
 | 
				
			||||||
 | 
					  user: "用户"
 | 
				
			||||||
 | 
					  group: "群组"
 | 
				
			||||||
 | 
					  start-with-user: "开始用户聊天"
 | 
				
			||||||
 | 
					  start-with-group: "开始群组聊天"
 | 
				
			||||||
common/views/components/messaging-room.vue:
 | 
					common/views/components/messaging-room.vue:
 | 
				
			||||||
  empty: "没有对话"
 | 
					  not-talked-user: "没有用户的会话记录"
 | 
				
			||||||
 | 
					  not-talked-group: "没有群组的会话记录"
 | 
				
			||||||
  no-history: "没有更多的历史记录"
 | 
					  no-history: "没有更多的历史记录"
 | 
				
			||||||
  resize-form: "拖动以调整大小"
 | 
					 | 
				
			||||||
  new-message: "新信息"
 | 
					  new-message: "新信息"
 | 
				
			||||||
  only-one-file-attached: "只有一个附件可以附加到消息中。"
 | 
					  only-one-file-attached: "只能添加一个附件"
 | 
				
			||||||
common/views/components/messaging-room.form.vue:
 | 
					common/views/components/messaging-room.form.vue:
 | 
				
			||||||
  input-message-here: "在此键入信息"
 | 
					  input-message-here: "在此键入信息"
 | 
				
			||||||
  send: "发送"
 | 
					  send: "发送"
 | 
				
			||||||
  attach-from-local: "从电脑中添加文件"
 | 
					  attach-from-local: "从电脑中添加文件"
 | 
				
			||||||
  attach-from-drive: "从网盘中添加文件"
 | 
					  attach-from-drive: "从网盘中添加文件"
 | 
				
			||||||
  only-one-file-attached: "在信息中只允许添加一个附件"
 | 
					  only-one-file-attached: "只能添加一个附件"
 | 
				
			||||||
common/views/components/messaging-room.message.vue:
 | 
					common/views/components/messaging-room.message.vue:
 | 
				
			||||||
  is-read: "已读"
 | 
					  is-read: "已读"
 | 
				
			||||||
  deleted: "此消息已被删除"
 | 
					  deleted: "此消息已被删除"
 | 
				
			||||||
| 
						 | 
					@ -694,9 +699,25 @@ common/views/components/user-list-editor.vue:
 | 
				
			||||||
  remove-user: "从此列表中删除"
 | 
					  remove-user: "从此列表中删除"
 | 
				
			||||||
  delete-are-you-sure: "删除列表“$1”?"
 | 
					  delete-are-you-sure: "删除列表“$1”?"
 | 
				
			||||||
  deleted: "已删除"
 | 
					  deleted: "已删除"
 | 
				
			||||||
 | 
					  add-user: "添加用户"
 | 
				
			||||||
 | 
					common/views/components/user-group-editor.vue:
 | 
				
			||||||
 | 
					  users: "成员"
 | 
				
			||||||
 | 
					  rename: "更改群组名"
 | 
				
			||||||
 | 
					  delete: "删除群组"
 | 
				
			||||||
 | 
					  remove-user: "从本群组中删除"
 | 
				
			||||||
 | 
					  delete-are-you-sure: "确定要删除「$1」组?"
 | 
				
			||||||
 | 
					  deleted: "已删除"
 | 
				
			||||||
 | 
					  invite: "邀请"
 | 
				
			||||||
common/views/components/user-lists.vue:
 | 
					common/views/components/user-lists.vue:
 | 
				
			||||||
 | 
					  user-lists: "列表"
 | 
				
			||||||
  create-list: "创建列表"
 | 
					  create-list: "创建列表"
 | 
				
			||||||
  list-name: "列表名称"
 | 
					  list-name: "列表名称"
 | 
				
			||||||
 | 
					common/views/components/user-groups.vue:
 | 
				
			||||||
 | 
					  user-groups: "群组"
 | 
				
			||||||
 | 
					  create-group: "创建群组"
 | 
				
			||||||
 | 
					  group-name: "群组名"
 | 
				
			||||||
 | 
					  invites: "邀请"
 | 
				
			||||||
 | 
					  reject-invite: "拒绝"
 | 
				
			||||||
common/views/widgets/broadcast.vue:
 | 
					common/views/widgets/broadcast.vue:
 | 
				
			||||||
  fetching: "确认中"
 | 
					  fetching: "确认中"
 | 
				
			||||||
  no-broadcasts: "没有公告"
 | 
					  no-broadcasts: "没有公告"
 | 
				
			||||||
| 
						 | 
					@ -758,6 +779,10 @@ common/views/pages/follow.vue:
 | 
				
			||||||
  request-pending: "发送关注申请"
 | 
					  request-pending: "发送关注申请"
 | 
				
			||||||
  follow-processing: "申请处理中"
 | 
					  follow-processing: "申请处理中"
 | 
				
			||||||
  follow-request: "关注请求"
 | 
					  follow-request: "关注请求"
 | 
				
			||||||
 | 
					common/views/pages/follow-requests.vue:
 | 
				
			||||||
 | 
					  received-follow-requests: "关注申请"
 | 
				
			||||||
 | 
					  accept: "接受"
 | 
				
			||||||
 | 
					  reject: "拒绝"
 | 
				
			||||||
desktop:
 | 
					desktop:
 | 
				
			||||||
  banner-crop-title: "裁剪显示为背景的部分"
 | 
					  banner-crop-title: "裁剪显示为背景的部分"
 | 
				
			||||||
  banner: "背景"
 | 
					  banner: "背景"
 | 
				
			||||||
| 
						 | 
					@ -1029,6 +1054,7 @@ desktop/views/components/ui.header.vue:
 | 
				
			||||||
desktop/views/components/ui.header.account.vue:
 | 
					desktop/views/components/ui.header.account.vue:
 | 
				
			||||||
  profile: "个人资料"
 | 
					  profile: "个人资料"
 | 
				
			||||||
  lists: "列表"
 | 
					  lists: "列表"
 | 
				
			||||||
 | 
					  groups: "群组"
 | 
				
			||||||
  follow-requests: "关注申请"
 | 
					  follow-requests: "关注申请"
 | 
				
			||||||
  admin: "管理"
 | 
					  admin: "管理"
 | 
				
			||||||
desktop/views/components/ui.header.nav.vue:
 | 
					desktop/views/components/ui.header.nav.vue:
 | 
				
			||||||
| 
						 | 
					@ -1039,12 +1065,6 @@ desktop/views/components/ui.header.post.vue:
 | 
				
			||||||
  post: "撰写新帖子"
 | 
					  post: "撰写新帖子"
 | 
				
			||||||
desktop/views/components/ui.header.search.vue:
 | 
					desktop/views/components/ui.header.search.vue:
 | 
				
			||||||
  placeholder: "搜索"
 | 
					  placeholder: "搜索"
 | 
				
			||||||
desktop/views/components/received-follow-requests-window.vue:
 | 
					 | 
				
			||||||
  title: "关注申请"
 | 
					 | 
				
			||||||
  accept: "接收"
 | 
					 | 
				
			||||||
  reject: "拒绝"
 | 
					 | 
				
			||||||
desktop/views/components/user-lists-window.vue:
 | 
					 | 
				
			||||||
  title: "用户列表"
 | 
					 | 
				
			||||||
desktop/views/components/user-preview.vue:
 | 
					desktop/views/components/user-preview.vue:
 | 
				
			||||||
  notes: "帖子"
 | 
					  notes: "帖子"
 | 
				
			||||||
  following: "关注中"
 | 
					  following: "关注中"
 | 
				
			||||||
| 
						 | 
					@ -1128,8 +1148,9 @@ admin/views/instance.vue:
 | 
				
			||||||
  recaptcha-config: "reCAPTCHA设置"
 | 
					  recaptcha-config: "reCAPTCHA设置"
 | 
				
			||||||
  recaptcha-info: "reCAPTCHA token是必要的. 请从 https://www.google.com/recaptcha/intro/ 获取。\n请注意, 该功能在中国大陆不可用。"
 | 
					  recaptcha-info: "reCAPTCHA token是必要的. 请从 https://www.google.com/recaptcha/intro/ 获取。\n请注意, 该功能在中国大陆不可用。"
 | 
				
			||||||
  enable-recaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)"
 | 
					  enable-recaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)"
 | 
				
			||||||
  recaptcha-site-key: "reCAPTCHA site key"
 | 
					  recaptcha-site-key: "网站密钥"
 | 
				
			||||||
  recaptcha-secret-key: "reCAPTCHA secret key"
 | 
					  recaptcha-secret-key: "密钥"
 | 
				
			||||||
 | 
					  recaptcha-preview: "预览"
 | 
				
			||||||
  hidden-tags: "隐藏哈希标签"
 | 
					  hidden-tags: "隐藏哈希标签"
 | 
				
			||||||
  hidden-tags-info: "使用换行符分隔要从集合中排除的哈希标签。"
 | 
					  hidden-tags-info: "使用换行符分隔要从集合中排除的哈希标签。"
 | 
				
			||||||
  external-service-integration-config: "连接外部服务"
 | 
					  external-service-integration-config: "连接外部服务"
 | 
				
			||||||
| 
						 | 
					@ -1532,6 +1553,7 @@ mobile/views/components/ui.nav.vue:
 | 
				
			||||||
  follow-requests: "关注申请"
 | 
					  follow-requests: "关注申请"
 | 
				
			||||||
  search: "搜索"
 | 
					  search: "搜索"
 | 
				
			||||||
  user-lists: "列表"
 | 
					  user-lists: "列表"
 | 
				
			||||||
 | 
					  user-groups: "群组"
 | 
				
			||||||
  widgets: "小部件"
 | 
					  widgets: "小部件"
 | 
				
			||||||
  game: "游戏"
 | 
					  game: "游戏"
 | 
				
			||||||
  admin: "管理"
 | 
					  admin: "管理"
 | 
				
			||||||
| 
						 | 
					@ -1544,8 +1566,6 @@ mobile/views/pages/drive.vue:
 | 
				
			||||||
    rename-folder: "重命名文件夹"
 | 
					    rename-folder: "重命名文件夹"
 | 
				
			||||||
    move-folder: "移动此文件夹"
 | 
					    move-folder: "移动此文件夹"
 | 
				
			||||||
    delete-folder: "删除此文件夹"
 | 
					    delete-folder: "删除此文件夹"
 | 
				
			||||||
mobile/views/pages/user-lists.vue:
 | 
					 | 
				
			||||||
  title: "列表"
 | 
					 | 
				
			||||||
mobile/views/pages/signup.vue:
 | 
					mobile/views/pages/signup.vue:
 | 
				
			||||||
  lets-start: "您的账户现已准备就绪! 📦"
 | 
					  lets-start: "您的账户现已准备就绪! 📦"
 | 
				
			||||||
mobile/views/pages/followers.vue:
 | 
					mobile/views/pages/followers.vue:
 | 
				
			||||||
| 
						 | 
					@ -1570,10 +1590,6 @@ mobile/views/pages/widgets/activity.vue:
 | 
				
			||||||
  activity: "活动"
 | 
					  activity: "活动"
 | 
				
			||||||
mobile/views/pages/share.vue:
 | 
					mobile/views/pages/share.vue:
 | 
				
			||||||
  share-with: "共享{name}"
 | 
					  share-with: "共享{name}"
 | 
				
			||||||
mobile/views/pages/received-follow-requests.vue:
 | 
					 | 
				
			||||||
  title: "关注申请"
 | 
					 | 
				
			||||||
  accept: "批准"
 | 
					 | 
				
			||||||
  reject: "拒绝"
 | 
					 | 
				
			||||||
mobile/views/pages/note.vue:
 | 
					mobile/views/pages/note.vue:
 | 
				
			||||||
  title: "帖文"
 | 
					  title: "帖文"
 | 
				
			||||||
  prev: "上一个帖子"
 | 
					  prev: "上一个帖子"
 | 
				
			||||||
| 
						 | 
					@ -1679,6 +1695,10 @@ pages:
 | 
				
			||||||
  edit-this-page: "编辑此页面"
 | 
					  edit-this-page: "编辑此页面"
 | 
				
			||||||
  view-source: "查看源代码"
 | 
					  view-source: "查看源代码"
 | 
				
			||||||
  view-page: "查看页面"
 | 
					  view-page: "查看页面"
 | 
				
			||||||
 | 
					  like: "赞"
 | 
				
			||||||
 | 
					  unlike: "取消赞"
 | 
				
			||||||
 | 
					  liked-pages: "喜欢的页面"
 | 
				
			||||||
 | 
					  my-pages: "个人页面"
 | 
				
			||||||
  inspector: "检查器"
 | 
					  inspector: "检查器"
 | 
				
			||||||
  content: "页面内容"
 | 
					  content: "页面内容"
 | 
				
			||||||
  variables: "变量"
 | 
					  variables: "变量"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -86,3 +86,5 @@ admin/views/dashboard.vue:
 | 
				
			||||||
  drive: "雲端硬碟"
 | 
					  drive: "雲端硬碟"
 | 
				
			||||||
admin/views/charts.vue:
 | 
					admin/views/charts.vue:
 | 
				
			||||||
  drive: "雲端硬碟"
 | 
					  drive: "雲端硬碟"
 | 
				
			||||||
 | 
					pages:
 | 
				
			||||||
 | 
					  like: "贊"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								package.json
									
										
									
									
									
								
							| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	"name": "misskey",
 | 
						"name": "misskey",
 | 
				
			||||||
	"author": "syuilo <i@syuilo.com>",
 | 
						"author": "syuilo <i@syuilo.com>",
 | 
				
			||||||
	"version": "11.17.1",
 | 
						"version": "11.18.0",
 | 
				
			||||||
	"codename": "daybreak",
 | 
						"codename": "daybreak",
 | 
				
			||||||
	"repository": {
 | 
						"repository": {
 | 
				
			||||||
		"type": "git",
 | 
							"type": "git",
 | 
				
			||||||
| 
						 | 
					@ -97,7 +97,7 @@
 | 
				
			||||||
		"@types/websocket": "0.0.40",
 | 
							"@types/websocket": "0.0.40",
 | 
				
			||||||
		"@types/ws": "6.0.1",
 | 
							"@types/ws": "6.0.1",
 | 
				
			||||||
		"animejs": "3.0.1",
 | 
							"animejs": "3.0.1",
 | 
				
			||||||
		"apexcharts": "3.6.11",
 | 
							"apexcharts": "3.6.12",
 | 
				
			||||||
		"autobind-decorator": "2.4.0",
 | 
							"autobind-decorator": "2.4.0",
 | 
				
			||||||
		"autosize": "4.0.2",
 | 
							"autosize": "4.0.2",
 | 
				
			||||||
		"autwh": "0.1.0",
 | 
							"autwh": "0.1.0",
 | 
				
			||||||
| 
						 | 
					@ -107,7 +107,7 @@
 | 
				
			||||||
		"cafy": "15.1.1",
 | 
							"cafy": "15.1.1",
 | 
				
			||||||
		"chai": "4.2.0",
 | 
							"chai": "4.2.0",
 | 
				
			||||||
		"chalk": "2.4.2",
 | 
							"chalk": "2.4.2",
 | 
				
			||||||
		"cli-highlight": "2.1.0",
 | 
							"cli-highlight": "2.1.1",
 | 
				
			||||||
		"commander": "2.20.0",
 | 
							"commander": "2.20.0",
 | 
				
			||||||
		"content-disposition": "0.5.3",
 | 
							"content-disposition": "0.5.3",
 | 
				
			||||||
		"crc-32": "1.2.0",
 | 
							"crc-32": "1.2.0",
 | 
				
			||||||
| 
						 | 
					@ -215,7 +215,7 @@
 | 
				
			||||||
		"summaly": "2.2.0",
 | 
							"summaly": "2.2.0",
 | 
				
			||||||
		"systeminformation": "4.1.6",
 | 
							"systeminformation": "4.1.6",
 | 
				
			||||||
		"syuilo-password-strength": "0.0.1",
 | 
							"syuilo-password-strength": "0.0.1",
 | 
				
			||||||
		"terser-webpack-plugin": "1.2.3",
 | 
							"terser-webpack-plugin": "1.2.4",
 | 
				
			||||||
		"textarea-caret": "3.1.0",
 | 
							"textarea-caret": "3.1.0",
 | 
				
			||||||
		"tinycolor2": "1.4.1",
 | 
							"tinycolor2": "1.4.1",
 | 
				
			||||||
		"tmp": "0.1.0",
 | 
							"tmp": "0.1.0",
 | 
				
			||||||
| 
						 | 
					@ -248,12 +248,12 @@
 | 
				
			||||||
		"vue-style-loader": "4.1.2",
 | 
							"vue-style-loader": "4.1.2",
 | 
				
			||||||
		"vue-svg-inline-loader": "1.2.15",
 | 
							"vue-svg-inline-loader": "1.2.15",
 | 
				
			||||||
		"vue-template-compiler": "2.6.10",
 | 
							"vue-template-compiler": "2.6.10",
 | 
				
			||||||
		"vuedraggable": "2.20.0",
 | 
							"vuedraggable": "2.21.0",
 | 
				
			||||||
		"vuewordcloud": "18.7.11",
 | 
							"vuewordcloud": "18.7.11",
 | 
				
			||||||
		"vuex": "3.1.1",
 | 
							"vuex": "3.1.1",
 | 
				
			||||||
		"vuex-persistedstate": "2.5.4",
 | 
							"vuex-persistedstate": "2.5.4",
 | 
				
			||||||
		"web-push": "3.3.4",
 | 
							"web-push": "3.3.5",
 | 
				
			||||||
		"webpack": "4.31.0",
 | 
							"webpack": "4.32.0",
 | 
				
			||||||
		"webpack-cli": "3.3.2",
 | 
							"webpack-cli": "3.3.2",
 | 
				
			||||||
		"websocket": "1.0.28",
 | 
							"websocket": "1.0.28",
 | 
				
			||||||
		"ws": "7.0.0",
 | 
							"ws": "7.0.0",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -144,8 +144,6 @@ export default prop => ({
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$emit(`update:${prop}`, this.$_ns_note_);
 | 
					 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										171
									
								
								src/client/app/common/scripts/paging.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								src/client/app/common/scripts/paging.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,171 @@
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default (opts) => ({
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								items: [],
 | 
				
			||||||
 | 
								queue: [],
 | 
				
			||||||
 | 
								fetching: true,
 | 
				
			||||||
 | 
								moreFetching: false,
 | 
				
			||||||
 | 
								inited: false,
 | 
				
			||||||
 | 
								more: false
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						computed: {
 | 
				
			||||||
 | 
							empty(): boolean {
 | 
				
			||||||
 | 
								return this.items.length == 0 && !this.fetching && this.inited;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							error(): boolean {
 | 
				
			||||||
 | 
								return !this.fetching && !this.inited;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						watch: {
 | 
				
			||||||
 | 
							queue(x) {
 | 
				
			||||||
 | 
								if (opts.onQueueChanged) opts.onQueueChanged(this, x);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pagination() {
 | 
				
			||||||
 | 
								this.init();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						created() {
 | 
				
			||||||
 | 
							opts.displayLimit = opts.displayLimit || 30;
 | 
				
			||||||
 | 
							this.init();
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mounted() {
 | 
				
			||||||
 | 
							if (opts.captureWindowScroll) {
 | 
				
			||||||
 | 
								this.isScrollTop = () => {
 | 
				
			||||||
 | 
									return window.scrollY <= 8;
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								window.addEventListener('scroll', this.onWindowScroll, { passive: true });
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						beforeDestroy() {
 | 
				
			||||||
 | 
							if (opts.captureWindowScroll) {
 | 
				
			||||||
 | 
								window.removeEventListener('scroll', this.onWindowScroll);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							updateItem(i, item) {
 | 
				
			||||||
 | 
								Vue.set((this as any).items, i, item);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							reload() {
 | 
				
			||||||
 | 
								this.queue = [];
 | 
				
			||||||
 | 
								this.items = [];
 | 
				
			||||||
 | 
								this.init();
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							async init() {
 | 
				
			||||||
 | 
								this.fetching = true;
 | 
				
			||||||
 | 
								let params = typeof this.pagination.params === 'function' ? this.pagination.params(true) : this.pagination.params;
 | 
				
			||||||
 | 
								if (params && params.then) params = await params;
 | 
				
			||||||
 | 
								await this.$root.api(this.pagination.endpoint, {
 | 
				
			||||||
 | 
									limit: (this.pagination.limit || 10) + 1,
 | 
				
			||||||
 | 
									...params
 | 
				
			||||||
 | 
								}).then(x => {
 | 
				
			||||||
 | 
									if (x.length == (this.pagination.limit || 10) + 1) {
 | 
				
			||||||
 | 
										x.pop();
 | 
				
			||||||
 | 
										this.items = x;
 | 
				
			||||||
 | 
										this.more = true;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										this.items = x;
 | 
				
			||||||
 | 
										this.more = false;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									this.inited = true;
 | 
				
			||||||
 | 
									this.fetching = false;
 | 
				
			||||||
 | 
									if (opts.onInited) opts.onInited(this);
 | 
				
			||||||
 | 
								}, e => {
 | 
				
			||||||
 | 
									this.fetching = false;
 | 
				
			||||||
 | 
									if (opts.onInited) opts.onInited(this);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							async fetchMore() {
 | 
				
			||||||
 | 
								if (!this.more || this.moreFetching || this.items.length === 0) return;
 | 
				
			||||||
 | 
								this.moreFetching = true;
 | 
				
			||||||
 | 
								let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params;
 | 
				
			||||||
 | 
								if (params && params.then) params = await params;
 | 
				
			||||||
 | 
								await this.$root.api(this.pagination.endpoint, {
 | 
				
			||||||
 | 
									limit: (this.pagination.limit || 10) + 1,
 | 
				
			||||||
 | 
									untilId: this.items[this.items.length - 1].id,
 | 
				
			||||||
 | 
									...params
 | 
				
			||||||
 | 
								}).then(x => {
 | 
				
			||||||
 | 
									if (x.length == (this.pagination.limit || 10) + 1) {
 | 
				
			||||||
 | 
										x.pop();
 | 
				
			||||||
 | 
										this.items = this.items.concat(x);
 | 
				
			||||||
 | 
										this.more = true;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										this.items = this.items.concat(x);
 | 
				
			||||||
 | 
										this.more = false;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									this.moreFetching = false;
 | 
				
			||||||
 | 
								}, e => {
 | 
				
			||||||
 | 
									this.moreFetching = false;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							prepend(item, silent = false) {
 | 
				
			||||||
 | 
								if (opts.onPrepend) {
 | 
				
			||||||
 | 
									const cancel = opts.onPrepend(this, item, silent);
 | 
				
			||||||
 | 
									if (cancel) return;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (this.isScrollTop == null || this.isScrollTop()) {
 | 
				
			||||||
 | 
									// Prepend the item
 | 
				
			||||||
 | 
									this.items.unshift(item);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// オーバーフローしたら古い投稿は捨てる
 | 
				
			||||||
 | 
									if (this.items.length >= opts.displayLimit) {
 | 
				
			||||||
 | 
										this.items = this.items.slice(0, opts.displayLimit);
 | 
				
			||||||
 | 
										this.more = true;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									this.queue.push(item);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							append(item) {
 | 
				
			||||||
 | 
								this.items.push(item);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							releaseQueue() {
 | 
				
			||||||
 | 
								for (const n of this.queue) {
 | 
				
			||||||
 | 
									this.prepend(n, true);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								this.queue = [];
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							onWindowScroll() {
 | 
				
			||||||
 | 
								if (this.isScrollTop()) {
 | 
				
			||||||
 | 
									this.onTop();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (this.$store.state.settings.fetchOnScroll) {
 | 
				
			||||||
 | 
									// 親要素が display none だったら弾く
 | 
				
			||||||
 | 
									// https://github.com/syuilo/misskey/issues/1569
 | 
				
			||||||
 | 
									// http://d.hatena.ne.jp/favril/20091105/1257403319
 | 
				
			||||||
 | 
									if (this.$el.offsetHeight == 0) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									const current = window.scrollY + window.innerHeight;
 | 
				
			||||||
 | 
									if (current > document.body.offsetHeight - 8) this.onBottom();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							onTop() {
 | 
				
			||||||
 | 
								this.releaseQueue();
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							onBottom() {
 | 
				
			||||||
 | 
								this.fetchMore();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										27
									
								
								src/client/app/common/views/components/avatars.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/client/app/common/views/components/avatars.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,27 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
						<mk-avatar v-for="user in us" :user="user" :key="user.id" style="width:32px;height:32px;"/>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							userIds: {
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								us: []
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						async created() {
 | 
				
			||||||
 | 
							this.us = await this.$root.api('users/show', {
 | 
				
			||||||
 | 
								userIds: this.userIds
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,6 @@ export default Vue.extend({
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
.a
 | 
					.a
 | 
				
			||||||
	display block
 | 
						display block
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,6 @@ import avatar from './avatar.vue';
 | 
				
			||||||
import nav from './nav.vue';
 | 
					import nav from './nav.vue';
 | 
				
			||||||
import misskeyFlavoredMarkdown from './misskey-flavored-markdown.vue';
 | 
					import misskeyFlavoredMarkdown from './misskey-flavored-markdown.vue';
 | 
				
			||||||
import poll from './poll.vue';
 | 
					import poll from './poll.vue';
 | 
				
			||||||
import pollEditor from './poll-editor.vue';
 | 
					 | 
				
			||||||
import reactionIcon from './reaction-icon.vue';
 | 
					import reactionIcon from './reaction-icon.vue';
 | 
				
			||||||
import reactionsViewer from './reactions-viewer.vue';
 | 
					import reactionsViewer from './reactions-viewer.vue';
 | 
				
			||||||
import time from './time.vue';
 | 
					import time from './time.vue';
 | 
				
			||||||
| 
						 | 
					@ -46,6 +45,7 @@ import uiSelect from './ui/select.vue';
 | 
				
			||||||
import uiInfo from './ui/info.vue';
 | 
					import uiInfo from './ui/info.vue';
 | 
				
			||||||
import uiMargin from './ui/margin.vue';
 | 
					import uiMargin from './ui/margin.vue';
 | 
				
			||||||
import uiHr from './ui/hr.vue';
 | 
					import uiHr from './ui/hr.vue';
 | 
				
			||||||
 | 
					import uiPagination from './ui/pagination.vue';
 | 
				
			||||||
import formButton from './ui/form/button.vue';
 | 
					import formButton from './ui/form/button.vue';
 | 
				
			||||||
import formRadio from './ui/form/radio.vue';
 | 
					import formRadio from './ui/form/radio.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,7 +70,6 @@ Vue.component('mk-acct', acct);
 | 
				
			||||||
Vue.component('mk-avatar', avatar);
 | 
					Vue.component('mk-avatar', avatar);
 | 
				
			||||||
Vue.component('mk-nav', nav);
 | 
					Vue.component('mk-nav', nav);
 | 
				
			||||||
Vue.component('mk-poll', poll);
 | 
					Vue.component('mk-poll', poll);
 | 
				
			||||||
Vue.component('mk-poll-editor', pollEditor);
 | 
					 | 
				
			||||||
Vue.component('mk-reaction-icon', reactionIcon);
 | 
					Vue.component('mk-reaction-icon', reactionIcon);
 | 
				
			||||||
Vue.component('mk-reactions-viewer', reactionsViewer);
 | 
					Vue.component('mk-reactions-viewer', reactionsViewer);
 | 
				
			||||||
Vue.component('mk-time', time);
 | 
					Vue.component('mk-time', time);
 | 
				
			||||||
| 
						 | 
					@ -95,5 +94,6 @@ Vue.component('ui-select', uiSelect);
 | 
				
			||||||
Vue.component('ui-info', uiInfo);
 | 
					Vue.component('ui-info', uiInfo);
 | 
				
			||||||
Vue.component('ui-margin', uiMargin);
 | 
					Vue.component('ui-margin', uiMargin);
 | 
				
			||||||
Vue.component('ui-hr', uiHr);
 | 
					Vue.component('ui-hr', uiHr);
 | 
				
			||||||
 | 
					Vue.component('ui-pagination', uiPagination);
 | 
				
			||||||
Vue.component('form-button', formButton);
 | 
					Vue.component('form-button', formButton);
 | 
				
			||||||
Vue.component('form-radio', formRadio);
 | 
					Vue.component('form-radio', formRadio);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,42 +21,23 @@
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<div class="history" v-if="messages.length > 0">
 | 
						<div class="history" v-if="messages.length > 0">
 | 
				
			||||||
		<div class="title">{{ $t('user') }}</div>
 | 
					 | 
				
			||||||
		<a v-for="message in messages"
 | 
							<a v-for="message in messages"
 | 
				
			||||||
			class="user"
 | 
								class="user"
 | 
				
			||||||
			:href="`/i/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
 | 
								:href="message.groupId ? `/i/messaging/group/${message.groupId}` : `/i/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
 | 
				
			||||||
			:data-is-me="isMe(message)"
 | 
								:data-is-me="isMe(message)"
 | 
				
			||||||
			:data-is-read="message.isRead"
 | 
								:data-is-read="message.groupId ? message.reads.includes($store.state.i.id) : message.isRead"
 | 
				
			||||||
			@click.prevent="navigate(isMe(message) ? message.recipient : message.user)"
 | 
								@click.prevent="message.groupId ? navigateGroup(message.group) : navigate(isMe(message) ? message.recipient : message.user)"
 | 
				
			||||||
			:key="message.id"
 | 
								:key="message.id"
 | 
				
			||||||
		>
 | 
							>
 | 
				
			||||||
			<div>
 | 
								<div>
 | 
				
			||||||
				<mk-avatar class="avatar" :user="isMe(message) ? message.recipient : message.user"/>
 | 
									<mk-avatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user"/>
 | 
				
			||||||
				<header>
 | 
									<header v-if="message.groupId">
 | 
				
			||||||
					<span class="name"><mk-user-name :user="isMe(message) ? message.recipient : message.user"/></span>
 | 
										<span class="name">{{ message.group.name }}</span>
 | 
				
			||||||
					<span class="username">@{{ isMe(message) ? message.recipient : message.user | acct }}</span>
 | 
					 | 
				
			||||||
					<mk-time :time="message.createdAt"/>
 | 
										<mk-time :time="message.createdAt"/>
 | 
				
			||||||
				</header>
 | 
									</header>
 | 
				
			||||||
				<div class="body">
 | 
									<header v-else>
 | 
				
			||||||
					<p class="text"><span class="me" v-if="isMe(message)">{{ $t('you') }}:</span>{{ message.text }}</p>
 | 
										<span class="name"><mk-user-name :user="isMe(message) ? message.recipient : message.user"/></span>
 | 
				
			||||||
				</div>
 | 
										<span class="username">@{{ isMe(message) ? message.recipient : message.user | acct }}</span>
 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
		</a>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
	<div class="history" v-if="groupMessages.length > 0">
 | 
					 | 
				
			||||||
		<div class="title">{{ $t('group') }}</div>
 | 
					 | 
				
			||||||
		<a v-for="message in groupMessages"
 | 
					 | 
				
			||||||
			class="user"
 | 
					 | 
				
			||||||
			:href="`/i/messaging/group/${message.groupId}`"
 | 
					 | 
				
			||||||
			:data-is-me="isMe(message)"
 | 
					 | 
				
			||||||
			:data-is-read="message.reads.includes($store.state.i.id)"
 | 
					 | 
				
			||||||
			@click.prevent="navigateGroup(message.group)"
 | 
					 | 
				
			||||||
			:key="message.id"
 | 
					 | 
				
			||||||
		>
 | 
					 | 
				
			||||||
			<div>
 | 
					 | 
				
			||||||
				<mk-avatar class="avatar" :user="message.user"/>
 | 
					 | 
				
			||||||
				<header>
 | 
					 | 
				
			||||||
					<span class="name">{{ message.group.name }}</span>
 | 
					 | 
				
			||||||
					<mk-time :time="message.createdAt"/>
 | 
										<mk-time :time="message.createdAt"/>
 | 
				
			||||||
				</header>
 | 
									</header>
 | 
				
			||||||
				<div class="body">
 | 
									<div class="body">
 | 
				
			||||||
| 
						 | 
					@ -97,7 +78,6 @@ export default Vue.extend({
 | 
				
			||||||
			fetching: true,
 | 
								fetching: true,
 | 
				
			||||||
			moreFetching: false,
 | 
								moreFetching: false,
 | 
				
			||||||
			messages: [],
 | 
								messages: [],
 | 
				
			||||||
			groupMessages: [],
 | 
					 | 
				
			||||||
			q: null,
 | 
								q: null,
 | 
				
			||||||
			result: [],
 | 
								result: [],
 | 
				
			||||||
			connection: null,
 | 
								connection: null,
 | 
				
			||||||
| 
						 | 
					@ -110,10 +90,11 @@ export default Vue.extend({
 | 
				
			||||||
		this.connection.on('message', this.onMessage);
 | 
							this.connection.on('message', this.onMessage);
 | 
				
			||||||
		this.connection.on('read', this.onRead);
 | 
							this.connection.on('read', this.onRead);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.$root.api('messaging/history', { group: false }).then(messages => {
 | 
							this.$root.api('messaging/history', { group: false }).then(userMessages => {
 | 
				
			||||||
			this.$root.api('messaging/history', { group: true }).then(groupMessages => {
 | 
								this.$root.api('messaging/history', { group: true }).then(groupMessages => {
 | 
				
			||||||
 | 
									const messages = userMessages.concat(groupMessages);
 | 
				
			||||||
 | 
									messages.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
 | 
				
			||||||
				this.messages = messages;
 | 
									this.messages = messages;
 | 
				
			||||||
				this.groupMessages = groupMessages;
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
									this.fetching = false;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
| 
						 | 
					@ -134,8 +115,8 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.messages.unshift(message);
 | 
									this.messages.unshift(message);
 | 
				
			||||||
			} else if (message.groupId) {
 | 
								} else if (message.groupId) {
 | 
				
			||||||
				this.groupMessages = this.groupMessages.filter(m => m.groupId !== message.groupId);
 | 
									this.messages = this.messages.filter(m => m.groupId !== message.groupId);
 | 
				
			||||||
				this.groupMessages.unshift(message);
 | 
									this.messages.unshift(message);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		onRead(ids) {
 | 
							onRead(ids) {
 | 
				
			||||||
| 
						 | 
					@ -243,9 +224,6 @@ export default Vue.extend({
 | 
				
			||||||
		font-size 0.8em
 | 
							font-size 0.8em
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> .history
 | 
							> .history
 | 
				
			||||||
			> .title
 | 
					 | 
				
			||||||
				padding 8px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			> a
 | 
								> a
 | 
				
			||||||
				&:last-child
 | 
									&:last-child
 | 
				
			||||||
					border-bottom none
 | 
										border-bottom none
 | 
				
			||||||
| 
						 | 
					@ -384,14 +362,6 @@ export default Vue.extend({
 | 
				
			||||||
						color rgba(#000, 0.3)
 | 
											color rgba(#000, 0.3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> .history
 | 
						> .history
 | 
				
			||||||
		> .title
 | 
					 | 
				
			||||||
			padding 6px 16px
 | 
					 | 
				
			||||||
			margin 0 auto
 | 
					 | 
				
			||||||
			max-width 500px
 | 
					 | 
				
			||||||
			background rgba(0, 0, 0, 0.05)
 | 
					 | 
				
			||||||
			color var(--text)
 | 
					 | 
				
			||||||
			font-size 85%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> a
 | 
							> a
 | 
				
			||||||
			display block
 | 
								display block
 | 
				
			||||||
			text-decoration none
 | 
								text-decoration none
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="mk-poll-editor">
 | 
					<div class="zmdxowus">
 | 
				
			||||||
	<p class="caution" v-if="choices.length < 2">
 | 
						<p class="caution" v-if="choices.length < 2">
 | 
				
			||||||
		<fa icon="exclamation-triangle"/>{{ $t('no-only-one-choice') }}
 | 
							<fa icon="exclamation-triangle"/>{{ $t('no-only-one-choice') }}
 | 
				
			||||||
	</p>
 | 
						</p>
 | 
				
			||||||
| 
						 | 
					@ -134,7 +134,7 @@ export default Vue.extend({
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
.mk-poll-editor
 | 
					.zmdxowus
 | 
				
			||||||
	padding 8px
 | 
						padding 8px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> .caution
 | 
						> .caution
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										36
									
								
								src/client/app/common/views/components/ui/pagination.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/client/app/common/views/components/ui/pagination.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div class="mwermpua" v-if="!fetching">
 | 
				
			||||||
 | 
						<sequential-entrance animation="entranceFromTop" delay="25">
 | 
				
			||||||
 | 
							<slot :items="items"></slot>
 | 
				
			||||||
 | 
						</sequential-entrance>
 | 
				
			||||||
 | 
						<div class="more" v-if="more">
 | 
				
			||||||
 | 
							<ui-button @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import paging from '../../../scripts/paging';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						mixins: [
 | 
				
			||||||
 | 
							paging({
 | 
				
			||||||
 | 
								captureWindowScroll: false,
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							pagination: {
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					.mwermpua
 | 
				
			||||||
 | 
						> .more
 | 
				
			||||||
 | 
							margin-top 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -2,13 +2,13 @@
 | 
				
			||||||
<ui-container :body-togglable="true">
 | 
					<ui-container :body-togglable="true">
 | 
				
			||||||
	<template #header><slot></slot></template>
 | 
						<template #header><slot></slot></template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<mk-error v-if="!fetching && !inited" @retry="init()"/>
 | 
						<mk-error v-if="error" @retry="init()"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="efvhhmdq" :class="{ iconOnly }" v-size="[{ lt: 500, class: 'narrow' }]">
 | 
						<div class="efvhhmdq" :class="{ iconOnly }" v-size="[{ lt: 500, class: 'narrow' }]">
 | 
				
			||||||
		<div class="no-users" v-if="inited && us.length == 0">
 | 
							<div class="no-users" v-if="empty">
 | 
				
			||||||
			<p>{{ $t('no-users') }}</p>
 | 
								<p>{{ $t('no-users') }}</p>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div class="user" v-for="user in us" :key="user.id">
 | 
							<div class="user" v-for="user in users" :key="user.id">
 | 
				
			||||||
			<mk-avatar class="avatar" :user="user"/>
 | 
								<mk-avatar class="avatar" :user="user"/>
 | 
				
			||||||
			<div class="body" v-if="!iconOnly">
 | 
								<div class="body" v-if="!iconOnly">
 | 
				
			||||||
				<div class="name">
 | 
									<div class="name">
 | 
				
			||||||
| 
						 | 
					@ -21,8 +21,8 @@
 | 
				
			||||||
				<mk-follow-button class="follow-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/>
 | 
									<mk-follow-button class="follow-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<button class="more" :class="{ fetching: fetchingMoreUsers }" v-if="cursor != null" @click="fetchMoreUsers()" :disabled="fetchingMoreUsers">
 | 
							<button class="more" :class="{ fetching: moreFetching }" v-if="more" @click="fetchMoreUsers()" :disabled="moreFetching">
 | 
				
			||||||
			<template v-if="fetchingMoreUsers"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreUsers ? $t('@.loading') : $t('@.load-more') }}
 | 
								<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>{{ moreFetching ? $t('@.loading') : $t('@.load-more') }}
 | 
				
			||||||
		</button>
 | 
							</button>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</ui-container>
 | 
					</ui-container>
 | 
				
			||||||
| 
						 | 
					@ -31,60 +31,31 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					import paging from '../../../common/scripts/paging';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('common/views/components/user-list.vue'),
 | 
						i18n: i18n('common/views/components/user-list.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mixins: [
 | 
				
			||||||
 | 
							paging({}),
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
		makePromise: {
 | 
							pagination: {
 | 
				
			||||||
			required: true
 | 
								required: true
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							extract: {
 | 
				
			||||||
 | 
								required: false
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		iconOnly: {
 | 
							iconOnly: {
 | 
				
			||||||
			type: Boolean,
 | 
								type: Boolean,
 | 
				
			||||||
			default: false
 | 
								default: false
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						computed: {
 | 
				
			||||||
		return {
 | 
							users() {
 | 
				
			||||||
			fetching: true,
 | 
								return this.extract ? this.extract(this.items) : this.items;
 | 
				
			||||||
			fetchingMoreUsers: false,
 | 
					 | 
				
			||||||
			us: [],
 | 
					 | 
				
			||||||
			inited: false,
 | 
					 | 
				
			||||||
			more: false
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	created() {
 | 
					 | 
				
			||||||
		this.init();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		async init() {
 | 
					 | 
				
			||||||
			this.fetching = true;
 | 
					 | 
				
			||||||
			await (this.makePromise()).then(x => {
 | 
					 | 
				
			||||||
				if (Array.isArray(x)) {
 | 
					 | 
				
			||||||
					this.us = x;
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					this.us = x.users;
 | 
					 | 
				
			||||||
					this.cursor = x.cursor;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				this.inited = true;
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
			}, e => {
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		async fetchMoreUsers() {
 | 
					 | 
				
			||||||
			this.fetchingMoreUsers = true;
 | 
					 | 
				
			||||||
			await (this.makePromise(this.cursor)).then(x => {
 | 
					 | 
				
			||||||
				this.us = this.us.concat(x.users);
 | 
					 | 
				
			||||||
				this.cursor = x.cursor;
 | 
					 | 
				
			||||||
				this.fetchingMoreUsers = false;
 | 
					 | 
				
			||||||
			}, e => {
 | 
					 | 
				
			||||||
				this.fetchingMoreUsers = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
<x-column :name="name" :column="column" :is-stacked="isStacked">
 | 
					<x-column :name="name" :column="column" :is-stacked="isStacked">
 | 
				
			||||||
	<template #header><fa :icon="['far', 'envelope']"/>{{ name }}</template>
 | 
						<template #header><fa :icon="['far', 'envelope']"/>{{ name }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<x-direct/>
 | 
						<x-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
 | 
				
			||||||
</x-column>
 | 
					</x-column>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,13 +10,14 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import XColumn from './deck.column.vue';
 | 
					import XColumn from './deck.column.vue';
 | 
				
			||||||
import XDirect from './deck.direct.vue';
 | 
					import XNotes from './deck.notes.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n(),
 | 
						i18n: i18n(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XColumn,
 | 
							XColumn,
 | 
				
			||||||
		XDirect
 | 
							XNotes
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
| 
						 | 
					@ -30,6 +31,22 @@ export default Vue.extend({
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								connection: null,
 | 
				
			||||||
 | 
								pagination: {
 | 
				
			||||||
 | 
									endpoint: 'notes/mentions',
 | 
				
			||||||
 | 
									limit: 10,
 | 
				
			||||||
 | 
									params: {
 | 
				
			||||||
 | 
										includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
				
			||||||
 | 
										includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
				
			||||||
 | 
										includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
 | 
				
			||||||
 | 
										visibility: 'specified'
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		name(): string {
 | 
							name(): string {
 | 
				
			||||||
			if (this.column.name) return this.column.name;
 | 
								if (this.column.name) return this.column.name;
 | 
				
			||||||
| 
						 | 
					@ -37,9 +54,25 @@ export default Vue.extend({
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mounted() {
 | 
				
			||||||
 | 
							this.connection = this.$root.stream.useSharedConnection('main');
 | 
				
			||||||
 | 
							this.connection.on('mention', this.onNote);
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						beforeDestroy() {
 | 
				
			||||||
 | 
							this.connection.dispose();
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
 | 
							onNote(note) {
 | 
				
			||||||
 | 
								// Prepend a note
 | 
				
			||||||
 | 
								if (note.visibility == 'specified') {
 | 
				
			||||||
 | 
									(this.$refs.timeline as any).prepend(note);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		focus() {
 | 
							focus() {
 | 
				
			||||||
			this.$refs.tl.focus();
 | 
								this.$refs.timeline.focus();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,65 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import XNotes from './deck.notes.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const fetchLimit = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	components: {
 | 
					 | 
				
			||||||
		XNotes
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			connection: null,
 | 
					 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/mentions', {
 | 
					 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
					 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
					 | 
				
			||||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
					 | 
				
			||||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
					 | 
				
			||||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
 | 
					 | 
				
			||||||
				visibility: 'specified'
 | 
					 | 
				
			||||||
			}).then(notes => {
 | 
					 | 
				
			||||||
				if (notes.length == fetchLimit + 1) {
 | 
					 | 
				
			||||||
					notes.pop();
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: true
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	mounted() {
 | 
					 | 
				
			||||||
		this.connection = this.$root.stream.useSharedConnection('main');
 | 
					 | 
				
			||||||
		this.connection.on('mention', this.onNote);
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	beforeDestroy() {
 | 
					 | 
				
			||||||
		this.connection.dispose();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		onNote(note) {
 | 
					 | 
				
			||||||
			// Prepend a note
 | 
					 | 
				
			||||||
			if (note.visibility == 'specified') {
 | 
					 | 
				
			||||||
				(this.$refs.timeline as any).prepend(note);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		focus() {
 | 
					 | 
				
			||||||
			this.$refs.timeline.focus();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,58 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<x-column>
 | 
					 | 
				
			||||||
	<template #header>
 | 
					 | 
				
			||||||
		<fa :icon="['fa', 'star']"/>{{ $t('@.favorites') }}
 | 
					 | 
				
			||||||
	</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<div>
 | 
					 | 
				
			||||||
		<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
</x-column>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
import XColumn from './deck.column.vue';
 | 
					 | 
				
			||||||
import XNotes from './deck.notes.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const fetchLimit = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n(),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	components: {
 | 
					 | 
				
			||||||
		XColumn,
 | 
					 | 
				
			||||||
		XNotes,
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			makePromise: cursor => this.$root.api('i/favorites', {
 | 
					 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
					 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
					 | 
				
			||||||
			}).then(notes => {
 | 
					 | 
				
			||||||
				notes = notes.map(x => x.note);
 | 
					 | 
				
			||||||
				if (notes.length == fetchLimit + 1) {
 | 
					 | 
				
			||||||
					notes.pop();
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: true
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		focus() {
 | 
					 | 
				
			||||||
			this.$refs.timeline.focus();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,46 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<x-column>
 | 
					 | 
				
			||||||
	<template #header>
 | 
					 | 
				
			||||||
		<fa :icon="faNewspaper"/>{{ $t('@.featured-notes') }}
 | 
					 | 
				
			||||||
	</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<div>
 | 
					 | 
				
			||||||
		<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
</x-column>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
import XColumn from './deck.column.vue';
 | 
					 | 
				
			||||||
import XNotes from './deck.notes.vue';
 | 
					 | 
				
			||||||
import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n(),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	components: {
 | 
					 | 
				
			||||||
		XColumn,
 | 
					 | 
				
			||||||
		XNotes,
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			faNewspaper,
 | 
					 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/featured', {
 | 
					 | 
				
			||||||
				limit: 30,
 | 
					 | 
				
			||||||
			}).then(notes => {
 | 
					 | 
				
			||||||
				notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
 | 
					 | 
				
			||||||
				return notes;
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		focus() {
 | 
					 | 
				
			||||||
			this.$refs.timeline.focus();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,11 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
 | 
					<x-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import XNotes from './deck.notes.vue';
 | 
					import XNotes from './deck.notes.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fetchLimit = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XNotes
 | 
							XNotes
 | 
				
			||||||
| 
						 | 
					@ -28,28 +26,18 @@ export default Vue.extend({
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			connection: null,
 | 
								connection: null,
 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/search-by-tag', {
 | 
								pagination: {
 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
									endpoint: 'notes/search-by-tag',
 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
									limit: 10,
 | 
				
			||||||
				withFiles: this.mediaOnly,
 | 
									params: init => ({
 | 
				
			||||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
										untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
										withFiles: this.mediaOnly,
 | 
				
			||||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
 | 
										includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
				
			||||||
				query: this.tagTl.query
 | 
										includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
				
			||||||
			}).then(notes => {
 | 
										includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
 | 
				
			||||||
				if (notes.length == fetchLimit + 1) {
 | 
										query: this.tagTl.query
 | 
				
			||||||
					notes.pop();
 | 
									})
 | 
				
			||||||
					return {
 | 
								}
 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: true
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,11 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
 | 
					<x-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import XNotes from './deck.notes.vue';
 | 
					import XNotes from './deck.notes.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fetchLimit = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XNotes
 | 
							XNotes
 | 
				
			||||||
| 
						 | 
					@ -28,28 +26,18 @@ export default Vue.extend({
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			connection: null,
 | 
								connection: null,
 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/user-list-timeline', {
 | 
								pagination: {
 | 
				
			||||||
				listId: this.list.id,
 | 
									endpoint: 'notes/user-list-timeline',
 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
									limit: 10,
 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
									params: init => ({
 | 
				
			||||||
				withFiles: this.mediaOnly,
 | 
										listId: this.list.id,
 | 
				
			||||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
										untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
										withFiles: this.mediaOnly,
 | 
				
			||||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
										includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
				
			||||||
			}).then(notes => {
 | 
										includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
				
			||||||
				if (notes.length == fetchLimit + 1) {
 | 
										includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
				
			||||||
					notes.pop();
 | 
									})
 | 
				
			||||||
					return {
 | 
								}
 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: true
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
<x-column :name="name" :column="column" :is-stacked="isStacked">
 | 
					<x-column :name="name" :column="column" :is-stacked="isStacked">
 | 
				
			||||||
	<template #header><fa icon="at"/>{{ name }}</template>
 | 
						<template #header><fa icon="at"/>{{ name }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<x-mentions ref="tl"/>
 | 
						<x-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
 | 
				
			||||||
</x-column>
 | 
					</x-column>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,13 +10,14 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import XColumn from './deck.column.vue';
 | 
					import XColumn from './deck.column.vue';
 | 
				
			||||||
import XMentions from './deck.mentions.vue';
 | 
					import XNotes from './deck.notes.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n(),
 | 
						i18n: i18n(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XColumn,
 | 
							XColumn,
 | 
				
			||||||
		XMentions
 | 
							XNotes
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
| 
						 | 
					@ -30,6 +31,22 @@ export default Vue.extend({
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								connection: null,
 | 
				
			||||||
 | 
								pagination: {
 | 
				
			||||||
 | 
									endpoint: 'notes/mentions',
 | 
				
			||||||
 | 
									limit: 10,
 | 
				
			||||||
 | 
									params: init => ({
 | 
				
			||||||
 | 
										untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
 | 
										includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
				
			||||||
 | 
										includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
				
			||||||
 | 
										includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		name(): string {
 | 
							name(): string {
 | 
				
			||||||
			if (this.column.name) return this.column.name;
 | 
								if (this.column.name) return this.column.name;
 | 
				
			||||||
| 
						 | 
					@ -37,9 +54,22 @@ export default Vue.extend({
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mounted() {
 | 
				
			||||||
 | 
							this.connection = this.$root.stream.useSharedConnection('main');
 | 
				
			||||||
 | 
							this.connection.on('mention', this.onNote);
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						beforeDestroy() {
 | 
				
			||||||
 | 
							this.connection.dispose();
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
 | 
							onNote(note) {
 | 
				
			||||||
 | 
								(this.$refs.timeline as any).prepend(note);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		focus() {
 | 
							focus() {
 | 
				
			||||||
			this.$refs.tl.focus();
 | 
								this.$refs.timeline.focus();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,61 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import XNotes from './deck.notes.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const fetchLimit = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	components: {
 | 
					 | 
				
			||||||
		XNotes
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			connection: null,
 | 
					 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/mentions', {
 | 
					 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
					 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
					 | 
				
			||||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
					 | 
				
			||||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
					 | 
				
			||||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
					 | 
				
			||||||
			}).then(notes => {
 | 
					 | 
				
			||||||
				if (notes.length == fetchLimit + 1) {
 | 
					 | 
				
			||||||
					notes.pop();
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: true
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	mounted() {
 | 
					 | 
				
			||||||
		this.connection = this.$root.stream.useSharedConnection('main');
 | 
					 | 
				
			||||||
		this.connection.on('mention', this.onNote);
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	beforeDestroy() {
 | 
					 | 
				
			||||||
		this.connection.dispose();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		onNote(note) {
 | 
					 | 
				
			||||||
			(this.$refs.timeline as any).prepend(note);
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		focus() {
 | 
					 | 
				
			||||||
			this.$refs.timeline.focus();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="eamppglmnmimdhrlzhplwpvyeaqmmhxu">
 | 
					<div class="eamppglmnmimdhrlzhplwpvyeaqmmhxu">
 | 
				
			||||||
	<div class="empty" v-if="notes.length == 0 && !fetching && inited">{{ $t('@.no-notes') }}</div>
 | 
						<div class="empty" v-if="empty">{{ $t('@.no-notes') }}</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<mk-error v-if="!fetching && !inited" @retry="init()"/>
 | 
						<mk-error v-if="error" @retry="init()"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="placeholder" v-if="fetching">
 | 
						<div class="placeholder" v-if="fetching">
 | 
				
			||||||
		<template v-for="i in 10">
 | 
							<template v-for="i in 10">
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,6 @@
 | 
				
			||||||
			<mk-note
 | 
								<mk-note
 | 
				
			||||||
				:note="note"
 | 
									:note="note"
 | 
				
			||||||
				:key="note.id"
 | 
									:key="note.id"
 | 
				
			||||||
				@update:note="onNoteUpdated(i, $event)"
 | 
					 | 
				
			||||||
				:compact="true"
 | 
									:compact="true"
 | 
				
			||||||
			/>
 | 
								/>
 | 
				
			||||||
			<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
 | 
								<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
 | 
				
			||||||
| 
						 | 
					@ -39,33 +38,47 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import shouldMuteNote from '../../../common/scripts/should-mute-note';
 | 
					import shouldMuteNote from '../../../common/scripts/should-mute-note';
 | 
				
			||||||
 | 
					import paging from '../../../common/scripts/paging';
 | 
				
			||||||
const displayLimit = 20;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n(),
 | 
						i18n: i18n(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	inject: ['column', 'isScrollTop', 'count'],
 | 
						inject: ['column', 'isScrollTop', 'count'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mixins: [
 | 
				
			||||||
 | 
							paging({
 | 
				
			||||||
 | 
								limit: 20,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								onQueueChanged: (self, q) => {
 | 
				
			||||||
 | 
									self.count(q.length);
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								onPrepend: (self, note, silent) => {
 | 
				
			||||||
 | 
									// 弾く
 | 
				
			||||||
 | 
									if (shouldMuteNote(self.$store.state.i, self.$store.state.settings, note)) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
 | 
				
			||||||
 | 
									if (document.hidden || !self.isScrollTop()) {
 | 
				
			||||||
 | 
										self.$store.commit('pushBehindNote', note);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
		makePromise: {
 | 
							pagination: {
 | 
				
			||||||
			required: true
 | 
								required: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							extract: {
 | 
				
			||||||
 | 
								required: false
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			rootEl: null,
 | 
					 | 
				
			||||||
			notes: [],
 | 
					 | 
				
			||||||
			queue: [],
 | 
					 | 
				
			||||||
			fetching: true,
 | 
					 | 
				
			||||||
			moreFetching: false,
 | 
					 | 
				
			||||||
			inited: false,
 | 
					 | 
				
			||||||
			more: false
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
 | 
							notes() {
 | 
				
			||||||
 | 
								return this.extract ? this.extract(this.items) : this.items;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_notes(): any[] {
 | 
							_notes(): any[] {
 | 
				
			||||||
			return (this.notes as any).map(note => {
 | 
								return (this.notes as any).map(note => {
 | 
				
			||||||
				const date = new Date(note.createdAt).getDate();
 | 
									const date = new Date(note.createdAt).getDate();
 | 
				
			||||||
| 
						 | 
					@ -77,15 +90,6 @@ export default Vue.extend({
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	watch: {
 | 
					 | 
				
			||||||
		queue(q) {
 | 
					 | 
				
			||||||
			this.count(q.length);
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		makePromise() {
 | 
					 | 
				
			||||||
			this.init();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	created() {
 | 
						created() {
 | 
				
			||||||
		this.column.$on('top', this.onTop);
 | 
							this.column.$on('top', this.onTop);
 | 
				
			||||||
		this.column.$on('bottom', this.onBottom);
 | 
							this.column.$on('bottom', this.onBottom);
 | 
				
			||||||
| 
						 | 
					@ -101,88 +105,6 @@ export default Vue.extend({
 | 
				
			||||||
		focus() {
 | 
							focus() {
 | 
				
			||||||
			(this.$refs.notes as any).children[0].focus ? (this.$refs.notes as any).children[0].focus() : (this.$refs.notes as any).$el.children[0].focus();
 | 
								(this.$refs.notes as any).children[0].focus ? (this.$refs.notes as any).children[0].focus() : (this.$refs.notes as any).$el.children[0].focus();
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					 | 
				
			||||||
		onNoteUpdated(i, note) {
 | 
					 | 
				
			||||||
			Vue.set((this as any).notes, i, note);
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		reload() {
 | 
					 | 
				
			||||||
			this.init();
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		async init() {
 | 
					 | 
				
			||||||
			this.queue = [];
 | 
					 | 
				
			||||||
			this.notes = [];
 | 
					 | 
				
			||||||
			this.fetching = true;
 | 
					 | 
				
			||||||
			await (this.makePromise()).then(x => {
 | 
					 | 
				
			||||||
				if (Array.isArray(x)) {
 | 
					 | 
				
			||||||
					this.notes = x;
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					this.notes = x.notes;
 | 
					 | 
				
			||||||
					this.more = x.more;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				this.inited = true;
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
				this.$emit('inited');
 | 
					 | 
				
			||||||
			}, e => {
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		async fetchMore() {
 | 
					 | 
				
			||||||
			if (!this.more || this.moreFetching) return;
 | 
					 | 
				
			||||||
			this.moreFetching = true;
 | 
					 | 
				
			||||||
			await (this.makePromise(this.notes[this.notes.length - 1].id)).then(x => {
 | 
					 | 
				
			||||||
				this.notes = this.notes.concat(x.notes);
 | 
					 | 
				
			||||||
				this.more = x.more;
 | 
					 | 
				
			||||||
				this.moreFetching = false;
 | 
					 | 
				
			||||||
			}, e => {
 | 
					 | 
				
			||||||
				this.moreFetching = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		prepend(note, silent = false) {
 | 
					 | 
				
			||||||
			// 弾く
 | 
					 | 
				
			||||||
			if (shouldMuteNote(this.$store.state.i, this.$store.state.settings, note)) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// タブが非表示ならタイトルで通知
 | 
					 | 
				
			||||||
			if (document.hidden) {
 | 
					 | 
				
			||||||
				this.$store.commit('pushBehindNote', note);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (this.isScrollTop()) {
 | 
					 | 
				
			||||||
				// Prepend the note
 | 
					 | 
				
			||||||
				this.notes.unshift(note);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				// オーバーフローしたら古い投稿は捨てる
 | 
					 | 
				
			||||||
				if (this.notes.length >= displayLimit) {
 | 
					 | 
				
			||||||
					this.notes = this.notes.slice(0, displayLimit);
 | 
					 | 
				
			||||||
					this.more = true;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				this.queue.push(note);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		append(note) {
 | 
					 | 
				
			||||||
			this.notes.push(note);
 | 
					 | 
				
			||||||
			this.cursor = this.notes[this.notes.length - 1].id
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		releaseQueue() {
 | 
					 | 
				
			||||||
			for (const n of this.queue) {
 | 
					 | 
				
			||||||
				this.prepend(n, true);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			this.queue = [];
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		onTop() {
 | 
					 | 
				
			||||||
			this.releaseQueue();
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		onBottom() {
 | 
					 | 
				
			||||||
			this.fetchMore();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,15 +81,15 @@
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<template v-if="notification.type == 'quote'">
 | 
						<template v-if="notification.type == 'quote'">
 | 
				
			||||||
		<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
 | 
							<mk-note :note="notification.note"/>
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<template v-if="notification.type == 'reply'">
 | 
						<template v-if="notification.type == 'reply'">
 | 
				
			||||||
		<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
 | 
							<mk-note :note="notification.note"/>
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<template v-if="notification.type == 'mention'">
 | 
						<template v-if="notification.type == 'mention'">
 | 
				
			||||||
		<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
 | 
							<mk-note :note="notification.note"/>
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -105,17 +105,6 @@ export default Vue.extend({
 | 
				
			||||||
			getNoteSummary
 | 
								getNoteSummary
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		onNoteUpdated(note) {
 | 
					 | 
				
			||||||
			switch (this.notification.type) {
 | 
					 | 
				
			||||||
				case 'quote':
 | 
					 | 
				
			||||||
				case 'reply':
 | 
					 | 
				
			||||||
				case 'mention':
 | 
					 | 
				
			||||||
					Vue.set(this.notification, 'note', note);
 | 
					 | 
				
			||||||
					break;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,16 +10,17 @@
 | 
				
			||||||
	<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications" tag="div">
 | 
						<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications" tag="div">
 | 
				
			||||||
		<template v-for="(notification, i) in _notifications">
 | 
							<template v-for="(notification, i) in _notifications">
 | 
				
			||||||
			<x-notification class="notification" :notification="notification" :key="notification.id"/>
 | 
								<x-notification class="notification" :notification="notification" :key="notification.id"/>
 | 
				
			||||||
			<p class="date" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date" :key="notification.id + '-time'">
 | 
								<p class="date" v-if="i != items.length - 1 && notification._date != _notifications[i + 1]._date" :key="notification.id + '-time'">
 | 
				
			||||||
				<span><fa icon="angle-up"/>{{ notification._datetext }}</span>
 | 
									<span><fa icon="angle-up"/>{{ notification._datetext }}</span>
 | 
				
			||||||
				<span><fa icon="angle-down"/>{{ _notifications[i + 1]._datetext }}</span>
 | 
									<span><fa icon="angle-down"/>{{ _notifications[i + 1]._datetext }}</span>
 | 
				
			||||||
			</p>
 | 
								</p>
 | 
				
			||||||
		</template>
 | 
							</template>
 | 
				
			||||||
	</component>
 | 
						</component>
 | 
				
			||||||
	<button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
 | 
						<button class="more" :class="{ fetching: moreFetching }" v-if="more" @click="fetchMore" :disabled="moreFetching">
 | 
				
			||||||
		<template v-if="fetchingMoreNotifications"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreNotifications ? this.$t('@.loading') : this.$t('@.load-more') }}
 | 
							<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>{{ moreFetching ? this.$t('@.loading') : this.$t('@.load-more') }}
 | 
				
			||||||
	</button>
 | 
						</button>
 | 
				
			||||||
	<p class="empty" v-if="notifications.length == 0 && !fetching">{{ $t('empty') }}</p>
 | 
						<p class="empty" v-if="empty">{{ $t('empty') }}</p>
 | 
				
			||||||
 | 
						<mk-error v-if="error" @retry="init()"/>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,31 +28,38 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import XNotification from './deck.notification.vue';
 | 
					import XNotification from './deck.notification.vue';
 | 
				
			||||||
 | 
					import paging from '../../../common/scripts/paging';
 | 
				
			||||||
const displayLimit = 20;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n(),
 | 
						i18n: i18n(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XNotification
 | 
							XNotification
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	inject: ['column', 'isScrollTop', 'count'],
 | 
						inject: ['column', 'isScrollTop', 'count'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mixins: [
 | 
				
			||||||
 | 
							paging({
 | 
				
			||||||
 | 
								onQueueChanged: (self, q) => {
 | 
				
			||||||
 | 
									self.count(q.length);
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			fetching: true,
 | 
								connection: null,
 | 
				
			||||||
			fetchingMoreNotifications: false,
 | 
								pagination: {
 | 
				
			||||||
			notifications: [],
 | 
									endpoint: 'i/notifications',
 | 
				
			||||||
			queue: [],
 | 
									limit: 20,
 | 
				
			||||||
			moreNotifications: false,
 | 
								}
 | 
				
			||||||
			connection: null
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		_notifications(): any[] {
 | 
							_notifications(): any[] {
 | 
				
			||||||
			return (this.notifications as any).map(notification => {
 | 
								return (this.items as any).map(notification => {
 | 
				
			||||||
				const date = new Date(notification.createdAt).getDate();
 | 
									const date = new Date(notification.createdAt).getDate();
 | 
				
			||||||
				const month = new Date(notification.createdAt).getMonth() + 1;
 | 
									const month = new Date(notification.createdAt).getMonth() + 1;
 | 
				
			||||||
				notification._date = date;
 | 
									notification._date = date;
 | 
				
			||||||
| 
						 | 
					@ -61,33 +69,12 @@ export default Vue.extend({
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	watch: {
 | 
					 | 
				
			||||||
		queue(q) {
 | 
					 | 
				
			||||||
			this.count(q.length);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		this.connection = this.$root.stream.useSharedConnection('main');
 | 
							this.connection = this.$root.stream.useSharedConnection('main');
 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.connection.on('notification', this.onNotification);
 | 
							this.connection.on('notification', this.onNotification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.column.$on('top', this.onTop);
 | 
							this.column.$on('top', this.onTop);
 | 
				
			||||||
		this.column.$on('bottom', this.onBottom);
 | 
							this.column.$on('bottom', this.onBottom);
 | 
				
			||||||
 | 
					 | 
				
			||||||
		const max = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.$root.api('i/notifications', {
 | 
					 | 
				
			||||||
			limit: max + 1
 | 
					 | 
				
			||||||
		}).then(notifications => {
 | 
					 | 
				
			||||||
			if (notifications.length == max + 1) {
 | 
					 | 
				
			||||||
				this.moreNotifications = true;
 | 
					 | 
				
			||||||
				notifications.pop();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.notifications = notifications;
 | 
					 | 
				
			||||||
			this.fetching = false;
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	beforeDestroy() {
 | 
						beforeDestroy() {
 | 
				
			||||||
| 
						 | 
					@ -98,26 +85,6 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		fetchMoreNotifications() {
 | 
					 | 
				
			||||||
			this.fetchingMoreNotifications = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const max = 20;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$root.api('i/notifications', {
 | 
					 | 
				
			||||||
				limit: max + 1,
 | 
					 | 
				
			||||||
				untilId: this.notifications[this.notifications.length - 1].id
 | 
					 | 
				
			||||||
			}).then(notifications => {
 | 
					 | 
				
			||||||
				if (notifications.length == max + 1) {
 | 
					 | 
				
			||||||
					this.moreNotifications = true;
 | 
					 | 
				
			||||||
					notifications.pop();
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					this.moreNotifications = false;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				this.notifications = this.notifications.concat(notifications);
 | 
					 | 
				
			||||||
				this.fetchingMoreNotifications = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		onNotification(notification) {
 | 
							onNotification(notification) {
 | 
				
			||||||
			// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
 | 
								// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
 | 
				
			||||||
			this.$root.stream.send('readNotification', {
 | 
								this.$root.stream.send('readNotification', {
 | 
				
			||||||
| 
						 | 
					@ -126,35 +93,6 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.prepend(notification);
 | 
								this.prepend(notification);
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					 | 
				
			||||||
		prepend(notification) {
 | 
					 | 
				
			||||||
			if (this.isScrollTop()) {
 | 
					 | 
				
			||||||
				// Prepend the notification
 | 
					 | 
				
			||||||
				this.notifications.unshift(notification);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				// オーバーフローしたら古い通知は捨てる
 | 
					 | 
				
			||||||
				if (this.notifications.length >= displayLimit) {
 | 
					 | 
				
			||||||
					this.notifications = this.notifications.slice(0, displayLimit);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				this.queue.push(notification);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		releaseQueue() {
 | 
					 | 
				
			||||||
			for (const n of this.queue) {
 | 
					 | 
				
			||||||
				this.prepend(n);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			this.queue = [];
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		onTop() {
 | 
					 | 
				
			||||||
			this.releaseQueue();
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		onBottom() {
 | 
					 | 
				
			||||||
			this.fetchMoreNotifications();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div>
 | 
						<div>
 | 
				
			||||||
		<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
 | 
							<x-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</x-column>
 | 
					</x-column>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -16,8 +16,6 @@ import XColumn from './deck.column.vue';
 | 
				
			||||||
import XNotes from './deck.notes.vue';
 | 
					import XNotes from './deck.notes.vue';
 | 
				
			||||||
import { genSearchQuery } from '../../../common/scripts/gen-search-query';
 | 
					import { genSearchQuery } from '../../../common/scripts/gen-search-query';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const limit = 20;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XColumn,
 | 
							XColumn,
 | 
				
			||||||
| 
						 | 
					@ -26,24 +24,11 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			makePromise: async cursor => this.$root.api('notes/search', {
 | 
								pagination: {
 | 
				
			||||||
				limit: limit + 1,
 | 
									endpoint: 'notes/search',
 | 
				
			||||||
				offset: cursor ? cursor : undefined,
 | 
									limit: 20,
 | 
				
			||||||
				...(await genSearchQuery(this, this.q))
 | 
									params: () => genSearchQuery(this, this.q)
 | 
				
			||||||
			}).then(notes => {
 | 
								}
 | 
				
			||||||
				if (notes.length == limit + 1) {
 | 
					 | 
				
			||||||
					notes.pop();
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						cursor: cursor ? cursor + limit : limit
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@
 | 
				
			||||||
	</p>
 | 
						</p>
 | 
				
			||||||
	<p class="desc">{{ $t('disabled-timeline.description') }}</p>
 | 
						<p class="desc">{{ $t('disabled-timeline.description') }}</p>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
<x-notes v-else ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
 | 
					<x-notes v-else ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
| 
						 | 
					@ -15,8 +15,6 @@ import XNotes from './deck.notes.vue';
 | 
				
			||||||
import { faMinusCircle } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faMinusCircle } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fetchLimit = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('deck'),
 | 
						i18n: i18n('deck'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,7 +40,7 @@ export default Vue.extend({
 | 
				
			||||||
			connection: null,
 | 
								connection: null,
 | 
				
			||||||
			disabled: false,
 | 
								disabled: false,
 | 
				
			||||||
			faMinusCircle,
 | 
								faMinusCircle,
 | 
				
			||||||
			makePromise: null
 | 
								pagination: null
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,27 +71,17 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	created() {
 | 
						created() {
 | 
				
			||||||
		this.makePromise = cursor => this.$root.api(this.endpoint, {
 | 
							this.pagination = {
 | 
				
			||||||
			limit: fetchLimit + 1,
 | 
								endpoint: this.endpoint,
 | 
				
			||||||
			untilId: cursor ? cursor : undefined,
 | 
								limit: 10,
 | 
				
			||||||
			withFiles: this.mediaOnly,
 | 
								params: init => ({
 | 
				
			||||||
			includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
									untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
			includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
									withFiles: this.mediaOnly,
 | 
				
			||||||
			includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
									includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
				
			||||||
		}).then(notes => {
 | 
									includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
				
			||||||
			if (notes.length == fetchLimit + 1) {
 | 
									includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
				
			||||||
				notes.pop();
 | 
								})
 | 
				
			||||||
				return {
 | 
							};
 | 
				
			||||||
					notes: notes,
 | 
					 | 
				
			||||||
					more: true
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				return {
 | 
					 | 
				
			||||||
					notes: notes,
 | 
					 | 
				
			||||||
					more: false
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,7 @@
 | 
				
			||||||
	<ui-container>
 | 
						<ui-container>
 | 
				
			||||||
		<template #header><fa :icon="['far', 'comment-alt']"/> {{ $t('timeline') }}</template>
 | 
							<template #header><fa :icon="['far', 'comment-alt']"/> {{ $t('timeline') }}</template>
 | 
				
			||||||
		<div>
 | 
							<div>
 | 
				
			||||||
			<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
 | 
								<x-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</ui-container>
 | 
						</ui-container>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					@ -43,8 +43,6 @@ import XNotes from './deck.notes.vue';
 | 
				
			||||||
import { concat } from '../../../../../prelude/array';
 | 
					import { concat } from '../../../../../prelude/array';
 | 
				
			||||||
import ApexCharts from 'apexcharts';
 | 
					import ApexCharts from 'apexcharts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fetchLimit = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('deck/deck.user-column.vue'),
 | 
						i18n: i18n('deck/deck.user-column.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,49 +61,38 @@ export default Vue.extend({
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			withFiles: false,
 | 
								withFiles: false,
 | 
				
			||||||
			images: [],
 | 
								images: [],
 | 
				
			||||||
			makePromise: null,
 | 
					 | 
				
			||||||
			chart: null as ApexCharts
 | 
								chart: null as ApexCharts
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						computed: {
 | 
				
			||||||
 | 
							pagination() {
 | 
				
			||||||
 | 
								return {
 | 
				
			||||||
 | 
									endpoint: 'users/notes',
 | 
				
			||||||
 | 
									limit: 10,
 | 
				
			||||||
 | 
									params: init => ({
 | 
				
			||||||
 | 
										userId: this.user.id,
 | 
				
			||||||
 | 
										untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
 | 
										withFiles: this.withFiles,
 | 
				
			||||||
 | 
										includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
				
			||||||
 | 
										includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
				
			||||||
 | 
										includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	watch: {
 | 
						watch: {
 | 
				
			||||||
		user() {
 | 
							user() {
 | 
				
			||||||
			this.fetch();
 | 
								this.fetch();
 | 
				
			||||||
			this.genPromiseMaker();
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	created() {
 | 
						created() {
 | 
				
			||||||
		this.fetch();
 | 
							this.fetch();
 | 
				
			||||||
		this.genPromiseMaker();
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		genPromiseMaker() {
 | 
					 | 
				
			||||||
			this.makePromise = cursor => this.$root.api('users/notes', {
 | 
					 | 
				
			||||||
				userId: this.user.id,
 | 
					 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
					 | 
				
			||||||
				untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365,
 | 
					 | 
				
			||||||
				withFiles: this.withFiles,
 | 
					 | 
				
			||||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
					 | 
				
			||||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
					 | 
				
			||||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
					 | 
				
			||||||
			}).then(notes => {
 | 
					 | 
				
			||||||
				if (notes.length == fetchLimit + 1) {
 | 
					 | 
				
			||||||
					notes.pop();
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: true
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fetch() {
 | 
							fetch() {
 | 
				
			||||||
			const image = [
 | 
								const image = [
 | 
				
			||||||
				'image/jpeg',
 | 
									'image/jpeg',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,24 +18,24 @@
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</ui-container>
 | 
						</ui-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<mk-user-list v-if="tag != null" :make-promise="tagUsers" :key="`${tag}-local`">
 | 
						<mk-user-list v-if="tag != null" :pagination="tagUsers" :key="`${tag}-local`">
 | 
				
			||||||
		<fa :icon="faHashtag" fixed-width/>{{ tag }}
 | 
							<fa :icon="faHashtag" fixed-width/>{{ tag }}
 | 
				
			||||||
	</mk-user-list>
 | 
						</mk-user-list>
 | 
				
			||||||
	<mk-user-list v-if="tag != null" :make-promise="tagRemoteUsers" :key="`${tag}-remote`">
 | 
						<mk-user-list v-if="tag != null" :pagination="tagRemoteUsers" :key="`${tag}-remote`">
 | 
				
			||||||
		<fa :icon="faHashtag" fixed-width/>{{ tag }} ({{ $t('federated') }})
 | 
							<fa :icon="faHashtag" fixed-width/>{{ tag }} ({{ $t('federated') }})
 | 
				
			||||||
	</mk-user-list>
 | 
						</mk-user-list>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<template v-if="tag == null">
 | 
						<template v-if="tag == null">
 | 
				
			||||||
		<mk-user-list :make-promise="pinnedUsers">
 | 
							<mk-user-list :pagination="pinnedUsers">
 | 
				
			||||||
			<fa :icon="faBookmark" fixed-width/>{{ $t('pinned-users') }}
 | 
								<fa :icon="faBookmark" fixed-width/>{{ $t('pinned-users') }}
 | 
				
			||||||
		</mk-user-list>
 | 
							</mk-user-list>
 | 
				
			||||||
		<mk-user-list :make-promise="popularUsers">
 | 
							<mk-user-list :pagination="popularUsers">
 | 
				
			||||||
			<fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }}
 | 
								<fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }}
 | 
				
			||||||
		</mk-user-list>
 | 
							</mk-user-list>
 | 
				
			||||||
		<mk-user-list :make-promise="recentlyUpdatedUsers">
 | 
							<mk-user-list :pagination="recentlyUpdatedUsers">
 | 
				
			||||||
			<fa :icon="faCommentAlt" fixed-width/>{{ $t('recently-updated-users') }}
 | 
								<fa :icon="faCommentAlt" fixed-width/>{{ $t('recently-updated-users') }}
 | 
				
			||||||
		</mk-user-list>
 | 
							</mk-user-list>
 | 
				
			||||||
		<mk-user-list :make-promise="recentlyRegisteredUsers">
 | 
							<mk-user-list :pagination="recentlyRegisteredUsers">
 | 
				
			||||||
			<fa :icon="faPlus" fixed-width/>{{ $t('recently-registered-users') }}
 | 
								<fa :icon="faPlus" fixed-width/>{{ $t('recently-registered-users') }}
 | 
				
			||||||
		</mk-user-list>
 | 
							</mk-user-list>
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
| 
						 | 
					@ -60,24 +60,21 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			pinnedUsers: () => this.$root.api('pinned-users'),
 | 
								pinnedUsers: { endpoint: 'pinned-users' },
 | 
				
			||||||
			popularUsers: () => this.$root.api('users', {
 | 
								popularUsers: { endpoint: 'users', limit: 10, params: {
 | 
				
			||||||
				state: 'alive',
 | 
									state: 'alive',
 | 
				
			||||||
				origin: 'local',
 | 
									origin: 'local',
 | 
				
			||||||
				sort: '+follower',
 | 
									sort: '+follower',
 | 
				
			||||||
				limit: 10
 | 
								} },
 | 
				
			||||||
			}),
 | 
								recentlyUpdatedUsers: { endpoint: 'users', limit: 10, params: {
 | 
				
			||||||
			recentlyUpdatedUsers: () => this.$root.api('users', {
 | 
					 | 
				
			||||||
				origin: 'local',
 | 
									origin: 'local',
 | 
				
			||||||
				sort: '+updatedAt',
 | 
									sort: '+updatedAt',
 | 
				
			||||||
				limit: 10
 | 
								} },
 | 
				
			||||||
			}),
 | 
								recentlyRegisteredUsers: { endpoint: 'users', limit: 10, params: {
 | 
				
			||||||
			recentlyRegisteredUsers: () => this.$root.api('users', {
 | 
					 | 
				
			||||||
				origin: 'local',
 | 
									origin: 'local',
 | 
				
			||||||
				state: 'alive',
 | 
									state: 'alive',
 | 
				
			||||||
				sort: '+createdAt',
 | 
									sort: '+createdAt',
 | 
				
			||||||
				limit: 10
 | 
								} },
 | 
				
			||||||
			}),
 | 
					 | 
				
			||||||
			tagsLocal: [],
 | 
								tagsLocal: [],
 | 
				
			||||||
			tagsRemote: [],
 | 
								tagsRemote: [],
 | 
				
			||||||
			stats: null,
 | 
								stats: null,
 | 
				
			||||||
| 
						 | 
					@ -88,24 +85,30 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		tagUsers(): () => Promise<any> {
 | 
							tagUsers(): any {
 | 
				
			||||||
			return () => this.$root.api('hashtags/users', {
 | 
								return {
 | 
				
			||||||
				tag: this.tag,
 | 
									endpoint: 'hashtags/users',
 | 
				
			||||||
				state: 'alive',
 | 
									limit: 30,
 | 
				
			||||||
				origin: 'local',
 | 
									params: {
 | 
				
			||||||
				sort: '+follower',
 | 
										tag: this.tag,
 | 
				
			||||||
				limit: 30
 | 
										state: 'alive',
 | 
				
			||||||
			});
 | 
										origin: 'local',
 | 
				
			||||||
 | 
										sort: '+follower',
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		tagRemoteUsers(): () => Promise<any> {
 | 
							tagRemoteUsers(): any {
 | 
				
			||||||
			return () => this.$root.api('hashtags/users', {
 | 
								return {
 | 
				
			||||||
				tag: this.tag,
 | 
									endpoint: 'hashtags/users',
 | 
				
			||||||
				state: 'alive',
 | 
									limit: 30,
 | 
				
			||||||
				origin: 'remote',
 | 
									params: {
 | 
				
			||||||
				sort: '+follower',
 | 
										tag: this.tag,
 | 
				
			||||||
				limit: 30
 | 
										state: 'alive',
 | 
				
			||||||
			});
 | 
										origin: 'remote',
 | 
				
			||||||
 | 
										sort: '+follower',
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										44
									
								
								src/client/app/common/views/pages/favorites.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/client/app/common/views/pages/favorites.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
						<component :is="notesComponent" :pagination="pagination" :extract="items => items.map(item => item.note)"/>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import { faStar } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					//import Progress from '../../../common/scripts/loading';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						i18n: i18n(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							platform: {
 | 
				
			||||||
 | 
								type: String,
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								pagination: {
 | 
				
			||||||
 | 
									endpoint: 'i/favorites',
 | 
				
			||||||
 | 
									limit: 10,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								notesComponent:
 | 
				
			||||||
 | 
									this.platform === 'desktop' ? () => import('../../../desktop/views/components/detail-notes.vue').then(m => m.default) :
 | 
				
			||||||
 | 
									this.platform === 'mobile' ? () => import('../../../mobile/views/components/detail-notes.vue').then(m => m.default) :
 | 
				
			||||||
 | 
									this.platform === 'deck' ? () => import('../deck/deck.notes.vue').then(m => m.default) : null
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						created() {
 | 
				
			||||||
 | 
							this.$emit('init', {
 | 
				
			||||||
 | 
								title: this.$t('@.favorites'),
 | 
				
			||||||
 | 
								icon: faStar
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										44
									
								
								src/client/app/common/views/pages/featured.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/client/app/common/views/pages/featured.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
						<component :is="notesComponent" :pagination="pagination"/>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					//import Progress from '../../../common/scripts/loading';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						i18n: i18n(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							platform: {
 | 
				
			||||||
 | 
								type: String,
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								pagination: {
 | 
				
			||||||
 | 
									endpoint: 'notes/featured',
 | 
				
			||||||
 | 
									limit: 29,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								notesComponent:
 | 
				
			||||||
 | 
									this.platform === 'desktop' ? () => import('../../../desktop/views/components/detail-notes.vue').then(m => m.default) :
 | 
				
			||||||
 | 
									this.platform === 'mobile' ? () => import('../../../mobile/views/components/detail-notes.vue').then(m => m.default) :
 | 
				
			||||||
 | 
									this.platform === 'deck' ? () => import('../deck/deck.notes.vue').then(m => m.default) : null
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						created() {
 | 
				
			||||||
 | 
							this.$emit('init', {
 | 
				
			||||||
 | 
								title: this.$t('@.featured-notes'),
 | 
				
			||||||
 | 
								icon: faNewspaper
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div>
 | 
					<mk-user-list :pagination="pagination" :extract="items => items.map(item => item.follower)">{{ $t('@.followers') }}</mk-user-list>
 | 
				
			||||||
	<mk-user-list :make-promise="makePromise">{{ $t('@.followers') }}</mk-user-list>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
| 
						 | 
					@ -9,31 +7,18 @@ import Vue from 'vue';
 | 
				
			||||||
import parseAcct from '../../../../../misc/acct/parse';
 | 
					import parseAcct from '../../../../../misc/acct/parse';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fetchLimit = 30;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n(),
 | 
						i18n: i18n(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			makePromise: cursor => this.$root.api('users/followers', {
 | 
								pagination: {
 | 
				
			||||||
				...parseAcct(this.$route.params.user),
 | 
									endpoint: 'users/followers',
 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
									limit: 30,
 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
									params: {
 | 
				
			||||||
			}).then(followings => {
 | 
										...parseAcct(this.$route.params.user),
 | 
				
			||||||
				if (followings.length == fetchLimit + 1) {
 | 
					 | 
				
			||||||
					followings.pop();
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						users: followings.map(following => following.follower),
 | 
					 | 
				
			||||||
						cursor: followings[followings.length - 1].id
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						users: followings.map(following => following.follower),
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}),
 | 
								},
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div>
 | 
					<mk-user-list :pagination="pagination" :extract="items => items.map(item => item.followee)">{{ $t('@.following') }}</mk-user-list>
 | 
				
			||||||
	<mk-user-list :make-promise="makePromise">{{ $t('@.following') }}</mk-user-list>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
| 
						 | 
					@ -9,31 +7,18 @@ import Vue from 'vue';
 | 
				
			||||||
import parseAcct from '../../../../../misc/acct/parse';
 | 
					import parseAcct from '../../../../../misc/acct/parse';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fetchLimit = 30;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n(),
 | 
						i18n: i18n(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			makePromise: cursor => this.$root.api('users/following', {
 | 
								pagination: {
 | 
				
			||||||
				...parseAcct(this.$route.params.user),
 | 
									endpoint: 'users/following',
 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
									limit: 30,
 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
									params: {
 | 
				
			||||||
			}).then(followings => {
 | 
										...parseAcct(this.$route.params.user),
 | 
				
			||||||
				if (followings.length == fetchLimit + 1) {
 | 
					 | 
				
			||||||
					followings.pop();
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						users: followings.map(following => following.followee),
 | 
					 | 
				
			||||||
						cursor: followings[followings.length - 1].id
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						users: followings.map(following => following.followee),
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}),
 | 
								},
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,22 +2,20 @@
 | 
				
			||||||
<div>
 | 
					<div>
 | 
				
			||||||
	<ui-container :body-togglable="true">
 | 
						<ui-container :body-togglable="true">
 | 
				
			||||||
		<template #header><fa :icon="faEdit" fixed-width/>{{ $t('my-pages') }}</template>
 | 
							<template #header><fa :icon="faEdit" fixed-width/>{{ $t('my-pages') }}</template>
 | 
				
			||||||
		<div class="rknalgpo" v-if="!fetching">
 | 
							<div class="rknalgpo my">
 | 
				
			||||||
			<ui-button class="new" @click="create()"><fa :icon="faPlus"/></ui-button>
 | 
								<ui-button class="new" @click="create()"><fa :icon="faPlus"/></ui-button>
 | 
				
			||||||
			<sequential-entrance animation="entranceFromTop" delay="25" tag="div" class="pages">
 | 
								<ui-pagination :pagination="myPagesPagination" #default="{items}">
 | 
				
			||||||
				<x-page-preview v-for="page in pages" class="page" :page="page" :key="page.id"/>
 | 
									<x-page-preview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/>
 | 
				
			||||||
			</sequential-entrance>
 | 
								</ui-pagination>
 | 
				
			||||||
			<ui-button v-if="existMore" @click="fetchMore()" style="margin-top:16px;">{{ $t('@.load-more') }}</ui-button>
 | 
					 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</ui-container>
 | 
						</ui-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ui-container :body-togglable="true">
 | 
						<ui-container :body-togglable="true">
 | 
				
			||||||
		<template #header><fa :icon="faHeart" fixed-width/>{{ $t('liked-pages') }}</template>
 | 
							<template #header><fa :icon="faHeart" fixed-width/>{{ $t('liked-pages') }}</template>
 | 
				
			||||||
		<div class="rknalgpo" v-if="!fetching">
 | 
							<div class="rknalgpo">
 | 
				
			||||||
			<sequential-entrance animation="entranceFromTop" delay="25" tag="div" class="pages">
 | 
								<ui-pagination :pagination="likedPagesPagination" #default="{items}">
 | 
				
			||||||
				<x-page-preview v-for="like in likes" class="page" :page="like.page" :key="like.page.id"/>
 | 
									<x-page-preview v-for="like in items" class="ckltabjg" :page="like.page" :key="like.page.id"/>
 | 
				
			||||||
			</sequential-entrance>
 | 
								</ui-pagination>
 | 
				
			||||||
			<ui-button v-if="existMoreLikes" @click="fetchMoreLiked()">{{ $t('@.load-more') }}</ui-button>
 | 
					 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</ui-container>
 | 
						</ui-container>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					@ -28,7 +26,6 @@ import Vue from 'vue';
 | 
				
			||||||
import { faPlus, faEdit } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faPlus, faEdit } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { faStickyNote, faHeart } from '@fortawesome/free-regular-svg-icons';
 | 
					import { faStickyNote, faHeart } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import Progress from '../../scripts/loading';
 | 
					 | 
				
			||||||
import XPagePreview from '../../views/components/page-preview.vue';
 | 
					import XPagePreview from '../../views/components/page-preview.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
| 
						 | 
					@ -38,87 +35,24 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			fetching: true,
 | 
								myPagesPagination: {
 | 
				
			||||||
			pages: [],
 | 
									endpoint: 'i/pages',
 | 
				
			||||||
			existMore: false,
 | 
									limit: 5,
 | 
				
			||||||
			moreFetching: false,
 | 
								},
 | 
				
			||||||
			likes: [],
 | 
								likedPagesPagination: {
 | 
				
			||||||
			existMoreLikes: false,
 | 
									endpoint: 'i/page-likes',
 | 
				
			||||||
			moreLikesFetching: false,
 | 
									limit: 5,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
			faStickyNote, faPlus, faEdit, faHeart
 | 
								faStickyNote, faPlus, faEdit, faHeart
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	created() {
 | 
						created() {
 | 
				
			||||||
		this.fetch();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.$emit('init', {
 | 
							this.$emit('init', {
 | 
				
			||||||
			title: this.$t('@.pages'),
 | 
								title: this.$t('@.pages'),
 | 
				
			||||||
			icon: faStickyNote
 | 
								icon: faStickyNote
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		async fetch() {
 | 
					 | 
				
			||||||
			Progress.start();
 | 
					 | 
				
			||||||
			this.fetching = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const pages = await this.$root.api('i/pages', {
 | 
					 | 
				
			||||||
				limit: 11
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (pages.length == 11) {
 | 
					 | 
				
			||||||
				this.existMore = true;
 | 
					 | 
				
			||||||
				pages.pop();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const likes = await this.$root.api('i/page-likes', {
 | 
					 | 
				
			||||||
				limit: 11
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (likes.length == 11) {
 | 
					 | 
				
			||||||
				this.existMoreLikes = true;
 | 
					 | 
				
			||||||
				likes.pop();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.pages = pages;
 | 
					 | 
				
			||||||
			this.likes = likes;
 | 
					 | 
				
			||||||
			this.fetching = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			Progress.done();
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		fetchMore() {
 | 
					 | 
				
			||||||
			this.moreFetching = true;
 | 
					 | 
				
			||||||
			this.$root.api('i/pages', {
 | 
					 | 
				
			||||||
				limit: 11,
 | 
					 | 
				
			||||||
				untilId: this.pages[this.pages.length - 1].id
 | 
					 | 
				
			||||||
			}).then(pages => {
 | 
					 | 
				
			||||||
				if (pages.length == 11) {
 | 
					 | 
				
			||||||
					this.existMore = true;
 | 
					 | 
				
			||||||
					pages.pop();
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					this.existMore = false;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				this.pages = this.pages.concat(pages);
 | 
					 | 
				
			||||||
				this.moreFetching = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		fetchMoreLiked() {
 | 
					 | 
				
			||||||
			this.moreLikesFetching = true;
 | 
					 | 
				
			||||||
			this.$root.api('i/page-likes', {
 | 
					 | 
				
			||||||
				limit: 11,
 | 
					 | 
				
			||||||
				untilId: this.likes[this.likes.length - 1].id
 | 
					 | 
				
			||||||
			}).then(pages => {
 | 
					 | 
				
			||||||
				if (pages.length == 11) {
 | 
					 | 
				
			||||||
					this.existMoreLikes = true;
 | 
					 | 
				
			||||||
					pages.pop();
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					this.existMoreLikes = false;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				this.likes = this.likes.concat(pages);
 | 
					 | 
				
			||||||
				this.moreLikesFetching = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		create() {
 | 
							create() {
 | 
				
			||||||
			this.$router.push(`/i/pages/new`);
 | 
								this.$router.push(`/i/pages/new`);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -130,14 +64,14 @@ export default Vue.extend({
 | 
				
			||||||
.rknalgpo
 | 
					.rknalgpo
 | 
				
			||||||
	padding 16px
 | 
						padding 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> .new
 | 
						&.my .ckltabjg:first-child
 | 
				
			||||||
		margin-bottom 16px
 | 
							margin-top 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> * > .page:not(:last-child)
 | 
						.ckltabjg:not(:last-child)
 | 
				
			||||||
		margin-bottom 8px
 | 
							margin-bottom 8px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@media (min-width 500px)
 | 
						@media (min-width 500px)
 | 
				
			||||||
		> * > .page:not(:last-child)
 | 
							.ckltabjg:not(:last-child)
 | 
				
			||||||
			margin-bottom 16px
 | 
								margin-bottom 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
	<h1>{{ $t('share-with', { name }) }}</h1>
 | 
						<h1>{{ $t('share-with', { name }) }}</h1>
 | 
				
			||||||
	<div>
 | 
						<div>
 | 
				
			||||||
		<mk-signin v-if="!$store.getters.isSignedIn"/>
 | 
							<mk-signin v-if="!$store.getters.isSignedIn"/>
 | 
				
			||||||
		<mk-post-form v-else-if="!posted" :initial-text="template" :instant="true" @posted="posted = true"/>
 | 
							<x-post-form v-else-if="!posted" :initial-text="template" :instant="true" @posted="posted = true"/>
 | 
				
			||||||
		<p v-if="posted" class="posted"><fa icon="check"/></p>
 | 
							<p v-if="posted" class="posted"><fa icon="check"/></p>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<ui-button class="close" v-if="posted" @click="close">{{ $t('@.close') }}</ui-button>
 | 
						<ui-button class="close" v-if="posted" @click="close">{{ $t('@.close') }}</ui-button>
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,9 @@ import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('mobile/views/pages/share.vue'),
 | 
						i18n: i18n('mobile/views/pages/share.vue'),
 | 
				
			||||||
 | 
						components: {
 | 
				
			||||||
 | 
							XPostForm: () => import('../../../desktop/views/components/post-form.vue').then(m => m.default)
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			name: null,
 | 
								name: null,
 | 
				
			||||||
| 
						 | 
					@ -35,15 +38,15 @@ export default Vue.extend({
 | 
				
			||||||
			return t.trim();
 | 
								return t.trim();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		close() {
 | 
					 | 
				
			||||||
			window.close();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		this.$root.getMeta().then(meta => {
 | 
							this.$root.getMeta().then(meta => {
 | 
				
			||||||
			this.name = meta.name || 'Misskey';
 | 
								this.name = meta.name || 'Misskey';
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							close() {
 | 
				
			||||||
 | 
								window.close();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div>
 | 
					<div>
 | 
				
			||||||
	<ui-container>
 | 
						<ui-container :body-togglable="true">
 | 
				
			||||||
		<template #header><fa :icon="faUsers"/> {{ $t('owned-groups') }}</template>
 | 
							<template #header><fa :icon="faUsers"/> {{ $t('owned-groups') }}</template>
 | 
				
			||||||
		<ui-margin>
 | 
							<ui-margin>
 | 
				
			||||||
			<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('create-group') }}</ui-button>
 | 
								<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('create-group') }}</ui-button>
 | 
				
			||||||
| 
						 | 
					@ -9,26 +9,29 @@
 | 
				
			||||||
			<ui-hr/>
 | 
								<ui-hr/>
 | 
				
			||||||
			<ui-margin>
 | 
								<ui-margin>
 | 
				
			||||||
				<router-link :to="`/i/groups/${group.id}`">{{ group.name }}</router-link>
 | 
									<router-link :to="`/i/groups/${group.id}`">{{ group.name }}</router-link>
 | 
				
			||||||
 | 
									<x-avatars :user-ids="group.userIds" style="margin-top:8px;"/>
 | 
				
			||||||
			</ui-margin>
 | 
								</ui-margin>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</ui-container>
 | 
						</ui-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ui-container>
 | 
						<ui-container :body-togglable="true">
 | 
				
			||||||
		<template #header><fa :icon="faUsers"/> {{ $t('joined-groups') }}</template>
 | 
							<template #header><fa :icon="faUsers"/> {{ $t('joined-groups') }}</template>
 | 
				
			||||||
		<div class="hwgkdrbl" v-for="(group, i) in joinedGroups" :key="group.id">
 | 
							<div class="hwgkdrbl" v-for="(group, i) in joinedGroups" :key="group.id">
 | 
				
			||||||
			<ui-hr v-if="i != 0"/>
 | 
								<ui-hr v-if="i != 0"/>
 | 
				
			||||||
			<ui-margin>
 | 
								<ui-margin>
 | 
				
			||||||
				<router-link :to="`/i/groups/${group.id}`">{{ group.name }}</router-link>
 | 
									<div>{{ group.name }}</div>
 | 
				
			||||||
 | 
									<x-avatars :user-ids="group.userIds" style="margin-top:8px;"/>
 | 
				
			||||||
			</ui-margin>
 | 
								</ui-margin>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</ui-container>
 | 
						</ui-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ui-container>
 | 
						<ui-container :body-togglable="true">
 | 
				
			||||||
		<template #header><fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</template>
 | 
							<template #header><fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</template>
 | 
				
			||||||
		<div class="fvlojuur" v-for="(invite, i) in invites" :key="invite.id">
 | 
							<div class="fvlojuur" v-for="(invite, i) in invites" :key="invite.id">
 | 
				
			||||||
			<ui-hr v-if="i != 0"/>
 | 
								<ui-hr v-if="i != 0"/>
 | 
				
			||||||
			<ui-margin>
 | 
								<ui-margin>
 | 
				
			||||||
				<div class="name">{{ invite.group.name }}</div>
 | 
									<div class="name">{{ invite.group.name }}</div>
 | 
				
			||||||
 | 
									<x-avatars :user-ids="invite.group.userIds" style="margin-top:8px;"/>
 | 
				
			||||||
				<ui-horizon-group>
 | 
									<ui-horizon-group>
 | 
				
			||||||
					<ui-button @click="acceptInvite(invite)"><fa :icon="faCheck"/> {{ $t('accept-invite') }}</ui-button>
 | 
										<ui-button @click="acceptInvite(invite)"><fa :icon="faCheck"/> {{ $t('accept-invite') }}</ui-button>
 | 
				
			||||||
					<ui-button @click="rejectInvite(invite)"><fa :icon="faBan"/> {{ $t('reject-invite') }}</ui-button>
 | 
										<ui-button @click="rejectInvite(invite)"><fa :icon="faBan"/> {{ $t('reject-invite') }}</ui-button>
 | 
				
			||||||
| 
						 | 
					@ -41,11 +44,15 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
import { faUsers, faPlus, faCheck, faBan, faEnvelopeOpenText } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faUsers, faPlus, faCheck, faBan, faEnvelopeOpenText } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					import XAvatars from '../../views/components/avatars.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('common/views/components/user-groups.vue'),
 | 
						i18n: i18n('common/views/components/user-groups.vue'),
 | 
				
			||||||
 | 
						components: {
 | 
				
			||||||
 | 
							XAvatars
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			ownedGroups: [],
 | 
								ownedGroups: [],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@
 | 
				
			||||||
		<ui-hr/>
 | 
							<ui-hr/>
 | 
				
			||||||
		<ui-margin>
 | 
							<ui-margin>
 | 
				
			||||||
			<router-link :to="`/i/lists/${list.id}`">{{ list.name }}</router-link>
 | 
								<router-link :to="`/i/lists/${list.id}`">{{ list.name }}</router-link>
 | 
				
			||||||
 | 
								<x-avatars :user-ids="list.userIds" style="margin-top:8px;"/>
 | 
				
			||||||
		</ui-margin>
 | 
							</ui-margin>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</ui-container>
 | 
					</ui-container>
 | 
				
			||||||
| 
						 | 
					@ -17,9 +18,13 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import { faListUl, faPlus } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faListUl, faPlus } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					import XAvatars from '../../views/components/avatars.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('common/views/components/user-lists.vue'),
 | 
						i18n: i18n('common/views/components/user-lists.vue'),
 | 
				
			||||||
 | 
						components: {
 | 
				
			||||||
 | 
							XAvatars
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			fetching: true,
 | 
								fetching: true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,7 +38,6 @@
 | 
				
			||||||
import define from '../../../common/define-widget';
 | 
					import define from '../../../common/define-widget';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import insertTextAtCursor from 'insert-text-at-cursor';
 | 
					import insertTextAtCursor from 'insert-text-at-cursor';
 | 
				
			||||||
import XPostFormAttaches from '../components/post-form-attaches.vue';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define({
 | 
					export default define({
 | 
				
			||||||
	name: 'post-form',
 | 
						name: 'post-form',
 | 
				
			||||||
| 
						 | 
					@ -49,7 +48,7 @@ export default define({
 | 
				
			||||||
	i18n: i18n('desktop/views/widgets/post-form.vue'),
 | 
						i18n: i18n('desktop/views/widgets/post-form.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XPostFormAttaches
 | 
							XPostFormAttaches: () => import('../components/post-form-attaches.vue').then(m => m.default)
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,6 @@ import fuckAdBlock from '../common/scripts/fuck-ad-block';
 | 
				
			||||||
import composeNotification from '../common/scripts/compose-notification';
 | 
					import composeNotification from '../common/scripts/compose-notification';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import MkHome from './views/home/home.vue';
 | 
					import MkHome from './views/home/home.vue';
 | 
				
			||||||
import MkDeck from '../common/views/deck/deck.vue';
 | 
					 | 
				
			||||||
import MkSelectDrive from './views/pages/selectdrive.vue';
 | 
					import MkSelectDrive from './views/pages/selectdrive.vue';
 | 
				
			||||||
import MkDrive from './views/pages/drive.vue';
 | 
					import MkDrive from './views/pages/drive.vue';
 | 
				
			||||||
import MkMessagingRoom from './views/pages/messaging-room.vue';
 | 
					import MkMessagingRoom from './views/pages/messaging-room.vue';
 | 
				
			||||||
| 
						 | 
					@ -25,7 +24,6 @@ import MkSettings from './views/pages/settings.vue';
 | 
				
			||||||
import DeckColumn from '../common/views/deck/deck.column-template.vue';
 | 
					import DeckColumn from '../common/views/deck/deck.column-template.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Ctx from './views/components/context-menu.vue';
 | 
					import Ctx from './views/components/context-menu.vue';
 | 
				
			||||||
import PostFormWindow from './views/components/post-form-window.vue';
 | 
					 | 
				
			||||||
import RenoteFormWindow from './views/components/renote-form-window.vue';
 | 
					import RenoteFormWindow from './views/components/renote-form-window.vue';
 | 
				
			||||||
import MkChooseFileFromDriveWindow from './views/components/choose-file-from-drive-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 MkChooseFolderFromDriveWindow from './views/components/choose-folder-from-drive-window.vue';
 | 
				
			||||||
| 
						 | 
					@ -62,12 +60,13 @@ init(async (launch, os) => {
 | 
				
			||||||
					});
 | 
										});
 | 
				
			||||||
					if (o.cb) vm.$once('closed', o.cb);
 | 
										if (o.cb) vm.$once('closed', o.cb);
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					const vm = this.$root.new(PostFormWindow, {
 | 
										this.$root.newAsync(() => import('./views/components/post-form-window.vue').then(m => m.default), {
 | 
				
			||||||
						reply: o.reply,
 | 
											reply: o.reply,
 | 
				
			||||||
						mention: o.mention,
 | 
											mention: o.mention,
 | 
				
			||||||
						animation: o.animation == null ? true : o.animation
 | 
											animation: o.animation == null ? true : o.animation
 | 
				
			||||||
 | 
										}).then(vm => {
 | 
				
			||||||
 | 
											if (o.cb) vm.$once('closed', o.cb);
 | 
				
			||||||
					});
 | 
										});
 | 
				
			||||||
					if (o.cb) vm.$once('closed', o.cb);
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -129,7 +128,7 @@ init(async (launch, os) => {
 | 
				
			||||||
		mode: 'history',
 | 
							mode: 'history',
 | 
				
			||||||
		routes: [
 | 
							routes: [
 | 
				
			||||||
			os.store.state.device.inDeckMode
 | 
								os.store.state.device.inDeckMode
 | 
				
			||||||
				? { path: '/', name: 'index', component: MkDeck, children: [
 | 
									? { path: '/', name: 'index', component: () => import('../common/views/deck/deck.vue').then(m => m.default), children: [
 | 
				
			||||||
					{ path: '/@:user', component: () => import('../common/views/deck/deck.user-column.vue').then(m => m.default), children: [
 | 
										{ path: '/@:user', component: () => import('../common/views/deck/deck.user-column.vue').then(m => m.default), children: [
 | 
				
			||||||
						{ path: '', name: 'user', component: () => import('../common/views/deck/deck.user-column.home.vue').then(m => m.default) },
 | 
											{ path: '', name: 'user', component: () => import('../common/views/deck/deck.user-column.home.vue').then(m => m.default) },
 | 
				
			||||||
						{ path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) },
 | 
											{ path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) },
 | 
				
			||||||
| 
						 | 
					@ -138,10 +137,10 @@ init(async (launch, os) => {
 | 
				
			||||||
					{ path: '/notes/:note', name: 'note', component: () => import('../common/views/deck/deck.note-column.vue').then(m => m.default) },
 | 
										{ path: '/notes/:note', name: 'note', component: () => import('../common/views/deck/deck.note-column.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/search', component: () => import('../common/views/deck/deck.search-column.vue').then(m => m.default) },
 | 
										{ path: '/search', component: () => import('../common/views/deck/deck.search-column.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/tags/:tag', name: 'tag', component: () => import('../common/views/deck/deck.hashtag-column.vue').then(m => m.default) },
 | 
										{ path: '/tags/:tag', name: 'tag', component: () => import('../common/views/deck/deck.hashtag-column.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/featured', name: 'featured', component: () => import('../common/views/deck/deck.featured-column.vue').then(m => m.default) },
 | 
										{ path: '/featured', name: 'featured', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/featured.vue').then(m => m.default), platform: 'deck' }) },
 | 
				
			||||||
					{ path: '/explore', name: 'explore', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default) }) },
 | 
										{ path: '/explore', name: 'explore', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default) }) },
 | 
				
			||||||
					{ path: '/explore/tags/:tag', name: 'explore-tag', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default), tag: route.params.tag }) },
 | 
										{ path: '/explore/tags/:tag', name: 'explore-tag', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default), tag: route.params.tag }) },
 | 
				
			||||||
					{ path: '/i/favorites', component: () => import('../common/views/deck/deck.favorites-column.vue').then(m => m.default) },
 | 
										{ path: '/i/favorites', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/favorites.vue').then(m => m.default), platform: 'deck' }) },
 | 
				
			||||||
					{ path: '/i/pages', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) },
 | 
										{ path: '/i/pages', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) },
 | 
				
			||||||
					{ path: '/i/lists', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) },
 | 
										{ path: '/i/lists', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) },
 | 
				
			||||||
					{ path: '/i/lists/:listId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.listId }) },
 | 
										{ path: '/i/lists/:listId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.listId }) },
 | 
				
			||||||
| 
						 | 
					@ -158,10 +157,10 @@ init(async (launch, os) => {
 | 
				
			||||||
					{ path: '/notes/:note', name: 'note', component: () => import('./views/home/note.vue').then(m => m.default) },
 | 
										{ path: '/notes/:note', name: 'note', component: () => import('./views/home/note.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/search', component: () => import('./views/home/search.vue').then(m => m.default) },
 | 
										{ path: '/search', component: () => import('./views/home/search.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/tags/:tag', name: 'tag', component: () => import('./views/home/tag.vue').then(m => m.default) },
 | 
										{ path: '/tags/:tag', name: 'tag', component: () => import('./views/home/tag.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/featured', name: 'featured', component: () => import('./views/home/featured.vue').then(m => m.default) },
 | 
										{ path: '/featured', name: 'featured', component: () => import('../common/views/pages/featured.vue').then(m => m.default), props: { platform: 'desktop' } },
 | 
				
			||||||
					{ path: '/explore', name: 'explore', component: () => import('../common/views/pages/explore.vue').then(m => m.default) },
 | 
										{ path: '/explore', name: 'explore', component: () => import('../common/views/pages/explore.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/explore/tags/:tag', name: 'explore-tag', props: true, component: () => import('../common/views/pages/explore.vue').then(m => m.default) },
 | 
										{ path: '/explore/tags/:tag', name: 'explore-tag', props: true, component: () => import('../common/views/pages/explore.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/i/favorites', component: () => import('./views/home/favorites.vue').then(m => m.default) },
 | 
										{ path: '/i/favorites', component: () => import('../common/views/pages/favorites.vue').then(m => m.default), props: { platform: 'desktop' } },
 | 
				
			||||||
					{ path: '/i/pages', component: () => import('../common/views/pages/pages.vue').then(m => m.default) },
 | 
										{ path: '/i/pages', component: () => import('../common/views/pages/pages.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/i/lists', component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) },
 | 
										{ path: '/i/lists', component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/i/lists/:listId', props: true, component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default) },
 | 
										{ path: '/i/lists/:listId', props: true, component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default) },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										56
									
								
								src/client/app/desktop/views/components/detail-notes.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/client/app/desktop/views/components/detail-notes.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,56 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div class="ecsvsegy" v-if="!fetching">
 | 
				
			||||||
 | 
						<sequential-entrance animation="entranceFromTop" delay="25">
 | 
				
			||||||
 | 
							<template v-for="note in notes">
 | 
				
			||||||
 | 
								<mk-note-detail class="post" :note="note" :key="note.id"/>
 | 
				
			||||||
 | 
							</template>
 | 
				
			||||||
 | 
						</sequential-entrance>
 | 
				
			||||||
 | 
						<div class="more" v-if="more">
 | 
				
			||||||
 | 
							<ui-button inline @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					import paging from '../../../common/scripts/paging';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						i18n: i18n(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mixins: [
 | 
				
			||||||
 | 
							paging({
 | 
				
			||||||
 | 
								captureWindowScroll: true,
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							pagination: {
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							extract: {
 | 
				
			||||||
 | 
								required: false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						computed: {
 | 
				
			||||||
 | 
							notes() {
 | 
				
			||||||
 | 
								return this.extract ? this.extract(this.items) : this.items;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					.ecsvsegy
 | 
				
			||||||
 | 
						margin 0 auto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> * > .post
 | 
				
			||||||
 | 
							margin-bottom 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> .more
 | 
				
			||||||
 | 
							margin 32px 16px 16px 16px
 | 
				
			||||||
 | 
							text-align center
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -6,11 +6,9 @@ import note from './note.vue';
 | 
				
			||||||
import notes from './notes.vue';
 | 
					import notes from './notes.vue';
 | 
				
			||||||
import subNoteContent from './sub-note-content.vue';
 | 
					import subNoteContent from './sub-note-content.vue';
 | 
				
			||||||
import window from './window.vue';
 | 
					import window from './window.vue';
 | 
				
			||||||
import noteFormWindow from './post-form-window.vue';
 | 
					 | 
				
			||||||
import renoteFormWindow from './renote-form-window.vue';
 | 
					import renoteFormWindow from './renote-form-window.vue';
 | 
				
			||||||
import mediaVideo from './media-video.vue';
 | 
					import mediaVideo from './media-video.vue';
 | 
				
			||||||
import notifications from './notifications.vue';
 | 
					import notifications from './notifications.vue';
 | 
				
			||||||
import noteForm from './post-form.vue';
 | 
					 | 
				
			||||||
import renoteForm from './renote-form.vue';
 | 
					import renoteForm from './renote-form.vue';
 | 
				
			||||||
import notePreview from './note-preview.vue';
 | 
					import notePreview from './note-preview.vue';
 | 
				
			||||||
import noteDetail from './note-detail.vue';
 | 
					import noteDetail from './note-detail.vue';
 | 
				
			||||||
| 
						 | 
					@ -25,11 +23,9 @@ Vue.component('mk-note', note);
 | 
				
			||||||
Vue.component('mk-notes', notes);
 | 
					Vue.component('mk-notes', notes);
 | 
				
			||||||
Vue.component('mk-sub-note-content', subNoteContent);
 | 
					Vue.component('mk-sub-note-content', subNoteContent);
 | 
				
			||||||
Vue.component('mk-window', window);
 | 
					Vue.component('mk-window', window);
 | 
				
			||||||
Vue.component('mk-post-form-window', noteFormWindow);
 | 
					 | 
				
			||||||
Vue.component('mk-renote-form-window', renoteFormWindow);
 | 
					Vue.component('mk-renote-form-window', renoteFormWindow);
 | 
				
			||||||
Vue.component('mk-media-video', mediaVideo);
 | 
					Vue.component('mk-media-video', mediaVideo);
 | 
				
			||||||
Vue.component('mk-notifications', notifications);
 | 
					Vue.component('mk-notifications', notifications);
 | 
				
			||||||
Vue.component('mk-post-form', noteForm);
 | 
					 | 
				
			||||||
Vue.component('mk-renote-form', renoteForm);
 | 
					Vue.component('mk-renote-form', renoteForm);
 | 
				
			||||||
Vue.component('mk-note-preview', notePreview);
 | 
					Vue.component('mk-note-preview', notePreview);
 | 
				
			||||||
Vue.component('mk-note-detail', noteDetail);
 | 
					Vue.component('mk-note-detail', noteDetail);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,9 +4,9 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="newer-indicator" :style="{ top: $store.state.uiHeaderHeight + 'px' }" v-show="queue.length > 0"></div>
 | 
						<div class="newer-indicator" :style="{ top: $store.state.uiHeaderHeight + 'px' }" v-show="queue.length > 0"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="empty" v-if="notes.length == 0 && !fetching && inited">{{ $t('@.no-notes') }}</div>
 | 
						<div class="empty" v-if="empty">{{ $t('@.no-notes') }}</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<mk-error v-if="!fetching && !inited" @retry="init()"/>
 | 
						<mk-error v-if="error" @retry="init()"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="placeholder" v-if="fetching">
 | 
						<div class="placeholder" v-if="fetching">
 | 
				
			||||||
		<template v-for="i in 10">
 | 
							<template v-for="i in 10">
 | 
				
			||||||
| 
						 | 
					@ -17,8 +17,8 @@
 | 
				
			||||||
	<!-- トランジションを有効にするとなぜかメモリリークする -->
 | 
						<!-- トランジションを有効にするとなぜかメモリリークする -->
 | 
				
			||||||
	<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div" ref="notes">
 | 
						<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div" ref="notes">
 | 
				
			||||||
		<template v-for="(note, i) in _notes">
 | 
							<template v-for="(note, i) in _notes">
 | 
				
			||||||
			<mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" :compact="true" ref="note"/>
 | 
								<mk-note :note="note" :key="note.id" :compact="true" ref="note"/>
 | 
				
			||||||
			<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
 | 
								<p class="date" :key="note.id + '_date'" v-if="i != items.length - 1 && note._date != _notes[i + 1]._date">
 | 
				
			||||||
				<span><fa icon="angle-up"/>{{ note._datetext }}</span>
 | 
									<span><fa icon="angle-up"/>{{ note._datetext }}</span>
 | 
				
			||||||
				<span><fa icon="angle-down"/>{{ _notes[i + 1]._datetext }}</span>
 | 
									<span><fa icon="angle-down"/>{{ _notes[i + 1]._datetext }}</span>
 | 
				
			||||||
			</p>
 | 
								</p>
 | 
				
			||||||
| 
						 | 
					@ -39,153 +39,66 @@ import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import * as config from '../../../config';
 | 
					import * as config from '../../../config';
 | 
				
			||||||
import shouldMuteNote from '../../../common/scripts/should-mute-note';
 | 
					import shouldMuteNote from '../../../common/scripts/should-mute-note';
 | 
				
			||||||
 | 
					import paging from '../../../common/scripts/paging';
 | 
				
			||||||
const displayLimit = 30;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n(),
 | 
						i18n: i18n(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						mixins: [
 | 
				
			||||||
		makePromise: {
 | 
							paging({
 | 
				
			||||||
			required: true
 | 
								captureWindowScroll: true,
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
								onQueueChanged: (self, x) => {
 | 
				
			||||||
		return {
 | 
									if (x.length > 0) {
 | 
				
			||||||
			notes: [],
 | 
										self.$store.commit('indicate', true);
 | 
				
			||||||
			queue: [],
 | 
									} else {
 | 
				
			||||||
			fetching: true,
 | 
										self.$store.commit('indicate', false);
 | 
				
			||||||
			moreFetching: false,
 | 
									}
 | 
				
			||||||
			inited: false,
 | 
								},
 | 
				
			||||||
			more: false
 | 
					
 | 
				
			||||||
		};
 | 
								onPrepend: (self, note, silent) => {
 | 
				
			||||||
 | 
									// 弾く
 | 
				
			||||||
 | 
									if (shouldMuteNote(self.$store.state.i, self.$store.state.settings, note)) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
 | 
				
			||||||
 | 
									if (document.hidden || !self.isScrollTop()) {
 | 
				
			||||||
 | 
										self.$store.commit('pushBehindNote', note);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (self.isScrollTop()) {
 | 
				
			||||||
 | 
										// サウンドを再生する
 | 
				
			||||||
 | 
										if (self.$store.state.device.enableSounds && !silent) {
 | 
				
			||||||
 | 
											const sound = new Audio(`${config.url}/assets/post.mp3`);
 | 
				
			||||||
 | 
											sound.volume = self.$store.state.device.soundVolume;
 | 
				
			||||||
 | 
											sound.play();
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							pagination: {
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		_notes(): any[] {
 | 
							_notes(): any[] {
 | 
				
			||||||
			return (this.notes as any).map(note => {
 | 
								return (this.items as any).map(item => {
 | 
				
			||||||
				const date = new Date(note.createdAt).getDate();
 | 
									const date = new Date(item.createdAt).getDate();
 | 
				
			||||||
				const month = new Date(note.createdAt).getMonth() + 1;
 | 
									const month = new Date(item.createdAt).getMonth() + 1;
 | 
				
			||||||
				note._date = date;
 | 
									item._date = date;
 | 
				
			||||||
				note._datetext = this.$t('@.month-and-day').replace('{month}', month.toString()).replace('{day}', date.toString());
 | 
									item._datetext = this.$t('@.month-and-day').replace('{month}', month.toString()).replace('{day}', date.toString());
 | 
				
			||||||
				return note;
 | 
									return item;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	created() {
 | 
					 | 
				
			||||||
		this.init();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	mounted() {
 | 
					 | 
				
			||||||
		window.addEventListener('scroll', this.onScroll, { passive: true });
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	beforeDestroy() {
 | 
					 | 
				
			||||||
		window.removeEventListener('scroll', this.onScroll);
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		isScrollTop() {
 | 
					 | 
				
			||||||
			return window.scrollY <= 8;
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		focus() {
 | 
							focus() {
 | 
				
			||||||
			(this.$refs.notes as any).children[0].focus ? (this.$refs.notes as any).children[0].focus() : (this.$refs.notes as any).$el.children[0].focus();
 | 
								(this.$refs.notes as any).children[0].focus ? (this.$refs.notes as any).children[0].focus() : (this.$refs.notes as any).$el.children[0].focus();
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					 | 
				
			||||||
		onNoteUpdated(i, note) {
 | 
					 | 
				
			||||||
			Vue.set((this as any).notes, i, note);
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		reload() {
 | 
					 | 
				
			||||||
			this.queue = [];
 | 
					 | 
				
			||||||
			this.notes = [];
 | 
					 | 
				
			||||||
			this.init();
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		async init() {
 | 
					 | 
				
			||||||
			this.fetching = true;
 | 
					 | 
				
			||||||
			await (this.makePromise()).then(x => {
 | 
					 | 
				
			||||||
				if (Array.isArray(x)) {
 | 
					 | 
				
			||||||
					this.notes = x;
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					this.notes = x.notes;
 | 
					 | 
				
			||||||
					this.more = x.more;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				this.inited = true;
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
				this.$emit('inited');
 | 
					 | 
				
			||||||
			}, e => {
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		async fetchMore() {
 | 
					 | 
				
			||||||
			if (!this.more || this.moreFetching || this.notes.length === 0) return;
 | 
					 | 
				
			||||||
			this.moreFetching = true;
 | 
					 | 
				
			||||||
			this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
 | 
					 | 
				
			||||||
				this.notes = this.notes.concat(x.notes);
 | 
					 | 
				
			||||||
				this.more = x.more;
 | 
					 | 
				
			||||||
				this.moreFetching = false;
 | 
					 | 
				
			||||||
			}, e => {
 | 
					 | 
				
			||||||
				this.moreFetching = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		prepend(note, silent = false) {
 | 
					 | 
				
			||||||
			// 弾く
 | 
					 | 
				
			||||||
			if (shouldMuteNote(this.$store.state.i, this.$store.state.settings, note)) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
 | 
					 | 
				
			||||||
			if (document.hidden || !this.isScrollTop()) {
 | 
					 | 
				
			||||||
				this.$store.commit('pushBehindNote', note);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (this.isScrollTop()) {
 | 
					 | 
				
			||||||
				// Prepend the note
 | 
					 | 
				
			||||||
				this.notes.unshift(note);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				// サウンドを再生する
 | 
					 | 
				
			||||||
				if (this.$store.state.device.enableSounds && !silent) {
 | 
					 | 
				
			||||||
					const sound = new Audio(`${config.url}/assets/post.mp3`);
 | 
					 | 
				
			||||||
					sound.volume = this.$store.state.device.soundVolume;
 | 
					 | 
				
			||||||
					sound.play();
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				// オーバーフローしたら古い投稿は捨てる
 | 
					 | 
				
			||||||
				if (this.notes.length >= displayLimit) {
 | 
					 | 
				
			||||||
					this.notes = this.notes.slice(0, displayLimit);
 | 
					 | 
				
			||||||
					this.more = true;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				this.queue.push(note);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		append(note) {
 | 
					 | 
				
			||||||
			this.notes.push(note);
 | 
					 | 
				
			||||||
			this.cursor = this.notes[this.notes.length - 1].id
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		releaseQueue() {
 | 
					 | 
				
			||||||
			for (const n of this.queue) {
 | 
					 | 
				
			||||||
				this.prepend(n, true);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			this.queue = [];
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		onScroll() {
 | 
					 | 
				
			||||||
			if (this.isScrollTop()) {
 | 
					 | 
				
			||||||
				this.releaseQueue();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (this.$store.state.settings.fetchOnScroll) {
 | 
					 | 
				
			||||||
				const current = window.scrollY + window.innerHeight;
 | 
					 | 
				
			||||||
				if (current > document.body.offsetHeight - 8) this.fetchMore();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@
 | 
				
			||||||
		</template>
 | 
							</template>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="notifications" v-if="notifications.length != 0">
 | 
						<div class="notifications" v-if="!empty">
 | 
				
			||||||
		<!-- トランジションを有効にするとなぜかメモリリークする -->
 | 
							<!-- トランジションを有効にするとなぜかメモリリークする -->
 | 
				
			||||||
		<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition" tag="div">
 | 
							<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition" tag="div">
 | 
				
			||||||
			<template v-for="(notification, i) in _notifications">
 | 
								<template v-for="(notification, i) in _notifications">
 | 
				
			||||||
| 
						 | 
					@ -125,17 +125,18 @@
 | 
				
			||||||
					</template>
 | 
										</template>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				<p class="date" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date" :key="notification.id + '-time'">
 | 
									<p class="date" v-if="i != items.length - 1 && notification._date != _notifications[i + 1]._date" :key="notification.id + '-time'">
 | 
				
			||||||
					<span><fa icon="angle-up"/>{{ notification._datetext }}</span>
 | 
										<span><fa icon="angle-up"/>{{ notification._datetext }}</span>
 | 
				
			||||||
					<span><fa icon="angle-down"/>{{ _notifications[i + 1]._datetext }}</span>
 | 
										<span><fa icon="angle-down"/>{{ _notifications[i + 1]._datetext }}</span>
 | 
				
			||||||
				</p>
 | 
									</p>
 | 
				
			||||||
			</template>
 | 
								</template>
 | 
				
			||||||
		</component>
 | 
							</component>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
 | 
						<button class="more" :class="{ fetching: moreFetching }" v-if="more" @click="fetchMore" :disabled="moreFetching">
 | 
				
			||||||
		<template v-if="fetchingMoreNotifications"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreNotifications ? $t('@.loading') : $t('@.load-more') }}
 | 
							<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>{{ moreFetching ? $t('@.loading') : $t('@.load-more') }}
 | 
				
			||||||
	</button>
 | 
						</button>
 | 
				
			||||||
	<p class="empty" v-if="notifications.length == 0 && !fetching">{{ $t('empty') }}</p>
 | 
						<p class="empty" v-if="empty">{{ $t('empty') }}</p>
 | 
				
			||||||
 | 
						<mk-error v-if="error" @retry="init()"/>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -143,23 +144,29 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import getNoteSummary from '../../../../../misc/get-note-summary';
 | 
					import getNoteSummary from '../../../../../misc/get-note-summary';
 | 
				
			||||||
 | 
					import paging from '../../../common/scripts/paging';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n(),
 | 
						i18n: i18n(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mixins: [
 | 
				
			||||||
 | 
							paging({}),
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			fetching: true,
 | 
					 | 
				
			||||||
			fetchingMoreNotifications: false,
 | 
					 | 
				
			||||||
			notifications: [],
 | 
					 | 
				
			||||||
			moreNotifications: false,
 | 
					 | 
				
			||||||
			connection: null,
 | 
								connection: null,
 | 
				
			||||||
			getNoteSummary
 | 
								getNoteSummary,
 | 
				
			||||||
 | 
								pagination: {
 | 
				
			||||||
 | 
									endpoint: 'i/notifications',
 | 
				
			||||||
 | 
									limit: 10,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		_notifications(): any[] {
 | 
							_notifications(): any[] {
 | 
				
			||||||
			return (this.notifications as any).map(notification => {
 | 
								return (this.items as any).map(notification => {
 | 
				
			||||||
				const date = new Date(notification.createdAt).getDate();
 | 
									const date = new Date(notification.createdAt).getDate();
 | 
				
			||||||
				const month = new Date(notification.createdAt).getMonth() + 1;
 | 
									const month = new Date(notification.createdAt).getMonth() + 1;
 | 
				
			||||||
				notification._date = date;
 | 
									notification._date = date;
 | 
				
			||||||
| 
						 | 
					@ -171,22 +178,7 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		this.connection = this.$root.stream.useSharedConnection('main');
 | 
							this.connection = this.$root.stream.useSharedConnection('main');
 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.connection.on('notification', this.onNotification);
 | 
							this.connection.on('notification', this.onNotification);
 | 
				
			||||||
 | 
					 | 
				
			||||||
		const max = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.$root.api('i/notifications', {
 | 
					 | 
				
			||||||
			limit: max + 1
 | 
					 | 
				
			||||||
		}).then(notifications => {
 | 
					 | 
				
			||||||
			if (notifications.length == max + 1) {
 | 
					 | 
				
			||||||
				this.moreNotifications = true;
 | 
					 | 
				
			||||||
				notifications.pop();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.notifications = notifications;
 | 
					 | 
				
			||||||
			this.fetching = false;
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	beforeDestroy() {
 | 
						beforeDestroy() {
 | 
				
			||||||
| 
						 | 
					@ -194,33 +186,13 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		fetchMoreNotifications() {
 | 
					 | 
				
			||||||
			this.fetchingMoreNotifications = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const max = 30;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$root.api('i/notifications', {
 | 
					 | 
				
			||||||
				limit: max + 1,
 | 
					 | 
				
			||||||
				untilId: this.notifications[this.notifications.length - 1].id
 | 
					 | 
				
			||||||
			}).then(notifications => {
 | 
					 | 
				
			||||||
				if (notifications.length == max + 1) {
 | 
					 | 
				
			||||||
					this.moreNotifications = true;
 | 
					 | 
				
			||||||
					notifications.pop();
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					this.moreNotifications = false;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				this.notifications = this.notifications.concat(notifications);
 | 
					 | 
				
			||||||
				this.fetchingMoreNotifications = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		onNotification(notification) {
 | 
							onNotification(notification) {
 | 
				
			||||||
			// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
 | 
								// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
 | 
				
			||||||
			this.$root.stream.send('readNotification', {
 | 
								this.$root.stream.send('readNotification', {
 | 
				
			||||||
				id: notification.id
 | 
									id: notification.id
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.notifications.unshift(notification);
 | 
								this.prepend(notification);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="mk-post-form-window--body">
 | 
						<div class="mk-post-form-window--body">
 | 
				
			||||||
		<mk-note-preview v-if="reply" class="notePreview" :note="reply"/>
 | 
							<mk-note-preview v-if="reply" class="notePreview" :note="reply"/>
 | 
				
			||||||
		<mk-post-form ref="form"
 | 
							<x-post-form ref="form"
 | 
				
			||||||
			:reply="reply"
 | 
								:reply="reply"
 | 
				
			||||||
			:mention="mention"
 | 
								:mention="mention"
 | 
				
			||||||
			@posted="onPosted"
 | 
								@posted="onPosted"
 | 
				
			||||||
| 
						 | 
					@ -27,9 +27,15 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					import XPostForm from './post-form.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('desktop/views/components/post-form-window.vue'),
 | 
						i18n: i18n('desktop/views/components/post-form-window.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						components: {
 | 
				
			||||||
 | 
							XPostForm
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
		reply: {
 | 
							reply: {
 | 
				
			||||||
			type: Object,
 | 
								type: Object,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="mk-post-form"
 | 
					<div class="gjisdzwh"
 | 
				
			||||||
	@dragover.stop="onDragover"
 | 
						@dragover.stop="onDragover"
 | 
				
			||||||
	@dragenter="onDragenter"
 | 
						@dragenter="onDragenter"
 | 
				
			||||||
	@dragleave="onDragleave"
 | 
						@dragleave="onDragleave"
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,7 @@
 | 
				
			||||||
				<fa :icon="['far', 'laugh']"/>
 | 
									<fa :icon="['far', 'laugh']"/>
 | 
				
			||||||
			</button>
 | 
								</button>
 | 
				
			||||||
			<x-post-form-attaches class="files" :class="{ with: poll }" :files="files"/>
 | 
								<x-post-form-attaches class="files" :class="{ with: poll }" :files="files"/>
 | 
				
			||||||
			<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/>
 | 
								<x-poll-editor class="poll-editor" v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
 | 
						<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
 | 
				
			||||||
| 
						 | 
					@ -72,7 +72,8 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		MkVisibilityChooser,
 | 
							MkVisibilityChooser,
 | 
				
			||||||
		XPostFormAttaches
 | 
							XPostFormAttaches,
 | 
				
			||||||
 | 
							XPollEditor: () => import('../../../common/views/components/poll-editor.vue').then(m => m.default)
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
| 
						 | 
					@ -518,7 +519,7 @@ export default Vue.extend({
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
.mk-post-form
 | 
					.gjisdzwh
 | 
				
			||||||
	display block
 | 
						display block
 | 
				
			||||||
	padding 16px
 | 
						padding 16px
 | 
				
			||||||
	background var(--desktopPostFormBg)
 | 
						background var(--desktopPostFormBg)
 | 
				
			||||||
| 
						 | 
					@ -617,7 +618,7 @@ export default Vue.extend({
 | 
				
			||||||
					border-bottom solid 1px var(--primaryAlpha01) !important
 | 
										border-bottom solid 1px var(--primaryAlpha01) !important
 | 
				
			||||||
					border-radius 0
 | 
										border-radius 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			> .mk-poll-editor
 | 
								> .poll-editor
 | 
				
			||||||
				background var(--desktopPostFormTextareaBg)
 | 
									background var(--desktopPostFormTextareaBg)
 | 
				
			||||||
				border solid 1px var(--primaryAlpha01)
 | 
									border solid 1px var(--primaryAlpha01)
 | 
				
			||||||
				border-top none
 | 
									border-top none
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@
 | 
				
			||||||
		</footer>
 | 
							</footer>
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
	<template v-if="quote">
 | 
						<template v-if="quote">
 | 
				
			||||||
		<mk-post-form ref="form" :renote="note" @posted="onChildFormPosted"/>
 | 
							<x-post-form ref="form" :renote="note" @posted="onChildFormPosted"/>
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -22,6 +22,10 @@ import i18n from '../../../i18n';
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('desktop/views/components/renote-form.vue'),
 | 
						i18n: i18n('desktop/views/components/renote-form.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						components: {
 | 
				
			||||||
 | 
							XPostForm: () => import('./post-form.vue').then(m => m.default)
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
		note: {
 | 
							note: {
 | 
				
			||||||
			type: Object,
 | 
								type: Object,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div>
 | 
					<div>
 | 
				
			||||||
	<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')">
 | 
						<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')">
 | 
				
			||||||
		<template #header>
 | 
							<template #header>
 | 
				
			||||||
			<slot></slot>
 | 
								<slot></slot>
 | 
				
			||||||
		</template>
 | 
							</template>
 | 
				
			||||||
| 
						 | 
					@ -11,36 +11,23 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fetchLimit = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	props: ['list'],
 | 
						props: ['list'],
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			connection: null,
 | 
								connection: null,
 | 
				
			||||||
			date: null,
 | 
								date: null,
 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/user-list-timeline', {
 | 
								pagination: {
 | 
				
			||||||
				listId: this.list.id,
 | 
									endpoint: 'notes/user-list-timeline',
 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
									limit: 10,
 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
									params: init => ({
 | 
				
			||||||
				untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
										listId: this.list.id,
 | 
				
			||||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
										untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
										includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
				
			||||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
										includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
				
			||||||
			}).then(notes => {
 | 
										includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
				
			||||||
				if (notes.length == fetchLimit + 1) {
 | 
									})
 | 
				
			||||||
					notes.pop();
 | 
								}
 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: true
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	watch: {
 | 
						watch: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,83 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<div class="ecsvsegy" v-if="!fetching">
 | 
					 | 
				
			||||||
	<sequential-entrance animation="entranceFromTop" delay="25">
 | 
					 | 
				
			||||||
		<template v-for="favorite in favorites">
 | 
					 | 
				
			||||||
			<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
 | 
					 | 
				
			||||||
		</template>
 | 
					 | 
				
			||||||
	</sequential-entrance>
 | 
					 | 
				
			||||||
	<div class="more" v-if="existMore">
 | 
					 | 
				
			||||||
		<ui-button inline @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n('.vue'),
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			fetching: true,
 | 
					 | 
				
			||||||
			favorites: [],
 | 
					 | 
				
			||||||
			existMore: false,
 | 
					 | 
				
			||||||
			moreFetching: false
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	created() {
 | 
					 | 
				
			||||||
		this.fetch();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		fetch() {
 | 
					 | 
				
			||||||
			Progress.start();
 | 
					 | 
				
			||||||
			this.fetching = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$root.api('i/favorites', {
 | 
					 | 
				
			||||||
				limit: 11
 | 
					 | 
				
			||||||
			}).then(favorites => {
 | 
					 | 
				
			||||||
				if (favorites.length == 11) {
 | 
					 | 
				
			||||||
					this.existMore = true;
 | 
					 | 
				
			||||||
					favorites.pop();
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				this.favorites = favorites;
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				Progress.done();
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		fetchMore() {
 | 
					 | 
				
			||||||
			this.moreFetching = true;
 | 
					 | 
				
			||||||
			this.$root.api('i/favorites', {
 | 
					 | 
				
			||||||
				limit: 11,
 | 
					 | 
				
			||||||
				untilId: this.favorites[this.favorites.length - 1].id
 | 
					 | 
				
			||||||
			}).then(favorites => {
 | 
					 | 
				
			||||||
				if (favorites.length == 11) {
 | 
					 | 
				
			||||||
					this.existMore = true;
 | 
					 | 
				
			||||||
					favorites.pop();
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					this.existMore = false;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				this.favorites = this.favorites.concat(favorites);
 | 
					 | 
				
			||||||
				this.moreFetching = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
.ecsvsegy
 | 
					 | 
				
			||||||
	margin 0 auto
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> * > .post
 | 
					 | 
				
			||||||
		margin-bottom 16px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> .more
 | 
					 | 
				
			||||||
		margin 32px 16px 16px 16px
 | 
					 | 
				
			||||||
		text-align center
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,55 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<div class="glowckho" v-if="!fetching">
 | 
					 | 
				
			||||||
	<sequential-entrance animation="entranceFromTop" delay="25">
 | 
					 | 
				
			||||||
		<template v-for="note in notes">
 | 
					 | 
				
			||||||
			<mk-note-detail class="post" :note="note" :key="note.id"/>
 | 
					 | 
				
			||||||
		</template>
 | 
					 | 
				
			||||||
	</sequential-entrance>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			fetching: true,
 | 
					 | 
				
			||||||
			notes: [],
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	created() {
 | 
					 | 
				
			||||||
		this.fetch();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		fetch() {
 | 
					 | 
				
			||||||
			Progress.start();
 | 
					 | 
				
			||||||
			this.fetching = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$root.api('notes/featured', {
 | 
					 | 
				
			||||||
				limit: 30
 | 
					 | 
				
			||||||
			}).then(notes => {
 | 
					 | 
				
			||||||
				notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
 | 
					 | 
				
			||||||
				this.notes = notes;
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				Progress.done();
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
.glowckho
 | 
					 | 
				
			||||||
	margin 0 auto
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> * > .post
 | 
					 | 
				
			||||||
		margin-bottom 16px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> .more
 | 
					 | 
				
			||||||
		margin 32px 16px 16px 16px
 | 
					 | 
				
			||||||
		text-align center
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div>
 | 
					<div>
 | 
				
			||||||
	<mk-notes ref="timeline" :make-promise="makePromise" @inited="inited">
 | 
						<mk-notes ref="timeline" :pagination="pagination" @inited="inited">
 | 
				
			||||||
		<template #header>
 | 
							<template #header>
 | 
				
			||||||
			<header class="oxgbmvii">
 | 
								<header class="oxgbmvii">
 | 
				
			||||||
				<span><fa icon="search"/> {{ q }}</span>
 | 
									<span><fa icon="search"/> {{ q }}</span>
 | 
				
			||||||
| 
						 | 
					@ -16,30 +16,15 @@ import i18n from '../../../i18n';
 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					import Progress from '../../../common/scripts/loading';
 | 
				
			||||||
import { genSearchQuery } from '../../../common/scripts/gen-search-query';
 | 
					import { genSearchQuery } from '../../../common/scripts/gen-search-query';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const limit = 20;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('desktop/views/pages/search.vue'),
 | 
						i18n: i18n('desktop/views/pages/search.vue'),
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			makePromise: async cursor => this.$root.api('notes/search', {
 | 
								pagination: {
 | 
				
			||||||
				limit: limit + 1,
 | 
									endpoint: 'notes/search',
 | 
				
			||||||
				offset: cursor ? cursor : undefined,
 | 
									limit: 20,
 | 
				
			||||||
				...(await genSearchQuery(this, this.q))
 | 
									params: () => genSearchQuery(this, this.q)
 | 
				
			||||||
			}).then(notes => {
 | 
								}
 | 
				
			||||||
				if (notes.length == limit + 1) {
 | 
					 | 
				
			||||||
					notes.pop();
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						cursor: cursor ? cursor + limit : limit
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div>
 | 
					<div>
 | 
				
			||||||
	<mk-notes ref="timeline" :make-promise="makePromise" @inited="inited">
 | 
						<mk-notes ref="timeline" :pagination="pagination" @inited="inited">
 | 
				
			||||||
		<template #header>
 | 
							<template #header>
 | 
				
			||||||
			<header class="wqraeznr">
 | 
								<header class="wqraeznr">
 | 
				
			||||||
				<span><fa icon="hashtag"/> {{ $route.params.tag }}</span>
 | 
									<span><fa icon="hashtag"/> {{ $route.params.tag }}</span>
 | 
				
			||||||
| 
						 | 
					@ -15,30 +15,17 @@ import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					import Progress from '../../../common/scripts/loading';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const limit = 20;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('desktop/views/pages/tag.vue'),
 | 
						i18n: i18n('desktop/views/pages/tag.vue'),
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/search-by-tag', {
 | 
								pagination: {
 | 
				
			||||||
				limit: limit + 1,
 | 
									endpoint: 'notes/search-by-tag',
 | 
				
			||||||
				offset: cursor ? cursor : undefined,
 | 
									limit: 20,
 | 
				
			||||||
				tag: this.$route.params.tag
 | 
									params: {
 | 
				
			||||||
			}).then(notes => {
 | 
										tag: this.$route.params.tag
 | 
				
			||||||
				if (notes.length == limit + 1) {
 | 
					 | 
				
			||||||
					notes.pop();
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						cursor: cursor ? cursor + limit : limit
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			})
 | 
								}
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	watch: {
 | 
						watch: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div>
 | 
					<div>
 | 
				
			||||||
	<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')">
 | 
						<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')">
 | 
				
			||||||
		<template #header>
 | 
							<template #header>
 | 
				
			||||||
			<slot></slot>
 | 
								<slot></slot>
 | 
				
			||||||
			<div v-if="src == 'home' && alone" class="ibpylqas">
 | 
								<div v-if="src == 'home' && alone" class="ibpylqas">
 | 
				
			||||||
| 
						 | 
					@ -16,8 +16,6 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fetchLimit = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('desktop/views/components/timeline.core.vue'),
 | 
						i18n: i18n('desktop/views/components/timeline.core.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,7 +40,7 @@ export default Vue.extend({
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			query: {},
 | 
								query: {},
 | 
				
			||||||
			endpoint: null,
 | 
								endpoint: null,
 | 
				
			||||||
			makePromise: null
 | 
								pagination: null
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -109,25 +107,14 @@ export default Vue.extend({
 | 
				
			||||||
			this.connection.on('mention', onNote);
 | 
								this.connection.on('mention', onNote);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.makePromise = cursor => this.$root.api(this.endpoint, {
 | 
							this.pagination = {
 | 
				
			||||||
			limit: fetchLimit + 1,
 | 
								endpoint: this.endpoint,
 | 
				
			||||||
			untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
								limit: 10,
 | 
				
			||||||
			untilId: cursor ? cursor : undefined,
 | 
								params: init => ({
 | 
				
			||||||
			...this.baseQuery, ...this.query
 | 
									untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
		}).then(notes => {
 | 
									...this.baseQuery, ...this.query
 | 
				
			||||||
			if (notes.length == fetchLimit + 1) {
 | 
								})
 | 
				
			||||||
				notes.pop();
 | 
							};
 | 
				
			||||||
				return {
 | 
					 | 
				
			||||||
					notes: notes,
 | 
					 | 
				
			||||||
					more: true
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				return {
 | 
					 | 
				
			||||||
					notes: notes,
 | 
					 | 
				
			||||||
					more: false
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="pwbzawku">
 | 
					<div class="pwbzawku">
 | 
				
			||||||
	<mk-post-form class="form" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }" v-if="$store.state.settings.showPostFormOnTopOfTl"/>
 | 
						<x-post-form class="form" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }" v-if="$store.state.settings.showPostFormOnTopOfTl"/>
 | 
				
			||||||
	<div class="main">
 | 
						<div class="main">
 | 
				
			||||||
		<component :is="src == 'list' ? 'mk-user-list-timeline' : 'x-core'" ref="tl" v-bind="options">
 | 
							<component :is="src == 'list' ? 'mk-user-list-timeline' : 'x-core'" ref="tl" v-bind="options">
 | 
				
			||||||
			<header class="zahtxcqi">
 | 
								<header class="zahtxcqi">
 | 
				
			||||||
| 
						 | 
					@ -31,8 +31,10 @@ import MkSettingsWindow from '../components/settings-window.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('desktop/views/components/timeline.vue'),
 | 
						i18n: i18n('desktop/views/components/timeline.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XCore
 | 
							XCore,
 | 
				
			||||||
 | 
							XPostForm: () => import('../components/post-form.vue').then(m => m.default)
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div>
 | 
					<div>
 | 
				
			||||||
	<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')">
 | 
						<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')">
 | 
				
			||||||
		<template #header>
 | 
							<template #header>
 | 
				
			||||||
			<header class="oh5y2r7l5lx8j6jj791ykeiwgihheguk">
 | 
								<header class="kugajpep">
 | 
				
			||||||
				<span :data-active="mode == 'default'" @click="mode = 'default'"><fa :icon="['far', 'comment-alt']"/> {{ $t('default') }}</span>
 | 
									<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-replies'" @click="mode = 'with-replies'"><fa icon="comments"/> {{ $t('with-replies') }}</span>
 | 
				
			||||||
				<span :data-active="mode == 'with-media'" @click="mode = 'with-media'"><fa :icon="['far', 'images']"/> {{ $t('with-media') }}</span>
 | 
									<span :data-active="mode == 'with-media'" @click="mode = 'with-media'"><fa :icon="['far', 'images']"/> {{ $t('with-media') }}</span>
 | 
				
			||||||
| 
						 | 
					@ -17,8 +17,6 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../../i18n';
 | 
					import i18n from '../../../../i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fetchLimit = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('desktop/views/pages/user/user.timeline.vue'),
 | 
						i18n: i18n('desktop/views/pages/user/user.timeline.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,28 +28,17 @@ export default Vue.extend({
 | 
				
			||||||
			mode: 'default',
 | 
								mode: 'default',
 | 
				
			||||||
			unreadCount: 0,
 | 
								unreadCount: 0,
 | 
				
			||||||
			date: null,
 | 
								date: null,
 | 
				
			||||||
			makePromise: cursor => this.$root.api('users/notes', {
 | 
								pagination: {
 | 
				
			||||||
				userId: this.user.id,
 | 
									endpoint: 'users/notes',
 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
									limit: 10,
 | 
				
			||||||
				includeReplies: this.mode == 'with-replies',
 | 
									params: init => ({
 | 
				
			||||||
				includeMyRenotes: this.mode != 'my-posts',
 | 
										userId: this.user.id,
 | 
				
			||||||
				withFiles: this.mode == 'with-media',
 | 
										untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
				untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
										includeReplies: this.mode == 'with-replies',
 | 
				
			||||||
				untilId: cursor ? cursor : undefined
 | 
										includeMyRenotes: this.mode != 'my-posts',
 | 
				
			||||||
			}).then(notes => {
 | 
										withFiles: this.mode == 'with-media',
 | 
				
			||||||
				if (notes.length == fetchLimit + 1) {
 | 
									})
 | 
				
			||||||
					notes.pop();
 | 
								}
 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: true
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -88,7 +75,7 @@ export default Vue.extend({
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
.oh5y2r7l5lx8j6jj791ykeiwgihheguk
 | 
					.kugajpep
 | 
				
			||||||
	padding 0 8px
 | 
						padding 0 8px
 | 
				
			||||||
	z-index 10
 | 
						z-index 10
 | 
				
			||||||
	background var(--faceHeader)
 | 
						background var(--faceHeader)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -456,6 +456,18 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS)
 | 
				
			||||||
						document.body.appendChild(x.$el);
 | 
											document.body.appendChild(x.$el);
 | 
				
			||||||
						return x;
 | 
											return x;
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
 | 
										newAsync(vm, props) {
 | 
				
			||||||
 | 
											return new Promise((res) => {
 | 
				
			||||||
 | 
												vm().then(vm => {
 | 
				
			||||||
 | 
													const x = new vm({
 | 
				
			||||||
 | 
														parent: this,
 | 
				
			||||||
 | 
														propsData: props
 | 
				
			||||||
 | 
													}).$mount();
 | 
				
			||||||
 | 
													document.body.appendChild(x.$el);
 | 
				
			||||||
 | 
													res(x);
 | 
				
			||||||
 | 
												});
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
					dialog(opts) {
 | 
										dialog(opts) {
 | 
				
			||||||
						const vm = this.new(Dialog, opts);
 | 
											const vm = this.new(Dialog, opts);
 | 
				
			||||||
						const p: any = new Promise((res) => {
 | 
											const p: any = new Promise((res) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,16 +11,13 @@ import './style.styl';
 | 
				
			||||||
import init from '../init';
 | 
					import init from '../init';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import MkIndex from './views/pages/index.vue';
 | 
					import MkIndex from './views/pages/index.vue';
 | 
				
			||||||
import MkDeck from '../common/views/deck/deck.vue';
 | 
					 | 
				
			||||||
import MkSignup from './views/pages/signup.vue';
 | 
					import MkSignup from './views/pages/signup.vue';
 | 
				
			||||||
import MkSelectDrive from './views/pages/selectdrive.vue';
 | 
					import MkSelectDrive from './views/pages/selectdrive.vue';
 | 
				
			||||||
import MkDrive from './views/pages/drive.vue';
 | 
					import MkDrive from './views/pages/drive.vue';
 | 
				
			||||||
import MkWidgets from './views/pages/widgets.vue';
 | 
					 | 
				
			||||||
import MkMessaging from './views/pages/messaging.vue';
 | 
					import MkMessaging from './views/pages/messaging.vue';
 | 
				
			||||||
import MkMessagingRoom from './views/pages/messaging-room.vue';
 | 
					import MkMessagingRoom from './views/pages/messaging-room.vue';
 | 
				
			||||||
import MkNote from './views/pages/note.vue';
 | 
					import MkNote from './views/pages/note.vue';
 | 
				
			||||||
import MkSearch from './views/pages/search.vue';
 | 
					import MkSearch from './views/pages/search.vue';
 | 
				
			||||||
import MkFavorites from './views/pages/favorites.vue';
 | 
					 | 
				
			||||||
import UI from './views/pages/ui.vue';
 | 
					import UI from './views/pages/ui.vue';
 | 
				
			||||||
import MkReversi from './views/pages/games/reversi.vue';
 | 
					import MkReversi from './views/pages/games/reversi.vue';
 | 
				
			||||||
import MkTag from './views/pages/tag.vue';
 | 
					import MkTag from './views/pages/tag.vue';
 | 
				
			||||||
| 
						 | 
					@ -29,7 +26,6 @@ import MkFollow from '../common/views/pages/follow.vue';
 | 
				
			||||||
import MkNotFound from '../common/views/pages/not-found.vue';
 | 
					import MkNotFound from '../common/views/pages/not-found.vue';
 | 
				
			||||||
import DeckColumn from '../common/views/deck/deck.column-template.vue';
 | 
					import DeckColumn from '../common/views/deck/deck.column-template.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PostForm from './views/components/post-form-dialog.vue';
 | 
					 | 
				
			||||||
import FileChooser from './views/components/drive-file-chooser.vue';
 | 
					import FileChooser from './views/components/drive-file-chooser.vue';
 | 
				
			||||||
import FolderChooser from './views/components/drive-folder-chooser.vue';
 | 
					import FolderChooser from './views/components/drive-folder-chooser.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,16 +50,16 @@ init((launch, os) => {
 | 
				
			||||||
					document.documentElement.style.overflow = 'auto';
 | 
										document.documentElement.style.overflow = 'auto';
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				const vm = this.$root.new(PostForm, {
 | 
									this.$root.newAsync(() => import('./views/components/post-form-dialog.vue').then(m => m.default), {
 | 
				
			||||||
					reply: o.reply,
 | 
										reply: o.reply,
 | 
				
			||||||
					mention: o.mention,
 | 
										mention: o.mention,
 | 
				
			||||||
					renote: o.renote
 | 
										renote: o.renote
 | 
				
			||||||
 | 
									}).then(vm => {
 | 
				
			||||||
 | 
										vm.$once('cancel', recover);
 | 
				
			||||||
 | 
										vm.$once('posted', recover);
 | 
				
			||||||
 | 
										if (o.cb) vm.$once('closed', o.cb);
 | 
				
			||||||
 | 
										(vm as any).focus();
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
 | 
					 | 
				
			||||||
				vm.$once('cancel', recover);
 | 
					 | 
				
			||||||
				vm.$once('posted', recover);
 | 
					 | 
				
			||||||
				if (o.cb) vm.$once('closed', o.cb);
 | 
					 | 
				
			||||||
				(vm as any).focus();
 | 
					 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			$chooseDriveFile(opts) {
 | 
								$chooseDriveFile(opts) {
 | 
				
			||||||
| 
						 | 
					@ -114,7 +110,7 @@ init((launch, os) => {
 | 
				
			||||||
		mode: 'history',
 | 
							mode: 'history',
 | 
				
			||||||
		routes: [
 | 
							routes: [
 | 
				
			||||||
			...(os.store.state.device.inDeckMode
 | 
								...(os.store.state.device.inDeckMode
 | 
				
			||||||
				? [{ path: '/', name: 'index', component: MkDeck, children: [
 | 
									? [{ path: '/', name: 'index', component: () => import('../common/views/deck/deck.vue').then(m => m.default), children: [
 | 
				
			||||||
					{ path: '/@:user', component: () => import('../common/views/deck/deck.user-column.vue').then(m => m.default), children: [
 | 
										{ path: '/@:user', component: () => import('../common/views/deck/deck.user-column.vue').then(m => m.default), children: [
 | 
				
			||||||
						{ path: '', name: 'user', component: () => import('../common/views/deck/deck.user-column.home.vue').then(m => m.default) },
 | 
											{ path: '', name: 'user', component: () => import('../common/views/deck/deck.user-column.home.vue').then(m => m.default) },
 | 
				
			||||||
						{ path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) },
 | 
											{ path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) },
 | 
				
			||||||
| 
						 | 
					@ -123,10 +119,10 @@ init((launch, os) => {
 | 
				
			||||||
					{ path: '/notes/:note', name: 'note', component: () => import('../common/views/deck/deck.note-column.vue').then(m => m.default) },
 | 
										{ path: '/notes/:note', name: 'note', component: () => import('../common/views/deck/deck.note-column.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/search', component: () => import('../common/views/deck/deck.search-column.vue').then(m => m.default) },
 | 
										{ path: '/search', component: () => import('../common/views/deck/deck.search-column.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/tags/:tag', name: 'tag', component: () => import('../common/views/deck/deck.hashtag-column.vue').then(m => m.default) },
 | 
										{ path: '/tags/:tag', name: 'tag', component: () => import('../common/views/deck/deck.hashtag-column.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/featured', name: 'featured', component: () => import('../common/views/deck/deck.featured-column.vue').then(m => m.default) },
 | 
										{ path: '/featured', name: 'featured', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/featured.vue').then(m => m.default), platform: 'deck' }) },
 | 
				
			||||||
					{ path: '/explore', name: 'explore', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default) }) },
 | 
										{ path: '/explore', name: 'explore', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default) }) },
 | 
				
			||||||
					{ path: '/explore/tags/:tag', name: 'explore-tag', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default), tag: route.params.tag }) },
 | 
										{ path: '/explore/tags/:tag', name: 'explore-tag', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default), tag: route.params.tag }) },
 | 
				
			||||||
					{ path: '/i/favorites', component: () => import('../common/views/deck/deck.favorites-column.vue').then(m => m.default) },
 | 
										{ path: '/i/favorites', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/favorites.vue').then(m => m.default), platform: 'deck' }) },
 | 
				
			||||||
					{ path: '/i/pages', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) },
 | 
										{ path: '/i/pages', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) },
 | 
				
			||||||
					{ path: '/i/lists', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) },
 | 
										{ path: '/i/lists', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) },
 | 
				
			||||||
					{ path: '/i/lists/:listId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.listId }) },
 | 
										{ path: '/i/lists/:listId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.listId }) },
 | 
				
			||||||
| 
						 | 
					@ -138,14 +134,14 @@ init((launch, os) => {
 | 
				
			||||||
		]),
 | 
							]),
 | 
				
			||||||
			{ path: '/signup', name: 'signup', component: MkSignup },
 | 
								{ path: '/signup', name: 'signup', component: MkSignup },
 | 
				
			||||||
			{ path: '/i/settings', name: 'settings', component: () => import('./views/pages/settings.vue').then(m => m.default) },
 | 
								{ path: '/i/settings', name: 'settings', component: () => import('./views/pages/settings.vue').then(m => m.default) },
 | 
				
			||||||
			{ path: '/i/favorites', name: 'favorites', component: MkFavorites },
 | 
								{ path: '/i/favorites', name: 'favorites', component: UI, props: route => ({ component: () => import('../common/views/pages/favorites.vue').then(m => m.default), platform: 'mobile' }) },
 | 
				
			||||||
			{ path: '/i/pages', name: 'pages', component: UI, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) },
 | 
								{ path: '/i/pages', name: 'pages', component: UI, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) },
 | 
				
			||||||
			{ path: '/i/lists', name: 'user-lists', component: UI, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) },
 | 
								{ path: '/i/lists', name: 'user-lists', component: UI, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) },
 | 
				
			||||||
			{ path: '/i/lists/:list', component: UI, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.list }) },
 | 
								{ path: '/i/lists/:list', component: UI, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.list }) },
 | 
				
			||||||
			{ path: '/i/groups', name: 'user-groups', component: UI, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) },
 | 
								{ path: '/i/groups', name: 'user-groups', component: UI, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) },
 | 
				
			||||||
			{ path: '/i/groups/:group', component: UI, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.group }) },
 | 
								{ path: '/i/groups/:group', component: UI, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.group }) },
 | 
				
			||||||
			{ path: '/i/follow-requests', name: 'follow-requests', component: UI, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) },
 | 
								{ path: '/i/follow-requests', name: 'follow-requests', component: UI, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) },
 | 
				
			||||||
			{ path: '/i/widgets', name: 'widgets', component: MkWidgets },
 | 
								{ path: '/i/widgets', name: 'widgets', component: () => import('./views/pages/widgets.vue').then(m => m.default) },
 | 
				
			||||||
			{ path: '/i/messaging', name: 'messaging', component: MkMessaging },
 | 
								{ path: '/i/messaging', name: 'messaging', component: MkMessaging },
 | 
				
			||||||
			{ path: '/i/messaging/group/:group', component: MkMessagingRoom },
 | 
								{ path: '/i/messaging/group/:group', component: MkMessagingRoom },
 | 
				
			||||||
			{ path: '/i/messaging/:user', component: MkMessagingRoom },
 | 
								{ path: '/i/messaging/:user', component: MkMessagingRoom },
 | 
				
			||||||
| 
						 | 
					@ -157,7 +153,7 @@ init((launch, os) => {
 | 
				
			||||||
			{ path: '/selectdrive', component: MkSelectDrive },
 | 
								{ path: '/selectdrive', component: MkSelectDrive },
 | 
				
			||||||
			{ path: '/search', component: MkSearch },
 | 
								{ path: '/search', component: MkSearch },
 | 
				
			||||||
			{ path: '/tags/:tag', component: MkTag },
 | 
								{ path: '/tags/:tag', component: MkTag },
 | 
				
			||||||
			{ path: '/featured', name: 'featured', component: () => import('./views/pages/featured.vue').then(m => m.default) },
 | 
								{ path: '/featured', name: 'featured', component: UI, props: route => ({ component: () => import('../common/views/pages/featured.vue').then(m => m.default), platform: 'mobile' }) },
 | 
				
			||||||
			{ path: '/explore', name: 'explore', component: UI, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default) }) },
 | 
								{ path: '/explore', name: 'explore', component: UI, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default) }) },
 | 
				
			||||||
			{ path: '/explore/tags/:tag', name: 'explore-tag', component: UI, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default), tag: route.params.tag }) },
 | 
								{ path: '/explore/tags/:tag', name: 'explore-tag', component: UI, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default), tag: route.params.tag }) },
 | 
				
			||||||
			{ path: '/share', component: MkShare },
 | 
								{ path: '/share', component: MkShare },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										52
									
								
								src/client/app/mobile/views/components/detail-notes.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/client/app/mobile/views/components/detail-notes.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,52 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div class="fdcvngpy">
 | 
				
			||||||
 | 
						<sequential-entrance animation="entranceFromTop" delay="25">
 | 
				
			||||||
 | 
							<template v-for="note in notes">
 | 
				
			||||||
 | 
								<mk-note-detail class="post" :note="note" :key="note.id"/>
 | 
				
			||||||
 | 
							</template>
 | 
				
			||||||
 | 
						</sequential-entrance>
 | 
				
			||||||
 | 
						<ui-button v-if="more" @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					import paging from '../../../common/scripts/paging';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						i18n: i18n(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mixins: [
 | 
				
			||||||
 | 
							paging({
 | 
				
			||||||
 | 
								captureWindowScroll: true,
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							pagination: {
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							extract: {
 | 
				
			||||||
 | 
								required: false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						computed: {
 | 
				
			||||||
 | 
							notes() {
 | 
				
			||||||
 | 
								return this.extract ? this.extract(this.items) : this.items;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					.fdcvngpy
 | 
				
			||||||
 | 
						> * > .post
 | 
				
			||||||
 | 
							margin-bottom 8px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@media (min-width 500px)
 | 
				
			||||||
 | 
							> * > .post
 | 
				
			||||||
 | 
								margin-bottom 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,6 @@ import notificationPreview from './notification-preview.vue';
 | 
				
			||||||
import userTimeline from './user-timeline.vue';
 | 
					import userTimeline from './user-timeline.vue';
 | 
				
			||||||
import userListTimeline from './user-list-timeline.vue';
 | 
					import userListTimeline from './user-list-timeline.vue';
 | 
				
			||||||
import uiContainer from './ui-container.vue';
 | 
					import uiContainer from './ui-container.vue';
 | 
				
			||||||
import postForm from './post-form.vue';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Vue.component('mk-ui', ui);
 | 
					Vue.component('mk-ui', ui);
 | 
				
			||||||
Vue.component('mk-note', note);
 | 
					Vue.component('mk-note', note);
 | 
				
			||||||
| 
						 | 
					@ -30,4 +29,3 @@ Vue.component('mk-notification-preview', notificationPreview);
 | 
				
			||||||
Vue.component('mk-user-timeline', userTimeline);
 | 
					Vue.component('mk-user-timeline', userTimeline);
 | 
				
			||||||
Vue.component('mk-user-list-timeline', userListTimeline);
 | 
					Vue.component('mk-user-list-timeline', userListTimeline);
 | 
				
			||||||
Vue.component('ui-container', uiContainer);
 | 
					Vue.component('ui-container', uiContainer);
 | 
				
			||||||
Vue.component('mk-post-form', postForm);
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="ivaojijs" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
 | 
					<div class="ivaojijs" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
 | 
				
			||||||
	<div class="empty" v-if="notes.length == 0 && !fetching && inited">{{ $t('@.no-notes') }}</div>
 | 
						<div class="empty" v-if="empty">{{ $t('@.no-notes') }}</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<mk-error v-if="!fetching && !inited" @retry="init()"/>
 | 
						<mk-error v-if="error" @retry="init()"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="placeholder" v-if="fetching">
 | 
						<div class="placeholder" v-if="fetching">
 | 
				
			||||||
		<template v-for="i in 10">
 | 
							<template v-for="i in 10">
 | 
				
			||||||
| 
						 | 
					@ -13,8 +13,8 @@
 | 
				
			||||||
	<!-- トランジションを有効にするとなぜかメモリリークする -->
 | 
						<!-- トランジションを有効にするとなぜかメモリリークする -->
 | 
				
			||||||
	<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="transition" tag="div">
 | 
						<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="transition" tag="div">
 | 
				
			||||||
		<template v-for="(note, i) in _notes">
 | 
							<template v-for="(note, i) in _notes">
 | 
				
			||||||
			<mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
 | 
								<mk-note :note="note" :key="note.id"/>
 | 
				
			||||||
			<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
 | 
								<p class="date" :key="note.id + '_date'" v-if="i != items.length - 1 && note._date != _notes[i + 1]._date">
 | 
				
			||||||
				<span><fa icon="angle-up"/>{{ note._datetext }}</span>
 | 
									<span><fa icon="angle-up"/>{{ note._datetext }}</span>
 | 
				
			||||||
				<span><fa icon="angle-down"/>{{ _notes[i + 1]._datetext }}</span>
 | 
									<span><fa icon="angle-down"/>{{ _notes[i + 1]._datetext }}</span>
 | 
				
			||||||
			</p>
 | 
								</p>
 | 
				
			||||||
| 
						 | 
					@ -34,158 +34,56 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import shouldMuteNote from '../../../common/scripts/should-mute-note';
 | 
					import shouldMuteNote from '../../../common/scripts/should-mute-note';
 | 
				
			||||||
 | 
					import paging from '../../../common/scripts/paging';
 | 
				
			||||||
const displayLimit = 30;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n(),
 | 
						i18n: i18n(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						mixins: [
 | 
				
			||||||
		makePromise: {
 | 
							paging({
 | 
				
			||||||
			required: true
 | 
								captureWindowScroll: true,
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
								onQueueChanged: (self, x) => {
 | 
				
			||||||
		return {
 | 
									if (x.length > 0) {
 | 
				
			||||||
			notes: [],
 | 
										self.$store.commit('indicate', true);
 | 
				
			||||||
			queue: [],
 | 
									} else {
 | 
				
			||||||
			fetching: true,
 | 
										self.$store.commit('indicate', false);
 | 
				
			||||||
			moreFetching: false,
 | 
									}
 | 
				
			||||||
			inited: false,
 | 
								},
 | 
				
			||||||
			more: false
 | 
					
 | 
				
			||||||
		};
 | 
								onPrepend: (self, note) => {
 | 
				
			||||||
 | 
									// 弾く
 | 
				
			||||||
 | 
									if (shouldMuteNote(self.$store.state.i, self.$store.state.settings, note)) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
 | 
				
			||||||
 | 
									if (document.hidden || !self.isScrollTop()) {
 | 
				
			||||||
 | 
										self.$store.commit('pushBehindNote', note);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								onInited: (self) => {
 | 
				
			||||||
 | 
									self.$emit('loaded');
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							pagination: {
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		_notes(): any[] {
 | 
							_notes(): any[] {
 | 
				
			||||||
			return (this.notes as any).map(note => {
 | 
								return (this.items as any).map(item => {
 | 
				
			||||||
				const date = new Date(note.createdAt).getDate();
 | 
									const date = new Date(item.createdAt).getDate();
 | 
				
			||||||
				const month = new Date(note.createdAt).getMonth() + 1;
 | 
									const month = new Date(item.createdAt).getMonth() + 1;
 | 
				
			||||||
				note._date = date;
 | 
									item._date = date;
 | 
				
			||||||
				note._datetext = this.$t('@.month-and-day').replace('{month}', month.toString()).replace('{day}', date.toString());
 | 
									item._datetext = this.$t('@.month-and-day').replace('{month}', month.toString()).replace('{day}', date.toString());
 | 
				
			||||||
				return note;
 | 
									return item;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					 | 
				
			||||||
	watch: {
 | 
					 | 
				
			||||||
		queue(x) {
 | 
					 | 
				
			||||||
			if (x.length > 0) {
 | 
					 | 
				
			||||||
				this.$store.commit('indicate', true);
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				this.$store.commit('indicate', false);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	created() {
 | 
					 | 
				
			||||||
		this.init();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	mounted() {
 | 
					 | 
				
			||||||
		window.addEventListener('scroll', this.onScroll, { passive: true });
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	beforeDestroy() {
 | 
					 | 
				
			||||||
		window.removeEventListener('scroll', this.onScroll);
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		isScrollTop() {
 | 
					 | 
				
			||||||
			return window.scrollY <= 8;
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		onNoteUpdated(i, note) {
 | 
					 | 
				
			||||||
			Vue.set((this as any).notes, i, note);
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		reload() {
 | 
					 | 
				
			||||||
			this.queue = [];
 | 
					 | 
				
			||||||
			this.notes = [];
 | 
					 | 
				
			||||||
			this.init();
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		async init() {
 | 
					 | 
				
			||||||
			this.fetching = true;
 | 
					 | 
				
			||||||
			await (this.makePromise()).then(x => {
 | 
					 | 
				
			||||||
				if (Array.isArray(x)) {
 | 
					 | 
				
			||||||
					this.notes = x;
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					this.notes = x.notes;
 | 
					 | 
				
			||||||
					this.more = x.more;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				this.inited = true;
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
				this.$emit('inited');
 | 
					 | 
				
			||||||
			}, e => {
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		async fetchMore() {
 | 
					 | 
				
			||||||
			if (!this.more || this.moreFetching || this.notes.length === 0) return;
 | 
					 | 
				
			||||||
			this.moreFetching = true;
 | 
					 | 
				
			||||||
			await (this.makePromise(this.notes[this.notes.length - 1].id)).then(x => {
 | 
					 | 
				
			||||||
				this.notes = this.notes.concat(x.notes);
 | 
					 | 
				
			||||||
				this.more = x.more;
 | 
					 | 
				
			||||||
				this.moreFetching = false;
 | 
					 | 
				
			||||||
			}, e => {
 | 
					 | 
				
			||||||
				this.moreFetching = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		prepend(note, silent = false) {
 | 
					 | 
				
			||||||
			// 弾く
 | 
					 | 
				
			||||||
			if (shouldMuteNote(this.$store.state.i, this.$store.state.settings, note)) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
 | 
					 | 
				
			||||||
			if (document.hidden || !this.isScrollTop()) {
 | 
					 | 
				
			||||||
				this.$store.commit('pushBehindNote', note);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (this.isScrollTop()) {
 | 
					 | 
				
			||||||
				// Prepend the note
 | 
					 | 
				
			||||||
				this.notes.unshift(note);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				// オーバーフローしたら古い投稿は捨てる
 | 
					 | 
				
			||||||
				if (this.notes.length >= displayLimit) {
 | 
					 | 
				
			||||||
					this.notes = this.notes.slice(0, displayLimit);
 | 
					 | 
				
			||||||
					this.more = true;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				this.queue.push(note);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		append(note) {
 | 
					 | 
				
			||||||
			this.notes.push(note);
 | 
					 | 
				
			||||||
			this.cursor = this.notes[this.notes.length - 1].id
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		releaseQueue() {
 | 
					 | 
				
			||||||
			for (const n of this.queue) {
 | 
					 | 
				
			||||||
				this.prepend(n, true);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			this.queue = [];
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		onScroll() {
 | 
					 | 
				
			||||||
			if (this.isScrollTop()) {
 | 
					 | 
				
			||||||
				this.releaseQueue();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (this.$store.state.settings.fetchOnScroll) {
 | 
					 | 
				
			||||||
				// 親要素が display none だったら弾く
 | 
					 | 
				
			||||||
				// https://github.com/syuilo/misskey/issues/1569
 | 
					 | 
				
			||||||
				// http://d.hatena.ne.jp/favril/20091105/1257403319
 | 
					 | 
				
			||||||
				if (this.$el.offsetHeight == 0) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				const current = window.scrollY + window.innerHeight;
 | 
					 | 
				
			||||||
				if (current > document.body.offsetHeight - 8) this.fetchMore();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -71,15 +71,15 @@
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<template v-if="notification.type == 'quote'">
 | 
						<template v-if="notification.type == 'quote'">
 | 
				
			||||||
		<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
 | 
							<mk-note :note="notification.note"/>
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<template v-if="notification.type == 'reply'">
 | 
						<template v-if="notification.type == 'reply'">
 | 
				
			||||||
		<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
 | 
							<mk-note :note="notification.note"/>
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<template v-if="notification.type == 'mention'">
 | 
						<template v-if="notification.type == 'mention'">
 | 
				
			||||||
		<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
 | 
							<mk-note :note="notification.note"/>
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -95,17 +95,6 @@ export default Vue.extend({
 | 
				
			||||||
			getNoteSummary
 | 
								getNoteSummary
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		onNoteUpdated(note) {
 | 
					 | 
				
			||||||
			switch (this.notification.type) {
 | 
					 | 
				
			||||||
				case 'quote':
 | 
					 | 
				
			||||||
				case 'reply':
 | 
					 | 
				
			||||||
				case 'mention':
 | 
					 | 
				
			||||||
					Vue.set(this.notification, 'note', note);
 | 
					 | 
				
			||||||
					break;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,41 +10,49 @@
 | 
				
			||||||
	<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications" tag="div">
 | 
						<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications" tag="div">
 | 
				
			||||||
		<template v-for="(notification, i) in _notifications">
 | 
							<template v-for="(notification, i) in _notifications">
 | 
				
			||||||
			<mk-notification :notification="notification" :key="notification.id"/>
 | 
								<mk-notification :notification="notification" :key="notification.id"/>
 | 
				
			||||||
			<p class="date" :key="notification.id + '_date'" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date">
 | 
								<p class="date" :key="notification.id + '_date'" v-if="i != items.length - 1 && notification._date != _notifications[i + 1]._date">
 | 
				
			||||||
				<span><fa icon="angle-up"/>{{ notification._datetext }}</span>
 | 
									<span><fa icon="angle-up"/>{{ notification._datetext }}</span>
 | 
				
			||||||
				<span><fa icon="angle-down"/>{{ _notifications[i + 1]._datetext }}</span>
 | 
									<span><fa icon="angle-down"/>{{ _notifications[i + 1]._datetext }}</span>
 | 
				
			||||||
			</p>
 | 
								</p>
 | 
				
			||||||
		</template>
 | 
							</template>
 | 
				
			||||||
	</component>
 | 
						</component>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<button class="more" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
 | 
						<button class="more" v-if="more" @click="fetchMore" :disabled="moreFetching">
 | 
				
			||||||
		<template v-if="fetchingMoreNotifications"><fa icon="spinner" pulse fixed-width/></template>
 | 
							<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>
 | 
				
			||||||
		{{ fetchingMoreNotifications ? $t('@.loading') : $t('@.load-more') }}
 | 
							{{ moreFetching ? $t('@.loading') : $t('@.load-more') }}
 | 
				
			||||||
	</button>
 | 
						</button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<p class="empty" v-if="notifications.length == 0 && !fetching">{{ $t('empty') }}</p>
 | 
						<p class="empty" v-if="empty">{{ $t('empty') }}</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<mk-error v-if="error" @retry="init()"/>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					import paging from '../../../common/scripts/paging';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('mobile/views/components/notifications.vue'),
 | 
						i18n: i18n('mobile/views/components/notifications.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mixins: [
 | 
				
			||||||
 | 
							paging({}),
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			fetching: true,
 | 
								connection: null,
 | 
				
			||||||
			fetchingMoreNotifications: false,
 | 
								pagination: {
 | 
				
			||||||
			notifications: [],
 | 
									endpoint: 'i/notifications',
 | 
				
			||||||
			moreNotifications: false,
 | 
									limit: 15,
 | 
				
			||||||
			connection: null
 | 
								}
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		_notifications(): any[] {
 | 
							_notifications(): any[] {
 | 
				
			||||||
			return (this.notifications as any).map(notification => {
 | 
								return (this.items as any).map(notification => {
 | 
				
			||||||
				const date = new Date(notification.createdAt).getDate();
 | 
									const date = new Date(notification.createdAt).getDate();
 | 
				
			||||||
				const month = new Date(notification.createdAt).getMonth() + 1;
 | 
									const month = new Date(notification.createdAt).getMonth() + 1;
 | 
				
			||||||
				notification._date = date;
 | 
									notification._date = date;
 | 
				
			||||||
| 
						 | 
					@ -55,76 +63,23 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		window.addEventListener('scroll', this.onScroll, { passive: true });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.connection = this.$root.stream.useSharedConnection('main');
 | 
							this.connection = this.$root.stream.useSharedConnection('main');
 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.connection.on('notification', this.onNotification);
 | 
							this.connection.on('notification', this.onNotification);
 | 
				
			||||||
 | 
					 | 
				
			||||||
		const max = 15;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.$root.api('i/notifications', {
 | 
					 | 
				
			||||||
			limit: max + 1
 | 
					 | 
				
			||||||
		}).then(notifications => {
 | 
					 | 
				
			||||||
			if (notifications.length == max + 1) {
 | 
					 | 
				
			||||||
				this.moreNotifications = true;
 | 
					 | 
				
			||||||
				notifications.pop();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.notifications = notifications;
 | 
					 | 
				
			||||||
			this.fetching = false;
 | 
					 | 
				
			||||||
			this.$emit('fetched');
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	beforeDestroy() {
 | 
						beforeDestroy() {
 | 
				
			||||||
		window.removeEventListener('scroll', this.onScroll);
 | 
					 | 
				
			||||||
		this.connection.dispose();
 | 
							this.connection.dispose();
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		fetchMoreNotifications() {
 | 
					 | 
				
			||||||
			if (this.fetchingMoreNotifications) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.fetchingMoreNotifications = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const max = 30;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$root.api('i/notifications', {
 | 
					 | 
				
			||||||
				limit: max + 1,
 | 
					 | 
				
			||||||
				untilId: this.notifications[this.notifications.length - 1].id
 | 
					 | 
				
			||||||
			}).then(notifications => {
 | 
					 | 
				
			||||||
				if (notifications.length == max + 1) {
 | 
					 | 
				
			||||||
					this.moreNotifications = true;
 | 
					 | 
				
			||||||
					notifications.pop();
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					this.moreNotifications = false;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				this.notifications = this.notifications.concat(notifications);
 | 
					 | 
				
			||||||
				this.fetchingMoreNotifications = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		onNotification(notification) {
 | 
							onNotification(notification) {
 | 
				
			||||||
			// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
 | 
								// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
 | 
				
			||||||
			this.$root.stream.send('readNotification', {
 | 
								this.$root.stream.send('readNotification', {
 | 
				
			||||||
				id: notification.id
 | 
									id: notification.id
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.notifications.unshift(notification);
 | 
								this.prepend(notification);
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					 | 
				
			||||||
		onScroll() {
 | 
					 | 
				
			||||||
			if (this.$store.state.settings.fetchOnScroll) {
 | 
					 | 
				
			||||||
				// 親要素が display none だったら弾く
 | 
					 | 
				
			||||||
				// https://github.com/syuilo/misskey/issues/1569
 | 
					 | 
				
			||||||
				// http://d.hatena.ne.jp/favril/20091105/1257403319
 | 
					 | 
				
			||||||
				if (this.$el.offsetHeight == 0) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				const current = window.scrollY + window.innerHeight;
 | 
					 | 
				
			||||||
				if (current > document.body.offsetHeight - 8) this.fetchMoreNotifications();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
 | 
					<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
 | 
				
			||||||
	<div class="bg" ref="bg"></div>
 | 
						<div class="bg" ref="bg"></div>
 | 
				
			||||||
	<div class="main" ref="main">
 | 
						<div class="main" ref="main">
 | 
				
			||||||
		<mk-post-form ref="form"
 | 
							<x-post-form ref="form"
 | 
				
			||||||
			:reply="reply"
 | 
								:reply="reply"
 | 
				
			||||||
			:renote="renote"
 | 
								:renote="renote"
 | 
				
			||||||
			:mention="mention"
 | 
								:mention="mention"
 | 
				
			||||||
| 
						 | 
					@ -17,8 +17,13 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import anime from 'animejs';
 | 
					import anime from 'animejs';
 | 
				
			||||||
 | 
					import XPostForm from './post-form.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						components: {
 | 
				
			||||||
 | 
							XPostForm
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
		reply: {
 | 
							reply: {
 | 
				
			||||||
			type: Object,
 | 
								type: Object,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="mk-post-form">
 | 
					<div class="gafaadew">
 | 
				
			||||||
	<div class="form">
 | 
						<div class="form">
 | 
				
			||||||
		<header>
 | 
							<header>
 | 
				
			||||||
			<button class="cancel" @click="cancel"><fa icon="times"/></button>
 | 
								<button class="cancel" @click="cancel"><fa icon="times"/></button>
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,7 @@
 | 
				
			||||||
			<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('annotations')" v-autocomplete="{ model: 'cw' }">
 | 
								<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('annotations')" v-autocomplete="{ model: 'cw' }">
 | 
				
			||||||
			<textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder" v-autocomplete="{ model: 'text' }"></textarea>
 | 
								<textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder" v-autocomplete="{ model: 'text' }"></textarea>
 | 
				
			||||||
			<x-post-form-attaches class="attaches" :files="files"/>
 | 
								<x-post-form-attaches class="attaches" :files="files"/>
 | 
				
			||||||
			<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/>
 | 
								<x-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/>
 | 
				
			||||||
			<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
 | 
								<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
 | 
				
			||||||
			<footer>
 | 
								<footer>
 | 
				
			||||||
				<button class="upload" @click="chooseFile"><fa icon="upload"/></button>
 | 
									<button class="upload" @click="chooseFile"><fa icon="upload"/></button>
 | 
				
			||||||
| 
						 | 
					@ -64,7 +64,8 @@ import XPostFormAttaches from '../../../common/views/components/post-form-attach
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('mobile/views/components/post-form.vue'),
 | 
						i18n: i18n('mobile/views/components/post-form.vue'),
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XPostFormAttaches
 | 
							XPostFormAttaches,
 | 
				
			||||||
 | 
							XPollEditor: () => import('../../../common/views/components/poll-editor.vue').then(m => m.default)
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
| 
						 | 
					@ -386,7 +387,7 @@ export default Vue.extend({
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
.mk-post-form
 | 
					.gafaadew
 | 
				
			||||||
	max-width 500px
 | 
						max-width 500px
 | 
				
			||||||
	width calc(100% - 16px)
 | 
						width calc(100% - 16px)
 | 
				
			||||||
	margin 8px auto
 | 
						margin 8px auto
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="header" ref="root" :class="{ shadow: $store.state.device.useShadow }">
 | 
					<div class="header" ref="root" :class="{ shadow: $store.state.device.useShadow }">
 | 
				
			||||||
	<p class="warn" v-if="env != 'production'">{{ $t('@.do-not-use-in-production') }} <a href="/assets/flush.html?force">Flush</a></p>
 | 
					 | 
				
			||||||
	<div class="main" ref="main">
 | 
						<div class="main" ref="main">
 | 
				
			||||||
		<div class="backdrop"></div>
 | 
							<div class="backdrop"></div>
 | 
				
			||||||
		<div class="content" ref="mainContainer">
 | 
							<div class="content" ref="mainContainer">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,14 +1,10 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div>
 | 
					<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
 | 
				
			||||||
	<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fetchLimit = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	props: ['list'],
 | 
						props: ['list'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,28 +12,17 @@ export default Vue.extend({
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			connection: null,
 | 
								connection: null,
 | 
				
			||||||
			date: null,
 | 
								date: null,
 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/user-list-timeline', {
 | 
								pagination: {
 | 
				
			||||||
				listId: this.list.id,
 | 
									endpoint: 'notes/user-list-timeline',
 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
									limit: 10,
 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
									params: init => ({
 | 
				
			||||||
				untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
										listId: this.list.id,
 | 
				
			||||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
										untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
										includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
				
			||||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
										includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
				
			||||||
			}).then(notes => {
 | 
										includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
				
			||||||
				if (notes.length == fetchLimit + 1) {
 | 
									})
 | 
				
			||||||
					notes.pop();
 | 
								}
 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: true
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,11 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="mk-user-timeline">
 | 
					<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
 | 
				
			||||||
	<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fetchLimit = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('mobile/views/components/user-timeline.vue'),
 | 
						i18n: i18n('mobile/views/components/user-timeline.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,26 +14,15 @@ export default Vue.extend({
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			date: null,
 | 
								date: null,
 | 
				
			||||||
			makePromise: cursor => this.$root.api('users/notes', {
 | 
								pagination: {
 | 
				
			||||||
				userId: this.user.id,
 | 
									endpoint: 'users/notes',
 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
									limit: 10,
 | 
				
			||||||
				withFiles: this.withMedia,
 | 
									params: init => ({
 | 
				
			||||||
				untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
										userId: this.user.id,
 | 
				
			||||||
				untilId: cursor ? cursor : undefined
 | 
										withFiles: this.withMedia,
 | 
				
			||||||
			}).then(notes => {
 | 
										untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
				if (notes.length == fetchLimit + 1) {
 | 
									})
 | 
				
			||||||
					notes.pop();
 | 
								}
 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: true
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,86 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<mk-ui>
 | 
					 | 
				
			||||||
	<template #header><span style="margin-right:4px;"><fa icon="star"/></span>{{ $t('@.favorites') }}</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<main>
 | 
					 | 
				
			||||||
		<sequential-entrance animation="entranceFromTop" delay="25">
 | 
					 | 
				
			||||||
			<template v-for="favorite in favorites">
 | 
					 | 
				
			||||||
				<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
 | 
					 | 
				
			||||||
			</template>
 | 
					 | 
				
			||||||
		</sequential-entrance>
 | 
					 | 
				
			||||||
		<ui-button v-if="existMore" @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
 | 
					 | 
				
			||||||
	</main>
 | 
					 | 
				
			||||||
</mk-ui>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n(),
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			fetching: true,
 | 
					 | 
				
			||||||
			favorites: [],
 | 
					 | 
				
			||||||
			existMore: false,
 | 
					 | 
				
			||||||
			moreFetching: false
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	created() {
 | 
					 | 
				
			||||||
		this.fetch();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	mounted() {
 | 
					 | 
				
			||||||
		document.title = `${this.$root.instanceName} | ${this.$t('@.favorites')}`;
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		fetch() {
 | 
					 | 
				
			||||||
			Progress.start();
 | 
					 | 
				
			||||||
			this.fetching = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$root.api('i/favorites', {
 | 
					 | 
				
			||||||
				limit: 11
 | 
					 | 
				
			||||||
			}).then(favorites => {
 | 
					 | 
				
			||||||
				if (favorites.length == 11) {
 | 
					 | 
				
			||||||
					this.existMore = true;
 | 
					 | 
				
			||||||
					favorites.pop();
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				this.favorites = favorites;
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				Progress.done();
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		fetchMore() {
 | 
					 | 
				
			||||||
			this.moreFetching = true;
 | 
					 | 
				
			||||||
			this.$root.api('i/favorites', {
 | 
					 | 
				
			||||||
				limit: 11,
 | 
					 | 
				
			||||||
				untilId: this.favorites[this.favorites.length - 1].id
 | 
					 | 
				
			||||||
			}).then(favorites => {
 | 
					 | 
				
			||||||
				if (favorites.length == 11) {
 | 
					 | 
				
			||||||
					this.existMore = true;
 | 
					 | 
				
			||||||
					favorites.pop();
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					this.existMore = false;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				this.favorites = this.favorites.concat(favorites);
 | 
					 | 
				
			||||||
				this.moreFetching = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
main
 | 
					 | 
				
			||||||
	> * > .post
 | 
					 | 
				
			||||||
		margin-bottom 8px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@media (min-width 500px)
 | 
					 | 
				
			||||||
		> * > .post
 | 
					 | 
				
			||||||
			margin-bottom 16px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,61 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<mk-ui>
 | 
					 | 
				
			||||||
	<template #header><span style="margin-right:4px;"><fa :icon="faNewspaper"/></span>{{ $t('@.featured-notes') }}</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<main>
 | 
					 | 
				
			||||||
		<sequential-entrance animation="entranceFromTop" delay="25">
 | 
					 | 
				
			||||||
			<template v-for="note in notes">
 | 
					 | 
				
			||||||
				<mk-note-detail class="post" :note="note" :key="note.id"/>
 | 
					 | 
				
			||||||
			</template>
 | 
					 | 
				
			||||||
		</sequential-entrance>
 | 
					 | 
				
			||||||
	</main>
 | 
					 | 
				
			||||||
</mk-ui>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					 | 
				
			||||||
import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n(''),
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			fetching: true,
 | 
					 | 
				
			||||||
			notes: [],
 | 
					 | 
				
			||||||
			faNewspaper
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	created() {
 | 
					 | 
				
			||||||
		this.fetch();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		fetch() {
 | 
					 | 
				
			||||||
			Progress.start();
 | 
					 | 
				
			||||||
			this.fetching = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$root.api('notes/featured', {
 | 
					 | 
				
			||||||
				limit: 30
 | 
					 | 
				
			||||||
			}).then(notes => {
 | 
					 | 
				
			||||||
				notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
 | 
					 | 
				
			||||||
				this.notes = notes;
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				Progress.done();
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
main
 | 
					 | 
				
			||||||
	> * > .post
 | 
					 | 
				
			||||||
		margin-bottom 8px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@media (min-width 500px)
 | 
					 | 
				
			||||||
		> * > .post
 | 
					 | 
				
			||||||
			margin-bottom 16px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</ui-container>
 | 
						</ui-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
 | 
						<mk-notes ref="timeline" :pagination="pagination" @loaded="() => $emit('loaded')"/>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,8 +15,6 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fetchLimit = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('mobile/views/pages/home.timeline.vue'),
 | 
						i18n: i18n('mobile/views/pages/home.timeline.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +41,7 @@ export default Vue.extend({
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			query: {},
 | 
								query: {},
 | 
				
			||||||
			endpoint: null,
 | 
								endpoint: null,
 | 
				
			||||||
			makePromise: null
 | 
								pagination: null
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,25 +108,14 @@ export default Vue.extend({
 | 
				
			||||||
			this.connection.on('mention', onNote);
 | 
								this.connection.on('mention', onNote);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.makePromise = cursor => this.$root.api(this.endpoint, {
 | 
							this.pagination = {
 | 
				
			||||||
			limit: fetchLimit + 1,
 | 
								endpoint: this.endpoint,
 | 
				
			||||||
			untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
								limit: 10,
 | 
				
			||||||
			untilId: cursor ? cursor : undefined,
 | 
								params: init => ({
 | 
				
			||||||
			...this.baseQuery, ...this.query
 | 
									untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
		}).then(notes => {
 | 
									...this.baseQuery, ...this.query
 | 
				
			||||||
			if (notes.length == fetchLimit + 1) {
 | 
								})
 | 
				
			||||||
				notes.pop();
 | 
							};
 | 
				
			||||||
				return {
 | 
					 | 
				
			||||||
					notes: notes,
 | 
					 | 
				
			||||||
					more: true
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				return {
 | 
					 | 
				
			||||||
					notes: notes,
 | 
					 | 
				
			||||||
					more: false
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
	<template #header><fa icon="search"/> {{ q }}</template>
 | 
						<template #header><fa icon="search"/> {{ q }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<main>
 | 
						<main>
 | 
				
			||||||
		<mk-notes ref="timeline" :make-promise="makePromise" @inited="inited"/>
 | 
							<mk-notes ref="timeline" :pagination="pagination" @inited="inited"/>
 | 
				
			||||||
	</main>
 | 
						</main>
 | 
				
			||||||
</mk-ui>
 | 
					</mk-ui>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -14,42 +14,27 @@ import i18n from '../../../i18n';
 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					import Progress from '../../../common/scripts/loading';
 | 
				
			||||||
import { genSearchQuery } from '../../../common/scripts/gen-search-query';
 | 
					import { genSearchQuery } from '../../../common/scripts/gen-search-query';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const limit = 20;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('mobile/views/pages/search.vue'),
 | 
						i18n: i18n('mobile/views/pages/search.vue'),
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			makePromise: async cursor => this.$root.api('notes/search', {
 | 
								pagination: {
 | 
				
			||||||
				limit: limit + 1,
 | 
									endpoint: 'notes/search',
 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
									limit: 20,
 | 
				
			||||||
				...(await genSearchQuery(this, this.q))
 | 
									params: () => genSearchQuery(this, this.q)
 | 
				
			||||||
			}).then(notes => {
 | 
								}
 | 
				
			||||||
				if (notes.length == limit + 1) {
 | 
					 | 
				
			||||||
					notes.pop();
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: true
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	watch: {
 | 
					 | 
				
			||||||
		$route() {
 | 
					 | 
				
			||||||
			this.$refs.timeline.reload();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		q(): string {
 | 
							q(): string {
 | 
				
			||||||
			return this.$route.query.q;
 | 
								return this.$route.query.q;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						watch: {
 | 
				
			||||||
 | 
							$route() {
 | 
				
			||||||
 | 
								this.$refs.timeline.reload();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		document.title = `${this.$t('search')}: ${this.q} | ${this.$root.instanceName}`;
 | 
							document.title = `${this.$t('search')}: ${this.q} | ${this.$root.instanceName}`;
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
	<template #header><span style="margin-right:4px;"><fa icon="hashtag"/></span>{{ $route.params.tag }}</template>
 | 
						<template #header><span style="margin-right:4px;"><fa icon="hashtag"/></span>{{ $route.params.tag }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<main>
 | 
						<main>
 | 
				
			||||||
		<mk-notes ref="timeline" :make-promise="makePromise" @inited="inited"/>
 | 
							<mk-notes ref="timeline" :pagination="pagination" @inited="inited"/>
 | 
				
			||||||
	</main>
 | 
						</main>
 | 
				
			||||||
</mk-ui>
 | 
					</mk-ui>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -13,30 +13,17 @@ import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					import Progress from '../../../common/scripts/loading';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const limit = 20;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('mobile/views/pages/tag.vue'),
 | 
						i18n: i18n('mobile/views/pages/tag.vue'),
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/search-by-tag', {
 | 
								pagination: {
 | 
				
			||||||
				limit: limit + 1,
 | 
									endpoint: 'notes/search-by-tag',
 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
									limit: 20,
 | 
				
			||||||
				tag: this.$route.params.tag
 | 
									params: {
 | 
				
			||||||
			}).then(notes => {
 | 
										tag: this.$route.params.tag
 | 
				
			||||||
				if (notes.length == limit + 1) {
 | 
					 | 
				
			||||||
					notes.pop();
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: true
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						notes: notes,
 | 
					 | 
				
			||||||
						more: false
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			})
 | 
								}
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	watch: {
 | 
						watch: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,8 +19,6 @@
 | 
				
			||||||
			<x-activity :user="user"/>
 | 
								<x-activity :user="user"/>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</ui-container>
 | 
						</ui-container>
 | 
				
			||||||
	<mk-user-list :make-promise="makeFrequentlyRepliedUsersPromise" :icon-only="true"><fa icon="users"/> {{ $t('frequently-replied-users') }}</mk-user-list>
 | 
					 | 
				
			||||||
	<mk-user-list v-if="$store.getters.isSignedIn && $store.state.i.id !== user.id" :make-promise="makeFollowersYouKnowPromise" :icon-only="true"><fa icon="users"/> {{ $t('followers-you-know') }}</mk-user-list>
 | 
					 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
 | 
				
			||||||
import { NoteFavorite } from '../entities/note-favorite';
 | 
					import { NoteFavorite } from '../entities/note-favorite';
 | 
				
			||||||
import { Notes } from '..';
 | 
					import { Notes } from '..';
 | 
				
			||||||
import { ensure } from '../../prelude/ensure';
 | 
					import { ensure } from '../../prelude/ensure';
 | 
				
			||||||
 | 
					import { types, bool } from '../../misc/schema';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@EntityRepository(NoteFavorite)
 | 
					@EntityRepository(NoteFavorite)
 | 
				
			||||||
export class NoteFavoriteRepository extends Repository<NoteFavorite> {
 | 
					export class NoteFavoriteRepository extends Repository<NoteFavorite> {
 | 
				
			||||||
| 
						 | 
					@ -26,3 +27,33 @@ export class NoteFavoriteRepository extends Repository<NoteFavorite> {
 | 
				
			||||||
		return Promise.all(favorites.map(x => this.pack(x, me)));
 | 
							return Promise.all(favorites.map(x => this.pack(x, me)));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const packedNoteFavoriteSchema = {
 | 
				
			||||||
 | 
						type: types.object,
 | 
				
			||||||
 | 
						optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
						properties: {
 | 
				
			||||||
 | 
							id: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								format: 'id',
 | 
				
			||||||
 | 
								description: 'The unique identifier for this favorite.',
 | 
				
			||||||
 | 
								example: 'xxxxxxxxxx',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							createdAt: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								format: 'date-time',
 | 
				
			||||||
 | 
								description: 'The date that the favorite was created.'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							note: {
 | 
				
			||||||
 | 
								type: types.object,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								ref: 'Note',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							noteId: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								format: 'id',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ import { ID } from '../../../../misc/cafy-id';
 | 
				
			||||||
import define from '../../define';
 | 
					import define from '../../define';
 | 
				
			||||||
import { NoteFavorites } from '../../../../models';
 | 
					import { NoteFavorites } from '../../../../models';
 | 
				
			||||||
import { makePaginationQuery } from '../../common/make-pagination-query';
 | 
					import { makePaginationQuery } from '../../common/make-pagination-query';
 | 
				
			||||||
 | 
					import { types, bool } from '../../../../misc/schema';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const meta = {
 | 
					export const meta = {
 | 
				
			||||||
	desc: {
 | 
						desc: {
 | 
				
			||||||
| 
						 | 
					@ -29,7 +30,17 @@ export const meta = {
 | 
				
			||||||
		untilId: {
 | 
							untilId: {
 | 
				
			||||||
			validator: $.optional.type(ID),
 | 
								validator: $.optional.type(ID),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res: {
 | 
				
			||||||
 | 
							type: types.array,
 | 
				
			||||||
 | 
							optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
							items: {
 | 
				
			||||||
 | 
								type: types.object,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								ref: 'NoteFavorite',
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps, user) => {
 | 
					export default define(meta, async (ps, user) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ import define from '../../define';
 | 
				
			||||||
import { maximum } from '../../../../prelude/array';
 | 
					import { maximum } from '../../../../prelude/array';
 | 
				
			||||||
import { ApiError } from '../../error';
 | 
					import { ApiError } from '../../error';
 | 
				
			||||||
import { getUser } from '../../common/getters';
 | 
					import { getUser } from '../../common/getters';
 | 
				
			||||||
import { Not, In } from 'typeorm';
 | 
					import { Not, In, IsNull } from 'typeorm';
 | 
				
			||||||
import { Notes, Users } from '../../../../models';
 | 
					import { Notes, Users } from '../../../../models';
 | 
				
			||||||
import { types, bool } from '../../../../misc/schema';
 | 
					import { types, bool } from '../../../../misc/schema';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,7 +58,7 @@ export default define(meta, async (ps, me) => {
 | 
				
			||||||
	const recentNotes = await Notes.find({
 | 
						const recentNotes = await Notes.find({
 | 
				
			||||||
		where: {
 | 
							where: {
 | 
				
			||||||
			userId: user.id,
 | 
								userId: user.id,
 | 
				
			||||||
			replyId: Not(null)
 | 
								replyId: Not(IsNull())
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		order: {
 | 
							order: {
 | 
				
			||||||
			id: -1
 | 
								id: -1
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@ import { packedNoteReactionSchema } from '../../../models/repositories/note-reac
 | 
				
			||||||
import { packedHashtagSchema } from '../../../models/repositories/hashtag';
 | 
					import { packedHashtagSchema } from '../../../models/repositories/hashtag';
 | 
				
			||||||
import { packedPageSchema } from '../../../models/repositories/page';
 | 
					import { packedPageSchema } from '../../../models/repositories/page';
 | 
				
			||||||
import { packedUserGroupSchema } from '../../../models/repositories/user-group';
 | 
					import { packedUserGroupSchema } from '../../../models/repositories/user-group';
 | 
				
			||||||
 | 
					import { packedNoteFavoriteSchema } from '../../../models/repositories/note-favorite';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function convertSchemaToOpenApiSchema(schema: Schema) {
 | 
					export function convertSchemaToOpenApiSchema(schema: Schema) {
 | 
				
			||||||
	const res: any = schema;
 | 
						const res: any = schema;
 | 
				
			||||||
| 
						 | 
					@ -71,13 +72,14 @@ export const schemas = {
 | 
				
			||||||
	App: convertSchemaToOpenApiSchema(packedAppSchema),
 | 
						App: convertSchemaToOpenApiSchema(packedAppSchema),
 | 
				
			||||||
	MessagingMessage: convertSchemaToOpenApiSchema(packedMessagingMessageSchema),
 | 
						MessagingMessage: convertSchemaToOpenApiSchema(packedMessagingMessageSchema),
 | 
				
			||||||
	Note: convertSchemaToOpenApiSchema(packedNoteSchema),
 | 
						Note: convertSchemaToOpenApiSchema(packedNoteSchema),
 | 
				
			||||||
 | 
						NoteReaction: convertSchemaToOpenApiSchema(packedNoteReactionSchema),
 | 
				
			||||||
 | 
						NoteFavorite: convertSchemaToOpenApiSchema(packedNoteFavoriteSchema),
 | 
				
			||||||
	Notification: convertSchemaToOpenApiSchema(packedNotificationSchema),
 | 
						Notification: convertSchemaToOpenApiSchema(packedNotificationSchema),
 | 
				
			||||||
	DriveFile: convertSchemaToOpenApiSchema(packedDriveFileSchema),
 | 
						DriveFile: convertSchemaToOpenApiSchema(packedDriveFileSchema),
 | 
				
			||||||
	DriveFolder: convertSchemaToOpenApiSchema(packedDriveFolderSchema),
 | 
						DriveFolder: convertSchemaToOpenApiSchema(packedDriveFolderSchema),
 | 
				
			||||||
	Following: convertSchemaToOpenApiSchema(packedFollowingSchema),
 | 
						Following: convertSchemaToOpenApiSchema(packedFollowingSchema),
 | 
				
			||||||
	Muting: convertSchemaToOpenApiSchema(packedMutingSchema),
 | 
						Muting: convertSchemaToOpenApiSchema(packedMutingSchema),
 | 
				
			||||||
	Blocking: convertSchemaToOpenApiSchema(packedBlockingSchema),
 | 
						Blocking: convertSchemaToOpenApiSchema(packedBlockingSchema),
 | 
				
			||||||
	NoteReaction: convertSchemaToOpenApiSchema(packedNoteReactionSchema),
 | 
					 | 
				
			||||||
	Hashtag: convertSchemaToOpenApiSchema(packedHashtagSchema),
 | 
						Hashtag: convertSchemaToOpenApiSchema(packedHashtagSchema),
 | 
				
			||||||
	Page: convertSchemaToOpenApiSchema(packedPageSchema),
 | 
						Page: convertSchemaToOpenApiSchema(packedPageSchema),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ import autobind from 'autobind-decorator';
 | 
				
			||||||
import Chart, { Obj, DeepPartial } from '../../core';
 | 
					import Chart, { Obj, DeepPartial } from '../../core';
 | 
				
			||||||
import { SchemaType } from '../../../../misc/schema';
 | 
					import { SchemaType } from '../../../../misc/schema';
 | 
				
			||||||
import { DriveFiles } from '../../../../models';
 | 
					import { DriveFiles } from '../../../../models';
 | 
				
			||||||
import { Not } from 'typeorm';
 | 
					import { Not, IsNull } from 'typeorm';
 | 
				
			||||||
import { DriveFile } from '../../../../models/entities/drive-file';
 | 
					import { DriveFile } from '../../../../models/entities/drive-file';
 | 
				
			||||||
import { name, schema } from '../schemas/drive';
 | 
					import { name, schema } from '../schemas/drive';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@ export default class DriveChart extends Chart<DriveLog> {
 | 
				
			||||||
	protected async fetchActual(): Promise<DeepPartial<DriveLog>> {
 | 
						protected async fetchActual(): Promise<DeepPartial<DriveLog>> {
 | 
				
			||||||
		const [localCount, remoteCount, localSize, remoteSize] = await Promise.all([
 | 
							const [localCount, remoteCount, localSize, remoteSize] = await Promise.all([
 | 
				
			||||||
			DriveFiles.count({ userHost: null }),
 | 
								DriveFiles.count({ userHost: null }),
 | 
				
			||||||
			DriveFiles.count({ userHost: Not(null) }),
 | 
								DriveFiles.count({ userHost: Not(IsNull()) }),
 | 
				
			||||||
			DriveFiles.clacDriveUsageOfLocal(),
 | 
								DriveFiles.clacDriveUsageOfLocal(),
 | 
				
			||||||
			DriveFiles.clacDriveUsageOfRemote()
 | 
								DriveFiles.clacDriveUsageOfRemote()
 | 
				
			||||||
		]);
 | 
							]);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ import autobind from 'autobind-decorator';
 | 
				
			||||||
import Chart, { Obj, DeepPartial } from '../../core';
 | 
					import Chart, { Obj, DeepPartial } from '../../core';
 | 
				
			||||||
import { SchemaType } from '../../../../misc/schema';
 | 
					import { SchemaType } from '../../../../misc/schema';
 | 
				
			||||||
import { Notes } from '../../../../models';
 | 
					import { Notes } from '../../../../models';
 | 
				
			||||||
import { Not } from 'typeorm';
 | 
					import { Not, IsNull } from 'typeorm';
 | 
				
			||||||
import { Note } from '../../../../models/entities/note';
 | 
					import { Note } from '../../../../models/entities/note';
 | 
				
			||||||
import { name, schema } from '../schemas/notes';
 | 
					import { name, schema } from '../schemas/notes';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,7 +29,7 @@ export default class NotesChart extends Chart<NotesLog> {
 | 
				
			||||||
	protected async fetchActual(): Promise<DeepPartial<NotesLog>> {
 | 
						protected async fetchActual(): Promise<DeepPartial<NotesLog>> {
 | 
				
			||||||
		const [localCount, remoteCount] = await Promise.all([
 | 
							const [localCount, remoteCount] = await Promise.all([
 | 
				
			||||||
			Notes.count({ userHost: null }),
 | 
								Notes.count({ userHost: null }),
 | 
				
			||||||
			Notes.count({ userHost: Not(null) })
 | 
								Notes.count({ userHost: Not(IsNull()) })
 | 
				
			||||||
		]);
 | 
							]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ import autobind from 'autobind-decorator';
 | 
				
			||||||
import Chart, { Obj, DeepPartial } from '../../core';
 | 
					import Chart, { Obj, DeepPartial } from '../../core';
 | 
				
			||||||
import { SchemaType } from '../../../../misc/schema';
 | 
					import { SchemaType } from '../../../../misc/schema';
 | 
				
			||||||
import { Followings, Users } from '../../../../models';
 | 
					import { Followings, Users } from '../../../../models';
 | 
				
			||||||
import { Not } from 'typeorm';
 | 
					import { Not, IsNull } from 'typeorm';
 | 
				
			||||||
import { User } from '../../../../models/entities/user';
 | 
					import { User } from '../../../../models/entities/user';
 | 
				
			||||||
import { name, schema } from '../schemas/per-user-following';
 | 
					import { name, schema } from '../schemas/per-user-following';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,8 +45,8 @@ export default class PerUserFollowingChart extends Chart<PerUserFollowingLog> {
 | 
				
			||||||
		] = await Promise.all([
 | 
							] = await Promise.all([
 | 
				
			||||||
			Followings.count({ followerId: group, followeeHost: null }),
 | 
								Followings.count({ followerId: group, followeeHost: null }),
 | 
				
			||||||
			Followings.count({ followeeId: group, followerHost: null }),
 | 
								Followings.count({ followeeId: group, followerHost: null }),
 | 
				
			||||||
			Followings.count({ followerId: group, followeeHost: Not(null) }),
 | 
								Followings.count({ followerId: group, followeeHost: Not(IsNull()) }),
 | 
				
			||||||
			Followings.count({ followeeId: group, followerHost: Not(null) })
 | 
								Followings.count({ followeeId: group, followerHost: Not(IsNull()) })
 | 
				
			||||||
		]);
 | 
							]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ import autobind from 'autobind-decorator';
 | 
				
			||||||
import Chart, { Obj, DeepPartial } from '../../core';
 | 
					import Chart, { Obj, DeepPartial } from '../../core';
 | 
				
			||||||
import { SchemaType } from '../../../../misc/schema';
 | 
					import { SchemaType } from '../../../../misc/schema';
 | 
				
			||||||
import { Users } from '../../../../models';
 | 
					import { Users } from '../../../../models';
 | 
				
			||||||
import { Not } from 'typeorm';
 | 
					import { Not, IsNull } from 'typeorm';
 | 
				
			||||||
import { User } from '../../../../models/entities/user';
 | 
					import { User } from '../../../../models/entities/user';
 | 
				
			||||||
import { name, schema } from '../schemas/users';
 | 
					import { name, schema } from '../schemas/users';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,7 +29,7 @@ export default class UsersChart extends Chart<UsersLog> {
 | 
				
			||||||
	protected async fetchActual(): Promise<DeepPartial<UsersLog>> {
 | 
						protected async fetchActual(): Promise<DeepPartial<UsersLog>> {
 | 
				
			||||||
		const [localCount, remoteCount] = await Promise.all([
 | 
							const [localCount, remoteCount] = await Promise.all([
 | 
				
			||||||
			Users.count({ host: null }),
 | 
								Users.count({ host: null }),
 | 
				
			||||||
			Users.count({ host: Not(null) })
 | 
								Users.count({ host: Not(IsNull()) })
 | 
				
			||||||
		]);
 | 
							]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,6 @@ import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
 | 
				
			||||||
import { User } from '../../models/entities/user';
 | 
					import { User } from '../../models/entities/user';
 | 
				
			||||||
import { Note } from '../../models/entities/note';
 | 
					import { Note } from '../../models/entities/note';
 | 
				
			||||||
import { Notes, Users, Followings, Instances } from '../../models';
 | 
					import { Notes, Users, Followings, Instances } from '../../models';
 | 
				
			||||||
import { Not } from 'typeorm';
 | 
					 | 
				
			||||||
import { notesChart, perUserNotesChart, instanceChart } from '../chart';
 | 
					import { notesChart, perUserNotesChart, instanceChart } from '../chart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -38,13 +37,21 @@ export default async function(user: User, note: Note, quiet = false) {
 | 
				
			||||||
		if (Users.isLocalUser(user)) {
 | 
							if (Users.isLocalUser(user)) {
 | 
				
			||||||
			const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${note.id}`), user));
 | 
								const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${note.id}`), user));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const followings = await Followings.find({
 | 
								const queue: string[] = [];
 | 
				
			||||||
				followeeId: user.id,
 | 
					
 | 
				
			||||||
				followerHost: Not(null)
 | 
								const followers = await Followings.find({
 | 
				
			||||||
 | 
									followeeId: note.userId
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for (const following of followings) {
 | 
								for (const following of followers) {
 | 
				
			||||||
				deliver(user, content, following.followerInbox);
 | 
									if (Followings.isRemoteFollower(following)) {
 | 
				
			||||||
 | 
										const inbox = following.followerSharedInbox || following.followerInbox;
 | 
				
			||||||
 | 
										if (!queue.includes(inbox)) queue.push(inbox);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for (const inbox of queue) {
 | 
				
			||||||
 | 
									deliver(user as any, content, inbox);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		//#endregion
 | 
							//#endregion
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,14 +1,14 @@
 | 
				
			||||||
import * as promiseLimit from 'promise-limit';
 | 
					import * as promiseLimit from 'promise-limit';
 | 
				
			||||||
import del from '../services/drive/delete-file';
 | 
					import del from '../services/drive/delete-file';
 | 
				
			||||||
import { DriveFiles } from '../models';
 | 
					import { DriveFiles } from '../models';
 | 
				
			||||||
import { Not } from 'typeorm';
 | 
					import { Not, IsNull } from 'typeorm';
 | 
				
			||||||
import { DriveFile } from '../models/entities/drive-file';
 | 
					import { DriveFile } from '../models/entities/drive-file';
 | 
				
			||||||
import { ensure } from '../prelude/ensure';
 | 
					import { ensure } from '../prelude/ensure';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const limit = promiseLimit(16);
 | 
					const limit = promiseLimit(16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DriveFiles.find({
 | 
					DriveFiles.find({
 | 
				
			||||||
	userHost: Not(null)
 | 
						userHost: Not(IsNull())
 | 
				
			||||||
}).then(async files => {
 | 
					}).then(async files => {
 | 
				
			||||||
	console.log(`there is ${files.length} files`);
 | 
						console.log(`there is ${files.length} files`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue