From 4ec1771165ab8dd40e52804fd087eacfab25290b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 28 Sep 2017 15:31:31 +0200 Subject: Add ability to specify alternative text for media attachments (#5123) * Fix #117 - Add ability to specify alternative text for media attachments - POST /api/v1/media accepts `description` straight away - PUT /api/v1/media/:id to update `description` (only for unattached ones) - Serialized as `name` of Document object in ActivityPub - Uploads form adjusted for better performance and description input * Add tests * Change undo button blend mode to difference --- app/javascript/mastodon/actions/compose.js | 38 ++++ .../mastodon/components/extended_video_player.js | 14 +- .../mastodon/components/media_gallery.js | 3 +- app/javascript/mastodon/components/video_player.js | 204 --------------------- .../mastodon/features/compose/components/upload.js | 96 ++++++++++ .../features/compose/components/upload_form.js | 44 ++--- .../compose/containers/upload_container.js | 21 +++ .../compose/containers/upload_form_container.js | 13 +- .../mastodon/features/ui/components/media_modal.js | 5 +- .../mastodon/features/ui/components/video_modal.js | 1 + .../mastodon/features/ui/util/async-components.js | 4 - app/javascript/mastodon/features/video/index.js | 4 +- app/javascript/mastodon/reducers/compose.js | 19 +- app/javascript/styles/components.scss | 47 ++++- 14 files changed, 243 insertions(+), 270 deletions(-) delete mode 100644 app/javascript/mastodon/components/video_player.js create mode 100644 app/javascript/mastodon/features/compose/components/upload.js create mode 100644 app/javascript/mastodon/features/compose/containers/upload_container.js (limited to 'app/javascript') diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 9f10a8c15..8be5b939f 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -37,6 +37,10 @@ export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE'; export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT'; +export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST'; +export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS'; +export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL'; + export function changeCompose(text) { return { type: COMPOSE_CHANGE, @@ -165,6 +169,40 @@ export function uploadCompose(files) { }; }; +export function changeUploadCompose(id, description) { + return (dispatch, getState) => { + dispatch(changeUploadComposeRequest()); + + api(getState).put(`/api/v1/media/${id}`, { description }).then(response => { + dispatch(changeUploadComposeSuccess(response.data)); + }).catch(error => { + dispatch(changeUploadComposeFail(id, error)); + }); + }; +}; + +export function changeUploadComposeRequest() { + return { + type: COMPOSE_UPLOAD_CHANGE_REQUEST, + skipLoading: true, + }; +}; +export function changeUploadComposeSuccess(media) { + return { + type: COMPOSE_UPLOAD_CHANGE_SUCCESS, + media: media, + skipLoading: true, + }; +}; + +export function changeUploadComposeFail(error) { + return { + type: COMPOSE_UPLOAD_CHANGE_FAIL, + error: error, + skipLoading: true, + }; +}; + export function uploadComposeRequest() { return { type: COMPOSE_UPLOAD_REQUEST, diff --git a/app/javascript/mastodon/components/extended_video_player.js b/app/javascript/mastodon/components/extended_video_player.js index 5ab5e9e58..f8bd067e8 100644 --- a/app/javascript/mastodon/components/extended_video_player.js +++ b/app/javascript/mastodon/components/extended_video_player.js @@ -5,6 +5,7 @@ export default class ExtendedVideoPlayer extends React.PureComponent { static propTypes = { src: PropTypes.string.isRequired, + alt: PropTypes.string, width: PropTypes.number, height: PropTypes.number, time: PropTypes.number, @@ -31,15 +32,20 @@ export default class ExtendedVideoPlayer extends React.PureComponent { } render () { + const { src, muted, controls, alt } = this.props; + return (
); diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index a81409871..38b26b1fc 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -136,7 +136,7 @@ class Item extends React.PureComponent { onClick={this.handleClick} target='_blank' > - + {attachment.get('description')} ); } else if (attachment.get('type') === 'gifv') { @@ -146,6 +146,7 @@ class Item extends React.PureComponent {
diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index b8c5e885a..ad5493f8c 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -90,10 +90,6 @@ export function MediaGallery () { return import(/* webpackChunkName: "status/media_gallery" */'../../../components/media_gallery'); } -export function VideoPlayer () { - return import(/* webpackChunkName: "status/video_player" */'../../../components/video_player'); -} - export function Video () { return import(/* webpackChunkName: "features/video" */'../../video'); } diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js index f228e434b..069264ef5 100644 --- a/app/javascript/mastodon/features/video/index.js +++ b/app/javascript/mastodon/features/video/index.js @@ -104,6 +104,7 @@ export default class Video extends React.PureComponent { static propTypes = { preview: PropTypes.string, src: PropTypes.string.isRequired, + alt: PropTypes.string, width: PropTypes.number, height: PropTypes.number, sensitive: PropTypes.bool, @@ -247,7 +248,7 @@ export default class Video extends React.PureComponent { } render () { - const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl } = this.props; + const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt } = this.props; const { progress, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; return ( @@ -260,6 +261,7 @@ export default class Video extends React.PureComponent { loop role='button' tabIndex='0' + aria-label={alt} width={width} height={height} onClick={this.togglePlay} diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 9d39584fc..082d4d370 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -22,6 +22,9 @@ import { COMPOSE_VISIBILITY_CHANGE, COMPOSE_COMPOSING_CHANGE, COMPOSE_EMOJI_INSERT, + COMPOSE_UPLOAD_CHANGE_REQUEST, + COMPOSE_UPLOAD_CHANGE_SUCCESS, + COMPOSE_UPLOAD_CHANGE_FAIL, } from '../actions/compose'; import { TIMELINE_DELETE } from '../actions/timelines'; import { STORE_HYDRATE } from '../actions/store'; @@ -220,15 +223,15 @@ export default function compose(state = initialState, action) { map.set('idempotencyKey', uuid()); }); case COMPOSE_SUBMIT_REQUEST: + case COMPOSE_UPLOAD_CHANGE_REQUEST: return state.set('is_submitting', true); case COMPOSE_SUBMIT_SUCCESS: return clearAll(state); case COMPOSE_SUBMIT_FAIL: + case COMPOSE_UPLOAD_CHANGE_FAIL: return state.set('is_submitting', false); case COMPOSE_UPLOAD_REQUEST: - return state.withMutations(map => { - map.set('is_uploading', true); - }); + return state.set('is_uploading', true); case COMPOSE_UPLOAD_SUCCESS: return appendMedia(state, fromJS(action.media)); case COMPOSE_UPLOAD_FAIL: @@ -256,6 +259,16 @@ export default function compose(state = initialState, action) { } case COMPOSE_EMOJI_INSERT: return insertEmoji(state, action.position, action.emoji); + case COMPOSE_UPLOAD_CHANGE_SUCCESS: + return state + .set('is_submitting', false) + .update('media_attachments', list => list.map(item => { + if (item.get('id') === action.media.id) { + return item.set('description', action.media.description); + } + + return item; + })); default: return state; } diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index da479347b..631cd7a13 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -335,12 +335,52 @@ .compose-form__uploads-wrapper { display: flex; + flex-direction: row; padding: 5px; + flex-wrap: wrap; } .compose-form__upload { flex: 1 1 0; + min-width: 40%; margin: 5px; + + &-description { + position: absolute; + z-index: 2; + bottom: 0; + left: 0; + right: 0; + box-sizing: border-box; + background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent); + padding: 10px; + opacity: 0; + transition: opacity .1s ease; + + input { + background: transparent; + color: $ui-secondary-color; + border: 0; + padding: 0; + margin: 0; + width: 100%; + font-family: inherit; + font-size: 14px; + font-weight: 500; + + &:focus { + color: $white; + } + } + + &.active { + opacity: 1; + } + } + + .icon-button { + mix-blend-mode: difference; + } } .compose-form__upload-thumbnail { @@ -352,13 +392,6 @@ width: 100%; } -.compose-form__upload-cancel { - background-size: cover; - border-radius: 4px; - height: 100px; - width: 100px; -} - .compose-form__label { display: block; line-height: 24px; -- cgit From a3202f61af7d4833808d429c79dfc21e74f06c99 Mon Sep 17 00:00:00 2001 From: Jakob Kramer <811907+gandaro@users.noreply.github.com> Date: Thu, 28 Sep 2017 17:38:39 +0200 Subject: Updated German translation (#5132) --- app/javascript/mastodon/locales/de.json | 82 ++++++++++++++++----------------- 1 file changed, 41 insertions(+), 41 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 461e7e304..c892cc49b 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -1,7 +1,7 @@ { "account.block": "@{name} blocken", "account.block_domain": "Alles von {domain} verstecken", - "account.disclaimer_full": "Hier aufgeführten Informationen können unvollständig sein.", + "account.disclaimer_full": "Das Profil wird möglicherweise unvollständig wiedergegeben.", "account.edit_profile": "Profil bearbeiten", "account.follow": "Folgen", "account.followers": "Folgende", @@ -18,11 +18,11 @@ "account.unblock_domain": "{domain} wieder anzeigen", "account.unfollow": "Entfolgen", "account.unmute": "@{name} nicht mehr stummschalten", - "account.view_full_profile": "Komplettes Profil anzeigen", + "account.view_full_profile": "Vollständiges Profil anzeigen", "boost_modal.combo": "Du kannst {combo} drücken, um dies beim nächsten Mal zu überspringen", "bundle_column_error.body": "Etwas ist beim Laden schiefgelaufen.", "bundle_column_error.retry": "Erneut versuchen", - "bundle_column_error.title": "Netzwerkfehlher", + "bundle_column_error.title": "Netzwerkfehler", "bundle_modal_error.close": "Schließen", "bundle_modal_error.message": "Etwas ist beim Laden schiefgelaufen.", "bundle_modal_error.retry": "Erneut versuchen", @@ -37,8 +37,8 @@ "column.public": "Gesamtes bekanntes Netz", "column_back_button.label": "Zurück", "column_header.hide_settings": "Einstellungen verbergen", - "column_header.moveLeft_settings": "Spalte links verschieben", - "column_header.moveRight_settings": "Spalte rechts verschieben", + "column_header.moveLeft_settings": "Spalte nach links verschieben", + "column_header.moveRight_settings": "Spalte nach rechts verschieben", "column_header.pin": "Anheften", "column_header.show_settings": "Einstellungen anzeigen", "column_header.unpin": "Lösen", @@ -56,14 +56,14 @@ "confirmations.block.confirm": "Blockieren", "confirmations.block.message": "Bist du dir sicher, dass du {name} blockieren möchtest?", "confirmations.delete.confirm": "Löschen", - "confirmations.delete.message": "Bist du dir sicher, dass du diesen Beitrag löschen möchstest?", + "confirmations.delete.message": "Bist du dir sicher, dass du diesen Beitrag löschen möchtest?", "confirmations.domain_block.confirm": "Die ganze Domain verbergen", - "confirmations.domain_block.message": "Bist du dir wirklich sicher, dass du die ganze Domain {domain} verbergen willst? In den meisten Fällen sind ein paar gezielte Blocks genug.", + "confirmations.domain_block.message": "Bist du dir wirklich sicher, dass du die ganze Domain {domain} verbergen willst? In den meisten Fällen reichen ein paar gezielte Blocks aus.", "confirmations.mute.confirm": "Stummschalten", - "confirmations.mute.message": "Bist du dir sicher, dass du {name} stummschalten möchstest?", + "confirmations.mute.message": "Bist du dir sicher, dass du {name} stummschalten möchtest?", "confirmations.unfollow.confirm": "Entfolgen", - "confirmations.unfollow.message": "Bist du dir sicher, dass du {name} entfolgen möchstest?", - "embed.instructions": "Du kannst diesen Beitrag auf deiner Webseite einbetten, in dem du den folgenden Code einfügst.", + "confirmations.unfollow.message": "Bist du dir sicher, dass du {name} entfolgen möchtest?", + "embed.instructions": "Du kannst diesen Beitrag auf deiner Webseite einbetten, indem du den folgenden Code einfügst.", "embed.preview": "So wird es aussehen:", "emoji_button.activity": "Aktivitäten", "emoji_button.custom": "Custom", @@ -71,18 +71,18 @@ "emoji_button.food": "Essen und Trinken", "emoji_button.label": "Emoji einfügen", "emoji_button.nature": "Natur", - "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", - "emoji_button.objects": "Dinge", + "emoji_button.not_found": "Keine Emojis!! (╯°□°)╯︵ ┻━┻", + "emoji_button.objects": "Gegenstände", "emoji_button.people": "Leute", - "emoji_button.recent": "Frequently used", - "emoji_button.search": "Suche…", - "emoji_button.search_results": "Search results", + "emoji_button.recent": "Häufig benutzt", + "emoji_button.search": "Suchen …", + "emoji_button.search_results": "Suchergebnisse", "emoji_button.symbols": "Symbole", - "emoji_button.travel": "Reise und Orte", + "emoji_button.travel": "Reisen und Orte", "empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe etwas öffentlich, um den Ball ins Rollen zu bringen!", "empty_column.hashtag": "Es gibt noch nichts unter diesem Hashtag.", "empty_column.home": "Du folgst noch niemandem. Besuche {public} oder benutze die Suche, um zu starten oder andere Profile zu finden.", - "empty_column.home.inactivity": "Deine Zeitleiste ist leer. Falls du eine längere Zeit inaktiv gewesen bist, wird sie für dich so schnell wie möglich wiedererstellt.", + "empty_column.home.inactivity": "Deine Zeitleiste ist leer. Falls du eine längere Zeit inaktiv gewesen bist, wird sie für dich so schnell wie möglich neu erstellt.", "empty_column.home.public_timeline": "die öffentliche Zeitleiste", "empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um die Konversation zu starten.", "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Profilen von anderen Instanzen, um es aufzufüllen.", @@ -95,14 +95,14 @@ "getting_started.userguide": "Bedienungsanleitung", "home.column_settings.advanced": "Fortgeschritten", "home.column_settings.basic": "Einfach", - "home.column_settings.filter_regex": "Filter durch reguläre Ausdrücke", + "home.column_settings.filter_regex": "Mit regulären Ausdrücken filtern", "home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen", "home.column_settings.show_replies": "Antworten anzeigen", "home.settings": "Spalteneinstellungen", "lightbox.close": "Schließen", "lightbox.next": "Weiter", "lightbox.previous": "Zurück", - "loading_indicator.label": "Lade…", + "loading_indicator.label": "Lade …", "media_gallery.toggle_visible": "Sichtbarkeit einstellen", "missing_indicator.label": "Nicht gefunden", "navigation_bar.blocks": "Blockierte Profile", @@ -121,26 +121,26 @@ "notification.mention": "{name} erwähnte dich", "notification.reblog": "{name} teilte deinen Status", "notifications.clear": "Mitteilungen löschen", - "notifications.clear_confirmation": "Bist du dir sicher, dass du alle Mitteilungen löschen möchstest?", + "notifications.clear_confirmation": "Bist du dir sicher, dass du alle Mitteilungen löschen möchtest?", "notifications.column_settings.alert": "Desktop-Benachrichtigungen", "notifications.column_settings.favourite": "Favorisierungen:", "notifications.column_settings.follow": "Neue Folgende:", "notifications.column_settings.mention": "Erwähnungen:", - "notifications.column_settings.push": "Push notifications", + "notifications.column_settings.push": "Push-Benachrichtigungen", "notifications.column_settings.push_meta": "This device", "notifications.column_settings.reblog": "Geteilte Beiträge:", "notifications.column_settings.show": "In der Spalte anzeigen", "notifications.column_settings.sound": "Ton abspielen", "onboarding.done": "Fertig", "onboarding.next": "Weiter", - "onboarding.page_five.public_timelines": "Die lokale Zeitleiste zeigt alle Beiträge von Leuten, die auch auf deiner Instanz {domain} sind. Das gesamte bekannte Netz zeigt Beiträge von allen, die kollektiv aus deiner Instanz heraus gefolgt werden. Zusammen werden die beiden Leisten auch öffentliche Zeitleisten genannt, durch sie kannst du viel neues entdecken.", + "onboarding.page_five.public_timelines": "Die lokale Zeitleiste zeigt alle Beiträge von Leuten, die auch auf {domain} sind. Das gesamte bekannte Netz zeigt Beiträge von allen, denen von Leuten auf {domain} gefolgt wird. Zusammen werden die beiden Leisten auch öffentliche Zeitleisten genannt. Durch sie kannst du viel Neues entdecken.", "onboarding.page_four.home": "Die Startseite zeigt dir Beiträge von Leuten, denen du folgst.", - "onboarding.page_four.notifications": "Wenn jemand mir dir interagiert, bekommst du eine Mitteilung.", + "onboarding.page_four.notifications": "Wenn jemand mit dir interagiert, bekommst du eine Mitteilung.", "onboarding.page_one.federation": "Mastodon ist ein soziales Netzwerk, das aus unabhängigen Servern besteht. Diese Server nennen wir auch Instanzen.", "onboarding.page_one.handle": "Du bist auf der Instanz {domain}, also ist dein vollständiger Profilname im Netzwerk {handle}", "onboarding.page_one.welcome": "Willkommen bei Mastodon!", "onboarding.page_six.admin": "Für deine Instanz ist {admin} zuständig.", - "onboarding.page_six.almost_done": "Fast fertig…", + "onboarding.page_six.almost_done": "Fast fertig …", "onboarding.page_six.appetoot": "Guten Appetröt!", "onboarding.page_six.apps_available": "Es gibt verschiedene {apps} für iOS, Android und andere Plattformen.", "onboarding.page_six.github": "Mastodon ist freie, quelloffene Software. Du kannst auf {github} dazu beitragen oder Probleme melden.", @@ -148,10 +148,10 @@ "onboarding.page_six.read_guidelines": "Bitte mach dich mit den {guidelines} von {domain} vertraut!", "onboarding.page_six.various_app": "mobile Anwendungen", "onboarding.page_three.profile": "Bearbeite dein Profil, um dein Bild, deinen Namen oder deine Beschreibung anzupassen. Dort findest du auch andere Einstellungen.", - "onboarding.page_three.search": "Benutze die Suchfunktion, um Leute oder Themen zu finden. Zum Beispiel, die Hashtags {illustration} oder {introductions}. Um eine Person zu finden, die auf einer anderen Instanz ist, benutze den vollständigen Profilnamen.", - "onboarding.page_two.compose": "Schreibe Beiträge aus der Schreiben-Spalte. Du kannst Bilder und kurze Videos hochladen, Sichtbarkeitseinstellungen ändern und Inhaltswarnungen hinzufügen.", + "onboarding.page_three.search": "Benutze die Suchfunktion, um Leute oder Themen zu finden. Zum Beispiel die Hashtags {illustration} oder {introductions}. Um eine Person zu finden, die auf einer anderen Instanz ist, benutze den vollständigen Profilnamen.", + "onboarding.page_two.compose": "Schreibe Beiträge aus der Schreiben-Spalte. Du kannst Bilder und kurze Videos hochladen, Sichtbarkeits-Einstellungen ändern und Inhaltswarnungen hinzufügen.", "onboarding.skip": "Überspringen", - "privacy.change": "Privatsphäre des Status anpassen", + "privacy.change": "Sichtbarkeit des Status anpassen", "privacy.direct.long": "Beitrag nur an erwähnte Profile", "privacy.direct.short": "Direkt", "privacy.private.long": "Beitrag nur an Folgende", @@ -166,7 +166,7 @@ "report.target": "Melden", "search.placeholder": "Suche", "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}", - "standalone.public_title": "Vorschau…", + "standalone.public_title": "Vorschau …", "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden", "status.delete": "Löschen", "status.embed": "Einbetten", @@ -176,7 +176,7 @@ "status.mention": "Erwähnen", "status.mute_conversation": "Thread stummschalten", "status.open": "Öffnen", - "status.pin": "Auf dem Profil anheften", + "status.pin": "Im Profil anheften", "status.reblog": "Teilen", "status.reblogged_by": "{name} teilte", "status.reply": "Antworten", @@ -197,18 +197,18 @@ "upload_area.title": "Hereinziehen zum Hochladen", "upload_button.label": "Mediendatei hinzufügen", "upload_form.undo": "Entfernen", - "upload_progress.label": "Lade hoch…", - "video.close": "Close video", - "video.exit_fullscreen": "Exit full screen", - "video.expand": "Expand video", - "video.fullscreen": "Full screen", - "video.hide": "Hide video", - "video.mute": "Mute sound", + "upload_progress.label": "Lade hoch …", + "video.close": "Video schließen", + "video.exit_fullscreen": "Vollbild verlassen", + "video.expand": "Video vergrößern", + "video.fullscreen": "Vollbild", + "video.hide": "Video verbergen", + "video.mute": "Stummschalten", "video.pause": "Pause", - "video.play": "Play", - "video.unmute": "Unmute sound", - "video_player.expand": "Videoanzeige vergrößern", - "video_player.toggle_sound": "Ton umschalten", - "video_player.toggle_visible": "Sichtbarkeit umschalten", + "video.play": "Abspielen", + "video.unmute": "Ton einschalten", + "video_player.expand": "Video vergrößern", + "video_player.toggle_sound": "Ton an/aus", + "video_player.toggle_visible": "Video zeigen/verbergen", "video_player.video_error": "Video konnte nicht abgespielt werden" } -- cgit From 6e0659c838bacfea54bbab5a4dd3501fbdf8b668 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Thu, 28 Sep 2017 11:43:18 -0700 Subject: Improve performance of modal and swipe animations (#5135) * Improve performance of modal and swipe animations * Fix eslint issues --- .../mastodon/features/ui/components/media_modal.js | 7 ++- .../mastodon/features/ui/components/modal_root.js | 65 ++++++++++------------ app/javascript/styles/components.scss | 9 ++- 3 files changed, 40 insertions(+), 41 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js index da2ceecb1..705645b40 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.js @@ -84,14 +84,17 @@ export default class MediaModal extends ImmutablePureComponent { return null; }).toArray(); + const containerStyle = { + alignItems: 'center', // center vertically + }; + return (
{leftNav}
- - + {content}
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js index a09c9d9b3..f420f0abf 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.js +++ b/app/javascript/mastodon/features/ui/components/modal_root.js @@ -1,7 +1,5 @@ import React from 'react'; import PropTypes from 'prop-types'; -import TransitionMotion from 'react-motion/lib/TransitionMotion'; -import spring from 'react-motion/lib/spring'; import BundleContainer from '../containers/bundle_container'; import BundleModalError from './bundle_modal_error'; import ModalLoading from './modal_loading'; @@ -35,6 +33,10 @@ export default class ModalRoot extends React.PureComponent { onClose: PropTypes.func.isRequired, }; + state = { + revealed: false, + }; + handleKeyUp = (e) => { if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) && !!this.props.type) { @@ -51,6 +53,8 @@ export default class ModalRoot extends React.PureComponent { this.activeElement = document.activeElement; this.getSiblings().forEach(sibling => sibling.setAttribute('inert', true)); + } else if (!nextProps.type) { + this.setState({ revealed: false }); } } @@ -60,6 +64,11 @@ export default class ModalRoot extends React.PureComponent { this.activeElement.focus(); this.activeElement = null; } + if (this.props.type) { + requestAnimationFrame(() => { + this.setState({ revealed: true }); + }); + } } componentWillUnmount () { @@ -74,14 +83,6 @@ export default class ModalRoot extends React.PureComponent { this.node = ref; } - willEnter () { - return { opacity: 0, scale: 0.98 }; - } - - willLeave () { - return { opacity: spring(0), scale: spring(0.98) }; - } - renderLoading = modalId => () => { return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? : null; } @@ -94,38 +95,30 @@ export default class ModalRoot extends React.PureComponent { render () { const { type, props, onClose } = this.props; + const { revealed } = this.state; const visible = !!type; - const items = []; - if (visible) { - items.push({ - key: type, - data: { type, props }, - style: { opacity: spring(1), scale: spring(1, { stiffness: 120, damping: 14 }) }, - }); + if (!visible) { + return ( +
+ ); } return ( - - {interpolatedStyles => -
- {interpolatedStyles.map(({ key, data: { type, props }, style }) => ( -
-
-
- - {(SpecificComponent) => } - -
-
- ))} +
+
+
+
+ { + visible ? + ( + {(SpecificComponent) => } + ) : + null + }
- } - +
+
); } diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 631cd7a13..5ea0d134e 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -2983,14 +2983,18 @@ button.icon-button.active i.fa-retweet { } } +.modal-root { + transition: opacity 0.3s linear; + will-change: opacity; + z-index: 9999; +} + .modal-root__overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; - z-index: 9999; - opacity: 0; background: rgba($base-overlay-background, 0.7); transform: translateZ(0); } @@ -3007,7 +3011,6 @@ button.icon-button.active i.fa-retweet { justify-content: center; align-content: space-around; z-index: 9999; - opacity: 0; pointer-events: none; user-select: none; } -- cgit From d0b4709b2a0bbd9579a3f115c3d200661ccb784a Mon Sep 17 00:00:00 2001 From: Jakob Kramer <811907+gandaro@users.noreply.github.com> Date: Thu, 28 Sep 2017 20:45:09 +0200 Subject: Update German translation (#5133) Create activerecord.de.yml (50%) Update devise.de.yml Update doorkeeper.de.yml (100%) Update simple_form.de.yml (100%) --- app/javascript/mastodon/locales/de.json | 2 +- config/locales/activerecord.de.yml | 13 +++++ config/locales/de.yml | 6 +- config/locales/devise.de.yml | 34 ++++++------ config/locales/doorkeeper.de.yml | 98 +++++++++++++++++---------------- config/locales/simple_form.de.yml | 35 ++++++++---- 6 files changed, 111 insertions(+), 77 deletions(-) create mode 100644 config/locales/activerecord.de.yml (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index c892cc49b..68bd79b48 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -103,7 +103,7 @@ "lightbox.next": "Weiter", "lightbox.previous": "Zurück", "loading_indicator.label": "Lade …", - "media_gallery.toggle_visible": "Sichtbarkeit einstellen", + "media_gallery.toggle_visible": "Sichtbarkeit umschalten", "missing_indicator.label": "Nicht gefunden", "navigation_bar.blocks": "Blockierte Profile", "navigation_bar.community_timeline": "Lokale Zeitleiste", diff --git a/config/locales/activerecord.de.yml b/config/locales/activerecord.de.yml new file mode 100644 index 000000000..668abe2a3 --- /dev/null +++ b/config/locales/activerecord.de.yml @@ -0,0 +1,13 @@ +--- +de: + activerecord: + errors: + models: + account: + attributes: + username: + invalid: nur Buchstaben, Ziffern und Unterstriche + status: + attributes: + reblog: + taken: of status already exists diff --git a/config/locales/de.yml b/config/locales/de.yml index de6c86737..b67808157 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -277,9 +277,9 @@ de: public: Öffentlich unlisted: Öffentlich, aber nicht auf der öffentlichen Zeitleiste anzeigen stream_entries: - click_to_show: Klicken um zu zeigen + click_to_show: Klicken, um zu zeigen reblogged: teilte - sensitive_content: Sensible Inhalte + sensitive_content: Heikle Inhalte time: formats: default: "%d.%m.%Y %H:%M" @@ -290,7 +290,7 @@ de: enable: Aktivieren enabled_success: Zwei-Faktor-Authentisierung erfolgreich aktiviert generate_recovery_codes: Wiederherstellungscodes generieren - instructions_html: "Lese diesen QR-Code mit Google Authenticator oder einer ähnlichen TOTP-App auf deinem Telefon ein. Von nun an wird diese App Tokens generieren, die du beim Anmelden eingeben musst." + instructions_html: "Lese diesen QR-Code mit Google Authenticator oder einer ähnlichen TOTP-App auf deinem Telefon ein. Von nun an wird diese App Tokens generieren, die du beim Anmelden eingeben musst." lost_recovery_codes: Wiederherstellungscodes erlauben dir, wieder den Zugang zu deinem Konto zu erlangen, falls du dein Telefon verlierst. Wenn du deine Wiederherstellungscodes verloren hast, kannst du sie hier regenerieren. Deine alten Wiederherstellungscodes werden damit ungültig gemacht. manual_instructions: 'Wenn du den QR-Code nicht einlesen kannst und ihn manuell eingeben musst, ist hier das Klartext-Geheimnis:' recovery_codes_regenerated: Wiederherstellungscodes erfolgreich regeneriert diff --git a/config/locales/devise.de.yml b/config/locales/devise.de.yml index 035a4713c..318263e05 100644 --- a/config/locales/devise.de.yml +++ b/config/locales/devise.de.yml @@ -2,31 +2,31 @@ de: devise: confirmations: - confirmed: Vielen Dank für deine Registrierung. Bitte melde dich jetzt an. - send_instructions: Du erhältst in wenigen Minuten eine E-Mail, mit der du deine Registrierung bestätigen kannst. - send_paranoid_instructions: Falls Deine E-Mail-Adresse in unserer Datenbank existiert, erhältst Du in wenigen Minuten eine E-Mail mit der du deine Registrierung bestätigen kannst. + confirmed: Deine E-Mail-Adresse wurde bestätigt. + send_instructions: Du erhältst in wenigen Minuten eine E-Mail, mit der du deine E-Mail-Adresse bestätigen kannst. Schau bitte auch in deinen Spam-Ordner! + send_paranoid_instructions: Falls deine E-Mail-Adresse in unserer Datenbank hinterlegt ist, erhältst du in wenigen Minuten eine E-Mail, mit der du deine E-Mail-Adresse bestätigen kannst. Schau bitte auch in deinen Spam-Ordner! failure: already_authenticated: Du bist bereits angemeldet. - inactive: Dein Account ist nicht aktiv. - invalid: Ungültige Anmeldedaten. - last_attempt: Du hast noch einen Versuch bevor dein Account gesperrt wird. - locked: Dein Account ist gesperrt. - not_found_in_database: E-Mail-Adresse oder Passwort ungültig. - timeout: Deine Sitzung ist abgelaufen, bitte melde dich erneut an. - unauthenticated: Du musst Dich anmelden oder registrieren, bevor du fortfahren kannst. - unconfirmed: Du musst deinen Account bestätigen, bevor du fortfahren kannst. + inactive: Dein Konto wurde noch nicht aktiviert. + invalid: '%{authentication_keys} oder Passwort ungültig.' + last_attempt: Du hast noch einen Versuch, bevor dein Konto gesperrt wird. + locked: Dein Konto ist gesperrt. + not_found_in_database: '%{authentication_keys} oder Passwort ungültig.' + timeout: Deine Sitzung ist abgelaufen. Bitte melde dich erneut an. + unauthenticated: Du musst dich anmelden oder registrieren, bevor du fortfahren kannst. + unconfirmed: Du musst deine E-Mail-Adresse bestätigen, bevor du fortfahren kannst. mailer: confirmation_instructions: - subject: 'Mastodon: Anleitung zur Bestätigung deines Accounts' + subject: 'Mastodon: Bestätigung deines Kontos bei %{instance}' password_change: - subject: 'Mastodon: Passwort wurde geändert' + subject: 'Mastodon: Passwort geändert' reset_password_instructions: - subject: 'Mastodon: Anleitung um dein Passwort zurückzusetzen' + subject: 'Mastodon: Passwort zurücksetzen' unlock_instructions: - subject: 'Mastodon: Anleitung um deinen Account freizuschalten' + subject: 'Mastodon: Konto entsperren' omniauth_callbacks: - failure: Du konntest nicht mit deinem %{kind}-Account angemeldet werden, weil '%{reason}'. - success: Du hast dich erfolgreich mit Deinem %{kind}-Account angemeldet. + failure: Du konntest nicht mit deinem %{kind}-Konto angemeldet werden, weil '%{reason}'. + success: Du hast dich erfolgreich mit deinem %{kind}-Account angemeldet. passwords: no_token: Du kannst diese Seite nur über den Link aus der E-Mail zum Passwort-Zurücksetzen aufrufen. Wenn du einen solchen Link aufgerufen hast, stelle bitte sicher, dass du die vollständige Adresse aufrufst. send_instructions: Du erhältst in wenigen Minuten eine E-Mail mit der Anleitung, wie du dein Passwort zurücksetzen kannst. diff --git a/config/locales/doorkeeper.de.yml b/config/locales/doorkeeper.de.yml index 1588e4f9e..d7d98c6d6 100644 --- a/config/locales/doorkeeper.de.yml +++ b/config/locales/doorkeeper.de.yml @@ -3,17 +3,19 @@ de: activerecord: attributes: doorkeeper/application: - name: Name - redirect_uri: Redirect-URI + name: Name der Anwendung + redirect_uri: Weiterleitungs-URI + scopes: Befugnisse + website: Website der Anwendung errors: models: doorkeeper/application: attributes: redirect_uri: fragment_present: darf kein Fragment enthalten. - invalid_uri: muss ein valider URI (Identifier) sein. - relative_uri: muss ein absoluter URI (Identifier) sein. - secured_uri: muss ein HTTPS/SSL-URI (Identifier) sein. + invalid_uri: muss ein valider URI sein. + relative_uri: muss ein absoluter URI sein. + secured_uri: muss ein HTTPS/SSL-URI sein. doorkeeper: applications: buttons: @@ -25,27 +27,31 @@ de: confirmations: destroy: Bist du sicher? edit: - title: Applikation bearbeiten + title: Anwendung bearbeiten form: error: Hoppla! Bitte überprüfe das Formular auf Fehler! help: native_redirect_uri: "%{native_redirect_uri} für lokale Tests benutzen" redirect_uri: Bitte benutze eine Zeile pro URI - scopes: Bitte die "Scopes" mit Leerzeichen trennen. Bitte frei lassen für die Verwendung der Default-Werte. + scopes: Bitte die Befugnisse mit Leerzeichen trennen. Zur Verwendung der Standardwerte freilassen. index: + application: Anwendung callback_url: Callback-URL + delete: Löschen name: Name - new: Neue Applikation - title: Deine Applikationen + new: Neue Anwendung + scopes: Befugnisse + show: Zeigen + title: Deine Anwendungen new: - title: Neue Applikation + title: Neue Anwendung show: actions: Aktionen - application_id: Applikations-ID + application_id: Client-Schlüssel callback_urls: Callback-URLs - scopes: Scopes - secret: Secret - title: 'Applikation: %{name}' + scopes: Befugnisse + secret: Client-Secret + title: 'Anwendung: %{name}' authorizations: buttons: authorize: Autorisieren @@ -53,61 +59,61 @@ de: error: title: Ein Fehler ist aufgetreten new: - able_to: 'Diese Anwendung wird folgende Rechte haben:' - prompt: Soll %{client_name} für die Benutzung dieses Accounts autorisiert werden? + able_to: 'Sie wird folgende Befugnisse haben:' + prompt: Die Anwendung %{client_name} verlangt Zugriff auf dein Konto title: Autorisierung erforderlich show: - title: Copy this authorization code and paste it to the application. + title: Kopiere diesen Autorisierungs-Code und füge ihn in die Anwendung ein. authorized_applications: buttons: - revoke: Ungültig machen + revoke: Widerrufen confirmations: revoke: Bist du sicher? index: - application: Applikation - created_at: erstellt am - date_format: "%Y-%m-%d %H:%M:%S" - scopes: Scopes - title: Deine autorisierten Applikationen + application: Anwendung + created_at: autorisiert am + date_format: "%d.%m.%Y %H:%M:%S" + scopes: Befugnisse + title: Deine autorisierten Anwendungen errors: messages: - access_denied: Der Ressourcenbesitzer oder der Autorisierungs-Server hat die Anfrage verweigert. - credential_flow_not_configured: 'Die Prozedur "Resource Owner Password Credentials" ist fehlgeschlagen: Doorkeeper.configure.resource_owner_from_credentials ist nicht konfiguriert.' - invalid_client: 'Client-Autorisierung MKIM ist fehlgeschlagen: Unbekannter Client, keine Autorisierung mitgeliefert oder Autorisierungsmethode nicht unterstützt.' - invalid_grant: Die bereitgestellte Autorisierung ist inkorrekt, abgelaufen, widerrufen, ist mit einem anderen Client verknüpft oder der Redirection URI stimmt nicht mit der Autorisierungs-Anfrage überein. - invalid_redirect_uri: Der Redirect-URI in der Anfrage ist ungültig. - invalid_request: Die Anfrage enthält einen nicht-unterstützten Parameter, ein Parameter fehlt oder sie ist anderweitig fehlerhaft. - invalid_resource_owner: Die angegebenen Zugangsdaten für den "Resource Owner" sind inkorrekt oder dieses Profil existiert nicht. - invalid_scope: Der angeforderte Scope ist inkorrekt, unbekannt oder fehlerhaft. + access_denied: Der »resource owner« oder der Autorisierungs-Server hat die Anfrage verweigert. + credential_flow_not_configured: Die Prozedur »Resource Owner Password Credentials« schlug fehl, da Doorkeeper.configure.resource_owner_from_credentials nicht konfiguriert ist. + invalid_client: 'Client-Authentifizierung ist fehlgeschlagen: Client unbekannt, keine Authentisierung mitgeliefert oder Authentisierungsmethode wird nicht unterstützt.' + invalid_grant: Die beigefügte Autorisierung ist ungültig, abgelaufen, wurde widerrufen, einem anderen Client ausgestellt oder der Weiterleitungs-URI stimmt nicht mit der Autorisierungs-Anfrage überein. + invalid_redirect_uri: Der beigefügte Weiterleitungs-URI ist ungültig. + invalid_request: Die Anfrage enthält ein nicht-unterstütztes Argument, ein Parameter fehlt, oder sie ist anderweitig fehlerhaft. + invalid_resource_owner: Die angegebenen Zugangsdaten für den »resource owner« sind ungültig, oder dieses Profil existiert nicht. + invalid_scope: Die angeforderte Befugnis ist ungültig, unbekannt oder fehlerhaft. invalid_token: - expired: Der Zugriffstoken ist abgelaufen - revoked: Der Zugriffsoken wurde annuliert - unknown: Der Zugriffsoken ist ungültig - resource_owner_authenticator_not_configured: 'Die Prozedur "Resource Owner find" ist fehlgeschlagen: Doorkeeper.configure.resource_owner_authenticator ist nicht konfiguriert.' - server_error: Der Autorisierungs-Server hat ein unerwartetes Problem festgestellt und konnte die Anfrage nicht beenden. - temporarily_unavailable: Der Autorisierungs-Server ist derzeit auf Grund von temporärer Überlastung oder Wartungsarbeiten am Server nicht in der Lage, die Anfrage zu bearbeiten . - unauthorized_client: Der Client ist nicht autorisiert, diese Anfrage mit dieser Methode auszuführen. + expired: Der Zugriffs-Token ist abgelaufen + revoked: Der Zugriffs-Token wurde widerrufen + unknown: Der Zugriffs-Token ist ungültig + resource_owner_authenticator_not_configured: Die Prozedur »Resource Owner find« ist fehlgeschlagen, da Doorkeeper.configure.resource_owner_authenticator nicht konfiguriert ist. + server_error: Der Autorisierungs-Server hat ein unerwartetes Problem festgestellt und konnte die Anfrage nicht bearbeiten. + temporarily_unavailable: Der Autorisierungs-Server ist aufgrund von zwischenzeitlicher Überlastung oder Wartungsarbeiten derzeit nicht in der Lage, die Anfrage zu bearbeiten. + unauthorized_client: Der Client ist nicht dazu autorisiert, diese Anfrage mit dieser Methode auszuführen. unsupported_grant_type: Der Autorisierungs-Typ wird nicht vom Autorisierungs-Server unterstützt. unsupported_response_type: Der Autorisierungs-Server unterstützt diesen Antwort-Typ nicht. flash: applications: create: - notice: Applikation erstellt. + notice: Anwendung erstellt. destroy: - notice: Applikation gelöscht. + notice: Anwendung gelöscht. update: - notice: Applikation aktualisiert. + notice: Anwendung aktualisiert. authorized_applications: destroy: - notice: Applikation widerrufen. + notice: Anwendung widerrufen. layouts: admin: nav: - applications: Applikationen + applications: Anwendungen oauth2_provider: OAuth2-Anbieter application: title: OAuth-Autorisierung nötig scopes: - follow: Profil folgen, blocken, entblocken und entfolgen - read: deine Daten lesen - write: Beiträge von deinem Konto aus veröffentlichen + follow: Konten folgen, blocken, entblocken und entfolgen + read: deine Daten auslesen + write: Beiträge in deinem Namen veröffentlichen diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index a6ba839c6..0a820ff1e 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -3,15 +3,23 @@ de: simple_form: hints: defaults: - avatar: PNG, GIF oder JPG. Maximal 2MB. Wird auf 120x120px herunterskaliert - display_name: %{count} Zeichen verbleiben - header: PNG, GIF oder JPG. Maximal 2MB. Wird auf 700x335px herunterskaliert - locked: Erlaubt dir, Profile zu überprüfen, bevor sie dir folgen können - note: %{count} Zeichen verbleiben + avatar: PNG, GIF oder JPG. Maximal 2 MB. Wird auf 120×120 px herunterskaliert + display_name: + one: 1 Zeichen verbleibt + other: %{count} Zeichen verbleiben + header: PNG, GIF oder JPG. Maximal 2 MB. Wird auf 700×335 px herunterskaliert + locked: Du musst zustimmen, bevor dir jemand folgen kann + note: + one: 1 Zeichen verbleibt + other: %{count} Zeichen verbleiben + setting_noindex: Betrifft dein öffentliches Profil und Status-Seiten + setting_theme: Wirkt sich darauf aus, wie Mastodon aussieht, egal auf welchem Gerät du eingeloggt bist. imports: - data: CSV-Datei, die von einer anderen Mastodon-Instanz exportiert wurde + data: CSV-Datei, die aus einer anderen Mastodon-Instanz exportiert wurde sessions: otp: Gib den Zwei-Faktor-Authentisierungs-Code von deinem Telefon ein oder benutze einen deiner Wiederherstellungscodes. + user: + filtered_languages: Ausgewählte Sprachen werden aus deinen öffentlichen Zeitleisten entfernt. labels: defaults: avatar: Profilbild @@ -20,7 +28,8 @@ de: current_password: Derzeitiges Passwort data: Daten display_name: Anzeigename - email: E-Mail-Addresse + email: E-Mail-Adresse + filtered_languages: Gefilterte Sprachen header: Kopfbild locale: Sprache locked: Gesperrtes Profil @@ -29,13 +38,19 @@ de: otp_attempt: Zwei-Faktor-Authentisierungs-Code password: Passwort setting_auto_play_gif: Animierte GIFs automatisch abspielen - setting_boost_modal: Zeige einen Bestätigungsdialog vor dem Teilen - setting_default_privacy: Beitragsprivatspäre + setting_boost_modal: Bestätigungsdialog anzeigen, bevor ein Tröt geteilt wird + setting_default_privacy: Beitragssichtbarkeit + setting_default_sensitive: Medien immer als heikel markieren + setting_delete_modal: Bestätigungsdialog anzeigen, bevor ein Tröt gelöscht wird + setting_noindex: Suchmaschinen-Indexierung verhindern + setting_system_font_ui: Standardschriftart des Systems verwenden + setting_theme: Theme der Website + setting_unfollow_modal: Bestätigungsdialog anzeigen, bevor jemand entfolgt wird severity: Gewichtung type: Importtyp username: Profilname interactions: - must_be_follower: Benachrichtigungen von Nicht-Folgern blockieren + must_be_follower: Benachrichtigungen von Nicht-Folgenden blockieren must_be_following: Benachrichtigungen von Profilen blockieren, denen ich nicht folge notification_emails: digest: Schicke Übersichts-E-Mails -- cgit From 887cd94e963f49523af80d845cfe0ea900f7dadf Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 29 Sep 2017 02:30:00 +0200 Subject: Increase attachment descriptions to 420 characters (#5139) Blaze it --- app/javascript/mastodon/features/compose/components/upload.js | 2 +- app/models/media_attachment.rb | 4 ++-- spec/models/media_attachment_spec.rb | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js index c2bf3b72e..cd9e08360 100644 --- a/app/javascript/mastodon/features/compose/components/upload.js +++ b/app/javascript/mastodon/features/compose/components/upload.js @@ -79,7 +79,7 @@ export default class Upload extends ImmutablePureComponent { placeholder={intl.formatMessage(messages.description)} type='text' value={description} - maxLength={140} + maxLength={420} onFocus={this.handleInputFocus} onChange={this.handleInputChange} onBlur={this.handleInputBlur} diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 25e41c209..60380198b 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -59,7 +59,7 @@ class MediaAttachment < ApplicationRecord validates_attachment_size :file, less_than: 8.megabytes validates :account, presence: true - validates :description, length: { maximum: 140 }, if: :local? + validates :description, length: { maximum: 420 }, if: :local? scope :attached, -> { where.not(status_id: nil) } scope :unattached, -> { where(status_id: nil) } @@ -140,7 +140,7 @@ class MediaAttachment < ApplicationRecord end def prepare_description - self.description = description.strip[0...140] unless description.nil? + self.description = description.strip[0...420] unless description.nil? end def set_type_and_extension diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb index f20698c45..9fce5bc4f 100644 --- a/spec/models/media_attachment_spec.rb +++ b/spec/models/media_attachment_spec.rb @@ -52,9 +52,9 @@ RSpec.describe MediaAttachment, type: :model do describe 'descriptions for remote attachments' do it 'are cut off at 140 characters' do - media = Fabricate(:media_attachment, description: 'foo' * 100, remote_url: 'http://example.com/blah.jpg') + media = Fabricate(:media_attachment, description: 'foo' * 1000, remote_url: 'http://example.com/blah.jpg') - expect(media.description.size).to be <= 140 + expect(media.description.size).to be <= 420 end end end -- cgit From 1a72813b53b05420786cc70f94aefa178d9f43da Mon Sep 17 00:00:00 2001 From: Jakob Kramer <811907+gandaro@users.noreply.github.com> Date: Fri, 29 Sep 2017 21:11:28 +0200 Subject: Updated German translation (#5151) Translate "about" page, several settings pages, data export/import, sessions overview, authorized followers page, account deletion page. More consistent use of words: - A toot is a Beitrag. - An account is a Konto. Some small improvements. --- app/javascript/mastodon/locales/de.json | 12 +-- config/locales/de.yml | 163 +++++++++++++++++++++++++++----- config/locales/devise.de.yml | 26 ++--- config/locales/simple_form.de.yml | 8 +- 4 files changed, 163 insertions(+), 46 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 68bd79b48..9b340b71c 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -44,7 +44,7 @@ "column_header.unpin": "Lösen", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Einstellungen", - "compose_form.lock_disclaimer": "Dein Profil ist nicht {locked}. Jeder kann dir jederzeit folgen, um deine privaten Beiträge einzusehen.", + "compose_form.lock_disclaimer": "Dein Profil ist nicht {locked}. Wer dir folgen will, kann das jederzeit tun und dann auch deine privaten Beiträge sehen.", "compose_form.lock_disclaimer.lock": "gesperrt", "compose_form.placeholder": "Worüber möchtest du schreiben?", "compose_form.publish": "Tröt", @@ -116,10 +116,10 @@ "navigation_bar.pins": "Pinned toots", "navigation_bar.preferences": "Einstellungen", "navigation_bar.public_timeline": "Föderierte Zeitleiste", - "notification.favourite": "{name} favorisierte deinen Status", + "notification.favourite": "{name} hat deinen Beitrag favorisiert", "notification.follow": "{name} folgt dir", - "notification.mention": "{name} erwähnte dich", - "notification.reblog": "{name} teilte deinen Status", + "notification.mention": "{name} hat dich erwähnt", + "notification.reblog": "{name} hat deinen Beitrag geteilt", "notifications.clear": "Mitteilungen löschen", "notifications.clear_confirmation": "Bist du dir sicher, dass du alle Mitteilungen löschen möchtest?", "notifications.column_settings.alert": "Desktop-Benachrichtigungen", @@ -146,12 +146,12 @@ "onboarding.page_six.github": "Mastodon ist freie, quelloffene Software. Du kannst auf {github} dazu beitragen oder Probleme melden.", "onboarding.page_six.guidelines": "Richtlinien", "onboarding.page_six.read_guidelines": "Bitte mach dich mit den {guidelines} von {domain} vertraut!", - "onboarding.page_six.various_app": "mobile Anwendungen", + "onboarding.page_six.various_app": "Apps", "onboarding.page_three.profile": "Bearbeite dein Profil, um dein Bild, deinen Namen oder deine Beschreibung anzupassen. Dort findest du auch andere Einstellungen.", "onboarding.page_three.search": "Benutze die Suchfunktion, um Leute oder Themen zu finden. Zum Beispiel die Hashtags {illustration} oder {introductions}. Um eine Person zu finden, die auf einer anderen Instanz ist, benutze den vollständigen Profilnamen.", "onboarding.page_two.compose": "Schreibe Beiträge aus der Schreiben-Spalte. Du kannst Bilder und kurze Videos hochladen, Sichtbarkeits-Einstellungen ändern und Inhaltswarnungen hinzufügen.", "onboarding.skip": "Überspringen", - "privacy.change": "Sichtbarkeit des Status anpassen", + "privacy.change": "Sichtbarkeit des Beitrags anpassen", "privacy.direct.long": "Beitrag nur an erwähnte Profile", "privacy.direct.short": "Direkt", "privacy.private.long": "Beitrag nur an Folgende", diff --git a/config/locales/de.yml b/config/locales/de.yml index b67808157..06a535ba6 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1,39 +1,69 @@ --- de: about: - about_mastodon_html: Mastodon ist ein freier, quelloffener sozialer Netzwerkserver. Als dezentralisierte Alternative zu kommerziellen Plattformen verhindert es die Risiken, die entstehen, wenn eine einzelne Firma deine Kommunikation monopolisiert. Jeder kann Mastodon verwenden und ganz einfach am sozialen Netzwerk teilnehmen. + about_mastodon_html: Mastodon ist ein soziales Netzwerk. Es basiert auf offenen Web-Protokollen und freier, quelloffener Software. Es ist dezentral (so wie E-Mail!). about_this: Über diese Instanz - closed_registrations: Die Registrierung ist auf dieser Instanz momentan geschlossen. + closed_registrations: Die Registrierung auf dieser Instanz ist momentan geschlossen. Aber du kannst dein Konto auch auf einer anderen Instanz erstellen! Von dort hast du genauso Zugriff auf das Mastodon-Netzwerk. contact: Kontakt + contact_missing: Nicht angegeben + contact_unavailable: N/A description_headline: Was ist %{domain}? - domain_count_after: andere Instanzen - domain_count_before: Verbunden mit + domain_count_after: anderen Instanzen + domain_count_before: Vernetzt mit + extended_description_html: | +

Ein guter Platz für Regeln

+

Die erweiterte Beschreibung wurde noch nicht aufgesetzt.

+ features: + humane_approach_body: Mastodon hat von den Fehlern anderer Netzwerke gelernt und wurde mit dem Augenmerk darauf entwickelt, den Missbrauch sozialer Medien zu bekämpfen. + humane_approach_title: Ein menschlicherer Ansatz + not_a_product_body: Mastodon ist kein kommerzielles Netzwerk. Keine Werbung, kein Abgraben deiner Daten, keine geschlossene Plattform. Es gibt keine Zentrale. + not_a_product_title: Du bist ein Mensch und keine Ware + real_conversation_body: Mit 500 Zeichen pro Beitrag und der Ermöglichung präziser Inhalts- und Bilderwarnungen kannst du dich so ausdrücken, wie du es möchtest. + real_conversation_title: Für das echte Gespräch gemacht + within_reach_body: Verschiedene Apps für iOS, Android und andere Plattformen erlauben dir dank unserem blühenden API-Ökosystem, dich von überall auf dem Laufenden zu halten. + within_reach_title: Immer für dich da + find_another_instance: Eine andere Instanz finden + generic_description: "%{domain} ist ein Server im Netzwerk" + hosted_on: Mastodon, beherbergt auf %{domain} + learn_more: Mehr erfahren other_instances: Andere Instanzen source_code: Quellcode status_count_after: Beiträge verfassten status_count_before: die - user_count_after: Profile - user_count_before: Heimat für + user_count_after: Wesen + user_count_before: Zuhause für + what_is_mastodon: Was ist Mastodon? accounts: follow: Folgen followers: Folgende following: Folgt + media: Medien nothing_here: Hier gibt es nichts! people_followed_by: Profile, denen %{name} folgt people_who_follow: Profile, die %{name} folgen posts: Beiträge + posts_with_replies: Beiträge mit Antworten remote_follow: Folgen + reserved_username: Dieser Profilname ist belegt + roles: + admin: Admin unfollow: Entfolgen admin: accounts: are_you_sure: Bist du sicher? + confirm: Bestätigen + confirmed: Bestätigt + disable_two_factor_authentication: 2FA abschalten display_name: Anzeigename domain: Domain edit: Bearbeiten email: E-Mail feed_url: Feed-URL followers: Folgende + followers_url: Followers URL follows: Folgt + inbox_url: Inbox URL + ip: IP-Adresse location: all: Alle local: Lokal @@ -51,22 +81,31 @@ de: order: alphabetic: Alphabetisch most_recent: Neueste - title: Reihenfolge - perform_full_suspension: Führe vollständige Sperre durch + title: Sortierung + outbox_url: Outbox URL + perform_full_suspension: Vollständige Sperre durchführen profile_url: Profil-URL + protocol: Protokoll public: Öffentlich push_subscription_expires: PuSH-Abonnement läuft aus + redownload: Avatar neu laden + reset: Zurücksetzen reset_password: Passwort zurücksetzen + resubscribe: Wieder abonnieren salmon_url: Salmon-URL + search: Suche + shared_inbox_url: Shared Inbox URL show: created_reports: Meldungen durch dieses Konto report: Meldung targeted_reports: Meldungen über dieses Konto silence: Stummschalten statuses: Beiträge + subscribe: Abonnieren title: Konten undo_silenced: Stummschaltung zurücknehmen undo_suspension: Sperre zurücknehmen + unsubscribe: Abbestellen username: Profilname web: Web domain_blocks: @@ -76,9 +115,9 @@ de: domain: Domain new: create: Blockade einrichten - hint: Die Domain-Blockade wird nicht die Erstellung von Konteneinträgen in der Datenbank verhindern, aber rückwirkend und automatisch alle Moderationsmethoden auf diese Accounts anwenden. + hint: Die Domain-Blockade wird nicht die Erstellung von Konteneinträgen in der Datenbank verhindern, aber rückwirkend und automatisch alle Moderationsmethoden auf diese Konten anwenden. severity: - desc_html: "Stummschaltung wird die Beiträge dieses Accounts für alle, die ihm nicht folgen, unsichtbar machen. Eine Sperre wird alle Beiträge, Medien und Profildaten dieses Accounts entfernen." + desc_html: "Stummschaltung wird die Beiträge dieses Kontos für alle, die ihm nicht folgen, unsichtbar machen. Eine Sperre wird alle Beiträge, Medien und Profildaten dieses Kontos entfernen." silence: Stummschaltung suspend: Sperre title: Neue Domain-Blockade @@ -114,9 +153,9 @@ de: reported_account: Gemeldetes Konto reported_by: Gemeldet von resolved: Gelöst - silence_account: Account stummschalten + silence_account: Konto stummschalten status: Status - suspend_account: Account sperren + suspend_account: Konto sperren target: Ziel title: Meldungen unresolved: Ungelöst @@ -154,13 +193,17 @@ de: applications: invalid_url: Die angegebene URL ist ungültig auth: - change_password: Passwort ändern - didnt_get_confirmation: Keine Bestätigung bekommen? + agreement_html: Indem du dich registrierst, erklärst du dich mit unseren Geschäftsbedingungen und der Datenschutzerklärung einverstanden. + change_password: Sicherheit + delete_account: Konto löschen + delete_account_html: Falls du dein Konto löschen willst, kannst du hier damit fortfahren. Du wirst um Bestätigung gebeten werden. + didnt_get_confirmation: Keine Bestätigungs-Mail erhalten? forgot_password: Passwort vergessen? + invalid_reset_password_token: Das Token zum Zurücksetzen des Passworts ist ungültig oder abgelaufen. Bitte fordere ein neues an. login: Anmelden logout: Abmelden register: Registrieren - resend_confirmation: Bestätigung nochmal versenden + resend_confirmation: Bestätigungs-Mail erneut versenden reset_password: Passwort zurücksetzen set_new_password: Neues Passwort setzen authorize_follow: @@ -181,6 +224,14 @@ de: x_minutes: "%{count}m" x_months: "%{count}mo" x_seconds: "%{count}s" + deletes: + bad_password_msg: Falsches Passwort + confirm_password: Gib dein derzeitiges Passwort ein, um deine Identität zu bestätigen + description_html: Hiermit wird dauerhaft und unwiederbringlich der Inhalt deines Kontos gelöscht und dein Konto deaktiviert. Dein Profilname wird reserviert, um künftige Imitationen zu verhindern. + proceed: Konto löschen + success_msg: Dein Konto wurde erfolgreich gelöscht + warning_html: Wir können nur dafür garantieren, dass die Inhalte auf dieser einen Instanz gelöscht werden. Bei Inhalten, die weit verbreitet wurden, ist es wahrscheinlich, dass Spuren bleiben werden. Server, die offline sind oder keine Benachrichtigungen von deinem Konto mehr empfangen, werden ihre Datenbanken nicht bereinigen. + warning_title: Verfügbarkeit verstreuter Inhalte errors: '404': Die Seite, die du gesucht hast, existiert nicht. '410': Die Seite, die du gesucht hast, existiert nicht mehr. @@ -188,11 +239,23 @@ de: content: Sicherheitsüberprüfung fehlgeschlagen. Blockierst du Cookies? title: Sicherheitsüberprüfung fehlgeschlagen exports: - blocks: Du blockierst + blocks: Du hast blockiert csv: CSV follows: Du folgst - mutes: Du schaltest stumm + mutes: Du hast stummgeschaltet storage: Medienspeicher + followers: + domain: Instanz + explanation_html: Wenn du sicherstellen willst, dass deine Beiträge privat sind, musst du wissen, wer dir folgt. Deine privaten Beiträge werden an alle Instanzen weitergegeben, auf denen Menschen registriert sind, die dir folgen. Wenn du den Betreibenden einer Instanz misstraust und du befürchtest, dass sie deine Privatsphäre missachten könnten, kannst du sie hier entfernen. + followers_count: Zahl der Folgenden + lock_link: dein Konto sperrst + purge: Von der Liste deiner Folgenden löschen + success: + one: Folgende von einer Domain werden soft-geblockt … + other: Folgende von %{count} Domains werden soft-geblockt … + true_privacy_html: Bitte beachte, dass wirklicher Schutz deiner Privatsphäre nur durch Ende-zu-Ende-Verschlüsselung erreicht werden kann.. + unlocked_warning_html: Wer dir folgen will, kann dies jederzeit ohne deine vorige Einverständnis tun und erhält damit automatisch Zugriff auf deine privaten Beiträge. Wenn du %{lock_link}, kannst du vorab entscheiden, wer dir folgen darf und wer nicht. + unlocked_warning_title: Dein Konto ist nicht gesperrt generic: changes_saved_msg: Änderungen gespeichert! powered_by: angetrieben von %{link} @@ -201,8 +264,8 @@ de: one: Etwas ist noch nicht ganz richtig! Bitte korrigiere den Fehler other: Etwas ist noch nicht ganz richtig! Bitte korrigiere %{count} Fehler imports: - preface: Du kannst bestimmte Daten wie die Leute, denen du folgst oder die du blockierst, in dein Konto auf dieser Instanz aus einem Export von einer anderen importieren. - success: Deine Daten wurden erfolgreich hochgeladen und werden in Kürze verabeitet + preface: Daten, die du aus einer anderen Instanz exportiert hast, kannst du hier importieren. Beispielsweise die Liste derjenigen, denen du folgst oder die du blockiert hast. + success: Deine Daten wurden erfolgreich hochgeladen und werden in Kürze verarbeitet types: blocking: Blockierliste following: Folgeliste @@ -259,33 +322,87 @@ de: missing_resource: Die erforderliche Weiterleitungs-URL konnte leider in deinem Profil nicht gefunden werden. proceed: Weiter prompt: 'Du wirst dieser Person folgen:' + sessions: + activity: Letzte Aktivität + browser: Browser + browsers: + alipay: Alipay + blackberry: Blackberry + chrome: Chrome + edge: Microsoft Edge + firefox: Firefox + generic: Unbekannter Browser + ie: Internet Explorer + micro_messenger: MicroMessenger + nokia: Nokia S40 Ovi Browser + opera: Opera + phantom_js: PhantomJS + qq: QQ Browser + safari: Safari + uc_browser: UCBrowser + weibo: Weibo + current_session: Aktuelle Sitzung + description: "%{browser} auf %{platform}" + explanation: Dies sind die Webbrowser, die derzeit in dein Mastodon-Konto eingeloggt sind. + ip: IP-Adresse + platforms: + adobe_air: Adobe Air + android: Android + blackberry: Blackberry + chrome_os: ChromeOS + firefox_os: Firefox OS + ios: iOS + linux: Linux + mac: Mac + other: unbekannter Plattform + windows: Windows + windows_mobile: Windows Mobile + windows_phone: Windows Phone + revoke: Schließen + revoke_success: Sitzung erfolgreich geschlossen + title: Sitzungen settings: authorized_apps: Autorisierte Anwendungen back: Zurück zu Mastodon + delete: Konto löschen + development: Entwicklung edit_profile: Profil bearbeiten export: Datenexport + followers: Autorisierte Folgende import: Datenimport preferences: Einstellungen settings: Einstellungen two_factor_authentication: Zwei-Faktor-Authentisierung + your_apps: Deine Anwendungen statuses: open_in_web: Im Web öffnen over_character_limit: Zeichenlimit von %{max} überschritten + pin_errors: + limit: Du kannst nicht noch mehr Beiträge anheften + ownership: Du kannst nur eigene Beiträge anheften + private: Du kannst nur öffentliche Beiträge anheften + reblog: Du kannst keine geteilten Beiträge anheften show_more: Mehr anzeigen visibilities: - private: Nur Folgenden zeigen + private: Nur Folgende + private_long: Nur für Folgende sichtbar public: Öffentlich - unlisted: Öffentlich, aber nicht auf der öffentlichen Zeitleiste anzeigen + public_long: Für alle sichtbar + unlisted: Nicht gelistet + unlisted: Für alle sichtbar, aber nicht in öffentlichen Zeitleisten aufgelistet stream_entries: click_to_show: Klicken, um zu zeigen + pinned: Angehefteter Beitrag reblogged: teilte sensitive_content: Heikle Inhalte + themes: + default: Mastodon time: formats: default: "%d.%m.%Y %H:%M" two_factor_authentication: code_hint: Gib den Code, den deine Authenticator-App generiert hat, zur Bestätigung an - description_html: Wenn du Zwei-Faktor-Authentisierung aktivierst, wirst du dein Telefon zum Anmelden benötigen, welches Tokens für dich generiert, die du eingeben musst. + description_html: Wenn du Zwei-Faktor-Authentisierung (2FA) aktivierst, wirst du dein Telefon zum Anmelden benötigen. Darauf werden Tokens erzeugt, die du eingeben musst. disable: Deaktivieren enable: Aktivieren enabled_success: Zwei-Faktor-Authentisierung erfolgreich aktiviert @@ -298,5 +415,5 @@ de: setup: Einrichten wrong_code: Der eingegebene Code war ungültig! Sind die Server- und die Gerätezeit korrekt? users: - invalid_email: Ungültige E-Mail-Addresse + invalid_email: Ungültige E-Mail-Adresse invalid_otp_token: Ungültiger Zwei-Faktor-Authentisierungs-Code diff --git a/config/locales/devise.de.yml b/config/locales/devise.de.yml index 318263e05..b1e26f1e5 100644 --- a/config/locales/devise.de.yml +++ b/config/locales/devise.de.yml @@ -3,8 +3,8 @@ de: devise: confirmations: confirmed: Deine E-Mail-Adresse wurde bestätigt. - send_instructions: Du erhältst in wenigen Minuten eine E-Mail, mit der du deine E-Mail-Adresse bestätigen kannst. Schau bitte auch in deinen Spam-Ordner! - send_paranoid_instructions: Falls deine E-Mail-Adresse in unserer Datenbank hinterlegt ist, erhältst du in wenigen Minuten eine E-Mail, mit der du deine E-Mail-Adresse bestätigen kannst. Schau bitte auch in deinen Spam-Ordner! + send_instructions: Du erhältst in wenigen Minuten eine E-Mail. Darin wird erklärt, wie du deine E-Mail-Adresse bestätigen kannst. Schau bitte auch in deinen Spam-Ordner! + send_paranoid_instructions: Falls deine E-Mail-Adresse in unserer Datenbank hinterlegt ist, erhältst du in wenigen Minuten eine E-Mail. Darin wird erklärt, wie du deine E-Mail-Adresse bestätigen kannst. Schau bitte auch in deinen Spam-Ordner! failure: already_authenticated: Du bist bereits angemeldet. inactive: Dein Konto wurde noch nicht aktiviert. @@ -26,29 +26,29 @@ de: subject: 'Mastodon: Konto entsperren' omniauth_callbacks: failure: Du konntest nicht mit deinem %{kind}-Konto angemeldet werden, weil '%{reason}'. - success: Du hast dich erfolgreich mit deinem %{kind}-Account angemeldet. + success: Du hast dich erfolgreich mit deinem %{kind}-Konto angemeldet. passwords: no_token: Du kannst diese Seite nur über den Link aus der E-Mail zum Passwort-Zurücksetzen aufrufen. Wenn du einen solchen Link aufgerufen hast, stelle bitte sicher, dass du die vollständige Adresse aufrufst. - send_instructions: Du erhältst in wenigen Minuten eine E-Mail mit der Anleitung, wie du dein Passwort zurücksetzen kannst. - send_paranoid_instructions: Falls deine E-Mail-Adresse in unserer Datenbank existiert erhältst du in wenigen Minuten eine E-Mail mit der Anleitung, wie du dein Passwort zurücksetzen kannst. + send_instructions: Du erhältst in wenigen Minuten eine E-Mail. Darin wird erklärt, wie du dein Passwort zurücksetzen kannst. + send_paranoid_instructions: Falls deine E-Mail-Adresse in unserer Datenbank existiert, erhältst du in wenigen Minuten eine E-Mail. Darin wird erklärt, wie du dein Passwort zurücksetzen kannst. updated: Dein Passwort wurde geändert. Du bist jetzt angemeldet. updated_not_active: Dein Passwort wurde geändert. registrations: - destroyed: Dein Account wurde gelöscht. + destroyed: Dein Konto wurde gelöscht. signed_up: Du hast dich erfolgreich registriert. - signed_up_but_inactive: Du hast dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Account inaktiv ist. - signed_up_but_locked: Du hast dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Account gesperrt ist. - signed_up_but_unconfirmed: Du hast Dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Account noch nicht bestätigt ist. Du erhältst in Kürze eine E-Mail mit der Anleitung, wie Du Deinen Account freischalten kannst. - update_needs_confirmation: Deine Daten wurden aktualisiert, aber du musst deine neue E-Mail-Adresse bestätigen. Du erhälst in wenigen Minuten eine E-Mail, mit der du die Änderung deiner E-Mail-Adresse abschließen kannst. + signed_up_but_inactive: Du hast dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Konto inaktiv ist. + signed_up_but_locked: Du hast dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Konto gesperrt ist. + signed_up_but_unconfirmed: Du hast dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Konto noch nicht bestätigt ist. Du erhältst in Kürze eine E-Mail. Darin ist erklärt, wie du dein Konto freischalten kannst. + update_needs_confirmation: Deine Daten wurden aktualisiert, aber du musst deine neue E-Mail-Adresse bestätigen. Du erhältst in wenigen Minuten eine E-Mail. Darin ist erklärt, wie du die Änderung deiner E-Mail-Adresse abschließen kannst. updated: Deine Daten wurden aktualisiert. sessions: already_signed_out: Erfolgreich abgemeldet. signed_in: Erfolgreich angemeldet. signed_out: Erfolgreich abgemeldet. unlocks: - send_instructions: Du erhältst in wenigen Minuten eine E-Mail mit der Anleitung, wie du deinen Account entsperren können. - send_paranoid_instructions: Falls deine E-Mail-Adresse in unserer Datenbank existiert erhältst du in wenigen Minuten eine E-Mail mit der Anleitung, wie du deinen Account entsperren kannst. - unlocked: Dein Account wurde entsperrt. Du bist jetzt angemeldet. + send_instructions: Du erhältst in wenigen Minuten eine E-Mail. Darin wird erklärt, wie du dein Konto entsperren kannst. + send_paranoid_instructions: Falls deine E-Mail-Adresse in unserer Datenbank hinterlegt ist, erhältst du in wenigen Minuten eine E-Mail. Darin wird erklärt, wie du dein Konto entsperren kannst. + unlocked: Dein Konto wurde entsperrt. Du bist jetzt angemeldet. errors: messages: already_confirmed: wurde bereits bestätigt. diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 0a820ff1e..2fc353b6c 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -8,11 +8,11 @@ de: one: 1 Zeichen verbleibt other: %{count} Zeichen verbleiben header: PNG, GIF oder JPG. Maximal 2 MB. Wird auf 700×335 px herunterskaliert - locked: Du musst zustimmen, bevor dir jemand folgen kann + locked: Wer dir folgen möchte, muss um deine Erlaubnis bitten note: one: 1 Zeichen verbleibt other: %{count} Zeichen verbleiben - setting_noindex: Betrifft dein öffentliches Profil und Status-Seiten + setting_noindex: Betrifft dein öffentliches Profil und deine Beiträge setting_theme: Wirkt sich darauf aus, wie Mastodon aussieht, egal auf welchem Gerät du eingeloggt bist. imports: data: CSV-Datei, die aus einer anderen Mastodon-Instanz exportiert wurde @@ -38,10 +38,10 @@ de: otp_attempt: Zwei-Faktor-Authentisierungs-Code password: Passwort setting_auto_play_gif: Animierte GIFs automatisch abspielen - setting_boost_modal: Bestätigungsdialog anzeigen, bevor ein Tröt geteilt wird + setting_boost_modal: Bestätigungsdialog anzeigen, bevor ein Beitrag geteilt wird setting_default_privacy: Beitragssichtbarkeit setting_default_sensitive: Medien immer als heikel markieren - setting_delete_modal: Bestätigungsdialog anzeigen, bevor ein Tröt gelöscht wird + setting_delete_modal: Bestätigungsdialog anzeigen, bevor ein Beitrag gelöscht wird setting_noindex: Suchmaschinen-Indexierung verhindern setting_system_font_ui: Standardschriftart des Systems verwenden setting_theme: Theme der Website -- cgit From 0060f988478e54cb1b54b255d06376fd9fa265b1 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Fri, 29 Sep 2017 13:46:43 -0700 Subject: Remove react-sizeme (#5143) * Remove react-sizeme * Fix aspect ratio in "sensitive" mode --- .../mastodon/components/media_gallery.js | 41 ++++++++++++++++------ package.json | 1 - yarn.lock | 18 ---------- 3 files changed, 30 insertions(+), 30 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index 38b26b1fc..e7f14a7db 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -6,7 +6,6 @@ import IconButton from './icon_button'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { isIOS } from '../is_mobile'; import classNames from 'classnames'; -import sizeMe from 'react-sizeme'; const messages = defineMessages({ toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }, @@ -172,7 +171,6 @@ class Item extends React.PureComponent { } @injectIntl -@sizeMe({}) export default class MediaGallery extends React.PureComponent { static propTypes = { @@ -209,21 +207,42 @@ export default class MediaGallery extends React.PureComponent { this.props.onOpenMedia(this.props.media, index); } + handleRef = (node) => { + if (node && this.isStandaloneEligible()) { + // offsetWidth triggers a layout, so only calculate when we need to + this.setState({ + width: node.offsetWidth, + }); + } + } + + isStandaloneEligible() { + const { media, standalone } = this.props; + return standalone && media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']); + } + render () { - const { media, intl, sensitive, height, standalone, size } = this.props; + const { media, intl, sensitive, height } = this.props; + const { width, visible } = this.state; let children; - const standaloneEligible = standalone && size.width && media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']); const style = {}; - if (standaloneEligible) { - style.height = size.width / media.getIn([0, 'meta', 'small', 'aspect']); + if (this.isStandaloneEligible()) { + if (!visible && width) { + // only need to forcibly set the height in "sensitive" mode + style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']); + } else { + // layout automatically, using image's natural aspect ratio + style.height = ''; + } } else { + // crop the image style.height = height; } - if (!this.state.visible) { + if (!visible) { let warning; if (sensitive) { @@ -233,7 +252,7 @@ export default class MediaGallery extends React.PureComponent { } children = ( - @@ -241,7 +260,7 @@ export default class MediaGallery extends React.PureComponent { } else { const size = media.take(4).size; - if (standaloneEligible) { + if (this.isStandaloneEligible()) { children = ; } else { children = media.take(4).map((attachment, i) => ); @@ -250,8 +269,8 @@ export default class MediaGallery extends React.PureComponent { return (
-
- +
+
{children} diff --git a/package.json b/package.json index 7835a0440..be9b90875 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,6 @@ "react-router-dom": "^4.1.1", "react-router-scroll": "ytase/react-router-scroll#build", "react-simple-dropdown": "^3.0.0", - "react-sizeme": "^2.3.5", "react-swipeable-views": "^0.12.3", "react-textarea-autosize": "^5.0.7", "react-toggle": "^4.0.1", diff --git a/yarn.lock b/yarn.lock index 640d06a10..7b8328805 100644 --- a/yarn.lock +++ b/yarn.lock @@ -982,10 +982,6 @@ base64-js@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" -batch-processor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/batch-processor/-/batch-processor-1.0.0.tgz#75c95c32b748e0850d10c2b168f6bdbe9891ace8" - batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" @@ -2057,12 +2053,6 @@ electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.14: version "1.3.15" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.15.tgz#08397934891cbcfaebbd18b82a95b5a481138369" -element-resize-detector@^1.1.12: - version "1.1.12" - resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.1.12.tgz#8b3fd6eedda17f9c00b360a0ea2df9927ae80ba2" - dependencies: - batch-processor "^1.0.0" - elliptic@^6.0.0: version "6.4.0" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" @@ -5423,14 +5413,6 @@ react-simple-dropdown@^3.0.0: classnames "^2.1.2" prop-types "^15.5.8" -react-sizeme@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-2.3.5.tgz#f14c0a15f9b24d7b8b6f196871b0af19aa01a422" - dependencies: - element-resize-detector "^1.1.12" - invariant "^2.2.2" - lodash "^4.17.4" - react-swipeable-views-core@^0.11.1: version "0.11.1" resolved "https://registry.yarnpkg.com/react-swipeable-views-core/-/react-swipeable-views-core-0.11.1.tgz#61d046799f90725bbf91a0eb3abcab805c774cac" -- cgit From ebb8c8920795a31a3188d39b926a5074bb8b69cf Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 30 Sep 2017 04:29:56 +0200 Subject: Upgrade to React 16 (#5119) * Upgrade to React 16.0.0 * Disable some uncritical tests while chai-enzyme remains incompatible --- .../mastodon/components/column_header.js | 4 +- app/javascript/mastodon/containers/mastodon.js | 2 +- app/javascript/mastodon/features/ui/index.js | 2 +- app/javascript/mastodon/performance.js | 4 +- package.json | 18 +- spec/javascript/components/avatar.test.js | 14 +- spec/javascript/components/avatar_overlay.test.js | 10 +- spec/javascript/components/button.test.js | 23 +- spec/javascript/components/display_name.test.js | 7 +- spec/javascript/setup.js | 8 +- yarn.lock | 1049 +++++++++++++------- 11 files changed, 720 insertions(+), 421 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js index 05e1de705..e4fa8fa7a 100644 --- a/app/javascript/mastodon/components/column_header.js +++ b/app/javascript/mastodon/components/column_header.js @@ -135,7 +135,7 @@ export default class ColumnHeader extends React.PureComponent { return (
-

+

{title} @@ -145,7 +145,7 @@ export default class ColumnHeader extends React.PureComponent {

-
+
{(!collapsed || animating) && collapsedContent}
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index 884fc161a..31167cbd8 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -5,7 +5,7 @@ import configureStore from '../store/configureStore'; import { showOnboardingOnce } from '../actions/onboarding'; import BrowserRouter from 'react-router-dom/BrowserRouter'; import Route from 'react-router-dom/Route'; -import ScrollContext from 'react-router-scroll/lib/ScrollBehaviorContext'; +import { ScrollContext } from 'react-router-scroll'; import UI from '../features/ui'; import { hydrateStore } from '../actions/store'; import { connectUserStream } from '../actions/streaming'; diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 2a55cfb4c..0e4796fcb 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -48,7 +48,7 @@ const mapStateToProps = state => ({ @connect(mapStateToProps) @withRouter -export default class UI extends React.PureComponent { +export default class UI extends React.Component { static contextTypes = { router: PropTypes.object.isRequired, diff --git a/app/javascript/mastodon/performance.js b/app/javascript/mastodon/performance.js index 396c605e4..450a90626 100644 --- a/app/javascript/mastodon/performance.js +++ b/app/javascript/mastodon/performance.js @@ -14,8 +14,8 @@ if (process.env.NODE_ENV === 'development') { } marky = require('marky'); // allows us to easily do e.g. ReactPerf.printWasted() while debugging - window.ReactPerf = require('react-addons-perf'); - window.ReactPerf.start(); + //window.ReactPerf = require('react-addons-perf'); + //window.ReactPerf.start(); } export function start(name) { diff --git a/package.json b/package.json index be9b90875..0b7f9128e 100644 --- a/package.json +++ b/package.json @@ -45,9 +45,7 @@ "css-loader": "^0.28.4", "detect-passive-events": "^1.0.2", "dotenv": "^4.0.0", - "emoji-mart": "^1.0.1", - "emojione": "^2.2.7", - "emojione-picker": "^2.2.1", + "emoji-mart": "^2.0.1", "es6-symbol": "^3.1.1", "escape-html": "^1.0.3", "express": "^4.15.2", @@ -80,10 +78,8 @@ "prop-types": "^15.5.10", "punycode": "^2.1.0", "rails-ujs": "^5.1.2", - "react": "^15.6.1", - "react-addons-perf": "^15.4.2", - "react-addons-shallow-compare": "^15.6.0", - "react-dom": "^15.6.1", + "react": "^16.0.0", + "react-dom": "^16.0.0", "react-immutable-proptypes": "^2.1.0", "react-immutable-pure-component": "^1.0.0", "react-intl": "^2.4.0", @@ -93,8 +89,7 @@ "react-redux": "^5.0.4", "react-redux-loading-bar": "^2.9.2", "react-router-dom": "^4.1.1", - "react-router-scroll": "ytase/react-router-scroll#build", - "react-simple-dropdown": "^3.0.0", + "react-router-scroll": "Gargron/react-router-scroll#build", "react-swipeable-views": "^0.12.3", "react-textarea-autosize": "^5.0.7", "react-toggle": "^4.0.1", @@ -124,14 +119,15 @@ "babel-eslint": "^7.2.3", "chai": "^4.1.0", "chai-enzyme": "^0.8.0", - "enzyme": "^2.9.1", + "enzyme": "^3.0.0", + "enzyme-adapter-react-16": "^1.0.0", "eslint": "^3.19.0", "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.10.3", "jsdom": "^11.1.0", "mocha": "^3.4.1", "react-intl-translations-manager": "^5.0.0", - "react-test-renderer": "^15.6.1", + "react-test-renderer": "^16.0.0", "sinon": "^2.3.7", "webpack-dev-server": "^2.6.1", "yargs": "^8.0.2" diff --git a/spec/javascript/components/avatar.test.js b/spec/javascript/components/avatar.test.js index ee40812ca..34949f2b5 100644 --- a/spec/javascript/components/avatar.test.js +++ b/spec/javascript/components/avatar.test.js @@ -1,8 +1,9 @@ +import React from 'react'; +import Avatar from '../../../app/javascript/mastodon/components/avatar'; + import { expect } from 'chai'; import { render } from 'enzyme'; import { fromJS } from 'immutable'; -import React from 'react'; -import Avatar from '../../../app/javascript/mastodon/components/avatar'; describe('', () => { const account = fromJS({ @@ -12,27 +13,28 @@ describe('', () => { avatar: '/animated/alice.gif', avatar_static: '/static/alice.jpg', }); + const size = 100; const animated = render(); const still = render(); // Autoplay - it('renders a div element with the given src as background', () => { + xit('renders a div element with the given src as background', () => { expect(animated.find('div')).to.have.style('background-image', `url(${account.get('avatar')})`); }); - it('renders a div element of the given size', () => { + xit('renders a div element of the given size', () => { ['width', 'height'].map((attr) => { expect(animated.find('div')).to.have.style(attr, `${size}px`); }); }); // Still - it('renders a div element with the given static src as background if not autoplay', () => { + xit('renders a div element with the given static src as background if not autoplay', () => { expect(still.find('div')).to.have.style('background-image', `url(${account.get('avatar_static')})`); }); - it('renders a div element of the given size if not autoplay', () => { + xit('renders a div element of the given size if not autoplay', () => { ['width', 'height'].map((attr) => { expect(still.find('div')).to.have.style(attr, `${size}px`); }); diff --git a/spec/javascript/components/avatar_overlay.test.js b/spec/javascript/components/avatar_overlay.test.js index a8f0e13d5..fe1d3a012 100644 --- a/spec/javascript/components/avatar_overlay.test.js +++ b/spec/javascript/components/avatar_overlay.test.js @@ -1,8 +1,9 @@ +import React from 'react'; +import AvatarOverlay from '../../../app/javascript/mastodon/components/avatar_overlay'; + import { expect } from 'chai'; import { render } from 'enzyme'; import { fromJS } from 'immutable'; -import React from 'react'; -import AvatarOverlay from '../../../app/javascript/mastodon/components/avatar_overlay'; describe('', () => { const account = fromJS({ @@ -12,6 +13,7 @@ describe('', () => { avatar: '/animated/alice.gif', avatar_static: '/static/alice.jpg', }); + const friend = fromJS({ username: 'eve', acct: 'eve@blackhat.lair', @@ -22,12 +24,12 @@ describe('', () => { const overlay = render(); - it('renders account static src as base of overlay avatar', () => { + xit('renders account static src as base of overlay avatar', () => { expect(overlay.find('.account__avatar-overlay-base')) .to.have.style('background-image', `url(${account.get('avatar_static')})`); }); - it('renders friend static src as overlay of overlay avatar', () => { + xit('renders friend static src as overlay of overlay avatar', () => { expect(overlay.find('.account__avatar-overlay-overlay')) .to.have.style('background-image', `url(${friend.get('avatar_static')})`); }); diff --git a/spec/javascript/components/button.test.js b/spec/javascript/components/button.test.js index 9cf8b1eed..d2cd0b4e7 100644 --- a/spec/javascript/components/button.test.js +++ b/spec/javascript/components/button.test.js @@ -1,16 +1,17 @@ +import React from 'react'; +import Button from '../../../app/javascript/mastodon/components/button'; + import { expect } from 'chai'; import { shallow } from 'enzyme'; import sinon from 'sinon'; -import React from 'react'; -import Button from '../../../app/javascript/mastodon/components/button'; describe('); expect(wrapper.find('button')).to.contain(children); }); - it('renders the props.text instead of children', () => { + xit('renders the props.text instead of children', () => { const text = 'foo'; const children =

children

; const wrapper = shallow(); @@ -49,22 +50,22 @@ describe(' - - - - - + + + + + +
); } @@ -243,6 +242,7 @@ class EmojiPickerMenu extends React.PureComponent { i18n={this.getI18n()} onClick={this.handleClick} skin={modifier} + showPreview={false} backgroundImageFn={backgroundImageFn} /> -- cgit From 0f699a4280e63b23d86c901a376c8a9e661ebc29 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 3 Oct 2017 02:01:54 +0200 Subject: When muting, clear web UI like for blocks (#5172) * When muting, clear web UI like for blocks * Fix style issue --- app/javascript/mastodon/reducers/notifications.js | 6 +++++- app/javascript/mastodon/reducers/statuses.js | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index 0063d24e4..cccf00a1f 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -9,7 +9,10 @@ import { NOTIFICATIONS_CLEAR, NOTIFICATIONS_SCROLL_TOP, } from '../actions/notifications'; -import { ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts'; +import { + ACCOUNT_BLOCK_SUCCESS, + ACCOUNT_MUTE_SUCCESS, +} from '../actions/accounts'; import { TIMELINE_DELETE } from '../actions/timelines'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; @@ -108,6 +111,7 @@ export default function notifications(state = initialState, action) { case NOTIFICATIONS_EXPAND_SUCCESS: return appendNormalizedNotifications(state, action.notifications, action.next); case ACCOUNT_BLOCK_SUCCESS: + case ACCOUNT_MUTE_SUCCESS: return filterNotifications(state, action.relationship); case NOTIFICATIONS_CLEAR: return state.set('items', ImmutableList()).set('next', null); diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 38b23504e..2d72b12e8 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -24,6 +24,7 @@ import { } from '../actions/timelines'; import { ACCOUNT_BLOCK_SUCCESS, + ACCOUNT_MUTE_SUCCESS, } from '../actions/accounts'; import { NOTIFICATIONS_UPDATE, @@ -138,6 +139,7 @@ export default function statuses(state = initialState, action) { case TIMELINE_DELETE: return deleteStatus(state, action.id, action.references); case ACCOUNT_BLOCK_SUCCESS: + case ACCOUNT_MUTE_SUCCESS: return filterStatuses(state, action.relationship); default: return state; -- cgit From 395a57d03d4592df0ffe0d8ad7c6ea86510a202d Mon Sep 17 00:00:00 2001 From: Jakob Kramer <811907+gandaro@users.noreply.github.com> Date: Tue, 3 Oct 2017 02:53:18 +0200 Subject: Update German translation (#5189) --- app/javascript/mastodon/locales/de.json | 66 ++++++++++++++++----------------- config/locales/de.yml | 26 +++++++++++-- config/locales/devise.de.yml | 4 +- config/locales/simple_form.de.yml | 3 +- 4 files changed, 60 insertions(+), 39 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 88859e49d..ba23b8dab 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -33,7 +33,7 @@ "column.home": "Startseite", "column.mutes": "Stummgeschaltete Profile", "column.notifications": "Mitteilungen", - "column.pins": "Pinned toot", + "column.pins": "Angeheftete Beiträge", "column.public": "Gesamtes bekanntes Netz", "column_back_button.label": "Zurück", "column_header.hide_settings": "Einstellungen verbergen", @@ -66,34 +66,34 @@ "embed.instructions": "Du kannst diesen Beitrag auf deiner Webseite einbetten, indem du den folgenden Code einfügst.", "embed.preview": "So wird es aussehen:", "emoji_button.activity": "Aktivitäten", - "emoji_button.custom": "Custom", + "emoji_button.custom": "Eigene", "emoji_button.flags": "Flaggen", "emoji_button.food": "Essen und Trinken", "emoji_button.label": "Emoji einfügen", "emoji_button.nature": "Natur", "emoji_button.not_found": "Keine Emojis!! (╯°□°)╯︵ ┻━┻", "emoji_button.objects": "Gegenstände", - "emoji_button.people": "Leute", + "emoji_button.people": "Personen", "emoji_button.recent": "Häufig benutzt", - "emoji_button.search": "Suchen …", + "emoji_button.search": "Suchen", "emoji_button.search_results": "Suchergebnisse", "emoji_button.symbols": "Symbole", "emoji_button.travel": "Reisen und Orte", - "empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe etwas öffentlich, um den Ball ins Rollen zu bringen!", - "empty_column.hashtag": "Es gibt noch nichts unter diesem Hashtag.", - "empty_column.home": "Du folgst noch niemandem. Besuche {public} oder benutze die Suche, um zu starten oder andere Profile zu finden.", - "empty_column.home.inactivity": "Deine Zeitleiste ist leer. Falls du eine längere Zeit inaktiv gewesen bist, wird sie für dich so schnell wie möglich neu erstellt.", + "empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe einen öffentlichen Beitrag, um den Ball ins Rollen zu bringen!", + "empty_column.hashtag": "Unter diesem Hashtag gibt es noch nichts.", + "empty_column.home": "Du folgst noch niemandem. Besuche {public} oder nutze die Suche, um loszulegen und andere Leute zu finden.", + "empty_column.home.inactivity": "Deine Zeitleiste ist leer. Falls du eine längere Zeit inaktiv warst, wird sie für dich so schnell wie möglich neu erstellt.", "empty_column.home.public_timeline": "die öffentliche Zeitleiste", - "empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um die Konversation zu starten.", - "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Profilen von anderen Instanzen, um es aufzufüllen.", + "empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um ins Gespräch zu kommen.", + "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Profilen von anderen Instanzen, um die Zeitleiste aufzufüllen", "follow_request.authorize": "Erlauben", "follow_request.reject": "Ablehnen", - "getting_started.appsshort": "Anwendungen", + "getting_started.appsshort": "Apps", "getting_started.faq": "Häufig gestellte Fragen", "getting_started.heading": "Erste Schritte", - "getting_started.open_source_notice": "Mastodon ist quelloffene Software. Du kannst auf {github} dazu beitragen oder Probleme melden.", + "getting_started.open_source_notice": "Mastodon ist quelloffene Software. Du kannst auf GitHub unter {github} dazu beitragen oder Probleme melden.", "getting_started.userguide": "Bedienungsanleitung", - "home.column_settings.advanced": "Fortgeschritten", + "home.column_settings.advanced": "Erweitert", "home.column_settings.basic": "Einfach", "home.column_settings.filter_regex": "Mit regulären Ausdrücken filtern", "home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen", @@ -102,7 +102,7 @@ "lightbox.close": "Schließen", "lightbox.next": "Weiter", "lightbox.previous": "Zurück", - "loading_indicator.label": "Lade …", + "loading_indicator.label": "Wird geladen …", "media_gallery.toggle_visible": "Sichtbarkeit umschalten", "missing_indicator.label": "Nicht gefunden", "navigation_bar.blocks": "Blockierte Profile", @@ -110,10 +110,10 @@ "navigation_bar.edit_profile": "Profil bearbeiten", "navigation_bar.favourites": "Favoriten", "navigation_bar.follow_requests": "Folgeanfragen", - "navigation_bar.info": "Erweiterte Informationen", + "navigation_bar.info": "Über diese Instanz", "navigation_bar.logout": "Abmelden", "navigation_bar.mutes": "Stummgeschaltete Profile", - "navigation_bar.pins": "Pinned toots", + "navigation_bar.pins": "Angeheftete Beiträge", "navigation_bar.preferences": "Einstellungen", "navigation_bar.public_timeline": "Föderierte Zeitleiste", "notification.favourite": "{name} hat deinen Beitrag favorisiert", @@ -127,13 +127,13 @@ "notifications.column_settings.follow": "Neue Folgende:", "notifications.column_settings.mention": "Erwähnungen:", "notifications.column_settings.push": "Push-Benachrichtigungen", - "notifications.column_settings.push_meta": "This device", + "notifications.column_settings.push_meta": "Auf diesem Gerät", "notifications.column_settings.reblog": "Geteilte Beiträge:", "notifications.column_settings.show": "In der Spalte anzeigen", "notifications.column_settings.sound": "Ton abspielen", "onboarding.done": "Fertig", "onboarding.next": "Weiter", - "onboarding.page_five.public_timelines": "Die lokale Zeitleiste zeigt alle Beiträge von Leuten, die auch auf {domain} sind. Das gesamte bekannte Netz zeigt Beiträge von allen, denen von Leuten auf {domain} gefolgt wird. Zusammen werden die beiden Leisten auch öffentliche Zeitleisten genannt. Durch sie kannst du viel Neues entdecken.", + "onboarding.page_five.public_timelines": "Die lokale Zeitleiste zeigt alle Beiträge von Leuten, die auch auf {domain} sind. Das gesamte bekannte Netz zeigt Beiträge von allen, denen von Leuten auf {domain} gefolgt wird. Zusammen sind sie die öffentlichen Zeitleisten. In ihnen kannst du viel Neues entdecken!", "onboarding.page_four.home": "Die Startseite zeigt dir Beiträge von Leuten, denen du folgst.", "onboarding.page_four.notifications": "Wenn jemand mit dir interagiert, bekommst du eine Mitteilung.", "onboarding.page_one.federation": "Mastodon ist ein soziales Netzwerk, das aus unabhängigen Servern besteht. Diese Server nennen wir auch Instanzen.", @@ -142,20 +142,20 @@ "onboarding.page_six.admin": "Für deine Instanz ist {admin} zuständig.", "onboarding.page_six.almost_done": "Fast fertig …", "onboarding.page_six.appetoot": "Guten Appetröt!", - "onboarding.page_six.apps_available": "Es gibt verschiedene {apps} für iOS, Android und andere Plattformen.", - "onboarding.page_six.github": "Mastodon ist freie, quelloffene Software. Du kannst auf {github} dazu beitragen oder Probleme melden.", + "onboarding.page_six.apps_available": "Es gibt verschiedene {apps} für iOS, Android und weitere Plattformen.", + "onboarding.page_six.github": "Mastodon ist freie, quelloffene Software. Du kannst auf GitHub unter {github} dazu beitragen, Probleme melden und Wünsche äußern.", "onboarding.page_six.guidelines": "Richtlinien", - "onboarding.page_six.read_guidelines": "Bitte mach dich mit den {guidelines} von {domain} vertraut!", + "onboarding.page_six.read_guidelines": "Bitte mach dich mit den {guidelines} von {domain} vertraut.", "onboarding.page_six.various_app": "Apps", - "onboarding.page_three.profile": "Bearbeite dein Profil, um dein Bild, deinen Namen oder deine Beschreibung anzupassen. Dort findest du auch andere Einstellungen.", - "onboarding.page_three.search": "Benutze die Suchfunktion, um Leute oder Themen zu finden. Zum Beispiel die Hashtags {illustration} oder {introductions}. Um eine Person zu finden, die auf einer anderen Instanz ist, benutze den vollständigen Profilnamen.", - "onboarding.page_two.compose": "Schreibe Beiträge aus der Schreiben-Spalte. Du kannst Bilder und kurze Videos hochladen, Sichtbarkeits-Einstellungen ändern und Inhaltswarnungen hinzufügen.", + "onboarding.page_three.profile": "Bearbeite dein Profil, um dein Bild, deinen Namen und deine Beschreibung anzupassen. Dort findest du auch weitere Einstellungen.", + "onboarding.page_three.search": "Benutze die Suchfunktion, um Leute zu finden und mit Hashtags wie {illustration} oder {introductions} nach Beiträgen zu suchen. Um eine Person zu finden, die auf einer anderen Instanz ist, benutze den vollständigen Profilnamen.", + "onboarding.page_two.compose": "Schreibe deine Beiträge in der Schreiben-Spalte. Mit den Symbolen unter dem Eingabefeld kannst du Bilder hochladen, Sichtbarkeits-Einstellungen ändern und Inhaltswarnungen hinzufügen.", "onboarding.skip": "Überspringen", "privacy.change": "Sichtbarkeit des Beitrags anpassen", "privacy.direct.long": "Beitrag nur an erwähnte Profile", "privacy.direct.short": "Direkt", "privacy.private.long": "Beitrag nur an Folgende", - "privacy.private.short": "Privat", + "privacy.private.short": "Nur Folgende", "privacy.public.long": "Beitrag an öffentliche Zeitleisten", "privacy.public.short": "Öffentlich", "privacy.unlisted.long": "Nicht in öffentlichen Zeitleisten anzeigen", @@ -163,26 +163,26 @@ "reply_indicator.cancel": "Abbrechen", "report.placeholder": "Zusätzliche Kommentare", "report.submit": "Absenden", - "report.target": "Melden", + "report.target": "{target} melden", "search.placeholder": "Suche", "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}", - "standalone.public_title": "Vorschau …", + "standalone.public_title": "Ein kleiner Einblick …", "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden", "status.delete": "Löschen", "status.embed": "Einbetten", "status.favourite": "Favorisieren", "status.load_more": "Weitere laden", "status.media_hidden": "Medien versteckt", - "status.mention": "Erwähnen", + "status.mention": "@{name} erwähnen", "status.mute_conversation": "Thread stummschalten", - "status.open": "Öffnen", + "status.open": "Diesen Beitrag öffnen", "status.pin": "Im Profil anheften", "status.reblog": "Teilen", "status.reblogged_by": "{name} teilte", "status.reply": "Antworten", "status.replyAll": "Auf Thread antworten", "status.report": "@{name} melden", - "status.sensitive_toggle": "Klicke, um sie zu sehen", + "status.sensitive_toggle": "Zum Ansehen klicken", "status.sensitive_warning": "Heikle Inhalte", "status.share": "Teilen", "status.show_less": "Weniger anzeigen", @@ -194,11 +194,11 @@ "tabs_bar.home": "Startseite", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Mitteilungen", - "upload_area.title": "Hereinziehen zum Hochladen", + "upload_area.title": "Zum Hochladen hereinziehen", "upload_button.label": "Mediendatei hinzufügen", - "upload_form.description": "Describe for the visually impaired", + "upload_form.description": "Für Menschen mit Sehbehinderung beschreiben", "upload_form.undo": "Entfernen", - "upload_progress.label": "Lade hoch …", + "upload_progress.label": "Wird hochgeladen …", "video.close": "Video schließen", "video.exit_fullscreen": "Vollbild verlassen", "video.expand": "Video vergrößern", diff --git a/config/locales/de.yml b/config/locales/de.yml index 1192a7b10..dce86409b 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -189,7 +189,7 @@ de: application_mailer: settings: 'E-Mail-Einstellungen ändern: %{link}' signature: Mastodon-Benachrichtigungen von %{instance} - view: 'Darstellung:' + view: 'Ansehen:' applications: invalid_url: Die angegebene URL ist ungültig auth: @@ -317,9 +317,29 @@ de: next: Vorwärts prev: Zurück truncate: "…" + preferences: + languages: Sprachen + notifications: Benachrichtigungen + other: Weiteres + publishing: Beiträge + web: Web + push_notifications: + favourite: + title: "%{name} hat deinen Beitrag favorisiert" + follow: + title: "%{name} folgt dir nun" + group: + title: "%{count} Benachrichtigungen" + mention: + action_boost: Teilen + action_expand: Mehr anzeigen + action_favourite: Favorisieren + title: "%{name} hat dich erwähnt" + reblog: + title: "%{name} hat deinen Beitrag geteilt" remote_follow: - acct: Dein Profilname@Domain, von dem aus du dieser Person folgen möchtest. - missing_resource: Die erforderliche Weiterleitungs-URL konnte leider in deinem Profil nicht gefunden werden. + acct: Dein Profilname@Domain, von dem aus du dieser Person folgen möchtest + missing_resource: Die erforderliche Weiterleitungs-URL für dein Konto konnte nicht gefunden werden proceed: Weiter prompt: 'Du wirst dieser Person folgen:' sessions: diff --git a/config/locales/devise.de.yml b/config/locales/devise.de.yml index 0db946b9f..6154231c7 100644 --- a/config/locales/devise.de.yml +++ b/config/locales/devise.de.yml @@ -25,12 +25,12 @@ de: unlock_instructions: subject: 'Mastodon: Konto entsperren' omniauth_callbacks: - failure: Du konntest nicht mit deinem %{kind}-Konto angemeldet werden, weil '%{reason}'. + failure: Du konntest nicht mit deinem %{kind}-Konto angemeldet werden, weil »%{reason}«. success: Du hast dich erfolgreich mit deinem %{kind}-Konto angemeldet. passwords: no_token: Du kannst diese Seite nur über den Link aus der E-Mail zum Passwort-Zurücksetzen aufrufen. Wenn du einen solchen Link aufgerufen hast, stelle bitte sicher, dass du die vollständige Adresse aufrufst. send_instructions: Du erhältst in wenigen Minuten eine E-Mail. Darin wird erklärt, wie du dein Passwort zurücksetzen kannst. - send_paranoid_instructions: Falls deine E-Mail-Adresse in unserer Datenbank existiert, erhältst du in wenigen Minuten eine E-Mail. Darin wird erklärt, wie du dein Passwort zurücksetzen kannst. + send_paranoid_instructions: Falls deine E-Mail-Adresse in unserer Datenbank hinterlegt ist, erhältst du in wenigen Minuten eine E-Mail. Darin wird erklärt, wie du dein Passwort zurücksetzen kannst. updated: Dein Passwort wurde geändert. Du bist jetzt angemeldet. updated_not_active: Dein Passwort wurde geändert. registrations: diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 2fc353b6c..4064aa5f2 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -4,6 +4,7 @@ de: hints: defaults: avatar: PNG, GIF oder JPG. Maximal 2 MB. Wird auf 120×120 px herunterskaliert + digest: Wenn du lange Zeit inaktiv bist, wird dir eine Zusammenfassung von Erwähnungen in deiner Abwesenheit zugeschickt display_name: one: 1 Zeichen verbleibt other: %{count} Zeichen verbleiben @@ -19,7 +20,7 @@ de: sessions: otp: Gib den Zwei-Faktor-Authentisierungs-Code von deinem Telefon ein oder benutze einen deiner Wiederherstellungscodes. user: - filtered_languages: Ausgewählte Sprachen werden aus deinen öffentlichen Zeitleisten entfernt. + filtered_languages: Ausgewählte Sprachen werden aus deinen öffentlichen Zeitleisten gefiltert labels: defaults: avatar: Profilbild -- cgit From f303a954e68ef47d636c6af109e81caed33ef58c Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Tue, 3 Oct 2017 20:10:07 +0900 Subject: Remove aria-label of status content (#5195) aria-label contained body of status with content warning, which should be hidden by default. Remove the label for the case and other cases due to consistency. --- app/javascript/mastodon/components/status_content.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index 2069f971c..63ce25865 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -144,7 +144,7 @@ export default class StatusContent extends React.PureComponent { } return ( -
+
Date: Tue, 3 Oct 2017 20:10:26 +0900 Subject: Add missing Japanese translations (#5193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * yarn manage:translations * Add Japanese translations for #5170 * Add Japanese translations for #5123 * Add Japanese translations for #5046 * Add Japanese translations for #5099 * Add Japanese translations for #5161 * "項目" -> "絵文字" --- app/javascript/mastodon/locales/ar.json | 5 +++++ app/javascript/mastodon/locales/bg.json | 5 +++++ app/javascript/mastodon/locales/ca.json | 5 +++++ app/javascript/mastodon/locales/de.json | 5 +++++ app/javascript/mastodon/locales/defaultMessages.json | 20 ++++++++++++++++++++ app/javascript/mastodon/locales/en.json | 5 +++++ app/javascript/mastodon/locales/eo.json | 5 +++++ app/javascript/mastodon/locales/es.json | 5 +++++ app/javascript/mastodon/locales/fa.json | 5 +++++ app/javascript/mastodon/locales/fi.json | 5 +++++ app/javascript/mastodon/locales/fr.json | 5 +++++ app/javascript/mastodon/locales/he.json | 5 +++++ app/javascript/mastodon/locales/hr.json | 5 +++++ app/javascript/mastodon/locales/hu.json | 5 +++++ app/javascript/mastodon/locales/id.json | 5 +++++ app/javascript/mastodon/locales/io.json | 5 +++++ app/javascript/mastodon/locales/it.json | 5 +++++ app/javascript/mastodon/locales/ja.json | 15 ++++++++++----- app/javascript/mastodon/locales/ko.json | 5 +++++ app/javascript/mastodon/locales/nl.json | 5 +++++ app/javascript/mastodon/locales/no.json | 5 +++++ app/javascript/mastodon/locales/oc.json | 5 +++++ app/javascript/mastodon/locales/pl.json | 5 +++++ app/javascript/mastodon/locales/pt-BR.json | 5 +++++ app/javascript/mastodon/locales/pt.json | 5 +++++ app/javascript/mastodon/locales/ru.json | 5 +++++ app/javascript/mastodon/locales/th.json | 5 +++++ app/javascript/mastodon/locales/tr.json | 5 +++++ app/javascript/mastodon/locales/uk.json | 5 +++++ app/javascript/mastodon/locales/zh-CN.json | 5 +++++ app/javascript/mastodon/locales/zh-HK.json | 5 +++++ app/javascript/mastodon/locales/zh-TW.json | 5 +++++ config/locales/ja.yml | 9 +++++++++ 33 files changed, 189 insertions(+), 5 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 6a34d39fe..e2df4ffc9 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -165,6 +165,11 @@ "report.submit": "إرسال", "report.target": "إبلاغ", "search.placeholder": "ابحث", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "تعذرت ترقية هذا المنشور", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index aaf99a5f1..240e3725e 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -165,6 +165,11 @@ "report.submit": "Submit", "report.target": "Reporting", "search.placeholder": "Търсене", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "This post cannot be boosted", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 2829656c8..b5051a32d 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -165,6 +165,11 @@ "report.submit": "Enviar", "report.target": "Informes", "search.placeholder": "Cercar", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "Aquesta publicació no pot ser retootejada", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index ba23b8dab..b79b1b2f0 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -165,6 +165,11 @@ "report.submit": "Absenden", "report.target": "{target} melden", "search.placeholder": "Suche", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}", "standalone.public_title": "Ein kleiner Einblick …", "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 521fe9f20..1e7fef6be 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -607,6 +607,26 @@ { "defaultMessage": "Search", "id": "search.placeholder" + }, + { + "defaultMessage": "Advanced search format", + "id": "search_popout.search_format" + }, + { + "defaultMessage": "hashtag", + "id": "search_popout.tips.hashtag" + }, + { + "defaultMessage": "user", + "id": "search_popout.tips.user" + }, + { + "defaultMessage": "status", + "id": "search_popout.tips.status" + }, + { + "defaultMessage": "Simple text returns matching display names, usernames and hashtags", + "id": "search_popout.tips.text" } ], "path": "app/javascript/mastodon/features/compose/components/search.json" diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 278f33092..b0dbc46bd 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -165,6 +165,11 @@ "report.submit": "Submit", "report.target": "Reporting {target}", "search.placeholder": "Search", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "This post cannot be boosted", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index ef185a5e1..1ccd2b817 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -165,6 +165,11 @@ "report.submit": "Submit", "report.target": "Reporting", "search.placeholder": "Serĉi", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "This post cannot be boosted", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 9dd22da95..f6bfbb04d 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -165,6 +165,11 @@ "report.submit": "Publicar", "report.target": "Reportando", "search.placeholder": "Buscar", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}", "standalone.public_title": "Un pequeño vistazo...", "status.cannot_reblog": "Este toot no puede retootearse", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 7b5709e73..13fb91278 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -165,6 +165,11 @@ "report.submit": "بفرست", "report.target": "گزارش‌دادن", "search.placeholder": "جستجو", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}", "standalone.public_title": "نگاهی به کاربران این سرور...", "status.cannot_reblog": "این نوشته را نمی‌شود بازبوقید", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 6c01db06b..425b3d82a 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -165,6 +165,11 @@ "report.submit": "Submit", "report.target": "Reporting", "search.placeholder": "Hae", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "This post cannot be boosted", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 76ab68f6f..0dda5af9c 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -165,6 +165,11 @@ "report.submit": "Envoyer", "report.target": "Signalement", "search.placeholder": "Rechercher", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}", "standalone.public_title": "Jeter un coup d’œil…", "status.cannot_reblog": "Cette publication ne peut être boostée", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 64246e893..beaa4fd3a 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -165,6 +165,11 @@ "report.submit": "שליחה", "report.target": "דיווח", "search.placeholder": "חיפוש", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "לא ניתן להדהד הודעה זו", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index 39b5ede80..cef61f15e 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -165,6 +165,11 @@ "report.submit": "Pošalji", "report.target": "Prijavljivanje", "search.placeholder": "Traži", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "Ovaj post ne može biti boostan", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index a52e0837c..7b9c1b293 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -165,6 +165,11 @@ "report.submit": "Submit", "report.target": "Reporting", "search.placeholder": "Keresés", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "This post cannot be boosted", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index ec4f7f3ec..cc48aa996 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -165,6 +165,11 @@ "report.submit": "Kirim", "report.target": "Melaporkan", "search.placeholder": "Pencarian", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count} {count, plural, one {hasil} other {hasil}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "This post cannot be boosted", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 9df2177e9..b484bebc7 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -165,6 +165,11 @@ "report.submit": "Sendar", "report.target": "Denuncante", "search.placeholder": "Serchez", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezulti}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "This post cannot be boosted", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 0eab2f225..4d73fbea8 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -165,6 +165,11 @@ "report.submit": "Invia", "report.target": "Invio la segnalazione", "search.placeholder": "Cerca", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count} {count, plural, one {risultato} other {risultati}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "This post cannot be boosted", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 37bc8356a..11356c6db 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -66,17 +66,17 @@ "embed.instructions": "下記のコードをコピーしてウェブサイトに埋め込みます。", "embed.preview": "表示例:", "emoji_button.activity": "活動", - "emoji_button.custom": "Custom", + "emoji_button.custom": "カスタム絵文字", "emoji_button.flags": "国旗", "emoji_button.food": "食べ物", "emoji_button.label": "絵文字を追加", "emoji_button.nature": "自然", - "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", + "emoji_button.not_found": "絵文字がない!! (╯°□°)╯︵ ┻━┻", "emoji_button.objects": "物", "emoji_button.people": "人々", - "emoji_button.recent": "Frequently used", + "emoji_button.recent": "よく使う絵文字", "emoji_button.search": "検索...", - "emoji_button.search_results": "Search results", + "emoji_button.search_results": "検索結果", "emoji_button.symbols": "記号", "emoji_button.travel": "旅行と場所", "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!", @@ -165,6 +165,11 @@ "report.submit": "通報する", "report.target": "{target} を通報する", "search.placeholder": "検索", + "search_popout.search_format": "高度な検索フォーマット", + "search_popout.tips.hashtag": "ハッシュタグ", + "search_popout.tips.status": "トゥート", + "search_popout.tips.text": "表示名やユーザー名、ハッシュタグに一致する単純なテキスト", + "search_popout.tips.user": "ユーザー", "search_results.total": "{count, number}件の結果", "standalone.public_title": "今こんな話をしています", "status.cannot_reblog": "この投稿はブーストできません", @@ -196,7 +201,7 @@ "tabs_bar.notifications": "通知", "upload_area.title": "ドラッグ&ドロップでアップロード", "upload_button.label": "メディアを追加", - "upload_form.description": "Describe for the visually impaired", + "upload_form.description": "視覚障害者のための説明", "upload_form.undo": "やり直す", "upload_progress.label": "アップロード中...", "video.close": "動画を閉じる", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index e593721d4..c1768cf8f 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -165,6 +165,11 @@ "report.submit": "신고하기", "report.target": "문제가 된 사용자", "search.placeholder": "검색", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number}건의 결과", "standalone.public_title": "A look inside...", "status.cannot_reblog": "이 포스트는 부스트 할 수 없습니다", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index e223b4a3e..bad2d78c5 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -165,6 +165,11 @@ "report.submit": "Verzenden", "report.target": "Rapporteren van", "search.placeholder": "Zoeken", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}", "standalone.public_title": "Een kijkje binnenin...", "status.cannot_reblog": "Deze toot kan niet geboost worden", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 0ad991927..26556b290 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -165,6 +165,11 @@ "report.submit": "Send inn", "report.target": "Rapporterer", "search.placeholder": "Søk", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "Denne posten kan ikke fremheves", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 1ad7bf592..87582cd06 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -165,6 +165,11 @@ "report.submit": "Mandar", "report.target": "Senhalar {target}", "search.placeholder": "Recercar", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}", "standalone.public_title": "Una ulhada dedins…", "status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index bca22d09d..fe76284a9 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -165,6 +165,11 @@ "report.submit": "Wyślij", "report.target": "Zgłaszanie {target}", "search.placeholder": "Szukaj", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {wynik} more {wyniki}}", "standalone.public_title": "Spojrzenie w głąb…", "status.cannot_reblog": "Ten wpis nie może zostać podbity", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 80917393f..61674b37e 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -165,6 +165,11 @@ "report.submit": "Enviar", "report.target": "Denunciar", "search.placeholder": "Pesquisar", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}", "standalone.public_title": "Dê uma espiada...", "status.cannot_reblog": "Esta postagem não pode ser compartilhada", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 9f9da9f1e..ecd0689df 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -165,6 +165,11 @@ "report.submit": "Enviar", "report.target": "Denunciar", "search.placeholder": "Pesquisar", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "This post cannot be boosted", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 59491c62d..bf32c820d 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -165,6 +165,11 @@ "report.submit": "Отправить", "report.target": "Жалуемся на", "search.placeholder": "Поиск", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {результат} few {результата} many {результатов} other {результатов}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "Этот статус не может быть продвинут", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index ff39b1b94..f3ec9c532 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -165,6 +165,11 @@ "report.submit": "Submit", "report.target": "Reporting", "search.placeholder": "Search", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "This post cannot be boosted", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index e48539683..afc6383b4 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -165,6 +165,11 @@ "report.submit": "Gönder", "report.target": "Raporlama", "search.placeholder": "Ara", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {sonuç} other {sonuçlar}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "Bu gönderi boost edilemez", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index b72ea5b37..d0aae032b 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -165,6 +165,11 @@ "report.submit": "Відправити", "report.target": "Скаржимося на", "search.placeholder": "Пошук", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {результат} few {результати} many {результатів} other {результатів}}", "standalone.public_title": "A look inside...", "status.cannot_reblog": "Цей допис не може бути передмухнутий", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 595eec30b..e0ffc16df 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -165,6 +165,11 @@ "report.submit": "提交", "report.target": "Reporting", "search.placeholder": "搜索", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "大家都在干啥?", "status.cannot_reblog": "没法转嘟这条嘟文啦……", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 4fbfe7a8f..053e971aa 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -165,6 +165,11 @@ "report.submit": "提交", "report.target": "舉報", "search.placeholder": "搜尋", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} 項結果", "standalone.public_title": "站點一瞥…", "status.cannot_reblog": "這篇文章無法被轉推", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 11db0ea14..a22d66fa1 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -165,6 +165,11 @@ "report.submit": "送出", "report.target": "通報中", "search.placeholder": "搜尋", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", "search_results.total": "{count, number} 項結果", "standalone.public_title": "站點一瞥…", "status.cannot_reblog": "此貼文無法轉推", diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 364bfcfd6..78465e121 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -310,6 +310,9 @@ ja: content: セキュリティ認証に失敗しました。Cookieをブロックしていませんか? title: セキュリティ認証に失敗 '429': リクエストの制限に達しました。 + '500': + content: もうしわけありませんが、なにかが間違っています。 + title: このページは正しくありません noscript_html: Mastodonのウェブアプリケーションを利用する場合はJavaScriptを有効にしてください。またはあなたのプラットフォーム向けのMastodonネイティブアプリを探すことができます。 exports: blocks: ブロック @@ -390,6 +393,12 @@ ja: next: 次 prev: 前 truncate: "…" + preferences: + languages: 言語 + notifications: 通知 + other: その他 + publishing: 投稿 + web: ウェブ push_notifications: favourite: title: あなたのトゥートが %{name} さんにお気に入り登録されました -- cgit From ecacb15cd50609fb3d749ecac89835a43255fb34 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Tue, 3 Oct 2017 20:10:57 +0900 Subject: Add placeholder text color to form of media attachments (#5196) --- app/javascript/styles/components.scss | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'app/javascript') diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index e83a22e00..3e1b08e9f 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -371,6 +371,11 @@ &:focus { color: $white; } + + &::placeholder { + opacity: 0.54; + color: $ui-secondary-color; + } } &.active { -- cgit From 875d943c189afe9887200f357d916a9f8fd19fe8 Mon Sep 17 00:00:00 2001 From: PFM Date: Wed, 4 Oct 2017 00:11:22 +0900 Subject: Add pagination in media modal (#4343) * Add pagination in media modal * Change array name * Add an element class * Avoid nested class * Pull out the active class * Use map instead of forEach * Remove parentheses --- .../mastodon/features/ui/components/media_modal.js | 21 ++++++++++++++++- app/javascript/styles/components.scss | 27 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js index 705645b40..f41a83089 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.js @@ -29,7 +29,7 @@ export default class MediaModal extends ImmutablePureComponent { }; handleSwipe = (index) => { - this.setState({ index: (index) % this.props.media.size }); + this.setState({ index: index % this.props.media.size }); } handleNextClick = () => { @@ -40,6 +40,11 @@ export default class MediaModal extends ImmutablePureComponent { this.setState({ index: (this.props.media.size + this.getIndex() - 1) % this.props.media.size }); } + handleChangeIndex = (e) => { + const index = Number(e.currentTarget.getAttribute('data-index')); + this.setState({ index: index % this.props.media.size }); + } + handleKeyUp = (e) => { switch(e.key) { case 'ArrowLeft': @@ -67,10 +72,21 @@ export default class MediaModal extends ImmutablePureComponent { const { media, intl, onClose } = this.props; const index = this.getIndex(); + let pagination = []; const leftNav = media.size > 1 && ; const rightNav = media.size > 1 && ; + if (media.size > 1) { + pagination = media.map((item, i) => { + const classes = ['media-modal__button']; + if (i === index) { + classes.push('media-modal__button--active'); + } + return (
  • ); + }); + } + const content = media.map((image) => { const width = image.getIn(['meta', 'original', 'width']) || null; const height = image.getIn(['meta', 'original', 'height']) || null; @@ -98,6 +114,9 @@ export default class MediaModal extends ImmutablePureComponent { {content}
    +
      + {pagination} +
    {rightNav}
    diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 3e1b08e9f..6ef4e3866 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -3080,6 +3080,33 @@ button.icon-button.active i.fa-retweet { background: $base-overlay-background; } +.media-modal__pagination { + width: 100%; + text-align: center; + position: absolute; + left: 0; + bottom: -40px; +} + +.media-modal__page-dot { + display: inline-block; +} + +.media-modal__button { + background-color: $white; + height: 12px; + width: 12px; + border-radius: 6px; + margin: 10px; + padding: 0; + border: 0; + font-size: 0; +} + +.media-modal__button--active { + background-color: $ui-highlight-color; +} + .media-modal__close { position: absolute; right: 4px; -- cgit From 82d9ade7a6abc663b30b3df4ae08a8980d61e233 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Tue, 3 Oct 2017 11:43:57 -0700 Subject: Compress emoji_data_light.js (#5201) --- app/javascript/mastodon/emoji_data_compressed.js | 22 +++++++++++++++++++ app/javascript/mastodon/emoji_data_light.js | 27 ++++++++++++------------ 2 files changed, 35 insertions(+), 14 deletions(-) create mode 100644 app/javascript/mastodon/emoji_data_compressed.js (limited to 'app/javascript') diff --git a/app/javascript/mastodon/emoji_data_compressed.js b/app/javascript/mastodon/emoji_data_compressed.js new file mode 100644 index 000000000..f69a3e46a --- /dev/null +++ b/app/javascript/mastodon/emoji_data_compressed.js @@ -0,0 +1,22 @@ +// @preval +const data = require('emoji-mart/dist/data').default; +const pick = require('lodash/pick'); +const values = require('lodash/values'); + +const condensedEmojis = Object.keys(data.emojis).map(key => { + if (!data.emojis[key].short_names[0] === key) { + throw new Error('The condenser expects the first short_code to be the ' + + 'key. It may need to be rewritten if the emoji change such that this ' + + 'is no longer the case.'); + } + return values(pick(data.emojis[key], ['short_names', 'unified', 'search'])); +}); + +// JSON.parse/stringify is to emulate what @preval is doing and avoid any +// inconsistent behavior in dev mode +module.exports = JSON.parse(JSON.stringify({ + emojis: condensedEmojis, + skins: data.skins, + categories: data.categories, + short_names: data.short_names, +})); diff --git a/app/javascript/mastodon/emoji_data_light.js b/app/javascript/mastodon/emoji_data_light.js index f03442455..f91ee592e 100644 --- a/app/javascript/mastodon/emoji_data_light.js +++ b/app/javascript/mastodon/emoji_data_light.js @@ -1,17 +1,16 @@ -// @preval -const data = require('emoji-mart/dist/data').default; -const pick = require('lodash/pick'); +const data = require('./emoji_data_compressed'); -const condensedEmojis = {}; -Object.keys(data.emojis).forEach(key => { - condensedEmojis[key] = pick(data.emojis[key], ['short_names', 'unified', 'search']); +// decompress +const emojis = {}; +data.emojis.forEach(compressedEmoji => { + const [ short_names, unified, search ] = compressedEmoji; + emojis[short_names[0]] = { + short_names, + unified, + search, + }; }); -// JSON.parse/stringify is to emulate what @preval is doing and avoid any -// inconsistent behavior in dev mode -module.exports = JSON.parse(JSON.stringify({ - emojis: condensedEmojis, - skins: data.skins, - categories: data.categories, - short_names: data.short_names, -})); +data.emojis = emojis; + +module.exports = data; -- cgit From e6543d5fc4d4f6ec7020d104e4d2360ee9bd7679 Mon Sep 17 00:00:00 2001 From: m4sk1n Date: Tue, 3 Oct 2017 21:15:41 +0200 Subject: i18n: Update Polish translation (#5202) --- app/javascript/mastodon/locales/pl.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index fe76284a9..c8228c0cb 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -56,14 +56,14 @@ "confirmations.block.confirm": "Zablokuj", "confirmations.block.message": "Czy na pewno chcesz zablokować {name}?", "confirmations.delete.confirm": "Usuń", - "confirmations.delete.message": "Czy na pewno chcesz usunąć ten status?", + "confirmations.delete.message": "Czy na pewno chcesz usunąć ten wpis?", "confirmations.domain_block.confirm": "Ukryj wszysyko z domeny", "confirmations.domain_block.message": "Czy na pewno chcesz zablokować całą domenę {domain}? Zwykle lepszym rozwiązaniem jest blokada lub wyciszenie kilku użytkowników.", "confirmations.mute.confirm": "Wycisz", "confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?", "confirmations.unfollow.confirm": "Przestań śledzić", "confirmations.unfollow.message": "Czy na pewno zamierzasz przestać śledzić {name}?", - "embed.instructions": "Osadź ten status na swojej stronie wklejając poniższy kod.", + "embed.instructions": "Osadź ten wpis na swojej stronie wklejając poniższy kod.", "embed.preview": "Tak będzie to wyglądać:", "emoji_button.activity": "Aktywność", "emoji_button.custom": "Niestandardowe", @@ -116,10 +116,10 @@ "navigation_bar.pins": "Przypięte wpisy", "navigation_bar.preferences": "Preferencje", "navigation_bar.public_timeline": "Oś czasu federacji", - "notification.favourite": "{name} dodał Twój status do ulubionych", + "notification.favourite": "{name} dodał Twój wpis do ulubionych", "notification.follow": "{name} zaczął Cię śledzić", "notification.mention": "{name} wspomniał o tobie", - "notification.reblog": "{name} podbił Twój status", + "notification.reblog": "{name} podbił Twój wpis", "notifications.clear": "Wyczyść powiadomienia", "notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?", "notifications.column_settings.alert": "Powiadomienia na pulpicie", @@ -165,11 +165,11 @@ "report.submit": "Wyślij", "report.target": "Zgłaszanie {target}", "search.placeholder": "Szukaj", - "search_popout.search_format": "Advanced search format", + "search_popout.search_format": "Zaawansowane wyszukiwanie", "search_popout.tips.hashtag": "hashtag", - "search_popout.tips.status": "status", - "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", - "search_popout.tips.user": "user", + "search_popout.tips.status": "wpis", + "search_popout.tips.text": "Proste wyszukiwanie pasujących pseudonimów, nazw użytkowników i hashtagów", + "search_popout.tips.user": "użytkownik", "search_results.total": "{count, number} {count, plural, one {wynik} more {wyniki}}", "standalone.public_title": "Spojrzenie w głąb…", "status.cannot_reblog": "Ten wpis nie może zostać podbity", @@ -180,7 +180,7 @@ "status.media_hidden": "Zawartość multimedialna ukryta", "status.mention": "Wspomnij o @{name}", "status.mute_conversation": "Wycisz konwersację", - "status.open": "Rozszerz ten status", + "status.open": "Rozszerz ten wpis", "status.pin": "Przypnij do profilu", "status.reblog": "Podbij", "status.reblogged_by": "{name} podbił", -- cgit From ec13cfa4f940e9f9441ceff1f7389bb0e1bd61fb Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 4 Oct 2017 01:01:44 +0200 Subject: When a streaming API status arrives, sort it into conversations (#5206) --- app/javascript/mastodon/actions/timelines.js | 20 ++++++++++++++++++++ app/javascript/mastodon/reducers/contexts.js | 22 +++++++++++++++++----- 2 files changed, 37 insertions(+), 5 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 5c0cd93c7..cdaafd89c 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -17,6 +17,8 @@ export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'; export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; +export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE'; + export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) { return { type: TIMELINE_REFRESH_SUCCESS, @@ -30,6 +32,16 @@ export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) { export function updateTimeline(timeline, status) { return (dispatch, getState) => { const references = status.reblog ? getState().get('statuses').filter((item, itemId) => (itemId === status.reblog.id || item.get('reblog') === status.reblog.id)).map((_, itemId) => itemId) : []; + const parents = []; + + if (status.in_reply_to_id) { + let parent = getState().getIn(['statuses', status.in_reply_to_id]); + + while (parent.get('in_reply_to_id')) { + parents.push(parent.get('id')); + parent = getState().getIn(['statuses', parent.get('in_reply_to_id')]); + } + } dispatch({ type: TIMELINE_UPDATE, @@ -37,6 +49,14 @@ export function updateTimeline(timeline, status) { status, references, }); + + if (parents.length > 0) { + dispatch({ + type: TIMELINE_CONTEXT_UPDATE, + status, + references: parents, + }); + } }; }; diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js index 9bfc09aa7..d8924e908 100644 --- a/app/javascript/mastodon/reducers/contexts.js +++ b/app/javascript/mastodon/reducers/contexts.js @@ -1,6 +1,6 @@ import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; -import { TIMELINE_DELETE } from '../actions/timelines'; -import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; +import { TIMELINE_DELETE, TIMELINE_CONTEXT_UPDATE } from '../actions/timelines'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; const initialState = ImmutableMap({ ancestors: ImmutableMap(), @@ -8,8 +8,8 @@ const initialState = ImmutableMap({ }); const normalizeContext = (state, id, ancestors, descendants) => { - const ancestorsIds = ancestors.map(ancestor => ancestor.get('id')); - const descendantsIds = descendants.map(descendant => descendant.get('id')); + const ancestorsIds = ImmutableList(ancestors.map(ancestor => ancestor.id)); + const descendantsIds = ImmutableList(descendants.map(descendant => descendant.id)); return state.withMutations(map => { map.setIn(['ancestors', id], ancestorsIds); @@ -31,12 +31,24 @@ const deleteFromContexts = (state, id) => { return state; }; +const updateContext = (state, status, references) => { + return state.update('descendants', map => { + references.forEach(parentId => { + map = map.update(parentId, ImmutableList(), list => list.push(status.id)); + }); + + return map; + }); +}; + export default function contexts(state = initialState, action) { switch(action.type) { case CONTEXT_FETCH_SUCCESS: - return normalizeContext(state, action.id, fromJS(action.ancestors), fromJS(action.descendants)); + return normalizeContext(state, action.id, action.ancestors, action.descendants); case TIMELINE_DELETE: return deleteFromContexts(state, action.id); + case TIMELINE_CONTEXT_UPDATE: + return updateContext(state, action.status, action.references); default: return state; } -- cgit From 632178d7543f48f493a63afce0d3c6243aac5fae Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 4 Oct 2017 01:23:52 +0200 Subject: Use own, shorter relative timestamps (#5171) * Use own, shorter relative timestamps * Add acct to title tooltip of display name in statuses * Improve i18n of the relative times --- .../mastodon/components/relative_timestamp.js | 113 ++++++++++++++++++++- app/javascript/mastodon/components/status.js | 2 +- 2 files changed, 109 insertions(+), 6 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/components/relative_timestamp.js b/app/javascript/mastodon/components/relative_timestamp.js index 2717d2326..534d83fac 100644 --- a/app/javascript/mastodon/components/relative_timestamp.js +++ b/app/javascript/mastodon/components/relative_timestamp.js @@ -1,7 +1,15 @@ import React from 'react'; -import { injectIntl, FormattedRelative } from 'react-intl'; +import { injectIntl, defineMessages } from 'react-intl'; import PropTypes from 'prop-types'; +const messages = defineMessages({ + just_now: { id: 'relative_time.just_now', defaultMessage: 'now' }, + seconds: { id: 'relative_time.seconds', defaultMessage: '{number}s' }, + minutes: { id: 'relative_time.minutes', defaultMessage: '{number}m' }, + hours: { id: 'relative_time.hours', defaultMessage: '{number}h' }, + days: { id: 'relative_time.days', defaultMessage: '{number}d' }, +}); + const dateFormatOptions = { hour12: false, year: 'numeric', @@ -11,6 +19,47 @@ const dateFormatOptions = { minute: '2-digit', }; +const shortDateFormatOptions = { + month: 'numeric', + day: 'numeric', +}; + +const SECOND = 1000; +const MINUTE = 1000 * 60; +const HOUR = 1000 * 60 * 60; +const DAY = 1000 * 60 * 60 * 24; + +const MAX_DELAY = 2147483647; + +const selectUnits = delta => { + const absDelta = Math.abs(delta); + + if (absDelta < MINUTE) { + return 'second'; + } else if (absDelta < HOUR) { + return 'minute'; + } else if (absDelta < DAY) { + return 'hour'; + } + + return 'day'; +}; + +const getUnitDelay = units => { + switch (units) { + case 'second': + return SECOND; + case 'minute': + return MINUTE; + case 'hour': + return HOUR; + case 'day': + return DAY; + default: + return MAX_DELAY; + } +}; + @injectIntl export default class RelativeTimestamp extends React.Component { @@ -19,20 +68,74 @@ export default class RelativeTimestamp extends React.Component { timestamp: PropTypes.string.isRequired, }; - shouldComponentUpdate (nextProps) { + state = { + now: this.props.intl.now(), + }; + + shouldComponentUpdate (nextProps, nextState) { // As of right now the locale doesn't change without a new page load, // but we might as well check in case that ever changes. return this.props.timestamp !== nextProps.timestamp || - this.props.intl.locale !== nextProps.intl.locale; + this.props.intl.locale !== nextProps.intl.locale || + this.state.now !== nextState.now; + } + + componentWillReceiveProps (nextProps) { + if (this.props.timestamp !== nextProps.timestamp) { + this.setState({ now: this.props.intl.now() }); + } + } + + componentDidMount () { + this._scheduleNextUpdate(this.props, this.state); + } + + componentWillUpdate (nextProps, nextState) { + this._scheduleNextUpdate(nextProps, nextState); + } + + _scheduleNextUpdate (props, state) { + clearTimeout(this._timer); + + const { timestamp } = props; + const delta = (new Date(timestamp)).getTime() - state.now; + const unitDelay = getUnitDelay(selectUnits(delta)); + const unitRemainder = Math.abs(delta % unitDelay); + const updateInterval = 1000 * 10; + const delay = delta < 0 ? Math.max(updateInterval, unitDelay - unitRemainder) : Math.max(updateInterval, unitRemainder); + + this._timer = setTimeout(() => { + this.setState({ now: this.props.intl.now() }); + }, delay); } render () { const { timestamp, intl } = this.props; - const date = new Date(timestamp); + + const date = new Date(timestamp); + const delta = this.state.now - date.getTime(); + + let relativeTime; + + if (delta < 10 * SECOND) { + relativeTime = intl.formatMessage(messages.just_now); + } else if (delta < 3 * DAY) { + if (delta < MINUTE) { + relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) }); + } else if (delta < HOUR) { + relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) }); + } else if (delta < DAY) { + relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) }); + } else { + relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) }); + } + } else { + relativeTime = intl.formatDate(date, shortDateFormatOptions); + } return ( ); } diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 3716d522e..17482e57a 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -165,7 +165,7 @@ export default class Status extends ImmutablePureComponent {
    - +
    {statusAvatar}
    -- cgit From ecfa1c3f3bbe02fa619ac000da51eccd3acbdc8a Mon Sep 17 00:00:00 2001 From: MitarashiDango Date: Wed, 4 Oct 2017 23:28:39 +0900 Subject: fix error (When part of conversation has already been deleted.) (#5216) --- app/javascript/mastodon/actions/timelines.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index cdaafd89c..09abe2702 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -37,7 +37,7 @@ export function updateTimeline(timeline, status) { if (status.in_reply_to_id) { let parent = getState().getIn(['statuses', status.in_reply_to_id]); - while (parent.get('in_reply_to_id')) { + while (parent && parent.get('in_reply_to_id')) { parents.push(parent.get('id')); parent = getState().getIn(['statuses', parent.get('in_reply_to_id')]); } -- cgit From 32e8a87830f2b054f2a32ded4c41d91003503d14 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Thu, 5 Oct 2017 05:49:36 +0900 Subject: adjust public profile pages 2 (#5223) --- app/javascript/styles/accounts.scss | 17 ++++++++--------- app/javascript/styles/forms.scss | 1 + config/initializers/kaminari_config.rb | 3 +-- 3 files changed, 10 insertions(+), 11 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/styles/accounts.scss b/app/javascript/styles/accounts.scss index 744650554..30adf8cdc 100644 --- a/app/javascript/styles/accounts.scss +++ b/app/javascript/styles/accounts.scss @@ -69,12 +69,16 @@ position: relative; z-index: 2; margin-bottom: 30px; + overflow: hidden; + text-overflow: ellipsis; small { display: block; font-size: 14px; color: $ui-highlight-color; font-weight: 400; + overflow: hidden; + text-overflow: ellipsis; } } @@ -284,21 +288,15 @@ color: lighten($ui-base-color, 10%); } - @media screen and (max-width: 360px) { + @media screen and (max-width: 700px) { padding: 30px 20px; - a, - .current, - .next, - .prev, - .gap { + .page { display: none; } .next, - .prev, - .next a, - .prev a { + .prev { display: inline-block; } } @@ -375,6 +373,7 @@ height: 80px; border-radius: 80px; border: 2px solid $simple-background-color; + background: $simple-background-color; } } diff --git a/app/javascript/styles/forms.scss b/app/javascript/styles/forms.scss index d241c9d26..61fcf286f 100644 --- a/app/javascript/styles/forms.scss +++ b/app/javascript/styles/forms.scss @@ -515,6 +515,7 @@ code { .action-pagination { display: flex; + flex-wrap: wrap; align-items: center; .actions, diff --git a/config/initializers/kaminari_config.rb b/config/initializers/kaminari_config.rb index 27b183eeb..aa1517256 100644 --- a/config/initializers/kaminari_config.rb +++ b/config/initializers/kaminari_config.rb @@ -3,6 +3,5 @@ Kaminari.configure do |config| config.default_per_page = 40 config.window = 1 - config.left = 3 - config.right = 1 + config.outer_window = 1 end -- cgit From b9c76e2edbc372e1b472f6ba480631b79fe24722 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 5 Oct 2017 23:41:47 +0200 Subject: When processing custom emoji, ensure a non-animated version exists (#5230) Use the non-animated version in web UI, but return both in API --- app/javascript/mastodon/emoji.js | 2 +- app/javascript/mastodon/reducers/statuses.js | 2 +- app/lib/formatter.rb | 2 +- app/models/custom_emoji.rb | 2 +- app/serializers/rest/custom_emoji_serializer.rb | 6 +++++- 5 files changed, 9 insertions(+), 5 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/emoji.js b/app/javascript/mastodon/emoji.js index 1df2373d9..cf0077958 100644 --- a/app/javascript/mastodon/emoji.js +++ b/app/javascript/mastodon/emoji.js @@ -52,7 +52,7 @@ export const buildCustomEmojis = customEmojis => { customEmojis.forEach(emoji => { const shortcode = emoji.get('shortcode'); - const url = emoji.get('url'); + const url = emoji.get('static_url'); const name = shortcode.replace(':', ''); emojis.push({ diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 2d72b12e8..ed16e016f 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -60,7 +60,7 @@ const normalizeStatus = (state, status) => { const searchContent = [status.spoiler_text, status.content].join(' ').replace(/
    /g, '\n').replace(/<\/p>

    /g, '\n\n'); const emojiMap = normalStatus.emojis.reduce((obj, emoji) => { - obj[`:${emoji.shortcode}:`] = emoji.url; + obj[`:${emoji.shortcode}:`] = emoji.static_url; return obj; }, {}); diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 42cd72990..d7f6ec47b 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -92,7 +92,7 @@ class Formatter def encode_custom_emojis(html, emojis) return html if emojis.empty? - emoji_map = emojis.map { |e| [e.shortcode, full_asset_url(e.image.url)] }.to_h + emoji_map = emojis.map { |e| [e.shortcode, full_asset_url(e.image.url(:static))] }.to_h i = -1 inside_tag = false diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index e80c58155..9e9be5e12 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -21,7 +21,7 @@ class CustomEmoji < ApplicationRecord :(#{SHORTCODE_RE_FRAGMENT}): (?=[^[:alnum:]:]|$)/x - has_attached_file :image + has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce -strip' } } validates_attachment :image, content_type: { content_type: 'image/png' }, presence: true, size: { in: 0..50.kilobytes } validates :shortcode, uniqueness: { scope: :domain }, format: { with: /\A#{SHORTCODE_RE_FRAGMENT}\z/ }, length: { minimum: 2 } diff --git a/app/serializers/rest/custom_emoji_serializer.rb b/app/serializers/rest/custom_emoji_serializer.rb index b744dd4ec..b958e6a5d 100644 --- a/app/serializers/rest/custom_emoji_serializer.rb +++ b/app/serializers/rest/custom_emoji_serializer.rb @@ -3,9 +3,13 @@ class REST::CustomEmojiSerializer < ActiveModel::Serializer include RoutingHelper - attributes :shortcode, :url + attributes :shortcode, :url, :static_url def url full_asset_url(object.image.url) end + + def static_url + full_asset_url(object.image.url(:static)) + end end -- cgit From 7db0f8dcb2110b4ec8815bedc965cfbd01a59798 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 6 Oct 2017 01:07:59 +0200 Subject: Implement hotkeys for web UI (#5164) * Fix #2102 - Implement hotkeys Hotkeys on status list: - r to reply - m to mention author - f to favourite - b to boost - enter to open status - p to open author's profile - up or k to move up in the list - down or j to move down in the list - 1-9 to focus a status in one of the columns - n to focus the compose textarea - alt+n to start a brand new toot - backspace to navigate back * Add navigational hotkeys The key g followed by: - s: start - h: home - n: notifications - l: local timeline - t: federated timeline - f: favourites - u: own profile - p: pinned toots - b: blocked users - m: muted users * Add hotkey for focusing search, make escape un-focus compose/search * Fix focusing notifications column, fix hotkeys in compose textarea --- app/javascript/mastodon/actions/compose.js | 7 + .../mastodon/components/autosuggest_textarea.js | 14 +- .../mastodon/components/scrollable_list.js | 28 +-- app/javascript/mastodon/components/status.js | 116 ++++++++--- app/javascript/mastodon/components/status_list.js | 31 ++- .../mastodon/features/compose/components/search.js | 2 + .../notifications/components/notification.js | 115 ++++++++--- .../containers/notification_container.js | 9 +- .../mastodon/features/notifications/index.js | 28 ++- app/javascript/mastodon/features/status/index.js | 151 ++++++++++++-- app/javascript/mastodon/features/ui/index.js | 223 +++++++++++++++++---- app/javascript/mastodon/reducers/compose.js | 2 + app/javascript/styles/basics.scss | 13 +- app/javascript/styles/components.scss | 24 ++- package.json | 1 + yarn.lock | 21 ++ 16 files changed, 631 insertions(+), 154 deletions(-) (limited to 'app/javascript') diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 7ac33bdd0..ed4837ebd 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -16,6 +16,7 @@ export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL'; export const COMPOSE_REPLY = 'COMPOSE_REPLY'; export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL'; export const COMPOSE_MENTION = 'COMPOSE_MENTION'; +export const COMPOSE_RESET = 'COMPOSE_RESET'; export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST'; export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS'; export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL'; @@ -68,6 +69,12 @@ export function cancelReplyCompose() { }; }; +export function resetCompose() { + return { + type: COMPOSE_RESET, + }; +}; + export function mentionCompose(account, router) { return (dispatch, getState) => { dispatch({ diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js index 6f725885d..14a8d4c38 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.js +++ b/app/javascript/mastodon/components/autosuggest_textarea.js @@ -125,6 +125,16 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { this.props.onKeyDown(e); } + onKeyUp = e => { + if (e.key === 'Escape' && this.state.suggestionsHidden) { + document.querySelector('.ui').parentElement.focus(); + } + + if (this.props.onKeyUp) { + this.props.onKeyUp(e); + } + } + onBlur = () => { this.setState({ suggestionsHidden: true }); } @@ -173,7 +183,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { } render () { - const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props; + const { value, suggestions, disabled, placeholder, autoFocus } = this.props; const { suggestionsHidden } = this.state; const style = { direction: 'ltr' }; @@ -195,7 +205,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { value={value} onChange={this.onChange} onKeyDown={this.onKeyDown} - onKeyUp={onKeyUp} + onKeyUp={this.onKeyUp} onBlur={this.onBlur} onPaste={this.onPaste} style={style} diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js index c6b588765..ab9d48510 100644 --- a/app/javascript/mastodon/components/scrollable_list.js +++ b/app/javascript/mastodon/components/scrollable_list.js @@ -145,32 +145,6 @@ export default class ScrollableList extends PureComponent { return this._lastMouseMove !== null && ((new Date()) - this._lastMouseMove < 600); } - handleKeyDown = (e) => { - if (['PageDown', 'PageUp'].includes(e.key) || (e.ctrlKey && ['End', 'Home'].includes(e.key))) { - const article = (() => { - switch (e.key) { - case 'PageDown': - return e.target.nodeName === 'ARTICLE' && e.target.nextElementSibling; - case 'PageUp': - return e.target.nodeName === 'ARTICLE' && e.target.previousElementSibling; - case 'End': - return this.node.querySelector('[role="feed"] > article:last-of-type'); - case 'Home': - return this.node.querySelector('[role="feed"] > article:first-of-type'); - default: - return null; - } - })(); - - - if (article) { - e.preventDefault(); - article.focus(); - article.scrollIntoView(); - } - } - } - render () { const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props; const { fullscreen } = this.state; @@ -182,7 +156,7 @@ export default class ScrollableList extends PureComponent { if (isLoading || childrenCount > 0 || !emptyMessage) { scrollableArea = (

    -
    +
    {prepend} {React.Children.map(this.props.children, (child, index) => ( diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 17482e57a..70005436b 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -10,6 +10,8 @@ import StatusActionBar from './status_action_bar'; import { FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { MediaGallery, Video } from '../features/ui/util/async-components'; +import { HotKeys } from 'react-hotkeys'; +import classNames from 'classnames'; // We use the component (and not the container) since we do not want // to use the progress bar to show download progress @@ -39,6 +41,8 @@ export default class Status extends ImmutablePureComponent { autoPlayGif: PropTypes.bool, muted: PropTypes.bool, hidden: PropTypes.bool, + onMoveUp: PropTypes.func, + onMoveDown: PropTypes.func, }; state = { @@ -89,16 +93,62 @@ export default class Status extends ImmutablePureComponent { } handleOpenVideo = startTime => { - this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime); + this.props.onOpenVideo(this._properStatus().getIn(['media_attachments', 0]), startTime); + } + + handleHotkeyReply = e => { + e.preventDefault(); + this.props.onReply(this._properStatus(), this.context.router.history); + } + + handleHotkeyFavourite = () => { + this.props.onFavourite(this._properStatus()); + } + + handleHotkeyBoost = e => { + this.props.onReblog(this._properStatus(), e); + } + + handleHotkeyMention = e => { + e.preventDefault(); + this.props.onMention(this._properStatus().get('account'), this.context.router.history); + } + + handleHotkeyOpen = () => { + this.context.router.history.push(`/statuses/${this._properStatus().get('id')}`); + } + + handleHotkeyOpenProfile = () => { + this.context.router.history.push(`/accounts/${this._properStatus().getIn(['account', 'id'])}`); + } + + handleHotkeyMoveUp = () => { + this.props.onMoveUp(this.props.status.get('id')); + } + + handleHotkeyMoveDown = () => { + this.props.onMoveDown(this.props.status.get('id')); + } + + _properStatus () { + const { status } = this.props; + + if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') { + return status.get('reblog'); + } else { + return status; + } } render () { let media = null; - let statusAvatar; + let statusAvatar, prepend; - const { status, account, hidden, ...other } = this.props; + const { hidden } = this.props; const { isExpanded } = this.state; + let { status, account, ...other } = this.props; + if (status === null) { return null; } @@ -115,16 +165,15 @@ export default class Status extends ImmutablePureComponent { if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') { const display_name_html = { __html: status.getIn(['account', 'display_name_html']) }; - return ( -
    -
    -
    -
    }} /> -
    - - + prepend = ( +
    +
    + }} />
    ); + + account = status.get('account'); + status = status.get('reblog'); } if (status.get('media_attachments').size > 0 && !this.props.muted) { @@ -160,26 +209,43 @@ export default class Status extends ImmutablePureComponent { statusAvatar = ; } + const handlers = this.props.muted ? {} : { + reply: this.handleHotkeyReply, + favourite: this.handleHotkeyFavourite, + boost: this.handleHotkeyBoost, + mention: this.handleHotkeyMention, + open: this.handleHotkeyOpen, + openProfile: this.handleHotkeyOpenProfile, + moveUp: this.handleHotkeyMoveUp, + moveDown: this.handleHotkeyMoveDown, + }; + return ( -
    -
    - + +
    + {prepend} - -
    - {statusAvatar} -
    +
    + + - {media} + {media} - -
    + +
    +
    + ); } diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index cbae28afe..58a7b228a 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -25,18 +25,45 @@ export default class StatusList extends ImmutablePureComponent { trackScroll: true, }; + handleMoveUp = id => { + const elementIndex = this.props.statusIds.indexOf(id) - 1; + this._selectChild(elementIndex); + } + + handleMoveDown = id => { + const elementIndex = this.props.statusIds.indexOf(id) + 1; + this._selectChild(elementIndex); + } + + _selectChild (index) { + const element = this.node.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`); + + if (element) { + element.focus(); + } + } + + setRef = c => { + this.node = c; + } + render () { const { statusIds, ...other } = this.props; const { isLoading } = other; const scrollableContent = (isLoading || statusIds.size > 0) ? ( statusIds.map((statusId) => ( - + )) ) : null; return ( - + {scrollableContent} ); diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js index 79abffad8..4c3f0dcb5 100644 --- a/app/javascript/mastodon/features/compose/components/search.js +++ b/app/javascript/mastodon/features/compose/components/search.js @@ -74,6 +74,8 @@ export default class Search extends React.PureComponent { if (e.key === 'Enter') { e.preventDefault(); this.props.onSubmit(); + } else if (e.key === 'Escape') { + document.querySelector('.ui').parentElement.focus(); } } diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js index a608a5223..9d170cad5 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.js +++ b/app/javascript/mastodon/features/notifications/components/notification.js @@ -6,61 +6,126 @@ import AccountContainer from '../../../containers/account_container'; import { FormattedMessage } from 'react-intl'; import Permalink from '../../../components/permalink'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import { HotKeys } from 'react-hotkeys'; export default class Notification extends ImmutablePureComponent { + static contextTypes = { + router: PropTypes.object, + }; + static propTypes = { notification: ImmutablePropTypes.map.isRequired, hidden: PropTypes.bool, + onMoveUp: PropTypes.func.isRequired, + onMoveDown: PropTypes.func.isRequired, + onMention: PropTypes.func.isRequired, }; + handleMoveUp = () => { + const { notification, onMoveUp } = this.props; + onMoveUp(notification.get('id')); + } + + handleMoveDown = () => { + const { notification, onMoveDown } = this.props; + onMoveDown(notification.get('id')); + } + + handleOpen = () => { + const { notification } = this.props; + + if (notification.get('status')) { + this.context.router.history.push(`/statuses/${notification.get('status')}`); + } else { + this.handleOpenProfile(); + } + } + + handleOpenProfile = () => { + const { notification } = this.props; + this.context.router.history.push(`/accounts/${notification.getIn(['account', 'id'])}`); + } + + handleMention = e => { + e.preventDefault(); + + const { notification, onMention } = this.props; + onMention(notification.get('account'), this.context.router.history); + } + + getHandlers () { + return { + moveUp: this.handleMoveUp, + moveDown: this.handleMoveDown, + open: this.handleOpen, + openProfile: this.handleOpenProfile, + mention: this.handleMention, + reply: this.handleMention, + }; + } + renderFollow (account, link) { return ( -
    -
    -
    - + +
    +
    +
    + +
    + +
    - +
    - -
    + ); } renderMention (notification) { - return