Merge branch 'develop'

This commit is contained in:
syuilo 2019-05-25 09:22:25 +09:00
commit fad84203c0
No known key found for this signature in database
GPG key ID: BDC4C49D06AB9D69
64 changed files with 833 additions and 348 deletions

View file

@ -62,6 +62,10 @@ db:
user: example-misskey-user user: example-misskey-user
pass: example-misskey-pass pass: example-misskey-pass
# Extra Connection options
#extra:
# ssl: true
# ┌─────────────────────┐ # ┌─────────────────────┐
#───┘ Redis configuration └───────────────────────────────────── #───┘ Redis configuration └─────────────────────────────────────

3
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,3 @@
# These are supported funding model platforms
patreon: syuilo

View file

@ -54,6 +54,27 @@ mongodb:
8. master ブランチに戻す 8. master ブランチに戻す
9. enjoy 9. enjoy
11.19.0 (2019/05/25)
--------------------
### ✨Improvements
* グループのオーナーを変えられるように
* PagesでURLプレビューを表示するように
* サーバーログを削除できるように
* リンク先が同じインスタンスの投稿内のURLをクリックしたときは新しいタブで開かないように
* PostgreSQLの接続オプションを設定ファイルで設定できるように
* いくつかのAPIでのパフォーマンスを向上
* ユーザビリティの向上
* デザインの調整
### 🐛Fixes
* デッキでユーザーページを遷移したときに前のユーザーのタイムラインが残り続ける問題を修正
* デッキでフォロー申請が見れない問題を修正
* グループ情報が更新できない問題を修正
* Firefoxでトークの下が隠れてしまう問題を修正
* トーク履歴がないと履歴が読み込み中のままになる問題を修正
* 通知で長い名前のユーザーがいると横スクロールが発生する問題を修正
* iOSでの選択ダイアログの動作を修正
11.18.1 (2019/05/21) 11.18.1 (2019/05/21)
-------------------- --------------------
### 🐛Fixes ### 🐛Fixes

View file

@ -7,7 +7,11 @@ Before creating a new issue, please search existing issues to avoid duplication.
If you find the existing issue, please add your reaction or comment to the issue. If you find the existing issue, please add your reaction or comment to the issue.
## Localization (l10n) ## Localization (l10n)
Please use [Crowdin](https://crowdin.com/project/misskey) for localization. Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management.
You can improve our translations with your Crowdin account.
Changes you make in Crowdin will be merged into develop branch.
If you can't find the language you want to contribute with, please open an issue.
![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg) ![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)

View file

@ -10,26 +10,35 @@ common:
about: "Misskey ist eine Quelloffene, <b>dezentralisierte microblogging Software</b>. Es bietet eine erweiterbare Benutzeroberfläche, verschiedenste Möglichkeiten auf Beiträge zu reagieren, kostenlosen Datenspeicher, und andere fortschrittliche Funktionen. Zusätzlich ist Misskey dazu in der Lage, sich mittels des Fediverse mit beliebig vielen anderen ActivityPub-kompatiblen Diensten zu verbinden. Wenn du zum Beispiel einen Betrag mit Misskey abschickst, wird dieser auch für Nutzer von Mastodon oder Pleroma sichtbar sein. So ähnlich wie eine Radioübertragung zwischen Planeten." about: "Misskey ist eine Quelloffene, <b>dezentralisierte microblogging Software</b>. Es bietet eine erweiterbare Benutzeroberfläche, verschiedenste Möglichkeiten auf Beiträge zu reagieren, kostenlosen Datenspeicher, und andere fortschrittliche Funktionen. Zusätzlich ist Misskey dazu in der Lage, sich mittels des Fediverse mit beliebig vielen anderen ActivityPub-kompatiblen Diensten zu verbinden. Wenn du zum Beispiel einen Betrag mit Misskey abschickst, wird dieser auch für Nutzer von Mastodon oder Pleroma sichtbar sein. So ähnlich wie eine Radioübertragung zwischen Planeten."
features: "Funktionen" features: "Funktionen"
rich-contents: "Notizen" rich-contents: "Notizen"
rich-contents-desc: "Poste einfach deine Ideen, Interessen und alles, was du teilen möchtest. Gestalte deine Nachrichten, teile deine Lieblingsbilder, sende Dateien und Videos und erstelle Umfragen das und mehr kann Misskey!"
reaction: "Reaktionen" reaction: "Reaktionen"
reaction-desc: "Der einfachste Weg, deine Gefühle mit anderen zu teilen. Mit Misskey kannst du auf verschiedenste Arten auf Beiträge reagieren, statt nur zu „liken“."
ui: "Benutzeroberfläche" ui: "Benutzeroberfläche"
ui-desc: "Geschmäcker sind verschieden. Deswegen ist Misskeys Oberfläche hochanpassbar und modular. Mache die Startseite zu deiner Startseite, indem du das Layout deiner Timeline änderst und mit Widgets staffierst."
drive: "Drive" drive: "Drive"
drive-desc: "Du willst ein hochgeladenes Foto nochmal posten? Deine Dateien benennen und in Ordnern sortieren? Misskeys Drive ist der beste Ort dafür. Damit wird das Teilen zum Kinderspiel!"
outro: "Probiere Misskey aus und entdecke Misskeys einzigartige Funktionen. Wenn dir diese Instanz nicht zusagt, nimm einfach eine andere. Dank Misskeys dezentralem System kannst du dich überall mit deinen Freunden verbinden. Also dann, GLHF!"
adblock: adblock:
detected: "Bitte deaktiviere den Werbeblocker." detected: "Bitte deaktiviere den Werbeblocker."
warning: "<strong>Misskey hat keine Werbung.</strong> Allerdings können Werbeblocker für unerwartete Probleme und fehlende Funktionen sorgen."
application-authorization: "Autorisierte Anwendungen" application-authorization: "Autorisierte Anwendungen"
close: "Schließen" close: "Schließen"
do-not-copy-paste: "Bitte keinen Code einfügen. Ihr Account könnte gefährdet werden."
load-more: "Mehr laden" load-more: "Mehr laden"
enter-password: "Bitte Passwort eingeben" enter-password: "Bitte Passwort eingeben"
2fa: "Zwei-Faktor-Authentifizierung" 2fa: "Zwei-Faktor-Authentifizierung"
customize-home: "Layout Anpassen" customize-home: "Layout Anpassen"
featured-notes: "Hervorgehobene Beiträge" featured-notes: "Beliebt"
dark-mode: "Dunkler Modus" dark-mode: "Dunkler Modus"
signin: "Einloggen" signin: "Einloggen"
signup: "Registrieren" signup: "Registrieren"
signout: "Ausloggen" signout: "Ausloggen"
reload-to-apply-the-setting: "Die Seite muss zum Übernehmen dieser Einstellung aktualisiert werden. Soll die Seite jetzt neu geladen werden?" reload-to-apply-the-setting: "Die Seite muss zum Übernehmen dieser Einstellung aktualisiert werden. Soll die Seite jetzt neu geladen werden?"
fetching-as-ap-object: "Hole Daten…"
got-it: "Verstanden!" got-it: "Verstanden!"
customization-tips: customization-tips:
title: "Anpassung-Tipps" title: "Anpassung-Tipps"
paragraph: "<p>Du kannst deine Startseite anpassen, indem du Widgets hinzufügst und verschiebst.</p><p><strong>Klicke <strong>rechts</strong></strong> auf ein Widget, um dessen Aussehen zu verändern.</p><p>Um ein Widget zu löschen, klicke und ziehe es auf den <strong>Papierkorb</strong> am Kopfende der Seite.</p><p>Wenn du fertig bist, drücke auf den Beenden-Knopf oben rechts.</p>"
gotit: "Verstanden!" gotit: "Verstanden!"
notification: notification:
file-uploaded: "Datei hochgeladen!" file-uploaded: "Datei hochgeladen!"
@ -56,21 +65,32 @@ common:
pages: "Seite" pages: "Seite"
messaging: "Unterhaltungen" messaging: "Unterhaltungen"
home: "Home" home: "Home"
deck: "Stapel" deck: "Deck"
timeline: "Zeitleiste" timeline: "Zeitleiste"
explore: "Entdecken" explore: "Entdecken"
following: "Folgt" following: "Folgt"
followers: "Folgende" followers: "Folgende"
favorites: "Diesen Beitrag favorisieren" favorites: "Favoriten"
permissions: permissions:
"read:account": "Accountinformationen anzeigen." "read:account": "Accountinformationen anzeigen."
"write:account": "Accountinformationen bearbeiten." "write:account": "Accountinformationen bearbeiten."
"read:blocks": "Blöcke anzeigen" "read:blocks": "Blöcke anzeigen"
"write:blocks": "Auf Sperrungen zugreifen"
"read:drive": "Dateien anzeigen" "read:drive": "Dateien anzeigen"
"write:drive": "Dateien bearbeiten" "write:drive": "Dateien bearbeiten"
"read:favorites": "Favoriten anzeigen" "read:favorites": "Favoriten anzeigen"
"write:favorites": "Auf Favoriten zugreifen"
"read:following": "Follower-Daten lesen"
"write:following": "Folgestatus bearbeiten"
"read:messaging": "Unterhaltung anzeigen" "read:messaging": "Unterhaltung anzeigen"
"write:messaging": "Unterhaltung bearbeiten" "write:messaging": "Unterhaltung bearbeiten"
"read:mutes": "Stummschaltungen lesen"
"write:mutes": "Stummschaltungen bearbeiten"
"write:notes": "Beiträge löschen und verfassen"
"read:notifications": "Benachrichtigungen lesen"
"write:notifications": "Benachrichtigungen bearbeiten"
"read:reactions": "Reaktionen sehen"
"write:reactions": "Reaktionen hinzufügen und bearbeiten"
"write:votes": "Abstimmen" "write:votes": "Abstimmen"
empty-timeline-info: empty-timeline-info:
follow-users-to-make-your-timeline: "Beiträge von Benutzern, denen du folgst, werden in der Zeitleiste angezeigt." follow-users-to-make-your-timeline: "Beiträge von Benutzern, denen du folgst, werden in der Zeitleiste angezeigt."
@ -147,22 +167,116 @@ common:
show-reversi-board-labels: "Zeige Reihen- und Spaltenbeschreibungen in Reversi an" show-reversi-board-labels: "Zeige Reihen- und Spaltenbeschreibungen in Reversi an"
use-avatar-reversi-stones: "Avatar als Stein in Reversi anzeigen" use-avatar-reversi-stones: "Avatar als Stein in Reversi anzeigen"
disable-animated-mfm: "Animierten Text in Beiträgen deaktivieren" disable-animated-mfm: "Animierten Text in Beiträgen deaktivieren"
disable-showing-animated-images: "Animierte Grafiken deaktivieren"
suggest-recent-hashtags: "Beim Verfassen von Beiträgen letzte Hashtags anzeigen"
always-show-nsfw: "Sensible Inhalte (NSFW) immer anzeigen"
always-mark-nsfw: "Meine Anhänge immer als NSFW markieren"
show-full-acct: "Servername bei Benutzernamen immer anzeigen"
show-via: "„via“ anzeigen"
reduce-motion: "Animationen der Benutzeroberfläche reduzieren"
this-setting-is-this-device-only: "Nur auf diesem Gerät"
use-os-default-emojis: "Betriebssystem-Emojis nutzen"
line-width: "Linienstärke"
line-width-thin: "Dünn"
line-width-normal: "Normal"
line-width-thick: "Dick"
font-size: "Schriftgröße"
font-size-x-small: "Sehr klein"
font-size-small: "Klein"
font-size-medium: "Normal"
font-size-large: "Groß"
font-size-x-large: "Sehr groß"
deck-column-align: "Spaltenaufteilung der Deck-Ansicht"
deck-column-align-center: "Mitte"
deck-column-align-left: "Links"
deck-column-align-flexible: "Flexibel"
deck-column-width: "Spaltenbreite des Decks"
deck-column-width-narrow: "Sehr eng"
deck-column-width-narrower: "Eng"
deck-column-width-normal: "Normal"
deck-column-width-wider: "Breit"
deck-column-width-wide: "Sehr breit"
use-shadow: "Nutze Schatten"
rounded-corners: "Abgerundete Ecken"
circle-icons: "Kreisförmige Icons"
contrasted-acct: "Nutzernamen kontrastreicher darstellen"
wallpaper: "Hintergrund"
choose-wallpaper: "Hintergrund auswählen"
delete-wallpaper: "Hintergrund entfernen"
post-form-on-timeline: "Beitragsformular über Timeline anzeigen"
show-clock-on-header: "Uhr am oberen rechten Rand anzeigen"
show-reply-target: "Zeige bei einer Antwort die ursprüngliche Nachricht"
timeline: "Timeline"
show-my-renotes: "Zeige eigene Renotes in der Timeline"
show-renoted-my-notes: "Zeige Renotes deiner Posts in der Timeline"
show-local-renotes: "Zeige Renotes lokaler Posts in der Timeline"
remain-deleted-note: "Gelöschte Beiträge weiterhin anzeigen"
sound: "Töne"
enable-sounds: "Töne aktivieren"
enable-sounds-desc: "Spiel einen Ton ab beim Erhalten eines Beitrags bzw. einer Nachricht. Diese Einstellung wird im Browser gespeichert."
volume: "Lautstärke"
test: "Test"
update: "Misskey-Update"
version: "Version:"
latest-version: "Neuste Version:"
update-checking: "Suche nach Updates"
do-update: "Nach Updates suchen"
update-settings: "Erweiterte Einstellungen"
no-updates: "Kein Update verfügbar"
no-updates-desc: "Misskey ist aktuell."
update-available: "Eine neue Version ist verfügbar!"
update-available-desc: "Änderungen werden beim Neuladen der Seite angewendet."
advanced-settings: "Erweiterte Einstellungen"
debug-mode: "Debug-Modus einschalten"
debug-mode-desc: "Diese Einstellung wird im Browser gespeichert."
navbar-position: "Postion der Navigationsleiste"
navbar-position-top: "Oben"
navbar-position-left: "Links"
navbar-position-right: "Rechts"
i-am-under-limited-internet: "Ich möchte Datenvolumen sparen"
post-style: "Beitrags-Anzeigestil"
post-style-standard: "Standard"
post-style-smart: "Smart"
notification-position: "Benachrichtigungen anzeigen"
notification-position-bottom: "Unten"
notification-position-top: "Oben"
disable-via-mobile: "Beitrag nicht als „vom Handy“ markieren"
load-raw-images: "Anhänge in voller Größe laden"
load-remote-media: "Zeige Inhalte von fremden Servern"
search: "Suche" search: "Suche"
delete: "Löschen" delete: "Löschen"
loading: "Laden" loading: "Laden"
ok: "Okay"
cancel: "Abbrechen"
update-available-title: "Aktualisierung verfügbar" update-available-title: "Aktualisierung verfügbar"
update-available: "Eine neue Version von Misskey ist verfügbar ({newer}, aktuell ist {current}). Lade die Seite neu um die aktuelle Version zu laden" update-available: "Eine neue Version von Misskey ist verfügbar ({newer}, aktuell ist {current}). Lade die Seite neu um die aktuelle Version zu laden"
my-token-regenerated: "Dein Token wurde generiert. Du wirst jetzt abgemeldet." my-token-regenerated: "Dein Token wurde generiert. Du wirst jetzt abgemeldet."
hide-password: "Passwort verbergen"
show-password: "Passwort zeigen"
enter-username: "Kontonamen eingeben"
do-not-use-in-production: "Dies ist eine Entwicklungsversion. Nicht in einer Produktivumgebung verwenden." do-not-use-in-production: "Dies ist eine Entwicklungsversion. Nicht in einer Produktivumgebung verwenden."
user-suspended: "Dieser Nutzer wurde gesperrt."
is-remote-user: "Diese Nutzerinformationen können unvollständig sein."
is-remote-post: "Dies ist ein entfernter Post."
view-on-remote: "Vollständige Infos auf Ursprungsserver anzeigen"
renoted-by: "Renote von {user}"
no-notes: "Keine Beiträge"
turn-on-darkmode: "Dunkles Design"
turn-off-darkmode: "Helles Design"
error: error:
title: "Allgemeiner Fehler"
retry: "Erneut versuchen" retry: "Erneut versuchen"
reversi: reversi:
drawn: "Unentschieden" drawn: "Unentschieden"
my-turn: "Du bist am Zug" my-turn: "Du bist am Zug"
opponent-turn: "Dein Gegner ist an der Reihe" opponent-turn: "Dein Gegner ist an der Reihe"
turn-of: "{name}s Zug"
past-turn-of: "Zug von {name}"
won: "{name} hat gewonnen"
black: "Schwarz" black: "Schwarz"
white: "Weiß" white: "Weiß"
total: "Gesamt" total: "Gesamt"
this-turn: "{count}. Zug"
widgets: widgets:
analog-clock: "Analoge Uhr" analog-clock: "Analoge Uhr"
profile: "Profil" profile: "Profil"
@ -173,8 +287,10 @@ common:
memo: "Notizen" memo: "Notizen"
trends: "Trends" trends: "Trends"
photo-stream: "Bilder" photo-stream: "Bilder"
posts-monitor: "Beitrags-Aktivität"
slideshow: "Diashow" slideshow: "Diashow"
version: "Version" version: "Version"
broadcast: "Ankündigungen"
notifications: "Benachrichtigungen" notifications: "Benachrichtigungen"
users: "Empfohlene Benutzer" users: "Empfohlene Benutzer"
polls: "Umfrage" polls: "Umfrage"
@ -183,7 +299,9 @@ common:
nav: "Navigation" nav: "Navigation"
tips: "Tipps" tips: "Tipps"
hashtags: "Hashtags" hashtags: "Hashtags"
queue: "Warteschlange"
dev: "Fehler beim Erstellen der Applikation. Bitte versuche es erneut." dev: "Fehler beim Erstellen der Applikation. Bitte versuche es erneut."
ai-chan-kawaii: "Ai-chan kawaii!"
you: "Du" you: "Du"
auth/views/form.vue: auth/views/form.vue:
share-access: "Erlaubst Du <i>{name}</i> auf deinen Account zuzugreifen?" share-access: "Erlaubst Du <i>{name}</i> auf deinen Account zuzugreifen?"
@ -193,17 +311,37 @@ auth/views/form.vue:
auth/views/index.vue: auth/views/index.vue:
loading: "Lädt" loading: "Lädt"
denied: "Autorisierung der Anwendung wurde verweigert." denied: "Autorisierung der Anwendung wurde verweigert."
denied-paragraph: "Diese App kann nicht auf deinen Account zugreifen."
already-authorized: "Diese Anwendung ist bereits autorisiert." already-authorized: "Diese Anwendung ist bereits autorisiert."
allowed: "Autorisierung der Anwendung wurde erlaubt." allowed: "Autorisierung der Anwendung wurde erlaubt."
callback-url: "Zur App zurückkehren"
please-go-back: "Bitte gehe zurück zur Anwendung." please-go-back: "Bitte gehe zurück zur Anwendung."
error: "Sitzung ist nicht vorhanden." error: "Sitzung ist nicht vorhanden."
sign-in: "Bitte melde dich an." sign-in: "Bitte melde dich an."
common/views/pages/explore.vue:
pinned-users: "Vorgeschlagen"
popular-users: "Beliebt"
recently-updated-users: "Kürzlich aktiv"
recently-registered-users: "Neue Benutzer"
popular-tags: "Beliebte Tags"
federated: "Aus dem Fediverse"
explore: "{host} erkunden"
users-info: "Momentan sind {users} Nutzer hier registriert"
common/views/components/url-preview.vue:
enable-player: "Player öffnen"
disable-player: "Player schließen"
common/views/components/user-list.vue:
no-users: "Keine Benutzer"
common/views/components/games/reversi/reversi.vue: common/views/components/games/reversi/reversi.vue:
matching: matching:
waiting-for: "Warten auf {}" waiting-for: "Warten auf {}"
cancel: "Abbrechen" cancel: "Abbrechen"
common/views/components/games/reversi/reversi.game.vue: common/views/components/games/reversi/reversi.game.vue:
surrender: "Aufgeben" surrender: "Aufgeben"
surrendered: "durch Aufgabe"
is-llotheo: "Der niedrigere gewinnt (Llotheo)"
looped-map: "Spielbrettenden verbinden"
can-put-everywhere: "Setzen ist überall erlaubt"
common/views/components/games/reversi/reversi.index.vue: common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi" title: "Misskey Reversi"
sub-title: "Spiele Reversi mit deinen Freunden!" sub-title: "Spiele Reversi mit deinen Freunden!"
@ -221,7 +359,14 @@ common/views/components/games/reversi/reversi.room.vue:
black-or-white: "Schwarz/Weiß" black-or-white: "Schwarz/Weiß"
black-is: "Schwarz ist {}" black-is: "Schwarz ist {}"
rules: "Regeln" rules: "Regeln"
is-llotheo: "Der niedrigere gewinnt (Llotheo)"
looped-map: "Spielbrettenden verbinden"
can-put-everywhere: "Setzen ist überall erlaubt"
settings-of-the-bot: "Bot-Einstellungen"
this-game-is-started-soon: "Spiel beginnt gleich"
waiting-for-other: "Warte auf den Gegner" waiting-for-other: "Warte auf den Gegner"
waiting-for-me: "Warten, bis du bereit bist"
waiting-for-both: "Vorbereiten…"
cancel: "Abbrechen" cancel: "Abbrechen"
ready: "Bereit" ready: "Bereit"
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
@ -252,10 +397,13 @@ common/views/components/media-banner.vue:
sensitive: "Dieser Inhalt ist NSFW" sensitive: "Dieser Inhalt ist NSFW"
click-to-show: "Klicke zum den Inhalt anzusehen" click-to-show: "Klicke zum den Inhalt anzusehen"
common/views/components/theme.vue: common/views/components/theme.vue:
theme: "Design"
light-theme: "Thema" light-theme: "Thema"
dark-theme: "Thema während des Nachtmodus" dark-theme: "Thema während des Nachtmodus"
light-themes: "Helles Thema" light-themes: "Helles Thema"
dark-themes: "Dunkles Thema" dark-themes: "Dunkles Thema"
install-a-theme: "Design wird installiert"
theme-code: "Design-Quelltext"
install: "Anwenden" install: "Anwenden"
installed: "\"{}\" wurde installiert" installed: "\"{}\" wurde installiert"
create-a-theme: "Thema erstellen" create-a-theme: "Thema erstellen"
@ -266,20 +414,37 @@ common/views/components/theme.vue:
base-theme: "Basisthema" base-theme: "Basisthema"
base-theme-light: "Hell" base-theme-light: "Hell"
base-theme-dark: "Dunkel" base-theme-dark: "Dunkel"
find-more-theme: "Mehr Designs finden"
theme-name: "Name des Themas" theme-name: "Name des Themas"
preview-created-theme: "Vorschau" preview-created-theme: "Vorschau"
invalid-theme: "Thema ist ungültig" invalid-theme: "Thema ist ungültig"
already-installed: "Thema ist bereits installiert" already-installed: "Thema ist bereits installiert"
saved: "Gespeichert"
manage-themes: "Designs verwalten"
builtin-themes: "Standard-Designs"
my-themes: "Meine Designs"
installed-themes: "Installierte Designs"
select-theme: "Design wählen"
uninstall: "Deinstallieren"
uninstalled: "„{}“ wurde deinstalliert"
author: "Autor" author: "Autor"
desc: "Beschreibung" desc: "Beschreibung"
export: "Exportieren" export: "Exportieren"
import: "Importieren" import: "Importieren"
import-by-code: "oder Quelltext einfügen"
theme-name-required: "Design-Name ist erforderlich"
common/views/components/cw-button.vue: common/views/components/cw-button.vue:
hide: "Ausblenden"
show: "Mehr"
chars: "{count} Zeichen"
files: "{count} Dateien"
poll: "Umfrage" poll: "Umfrage"
common/views/components/messaging.vue: common/views/components/messaging.vue:
search-user: "Einen Nutzer suchen" search-user: "Einen Nutzer suchen"
you: "Du" you: "Du"
no-history: "Keine Chronik" no-history: "Keine Chronik"
user: "Benutzer"
group: "Gruppen"
common/views/components/messaging-room.vue: common/views/components/messaging-room.vue:
no-history: "Keine weitere Chronik vorhanden" no-history: "Keine weitere Chronik vorhanden"
new-message: "Neue Nachricht" new-message: "Neue Nachricht"
@ -301,12 +466,28 @@ common/views/components/nav.vue:
develop: "Entwickler" develop: "Entwickler"
feedback: "Feedback" feedback: "Feedback"
common/views/components/note-menu.vue: common/views/components/note-menu.vue:
mention: "Erwähnungen"
detail: "Details"
copy-content: "Inhalt kopieren"
copy-link: "Link kopieren"
favorite: "Diesen Beitrag favorisieren" favorite: "Diesen Beitrag favorisieren"
unfavorite: "Aus Favoriten entfernen" unfavorite: "Aus Favoriten entfernen"
watch: "Beobachten"
unwatch: "Nicht mehr beobachten"
pin: "An die Profilseite pinnen" pin: "An die Profilseite pinnen"
unpin: "Lösen"
delete: "Löschen" delete: "Löschen"
delete-confirm: "Diesen Beitrag löschen?" delete-confirm: "Diesen Beitrag löschen?"
remote: "Auf Quelle anzeigen" remote: "Auf Quelle anzeigen"
common/views/components/user-menu.vue:
mention: "Erwähnungen"
mute: "Stummschalten"
unmute: "Stummschaltung aufheben"
mute-confirm: "Bist du sicher, dass du diesen Nutzer stummschalten möchtest?"
unmute-confirm: "Stummschaltung für diesen Nutzer aufheben?"
block: "Sperren"
unblock: "Sperrung aufheben"
block-confirm: "Diesen Nutzer wirklich sperren?"
common/views/components/poll.vue: common/views/components/poll.vue:
vote-to: "Stimme für '{}'" vote-to: "Stimme für '{}'"
vote-count: "{} Stimmen" vote-count: "{} Stimmen"
@ -389,6 +570,8 @@ common/views/components/user-group-editor.vue:
common/views/components/user-lists.vue: common/views/components/user-lists.vue:
user-lists: "Listen" user-lists: "Listen"
common/views/components/user-groups.vue: common/views/components/user-groups.vue:
user-groups: "Gruppen"
owned-groups: "Meine Gruppen"
invites: "Einladen" invites: "Einladen"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "Laden" fetching: "Laden"
@ -399,6 +582,7 @@ common/views/widgets/photo-stream.vue:
title: "Fotostream" title: "Fotostream"
no-photos: "Keine Fotos" no-photos: "Keine Fotos"
common/views/widgets/posts-monitor.vue: common/views/widgets/posts-monitor.vue:
title: "Beitrags-Aktivität"
toggle: "Sicht umschalten" toggle: "Sicht umschalten"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "Serverinformationen" title: "Serverinformationen"
@ -486,7 +670,7 @@ desktop/views/components/followers.vue:
desktop/views/components/following.vue: desktop/views/components/following.vue:
empty: "Du folgst niemanden" empty: "Du folgst niemanden"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "Verbunden" done: "Beenden"
add-widget: "Widget hinzufügen:" add-widget: "Widget hinzufügen:"
add: "Hinzufügen" add: "Hinzufügen"
desktop/views/input-dialog.vue: desktop/views/input-dialog.vue:
@ -501,6 +685,7 @@ desktop/views/components/note-detail.vue:
desktop/views/components/note.vue: desktop/views/components/note.vue:
reply: "Antworten" reply: "Antworten"
renote: "Anmerken" renote: "Anmerken"
detail: "Details"
private: "Dieser Beitrag ist privat" private: "Dieser Beitrag ist privat"
deleted: "Dieser Beitrag wurde entfernt" deleted: "Dieser Beitrag wurde entfernt"
desktop/views/components/notes.vue: desktop/views/components/notes.vue:
@ -579,6 +764,7 @@ desktop/views/components/timeline.vue:
desktop/views/components/ui.header.account.vue: desktop/views/components/ui.header.account.vue:
profile: "Dein Profil" profile: "Dein Profil"
lists: "Listen" lists: "Listen"
groups: "Gruppen"
desktop/views/components/ui.header.nav.vue: desktop/views/components/ui.header.nav.vue:
game: "Spielen" game: "Spielen"
desktop/views/components/ui.header.notifications.vue: desktop/views/components/ui.header.notifications.vue:
@ -592,11 +778,13 @@ desktop/views/components/users-list.vue:
admin/views/dashboard.vue: admin/views/dashboard.vue:
drive: "Drive" drive: "Drive"
admin/views/abuse.vue: admin/views/abuse.vue:
details: "Details"
remove-report: "Löschen" remove-report: "Löschen"
admin/views/instance.vue: admin/views/instance.vue:
recaptcha-preview: "Vorschau" recaptcha-preview: "Vorschau"
invite: "Einladen" invite: "Einladen"
save: "Speichern" save: "Speichern"
saved: "Gespeichert"
admin/views/charts.vue: admin/views/charts.vue:
drive: "Drive" drive: "Drive"
admin/views/drive.vue: admin/views/drive.vue:
@ -616,6 +804,7 @@ admin/views/announcements.vue:
save: "Speichern" save: "Speichern"
remove: "Löschen" remove: "Löschen"
add: "Hinzufügen" add: "Hinzufügen"
saved: "Gespeichert"
admin/views/federation.vue: admin/views/federation.vue:
status: "Status" status: "Status"
desktop/views/pages/note.vue: desktop/views/pages/note.vue:
@ -674,6 +863,7 @@ mobile/views/components/ui.nav.vue:
notifications: "Benachrichtigungen" notifications: "Benachrichtigungen"
search: "Suchen" search: "Suchen"
user-lists: "Listen" user-lists: "Listen"
user-groups: "Gruppen"
game: "Spielen" game: "Spielen"
about: "Über" about: "Über"
mobile/views/pages/drive.vue: mobile/views/pages/drive.vue:

View file

@ -450,14 +450,22 @@ common/views/components/messaging.vue:
you: "You" you: "You"
no-history: "Without history" no-history: "Without history"
user: "User" user: "User"
group: "Group"
start-with-user: "Start chatting with a user"
start-with-group: "Start a group and chat"
select-group: "Select a group"
common/views/components/messaging-room.vue: common/views/components/messaging-room.vue:
not-talked-user: "You have not talked to this user yet"
not-talked-group: "There is no conversation in this group"
no-history: "There is no further history" no-history: "There is no further history"
new-message: "New message" new-message: "New message"
only-one-file-attached: "You can only attach one file to a message"
common/views/components/messaging-room.form.vue: common/views/components/messaging-room.form.vue:
input-message-here: "Enter message here" input-message-here: "Enter message here"
send: "Send" send: "Send"
attach-from-local: "Attach files from your device" attach-from-local: "Attach files from your device"
attach-from-drive: "Attach files from your Drive" attach-from-drive: "Attach files from your Drive"
only-one-file-attached: "You can only attach one file to a message"
common/views/components/messaging-room.message.vue: common/views/components/messaging-room.message.vue:
is-read: "Read" is-read: "Read"
deleted: "This message has been deleted" deleted: "This message has been deleted"
@ -542,6 +550,7 @@ common/views/components/poll-editor.vue:
day: "S" day: "S"
common/views/components/reaction-picker.vue: common/views/components/reaction-picker.vue:
choose-reaction: "Send a reaction" choose-reaction: "Send a reaction"
input-reaction-placeholder: "or input Emoji"
common/views/components/emoji-picker.vue: common/views/components/emoji-picker.vue:
custom-emoji: "Custom Emoji" custom-emoji: "Custom Emoji"
people: "People" people: "People"
@ -694,14 +703,28 @@ common/views/components/user-list-editor.vue:
deleted: "Deleted successfully" deleted: "Deleted successfully"
add-user: "Add a user" add-user: "Add a user"
common/views/components/user-group-editor.vue: common/views/components/user-group-editor.vue:
users: "Members"
rename: "Rename group"
delete: "Delete group"
transfer: "transfer group"
transfer-are-you-sure: "Are you sure you want to add @$2 to the group $1?"
remove-user: "Remove a user from this group"
deleted: "Deleted" deleted: "Deleted"
invite: "Invite" invite: "Invite"
invited: "The invitation was successfully sent"
common/views/components/user-lists.vue: common/views/components/user-lists.vue:
user-lists: "Lists" user-lists: "Lists"
create-list: "Create a list" create-list: "Create a list"
list-name: "List name" list-name: "List name"
common/views/components/user-groups.vue: common/views/components/user-groups.vue:
user-groups: "Group"
create-group: "Create a group"
group-name: "Group name"
owned-groups: "My groups"
joined-groups: "Membership in groups"
invites: "Invite" invites: "Invite"
accept-invite: "Join"
reject-invite: "Decline"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "Checking" fetching: "Checking"
no-broadcasts: "No announcements" no-broadcasts: "No announcements"
@ -765,6 +788,8 @@ common/views/pages/follow.vue:
follow-request: "Follow request" follow-request: "Follow request"
common/views/pages/follow-requests.vue: common/views/pages/follow-requests.vue:
received-follow-requests: "Follow requests" received-follow-requests: "Follow requests"
accept: "Accept"
reject: "Reject"
desktop: desktop:
banner-crop-title: "Crop the part that appears as a banner" banner-crop-title: "Crop the part that appears as a banner"
banner: "Banner" banner: "Banner"
@ -781,7 +806,7 @@ desktop/views/components/activity.chart.vue:
total: "Black ... Total" total: "Black ... Total"
notes: "Blue ... Notes" notes: "Blue ... Notes"
replies: "Red ... Replies" replies: "Red ... Replies"
renotes: "Green ... Repost" renotes: "Green ... Renotes"
desktop/views/components/activity.vue: desktop/views/components/activity.vue:
title: "Activity" title: "Activity"
toggle: "Toggle views" toggle: "Toggle views"
@ -878,7 +903,7 @@ desktop/views/components/note-detail.vue:
private: "Post is private" private: "Post is private"
deleted: "Post has been removed" deleted: "Post has been removed"
location: "Location" location: "Location"
renote: "Repost" renote: "Renote"
add-reaction: "Add a reaction" add-reaction: "Add a reaction"
undo-reaction: "Reverse reaction" undo-reaction: "Reverse reaction"
desktop/views/components/note.vue: desktop/views/components/note.vue:
@ -902,10 +927,10 @@ desktop/views/components/post-form.vue:
quote-placeholder: "Quote this Post..." quote-placeholder: "Quote this Post..."
submit: "Post" submit: "Post"
reply: "Reply" reply: "Reply"
renote: "Repost" renote: "Renote"
posted: "Posted!" posted: "Posted!"
replied: "Replied!" replied: "Replied!"
reposted: "Reposted!" reposted: "Renoted!"
note-failed: "Failed to post" note-failed: "Failed to post"
reply-failed: "Failed to reply" reply-failed: "Failed to reply"
renote-failed: "Failed to Renote" renote-failed: "Failed to Renote"
@ -933,13 +958,13 @@ desktop/views/components/progress-dialog.vue:
desktop/views/components/renote-form.vue: desktop/views/components/renote-form.vue:
quote: "Quote..." quote: "Quote..."
cancel: "Cancel" cancel: "Cancel"
renote: "Repost" renote: "Renote"
renote-home: "Renote (Home)" renote-home: "Renote (Home)"
reposting: "Renoting..." reposting: "Renoting..."
success: "Reposted!" success: "Renoted!"
failure: "Failed to Repost" failure: "Failed to Renote"
desktop/views/components/renote-form-window.vue: desktop/views/components/renote-form-window.vue:
title: "Do you want to Renote it?" title: "Do you want to renote it?"
desktop/views/pages/user-following-or-followers.vue: desktop/views/pages/user-following-or-followers.vue:
following: "{user}'s following" following: "{user}'s following"
followers: "{user}'s follower" followers: "{user}'s follower"
@ -1036,6 +1061,7 @@ desktop/views/components/ui.header.vue:
desktop/views/components/ui.header.account.vue: desktop/views/components/ui.header.account.vue:
profile: "Your profile" profile: "Your profile"
lists: "Lists" lists: "Lists"
groups: "Group"
follow-requests: "Follow requests" follow-requests: "Follow requests"
admin: "Admin" admin: "Admin"
desktop/views/components/ui.header.nav.vue: desktop/views/components/ui.header.nav.vue:
@ -1109,6 +1135,10 @@ admin/views/instance.vue:
drive-config: "Drive settings" drive-config: "Drive settings"
object-storage-base-url: "URL" object-storage-base-url: "URL"
object-storage-endpoint: "Endpoint" object-storage-endpoint: "Endpoint"
object-storage-port: "Port"
object-storage-access-key: "Access Key"
object-storage-secret-key: "Secret Key"
object-storage-use-ssl: "Use SSL"
cache-remote-files: "Cache remote files" cache-remote-files: "Cache remote files"
cache-remote-files-desc: "Without this parameter, all remote files are linked to their host server directly. This will be an effective solution to save your server storage, however make remote files invisible to users who set direct-link disabled, since no thumbnail will be generated, increase traffic. It is recommended that this parameter set enabled." cache-remote-files-desc: "Without this parameter, all remote files are linked to their host server directly. This will be an effective solution to save your server storage, however make remote files invisible to users who set direct-link disabled, since no thumbnail will be generated, increase traffic. It is recommended that this parameter set enabled."
local-drive-capacity-mb: "Volume of Drive per user" local-drive-capacity-mb: "Volume of Drive per user"
@ -1117,6 +1147,7 @@ admin/views/instance.vue:
recaptcha-config: "the reCAPTCHA settings" recaptcha-config: "the reCAPTCHA settings"
recaptcha-info: "reCAPTCHA token is required. Please get it on https://www.google.com/recaptcha/intro/" recaptcha-info: "reCAPTCHA token is required. Please get it on https://www.google.com/recaptcha/intro/"
enable-recaptcha: "enable reCAPTCHA" enable-recaptcha: "enable reCAPTCHA"
recaptcha-secret-key: "Secret Key"
recaptcha-preview: "Preview" recaptcha-preview: "Preview"
twitter-integration-config: "Settings of connecting to Twitter" twitter-integration-config: "Settings of connecting to Twitter"
twitter-integration-info: "The callback URL is set on {url}." twitter-integration-info: "The callback URL is set on {url}."
@ -1234,6 +1265,8 @@ admin/views/users.vue:
unsilence-confirm: "Are you certain that you want to stop silencing this user?" unsilence-confirm: "Are you certain that you want to stop silencing this user?"
update-remote-user: "Update information about remote user" update-remote-user: "Update information about remote user"
remote-user-updated: "The information regarding the remote user has been updated." remote-user-updated: "The information regarding the remote user has been updated."
delete-all-files: "Delete all files"
delete-all-files-confirm: "Are you sure that you want to delete all files?"
users: users:
title: "Users" title: "Users"
sort: sort:
@ -1309,6 +1342,7 @@ admin/views/federation.vue:
latest-request-received-at: "Last request received at" latest-request-received-at: "Last request received at"
remove-all-following: "Withold all followers" remove-all-following: "Withold all followers"
remove-all-following-info: "Unfollow all accounts from {host}. Please run this if the instance no longer exists." remove-all-following-info: "Unfollow all accounts from {host}. Please run this if the instance no longer exists."
delete-all-files: "Remove all files"
block: "Block" block: "Block"
marked-as-closed: "Marked as closed" marked-as-closed: "Marked as closed"
lookup: "Look up" lookup: "Look up"
@ -1512,6 +1546,7 @@ mobile/views/components/ui.nav.vue:
follow-requests: "Follow requests" follow-requests: "Follow requests"
search: "Search" search: "Search"
user-lists: "Lists" user-lists: "Lists"
user-groups: "Group"
widgets: "Widgets" widgets: "Widgets"
game: "Games" game: "Games"
admin: "Admin" admin: "Admin"
@ -1654,6 +1689,9 @@ pages:
view-source: "View Source" view-source: "View Source"
view-page: "View page" view-page: "View page"
like: "Like" like: "Like"
unlike: "Unlike"
liked-pages: "Favorite pages"
my-pages: "My pages"
inspector: "Inspector" inspector: "Inspector"
content: "Page block" content: "Page block"
variables: "Variables" variables: "Variables"

View file

@ -78,6 +78,7 @@ common:
"write:drive": "Écrire sur le Drive" "write:drive": "Écrire sur le Drive"
"read:favorites": "Afficher les favoris" "read:favorites": "Afficher les favoris"
"write:favorites": "Écrire des favoris" "write:favorites": "Écrire des favoris"
"read:messaging": "Lire les conversations"
"write:messaging": "Utiliser la messagerie" "write:messaging": "Utiliser la messagerie"
"write:notes": "Créer ou supprimer des publications" "write:notes": "Créer ou supprimer des publications"
"read:notifications": "Afficher les notifications" "read:notifications": "Afficher les notifications"
@ -438,14 +439,21 @@ common/views/components/messaging.vue:
you: "Vous" you: "Vous"
no-history: "Pas d'historique" no-history: "Pas d'historique"
user: "Utilisateur·rice·s" user: "Utilisateur·rice·s"
group: "Groupe"
start-with-user: "Initier une discussion avec un·e utilisateur·rice"
select-group: "Sélectionner un groupe"
common/views/components/messaging-room.vue: common/views/components/messaging-room.vue:
not-talked-user: "Vous n'avez pas encore discuté avec cet·te utilisateur·rice"
not-talked-group: "Il n y a aucune conversation dans ce groupe"
no-history: "Aucun historique" no-history: "Aucun historique"
new-message: "Nouveau message" new-message: "Nouveau message"
only-one-file-attached: "Vous ne pouvez joindre qu'un seul fichier au message"
common/views/components/messaging-room.form.vue: common/views/components/messaging-room.form.vue:
input-message-here: "Tapez ici votre message" input-message-here: "Tapez ici votre message"
send: "Envoyer" send: "Envoyer"
attach-from-local: "Joindre un fichier depuis votre ordinateur" attach-from-local: "Joindre un fichier depuis votre ordinateur"
attach-from-drive: "Joindre un fichier depuis votre Drive" attach-from-drive: "Joindre un fichier depuis votre Drive"
only-one-file-attached: "Vous ne pouvez joindre qu'un seul fichier au message"
common/views/components/messaging-room.message.vue: common/views/components/messaging-room.message.vue:
is-read: "Lu" is-read: "Lu"
deleted: "Ce message a été supprimé" deleted: "Ce message a été supprimé"
@ -679,13 +687,19 @@ common/views/components/user-group-editor.vue:
delete: "Supprimer le groupe" delete: "Supprimer le groupe"
deleted: "Supprimé" deleted: "Supprimé"
invite: "Inviter" invite: "Inviter"
invited: "Invitation envoyée avec succès"
common/views/components/user-lists.vue: common/views/components/user-lists.vue:
user-lists: "Listes" user-lists: "Listes"
create-list: "Créer une liste" create-list: "Créer une liste"
list-name: "Nom de la liste" list-name: "Nom de la liste"
common/views/components/user-groups.vue: common/views/components/user-groups.vue:
user-groups: "Groupe"
create-group: "Créer un groupe" create-group: "Créer un groupe"
group-name: "Nom du groupe"
owned-groups: "Mes groupes"
joined-groups: "Membre dans les groupes"
invites: "Inviter" invites: "Inviter"
accept-invite: "Participer"
reject-invite: "Refuser" reject-invite: "Refuser"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "Récupération" fetching: "Récupération"
@ -1051,7 +1065,7 @@ admin/views/index.vue:
instance: "Instance" instance: "Instance"
emoji: "Émoji" emoji: "Émoji"
moderators: "Modérateurs" moderators: "Modérateurs"
users: "Utilisateurs" users: "Utilisateur·rice·s"
federation: "Fédération" federation: "Fédération"
announcements: "Annonces" announcements: "Annonces"
abuse: "Abus" abuse: "Abus"
@ -1095,8 +1109,11 @@ admin/views/instance.vue:
drive-config: "Paramètres du lecteur" drive-config: "Paramètres du lecteur"
object-storage-base-url: "URL" object-storage-base-url: "URL"
object-storage-endpoint: "Point de terminaison" object-storage-endpoint: "Point de terminaison"
object-storage-port: "Port"
object-storage-access-key: "Clé d'accès" object-storage-access-key: "Clé d'accès"
object-storage-secret-key: "Clé secrète" object-storage-secret-key: "Clé secrète"
object-storage-use-ssl: "Utiliser SSL"
object-storage-s3-info-here: "ici"
cache-remote-files: "Mettre en cache des fichiers distants" cache-remote-files: "Mettre en cache des fichiers distants"
local-drive-capacity-mb: "Volume du lecteur par utilisateur" local-drive-capacity-mb: "Volume du lecteur par utilisateur"
remote-drive-capacity-mb: "Volume du lecteur par utilisateur distant" remote-drive-capacity-mb: "Volume du lecteur par utilisateur distant"
@ -1106,6 +1123,7 @@ admin/views/instance.vue:
enable-recaptcha: "Activation de reCAPTCHA" enable-recaptcha: "Activation de reCAPTCHA"
recaptcha-secret-key: "Clé secrète" recaptcha-secret-key: "Clé secrète"
recaptcha-preview: "Prévisualisation" recaptcha-preview: "Prévisualisation"
hidden-tags: "Tags cachés"
twitter-integration-config: "Paramètres de connexion à Twitter" twitter-integration-config: "Paramètres de connexion à Twitter"
twitter-integration-info: "L'URL de callback est {url}." twitter-integration-info: "L'URL de callback est {url}."
enable-twitter-integration: "Activer la connexion à Twitter" enable-twitter-integration: "Activer la connexion à Twitter"
@ -1218,7 +1236,7 @@ admin/views/users.vue:
update-remote-user: "Mettre à jour les informations de lutilisateur·rice distant·e" update-remote-user: "Mettre à jour les informations de lutilisateur·rice distant·e"
remote-user-updated: "Les informations de lutilisateur·rice distant·e ont étés mis à jour" remote-user-updated: "Les informations de lutilisateur·rice distant·e ont étés mis à jour"
users: users:
title: "Utilisateurs" title: "Utilisateur·rice·s"
sort: sort:
title: "Trier par" title: "Trier par"
createdAtAsc: "Date dinscription (Ascendant)" createdAtAsc: "Date dinscription (Ascendant)"
@ -1433,7 +1451,7 @@ mobile/views/components/media-video.vue:
sensitive: "Le contenu est NSFW" sensitive: "Le contenu est NSFW"
click-to-show: "Cliquer pour afficher" click-to-show: "Cliquer pour afficher"
common/views/components/follow-button.vue: common/views/components/follow-button.vue:
following: "Abonné" following: "Abonné·e"
follow: " Suivre" follow: " Suivre"
request-pending: "Demande en attente" request-pending: "Demande en attente"
follow-processing: "En cours dabonnement" follow-processing: "En cours dabonnement"
@ -1483,6 +1501,7 @@ mobile/views/components/ui.nav.vue:
follow-requests: "Demandes dabonnement" follow-requests: "Demandes dabonnement"
search: "Rechercher" search: "Rechercher"
user-lists: "Listes" user-lists: "Listes"
user-groups: "Groupe"
widgets: "Modules" widgets: "Modules"
game: "Jeux" game: "Jeux"
admin: "Admin" admin: "Admin"
@ -1498,9 +1517,9 @@ mobile/views/pages/drive.vue:
mobile/views/pages/signup.vue: mobile/views/pages/signup.vue:
lets-start: "Votre compte est prêt ! 📦" lets-start: "Votre compte est prêt ! 📦"
mobile/views/pages/followers.vue: mobile/views/pages/followers.vue:
followers-of: "Abonnés de {name}" followers-of: "Abonné·e·s de {name}"
mobile/views/pages/following.vue: mobile/views/pages/following.vue:
following-of: "Abonnés de {name}" following-of: "Abonné·e·s de {name}"
mobile/views/pages/home.vue: mobile/views/pages/home.vue:
home: "Accueil" home: "Accueil"
local: "Local" local: "Local"
@ -1752,16 +1771,32 @@ pages:
_gtEq: _gtEq:
arg1: "A" arg1: "A"
arg2: "B" arg2: "B"
_if:
arg1: "Si"
arg2: "donc"
arg3: "sinon"
not: "négation"
_not:
arg1: "négation"
random: "Aléatoire" random: "Aléatoire"
_randomPick: _randomPick:
arg1: "Listes" arg1: "Listes"
_dailyRandom:
arg1: "Probabilité"
_dailyRannum:
arg1: "Minimum"
arg2: "Maximum"
_dailyRandomPick: _dailyRandomPick:
arg1: "Listes" arg1: "Listes"
_seedRandom:
arg2: "Probabilité"
_seedRannum: _seedRannum:
arg2: "Min" arg2: "Min"
arg3: "Max" arg3: "Max"
_seedRandomPick: _seedRandomPick:
arg2: "Listes" arg2: "Listes"
_DRPWPM:
arg1: "Liste de texte"
pick: "Sélectionner dans la liste" pick: "Sélectionner dans la liste"
_pick: _pick:
arg1: "Listes" arg1: "Listes"
@ -1781,9 +1816,12 @@ pages:
slots: "Emplacement" slots: "Emplacement"
arg1: "Sortie" arg1: "Sortie"
for: "Répéter" for: "Répéter"
thereIsEmptySlot: "Slot {slot} est vide !"
types: types:
string: "Texte" string: "Texte"
number: "Numérique" number: "Numérique"
array: "Listes" array: "Listes"
stringArray: "Liste de texte" stringArray: "Liste de texte"
emptySlot: "Slot vide"
enviromentVariables: "Variables d'environnement" enviromentVariables: "Variables d'environnement"
pageVariables: "Élément de page"

View file

@ -591,6 +591,7 @@ common/views/components/poll-editor.vue:
common/views/components/reaction-picker.vue: common/views/components/reaction-picker.vue:
choose-reaction: "リアクションを選択" choose-reaction: "リアクションを選択"
input-reaction-placeholder: "または絵文字を入力"
common/views/components/emoji-picker.vue: common/views/components/emoji-picker.vue:
custom-emoji: "カスタム絵文字" custom-emoji: "カスタム絵文字"
@ -762,6 +763,9 @@ common/views/components/user-group-editor.vue:
users: "メンバー" users: "メンバー"
rename: "グループ名を変更" rename: "グループ名を変更"
delete: "グループを削除" delete: "グループを削除"
transfer: "グループを譲渡"
transfer-are-you-sure: "グループ「$1」を「@$2」さんに譲渡しますか"
transferred: "グループを譲渡しました"
remove-user: "このグループから削除" remove-user: "このグループから削除"
delete-are-you-sure: "グループ「$1」を削除しますか" delete-are-you-sure: "グループ「$1」を削除しますか"
deleted: "削除しました" deleted: "削除しました"
@ -1278,6 +1282,7 @@ admin/views/instance.vue:
mb: "メガバイト単位" mb: "メガバイト単位"
recaptcha-config: "reCAPTCHAの設定" recaptcha-config: "reCAPTCHAの設定"
recaptcha-info: "reCAPTCHAを有効にする場合、reCAPTCHAトークンを取得する必要があります。https://www.google.com/recaptcha/intro/ にアクセスしてトークンを取得してください。" recaptcha-info: "reCAPTCHAを有効にする場合、reCAPTCHAトークンを取得する必要があります。https://www.google.com/recaptcha/intro/ にアクセスしてトークンを取得してください。"
recaptcha-info2: "v3は非対応です。v2を使用してください。"
enable-recaptcha: "reCAPTCHAを有効にする" enable-recaptcha: "reCAPTCHAを有効にする"
recaptcha-site-key: "サイトキー" recaptcha-site-key: "サイトキー"
recaptcha-secret-key: "シークレットキー" recaptcha-secret-key: "シークレットキー"

View file

@ -20,7 +20,7 @@ common:
outro: "이외에도 Misskey에만 있는 기능이 아직도 더 있으니 부디 여러분 자신의 눈으로 확인해보시기 바랍니다. Misskey는 분산형 SNS라서 이 인스턴스가 마음에 들지 않으신다면 다른 인스턴스를 시도해보실 수도 있습니다. 그럼, GLHF!" outro: "이외에도 Misskey에만 있는 기능이 아직도 더 있으니 부디 여러분 자신의 눈으로 확인해보시기 바랍니다. Misskey는 분산형 SNS라서 이 인스턴스가 마음에 들지 않으신다면 다른 인스턴스를 시도해보실 수도 있습니다. 그럼, GLHF!"
adblock: adblock:
detected: "광고 차단기를 해제하십시오" detected: "광고 차단기를 해제하십시오"
warning: "<strong>Misskey는 광고를 게재하지 않습니다</strong>. 그러나 광고 차단 기능을 사용할 경우 일부 기능을 사용할 수 없게 될 가능성이나 결함이 발생하는 경우가 있습니다." warning: "<strong>Misskey는 광고를 게재하지 않습니다</strong>. 하지만 광고 차단 기능을 사용할 경우 일부 기능을 사용할 수 없거나 문제가 발생할 수 있습니다."
application-authorization: "앱 연계" application-authorization: "앱 연계"
close: "닫기" close: "닫기"
do-not-copy-paste: "여기에 코드를 입력하거나 붙여넣지 마십시오. 계정이 무단으로 사용될 수 있습니다." do-not-copy-paste: "여기에 코드를 입력하거나 붙여넣지 마십시오. 계정이 무단으로 사용될 수 있습니다."
@ -42,7 +42,7 @@ common:
gotit: "Got it!" gotit: "Got it!"
notification: notification:
file-uploaded: "파일이 업로드되었습니다" file-uploaded: "파일이 업로드되었습니다"
message-from: "{}님으로부터 메시지:" message-from: "{}님 메시지:"
reversi-invited: "게임 초대가 있습니다" reversi-invited: "게임 초대가 있습니다"
reversi-invited-by: "{}님으로부터" reversi-invited-by: "{}님으로부터"
notified-by: "{}님으로부터" notified-by: "{}님으로부터"
@ -51,7 +51,7 @@ common:
time: time:
unknown: "알 수 없는 시간" unknown: "알 수 없는 시간"
future: "미래" future: "미래"
just_now: "방금" just_now: "방금"
seconds_ago: "{}초 전" seconds_ago: "{}초 전"
minutes_ago: "{}분 전" minutes_ago: "{}분 전"
hours_ago: "{}시간 전" hours_ago: "{}시간 전"
@ -67,19 +67,19 @@ common:
home: "홈" home: "홈"
deck: "덱" deck: "덱"
timeline: "타임라인" timeline: "타임라인"
explore: "발견" explore: "발견하기"
following: "팔로우 중" following: "팔로우 중"
followers: "팔로워" followers: "팔로워"
favorites: "즐겨찾기" favorites: "즐겨찾기"
permissions: permissions:
"read:account": "계정 정보 보기" "read:account": "계정의 정보를 볼 수 있습니다."
"write:account": "계정 정보 변경" "write:account": "계정 정보 변경할 수 있습니다."
"read:blocks": "차단 보기" "read:blocks": "차단 목록을 볼 수 있습니다."
"write:blocks": "차단 수정" "write:blocks": "차단 목록을 조작할 수 있습니다."
"read:drive": "드라이브 보기" "read:drive": "드라이브의 파일들을 볼 수 있습니다."
"write:drive": "드라이브 수정" "write:drive": "드라이브의 파일들을 조작할 수 있습니다."
"read:favorites": "즐겨찾기 보기" "read:favorites": "즐겨찾기 목록을 볼 수 있습니다."
"write:favorites": "즐겨찾기 수정" "write:favorites": "즐겨찾기 목록을 조작할 수 있습니다."
"read:following": "팔로우 정보 보기" "read:following": "팔로우 정보 보기"
"write:following": "팔로잉, 팔로우 수정" "write:following": "팔로잉, 팔로우 수정"
"read:messaging": "대화 보기" "read:messaging": "대화 보기"
@ -550,6 +550,7 @@ common/views/components/poll-editor.vue:
day: "일" day: "일"
common/views/components/reaction-picker.vue: common/views/components/reaction-picker.vue:
choose-reaction: "리액션 선택" choose-reaction: "리액션 선택"
input-reaction-placeholder: "또는 이모지 입력"
common/views/components/emoji-picker.vue: common/views/components/emoji-picker.vue:
custom-emoji: "커스텀 이모지" custom-emoji: "커스텀 이모지"
people: "사람들" people: "사람들"
@ -705,6 +706,9 @@ common/views/components/user-group-editor.vue:
users: "멤버" users: "멤버"
rename: "그룹명을 변경" rename: "그룹명을 변경"
delete: "그룹을 삭제" delete: "그룹을 삭제"
transfer: "그룹을 양도"
transfer-are-you-sure: "그룹 「$1」을 「@$2」 님에게 양도하시겠습니까?"
transferred: "그룹을 양도하였습니다"
remove-user: "이 그룹에서 삭제" remove-user: "이 그룹에서 삭제"
delete-are-you-sure: "그룹 「$1」을 삭제하시겠습니까?" delete-are-you-sure: "그룹 「$1」을 삭제하시겠습니까?"
deleted: "삭제하였습니다" deleted: "삭제하였습니다"
@ -1152,6 +1156,7 @@ admin/views/instance.vue:
mb: "메가바이트 단위" mb: "메가바이트 단위"
recaptcha-config: "reCAPCHA 설정" recaptcha-config: "reCAPCHA 설정"
recaptcha-info: "reCAPCHA를 사용하도록 설정하는 경우 reCAPCHA 토큰을 확보해야 합니다. https://www.google.com/recaptcha/intro/ 에 접속하여 토큰을 가져와주십시오." recaptcha-info: "reCAPCHA를 사용하도록 설정하는 경우 reCAPCHA 토큰을 확보해야 합니다. https://www.google.com/recaptcha/intro/ 에 접속하여 토큰을 가져와주십시오."
recaptcha-info2: "v3는지원하지 않습니다. v2를 사용하여 주십시오."
enable-recaptcha: "reCAPCHA 활성화" enable-recaptcha: "reCAPCHA 활성화"
recaptcha-site-key: "사이트 키" recaptcha-site-key: "사이트 키"
recaptcha-secret-key: "시크릿 키" recaptcha-secret-key: "시크릿 키"

View file

@ -453,6 +453,7 @@ common/views/components/messaging.vue:
group: "群组" group: "群组"
start-with-user: "开始用户聊天" start-with-user: "开始用户聊天"
start-with-group: "开始群组聊天" start-with-group: "开始群组聊天"
select-group: "请选择群组"
common/views/components/messaging-room.vue: common/views/components/messaging-room.vue:
not-talked-user: "没有用户的会话记录" not-talked-user: "没有用户的会话记录"
not-talked-group: "没有群组的会话记录" not-talked-group: "没有群组的会话记录"
@ -704,10 +705,14 @@ common/views/components/user-group-editor.vue:
users: "成员" users: "成员"
rename: "更改群组名" rename: "更改群组名"
delete: "删除群组" delete: "删除群组"
transfer: "群组转让"
transfer-are-you-sure: "将群组「$1」转让给「@$2」吗"
transferred: "群组已转让"
remove-user: "从本群组中删除" remove-user: "从本群组中删除"
delete-are-you-sure: "确定要删除「$1」组" delete-are-you-sure: "确定要删除「$1」组"
deleted: "已删除" deleted: "已删除"
invite: "邀请" invite: "邀请"
invited: "邀请已发送"
common/views/components/user-lists.vue: common/views/components/user-lists.vue:
user-lists: "列表" user-lists: "列表"
create-list: "创建列表" create-list: "创建列表"
@ -716,7 +721,10 @@ common/views/components/user-groups.vue:
user-groups: "群组" user-groups: "群组"
create-group: "创建群组" create-group: "创建群组"
group-name: "群组名" group-name: "群组名"
owned-groups: "我的群组"
joined-groups: "加入群组"
invites: "邀请" invites: "邀请"
accept-invite: "加入"
reject-invite: "拒绝" reject-invite: "拒绝"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "确认中" fetching: "确认中"
@ -1147,6 +1155,7 @@ admin/views/instance.vue:
mb: "以兆字节(Mbps)为单位" mb: "以兆字节(Mbps)为单位"
recaptcha-config: "reCAPTCHA设置" recaptcha-config: "reCAPTCHA设置"
recaptcha-info: "reCAPTCHA token是必要的. 请从 https://www.google.com/recaptcha/intro/ 获取。\n请注意, 该功能在中国大陆不可用。" recaptcha-info: "reCAPTCHA token是必要的. 请从 https://www.google.com/recaptcha/intro/ 获取。\n请注意, 该功能在中国大陆不可用。"
recaptcha-info2: "不支持v3。请使用v2。"
enable-recaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)" enable-recaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)"
recaptcha-site-key: "网站密钥" recaptcha-site-key: "网站密钥"
recaptcha-secret-key: "密钥" recaptcha-secret-key: "密钥"

View file

@ -3,21 +3,14 @@ import {MigrationInterface, QueryRunner} from "typeorm";
export class UserProfile1556746559567 implements MigrationInterface { export class UserProfile1556746559567 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> { public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`UPDATE "user_profile" SET github = FALSE`); await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "githubId" TYPE VARCHAR(64) USING "githubId"::VARCHAR(64)`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "githubId"`); await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "discordExpiresDate" TYPE VARCHAR(64) USING "discordExpiresDate"::VARCHAR(64)`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD COLUMN "githubId" VARCHAR(64)`);
await queryRunner.query(`UPDATE "user_profile" SET discord = FALSE`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "discordExpiresDate"`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD COLUMN "discordExpiresDate" VARCHAR(64)`);
} }
public async down(queryRunner: QueryRunner): Promise<any> { public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`UPDATE "user_profile" SET github = FALSE`); await queryRunner.query(`UPDATE "user_profile" SET github = FALSE, discord = FALSE`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "githubId"`); await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "githubId" TYPE INTEGER USING NULL`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD COLUMN "githubId" INTEGER`); await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "discordExpiresDate" TYPE INTEGER USING NULL`);
await queryRunner.query(`UPDATE "user_profile" SET discord = FALSE`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "discordExpiresDate"`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD COLUMN "discordExpiresDate" INTEGER`);
} }
} }

View file

@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "11.18.1", "version": "11.19.0",
"codename": "daybreak", "codename": "daybreak",
"repository": { "repository": {
"type": "git", "type": "git",
@ -253,7 +253,7 @@
"vuex": "3.1.1", "vuex": "3.1.1",
"vuex-persistedstate": "2.5.4", "vuex-persistedstate": "2.5.4",
"web-push": "3.3.5", "web-push": "3.3.5",
"webpack": "4.32.0", "webpack": "4.32.2",
"webpack-cli": "3.3.2", "webpack-cli": "3.3.2",
"websocket": "1.0.28", "websocket": "1.0.28",
"ws": "7.0.0", "ws": "7.0.0",

View file

@ -9,11 +9,11 @@
<ui-input v-model="iconUrl"><template #icon><fa icon="link"/></template>{{ $t('icon-url') }}</ui-input> <ui-input v-model="iconUrl"><template #icon><fa icon="link"/></template>{{ $t('icon-url') }}</ui-input>
<ui-input v-model="mascotImageUrl"><template #icon><fa icon="link"/></template>{{ $t('logo-url') }}</ui-input> <ui-input v-model="mascotImageUrl"><template #icon><fa icon="link"/></template>{{ $t('logo-url') }}</ui-input>
<ui-input v-model="bannerUrl"><template #icon><fa icon="link"/></template>{{ $t('banner-url') }}</ui-input> <ui-input v-model="bannerUrl"><template #icon><fa icon="link"/></template>{{ $t('banner-url') }}</ui-input>
<ui-input v-model="errorImageUrl"><template #icon><fa icon="link"/></template>{{ $t('error-image-url') }}</ui-input>
<ui-input v-model="ToSUrl"><template #icon><fa icon="link"/></template>{{ $t('tos-url') }}</ui-input> <ui-input v-model="ToSUrl"><template #icon><fa icon="link"/></template>{{ $t('tos-url') }}</ui-input>
<ui-input v-model="languages"><template #icon><fa icon="language"/></template>{{ $t('languages') }}<template #desc>{{ $t('languages-desc') }}</template></ui-input>
<details> <details>
<summary>{{ $t('advanced-config') }}</summary> <summary>{{ $t('advanced-config') }}</summary>
<ui-input v-model="errorImageUrl"><template #icon><fa icon="link"/></template>{{ $t('error-image-url') }}</ui-input>
<ui-input v-model="languages"><template #icon><fa icon="language"/></template>{{ $t('languages') }}<template #desc>{{ $t('languages-desc') }}</template></ui-input>
<ui-input v-model="repositoryUrl"><template #icon><fa icon="link"/></template>{{ $t('repository-url') }}</ui-input> <ui-input v-model="repositoryUrl"><template #icon><fa icon="link"/></template>{{ $t('repository-url') }}</ui-input>
<ui-input v-model="feedbackUrl"><template #icon><fa icon="link"/></template>{{ $t('feedback-url') }}</ui-input> <ui-input v-model="feedbackUrl"><template #icon><fa icon="link"/></template>{{ $t('feedback-url') }}</ui-input>
</details> </details>
@ -159,6 +159,7 @@
<ui-switch v-model="enableRecaptcha">{{ $t('enable-recaptcha') }}</ui-switch> <ui-switch v-model="enableRecaptcha">{{ $t('enable-recaptcha') }}</ui-switch>
<template v-if="enableRecaptcha"> <template v-if="enableRecaptcha">
<ui-info>{{ $t('recaptcha-info') }}</ui-info> <ui-info>{{ $t('recaptcha-info') }}</ui-info>
<ui-info warn>{{ $t('recaptcha-info2') }}</ui-info>
<ui-horizon-group inputs> <ui-horizon-group inputs>
<ui-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><template #icon><fa icon="key"/></template>{{ $t('recaptcha-site-key') }}</ui-input> <ui-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><template #icon><fa icon="key"/></template>{{ $t('recaptcha-site-key') }}</ui-input>
<ui-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><template #icon><fa icon="key"/></template>{{ $t('recaptcha-secret-key') }}</ui-input> <ui-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><template #icon><fa icon="key"/></template>{{ $t('recaptcha-secret-key') }}</ui-input>

View file

@ -26,6 +26,8 @@
</details> </details>
</code> </code>
</div> </div>
<ui-button @click="deleteAll()">{{ $t('delete-all') }}</ui-button>
</section> </section>
</ui-card> </ui-card>
</div> </div>
@ -78,6 +80,15 @@ export default Vue.extend({
}).then(logs => { }).then(logs => {
this.logs = logs.reverse(); this.logs = logs.reverse();
}); });
},
deleteAll() {
this.$root.api('admin/delete-logs').then(() => {
this.$root.dialog({
type: 'success',
splash: true
});
});
} }
} }
}); });

View file

@ -98,7 +98,7 @@ export default Vue.extend({
return { return {
inputValue: this.input && this.input.default ? this.input.default : null, inputValue: this.input && this.input.default ? this.input.default : null,
userInputValue: null, userInputValue: null,
selectedValue: null, selectedValue: this.select ? this.select.items ? this.select.items[0].value : this.select.groupedItems[0].items[0].value : null,
faTimesCircle, faQuestionCircle faTimesCircle, faQuestionCircle
}; };
}, },

View file

@ -300,17 +300,13 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
.mk-messaging-room .mk-messaging-room
display flex
flex 1
flex-direction column
height 100%
background var(--messagingRoomBg) background var(--messagingRoomBg)
> .body > .body
width 100% width 100%
max-width 600px max-width 600px
margin 0 auto margin 0 auto
flex 1 min-height calc(100% - 103px)
> .init, > .init,
> .empty > .empty

View file

@ -46,7 +46,7 @@
</div> </div>
</a> </a>
</div> </div>
<p class="no-history" v-if="!fetching && (messages.length == 0 && groupMessages.length == 0)">{{ $t('no-history') }}</p> <p class="no-history" v-if="!fetching && messages.length == 0">{{ $t('no-history') }}</p>
<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<ui-margin> <ui-margin>
<ui-button @click="startUser()"><fa :icon="faUser"/> {{ $t('start-with-user') }}</ui-button> <ui-button @click="startUser()"><fa :icon="faUser"/> {{ $t('start-with-user') }}</ui-button>

View file

@ -8,7 +8,7 @@ import { concat, sum } from '../../../../../prelude/array';
import MkFormula from './formula.vue'; import MkFormula from './formula.vue';
import MkCode from './code.vue'; import MkCode from './code.vue';
import MkGoogle from './google.vue'; import MkGoogle from './google.vue';
import { host } from '../../../config'; import { host, url } from '../../../config';
import { preorderF, countNodesF } from '../../../../../prelude/tree'; import { preorderF, countNodesF } from '../../../../../prelude/tree';
function sumTextsLength(ts: MfmForest): number { function sumTextsLength(ts: MfmForest): number {
@ -175,7 +175,9 @@ export default Vue.component('misskey-flavored-markdown', {
props: { props: {
url: token.node.props.url, url: token.node.props.url,
rel: 'nofollow noopener', rel: 'nofollow noopener',
...(token.node.props.url.startsWith(url) ? {} : {
target: '_blank' target: '_blank'
})
}, },
attrs: { attrs: {
style: 'color:var(--mfmUrl);' style: 'color:var(--mfmUrl);'

View file

@ -16,7 +16,7 @@
<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')" v-particle><mk-reaction-icon reaction="pudding"/></button> <button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')" v-particle><mk-reaction-icon reaction="pudding"/></button>
</div> </div>
<div v-if="enableEmojiReaction" class="text"> <div v-if="enableEmojiReaction" class="text">
<input v-model="text" placeholder="または絵文字を入力" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }"> <input v-model="text" :placeholder="$t('input-reaction-placeholder')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
</div> </div>
</div> </div>
</div> </div>

View file

@ -9,7 +9,7 @@
</blockquote> </blockquote>
</div> </div>
<div v-else class="mk-url-preview"> <div v-else class="mk-url-preview">
<a :class="{ mini: narrow, compact }" :href="url" rel="nofollow noopener" target="_blank" :title="url" v-if="!fetching"> <component :is="self ? 'router-link' : 'a'" :class="{ mini: narrow, compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="self ? null : '_blank'" :title="url" v-if="!fetching">
<div class="thumbnail" v-if="thumbnail" :style="`background-image: url('${thumbnail}')`"> <div class="thumbnail" v-if="thumbnail" :style="`background-image: url('${thumbnail}')`">
<button v-if="!playerEnabled && player.url" @click.prevent="playerEnabled = true" :title="$t('enable-player')"><fa :icon="['far', 'play-circle']"/></button> <button v-if="!playerEnabled && player.url" @click.prevent="playerEnabled = true" :title="$t('enable-player')"><fa :icon="['far', 'play-circle']"/></button>
</div> </div>
@ -23,17 +23,18 @@
<p :title="sitename">{{ sitename }}</p> <p :title="sitename">{{ sitename }}</p>
</footer> </footer>
</article> </article>
</a> </component>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import { url as misskeyUrl } from '../../../config'; import { url as local } from '../../../config';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('common/views/components/url-preview.vue'), i18n: i18n('common/views/components/url-preview.vue'),
props: { props: {
url: { url: {
type: String, type: String,
@ -74,7 +75,9 @@ export default Vue.extend({
}, },
tweetUrl: null, tweetUrl: null,
playerEnabled: false, playerEnabled: false,
misskeyUrl, local,
self: this.url.startsWith(local),
attr: this.url.startsWith(local) ? 'to' : 'href'
}; };
}, },

View file

@ -1,29 +1,35 @@
<template> <template>
<a class="mk-url" :href="url" :rel="rel" :target="target"> <component :is="self ? 'router-link' : 'a'" class="mk-url" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target">
<template v-if="!self">
<span class="schema">{{ schema }}//</span> <span class="schema">{{ schema }}//</span>
<span class="hostname">{{ hostname }}</span> <span class="hostname">{{ hostname }}</span>
<span class="port" v-if="port != ''">:{{ port }}</span> <span class="port" v-if="port != ''">:{{ port }}</span>
<span class="pathname" v-if="pathname != ''">{{ pathname }}</span> </template>
<span class="pathname" v-if="pathname != ''">{{ self ? pathname.substr(1) : pathname }}</span>
<span class="query">{{ query }}</span> <span class="query">{{ query }}</span>
<span class="hash">{{ hash }}</span> <span class="hash">{{ hash }}</span>
<fa icon="external-link-square-alt"/> <fa icon="external-link-square-alt" v-if="!self"/>
</a> </component>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { toUnicode as decodePunycode } from 'punycode'; import { toUnicode as decodePunycode } from 'punycode';
import { url as local } from '../../../config';
export default Vue.extend({ export default Vue.extend({
props: ['url', 'rel', 'target'], props: ['url', 'rel', 'target'],
data() { data() {
return { return {
local,
schema: null, schema: null,
hostname: null, hostname: null,
port: null, port: null,
pathname: null, pathname: null,
query: null, query: null,
hash: null hash: null,
self: this.url.startsWith(local),
attr: this.url.startsWith(local) ? 'to' : 'href'
}; };
}, },
created() { created() {

View file

@ -30,7 +30,7 @@
<ui-container> <ui-container>
<template #header><fa :icon="['far', 'comment-alt']"/> {{ $t('timeline') }}</template> <template #header><fa :icon="['far', 'comment-alt']"/> {{ $t('timeline') }}</template>
<div> <div>
<x-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')"/> <x-notes ref="timeline" :pagination="pagination" @inited="() => $emit('loaded')" :key="user.id"/>
</div> </div>
</ui-container> </ui-container>
</div> </div>

View file

@ -22,6 +22,7 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import Progress from '../../scripts/loading'; import Progress from '../../scripts/loading';
import { faUserClock } from '@fortawesome/free-solid-svg-icons';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('common/views/pages/follow-requests.vue'), i18n: i18n('common/views/pages/follow-requests.vue'),
@ -31,6 +32,12 @@ export default Vue.extend({
requests: [] requests: []
}; };
}, },
created() {
this.$emit('init', {
title: this.$t('received-follow-requests'),
icon: faUserClock
});
},
mounted() { mounted() {
Progress.start(); Progress.start();
this.$root.api('following/requests/list').then(requests => { this.$root.api('following/requests/list').then(requests => {

View file

@ -19,7 +19,7 @@ import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
import { faImage, faFolderOpen } from '@fortawesome/free-regular-svg-icons'; import { faImage, faFolderOpen } from '@fortawesome/free-regular-svg-icons';
import i18n from '../../../../../i18n'; import i18n from '../../../../../i18n';
import XContainer from '../page-editor.container.vue'; import XContainer from '../page-editor.container.vue';
import XFileThumbnail from '../../drive-file-thumbnail.vue'; import XFileThumbnail from '../../../components/drive-file-thumbnail.vue';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('pages'), i18n: i18n('pages'),

View file

@ -11,7 +11,7 @@
</header> </header>
<section> <section>
<a class="view" v-if="pageId" :href="`/@${ author.username }/pages/${ currentName }`" target="_blank"><fa :icon="faExternalLinkSquareAlt"/> {{ $t('view-page') }}</a> <router-link class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><fa :icon="faExternalLinkSquareAlt"/> {{ $t('view-page') }}</router-link>
<ui-input v-model="title"> <ui-input v-model="title">
<span>{{ $t('title') }}</span> <span>{{ $t('title') }}</span>
@ -111,20 +111,25 @@ export default Vue.extend({
}, },
props: { props: {
page: { initPageId: {
type: Object, type: String,
required: false required: false
}, },
readonly: { initPageName: {
type: Boolean, type: String,
required: false, required: false
default: false },
initUser: {
type: String,
required: false
}, },
}, },
data() { data() {
return { return {
author: this.$store.state.i, author: this.$store.state.i,
readonly: false,
page: null,
pageId: null, pageId: null,
currentName: null, currentName: null,
title: '', title: '',
@ -156,7 +161,7 @@ export default Vue.extend({
}, },
}, },
created() { async created() {
this.aiScript = new ASTypeChecker(); this.aiScript = new ASTypeChecker();
this.$watch('variables', () => { this.$watch('variables', () => {
@ -167,6 +172,18 @@ export default Vue.extend({
this.aiScript.pageVars = collectPageVars(this.content); this.aiScript.pageVars = collectPageVars(this.content);
}, { deep: true }); }, { deep: true });
if (this.initPageId) {
this.page = await this.$root.api('pages/show', {
pageId: this.initPageId,
});
} else if (this.initPageName && this.initUser) {
this.page = await this.$root.api('pages/show', {
name: this.initPageName,
username: this.initUser,
});
this.readonly = true;
}
if (this.page) { if (this.page) {
this.author = this.page.user; this.author = this.page.user;
this.pageId = this.page.id; this.pageId = this.page.id;

View file

@ -1,11 +1,15 @@
<template> <template>
<div class=""> <div class="mrdgzndn">
<mfm :text="text" :is-note="false" :i="$store.state.i" :key="text"/> <mfm :text="text" :is-note="false" :i="$store.state.i" :key="text"/>
<mk-url-preview v-for="url in urls" :url="url" :key="url" class="url"/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { parse } from '../../../../../../mfm/parse';
import { unique } from '../../../../../../prelude/array';
export default Vue.extend({ export default Vue.extend({
props: { props: {
@ -23,6 +27,20 @@ export default Vue.extend({
}; };
}, },
computed: {
urls(): string[] {
if (this.text) {
const ast = parse(this.text);
// TODO: URL調
return unique(ast
.filter(t => ((t.node.type == 'url' || t.node.type == 'link') && t.node.props.url && !t.node.props.silent))
.map(t => t.node.props.url));
} else {
return [];
}
}
},
created() { created() {
this.$watch('script.vars', () => { this.$watch('script.vars', () => {
this.text = this.script.interpolate(this.value.text); this.text = this.script.interpolate(this.value.text);
@ -32,4 +50,13 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.mrdgzndn
&:not(:first-child)
margin-top 0.5em
&:not(:last-child)
margin-bottom 0.5em
> .url
margin 0.5em 0
</style> </style>

View file

@ -25,7 +25,7 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../../i18n'; import i18n from '../../../../i18n';
import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons'; import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons';
import { faHeart } from '@fortawesome/free-regular-svg-icons'; import { faHeart, faStickyNote } from '@fortawesome/free-regular-svg-icons';
import XBlock from './page.block.vue'; import XBlock from './page.block.vue';
import { ASEvaluator } from '../../../../../../misc/aiscript/evaluator'; import { ASEvaluator } from '../../../../../../misc/aiscript/evaluator';
import { collectPageVars } from '../../../scripts/collect-page-vars'; import { collectPageVars } from '../../../scripts/collect-page-vars';
@ -91,6 +91,10 @@ export default Vue.extend({
username: this.username, username: this.username,
}).then(page => { }).then(page => {
this.page = page; this.page = page;
this.$emit('init', {
title: this.page.title,
icon: faStickyNote
});
const pageVars = this.getPageVars(); const pageVars = this.getPageVars();
this.script = new Script(new ASEvaluator(this.page.variables, pageVars, { this.script = new Script(new ASEvaluator(this.page.variables, pageVars, {
randomSeed: Math.random(), randomSeed: Math.random(),
@ -148,8 +152,8 @@ export default Vue.extend({
> .title > .title
z-index 1 z-index 1
margin 0 margin 0
padding 32px 64px padding 16px 32px
font-size 24px font-size 20px
font-weight bold font-weight bold
color var(--text) color var(--text)
box-shadow 0 var(--lineWidth) rgba(#000, 0.07) box-shadow 0 var(--lineWidth) rgba(#000, 0.07)
@ -158,28 +162,40 @@ export default Vue.extend({
padding 16px 32px padding 16px 32px
font-size 20px font-size 20px
@media (max-width 400px)
padding 10px 20px
font-size 16px
> div > div
color var(--text) color var(--text)
padding 48px 64px padding 24px 32px
font-size 18px font-size 16px
@media (max-width 600px) @media (max-width 600px)
padding 24px 32px padding 24px 32px
font-size 16px font-size 16px
@media (max-width 400px)
padding 20px 20px
font-size 15px
> footer > footer
color var(--text) color var(--text)
padding 0 64px 38px 64px padding 0 32px 28px 32px
@media (max-width 600px) @media (max-width 600px)
padding 0 32px 28px 32px padding 0 32px 28px 32px
@media (max-width 400px)
padding 0 20px 20px 20px
font-size 14px
> small > small
display block display block
opacity 0.5 opacity 0.5
> a > a
font-size 14px font-size 90%
> a + a > a + a
margin-left 8px margin-left 8px

View file

@ -7,6 +7,7 @@
<ui-margin> <ui-margin>
<ui-button @click="rename"><fa :icon="faICursor"/> {{ $t('rename') }}</ui-button> <ui-button @click="rename"><fa :icon="faICursor"/> {{ $t('rename') }}</ui-button>
<ui-button @click="del"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button> <ui-button @click="del"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button>
<ui-button @click="transfer"><fa :icon="faCrown"/> {{ $t('transfer') }}</ui-button>
</ui-margin> </ui-margin>
</section> </section>
</ui-container> </ui-container>
@ -28,9 +29,10 @@
<div> <div>
<header> <header>
<b><mk-user-name :user="user"/></b> <b><mk-user-name :user="user"/></b>
<span class="is-owner" v-if="group.ownerId === user.id">owner</span>
<span class="username">@{{ user | acct }}</span> <span class="username">@{{ user | acct }}</span>
</header> </header>
<div> <div v-if="group.ownerId !== user.id">
<a @click="remove(user)">{{ $t('remove-user') }}</a> <a @click="remove(user)">{{ $t('remove-user') }}</a>
</div> </div>
</div> </div>
@ -44,7 +46,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import { faICursor, faUsers, faPlus } from '@fortawesome/free-solid-svg-icons'; import { faCrown, faICursor, faUsers, faPlus } from '@fortawesome/free-solid-svg-icons';
import { faTrashAlt } from '@fortawesome/free-regular-svg-icons'; import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({ export default Vue.extend({
@ -60,7 +62,7 @@ export default Vue.extend({
return { return {
group: null, group: null,
users: [], users: [],
faICursor, faTrashAlt, faUsers, faPlus faCrown, faICursor, faTrashAlt, faUsers, faPlus
}; };
}, },
@ -78,6 +80,14 @@ export default Vue.extend({
}, },
methods: { methods: {
fetchGroup() {
this.$root.api('users/groups/show', {
groupId: this.group.id
}).then(group => {
this.group = group;
})
},
fetchUsers() { fetchUsers() {
this.$root.api('users/show', { this.$root.api('users/show', {
userIds: this.group.userIds userIds: this.group.userIds
@ -97,8 +107,15 @@ export default Vue.extend({
this.$root.api('users/groups/update', { this.$root.api('users/groups/update', {
groupId: this.group.id, groupId: this.group.id,
name: name name: name
}).then(() => {
this.fetchGroup();
}).catch(e => {
this.$root.dialog({
type: 'error',
text: e
}); });
}); });
})
}, },
del() { del() {
@ -130,7 +147,13 @@ export default Vue.extend({
groupId: this.group.id, groupId: this.group.id,
userId: user.id userId: user.id
}).then(() => { }).then(() => {
this.fetchGroup();
this.fetchUsers(); this.fetchUsers();
}).catch(e => {
this.$root.dialog({
type: 'error',
text: e
});
}); });
}, },
@ -150,6 +173,43 @@ export default Vue.extend({
type: 'success', type: 'success',
text: t text: t
}); });
}).catch(e => {
this.$root.dialog({
type: 'error',
text: e
});
});
},
async transfer() {
const { result: user } = await this.$root.dialog({
user: {
local: true
}
});
if (user == null) return;
this.$root.dialog({
type: 'warning',
text: this.$t('transfer-are-you-sure').replace('$1', this.group.name).replace('$2', user.username),
showCancelButton: true
}).then(({ canceled }) => {
if (canceled) return;
this.$root.api('users/groups/transfer', {
groupId: this.group.id,
userId: user.id
}).then(() => {
this.$root.dialog({
type: 'success',
text: this.$t('transferred')
});
}).catch(e => {
this.$root.dialog({
type: 'error',
text: e
});
});
}); });
} }
} }
@ -179,6 +239,16 @@ export default Vue.extend({
> header > header
color var(--text) color var(--text)
> .is-owner
flex-shrink 0
align-self center
margin-left 8px
padding 1px 6px
font-size 80%
background var(--groupUserListOwnerBg)
color var(--groupUserListOwnerFg)
border-radius 3px
> .username > .username
margin-left 8px margin-left 8px
opacity 0.7 opacity 0.7

View file

@ -103,6 +103,10 @@ export default Vue.extend({
}); });
this.$root.api('i/user-group-invites').then(invites => { this.$root.api('i/user-group-invites').then(invites => {
this.invites = invites; this.invites = invites;
}).then(() => {
this.$root.api('users/groups/joined').then(groups => {
this.joinedGroups = groups;
});
}); });
}); });
}, },

View file

@ -146,6 +146,7 @@ init(async (launch, os) => {
{ path: '/i/lists/:listId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.listId }) }, { path: '/i/lists/:listId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.listId }) },
{ path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) }, { path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) },
{ path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) }, { path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) },
{ path: '/i/follow-requests', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) },
]} ]}
: { path: '/', component: MkHome, children: [ : { path: '/', component: MkHome, children: [
{ path: '', name: 'index', component: MkHomeTimeline }, { path: '', name: 'index', component: MkHomeTimeline },
@ -167,11 +168,15 @@ init(async (launch, os) => {
{ path: '/i/groups', component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }, { path: '/i/groups', component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) },
{ path: '/i/groups/:groupId', props: true, component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default) }, { path: '/i/groups/:groupId', props: true, component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default) },
{ path: '/i/follow-requests', component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }, { path: '/i/follow-requests', component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) },
{ path: '/i/pages/new', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default) },
{ path: '/i/pages/edit/:pageId', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initPageId: route.params.pageId }) },
{ path: '/@:user/pages/:page', component: () => import('../common/views/pages/page/page.vue').then(m => m.default), props: route => ({ pageName: route.params.page, username: route.params.user }) },
{ path: '/@:user/pages/:pageName/view-source', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) },
]}, ]},
{ path: '/@:user/pages/:page', props: true, component: () => import('./views/pages/page.vue').then(m => m.default) }, { path: '/i/pages/new', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default) },
{ path: '/@:user/pages/:pageName/view-source', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) }, { path: '/i/pages/edit/:pageId', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initPageId: route.params.pageId }) },
{ path: '/i/pages/new', component: () => import('./views/pages/page-editor.vue').then(m => m.default) }, { path: '/@:user/pages/:page', component: () => import('../common/views/pages/page/page.vue').then(m => m.default), props: route => ({ pageName: route.params.page, username: route.params.user }) },
{ path: '/i/pages/edit/:pageId', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) }, { path: '/@:user/pages/:pageName/view-source', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) },
{ path: '/i/messaging/group/:group', component: MkMessagingRoom }, { path: '/i/messaging/group/:group', component: MkMessagingRoom },
{ path: '/i/messaging/:user', component: MkMessagingRoom }, { path: '/i/messaging/:user', component: MkMessagingRoom },
{ path: '/i/drive', component: MkDrive }, { path: '/i/drive', component: MkDrive },

View file

@ -1,67 +0,0 @@
<template>
<mk-ui>
<main>
<x-page-editor v-if="page !== undefined" :page="page" :readonly="readonly"/>
</main>
</mk-ui>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
components: {
XPageEditor: () => import('../../../common/views/components/page-editor/page-editor.vue').then(m => m.default)
},
props: {
pageId: {
type: String,
required: false
},
pageName: {
type: String,
required: false
},
user: {
type: String,
required: false
}
},
data() {
return {
page: undefined,
readonly: false
};
},
created() {
if (this.pageId) {
this.$root.api('pages/show', {
pageId: this.pageId,
}).then(page => {
this.page = page;
});
} else if (this.pageName && this.user) {
this.$root.api('pages/show', {
name: this.pageName,
username: this.user,
}).then(page => {
this.readonly = true;
this.page = page;
});
} else {
this.page = null;
}
}
});
</script>
<style lang="stylus" scoped>
main
margin 0 auto
padding 16px
max-width 900px
</style>

View file

@ -1,36 +0,0 @@
<template>
<mk-ui>
<main>
<x-page :page-name="page" :username="user"/>
</main>
</mk-ui>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
components: {
XPage: () => import('../../../common/views/pages/page/page.vue').then(m => m.default)
},
props: {
page: {
type: String,
required: true
},
user: {
type: String,
required: true
},
}
});
</script>
<style lang="stylus" scoped>
main
margin 0 auto
padding 16px
max-width 950px
</style>

View file

@ -128,6 +128,7 @@ init((launch, os) => {
{ path: '/i/lists/:listId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.listId }) }, { path: '/i/lists/:listId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.listId }) },
{ path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) }, { path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) },
{ path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) }, { path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) },
{ path: '/i/follow-requests', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) },
]}] ]}]
: [ : [
{ path: '/', name: 'index', component: MkIndex }, { path: '/', name: 'index', component: MkIndex },
@ -148,8 +149,8 @@ init((launch, os) => {
{ path: '/i/drive', name: 'drive', component: MkDrive }, { path: '/i/drive', name: 'drive', component: MkDrive },
{ path: '/i/drive/folder/:folder', component: MkDrive }, { path: '/i/drive/folder/:folder', component: MkDrive },
{ path: '/i/drive/file/:file', component: MkDrive }, { path: '/i/drive/file/:file', component: MkDrive },
{ path: '/i/pages/new', component: () => import('./views/pages/page-editor.vue').then(m => m.default) }, { path: '/i/pages/new', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default) }) },
{ path: '/i/pages/edit/:pageId', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) }, { path: '/i/pages/edit/:pageId', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), initPageId: route.params.pageId }) },
{ path: '/selectdrive', component: MkSelectDrive }, { path: '/selectdrive', component: MkSelectDrive },
{ path: '/search', component: MkSearch }, { path: '/search', component: MkSearch },
{ path: '/tags/:tag', component: MkTag }, { path: '/tags/:tag', component: MkTag },
@ -162,8 +163,8 @@ init((launch, os) => {
{ path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) }, { path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) },
{ path: 'followers', component: () => import('../common/views/pages/followers.vue').then(m => m.default) }, { path: 'followers', component: () => import('../common/views/pages/followers.vue').then(m => m.default) },
]}, ]},
{ path: '/@:user/pages/:page', props: true, component: () => import('./views/pages/page.vue').then(m => m.default) }, { path: '/@:user/pages/:page', component: UI, props: route => ({ component: () => import('../common/views/pages/page/page.vue').then(m => m.default), pageName: route.params.page, username: route.params.user }) },
{ path: '/@:user/pages/:pageName/view-source', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) }, { path: '/@:user/pages/:pageName/view-source', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), initUser: route.params.user, initPageName: route.params.pageName }) },
{ path: '/notes/:note', component: MkNote }, { path: '/notes/:note', component: MkNote },
{ path: '/authorize-follow', component: MkFollow }, { path: '/authorize-follow', component: MkFollow },
{ path: '*', component: MkNotFound } { path: '*', component: MkNotFound }

View file

@ -5,7 +5,7 @@
<div> <div>
<header> <header>
<mk-reaction-icon :reaction="notification.reaction"/> <mk-reaction-icon :reaction="notification.reaction"/>
<router-link :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link> <router-link class="name" :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link>
<mk-time :time="notification.createdAt"/> <mk-time :time="notification.createdAt"/>
</header> </header>
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)"> <router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
@ -21,7 +21,7 @@
<div> <div>
<header> <header>
<fa icon="retweet"/> <fa icon="retweet"/>
<router-link :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link> <router-link class="name" :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link>
<mk-time :time="notification.createdAt"/> <mk-time :time="notification.createdAt"/>
</header> </header>
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note.renote)"> <router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note.renote)">
@ -37,7 +37,7 @@
<div> <div>
<header> <header>
<fa icon="user-plus"/> <fa icon="user-plus"/>
<router-link :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link> <router-link class="name" :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link>
<mk-time :time="notification.createdAt"/> <mk-time :time="notification.createdAt"/>
</header> </header>
</div> </div>
@ -48,7 +48,7 @@
<div> <div>
<header> <header>
<fa icon="user-clock"/> <fa icon="user-clock"/>
<router-link :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link> <router-link class="name" :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link>
<mk-time :time="notification.createdAt"/> <mk-time :time="notification.createdAt"/>
</header> </header>
</div> </div>
@ -59,7 +59,7 @@
<div> <div>
<header> <header>
<fa icon="chart-pie"/> <fa icon="chart-pie"/>
<router-link :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link> <router-link class="name" :to="notification.user | userPage"><mk-user-name :user="notification.user"/></router-link>
<mk-time :time="notification.createdAt"/> <mk-time :time="notification.createdAt"/>
</header> </header>
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)"> <router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
@ -130,6 +130,12 @@ export default Vue.extend({
[data-icon], .mk-reaction-icon [data-icon], .mk-reaction-icon
margin-right 4px margin-right 4px
> .name
text-overflow ellipsis
white-space nowrap
min-width 0
overflow hidden
> .mk-time > .mk-time
margin-left auto margin-left auto
color var(--noteHeaderInfo) color var(--noteHeaderInfo)

View file

@ -1,67 +0,0 @@
<template>
<mk-ui>
<main>
<x-page-editor v-if="page !== undefined" :page="page" :readonly="readonly"/>
</main>
</mk-ui>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
components: {
XPageEditor: () => import('../../../common/views/components/page-editor/page-editor.vue').then(m => m.default)
},
props: {
pageId: {
type: String,
required: false
},
pageName: {
type: String,
required: false
},
user: {
type: String,
required: false
}
},
data() {
return {
page: undefined,
readonly: false
};
},
created() {
if (this.pageId) {
this.$root.api('pages/show', {
pageId: this.pageId,
}).then(page => {
this.page = page;
});
} else if (this.pageName && this.user) {
this.$root.api('pages/show', {
name: this.pageName,
username: this.user,
}).then(page => {
this.readonly = true;
this.page = page;
});
} else {
this.page = null;
}
}
});
</script>
<style lang="stylus" scoped>
main
margin 0 auto
padding 16px
max-width 1000px
</style>

View file

@ -1,39 +0,0 @@
<template>
<mk-ui>
<main>
<x-page :page-name="page" :username="user"/>
</main>
</mk-ui>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
components: {
XPage: () => import('../../../common/views/pages/page/page.vue').then(m => m.default)
},
props: {
page: {
type: String,
required: true
},
user: {
type: String,
required: true
},
}
});
</script>
<style lang="stylus" scoped>
main
margin 0 auto
padding 16px
max-width 1000px
@media (min-width 600px)
padding 32px
</style>

View file

@ -235,5 +235,8 @@
pageBlockBorder: 'rgba(255, 255, 255, 0.1)', pageBlockBorder: 'rgba(255, 255, 255, 0.1)',
pageBlockBorderHover: 'rgba(255, 255, 255, 0.15)', pageBlockBorderHover: 'rgba(255, 255, 255, 0.15)',
groupUserListOwnerFg: '#f15f71',
groupUserListOwnerBg: '#5d282e'
}, },
} }

View file

@ -235,5 +235,8 @@
pageBlockBorder: 'rgba(0, 0, 0, 0.1)', pageBlockBorder: 'rgba(0, 0, 0, 0.1)',
pageBlockBorderHover: 'rgba(0, 0, 0, 0.15)', pageBlockBorderHover: 'rgba(0, 0, 0, 0.15)',
groupUserListOwnerFg: '#f15f71',
groupUserListOwnerBg: '#ffdfdf'
}, },
} }

View file

@ -14,6 +14,7 @@ export type Source = {
db: string; db: string;
user: string; user: string;
pass: string; pass: string;
extra?: { [x: string]: string };
}; };
redis: { redis: {
host: string; host: string;

View file

@ -93,8 +93,21 @@ export function initDb(justBorrow = false, sync = false, log = false) {
username: config.db.user, username: config.db.user,
password: config.db.pass, password: config.db.pass,
database: config.db.db, database: config.db.db,
extra: config.db.extra,
synchronize: process.env.NODE_ENV === 'test' || sync, synchronize: process.env.NODE_ENV === 'test' || sync,
dropSchema: process.env.NODE_ENV === 'test' && !justBorrow, dropSchema: process.env.NODE_ENV === 'test' && !justBorrow,
cache: {
type: 'redis',
options: {
host: config.redis.host,
port: config.redis.port,
options:{
auth_pass: config.redis.pass,
prefix: config.redis.prefix,
db: config.redis.db || 0
}
}
},
logging: log, logging: log,
logger: log ? new MyCustomLogger() : undefined, logger: log ? new MyCustomLogger() : undefined,
entities: [ entities: [

View file

@ -21,6 +21,7 @@ export class UserGroupRepository extends Repository<UserGroup> {
id: userGroup.id, id: userGroup.id,
createdAt: userGroup.createdAt.toISOString(), createdAt: userGroup.createdAt.toISOString(),
name: userGroup.name, name: userGroup.name,
ownerId: userGroup.userId,
userIds: users.map(x => x.userId) userIds: users.map(x => x.userId)
}; };
} }
@ -48,6 +49,11 @@ export const packedUserGroupSchema = {
optional: bool.false, nullable: bool.false, optional: bool.false, nullable: bool.false,
description: 'The name of the UserGroup.' description: 'The name of the UserGroup.'
}, },
ownerId: {
type: types.string,
nullable: bool.false, optional: bool.false,
format: 'id',
},
userIds: { userIds: {
type: types.array, type: types.array,
nullable: bool.false, optional: bool.true, nullable: bool.false, optional: bool.true,

View file

@ -8,6 +8,7 @@ const json = {
username: config.db.user, username: config.db.user,
password: config.db.pass, password: config.db.pass,
database: config.db.db, database: config.db.db,
extra: config.db.extra,
entities: ['src/models/entities/*.ts'], entities: ['src/models/entities/*.ts'],
migrations: ['migration/*.ts'], migrations: ['migration/*.ts'],
cli: { cli: {

View file

@ -0,0 +1,13 @@
import define from '../../define';
import { Logs } from '../../../../models';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
};
export default define(meta, async (ps) => {
await Logs.delete({});
});

View file

@ -59,6 +59,7 @@ export default define(meta, async () => {
.where(`note.createdAt > :date`, { date: new Date(Date.now() - rangeA) }) .where(`note.createdAt > :date`, { date: new Date(Date.now() - rangeA) })
.andWhere(`note.tags != '{}'`) .andWhere(`note.tags != '{}'`)
.select(['note.tags', 'note.userId']) .select(['note.tags', 'note.userId'])
.cache(60000) // 1 min
.getMany(); .getMany();
if (tagNotes.length === 0) { if (tagNotes.length === 0) {
@ -108,6 +109,7 @@ export default define(meta, async () => {
.where(':tag = ANY(note.tags)', { tag: tag }) .where(':tag = ANY(note.tags)', { tag: tag })
.andWhere('note.createdAt < :lt', { lt: new Date(Date.now() - (interval * i)) }) .andWhere('note.createdAt < :lt', { lt: new Date(Date.now() - (interval * i)) })
.andWhere('note.createdAt > :gt', { gt: new Date(Date.now() - (interval * (i + 1))) }) .andWhere('note.createdAt > :gt', { gt: new Date(Date.now() - (interval * (i + 1))) })
.cache(60000) // 1 min
.getRawOne() .getRawOne()
.then(x => parseInt(x.count, 10)) .then(x => parseInt(x.count, 10))
))); )));
@ -119,6 +121,7 @@ export default define(meta, async () => {
.select('count(distinct note.userId)') .select('count(distinct note.userId)')
.where(':tag = ANY(note.tags)', { tag: tag }) .where(':tag = ANY(note.tags)', { tag: tag })
.andWhere('note.createdAt > :gt', { gt: new Date(Date.now() - (interval * range)) }) .andWhere('note.createdAt > :gt', { gt: new Date(Date.now() - (interval * range)) })
.cache(60000) // 1 min
.getRawOne() .getRawOne()
.then(x => parseInt(x.count, 10)) .then(x => parseInt(x.count, 10))
)); ));

View file

@ -1,15 +1,13 @@
import $ from 'cafy'; import $ from 'cafy';
import { publishMainStream } from '../../../../services/stream'; import { publishMainStream } from '../../../../services/stream';
import define from '../../define'; import define from '../../define';
import * as nodemailer from 'nodemailer';
import { fetchMeta } from '../../../../misc/fetch-meta';
import rndstr from 'rndstr'; import rndstr from 'rndstr';
import config from '../../../../config'; import config from '../../../../config';
import * as ms from 'ms'; import * as ms from 'ms';
import * as bcrypt from 'bcryptjs'; import * as bcrypt from 'bcryptjs';
import { apiLogger } from '../../logger';
import { Users, UserProfiles } from '../../../../models'; import { Users, UserProfiles } from '../../../../models';
import { ensure } from '../../../../prelude/ensure'; import { ensure } from '../../../../prelude/ensure';
import { sendEmail } from '../../../../services/send-email';
export const meta = { export const meta = {
requireCredential: true, requireCredential: true,
@ -63,36 +61,9 @@ export default define(meta, async (ps, user) => {
emailVerifyCode: code emailVerifyCode: code
}); });
const meta = await fetchMeta(true);
const enableAuth = meta.smtpUser != null && meta.smtpUser !== '';
const transporter = nodemailer.createTransport({
host: meta.smtpHost,
port: meta.smtpPort,
secure: meta.smtpSecure,
ignoreTLS: !enableAuth,
auth: enableAuth ? {
user: meta.smtpUser,
pass: meta.smtpPass
} : undefined
} as any);
const link = `${config.url}/verify-email/${code}`; const link = `${config.url}/verify-email/${code}`;
transporter.sendMail({ sendEmail(ps.email, 'Email verification', `To verify email, please click this link: ${link}`);
from: meta.email!,
to: ps.email,
subject: meta.name || 'Misskey',
text: `To verify email, please click this link: ${link}`
}, (error, info) => {
if (error) {
apiLogger.error(error);
return;
}
apiLogger.info('Message sent: %s', info.messageId);
});
} }
return iObj; return iObj;

View file

@ -94,7 +94,7 @@ export const meta = {
export default define(meta, async (ps, me) => { export default define(meta, async (ps, me) => {
const instance = await fetchMeta(true); const instance = await fetchMeta(true);
const emojis = await Emojis.find({ host: null }); const emojis = await Emojis.find({ where: { host: null }, cache: 3600000 }); // 1 hour
const response: any = { const response: any = {
maintainerName: instance.maintainerName, maintainerName: instance.maintainerName,

View file

@ -57,10 +57,10 @@ export default define(meta, async () => {
driveUsageLocal, driveUsageLocal,
driveUsageRemote driveUsageRemote
] = await Promise.all([ ] = await Promise.all([
Notes.count(), Notes.count({ cache: 3600000 }), // 1 hour
Notes.count({ userHost: null }), Notes.count({ where: { userHost: null }, cache: 3600000 }),
Users.count(), Users.count({ cache: 3600000 }),
Users.count({ host: null }), Users.count({ where: { host: null }, cache: 3600000 }),
federationChart.getChart('hour', 1).then(chart => chart.instance.total[0]), federationChart.getChart('hour', 1).then(chart => chart.instance.total[0]),
driveChart.getChart('hour', 1).then(chart => chart.local.totalSize[0]), driveChart.getChart('hour', 1).then(chart => chart.local.totalSize[0]),
driveChart.getChart('hour', 1).then(chart => chart.remote.totalSize[0]), driveChart.getChart('hour', 1).then(chart => chart.remote.totalSize[0]),

View file

@ -0,0 +1,93 @@
import $ from 'cafy';
import { ID } from '../../../../../misc/cafy-id';
import define from '../../../define';
import { ApiError } from '../../../error';
import { getUser } from '../../../common/getters';
import { UserGroups, UserGroupJoinings } from '../../../../../models';
import { types, bool } from '../../../../../misc/schema';
export const meta = {
desc: {
'ja-JP': '指定したユーザーグループを指定したユーザーグループ内のユーザーに譲渡します。',
'en-US': 'Transfer user group ownership to another user in group.'
},
tags: ['groups', 'users'],
requireCredential: true,
kind: 'write:user-groups',
params: {
groupId: {
validator: $.type(ID),
},
userId: {
validator: $.type(ID),
desc: {
'ja-JP': '対象のユーザーのID',
'en-US': 'Target user ID'
}
},
},
res: {
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'UserGroup',
},
errors: {
noSuchGroup: {
message: 'No such group.',
code: 'NO_SUCH_GROUP',
id: '8e31d36b-2f88-4ccd-a438-e2d78a9162db'
},
noSuchUser: {
message: 'No such user.',
code: 'NO_SUCH_USER',
id: '711f7ebb-bbb9-4dfa-b540-b27809fed5e9'
},
noSuchGroupMember: {
message: 'No such group member.',
code: 'NO_SUCH_GROUP_MEMBER',
id: 'd31bebee-196d-42c2-9a3e-9474d4be6cc4'
},
}
};
export default define(meta, async (ps, me) => {
// Fetch the group
const userGroup = await UserGroups.findOne({
id: ps.groupId,
userId: me.id,
});
if (userGroup == null) {
throw new ApiError(meta.errors.noSuchGroup);
}
// Fetch the user
const user = await getUser(ps.userId).catch(e => {
if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
throw e;
});
const joining = await UserGroupJoinings.findOne({
userGroupId: userGroup.id,
userId: user.id
});
if (joining == null) {
throw new ApiError(meta.errors.noSuchGroupMember);
}
await UserGroups.update(userGroup.id, {
userId: ps.userId
});
return await UserGroups.pack(userGroup.id);
});

View file

@ -0,0 +1,69 @@
import $ from 'cafy';
import { ID } from '../../../../../misc/cafy-id';
import define from '../../../define';
import { ApiError } from '../../../error';
import { UserGroups } from '../../../../../models';
import { types, bool } from '../../../../../misc/schema';
export const meta = {
desc: {
'ja-JP': '指定したユーザーグループを更新します。',
'en-US': 'Update a user group'
},
tags: ['groups'],
requireCredential: true,
kind: 'write:user-groups',
params: {
groupId: {
validator: $.type(ID),
desc: {
'ja-JP': '対象となるユーザーグループのID',
'en-US': 'ID of target user group'
}
},
name: {
validator: $.str.range(1, 100),
desc: {
'ja-JP': 'このユーザーグループの名前',
'en-US': 'name of this user group'
}
}
},
res: {
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'UserGroup',
},
errors: {
noSuchGroup: {
message: 'No such group.',
code: 'NO_SUCH_GROUP',
id: '9081cda3-7a9e-4fac-a6ce-908d70f282f6'
},
}
};
export default define(meta, async (ps, me) => {
// Fetch the group
const userGroup = await UserGroups.findOne({
id: ps.groupId,
userId: me.id
});
if (userGroup == null) {
throw new ApiError(meta.errors.noSuchGroup);
}
await UserGroups.update(userGroup.id, {
name: ps.name
});
return await UserGroups.pack(userGroup.id);
});

View file

@ -0,0 +1,36 @@
import * as nodemailer from 'nodemailer';
import { fetchMeta } from '../misc/fetch-meta';
import Logger from './logger';
export const logger = new Logger('email');
export async function sendEmail(to: string, subject: string, text: string) {
const meta = await fetchMeta(true);
const enableAuth = meta.smtpUser != null && meta.smtpUser !== '';
const transporter = nodemailer.createTransport({
host: meta.smtpHost,
port: meta.smtpPort,
secure: meta.smtpSecure,
ignoreTLS: !enableAuth,
auth: enableAuth ? {
user: meta.smtpUser,
pass: meta.smtpPass
} : undefined
} as any);
try {
const info = await transporter.sendMail({
from: meta.email!,
to: to,
subject: subject || 'Misskey',
text: text
});
logger.info('Message sent: %s', info.messageId);
} catch (e) {
logger.error(e);
throw e;
}
}