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 ブランチに戻す
|
||||
9. enjoy
|
||||
|
||||
11.18.0 (2019/05/21)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* デザインの調整
|
||||
|
||||
### 🐛Fixes
|
||||
* 投稿の削除が連合しない問題を修正
|
||||
|
||||
11.17.1 (2019/05/20)
|
||||
--------------------
|
||||
### 🐛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
|
||||
SQLを発行する際、パラメータが`null`になる可能性のある場合はSQL文を出し分けなければならない
|
||||
例えば
|
||||
|
|
|
@ -251,6 +251,7 @@ common:
|
|||
my-token-regenerated: "Váš token byl regenerován, proto budete odhlášen/a."
|
||||
hide-password: "Skrýt 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."
|
||||
user-suspended: "Tomuto uživateli byl pozastaven účet."
|
||||
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"
|
||||
you: "Vy"
|
||||
no-history: "Žádná historie"
|
||||
user: "Uživatel"
|
||||
common/views/components/messaging-room.vue:
|
||||
empty: "Žádné zprávy"
|
||||
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:
|
||||
send: "Odeslat"
|
||||
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:
|
||||
is-read: "Přečtené"
|
||||
deleted: "Tato zpráva byla odstraněna"
|
||||
|
@ -617,8 +616,15 @@ common/views/components/user-list-editor.vue:
|
|||
remove-user: "Odebrat z tohoto seznamu"
|
||||
delete-are-you-sure: "Smazat seznam \"$1\"?"
|
||||
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:
|
||||
user-lists: "Seznamy"
|
||||
list-name: "Název seznamu"
|
||||
common/views/components/user-groups.vue:
|
||||
invites: "Pozvat"
|
||||
common/views/widgets/broadcast.vue:
|
||||
fetching: "Načítám"
|
||||
next: "Další"
|
||||
|
@ -853,11 +859,6 @@ desktop/views/components/ui.header.post.vue:
|
|||
post: "Nový příspěvek"
|
||||
desktop/views/components/ui.header.search.vue:
|
||||
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:
|
||||
notes: "Příspěvky"
|
||||
desktop/views/components/users-list.vue:
|
||||
|
@ -902,8 +903,7 @@ admin/views/instance.vue:
|
|||
recaptcha-config: "nastavení služby reCAPTCHA"
|
||||
recaptcha-info: "reCAPTCHA token je povinný. Můžete jej získat na https://www.google.com/recaptcha/intro/"
|
||||
enable-recaptcha: "povolit reCAPTCHA"
|
||||
recaptcha-site-key: "reCAPTCHA klíč stránky (site key)"
|
||||
recaptcha-secret-key: "reCAPTCHA tajný klíč"
|
||||
recaptcha-preview: "Náhled"
|
||||
twitter-integration-config: "Nastavení spojení s Twitterem"
|
||||
twitter-integration-info: "The callback URL is set on {url}."
|
||||
enable-twitter-integration: "Povolit připojení k Twitteru"
|
||||
|
@ -1159,8 +1159,6 @@ mobile/views/pages/drive.vue:
|
|||
contextmenu:
|
||||
upload: "Nahrát soubor"
|
||||
create-folder: "Vytvořit složku"
|
||||
mobile/views/pages/user-lists.vue:
|
||||
title: "Seznamy"
|
||||
mobile/views/pages/signup.vue:
|
||||
lets-start: "Váš účet je připraven! 📦"
|
||||
mobile/views/pages/home.vue:
|
||||
|
@ -1177,9 +1175,6 @@ mobile/views/pages/widgets/activity.vue:
|
|||
activity: "Aktivita"
|
||||
mobile/views/pages/share.vue:
|
||||
share-with: "Sdílet na {name}"
|
||||
mobile/views/pages/received-follow-requests.vue:
|
||||
accept: "Přijmout"
|
||||
reject: "Odmítnout"
|
||||
mobile/views/pages/note.vue:
|
||||
prev: "Předchozí příspěvěk"
|
||||
next: "Následující příspěvek"
|
||||
|
@ -1212,6 +1207,7 @@ deck/deck.user-column.vue:
|
|||
dev/views/new-app.vue:
|
||||
app-name-desc: "Jméno vaší aplikace"
|
||||
pages:
|
||||
like: "Lajk"
|
||||
title: "Titulek"
|
||||
blocks:
|
||||
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"
|
||||
no-history: "Keine Chronik"
|
||||
common/views/components/messaging-room.vue:
|
||||
empty: "Keine Unterhaltungen"
|
||||
no-history: "Keine weitere Chronik vorhanden"
|
||||
resize-form: "Ziehen um die Größe zu verändern"
|
||||
new-message: "Neue Nachricht"
|
||||
common/views/components/messaging-room.form.vue:
|
||||
input-message-here: "Nachricht hier eingeben"
|
||||
|
@ -386,6 +384,12 @@ common/views/components/profile-editor.vue:
|
|||
export-targets:
|
||||
user-lists: "Listen"
|
||||
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:
|
||||
fetching: "Laden"
|
||||
no-broadcasts: "Keine Broadcasts"
|
||||
|
@ -583,8 +587,6 @@ desktop/views/components/ui.header.post.vue:
|
|||
post: "Einen neuen Post erstellen"
|
||||
desktop/views/components/ui.header.search.vue:
|
||||
placeholder: "Suchen"
|
||||
desktop/views/components/user-lists-window.vue:
|
||||
title: "Listen"
|
||||
desktop/views/components/users-list.vue:
|
||||
fetching: "Lade…"
|
||||
admin/views/dashboard.vue:
|
||||
|
@ -592,6 +594,7 @@ admin/views/dashboard.vue:
|
|||
admin/views/abuse.vue:
|
||||
remove-report: "Löschen"
|
||||
admin/views/instance.vue:
|
||||
recaptcha-preview: "Vorschau"
|
||||
invite: "Einladen"
|
||||
save: "Speichern"
|
||||
admin/views/charts.vue:
|
||||
|
@ -677,8 +680,6 @@ mobile/views/pages/drive.vue:
|
|||
contextmenu:
|
||||
upload: "Eine Datei hochladen"
|
||||
create-folder: "Ein Verzeichnis erstellen"
|
||||
mobile/views/pages/user-lists.vue:
|
||||
title: "Listen"
|
||||
mobile/views/pages/home.vue:
|
||||
home: "Home"
|
||||
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-warning: "Dies kann auch nach dem erstellen der Anwendung geändert werden, allerdings werden dann alle bisher generierten Token ungültig."
|
||||
pages:
|
||||
like: "Gefällt mir"
|
||||
blocks:
|
||||
post: "\"Neuer Beitrag\"-Formular"
|
||||
script:
|
||||
|
|
|
@ -253,6 +253,7 @@ common:
|
|||
my-token-regenerated: "Your token has been regenerated, so you will be signed out."
|
||||
hide-password: "Hide 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."
|
||||
user-suspended: "This user has been suspended."
|
||||
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"
|
||||
you: "You"
|
||||
no-history: "Without history"
|
||||
user: "User"
|
||||
common/views/components/messaging-room.vue:
|
||||
empty: "No conversations"
|
||||
no-history: "There is no further history"
|
||||
resize-form: "Drag to resize"
|
||||
new-message: "New message"
|
||||
only-one-file-attached: "Only ONE file can be attached to a message."
|
||||
common/views/components/messaging-room.form.vue:
|
||||
input-message-here: "Enter message here"
|
||||
send: "Send"
|
||||
attach-from-local: "Attach files from your device"
|
||||
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:
|
||||
is-read: "Read"
|
||||
deleted: "This message has been deleted"
|
||||
|
@ -472,6 +470,7 @@ common/views/components/nav.vue:
|
|||
repository: "Repository"
|
||||
develop: "Developers"
|
||||
feedback: "Feedback"
|
||||
tos: "Terms Of Service"
|
||||
common/views/components/note-menu.vue:
|
||||
mention: "Mention"
|
||||
detail: "Details"
|
||||
|
@ -584,6 +583,8 @@ common/views/components/signup.vue:
|
|||
password-matched: "OK"
|
||||
password-not-matched: "Doesn't match"
|
||||
recaptcha: "Verification"
|
||||
agree-to: "Accept {0}."
|
||||
tos: "Terms Of Service"
|
||||
create: "Create an Account"
|
||||
some-error: "An attempt at account creation has failed for some reason. Please try again."
|
||||
common/views/components/special-message.vue:
|
||||
|
@ -691,9 +692,16 @@ common/views/components/user-list-editor.vue:
|
|||
remove-user: "Remove from this list"
|
||||
delete-are-you-sure: "Delete list \"$1\"?"
|
||||
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:
|
||||
user-lists: "Lists"
|
||||
create-list: "Create a list"
|
||||
list-name: "List name"
|
||||
common/views/components/user-groups.vue:
|
||||
invites: "Invite"
|
||||
common/views/widgets/broadcast.vue:
|
||||
fetching: "Checking"
|
||||
no-broadcasts: "No announcements"
|
||||
|
@ -755,6 +763,8 @@ common/views/pages/follow.vue:
|
|||
request-pending: "Pending follow request"
|
||||
follow-processing: "Processing follow"
|
||||
follow-request: "Follow request"
|
||||
common/views/pages/follow-requests.vue:
|
||||
received-follow-requests: "Follow requests"
|
||||
desktop:
|
||||
banner-crop-title: "Crop the part that appears as a banner"
|
||||
banner: "Banner"
|
||||
|
@ -1036,12 +1046,6 @@ desktop/views/components/ui.header.post.vue:
|
|||
post: "Compose new Post"
|
||||
desktop/views/components/ui.header.search.vue:
|
||||
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:
|
||||
notes: "Posts"
|
||||
following: "Following"
|
||||
|
@ -1089,14 +1093,21 @@ admin/views/instance.vue:
|
|||
instance-name: "Instance name"
|
||||
instance-description: "Instance description"
|
||||
host: "Host"
|
||||
icon-url: "URL of the icon"
|
||||
logo-url: "URL of the logo"
|
||||
banner-url: "Banner image URL"
|
||||
error-image-url: "Error image URL"
|
||||
languages: "Language of this instance"
|
||||
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-name: "Administrator name"
|
||||
maintainer-email: "Contact Administrator"
|
||||
advanced-config: "Other settings"
|
||||
drive-config: "Drive settings"
|
||||
object-storage-base-url: "URL"
|
||||
object-storage-endpoint: "Endpoint"
|
||||
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."
|
||||
|
@ -1106,8 +1117,7 @@ admin/views/instance.vue:
|
|||
recaptcha-config: "the reCAPTCHA settings"
|
||||
recaptcha-info: "reCAPTCHA token is required. Please get it on https://www.google.com/recaptcha/intro/"
|
||||
enable-recaptcha: "enable reCAPTCHA"
|
||||
recaptcha-site-key: "reCAPTCHA site key"
|
||||
recaptcha-secret-key: "reCAPTCHA secret key"
|
||||
recaptcha-preview: "Preview"
|
||||
twitter-integration-config: "Settings of connecting to Twitter"
|
||||
twitter-integration-info: "The callback URL is set on {url}."
|
||||
enable-twitter-integration: "Enable connection to Twitter"
|
||||
|
@ -1514,8 +1524,6 @@ mobile/views/pages/drive.vue:
|
|||
rename-folder: "Rename folder"
|
||||
move-folder: "Move this folder"
|
||||
delete-folder: "Delete this folder"
|
||||
mobile/views/pages/user-lists.vue:
|
||||
title: "Lists"
|
||||
mobile/views/pages/signup.vue:
|
||||
lets-start: "Your account is now ready! 📦"
|
||||
mobile/views/pages/followers.vue:
|
||||
|
@ -1540,10 +1548,6 @@ mobile/views/pages/widgets/activity.vue:
|
|||
activity: "Activity"
|
||||
mobile/views/pages/share.vue:
|
||||
share-with: "Share on {name}"
|
||||
mobile/views/pages/received-follow-requests.vue:
|
||||
title: "Follow requests"
|
||||
accept: "Accept"
|
||||
reject: "Reject"
|
||||
mobile/views/pages/note.vue:
|
||||
title: "Post"
|
||||
prev: "Previous note"
|
||||
|
@ -1649,6 +1653,7 @@ pages:
|
|||
edit-this-page: "Edit this page"
|
||||
view-source: "View Source"
|
||||
view-page: "View page"
|
||||
like: "Like"
|
||||
inspector: "Inspector"
|
||||
content: "Page block"
|
||||
variables: "Variables"
|
||||
|
|
|
@ -395,17 +395,13 @@ common/views/components/messaging.vue:
|
|||
you: "Tu"
|
||||
no-history: "Sin historial"
|
||||
common/views/components/messaging-room.vue:
|
||||
empty: "Sin conversaciones"
|
||||
no-history: "El historial se ha acabado"
|
||||
resize-form: "Arrastra para redimensionar"
|
||||
new-message: "Nuevo mensaje"
|
||||
only-one-file-attached: "Un único archivo se puede conectar al mensaje"
|
||||
common/views/components/messaging-room.form.vue:
|
||||
input-message-here: "Escribe el mensaje aquí"
|
||||
send: "Enviar"
|
||||
attach-from-local: "Adjunta ficheros desde tu PC"
|
||||
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:
|
||||
is-read: "Leer"
|
||||
deleted: "El mensaje se ha borrado"
|
||||
|
@ -587,8 +583,13 @@ common/views/components/user-list-editor.vue:
|
|||
rename: "Cambiar el nombre de la lista"
|
||||
delete: "Eliminar lista"
|
||||
remove-user: "Eliminar de la lista"
|
||||
common/views/components/user-group-editor.vue:
|
||||
invite: "Invitar"
|
||||
common/views/components/user-lists.vue:
|
||||
user-lists: "Listas"
|
||||
list-name: "Nombre de lista"
|
||||
common/views/components/user-groups.vue:
|
||||
invites: "Invitar"
|
||||
common/views/widgets/broadcast.vue:
|
||||
fetching: "Recuperando"
|
||||
no-broadcasts: "Sin emisión"
|
||||
|
@ -647,6 +648,8 @@ common/views/pages/follow.vue:
|
|||
request-pending: "Solicitud pendiente"
|
||||
follow-processing: "Solicitud en proceso"
|
||||
follow-request: "Solicitar suscripción"
|
||||
common/views/pages/follow-requests.vue:
|
||||
received-follow-requests: "Solicitudes de seguimiento"
|
||||
desktop:
|
||||
banner-crop-title: "Corta la parte que aparece como un banner"
|
||||
banner: "Banner"
|
||||
|
@ -890,12 +893,6 @@ desktop/views/components/ui.header.post.vue:
|
|||
post: "Crear una publicación"
|
||||
desktop/views/components/ui.header.search.vue:
|
||||
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:
|
||||
notes: "Publicaciones"
|
||||
following: "Sigue"
|
||||
|
@ -944,7 +941,7 @@ admin/views/instance.vue:
|
|||
maintainer-email: "Contactar con el administrador"
|
||||
drive-config: "Ajustes del Drive"
|
||||
cache-remote-files: "Mantener en cache los archivos remotos"
|
||||
recaptcha-secret-key: "clave secreta reCAPTCHA"
|
||||
recaptcha-preview: "Vista previa"
|
||||
invite: "Invitar"
|
||||
save: "Guardar"
|
||||
saved: "Guardado"
|
||||
|
@ -1079,8 +1076,6 @@ mobile/views/pages/drive.vue:
|
|||
contextmenu:
|
||||
upload: "Subir fichero"
|
||||
create-folder: "Crear una carpeta"
|
||||
mobile/views/pages/user-lists.vue:
|
||||
title: "Listas"
|
||||
mobile/views/pages/home.vue:
|
||||
home: "Inicio"
|
||||
local: "Local"
|
||||
|
@ -1091,10 +1086,6 @@ mobile/views/pages/widgets.vue:
|
|||
customization-tips: "Consejos de personalización"
|
||||
mobile/views/pages/widgets/activity.vue:
|
||||
activity: "Actividad"
|
||||
mobile/views/pages/received-follow-requests.vue:
|
||||
title: "Solicitudes de seguimiento"
|
||||
accept: "Aceptar"
|
||||
reject: "Rechazar"
|
||||
mobile/views/pages/games/reversi.vue:
|
||||
reversi: "Reversi"
|
||||
mobile/views/pages/search.vue:
|
||||
|
@ -1114,6 +1105,7 @@ deck:
|
|||
deck/deck.user-column.vue:
|
||||
activity: "Actividad"
|
||||
pages:
|
||||
like: "Me gusta"
|
||||
blocks:
|
||||
post: "Formulario"
|
||||
script:
|
||||
|
|
|
@ -243,6 +243,7 @@ common:
|
|||
my-token-regenerated: "Votre jeton vient d’être généré, vous allez maintenant être déconnecté."
|
||||
hide-password: "Masquer 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."
|
||||
user-suspended: "Cet·te utilisateur·trice a été suspendu·e"
|
||||
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"
|
||||
you: "Vous"
|
||||
no-history: "Pas d'historique"
|
||||
user: "Utilisateur·rice·s"
|
||||
common/views/components/messaging-room.vue:
|
||||
empty: "Pas de conversations"
|
||||
no-history: "Aucun historique"
|
||||
resize-form: "Faites glisser pour redimensionner"
|
||||
new-message: "Nouveau message"
|
||||
only-one-file-attached: "Un seul fichier peut être joint au message"
|
||||
common/views/components/messaging-room.form.vue:
|
||||
input-message-here: "Tapez ici votre message"
|
||||
send: "Envoyer"
|
||||
attach-from-local: "Joindre un fichier depuis votre ordinateur"
|
||||
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:
|
||||
is-read: "Lu"
|
||||
deleted: "Ce message a été supprimé"
|
||||
|
@ -675,9 +673,20 @@ common/views/components/user-list-editor.vue:
|
|||
remove-user: "Retirer de cette liste"
|
||||
delete-are-you-sure: "Voulez-vous vraiment supprimer la liste « $1 » ?"
|
||||
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:
|
||||
user-lists: "Listes"
|
||||
create-list: "Créer une 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:
|
||||
fetching: "Récupération"
|
||||
no-broadcasts: "Aucune annonce"
|
||||
|
@ -738,6 +747,10 @@ common/views/pages/follow.vue:
|
|||
request-pending: "Demande d’abonnement en attente"
|
||||
follow-processing: "Demande en attente"
|
||||
follow-request: "Demande d’abonnement"
|
||||
common/views/pages/follow-requests.vue:
|
||||
received-follow-requests: "Demandes d’abonnement"
|
||||
accept: "Accepter"
|
||||
reject: "Refuser"
|
||||
desktop:
|
||||
banner-crop-title: "Découpez la partie qui apparaîtra comme bannière"
|
||||
banner: "Bannière"
|
||||
|
@ -1009,6 +1022,7 @@ desktop/views/components/ui.header.vue:
|
|||
desktop/views/components/ui.header.account.vue:
|
||||
profile: "Votre profil"
|
||||
lists: "Listes"
|
||||
groups: "Groupes"
|
||||
follow-requests: "Demandes d’abonnement"
|
||||
admin: "Admin"
|
||||
desktop/views/components/ui.header.nav.vue:
|
||||
|
@ -1019,12 +1033,6 @@ desktop/views/components/ui.header.post.vue:
|
|||
post: "Rédiger une nouvelle publication"
|
||||
desktop/views/components/ui.header.search.vue:
|
||||
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:
|
||||
notes: "Publications"
|
||||
following: "Abonné à"
|
||||
|
@ -1083,8 +1091,12 @@ admin/views/instance.vue:
|
|||
maintainer-config: "Informations de l’administrateur"
|
||||
maintainer-name: "Nom de l’administrateur"
|
||||
maintainer-email: "Contact administratif"
|
||||
advanced-config: "Autres réglages"
|
||||
drive-config: "Paramètres du lecteur"
|
||||
object-storage-base-url: "URL"
|
||||
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"
|
||||
local-drive-capacity-mb: "Volume du lecteur par utilisateur"
|
||||
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-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"
|
||||
recaptcha-site-key: "Clé reCAPTCHA du site"
|
||||
recaptcha-secret-key: "Clé secrète reCAPTCHA"
|
||||
recaptcha-secret-key: "Clé secrète"
|
||||
recaptcha-preview: "Prévisualisation"
|
||||
twitter-integration-config: "Paramètres de connexion à Twitter"
|
||||
twitter-integration-info: "L'URL de callback est {url}."
|
||||
enable-twitter-integration: "Activer la connexion à Twitter"
|
||||
|
@ -1483,8 +1495,6 @@ mobile/views/pages/drive.vue:
|
|||
rename-folder: "Renommer le dossier"
|
||||
move-folder: "Déplacer ce dossier"
|
||||
delete-folder: "Supprimer ce dossier"
|
||||
mobile/views/pages/user-lists.vue:
|
||||
title: "Listes"
|
||||
mobile/views/pages/signup.vue:
|
||||
lets-start: "Votre compte est prêt ! 📦"
|
||||
mobile/views/pages/followers.vue:
|
||||
|
@ -1508,10 +1518,6 @@ mobile/views/pages/widgets/activity.vue:
|
|||
activity: "Activité"
|
||||
mobile/views/pages/share.vue:
|
||||
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:
|
||||
title: "Publication"
|
||||
prev: "Note précédente"
|
||||
|
@ -1608,6 +1614,7 @@ pages:
|
|||
edit-this-page: "Éditer cette page"
|
||||
view-source: "Afficher la source"
|
||||
view-page: "Afficher la page"
|
||||
like: "Bien"
|
||||
inspector: "Inspecteur"
|
||||
content: "Bloc de page"
|
||||
variables: "Variables"
|
||||
|
|
|
@ -15,6 +15,7 @@ const merge = (...args) => args.reduce((a, c) => ({
|
|||
|
||||
const languages = [
|
||||
'cs-CZ',
|
||||
'da-DK',
|
||||
'de-DE',
|
||||
'en-US',
|
||||
'es-ES',
|
||||
|
|
|
@ -121,6 +121,7 @@ common:
|
|||
update-available-title: "更新があんで"
|
||||
update-available: "Misskeyの新しいバージョンがあんで({newer}。現在{current}をつこてるわ)。ページを再度読み込みしたると更新が適用されるわ。"
|
||||
my-token-regenerated: "あんさんのトークンが更新されたらしいわ。すまんがとりあえずサインアウトすんで。"
|
||||
enter-username: "ユーザー名を入力してや"
|
||||
do-not-use-in-production: "開発ビルドや。本番環境で使わんといて!知らんで!"
|
||||
is-remote-post: "この投稿情報はコピーです。"
|
||||
view-on-remote: "ちゃんとした情報見せてや!"
|
||||
|
@ -294,18 +295,15 @@ common/views/components/messaging.vue:
|
|||
search-user: "ユーザーを探す"
|
||||
you: "あんさん"
|
||||
no-history: "履歴はあらへんで"
|
||||
user: "ユーザー"
|
||||
common/views/components/messaging-room.vue:
|
||||
empty: "このユーザーと話したことはあらへんで"
|
||||
no-history: "これより過去の履歴はあらへんで"
|
||||
resize-form: "ドラッグしてフォームの広さを調整"
|
||||
new-message: "新しいメッセージがあるで"
|
||||
only-one-file-attached: "メッセージに添付できんのはひとつのファイルのみやで"
|
||||
common/views/components/messaging-room.form.vue:
|
||||
input-message-here: "ここにメッセージ書いてや"
|
||||
send: "送信"
|
||||
attach-from-local: "PCからファイルを添付する"
|
||||
attach-from-drive: "ドライブからファイルを添付する"
|
||||
only-one-file-attached: "メッセージに添付できんのはひとつのファイルのみやで"
|
||||
common/views/components/messaging-room.message.vue:
|
||||
is-read: "既読"
|
||||
deleted: "このメッセージは削除されたわ"
|
||||
|
@ -476,8 +474,14 @@ common/views/components/profile-editor.vue:
|
|||
enter-password: "パスワードを入れてや"
|
||||
common/views/components/user-list-editor.vue:
|
||||
users: "ユーザー"
|
||||
add-user: "ユーザー増やす"
|
||||
common/views/components/user-group-editor.vue:
|
||||
invite: "招待"
|
||||
common/views/components/user-lists.vue:
|
||||
user-lists: "リスト"
|
||||
list-name: "リスト名"
|
||||
common/views/components/user-groups.vue:
|
||||
invites: "招待"
|
||||
common/views/widgets/broadcast.vue:
|
||||
fetching: "見てみるわ…"
|
||||
no-broadcasts: "お知らせはあらへんで"
|
||||
|
@ -536,6 +540,8 @@ common/views/pages/follow.vue:
|
|||
request-pending: "フォローの許し待っとる"
|
||||
follow-processing: "今フォロー処理やっとる‥"
|
||||
follow-request: "フォロー許してくれや!言うてみる"
|
||||
common/views/pages/follow-requests.vue:
|
||||
received-follow-requests: "フォロー許してくれや!言うてみる"
|
||||
desktop:
|
||||
banner-crop-title: "どこバナーとして出す?"
|
||||
banner: "バナー"
|
||||
|
@ -809,12 +815,6 @@ desktop/views/components/ui.header.post.vue:
|
|||
post: "新規投稿"
|
||||
desktop/views/components/ui.header.search.vue:
|
||||
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:
|
||||
notes: "投稿"
|
||||
following: "フォロー"
|
||||
|
@ -869,8 +869,7 @@ admin/views/instance.vue:
|
|||
recaptcha-config: "reCAPTCHAの設定"
|
||||
recaptcha-info: "reCAPTCHAを有効にするにはreCAPTCHAトークンが要るで。https://www.google.com/recaptcha/intro/ にアクセスしてトークンを取得してな。"
|
||||
enable-recaptcha: "reCAPTCHAを有効にする"
|
||||
recaptcha-site-key: "reCAPTCHA site key"
|
||||
recaptcha-secret-key: "reCAPTCHA secret key"
|
||||
recaptcha-preview: "試してみる"
|
||||
twitter-integration-config: "Twitter連携の設定"
|
||||
twitter-integration-info: "コールバックURLは {url} に設定してや。"
|
||||
enable-twitter-integration: "Twitter連携を有効にする"
|
||||
|
@ -1148,8 +1147,6 @@ mobile/views/pages/drive.vue:
|
|||
contextmenu:
|
||||
upload: "ファイル上げる"
|
||||
create-folder: "フォルダー作る"
|
||||
mobile/views/pages/user-lists.vue:
|
||||
title: "リスト"
|
||||
mobile/views/pages/signup.vue:
|
||||
lets-start: "📦 始めようや"
|
||||
mobile/views/pages/followers.vue:
|
||||
|
@ -1172,10 +1169,6 @@ mobile/views/pages/widgets/activity.vue:
|
|||
activity: "やっとること"
|
||||
mobile/views/pages/share.vue:
|
||||
share-with: "{name}で共有"
|
||||
mobile/views/pages/received-follow-requests.vue:
|
||||
title: "フォロー許してくれや!"
|
||||
accept: "許す"
|
||||
reject: "許さん"
|
||||
mobile/views/pages/note.vue:
|
||||
title: "投稿"
|
||||
prev: "前のやつ"
|
||||
|
@ -1260,6 +1253,7 @@ dev/views/new-app.vue:
|
|||
authority-desc: "ここにチェックした機能しかAPIからアクセスできひんから気ぃつけてな"
|
||||
authority-warning: "アプリ作った後でも変えれるけど、新しいやつ追加したらそん時関連付いてるユーザーキーは全部ほかされるで。"
|
||||
pages:
|
||||
like: "ええやん"
|
||||
blocks:
|
||||
image: "画像"
|
||||
post: "投稿フォーム"
|
||||
|
|
|
@ -253,6 +253,7 @@ common:
|
|||
my-token-regenerated: "당신의 토큰이 업데이트되었으므로 로그아웃합니다."
|
||||
hide-password: "비밀번호 숨기기"
|
||||
show-password: "비밀번호 표시"
|
||||
enter-username: "사용자명을 입력하여 주십시오"
|
||||
do-not-use-in-production: "이것은 개발 빌드입니다. 프로덕션 환경에서 사용하지 마십시오."
|
||||
user-suspended: "이 사용자는 정지된 상태입니다."
|
||||
is-remote-user: "이 사용자 정보는 정확하지 않을 수 있습니다."
|
||||
|
@ -448,18 +449,23 @@ common/views/components/messaging.vue:
|
|||
search-user: "사용자 찾기"
|
||||
you: "당신"
|
||||
no-history: "기록이 없습니다"
|
||||
user: "사용자"
|
||||
group: "그룹"
|
||||
start-with-user: "사용자와 대화 시작"
|
||||
start-with-group: "그룹과 대화 시작"
|
||||
select-group: "그룹을 선택하여 주십시오"
|
||||
common/views/components/messaging-room.vue:
|
||||
empty: "이 사용자와 대화한 적이 없습니다"
|
||||
not-talked-user: "이 사용자와의 대화가 없습니다"
|
||||
not-talked-group: "이 그룹과의 대화가 없습니다"
|
||||
no-history: "이것보다 과거의 기록이 없습니다"
|
||||
resize-form: "드래그하여 폼의 크기 조절"
|
||||
new-message: "새 메시지가 있습니다"
|
||||
only-one-file-attached: "메시지에는 하나의 파일만 첨부할 수 있습니다"
|
||||
only-one-file-attached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
|
||||
common/views/components/messaging-room.form.vue:
|
||||
input-message-here: "여기에 메시지를 입력하세요"
|
||||
send: "전송"
|
||||
attach-from-local: "PC에서 파일 첨부"
|
||||
attach-from-drive: "드라이브에서 파일 첨부"
|
||||
only-one-file-attached: "메시지에는 하나의 파일만 첨부할 수 있습니다"
|
||||
only-one-file-attached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
|
||||
common/views/components/messaging-room.message.vue:
|
||||
is-read: "읽음"
|
||||
deleted: "이 메시지는 삭제되었습니다"
|
||||
|
@ -585,6 +591,7 @@ common/views/components/signup.vue:
|
|||
password-matched: "확인되었습니다"
|
||||
password-not-matched: "일치하지 않습니다"
|
||||
recaptcha: "자동 가입 방지"
|
||||
agree-to: "{0}에 동의합니다."
|
||||
tos: "이용 약관"
|
||||
create: "계정 만들기"
|
||||
some-error: "알 수 없는 이유로 계정 만들기에 실패했습니다. 다시 한번 시도해 주세요."
|
||||
|
@ -693,9 +700,29 @@ common/views/components/user-list-editor.vue:
|
|||
remove-user: "이 리스트에서 제거"
|
||||
delete-are-you-sure: "리스트 \"$1\"을 삭제하시겠습니까?"
|
||||
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:
|
||||
user-lists: "리스트"
|
||||
create-list: "리스트 만들기"
|
||||
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:
|
||||
fetching: "확인중"
|
||||
no-broadcasts: "공지사항이 없습니다"
|
||||
|
@ -757,6 +784,10 @@ common/views/pages/follow.vue:
|
|||
request-pending: "팔로우 허가 대기중"
|
||||
follow-processing: "팔로우 처리중"
|
||||
follow-request: "팔로우 요청"
|
||||
common/views/pages/follow-requests.vue:
|
||||
received-follow-requests: "팔로우 요청"
|
||||
accept: "승인"
|
||||
reject: "거부"
|
||||
desktop:
|
||||
banner-crop-title: "배너로 표시할 부분을 선택"
|
||||
banner: "배너"
|
||||
|
@ -1028,6 +1059,7 @@ desktop/views/components/ui.header.vue:
|
|||
desktop/views/components/ui.header.account.vue:
|
||||
profile: "프로필"
|
||||
lists: "리스트"
|
||||
groups: "그룹"
|
||||
follow-requests: "팔로우 요청"
|
||||
admin: "관리"
|
||||
desktop/views/components/ui.header.nav.vue:
|
||||
|
@ -1038,12 +1070,6 @@ desktop/views/components/ui.header.post.vue:
|
|||
post: "새 글"
|
||||
desktop/views/components/ui.header.search.vue:
|
||||
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:
|
||||
notes: "글"
|
||||
following: "팔로잉"
|
||||
|
@ -1103,8 +1129,22 @@ admin/views/instance.vue:
|
|||
maintainer-config: "관리자 정보"
|
||||
maintainer-name: "관리자 이름"
|
||||
maintainer-email: "관리자 연락처"
|
||||
advanced-config: "그 외 설정"
|
||||
note-and-tl: "글과 타임라인"
|
||||
drive-config: "드라이브 설정"
|
||||
use-object-storage: "오브젝트 스토리지를 사용"
|
||||
object-storage-base-url: "URL"
|
||||
object-storage-bucket: "버킷 이름"
|
||||
object-storage-prefix: "프리픽스"
|
||||
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-desc: "이 설정을 해지하면 원격 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라 서버의 저장 공간을 절약할 수 있지만, 프라이버시 설정에서 직접 링크를 무효로 설정한 사용자에게는 파일이 보이지 않거나, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다. 일반적으로 이 설정을 ON으로 두는 것을 추천합니다."
|
||||
local-drive-capacity-mb: "로컬 사용자 한 명당 드라이브 용량"
|
||||
|
@ -1113,8 +1153,12 @@ admin/views/instance.vue:
|
|||
recaptcha-config: "reCAPCHA 설정"
|
||||
recaptcha-info: "reCAPCHA를 사용하도록 설정하는 경우 reCAPCHA 토큰을 확보해야 합니다. https://www.google.com/recaptcha/intro/ 에 접속하여 토큰을 가져와주십시오."
|
||||
enable-recaptcha: "reCAPCHA 활성화"
|
||||
recaptcha-site-key: "reCAPTCHA site key"
|
||||
recaptcha-secret-key: "reCAPTCHA secret key"
|
||||
recaptcha-site-key: "사이트 키"
|
||||
recaptcha-secret-key: "시크릿 키"
|
||||
recaptcha-preview: "미리보기"
|
||||
hidden-tags: "숨긴 해시태그"
|
||||
hidden-tags-info: "집계에서 제외할 해시태그를 줄 바꿈으로 구분하여 기술합니다."
|
||||
external-service-integration-config: "외부 서비스 연계"
|
||||
twitter-integration-config: "Twitter 연동 설정"
|
||||
twitter-integration-info: "콜백 URL은 {url} 로 설정됩니다."
|
||||
enable-twitter-integration: "트위터 연동 활성화"
|
||||
|
@ -1146,6 +1190,7 @@ admin/views/instance.vue:
|
|||
save: "저장"
|
||||
saved: "저장하였습니다"
|
||||
pinned-users: "고정된 사용자"
|
||||
pinned-users-info: "고정해두고 싶은 사용자를 줄바꿈으로 구분하여 기술합니다."
|
||||
email-config: "메일 서버 설정"
|
||||
email-config-info: "메일 주소 확인 혹은 비밀번호 재설정에 사용 됩니다."
|
||||
enable-email: "메일 발신 활성화"
|
||||
|
@ -1231,6 +1276,8 @@ admin/views/users.vue:
|
|||
unsilence-confirm: "침묵 해제하시겠습니까?"
|
||||
update-remote-user: "원격 사용자 정보 갱신"
|
||||
remote-user-updated: "원격 사용자 정보를 갱신하였습니다"
|
||||
delete-all-files: "모든 파일 삭제"
|
||||
delete-all-files-confirm: "모든 파일을 삭제하시겠습니까?"
|
||||
users:
|
||||
title: "사용자"
|
||||
sort:
|
||||
|
@ -1306,6 +1353,7 @@ admin/views/federation.vue:
|
|||
latest-request-received-at: "마지막으로 요청을 받은 시간"
|
||||
remove-all-following: "모든 팔로잉 해제"
|
||||
remove-all-following-info: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행하십시오."
|
||||
delete-all-files: "파일을 모두 삭제"
|
||||
block: "차단"
|
||||
marked-as-closed: "폐쇄된 것으로 표시"
|
||||
lookup: "조회"
|
||||
|
@ -1353,6 +1401,7 @@ admin/views/federation.vue:
|
|||
hour: "1시간마다"
|
||||
day: "1일마다"
|
||||
blocked-hosts: "차단"
|
||||
blocked-hosts-info: "차단할 호스트를 줄바꿈으로 구분하여 기술합니다."
|
||||
desktop/views/pages/welcome.vue:
|
||||
about: "자세히..."
|
||||
timeline: "타임라인"
|
||||
|
@ -1509,6 +1558,7 @@ mobile/views/components/ui.nav.vue:
|
|||
follow-requests: "팔로우 요청"
|
||||
search: "검색"
|
||||
user-lists: "리스트"
|
||||
user-groups: "그룹"
|
||||
widgets: "위젯"
|
||||
game: "게임"
|
||||
admin: "관리"
|
||||
|
@ -1521,8 +1571,6 @@ mobile/views/pages/drive.vue:
|
|||
rename-folder: "폴더 이름 바꾸기"
|
||||
move-folder: "이 폴더를 이동"
|
||||
delete-folder: "이 폴더를 삭제"
|
||||
mobile/views/pages/user-lists.vue:
|
||||
title: "리스트"
|
||||
mobile/views/pages/signup.vue:
|
||||
lets-start: "📦 이제 시작해도 됩니다"
|
||||
mobile/views/pages/followers.vue:
|
||||
|
@ -1547,10 +1595,6 @@ mobile/views/pages/widgets/activity.vue:
|
|||
activity: "활동"
|
||||
mobile/views/pages/share.vue:
|
||||
share-with: "{name}(으)로 공유"
|
||||
mobile/views/pages/received-follow-requests.vue:
|
||||
title: "팔로우 요청"
|
||||
accept: "승인"
|
||||
reject: "거부"
|
||||
mobile/views/pages/note.vue:
|
||||
title: "글"
|
||||
prev: "이전 글"
|
||||
|
@ -1656,9 +1700,17 @@ pages:
|
|||
edit-this-page: "이 페이지를 편집"
|
||||
view-source: "소스 보기"
|
||||
view-page: "페이지 보기"
|
||||
like: "좋아요"
|
||||
unlike: "좋아요 해제"
|
||||
liked-pages: "좋아요한 페이지"
|
||||
my-pages: "내 페이지"
|
||||
inspector: "인스펙터"
|
||||
content: "페이지 블록"
|
||||
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: "자세한 설명"
|
||||
title: "제목"
|
||||
url: "페이지 URL"
|
||||
|
@ -1739,6 +1791,8 @@ pages:
|
|||
text: "텍스트"
|
||||
multiLineText: "텍스트 (여러줄)"
|
||||
textList: "텍스트 목록"
|
||||
_textList:
|
||||
info: "각각을 줄 바꿈으로 구분해주십시오"
|
||||
strLen: "텍스트의 길이"
|
||||
_strLen:
|
||||
arg1: "텍스트"
|
||||
|
@ -1828,40 +1882,57 @@ pages:
|
|||
randomPick: "목록에서 임의로 선택"
|
||||
_randomPick:
|
||||
arg1: "리스트"
|
||||
dailyRandom: "랜덤 (하루동안 결과 유지)"
|
||||
_dailyRandom:
|
||||
arg1: "확률"
|
||||
dailyRannum: "난수 (하루동안 결과 유지)"
|
||||
_dailyRannum:
|
||||
arg1: "최소"
|
||||
arg2: "최대"
|
||||
dailyRandomPick: "목록에서 임의로 선택 (하루동안 결과 유지)"
|
||||
_dailyRandomPick:
|
||||
arg1: "리스트"
|
||||
seedRandom: "무작위 (시드)"
|
||||
_seedRandom:
|
||||
arg1: "시드"
|
||||
arg2: "확률"
|
||||
seedRannum: "난수 (시드)"
|
||||
_seedRannum:
|
||||
arg1: "시드"
|
||||
arg2: "최소"
|
||||
arg3: "최대"
|
||||
seedRandomPick: "목록에서 무작위로 선택 (시드)"
|
||||
_seedRandomPick:
|
||||
arg1: "시드"
|
||||
arg2: "리스트"
|
||||
DRPWPM: "확률형 목록에서 임의로 선택 (하루동안 결과 유지)"
|
||||
_DRPWPM:
|
||||
arg1: "텍스트 목록"
|
||||
pick: "목록에서 선택"
|
||||
_pick:
|
||||
arg1: "리스트"
|
||||
arg2: "위치"
|
||||
number: "수치"
|
||||
stringToNumber: "텍스트를 수치로"
|
||||
_stringToNumber:
|
||||
arg1: "텍스트"
|
||||
numberToString: "수치를 텍스트로"
|
||||
_numberToString:
|
||||
arg1: "수치"
|
||||
splitStrByLine: "텍스트를 행 단위로 분할"
|
||||
_splitStrByLine:
|
||||
arg1: "텍스트"
|
||||
ref: "변수"
|
||||
fn: "함수"
|
||||
_fn:
|
||||
slots: "슬롯"
|
||||
slots-info: "각 슬롯을 줄 바꿈으로 구분하여 주십시오"
|
||||
arg1: "출력"
|
||||
for: "반복"
|
||||
_for:
|
||||
arg1: "횟수"
|
||||
arg2: "처리"
|
||||
typeError: "슬롯 {slot}은 \"{expect}\"를 사용할 수 있지만 \"{actual}이 들어있습니다!"
|
||||
thereIsEmptySlot: "슬롯 {slot}이(가) 비었습니다!"
|
||||
types:
|
||||
string: "텍스트"
|
||||
|
|
|
@ -104,10 +104,9 @@ common/views/components/messaging.vue:
|
|||
search-user: "Gebruiker zoeken"
|
||||
you: "Jij"
|
||||
no-history: "Geen geschiedenis"
|
||||
user: "Gebruiker"
|
||||
common/views/components/messaging-room.vue:
|
||||
empty: "Geen gesprekken"
|
||||
no-history: "Er is geen verdere geschiedenis"
|
||||
resize-form: "Versleep om grootte te wijzigen"
|
||||
new-message: "Nieuw bericht"
|
||||
common/views/components/messaging-room.form.vue:
|
||||
input-message-here: "Voer hier je bericht in"
|
||||
|
@ -202,6 +201,9 @@ common/views/components/profile-editor.vue:
|
|||
enter-password: "Voer het wachtwoord in"
|
||||
common/views/components/user-list-editor.vue:
|
||||
users: "Gebruiker"
|
||||
add-user: "Gebruiker toevoegen"
|
||||
common/views/components/user-lists.vue:
|
||||
user-lists: "Lijsten"
|
||||
common/views/widgets/broadcast.vue:
|
||||
fetching: "Bezig met ophalen"
|
||||
no-broadcasts: "Geen uitzendingen"
|
||||
|
@ -401,8 +403,6 @@ desktop/views/components/ui.header.post.vue:
|
|||
post: "Nieuw bericht opstellen"
|
||||
desktop/views/components/ui.header.search.vue:
|
||||
placeholder: "Zoeken"
|
||||
desktop/views/components/user-lists-window.vue:
|
||||
title: "Lijsten"
|
||||
desktop/views/components/user-preview.vue:
|
||||
notes: "Berichten"
|
||||
following: "Volgend"
|
||||
|
@ -558,8 +558,6 @@ mobile/views/pages/drive.vue:
|
|||
contextmenu:
|
||||
upload: "Bestand uploaden"
|
||||
create-folder: "Map creëren"
|
||||
mobile/views/pages/user-lists.vue:
|
||||
title: "Lijsten"
|
||||
mobile/views/pages/home.vue:
|
||||
home: "Startpagina"
|
||||
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-link: "Bewerk deze pagina op GitHub!"
|
||||
pages:
|
||||
like: "Leuk"
|
||||
blocks:
|
||||
image: "Afbeeldingen"
|
||||
script:
|
||||
|
|
|
@ -130,6 +130,7 @@ common/views/components/cw-button.vue:
|
|||
hide: "Skjul"
|
||||
common/views/components/messaging.vue:
|
||||
you: "Du"
|
||||
user: "Bruker"
|
||||
common/views/components/messaging-room.form.vue:
|
||||
send: "Send"
|
||||
common/views/components/messaging-room.message.vue:
|
||||
|
@ -192,8 +193,13 @@ common/views/components/profile-editor.vue:
|
|||
user-lists: "Lister"
|
||||
common/views/components/user-list-editor.vue:
|
||||
users: "Bruker"
|
||||
common/views/components/user-group-editor.vue:
|
||||
invite: "Inviter"
|
||||
common/views/components/user-lists.vue:
|
||||
user-lists: "Lister"
|
||||
list-name: "Liste navn"
|
||||
common/views/components/user-groups.vue:
|
||||
invites: "Inviter"
|
||||
common/views/widgets/broadcast.vue:
|
||||
fetching: "Henter"
|
||||
next: "Neste"
|
||||
|
@ -307,11 +313,6 @@ desktop/views/components/ui.header.post.vue:
|
|||
post: "Skriv nytt innlegg"
|
||||
desktop/views/components/ui.header.search.vue:
|
||||
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:
|
||||
notes: "Innlegg"
|
||||
following: "Følger"
|
||||
|
@ -448,17 +449,12 @@ mobile/views/components/ui.nav.vue:
|
|||
user-lists: "Lister"
|
||||
game: "Spill"
|
||||
admin: "Admin"
|
||||
mobile/views/pages/user-lists.vue:
|
||||
title: "Lister"
|
||||
mobile/views/pages/home.vue:
|
||||
home: "Hjem"
|
||||
local: "Lokalt"
|
||||
global: "Globalt"
|
||||
mobile/views/pages/widgets.vue:
|
||||
add-widget: "Legg til"
|
||||
mobile/views/pages/received-follow-requests.vue:
|
||||
accept: "Godta"
|
||||
reject: "Avslå"
|
||||
mobile/views/pages/note.vue:
|
||||
title: "Innlegg"
|
||||
prev: "Forrige innlegg"
|
||||
|
@ -490,6 +486,7 @@ deck/deck.user-column.vue:
|
|||
followers: "Følgere"
|
||||
images: "Bilder"
|
||||
pages:
|
||||
like: "Lik"
|
||||
blocks:
|
||||
image: "Bilder"
|
||||
script:
|
||||
|
|
|
@ -168,6 +168,7 @@ common:
|
|||
my-token-regenerated: "Twój token został wygenerowany. Zostaniesz wylogowany."
|
||||
hide-password: "Ukryj hasło"
|
||||
show-password: "Pokaż hasło"
|
||||
enter-username: "Wprowadź nazwę użytkownika"
|
||||
view-on-remote: "Dla dopełnienia, zobacz to zdalnie."
|
||||
renoted-by: "{user} udostępnił(a)"
|
||||
error:
|
||||
|
@ -323,18 +324,15 @@ common/views/components/messaging.vue:
|
|||
search-user: "Znajdź użytkownika"
|
||||
you: "Ty"
|
||||
no-history: "Brak historii"
|
||||
user: "Użytkownicy"
|
||||
common/views/components/messaging-room.vue:
|
||||
empty: "Brak konwersacji"
|
||||
no-history: "Brak dalszej historii"
|
||||
resize-form: "Przeciągnij aby zmienić rozmiar"
|
||||
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:
|
||||
input-message-here: "Wprowadź wiadomość tutaj"
|
||||
send: "Wyślij"
|
||||
attach-from-local: "Załącz pliki z komputera"
|
||||
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:
|
||||
is-read: "Przeczytano"
|
||||
deleted: "Wiadomość została usunięta"
|
||||
|
@ -511,8 +509,15 @@ common/views/components/user-list-editor.vue:
|
|||
remove-user: "Usuń z tej listy"
|
||||
delete-are-you-sure: "Usunąć listę \"$1\"?"
|
||||
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:
|
||||
user-lists: "Listy"
|
||||
list-name: "Nazwa listy"
|
||||
common/views/components/user-groups.vue:
|
||||
invites: "Zaproś"
|
||||
common/views/widgets/broadcast.vue:
|
||||
fetching: "Sprawdzanie"
|
||||
no-broadcasts: "Brak transmisji"
|
||||
|
@ -568,6 +573,8 @@ common/views/pages/follow.vue:
|
|||
request-pending: "Oczekiwanie na pozwolenie"
|
||||
follow-processing: "Przetwarzanie śledzenia"
|
||||
follow-request: "Poproś o śledzenie"
|
||||
common/views/pages/follow-requests.vue:
|
||||
received-follow-requests: "Prośby o śledzenie"
|
||||
desktop:
|
||||
banner: "Baner"
|
||||
uploading-banner: "Wysyłanie baneru"
|
||||
|
@ -829,12 +836,6 @@ desktop/views/components/ui.header.post.vue:
|
|||
post: "Utwórz nowy wpis"
|
||||
desktop/views/components/ui.header.search.vue:
|
||||
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:
|
||||
notes: "Wpisy"
|
||||
following: "Śledzeni"
|
||||
|
@ -866,6 +867,7 @@ admin/views/abuse.vue:
|
|||
remove-report: "Usuń"
|
||||
admin/views/instance.vue:
|
||||
instance: "Instancja"
|
||||
recaptcha-preview: "Pokaż podgląd"
|
||||
github-integration-client-id: "Client ID"
|
||||
github-integration-client-secret: "Client Secret"
|
||||
discord-integration-client-id: "Client ID"
|
||||
|
@ -1102,8 +1104,6 @@ mobile/views/pages/drive.vue:
|
|||
contextmenu:
|
||||
upload: "Wyślij plik"
|
||||
create-folder: "Utwórz katalog"
|
||||
mobile/views/pages/user-lists.vue:
|
||||
title: "Listy"
|
||||
mobile/views/pages/signup.vue:
|
||||
lets-start: "Rozpocznijmy! 📦"
|
||||
mobile/views/pages/home.vue:
|
||||
|
@ -1118,10 +1118,6 @@ mobile/views/pages/widgets.vue:
|
|||
customization-tips: "Wskazówki o dostosowywaniu"
|
||||
mobile/views/pages/widgets/activity.vue:
|
||||
activity: "Aktywność"
|
||||
mobile/views/pages/received-follow-requests.vue:
|
||||
title: "Prośby o śledzenie"
|
||||
accept: "Zatwierdź"
|
||||
reject: "Odmów"
|
||||
mobile/views/pages/note.vue:
|
||||
title: "Wpis"
|
||||
prev: "Poprzedni wpis"
|
||||
|
@ -1196,6 +1192,7 @@ dev/views/new-app.vue:
|
|||
app-name: "Nazwa Aplikacji"
|
||||
authority: "Uprawnienia"
|
||||
pages:
|
||||
like: "Lubię"
|
||||
title: "Tytuł"
|
||||
blocks:
|
||||
image: "Zdjęcia"
|
||||
|
|
|
@ -160,6 +160,10 @@ common/views/components/profile-editor.vue:
|
|||
name: "Nome"
|
||||
export-targets:
|
||||
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:
|
||||
title: "Gráfico de publicações"
|
||||
common/views/widgets/memo.vue:
|
||||
|
@ -270,6 +274,7 @@ docs:
|
|||
dev/views/index.vue:
|
||||
manage-apps: "Gerenciar aplicativos"
|
||||
pages:
|
||||
like: "Curtir"
|
||||
blocks:
|
||||
image: "Imagens"
|
||||
post: "Formulário de publicação"
|
||||
|
|
|
@ -147,6 +147,7 @@ mobile/views/components/sub-note-content.vue:
|
|||
mobile/views/pages/widgets.vue:
|
||||
customization-tips: "Советы по настройке"
|
||||
pages:
|
||||
like: "Нравится"
|
||||
script:
|
||||
categories:
|
||||
random: "Случайно"
|
||||
|
|
|
@ -253,6 +253,7 @@ common:
|
|||
my-token-regenerated: "您的 Token 已被重置, 您将自动登出。"
|
||||
hide-password: "隐藏密码"
|
||||
show-password: "显示密码"
|
||||
enter-username: "请输入用户名"
|
||||
do-not-use-in-production: "这是一个开发者测试版. 请勿在生产环境中使用."
|
||||
user-suspended: "该用户已被冻结。"
|
||||
is-remote-user: "此用户信息可能不准确。"
|
||||
|
@ -448,18 +449,22 @@ common/views/components/messaging.vue:
|
|||
search-user: "查找用户"
|
||||
you: "您"
|
||||
no-history: "没有历史记录"
|
||||
user: "用户"
|
||||
group: "群组"
|
||||
start-with-user: "开始用户聊天"
|
||||
start-with-group: "开始群组聊天"
|
||||
common/views/components/messaging-room.vue:
|
||||
empty: "没有对话"
|
||||
not-talked-user: "没有用户的会话记录"
|
||||
not-talked-group: "没有群组的会话记录"
|
||||
no-history: "没有更多的历史记录"
|
||||
resize-form: "拖动以调整大小"
|
||||
new-message: "新信息"
|
||||
only-one-file-attached: "只有一个附件可以附加到消息中。"
|
||||
only-one-file-attached: "只能添加一个附件"
|
||||
common/views/components/messaging-room.form.vue:
|
||||
input-message-here: "在此键入信息"
|
||||
send: "发送"
|
||||
attach-from-local: "从电脑中添加文件"
|
||||
attach-from-drive: "从网盘中添加文件"
|
||||
only-one-file-attached: "在信息中只允许添加一个附件"
|
||||
only-one-file-attached: "只能添加一个附件"
|
||||
common/views/components/messaging-room.message.vue:
|
||||
is-read: "已读"
|
||||
deleted: "此消息已被删除"
|
||||
|
@ -694,9 +699,25 @@ common/views/components/user-list-editor.vue:
|
|||
remove-user: "从此列表中删除"
|
||||
delete-are-you-sure: "删除列表“$1”?"
|
||||
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:
|
||||
user-lists: "列表"
|
||||
create-list: "创建列表"
|
||||
list-name: "列表名称"
|
||||
common/views/components/user-groups.vue:
|
||||
user-groups: "群组"
|
||||
create-group: "创建群组"
|
||||
group-name: "群组名"
|
||||
invites: "邀请"
|
||||
reject-invite: "拒绝"
|
||||
common/views/widgets/broadcast.vue:
|
||||
fetching: "确认中"
|
||||
no-broadcasts: "没有公告"
|
||||
|
@ -758,6 +779,10 @@ common/views/pages/follow.vue:
|
|||
request-pending: "发送关注申请"
|
||||
follow-processing: "申请处理中"
|
||||
follow-request: "关注请求"
|
||||
common/views/pages/follow-requests.vue:
|
||||
received-follow-requests: "关注申请"
|
||||
accept: "接受"
|
||||
reject: "拒绝"
|
||||
desktop:
|
||||
banner-crop-title: "裁剪显示为背景的部分"
|
||||
banner: "背景"
|
||||
|
@ -1029,6 +1054,7 @@ desktop/views/components/ui.header.vue:
|
|||
desktop/views/components/ui.header.account.vue:
|
||||
profile: "个人资料"
|
||||
lists: "列表"
|
||||
groups: "群组"
|
||||
follow-requests: "关注申请"
|
||||
admin: "管理"
|
||||
desktop/views/components/ui.header.nav.vue:
|
||||
|
@ -1039,12 +1065,6 @@ desktop/views/components/ui.header.post.vue:
|
|||
post: "撰写新帖子"
|
||||
desktop/views/components/ui.header.search.vue:
|
||||
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:
|
||||
notes: "帖子"
|
||||
following: "关注中"
|
||||
|
@ -1128,8 +1148,9 @@ admin/views/instance.vue:
|
|||
recaptcha-config: "reCAPTCHA设置"
|
||||
recaptcha-info: "reCAPTCHA token是必要的. 请从 https://www.google.com/recaptcha/intro/ 获取。\n请注意, 该功能在中国大陆不可用。"
|
||||
enable-recaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)"
|
||||
recaptcha-site-key: "reCAPTCHA site key"
|
||||
recaptcha-secret-key: "reCAPTCHA secret key"
|
||||
recaptcha-site-key: "网站密钥"
|
||||
recaptcha-secret-key: "密钥"
|
||||
recaptcha-preview: "预览"
|
||||
hidden-tags: "隐藏哈希标签"
|
||||
hidden-tags-info: "使用换行符分隔要从集合中排除的哈希标签。"
|
||||
external-service-integration-config: "连接外部服务"
|
||||
|
@ -1532,6 +1553,7 @@ mobile/views/components/ui.nav.vue:
|
|||
follow-requests: "关注申请"
|
||||
search: "搜索"
|
||||
user-lists: "列表"
|
||||
user-groups: "群组"
|
||||
widgets: "小部件"
|
||||
game: "游戏"
|
||||
admin: "管理"
|
||||
|
@ -1544,8 +1566,6 @@ mobile/views/pages/drive.vue:
|
|||
rename-folder: "重命名文件夹"
|
||||
move-folder: "移动此文件夹"
|
||||
delete-folder: "删除此文件夹"
|
||||
mobile/views/pages/user-lists.vue:
|
||||
title: "列表"
|
||||
mobile/views/pages/signup.vue:
|
||||
lets-start: "您的账户现已准备就绪! 📦"
|
||||
mobile/views/pages/followers.vue:
|
||||
|
@ -1570,10 +1590,6 @@ mobile/views/pages/widgets/activity.vue:
|
|||
activity: "活动"
|
||||
mobile/views/pages/share.vue:
|
||||
share-with: "共享{name}"
|
||||
mobile/views/pages/received-follow-requests.vue:
|
||||
title: "关注申请"
|
||||
accept: "批准"
|
||||
reject: "拒绝"
|
||||
mobile/views/pages/note.vue:
|
||||
title: "帖文"
|
||||
prev: "上一个帖子"
|
||||
|
@ -1679,6 +1695,10 @@ pages:
|
|||
edit-this-page: "编辑此页面"
|
||||
view-source: "查看源代码"
|
||||
view-page: "查看页面"
|
||||
like: "赞"
|
||||
unlike: "取消赞"
|
||||
liked-pages: "喜欢的页面"
|
||||
my-pages: "个人页面"
|
||||
inspector: "检查器"
|
||||
content: "页面内容"
|
||||
variables: "变量"
|
||||
|
|
|
@ -86,3 +86,5 @@ admin/views/dashboard.vue:
|
|||
drive: "雲端硬碟"
|
||||
admin/views/charts.vue:
|
||||
drive: "雲端硬碟"
|
||||
pages:
|
||||
like: "贊"
|
||||
|
|
14
package.json
14
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "11.17.1",
|
||||
"version": "11.18.0",
|
||||
"codename": "daybreak",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -97,7 +97,7 @@
|
|||
"@types/websocket": "0.0.40",
|
||||
"@types/ws": "6.0.1",
|
||||
"animejs": "3.0.1",
|
||||
"apexcharts": "3.6.11",
|
||||
"apexcharts": "3.6.12",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"autosize": "4.0.2",
|
||||
"autwh": "0.1.0",
|
||||
|
@ -107,7 +107,7 @@
|
|||
"cafy": "15.1.1",
|
||||
"chai": "4.2.0",
|
||||
"chalk": "2.4.2",
|
||||
"cli-highlight": "2.1.0",
|
||||
"cli-highlight": "2.1.1",
|
||||
"commander": "2.20.0",
|
||||
"content-disposition": "0.5.3",
|
||||
"crc-32": "1.2.0",
|
||||
|
@ -215,7 +215,7 @@
|
|||
"summaly": "2.2.0",
|
||||
"systeminformation": "4.1.6",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"terser-webpack-plugin": "1.2.3",
|
||||
"terser-webpack-plugin": "1.2.4",
|
||||
"textarea-caret": "3.1.0",
|
||||
"tinycolor2": "1.4.1",
|
||||
"tmp": "0.1.0",
|
||||
|
@ -248,12 +248,12 @@
|
|||
"vue-style-loader": "4.1.2",
|
||||
"vue-svg-inline-loader": "1.2.15",
|
||||
"vue-template-compiler": "2.6.10",
|
||||
"vuedraggable": "2.20.0",
|
||||
"vuedraggable": "2.21.0",
|
||||
"vuewordcloud": "18.7.11",
|
||||
"vuex": "3.1.1",
|
||||
"vuex-persistedstate": "2.5.4",
|
||||
"web-push": "3.3.4",
|
||||
"webpack": "4.31.0",
|
||||
"web-push": "3.3.5",
|
||||
"webpack": "4.32.0",
|
||||
"webpack-cli": "3.3.2",
|
||||
"websocket": "1.0.28",
|
||||
"ws": "7.0.0",
|
||||
|
|
|
@ -144,8 +144,6 @@ export default prop => ({
|
|||
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>
|
||||
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.a
|
||||
display block
|
||||
|
|
|
@ -21,7 +21,6 @@ import avatar from './avatar.vue';
|
|||
import nav from './nav.vue';
|
||||
import misskeyFlavoredMarkdown from './misskey-flavored-markdown.vue';
|
||||
import poll from './poll.vue';
|
||||
import pollEditor from './poll-editor.vue';
|
||||
import reactionIcon from './reaction-icon.vue';
|
||||
import reactionsViewer from './reactions-viewer.vue';
|
||||
import time from './time.vue';
|
||||
|
@ -46,6 +45,7 @@ import uiSelect from './ui/select.vue';
|
|||
import uiInfo from './ui/info.vue';
|
||||
import uiMargin from './ui/margin.vue';
|
||||
import uiHr from './ui/hr.vue';
|
||||
import uiPagination from './ui/pagination.vue';
|
||||
import formButton from './ui/form/button.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-nav', nav);
|
||||
Vue.component('mk-poll', poll);
|
||||
Vue.component('mk-poll-editor', pollEditor);
|
||||
Vue.component('mk-reaction-icon', reactionIcon);
|
||||
Vue.component('mk-reactions-viewer', reactionsViewer);
|
||||
Vue.component('mk-time', time);
|
||||
|
@ -95,5 +94,6 @@ Vue.component('ui-select', uiSelect);
|
|||
Vue.component('ui-info', uiInfo);
|
||||
Vue.component('ui-margin', uiMargin);
|
||||
Vue.component('ui-hr', uiHr);
|
||||
Vue.component('ui-pagination', uiPagination);
|
||||
Vue.component('form-button', formButton);
|
||||
Vue.component('form-radio', formRadio);
|
||||
|
|
|
@ -21,42 +21,23 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="history" v-if="messages.length > 0">
|
||||
<div class="title">{{ $t('user') }}</div>
|
||||
<a v-for="message in messages"
|
||||
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-read="message.isRead"
|
||||
@click.prevent="navigate(isMe(message) ? message.recipient : message.user)"
|
||||
:data-is-read="message.groupId ? message.reads.includes($store.state.i.id) : message.isRead"
|
||||
@click.prevent="message.groupId ? navigateGroup(message.group) : navigate(isMe(message) ? message.recipient : message.user)"
|
||||
:key="message.id"
|
||||
>
|
||||
<div>
|
||||
<mk-avatar class="avatar" :user="isMe(message) ? message.recipient : message.user"/>
|
||||
<header>
|
||||
<span class="name"><mk-user-name :user="isMe(message) ? message.recipient : message.user"/></span>
|
||||
<span class="username">@{{ isMe(message) ? message.recipient : message.user | acct }}</span>
|
||||
<mk-avatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user"/>
|
||||
<header v-if="message.groupId">
|
||||
<span class="name">{{ message.group.name }}</span>
|
||||
<mk-time :time="message.createdAt"/>
|
||||
</header>
|
||||
<div class="body">
|
||||
<p class="text"><span class="me" v-if="isMe(message)">{{ $t('you') }}:</span>{{ message.text }}</p>
|
||||
</div>
|
||||
</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>
|
||||
<header v-else>
|
||||
<span class="name"><mk-user-name :user="isMe(message) ? message.recipient : message.user"/></span>
|
||||
<span class="username">@{{ isMe(message) ? message.recipient : message.user | acct }}</span>
|
||||
<mk-time :time="message.createdAt"/>
|
||||
</header>
|
||||
<div class="body">
|
||||
|
@ -97,7 +78,6 @@ export default Vue.extend({
|
|||
fetching: true,
|
||||
moreFetching: false,
|
||||
messages: [],
|
||||
groupMessages: [],
|
||||
q: null,
|
||||
result: [],
|
||||
connection: null,
|
||||
|
@ -110,10 +90,11 @@ export default Vue.extend({
|
|||
this.connection.on('message', this.onMessage);
|
||||
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 => {
|
||||
const messages = userMessages.concat(groupMessages);
|
||||
messages.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
||||
this.messages = messages;
|
||||
this.groupMessages = groupMessages;
|
||||
this.fetching = false;
|
||||
});
|
||||
});
|
||||
|
@ -134,8 +115,8 @@ export default Vue.extend({
|
|||
|
||||
this.messages.unshift(message);
|
||||
} else if (message.groupId) {
|
||||
this.groupMessages = this.groupMessages.filter(m => m.groupId !== message.groupId);
|
||||
this.groupMessages.unshift(message);
|
||||
this.messages = this.messages.filter(m => m.groupId !== message.groupId);
|
||||
this.messages.unshift(message);
|
||||
}
|
||||
},
|
||||
onRead(ids) {
|
||||
|
@ -243,9 +224,6 @@ export default Vue.extend({
|
|||
font-size 0.8em
|
||||
|
||||
> .history
|
||||
> .title
|
||||
padding 8px
|
||||
|
||||
> a
|
||||
&:last-child
|
||||
border-bottom none
|
||||
|
@ -384,14 +362,6 @@ export default Vue.extend({
|
|||
color rgba(#000, 0.3)
|
||||
|
||||
> .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
|
||||
display block
|
||||
text-decoration none
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="mk-poll-editor">
|
||||
<div class="zmdxowus">
|
||||
<p class="caution" v-if="choices.length < 2">
|
||||
<fa icon="exclamation-triangle"/>{{ $t('no-only-one-choice') }}
|
||||
</p>
|
||||
|
@ -134,7 +134,7 @@ export default Vue.extend({
|
|||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.mk-poll-editor
|
||||
.zmdxowus
|
||||
padding 8px
|
||||
|
||||
> .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">
|
||||
<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="no-users" v-if="inited && us.length == 0">
|
||||
<div class="no-users" v-if="empty">
|
||||
<p>{{ $t('no-users') }}</p>
|
||||
</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"/>
|
||||
<div class="body" v-if="!iconOnly">
|
||||
<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/>
|
||||
</div>
|
||||
</div>
|
||||
<button class="more" :class="{ fetching: fetchingMoreUsers }" v-if="cursor != null" @click="fetchMoreUsers()" :disabled="fetchingMoreUsers">
|
||||
<template v-if="fetchingMoreUsers"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreUsers ? $t('@.loading') : $t('@.load-more') }}
|
||||
<button class="more" :class="{ fetching: moreFetching }" v-if="more" @click="fetchMoreUsers()" :disabled="moreFetching">
|
||||
<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>{{ moreFetching ? $t('@.loading') : $t('@.load-more') }}
|
||||
</button>
|
||||
</div>
|
||||
</ui-container>
|
||||
|
@ -31,60 +31,31 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import paging from '../../../common/scripts/paging';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/user-list.vue'),
|
||||
|
||||
mixins: [
|
||||
paging({}),
|
||||
],
|
||||
|
||||
props: {
|
||||
makePromise: {
|
||||
pagination: {
|
||||
required: true
|
||||
},
|
||||
extract: {
|
||||
required: false
|
||||
},
|
||||
iconOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
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;
|
||||
});
|
||||
computed: {
|
||||
users() {
|
||||
return this.extract ? this.extract(this.items) : this.items;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<x-column :name="name" :column="column" :is-stacked="isStacked">
|
||||
<template #header><fa :icon="['far', 'envelope']"/>{{ name }}</template>
|
||||
|
||||
<x-direct/>
|
||||
<x-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
|
||||
</x-column>
|
||||
</template>
|
||||
|
||||
|
@ -10,13 +10,14 @@
|
|||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import XColumn from './deck.column.vue';
|
||||
import XDirect from './deck.direct.vue';
|
||||
import XNotes from './deck.notes.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
|
||||
components: {
|
||||
XColumn,
|
||||
XDirect
|
||||
XNotes
|
||||
},
|
||||
|
||||
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: {
|
||||
name(): string {
|
||||
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: {
|
||||
onNote(note) {
|
||||
// Prepend a note
|
||||
if (note.visibility == 'specified') {
|
||||
(this.$refs.timeline as any).prepend(note);
|
||||
}
|
||||
},
|
||||
|
||||
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>
|
||||
<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
|
||||
<x-notes ref="timeline" :pagination="pagination" @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
|
||||
|
@ -28,28 +26,18 @@ export default Vue.extend({
|
|||
data() {
|
||||
return {
|
||||
connection: null,
|
||||
makePromise: cursor => this.$root.api('notes/search-by-tag', {
|
||||
limit: fetchLimit + 1,
|
||||
untilId: cursor ? cursor : undefined,
|
||||
pagination: {
|
||||
endpoint: 'notes/search-by-tag',
|
||||
limit: 10,
|
||||
params: init => ({
|
||||
untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||
withFiles: this.mediaOnly,
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
|
||||
query: this.tagTl.query
|
||||
}).then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
notes.pop();
|
||||
return {
|
||||
notes: notes,
|
||||
more: true
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
notes: notes,
|
||||
more: false
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
<template>
|
||||
<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
|
||||
<x-notes ref="timeline" :pagination="pagination" @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
|
||||
|
@ -28,28 +26,18 @@ export default Vue.extend({
|
|||
data() {
|
||||
return {
|
||||
connection: null,
|
||||
makePromise: cursor => this.$root.api('notes/user-list-timeline', {
|
||||
pagination: {
|
||||
endpoint: 'notes/user-list-timeline',
|
||||
limit: 10,
|
||||
params: init => ({
|
||||
listId: this.list.id,
|
||||
limit: fetchLimit + 1,
|
||||
untilId: cursor ? cursor : undefined,
|
||||
untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||
withFiles: this.mediaOnly,
|
||||
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
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<x-column :name="name" :column="column" :is-stacked="isStacked">
|
||||
<template #header><fa icon="at"/>{{ name }}</template>
|
||||
|
||||
<x-mentions ref="tl"/>
|
||||
<x-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
|
||||
</x-column>
|
||||
</template>
|
||||
|
||||
|
@ -10,13 +10,14 @@
|
|||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import XColumn from './deck.column.vue';
|
||||
import XMentions from './deck.mentions.vue';
|
||||
import XNotes from './deck.notes.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
|
||||
components: {
|
||||
XColumn,
|
||||
XMentions
|
||||
XNotes
|
||||
},
|
||||
|
||||
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: {
|
||||
name(): string {
|
||||
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: {
|
||||
onNote(note) {
|
||||
(this.$refs.timeline as any).prepend(note);
|
||||
},
|
||||
|
||||
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>
|
||||
<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">
|
||||
<template v-for="i in 10">
|
||||
|
@ -16,7 +16,6 @@
|
|||
<mk-note
|
||||
:note="note"
|
||||
:key="note.id"
|
||||
@update:note="onNoteUpdated(i, $event)"
|
||||
:compact="true"
|
||||
/>
|
||||
<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 i18n from '../../../i18n';
|
||||
import shouldMuteNote from '../../../common/scripts/should-mute-note';
|
||||
|
||||
const displayLimit = 20;
|
||||
import paging from '../../../common/scripts/paging';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
|
||||
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: {
|
||||
makePromise: {
|
||||
pagination: {
|
||||
required: true
|
||||
},
|
||||
extract: {
|
||||
required: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
rootEl: null,
|
||||
notes: [],
|
||||
queue: [],
|
||||
fetching: true,
|
||||
moreFetching: false,
|
||||
inited: false,
|
||||
more: false
|
||||
};
|
||||
computed: {
|
||||
notes() {
|
||||
return this.extract ? this.extract(this.items) : this.items;
|
||||
},
|
||||
|
||||
computed: {
|
||||
_notes(): any[] {
|
||||
return (this.notes as any).map(note => {
|
||||
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() {
|
||||
this.column.$on('top', this.onTop);
|
||||
this.column.$on('bottom', this.onBottom);
|
||||
|
@ -101,88 +105,6 @@ export default Vue.extend({
|
|||
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>
|
||||
|
|
|
@ -81,15 +81,15 @@
|
|||
</div>
|
||||
|
||||
<template v-if="notification.type == 'quote'">
|
||||
<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
|
||||
<mk-note :note="notification.note"/>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type == 'reply'">
|
||||
<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
|
||||
<mk-note :note="notification.note"/>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type == 'mention'">
|
||||
<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
|
||||
<mk-note :note="notification.note"/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -105,17 +105,6 @@ export default Vue.extend({
|
|||
getNoteSummary
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onNoteUpdated(note) {
|
||||
switch (this.notification.type) {
|
||||
case 'quote':
|
||||
case 'reply':
|
||||
case 'mention':
|
||||
Vue.set(this.notification, 'note', note);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -10,16 +10,17 @@
|
|||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications" tag="div">
|
||||
<template v-for="(notification, i) in _notifications">
|
||||
<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-down"/>{{ _notifications[i + 1]._datetext }}</span>
|
||||
</p>
|
||||
</template>
|
||||
</component>
|
||||
<button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
|
||||
<template v-if="fetchingMoreNotifications"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreNotifications ? this.$t('@.loading') : this.$t('@.load-more') }}
|
||||
<button class="more" :class="{ fetching: moreFetching }" v-if="more" @click="fetchMore" :disabled="moreFetching">
|
||||
<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>{{ moreFetching ? this.$t('@.loading') : this.$t('@.load-more') }}
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
@ -27,31 +28,38 @@
|
|||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import XNotification from './deck.notification.vue';
|
||||
|
||||
const displayLimit = 20;
|
||||
import paging from '../../../common/scripts/paging';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
|
||||
components: {
|
||||
XNotification
|
||||
},
|
||||
|
||||
inject: ['column', 'isScrollTop', 'count'],
|
||||
|
||||
mixins: [
|
||||
paging({
|
||||
onQueueChanged: (self, q) => {
|
||||
self.count(q.length);
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
fetchingMoreNotifications: false,
|
||||
notifications: [],
|
||||
queue: [],
|
||||
moreNotifications: false,
|
||||
connection: null
|
||||
connection: null,
|
||||
pagination: {
|
||||
endpoint: 'i/notifications',
|
||||
limit: 20,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
_notifications(): any[] {
|
||||
return (this.notifications as any).map(notification => {
|
||||
return (this.items as any).map(notification => {
|
||||
const date = new Date(notification.createdAt).getDate();
|
||||
const month = new Date(notification.createdAt).getMonth() + 1;
|
||||
notification._date = date;
|
||||
|
@ -61,33 +69,12 @@ export default Vue.extend({
|
|||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
queue(q) {
|
||||
this.count(q.length);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.connection = this.$root.stream.useSharedConnection('main');
|
||||
|
||||
this.connection.on('notification', this.onNotification);
|
||||
|
||||
this.column.$on('top', this.onTop);
|
||||
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() {
|
||||
|
@ -98,26 +85,6 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
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) {
|
||||
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
||||
this.$root.stream.send('readNotification', {
|
||||
|
@ -126,35 +93,6 @@ export default Vue.extend({
|
|||
|
||||
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>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</template>
|
||||
|
||||
<div>
|
||||
<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
|
||||
<x-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
|
||||
</div>
|
||||
</x-column>
|
||||
</template>
|
||||
|
@ -16,8 +16,6 @@ import XColumn from './deck.column.vue';
|
|||
import XNotes from './deck.notes.vue';
|
||||
import { genSearchQuery } from '../../../common/scripts/gen-search-query';
|
||||
|
||||
const limit = 20;
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XColumn,
|
||||
|
@ -26,24 +24,11 @@ export default Vue.extend({
|
|||
|
||||
data() {
|
||||
return {
|
||||
makePromise: async cursor => this.$root.api('notes/search', {
|
||||
limit: limit + 1,
|
||||
offset: cursor ? cursor : undefined,
|
||||
...(await 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
|
||||
};
|
||||
pagination: {
|
||||
endpoint: 'notes/search',
|
||||
limit: 20,
|
||||
params: () => genSearchQuery(this, this.q)
|
||||
}
|
||||
})
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</p>
|
||||
<p class="desc">{{ $t('disabled-timeline.description') }}</p>
|
||||
</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>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -15,8 +15,6 @@ import XNotes from './deck.notes.vue';
|
|||
import { faMinusCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
const fetchLimit = 10;
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('deck'),
|
||||
|
||||
|
@ -42,7 +40,7 @@ export default Vue.extend({
|
|||
connection: null,
|
||||
disabled: false,
|
||||
faMinusCircle,
|
||||
makePromise: null
|
||||
pagination: null
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -73,27 +71,17 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
created() {
|
||||
this.makePromise = cursor => this.$root.api(this.endpoint, {
|
||||
limit: fetchLimit + 1,
|
||||
untilId: cursor ? cursor : undefined,
|
||||
this.pagination = {
|
||||
endpoint: this.endpoint,
|
||||
limit: 10,
|
||||
params: init => ({
|
||||
untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||
withFiles: this.mediaOnly,
|
||||
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() {
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<ui-container>
|
||||
<template #header><fa :icon="['far', 'comment-alt']"/> {{ $t('timeline') }}</template>
|
||||
<div>
|
||||
<x-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
|
||||
<x-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
|
||||
</div>
|
||||
</ui-container>
|
||||
</div>
|
||||
|
@ -43,8 +43,6 @@ import XNotes from './deck.notes.vue';
|
|||
import { concat } from '../../../../../prelude/array';
|
||||
import ApexCharts from 'apexcharts';
|
||||
|
||||
const fetchLimit = 10;
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('deck/deck.user-column.vue'),
|
||||
|
||||
|
@ -63,49 +61,38 @@ export default Vue.extend({
|
|||
return {
|
||||
withFiles: false,
|
||||
images: [],
|
||||
makePromise: null,
|
||||
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: {
|
||||
user() {
|
||||
this.fetch();
|
||||
this.genPromiseMaker();
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.fetch();
|
||||
this.genPromiseMaker();
|
||||
},
|
||||
|
||||
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() {
|
||||
const image = [
|
||||
'image/jpeg',
|
||||
|
|
|
@ -18,24 +18,24 @@
|
|||
</div>
|
||||
</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 }}
|
||||
</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') }})
|
||||
</mk-user-list>
|
||||
|
||||
<template v-if="tag == null">
|
||||
<mk-user-list :make-promise="pinnedUsers">
|
||||
<mk-user-list :pagination="pinnedUsers">
|
||||
<fa :icon="faBookmark" fixed-width/>{{ $t('pinned-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :make-promise="popularUsers">
|
||||
<mk-user-list :pagination="popularUsers">
|
||||
<fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :make-promise="recentlyUpdatedUsers">
|
||||
<mk-user-list :pagination="recentlyUpdatedUsers">
|
||||
<fa :icon="faCommentAlt" fixed-width/>{{ $t('recently-updated-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :make-promise="recentlyRegisteredUsers">
|
||||
<mk-user-list :pagination="recentlyRegisteredUsers">
|
||||
<fa :icon="faPlus" fixed-width/>{{ $t('recently-registered-users') }}
|
||||
</mk-user-list>
|
||||
</template>
|
||||
|
@ -60,24 +60,21 @@ export default Vue.extend({
|
|||
|
||||
data() {
|
||||
return {
|
||||
pinnedUsers: () => this.$root.api('pinned-users'),
|
||||
popularUsers: () => this.$root.api('users', {
|
||||
pinnedUsers: { endpoint: 'pinned-users' },
|
||||
popularUsers: { endpoint: 'users', limit: 10, params: {
|
||||
state: 'alive',
|
||||
origin: 'local',
|
||||
sort: '+follower',
|
||||
limit: 10
|
||||
}),
|
||||
recentlyUpdatedUsers: () => this.$root.api('users', {
|
||||
} },
|
||||
recentlyUpdatedUsers: { endpoint: 'users', limit: 10, params: {
|
||||
origin: 'local',
|
||||
sort: '+updatedAt',
|
||||
limit: 10
|
||||
}),
|
||||
recentlyRegisteredUsers: () => this.$root.api('users', {
|
||||
} },
|
||||
recentlyRegisteredUsers: { endpoint: 'users', limit: 10, params: {
|
||||
origin: 'local',
|
||||
state: 'alive',
|
||||
sort: '+createdAt',
|
||||
limit: 10
|
||||
}),
|
||||
} },
|
||||
tagsLocal: [],
|
||||
tagsRemote: [],
|
||||
stats: null,
|
||||
|
@ -88,24 +85,30 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
computed: {
|
||||
tagUsers(): () => Promise<any> {
|
||||
return () => this.$root.api('hashtags/users', {
|
||||
tagUsers(): any {
|
||||
return {
|
||||
endpoint: 'hashtags/users',
|
||||
limit: 30,
|
||||
params: {
|
||||
tag: this.tag,
|
||||
state: 'alive',
|
||||
origin: 'local',
|
||||
sort: '+follower',
|
||||
limit: 30
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
tagRemoteUsers(): () => Promise<any> {
|
||||
return () => this.$root.api('hashtags/users', {
|
||||
tagRemoteUsers(): any {
|
||||
return {
|
||||
endpoint: 'hashtags/users',
|
||||
limit: 30,
|
||||
params: {
|
||||
tag: this.tag,
|
||||
state: 'alive',
|
||||
origin: 'remote',
|
||||
sort: '+follower',
|
||||
limit: 30
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
|
|
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>
|
||||
<div>
|
||||
<mk-user-list :make-promise="makePromise">{{ $t('@.followers') }}</mk-user-list>
|
||||
</div>
|
||||
<mk-user-list :pagination="pagination" :extract="items => items.map(item => item.follower)">{{ $t('@.followers') }}</mk-user-list>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -9,31 +7,18 @@ import Vue from 'vue';
|
|||
import parseAcct from '../../../../../misc/acct/parse';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
const fetchLimit = 30;
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
|
||||
data() {
|
||||
return {
|
||||
makePromise: cursor => this.$root.api('users/followers', {
|
||||
pagination: {
|
||||
endpoint: 'users/followers',
|
||||
limit: 30,
|
||||
params: {
|
||||
...parseAcct(this.$route.params.user),
|
||||
limit: fetchLimit + 1,
|
||||
untilId: cursor ? cursor : undefined,
|
||||
}).then(followings => {
|
||||
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>
|
||||
<div>
|
||||
<mk-user-list :make-promise="makePromise">{{ $t('@.following') }}</mk-user-list>
|
||||
</div>
|
||||
<mk-user-list :pagination="pagination" :extract="items => items.map(item => item.followee)">{{ $t('@.following') }}</mk-user-list>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -9,31 +7,18 @@ import Vue from 'vue';
|
|||
import parseAcct from '../../../../../misc/acct/parse';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
const fetchLimit = 30;
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
|
||||
data() {
|
||||
return {
|
||||
makePromise: cursor => this.$root.api('users/following', {
|
||||
pagination: {
|
||||
endpoint: 'users/following',
|
||||
limit: 30,
|
||||
params: {
|
||||
...parseAcct(this.$route.params.user),
|
||||
limit: fetchLimit + 1,
|
||||
untilId: cursor ? cursor : undefined,
|
||||
}).then(followings => {
|
||||
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>
|
||||
<ui-container :body-togglable="true">
|
||||
<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>
|
||||
<sequential-entrance animation="entranceFromTop" delay="25" tag="div" class="pages">
|
||||
<x-page-preview v-for="page in pages" class="page" :page="page" :key="page.id"/>
|
||||
</sequential-entrance>
|
||||
<ui-button v-if="existMore" @click="fetchMore()" style="margin-top:16px;">{{ $t('@.load-more') }}</ui-button>
|
||||
<ui-pagination :pagination="myPagesPagination" #default="{items}">
|
||||
<x-page-preview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/>
|
||||
</ui-pagination>
|
||||
</div>
|
||||
</ui-container>
|
||||
|
||||
<ui-container :body-togglable="true">
|
||||
<template #header><fa :icon="faHeart" fixed-width/>{{ $t('liked-pages') }}</template>
|
||||
<div class="rknalgpo" v-if="!fetching">
|
||||
<sequential-entrance animation="entranceFromTop" delay="25" tag="div" class="pages">
|
||||
<x-page-preview v-for="like in likes" class="page" :page="like.page" :key="like.page.id"/>
|
||||
</sequential-entrance>
|
||||
<ui-button v-if="existMoreLikes" @click="fetchMoreLiked()">{{ $t('@.load-more') }}</ui-button>
|
||||
<div class="rknalgpo">
|
||||
<ui-pagination :pagination="likedPagesPagination" #default="{items}">
|
||||
<x-page-preview v-for="like in items" class="ckltabjg" :page="like.page" :key="like.page.id"/>
|
||||
</ui-pagination>
|
||||
</div>
|
||||
</ui-container>
|
||||
</div>
|
||||
|
@ -28,7 +26,6 @@ import Vue from 'vue';
|
|||
import { faPlus, faEdit } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faStickyNote, faHeart } from '@fortawesome/free-regular-svg-icons';
|
||||
import i18n from '../../../i18n';
|
||||
import Progress from '../../scripts/loading';
|
||||
import XPagePreview from '../../views/components/page-preview.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
|
@ -38,87 +35,24 @@ export default Vue.extend({
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
pages: [],
|
||||
existMore: false,
|
||||
moreFetching: false,
|
||||
likes: [],
|
||||
existMoreLikes: false,
|
||||
moreLikesFetching: false,
|
||||
myPagesPagination: {
|
||||
endpoint: 'i/pages',
|
||||
limit: 5,
|
||||
},
|
||||
likedPagesPagination: {
|
||||
endpoint: 'i/page-likes',
|
||||
limit: 5,
|
||||
},
|
||||
faStickyNote, faPlus, faEdit, faHeart
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetch();
|
||||
|
||||
this.$emit('init', {
|
||||
title: this.$t('@.pages'),
|
||||
icon: faStickyNote
|
||||
});
|
||||
},
|
||||
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() {
|
||||
this.$router.push(`/i/pages/new`);
|
||||
}
|
||||
|
@ -130,14 +64,14 @@ export default Vue.extend({
|
|||
.rknalgpo
|
||||
padding 16px
|
||||
|
||||
> .new
|
||||
margin-bottom 16px
|
||||
&.my .ckltabjg:first-child
|
||||
margin-top 16px
|
||||
|
||||
> * > .page:not(:last-child)
|
||||
.ckltabjg:not(:last-child)
|
||||
margin-bottom 8px
|
||||
|
||||
@media (min-width 500px)
|
||||
> * > .page:not(:last-child)
|
||||
.ckltabjg:not(:last-child)
|
||||
margin-bottom 16px
|
||||
|
||||
</style>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<h1>{{ $t('share-with', { name }) }}</h1>
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
<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({
|
||||
i18n: i18n('mobile/views/pages/share.vue'),
|
||||
components: {
|
||||
XPostForm: () => import('../../../desktop/views/components/post-form.vue').then(m => m.default)
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: null,
|
||||
|
@ -35,15 +38,15 @@ export default Vue.extend({
|
|||
return t.trim();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
window.close();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.name = meta.name || 'Misskey';
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<ui-container>
|
||||
<ui-container :body-togglable="true">
|
||||
<template #header><fa :icon="faUsers"/> {{ $t('owned-groups') }}</template>
|
||||
<ui-margin>
|
||||
<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('create-group') }}</ui-button>
|
||||
|
@ -9,26 +9,29 @@
|
|||
<ui-hr/>
|
||||
<ui-margin>
|
||||
<router-link :to="`/i/groups/${group.id}`">{{ group.name }}</router-link>
|
||||
<x-avatars :user-ids="group.userIds" style="margin-top:8px;"/>
|
||||
</ui-margin>
|
||||
</div>
|
||||
</ui-container>
|
||||
|
||||
<ui-container>
|
||||
<ui-container :body-togglable="true">
|
||||
<template #header><fa :icon="faUsers"/> {{ $t('joined-groups') }}</template>
|
||||
<div class="hwgkdrbl" v-for="(group, i) in joinedGroups" :key="group.id">
|
||||
<ui-hr v-if="i != 0"/>
|
||||
<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>
|
||||
</div>
|
||||
</ui-container>
|
||||
|
||||
<ui-container>
|
||||
<ui-container :body-togglable="true">
|
||||
<template #header><fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</template>
|
||||
<div class="fvlojuur" v-for="(invite, i) in invites" :key="invite.id">
|
||||
<ui-hr v-if="i != 0"/>
|
||||
<ui-margin>
|
||||
<div class="name">{{ invite.group.name }}</div>
|
||||
<x-avatars :user-ids="invite.group.userIds" style="margin-top:8px;"/>
|
||||
<ui-horizon-group>
|
||||
<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>
|
||||
|
@ -41,11 +44,15 @@
|
|||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
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({
|
||||
i18n: i18n('common/views/components/user-groups.vue'),
|
||||
components: {
|
||||
XAvatars
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ownedGroups: [],
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<ui-hr/>
|
||||
<ui-margin>
|
||||
<router-link :to="`/i/lists/${list.id}`">{{ list.name }}</router-link>
|
||||
<x-avatars :user-ids="list.userIds" style="margin-top:8px;"/>
|
||||
</ui-margin>
|
||||
</div>
|
||||
</ui-container>
|
||||
|
@ -17,9 +18,13 @@
|
|||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { faListUl, faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import XAvatars from '../../views/components/avatars.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/user-lists.vue'),
|
||||
components: {
|
||||
XAvatars
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
import define from '../../../common/define-widget';
|
||||
import i18n from '../../../i18n';
|
||||
import insertTextAtCursor from 'insert-text-at-cursor';
|
||||
import XPostFormAttaches from '../components/post-form-attaches.vue';
|
||||
|
||||
export default define({
|
||||
name: 'post-form',
|
||||
|
@ -49,7 +48,7 @@ export default define({
|
|||
i18n: i18n('desktop/views/widgets/post-form.vue'),
|
||||
|
||||
components: {
|
||||
XPostFormAttaches
|
||||
XPostFormAttaches: () => import('../components/post-form-attaches.vue').then(m => m.default)
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
|
@ -13,7 +13,6 @@ import fuckAdBlock from '../common/scripts/fuck-ad-block';
|
|||
import composeNotification from '../common/scripts/compose-notification';
|
||||
|
||||
import MkHome from './views/home/home.vue';
|
||||
import MkDeck from '../common/views/deck/deck.vue';
|
||||
import MkSelectDrive from './views/pages/selectdrive.vue';
|
||||
import MkDrive from './views/pages/drive.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 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 MkChooseFileFromDriveWindow from './views/components/choose-file-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);
|
||||
} else {
|
||||
const vm = this.$root.new(PostFormWindow, {
|
||||
this.$root.newAsync(() => import('./views/components/post-form-window.vue').then(m => m.default), {
|
||||
reply: o.reply,
|
||||
mention: o.mention,
|
||||
animation: o.animation == null ? true : o.animation
|
||||
});
|
||||
}).then(vm => {
|
||||
if (o.cb) vm.$once('closed', o.cb);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -129,7 +128,7 @@ init(async (launch, os) => {
|
|||
mode: 'history',
|
||||
routes: [
|
||||
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: '', 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) },
|
||||
|
@ -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: '/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: '/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/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/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 }) },
|
||||
|
@ -158,10 +157,10 @@ init(async (launch, os) => {
|
|||
{ 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: '/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/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/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) },
|
||||
|
|
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 subNoteContent from './sub-note-content.vue';
|
||||
import window from './window.vue';
|
||||
import noteFormWindow from './post-form-window.vue';
|
||||
import renoteFormWindow from './renote-form-window.vue';
|
||||
import mediaVideo from './media-video.vue';
|
||||
import notifications from './notifications.vue';
|
||||
import noteForm from './post-form.vue';
|
||||
import renoteForm from './renote-form.vue';
|
||||
import notePreview from './note-preview.vue';
|
||||
import noteDetail from './note-detail.vue';
|
||||
|
@ -25,11 +23,9 @@ Vue.component('mk-note', note);
|
|||
Vue.component('mk-notes', notes);
|
||||
Vue.component('mk-sub-note-content', subNoteContent);
|
||||
Vue.component('mk-window', window);
|
||||
Vue.component('mk-post-form-window', noteFormWindow);
|
||||
Vue.component('mk-renote-form-window', renoteFormWindow);
|
||||
Vue.component('mk-media-video', mediaVideo);
|
||||
Vue.component('mk-notifications', notifications);
|
||||
Vue.component('mk-post-form', noteForm);
|
||||
Vue.component('mk-renote-form', renoteForm);
|
||||
Vue.component('mk-note-preview', notePreview);
|
||||
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="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">
|
||||
<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">
|
||||
<template v-for="(note, i) in _notes">
|
||||
<mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" :compact="true" ref="note"/>
|
||||
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
|
||||
<mk-note :note="note" :key="note.id" :compact="true" ref="note"/>
|
||||
<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-down"/>{{ _notes[i + 1]._datetext }}</span>
|
||||
</p>
|
||||
|
@ -39,153 +39,66 @@ import Vue from 'vue';
|
|||
import i18n from '../../../i18n';
|
||||
import * as config from '../../../config';
|
||||
import shouldMuteNote from '../../../common/scripts/should-mute-note';
|
||||
|
||||
const displayLimit = 30;
|
||||
import paging from '../../../common/scripts/paging';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
|
||||
props: {
|
||||
makePromise: {
|
||||
required: true
|
||||
mixins: [
|
||||
paging({
|
||||
captureWindowScroll: true,
|
||||
|
||||
onQueueChanged: (self, x) => {
|
||||
if (x.length > 0) {
|
||||
self.$store.commit('indicate', true);
|
||||
} else {
|
||||
self.$store.commit('indicate', false);
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
notes: [],
|
||||
queue: [],
|
||||
fetching: true,
|
||||
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: {
|
||||
_notes(): any[] {
|
||||
return (this.notes as any).map(note => {
|
||||
const date = new Date(note.createdAt).getDate();
|
||||
const month = new Date(note.createdAt).getMonth() + 1;
|
||||
note._date = date;
|
||||
note._datetext = this.$t('@.month-and-day').replace('{month}', month.toString()).replace('{day}', date.toString());
|
||||
return note;
|
||||
return (this.items as any).map(item => {
|
||||
const date = new Date(item.createdAt).getDate();
|
||||
const month = new Date(item.createdAt).getMonth() + 1;
|
||||
item._date = date;
|
||||
item._datetext = this.$t('@.month-and-day').replace('{month}', month.toString()).replace('{day}', date.toString());
|
||||
return item;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
|
||||
mounted() {
|
||||
window.addEventListener('scroll', this.onScroll, { passive: true });
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('scroll', this.onScroll);
|
||||
},
|
||||
|
||||
methods: {
|
||||
isScrollTop() {
|
||||
return window.scrollY <= 8;
|
||||
},
|
||||
|
||||
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>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</template>
|
||||
</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">
|
||||
<template v-for="(notification, i) in _notifications">
|
||||
|
@ -125,17 +125,18 @@
|
|||
</template>
|
||||
</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-down"/>{{ _notifications[i + 1]._datetext }}</span>
|
||||
</p>
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
<button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
|
||||
<template v-if="fetchingMoreNotifications"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreNotifications ? $t('@.loading') : $t('@.load-more') }}
|
||||
<button class="more" :class="{ fetching: moreFetching }" v-if="more" @click="fetchMore" :disabled="moreFetching">
|
||||
<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>{{ moreFetching ? $t('@.loading') : $t('@.load-more') }}
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
@ -143,23 +144,29 @@
|
|||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import getNoteSummary from '../../../../../misc/get-note-summary';
|
||||
import paging from '../../../common/scripts/paging';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
|
||||
mixins: [
|
||||
paging({}),
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
fetchingMoreNotifications: false,
|
||||
notifications: [],
|
||||
moreNotifications: false,
|
||||
connection: null,
|
||||
getNoteSummary
|
||||
getNoteSummary,
|
||||
pagination: {
|
||||
endpoint: 'i/notifications',
|
||||
limit: 10,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
_notifications(): any[] {
|
||||
return (this.notifications as any).map(notification => {
|
||||
return (this.items as any).map(notification => {
|
||||
const date = new Date(notification.createdAt).getDate();
|
||||
const month = new Date(notification.createdAt).getMonth() + 1;
|
||||
notification._date = date;
|
||||
|
@ -171,22 +178,7 @@ export default Vue.extend({
|
|||
|
||||
mounted() {
|
||||
this.connection = this.$root.stream.useSharedConnection('main');
|
||||
|
||||
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() {
|
||||
|
@ -194,33 +186,13 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
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) {
|
||||
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
||||
this.$root.stream.send('readNotification', {
|
||||
id: notification.id
|
||||
});
|
||||
|
||||
this.notifications.unshift(notification);
|
||||
this.prepend(notification);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<div class="mk-post-form-window--body">
|
||||
<mk-note-preview v-if="reply" class="notePreview" :note="reply"/>
|
||||
<mk-post-form ref="form"
|
||||
<x-post-form ref="form"
|
||||
:reply="reply"
|
||||
:mention="mention"
|
||||
@posted="onPosted"
|
||||
|
@ -27,9 +27,15 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import XPostForm from './post-form.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/post-form-window.vue'),
|
||||
|
||||
components: {
|
||||
XPostForm
|
||||
},
|
||||
|
||||
props: {
|
||||
reply: {
|
||||
type: Object,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="mk-post-form"
|
||||
<div class="gjisdzwh"
|
||||
@dragover.stop="onDragover"
|
||||
@dragenter="onDragenter"
|
||||
@dragleave="onDragleave"
|
||||
|
@ -28,7 +28,7 @@
|
|||
<fa :icon="['far', 'laugh']"/>
|
||||
</button>
|
||||
<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>
|
||||
<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
|
||||
|
@ -72,7 +72,8 @@ export default Vue.extend({
|
|||
|
||||
components: {
|
||||
MkVisibilityChooser,
|
||||
XPostFormAttaches
|
||||
XPostFormAttaches,
|
||||
XPollEditor: () => import('../../../common/views/components/poll-editor.vue').then(m => m.default)
|
||||
},
|
||||
|
||||
props: {
|
||||
|
@ -518,7 +519,7 @@ export default Vue.extend({
|
|||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.mk-post-form
|
||||
.gjisdzwh
|
||||
display block
|
||||
padding 16px
|
||||
background var(--desktopPostFormBg)
|
||||
|
@ -617,7 +618,7 @@ export default Vue.extend({
|
|||
border-bottom solid 1px var(--primaryAlpha01) !important
|
||||
border-radius 0
|
||||
|
||||
> .mk-poll-editor
|
||||
> .poll-editor
|
||||
background var(--desktopPostFormTextareaBg)
|
||||
border solid 1px var(--primaryAlpha01)
|
||||
border-top none
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</footer>
|
||||
</template>
|
||||
<template v-if="quote">
|
||||
<mk-post-form ref="form" :renote="note" @posted="onChildFormPosted"/>
|
||||
<x-post-form ref="form" :renote="note" @posted="onChildFormPosted"/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -22,6 +22,10 @@ import i18n from '../../../i18n';
|
|||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/renote-form.vue'),
|
||||
|
||||
components: {
|
||||
XPostForm: () => import('./post-form.vue').then(m => m.default)
|
||||
},
|
||||
|
||||
props: {
|
||||
note: {
|
||||
type: Object,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')">
|
||||
<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')">
|
||||
<template #header>
|
||||
<slot></slot>
|
||||
</template>
|
||||
|
@ -11,36 +11,23 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
const fetchLimit = 10;
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['list'],
|
||||
data() {
|
||||
return {
|
||||
connection: null,
|
||||
date: null,
|
||||
makePromise: cursor => this.$root.api('notes/user-list-timeline', {
|
||||
pagination: {
|
||||
endpoint: 'notes/user-list-timeline',
|
||||
limit: 10,
|
||||
params: init => ({
|
||||
listId: this.list.id,
|
||||
limit: fetchLimit + 1,
|
||||
untilId: cursor ? cursor : undefined,
|
||||
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||
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
|
||||
}).then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
notes.pop();
|
||||
return {
|
||||
notes: notes,
|
||||
more: true
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
notes: notes,
|
||||
more: false
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
},
|
||||
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>
|
||||
<div>
|
||||
<mk-notes ref="timeline" :make-promise="makePromise" @inited="inited">
|
||||
<mk-notes ref="timeline" :pagination="pagination" @inited="inited">
|
||||
<template #header>
|
||||
<header class="oxgbmvii">
|
||||
<span><fa icon="search"/> {{ q }}</span>
|
||||
|
@ -16,30 +16,15 @@ import i18n from '../../../i18n';
|
|||
import Progress from '../../../common/scripts/loading';
|
||||
import { genSearchQuery } from '../../../common/scripts/gen-search-query';
|
||||
|
||||
const limit = 20;
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/pages/search.vue'),
|
||||
data() {
|
||||
return {
|
||||
makePromise: async cursor => this.$root.api('notes/search', {
|
||||
limit: limit + 1,
|
||||
offset: cursor ? cursor : undefined,
|
||||
...(await 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
|
||||
};
|
||||
pagination: {
|
||||
endpoint: 'notes/search',
|
||||
limit: 20,
|
||||
params: () => genSearchQuery(this, this.q)
|
||||
}
|
||||
})
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<mk-notes ref="timeline" :make-promise="makePromise" @inited="inited">
|
||||
<mk-notes ref="timeline" :pagination="pagination" @inited="inited">
|
||||
<template #header>
|
||||
<header class="wqraeznr">
|
||||
<span><fa icon="hashtag"/> {{ $route.params.tag }}</span>
|
||||
|
@ -15,30 +15,17 @@ import Vue from 'vue';
|
|||
import i18n from '../../../i18n';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
|
||||
const limit = 20;
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/pages/tag.vue'),
|
||||
data() {
|
||||
return {
|
||||
makePromise: cursor => this.$root.api('notes/search-by-tag', {
|
||||
limit: limit + 1,
|
||||
offset: cursor ? cursor : undefined,
|
||||
pagination: {
|
||||
endpoint: 'notes/search-by-tag',
|
||||
limit: 20,
|
||||
params: {
|
||||
tag: this.$route.params.tag
|
||||
}).then(notes => {
|
||||
if (notes.length == limit + 1) {
|
||||
notes.pop();
|
||||
return {
|
||||
notes: notes,
|
||||
cursor: cursor ? cursor + limit : limit
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
notes: notes,
|
||||
more: false
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')">
|
||||
<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')">
|
||||
<template #header>
|
||||
<slot></slot>
|
||||
<div v-if="src == 'home' && alone" class="ibpylqas">
|
||||
|
@ -16,8 +16,6 @@
|
|||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
const fetchLimit = 10;
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/timeline.core.vue'),
|
||||
|
||||
|
@ -42,7 +40,7 @@ export default Vue.extend({
|
|||
},
|
||||
query: {},
|
||||
endpoint: null,
|
||||
makePromise: null
|
||||
pagination: null
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -109,25 +107,14 @@ export default Vue.extend({
|
|||
this.connection.on('mention', onNote);
|
||||
}
|
||||
|
||||
this.makePromise = cursor => this.$root.api(this.endpoint, {
|
||||
limit: fetchLimit + 1,
|
||||
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||
untilId: cursor ? cursor : undefined,
|
||||
this.pagination = {
|
||||
endpoint: this.endpoint,
|
||||
limit: 10,
|
||||
params: init => ({
|
||||
untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||
...this.baseQuery, ...this.query
|
||||
}).then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
notes.pop();
|
||||
return {
|
||||
notes: notes,
|
||||
more: true
|
||||
})
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
notes: notes,
|
||||
more: false
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<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">
|
||||
<component :is="src == 'list' ? 'mk-user-list-timeline' : 'x-core'" ref="tl" v-bind="options">
|
||||
<header class="zahtxcqi">
|
||||
|
@ -31,8 +31,10 @@ import MkSettingsWindow from '../components/settings-window.vue';
|
|||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/timeline.vue'),
|
||||
|
||||
components: {
|
||||
XCore
|
||||
XCore,
|
||||
XPostForm: () => import('../components/post-form.vue').then(m => m.default)
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div>
|
||||
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')">
|
||||
<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')">
|
||||
<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 == '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>
|
||||
|
@ -17,8 +17,6 @@
|
|||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
|
||||
const fetchLimit = 10;
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/pages/user/user.timeline.vue'),
|
||||
|
||||
|
@ -30,28 +28,17 @@ export default Vue.extend({
|
|||
mode: 'default',
|
||||
unreadCount: 0,
|
||||
date: null,
|
||||
makePromise: cursor => this.$root.api('users/notes', {
|
||||
pagination: {
|
||||
endpoint: 'users/notes',
|
||||
limit: 10,
|
||||
params: init => ({
|
||||
userId: this.user.id,
|
||||
limit: fetchLimit + 1,
|
||||
untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||
includeReplies: this.mode == 'with-replies',
|
||||
includeMyRenotes: this.mode != 'my-posts',
|
||||
withFiles: this.mode == 'with-media',
|
||||
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||
untilId: cursor ? cursor : undefined
|
||||
}).then(notes => {
|
||||
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>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.oh5y2r7l5lx8j6jj791ykeiwgihheguk
|
||||
.kugajpep
|
||||
padding 0 8px
|
||||
z-index 10
|
||||
background var(--faceHeader)
|
||||
|
|
|
@ -456,6 +456,18 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS)
|
|||
document.body.appendChild(x.$el);
|
||||
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) {
|
||||
const vm = this.new(Dialog, opts);
|
||||
const p: any = new Promise((res) => {
|
||||
|
|
|
@ -11,16 +11,13 @@ import './style.styl';
|
|||
import init from '../init';
|
||||
|
||||
import MkIndex from './views/pages/index.vue';
|
||||
import MkDeck from '../common/views/deck/deck.vue';
|
||||
import MkSignup from './views/pages/signup.vue';
|
||||
import MkSelectDrive from './views/pages/selectdrive.vue';
|
||||
import MkDrive from './views/pages/drive.vue';
|
||||
import MkWidgets from './views/pages/widgets.vue';
|
||||
import MkMessaging from './views/pages/messaging.vue';
|
||||
import MkMessagingRoom from './views/pages/messaging-room.vue';
|
||||
import MkNote from './views/pages/note.vue';
|
||||
import MkSearch from './views/pages/search.vue';
|
||||
import MkFavorites from './views/pages/favorites.vue';
|
||||
import UI from './views/pages/ui.vue';
|
||||
import MkReversi from './views/pages/games/reversi.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 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 FolderChooser from './views/components/drive-folder-chooser.vue';
|
||||
|
||||
|
@ -54,16 +50,16 @@ init((launch, os) => {
|
|||
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,
|
||||
mention: o.mention,
|
||||
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();
|
||||
});
|
||||
},
|
||||
|
||||
$chooseDriveFile(opts) {
|
||||
|
@ -114,7 +110,7 @@ init((launch, os) => {
|
|||
mode: 'history',
|
||||
routes: [
|
||||
...(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: '', 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) },
|
||||
|
@ -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: '/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: '/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/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/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 }) },
|
||||
|
@ -138,14 +134,14 @@ init((launch, os) => {
|
|||
]),
|
||||
{ path: '/signup', name: 'signup', component: MkSignup },
|
||||
{ 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/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/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/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/group/:group', component: MkMessagingRoom },
|
||||
{ path: '/i/messaging/:user', component: MkMessagingRoom },
|
||||
|
@ -157,7 +153,7 @@ init((launch, os) => {
|
|||
{ path: '/selectdrive', component: MkSelectDrive },
|
||||
{ path: '/search', component: MkSearch },
|
||||
{ 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/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 },
|
||||
|
|
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 userListTimeline from './user-list-timeline.vue';
|
||||
import uiContainer from './ui-container.vue';
|
||||
import postForm from './post-form.vue';
|
||||
|
||||
Vue.component('mk-ui', ui);
|
||||
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-list-timeline', userListTimeline);
|
||||
Vue.component('ui-container', uiContainer);
|
||||
Vue.component('mk-post-form', postForm);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<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">
|
||||
<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">
|
||||
<template v-for="(note, i) in _notes">
|
||||
<mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
|
||||
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
|
||||
<mk-note :note="note" :key="note.id"/>
|
||||
<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-down"/>{{ _notes[i + 1]._datetext }}</span>
|
||||
</p>
|
||||
|
@ -34,158 +34,56 @@
|
|||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import shouldMuteNote from '../../../common/scripts/should-mute-note';
|
||||
|
||||
const displayLimit = 30;
|
||||
import paging from '../../../common/scripts/paging';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
|
||||
props: {
|
||||
makePromise: {
|
||||
required: true
|
||||
mixins: [
|
||||
paging({
|
||||
captureWindowScroll: true,
|
||||
|
||||
onQueueChanged: (self, x) => {
|
||||
if (x.length > 0) {
|
||||
self.$store.commit('indicate', true);
|
||||
} else {
|
||||
self.$store.commit('indicate', false);
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
notes: [],
|
||||
queue: [],
|
||||
fetching: true,
|
||||
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: {
|
||||
_notes(): any[] {
|
||||
return (this.notes as any).map(note => {
|
||||
const date = new Date(note.createdAt).getDate();
|
||||
const month = new Date(note.createdAt).getMonth() + 1;
|
||||
note._date = date;
|
||||
note._datetext = this.$t('@.month-and-day').replace('{month}', month.toString()).replace('{day}', date.toString());
|
||||
return note;
|
||||
return (this.items as any).map(item => {
|
||||
const date = new Date(item.createdAt).getDate();
|
||||
const month = new Date(item.createdAt).getMonth() + 1;
|
||||
item._date = date;
|
||||
item._datetext = this.$t('@.month-and-day').replace('{month}', month.toString()).replace('{day}', date.toString());
|
||||
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>
|
||||
|
||||
|
|
|
@ -71,15 +71,15 @@
|
|||
</div>
|
||||
|
||||
<template v-if="notification.type == 'quote'">
|
||||
<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
|
||||
<mk-note :note="notification.note"/>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type == 'reply'">
|
||||
<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
|
||||
<mk-note :note="notification.note"/>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type == 'mention'">
|
||||
<mk-note :note="notification.note" @update:note="onNoteUpdated"/>
|
||||
<mk-note :note="notification.note"/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -95,17 +95,6 @@ export default Vue.extend({
|
|||
getNoteSummary
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onNoteUpdated(note) {
|
||||
switch (this.notification.type) {
|
||||
case 'quote':
|
||||
case 'reply':
|
||||
case 'mention':
|
||||
Vue.set(this.notification, 'note', note);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -10,41 +10,49 @@
|
|||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications" tag="div">
|
||||
<template v-for="(notification, i) in _notifications">
|
||||
<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-down"/>{{ _notifications[i + 1]._datetext }}</span>
|
||||
</p>
|
||||
</template>
|
||||
</component>
|
||||
|
||||
<button class="more" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
|
||||
<template v-if="fetchingMoreNotifications"><fa icon="spinner" pulse fixed-width/></template>
|
||||
{{ fetchingMoreNotifications ? $t('@.loading') : $t('@.load-more') }}
|
||||
<button class="more" v-if="more" @click="fetchMore" :disabled="moreFetching">
|
||||
<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>
|
||||
{{ moreFetching ? $t('@.loading') : $t('@.load-more') }}
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import paging from '../../../common/scripts/paging';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/components/notifications.vue'),
|
||||
|
||||
mixins: [
|
||||
paging({}),
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
fetchingMoreNotifications: false,
|
||||
notifications: [],
|
||||
moreNotifications: false,
|
||||
connection: null
|
||||
connection: null,
|
||||
pagination: {
|
||||
endpoint: 'i/notifications',
|
||||
limit: 15,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
_notifications(): any[] {
|
||||
return (this.notifications as any).map(notification => {
|
||||
return (this.items as any).map(notification => {
|
||||
const date = new Date(notification.createdAt).getDate();
|
||||
const month = new Date(notification.createdAt).getMonth() + 1;
|
||||
notification._date = date;
|
||||
|
@ -55,76 +63,23 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
mounted() {
|
||||
window.addEventListener('scroll', this.onScroll, { passive: true });
|
||||
|
||||
this.connection = this.$root.stream.useSharedConnection('main');
|
||||
|
||||
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() {
|
||||
window.removeEventListener('scroll', this.onScroll);
|
||||
this.connection.dispose();
|
||||
},
|
||||
|
||||
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) {
|
||||
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
||||
this.$root.stream.send('readNotification', {
|
||||
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>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
|
||||
<div class="bg" ref="bg"></div>
|
||||
<div class="main" ref="main">
|
||||
<mk-post-form ref="form"
|
||||
<x-post-form ref="form"
|
||||
:reply="reply"
|
||||
:renote="renote"
|
||||
:mention="mention"
|
||||
|
@ -17,8 +17,13 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import anime from 'animejs';
|
||||
import XPostForm from './post-form.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XPostForm
|
||||
},
|
||||
|
||||
props: {
|
||||
reply: {
|
||||
type: Object,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="mk-post-form">
|
||||
<div class="gafaadew">
|
||||
<div class="form">
|
||||
<header>
|
||||
<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' }">
|
||||
<textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder" v-autocomplete="{ model: 'text' }"></textarea>
|
||||
<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"/>
|
||||
<footer>
|
||||
<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({
|
||||
i18n: i18n('mobile/views/components/post-form.vue'),
|
||||
components: {
|
||||
XPostFormAttaches
|
||||
XPostFormAttaches,
|
||||
XPollEditor: () => import('../../../common/views/components/poll-editor.vue').then(m => m.default)
|
||||
},
|
||||
|
||||
props: {
|
||||
|
@ -386,7 +387,7 @@ export default Vue.extend({
|
|||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.mk-post-form
|
||||
.gafaadew
|
||||
max-width 500px
|
||||
width calc(100% - 16px)
|
||||
margin 8px auto
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<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="backdrop"></div>
|
||||
<div class="content" ref="mainContainer">
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
<template>
|
||||
<div>
|
||||
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
|
||||
</div>
|
||||
<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
const fetchLimit = 10;
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['list'],
|
||||
|
||||
|
@ -16,28 +12,17 @@ export default Vue.extend({
|
|||
return {
|
||||
connection: null,
|
||||
date: null,
|
||||
makePromise: cursor => this.$root.api('notes/user-list-timeline', {
|
||||
pagination: {
|
||||
endpoint: 'notes/user-list-timeline',
|
||||
limit: 10,
|
||||
params: init => ({
|
||||
listId: this.list.id,
|
||||
limit: fetchLimit + 1,
|
||||
untilId: cursor ? cursor : undefined,
|
||||
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||
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
|
||||
}).then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
notes.pop();
|
||||
return {
|
||||
notes: notes,
|
||||
more: true
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
notes: notes,
|
||||
more: false
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
<template>
|
||||
<div class="mk-user-timeline">
|
||||
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
|
||||
</div>
|
||||
<mk-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
const fetchLimit = 10;
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/components/user-timeline.vue'),
|
||||
|
||||
|
@ -18,26 +14,15 @@ export default Vue.extend({
|
|||
data() {
|
||||
return {
|
||||
date: null,
|
||||
makePromise: cursor => this.$root.api('users/notes', {
|
||||
pagination: {
|
||||
endpoint: 'users/notes',
|
||||
limit: 10,
|
||||
params: init => ({
|
||||
userId: this.user.id,
|
||||
limit: fetchLimit + 1,
|
||||
withFiles: this.withMedia,
|
||||
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||
untilId: cursor ? cursor : undefined
|
||||
}).then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
notes.pop();
|
||||
return {
|
||||
notes: notes,
|
||||
more: true
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
notes: notes,
|
||||
more: false
|
||||
};
|
||||
}
|
||||
untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||
})
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -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>
|
||||
</ui-container>
|
||||
|
||||
<mk-notes ref="timeline" :make-promise="makePromise" @inited="() => $emit('loaded')"/>
|
||||
<mk-notes ref="timeline" :pagination="pagination" @loaded="() => $emit('loaded')"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -15,8 +15,6 @@
|
|||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
const fetchLimit = 10;
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/pages/home.timeline.vue'),
|
||||
|
||||
|
@ -43,7 +41,7 @@ export default Vue.extend({
|
|||
},
|
||||
query: {},
|
||||
endpoint: null,
|
||||
makePromise: null
|
||||
pagination: null
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -110,25 +108,14 @@ export default Vue.extend({
|
|||
this.connection.on('mention', onNote);
|
||||
}
|
||||
|
||||
this.makePromise = cursor => this.$root.api(this.endpoint, {
|
||||
limit: fetchLimit + 1,
|
||||
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||
untilId: cursor ? cursor : undefined,
|
||||
this.pagination = {
|
||||
endpoint: this.endpoint,
|
||||
limit: 10,
|
||||
params: init => ({
|
||||
untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||
...this.baseQuery, ...this.query
|
||||
}).then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
notes.pop();
|
||||
return {
|
||||
notes: notes,
|
||||
more: true
|
||||
})
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
notes: notes,
|
||||
more: false
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<template #header><fa icon="search"/> {{ q }}</template>
|
||||
|
||||
<main>
|
||||
<mk-notes ref="timeline" :make-promise="makePromise" @inited="inited"/>
|
||||
<mk-notes ref="timeline" :pagination="pagination" @inited="inited"/>
|
||||
</main>
|
||||
</mk-ui>
|
||||
</template>
|
||||
|
@ -14,42 +14,27 @@ import i18n from '../../../i18n';
|
|||
import Progress from '../../../common/scripts/loading';
|
||||
import { genSearchQuery } from '../../../common/scripts/gen-search-query';
|
||||
|
||||
const limit = 20;
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/pages/search.vue'),
|
||||
data() {
|
||||
return {
|
||||
makePromise: async cursor => this.$root.api('notes/search', {
|
||||
limit: limit + 1,
|
||||
untilId: cursor ? cursor : undefined,
|
||||
...(await genSearchQuery(this, this.q))
|
||||
}).then(notes => {
|
||||
if (notes.length == limit + 1) {
|
||||
notes.pop();
|
||||
return {
|
||||
notes: notes,
|
||||
more: true
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
notes: notes,
|
||||
more: false
|
||||
};
|
||||
pagination: {
|
||||
endpoint: 'notes/search',
|
||||
limit: 20,
|
||||
params: () => genSearchQuery(this, this.q)
|
||||
}
|
||||
})
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.$refs.timeline.reload();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
q(): string {
|
||||
return this.$route.query.q;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.$refs.timeline.reload();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
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>
|
||||
|
||||
<main>
|
||||
<mk-notes ref="timeline" :make-promise="makePromise" @inited="inited"/>
|
||||
<mk-notes ref="timeline" :pagination="pagination" @inited="inited"/>
|
||||
</main>
|
||||
</mk-ui>
|
||||
</template>
|
||||
|
@ -13,30 +13,17 @@ import Vue from 'vue';
|
|||
import i18n from '../../../i18n';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
|
||||
const limit = 20;
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/pages/tag.vue'),
|
||||
data() {
|
||||
return {
|
||||
makePromise: cursor => this.$root.api('notes/search-by-tag', {
|
||||
limit: limit + 1,
|
||||
untilId: cursor ? cursor : undefined,
|
||||
pagination: {
|
||||
endpoint: 'notes/search-by-tag',
|
||||
limit: 20,
|
||||
params: {
|
||||
tag: this.$route.params.tag
|
||||
}).then(notes => {
|
||||
if (notes.length == limit + 1) {
|
||||
notes.pop();
|
||||
return {
|
||||
notes: notes,
|
||||
more: true
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
notes: notes,
|
||||
more: false
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
<x-activity :user="user"/>
|
||||
</div>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
|
|||
import { NoteFavorite } from '../entities/note-favorite';
|
||||
import { Notes } from '..';
|
||||
import { ensure } from '../../prelude/ensure';
|
||||
import { types, bool } from '../../misc/schema';
|
||||
|
||||
@EntityRepository(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)));
|
||||
}
|
||||
}
|
||||
|
||||
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 { NoteFavorites } from '../../../../models';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { types, bool } from '../../../../misc/schema';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
|
@ -29,7 +30,17 @@ export const meta = {
|
|||
untilId: {
|
||||
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) => {
|
||||
|
|
|
@ -4,7 +4,7 @@ import define from '../../define';
|
|||
import { maximum } from '../../../../prelude/array';
|
||||
import { ApiError } from '../../error';
|
||||
import { getUser } from '../../common/getters';
|
||||
import { Not, In } from 'typeorm';
|
||||
import { Not, In, IsNull } from 'typeorm';
|
||||
import { Notes, Users } from '../../../../models';
|
||||
import { types, bool } from '../../../../misc/schema';
|
||||
|
||||
|
@ -58,7 +58,7 @@ export default define(meta, async (ps, me) => {
|
|||
const recentNotes = await Notes.find({
|
||||
where: {
|
||||
userId: user.id,
|
||||
replyId: Not(null)
|
||||
replyId: Not(IsNull())
|
||||
},
|
||||
order: {
|
||||
id: -1
|
||||
|
|
|
@ -14,6 +14,7 @@ import { packedNoteReactionSchema } from '../../../models/repositories/note-reac
|
|||
import { packedHashtagSchema } from '../../../models/repositories/hashtag';
|
||||
import { packedPageSchema } from '../../../models/repositories/page';
|
||||
import { packedUserGroupSchema } from '../../../models/repositories/user-group';
|
||||
import { packedNoteFavoriteSchema } from '../../../models/repositories/note-favorite';
|
||||
|
||||
export function convertSchemaToOpenApiSchema(schema: Schema) {
|
||||
const res: any = schema;
|
||||
|
@ -71,13 +72,14 @@ export const schemas = {
|
|||
App: convertSchemaToOpenApiSchema(packedAppSchema),
|
||||
MessagingMessage: convertSchemaToOpenApiSchema(packedMessagingMessageSchema),
|
||||
Note: convertSchemaToOpenApiSchema(packedNoteSchema),
|
||||
NoteReaction: convertSchemaToOpenApiSchema(packedNoteReactionSchema),
|
||||
NoteFavorite: convertSchemaToOpenApiSchema(packedNoteFavoriteSchema),
|
||||
Notification: convertSchemaToOpenApiSchema(packedNotificationSchema),
|
||||
DriveFile: convertSchemaToOpenApiSchema(packedDriveFileSchema),
|
||||
DriveFolder: convertSchemaToOpenApiSchema(packedDriveFolderSchema),
|
||||
Following: convertSchemaToOpenApiSchema(packedFollowingSchema),
|
||||
Muting: convertSchemaToOpenApiSchema(packedMutingSchema),
|
||||
Blocking: convertSchemaToOpenApiSchema(packedBlockingSchema),
|
||||
NoteReaction: convertSchemaToOpenApiSchema(packedNoteReactionSchema),
|
||||
Hashtag: convertSchemaToOpenApiSchema(packedHashtagSchema),
|
||||
Page: convertSchemaToOpenApiSchema(packedPageSchema),
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ import autobind from 'autobind-decorator';
|
|||
import Chart, { Obj, DeepPartial } from '../../core';
|
||||
import { SchemaType } from '../../../../misc/schema';
|
||||
import { DriveFiles } from '../../../../models';
|
||||
import { Not } from 'typeorm';
|
||||
import { Not, IsNull } from 'typeorm';
|
||||
import { DriveFile } from '../../../../models/entities/drive-file';
|
||||
import { name, schema } from '../schemas/drive';
|
||||
|
||||
|
@ -31,7 +31,7 @@ export default class DriveChart extends Chart<DriveLog> {
|
|||
protected async fetchActual(): Promise<DeepPartial<DriveLog>> {
|
||||
const [localCount, remoteCount, localSize, remoteSize] = await Promise.all([
|
||||
DriveFiles.count({ userHost: null }),
|
||||
DriveFiles.count({ userHost: Not(null) }),
|
||||
DriveFiles.count({ userHost: Not(IsNull()) }),
|
||||
DriveFiles.clacDriveUsageOfLocal(),
|
||||
DriveFiles.clacDriveUsageOfRemote()
|
||||
]);
|
||||
|
|
|
@ -2,7 +2,7 @@ import autobind from 'autobind-decorator';
|
|||
import Chart, { Obj, DeepPartial } from '../../core';
|
||||
import { SchemaType } from '../../../../misc/schema';
|
||||
import { Notes } from '../../../../models';
|
||||
import { Not } from 'typeorm';
|
||||
import { Not, IsNull } from 'typeorm';
|
||||
import { Note } from '../../../../models/entities/note';
|
||||
import { name, schema } from '../schemas/notes';
|
||||
|
||||
|
@ -29,7 +29,7 @@ export default class NotesChart extends Chart<NotesLog> {
|
|||
protected async fetchActual(): Promise<DeepPartial<NotesLog>> {
|
||||
const [localCount, remoteCount] = await Promise.all([
|
||||
Notes.count({ userHost: null }),
|
||||
Notes.count({ userHost: Not(null) })
|
||||
Notes.count({ userHost: Not(IsNull()) })
|
||||
]);
|
||||
|
||||
return {
|
||||
|
|
|
@ -2,7 +2,7 @@ import autobind from 'autobind-decorator';
|
|||
import Chart, { Obj, DeepPartial } from '../../core';
|
||||
import { SchemaType } from '../../../../misc/schema';
|
||||
import { Followings, Users } from '../../../../models';
|
||||
import { Not } from 'typeorm';
|
||||
import { Not, IsNull } from 'typeorm';
|
||||
import { User } from '../../../../models/entities/user';
|
||||
import { name, schema } from '../schemas/per-user-following';
|
||||
|
||||
|
@ -45,8 +45,8 @@ export default class PerUserFollowingChart extends Chart<PerUserFollowingLog> {
|
|||
] = await Promise.all([
|
||||
Followings.count({ followerId: group, followeeHost: null }),
|
||||
Followings.count({ followeeId: group, followerHost: null }),
|
||||
Followings.count({ followerId: group, followeeHost: Not(null) }),
|
||||
Followings.count({ followeeId: group, followerHost: Not(null) })
|
||||
Followings.count({ followerId: group, followeeHost: Not(IsNull()) }),
|
||||
Followings.count({ followeeId: group, followerHost: Not(IsNull()) })
|
||||
]);
|
||||
|
||||
return {
|
||||
|
|
|
@ -2,7 +2,7 @@ import autobind from 'autobind-decorator';
|
|||
import Chart, { Obj, DeepPartial } from '../../core';
|
||||
import { SchemaType } from '../../../../misc/schema';
|
||||
import { Users } from '../../../../models';
|
||||
import { Not } from 'typeorm';
|
||||
import { Not, IsNull } from 'typeorm';
|
||||
import { User } from '../../../../models/entities/user';
|
||||
import { name, schema } from '../schemas/users';
|
||||
|
||||
|
@ -29,7 +29,7 @@ export default class UsersChart extends Chart<UsersLog> {
|
|||
protected async fetchActual(): Promise<DeepPartial<UsersLog>> {
|
||||
const [localCount, remoteCount] = await Promise.all([
|
||||
Users.count({ host: null }),
|
||||
Users.count({ host: Not(null) })
|
||||
Users.count({ host: Not(IsNull()) })
|
||||
]);
|
||||
|
||||
return {
|
||||
|
|
|
@ -8,7 +8,6 @@ import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
|
|||
import { User } from '../../models/entities/user';
|
||||
import { Note } from '../../models/entities/note';
|
||||
import { Notes, Users, Followings, Instances } from '../../models';
|
||||
import { Not } from 'typeorm';
|
||||
import { notesChart, perUserNotesChart, instanceChart } from '../chart';
|
||||
|
||||
/**
|
||||
|
@ -38,13 +37,21 @@ export default async function(user: User, note: Note, quiet = false) {
|
|||
if (Users.isLocalUser(user)) {
|
||||
const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${note.id}`), user));
|
||||
|
||||
const followings = await Followings.find({
|
||||
followeeId: user.id,
|
||||
followerHost: Not(null)
|
||||
const queue: string[] = [];
|
||||
|
||||
const followers = await Followings.find({
|
||||
followeeId: note.userId
|
||||
});
|
||||
|
||||
for (const following of followings) {
|
||||
deliver(user, content, following.followerInbox);
|
||||
for (const following of followers) {
|
||||
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
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import * as promiseLimit from 'promise-limit';
|
||||
import del from '../services/drive/delete-file';
|
||||
import { DriveFiles } from '../models';
|
||||
import { Not } from 'typeorm';
|
||||
import { Not, IsNull } from 'typeorm';
|
||||
import { DriveFile } from '../models/entities/drive-file';
|
||||
import { ensure } from '../prelude/ensure';
|
||||
|
||||
const limit = promiseLimit(16);
|
||||
|
||||
DriveFiles.find({
|
||||
userHost: Not(null)
|
||||
userHost: Not(IsNull())
|
||||
}).then(async files => {
|
||||
console.log(`there is ${files.length} files`);
|
||||
|
||||
|
|
Loading…
Reference in a new issue