diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/post_status_form/post_status_form.js | 54 | ||||
| -rw-r--r-- | src/components/post_status_form/post_status_form.vue | 24 | ||||
| -rw-r--r-- | src/components/user_profile/user_profile.vue | 2 | ||||
| -rw-r--r-- | src/i18n/messages.js | 109 | ||||
| -rw-r--r-- | src/services/api/api.service.js | 3 |
5 files changed, 176 insertions, 16 deletions
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index c1213fa9..999aa732 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -41,6 +41,7 @@ const PostStatusForm = { submitDisabled: false, error: null, posting: false, + highlighted: 0, newStatus: { status: statusText, files: [] @@ -57,23 +58,26 @@ const PostStatusForm = { return false } // eslint-disable-next-line camelcase - return map(take(matchedUsers, 5), ({screen_name, name, profile_image_url_original}) => ({ + return map(take(matchedUsers, 5), ({screen_name, name, profile_image_url_original}, index) => ({ // eslint-disable-next-line camelcase screen_name: `@${screen_name}`, name: name, - img: profile_image_url_original + img: profile_image_url_original, + highlighted: index === this.highlighted })) } else if (firstchar === ':') { + if (this.textAtCaret === ':') { return } const matchedEmoji = filter(this.emoji.concat(this.customEmoji), (emoji) => emoji.shortcode.match(this.textAtCaret.slice(1))) if (matchedEmoji.length <= 0) { return false } - return map(take(matchedEmoji, 5), ({shortcode, image_url, utf}) => ({ + return map(take(matchedEmoji, 5), ({shortcode, image_url, utf}, index) => ({ // eslint-disable-next-line camelcase screen_name: `:${shortcode}:`, name: '', utf: utf || '', - img: image_url + img: image_url, + highlighted: index === this.highlighted })) } else { return false @@ -106,6 +110,9 @@ const PostStatusForm = { }, charactersLeft () { return this.statusLengthLimit - this.statusLength + }, + isOverLengthLimit () { + return this.hasStatusLengthLimit && (this.statusLength > this.statusLengthLimit) } }, methods: { @@ -115,6 +122,45 @@ const PostStatusForm = { el.focus() this.caret = 0 }, + replaceCandidate (e) { + const len = this.candidates.length || 0 + if (this.textAtCaret === ':' || e.ctrlKey) { return } + if (len > 0) { + e.preventDefault() + const candidate = this.candidates[this.highlighted] + const replacement = candidate.utf || (candidate.screen_name + ' ') + this.newStatus.status = Completion.replaceWord(this.newStatus.status, this.wordAtCaret, replacement) + const el = this.$el.querySelector('textarea') + el.focus() + this.caret = 0 + this.highlighted = 0 + } + }, + cycleBackward (e) { + const len = this.candidates.length || 0 + if (len > 0) { + e.preventDefault() + this.highlighted -= 1 + if (this.highlighted < 0) { + this.highlighted = this.candidates.length - 1 + } + } else { + this.highlighted = 0 + } + }, + cycleForward (e) { + const len = this.candidates.length || 0 + if (len > 0) { + if (e.shiftKey) { return } + e.preventDefault() + this.highlighted += 1 + if (this.highlighted >= len) { + this.highlighted = 0 + } + } else { + this.highlighted = 0 + } + }, setCaret ({target: {selectionStart}}) { this.caret = selectionStart }, diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index a759bb53..4871bcae 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -2,26 +2,32 @@ <div class="post-status-form"> <form @submit.prevent="postStatus(newStatus)"> <div class="form-group base03-border" > - <textarea @click="setCaret" @keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control" @keydown.meta.enter="postStatus(newStatus)" @keyup.ctrl.enter="postStatus(newStatus)" @drop="fileDrop" @dragover.prevent="fileDrag" @input="resize" @paste="paste"></textarea> + <textarea @click="setCaret" @keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control" @keydown.down="cycleForward" @keydown.up="cycleBackward" @keydown.shift.tab="cycleBackward" @keydown.tab="cycleForward" @keydown.enter="replaceCandidate" @keydown.meta.enter="postStatus(newStatus)" @keyup.ctrl.enter="postStatus(newStatus)" @drop="fileDrop" @dragover.prevent="fileDrag" @input="resize" @paste="paste"></textarea> </div> <div style="position:relative;" v-if="candidates"> <div class="autocomplete-panel base05-background"> - <div v-for="candidate in candidates" @click="replace(candidate.utf || (candidate.screen_name + ' '))" class="autocomplete base02"> - <span v-if="candidate.img"><img :src="candidate.img"></img></span> - <span v-else>{{candidate.utf}}</span> - <span> - {{candidate.screen_name}} - <small class="base02">{{candidate.name}}</small> - </span> + <div v-for="candidate in candidates" @click="replace(candidate.utf || (candidate.screen_name + ' '))"> + <div v-if="candidate.highlighted" class="autocomplete base02"> + <span v-if="candidate.img"><img :src="candidate.img"></span> + <span v-else>{{candidate.utf}}</span> + <span>{{candidate.screen_name}}<small class="base02">{{candidate.name}}</small></span> + </div> + <div v-else class="autocomplete base04"> + <span v-if="candidate.img"><img :src="candidate.img"></img></span> + <span v-else>{{candidate.utf}}</span> + <span>{{candidate.screen_name}}<small class="base02">{{candidate.name}}</small></span> + </div> </div> </div> </div> <div class='form-bottom'> <media-upload @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="enableSubmit" :drop-files="dropFiles"></media-upload> - <p v-if="hasStatusLengthLimit" class="base04">{{ charactersLeft }}</p> + <p v-if="isOverLengthLimit" class="error">{{ charactersLeft }}</p> + <p v-else-if="hasStatusLengthLimit" class="base04">{{ charactersLeft }}</p> <button v-if="posting" disabled class="btn btn-default base05 base02-background">{{$t('post_status.posting')}}</button> + <button v-else-if="isOverLengthLimit" disabled class="btn btn-default base05 base02-background">{{$t('general.submit')}}</button> <button v-else :disabled="submitDisabled" type="submit" class="btn btn-default base05 base02-background">{{$t('general.submit')}}</button> </div> <div class='error' v-if="error"> diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index ec90b8b0..359abfef 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -3,7 +3,7 @@ <div v-if="user" class="user-profile panel panel-default base00-background"> <user-card-content :user="user" :switcher="true"></user-card-content> </div> - <Timeline :title="'User Timeline'" v-bind:timeline="timeline" v-bind:timeline-name="'user'" :user-id="userId"/> + <Timeline :title="$t('user_profile.timeline_title')" :timeline="timeline" :timeline-name="'user'" :user-id="userId"/> </div> </template> diff --git a/src/i18n/messages.js b/src/i18n/messages.js index 6779420d..a2ce3dff 100644 --- a/src/i18n/messages.js +++ b/src/i18n/messages.js @@ -283,6 +283,9 @@ const en = { general: { submit: 'Submit', apply: 'Apply' + }, + user_profile: { + timeline_title: 'User Timeline' } } @@ -1038,6 +1041,109 @@ const pt = { } } +const ru = { + chat: { + title: 'Чат' + }, + nav: { + chat: 'Локальный чат', + timeline: 'Лента', + mentions: 'Упоминания', + public_tl: 'Публичная лента', + twkn: 'Федеративная лента' + }, + user_card: { + follows_you: 'Читает вас', + following: 'Читаю', + follow: 'Читать', + blocked: 'Заблокирован', + block: 'Заблокировать', + statuses: 'Статусы', + mute: 'Игнорировать', + muted: 'Игнорирую', + followers: 'Читатели', + followees: 'Читаемые', + per_day: 'в день', + remote_follow: 'Читать удалённо' + }, + timeline: { + show_new: 'Показать новые', + error_fetching: 'Ошибка при обновлении', + up_to_date: 'Обновлено', + load_older: 'Загрузить старые статусы', + conversation: 'Разговор' + }, + settings: { + user_settings: 'Настройки пользователя', + name_bio: 'Имя и описание', + name: 'Имя', + bio: 'Описание', + avatar: 'Аватар', + current_avatar: 'Текущий аватар', + set_new_avatar: 'Загрузить новый аватар', + profile_banner: 'Баннер профиля', + current_profile_banner: 'Текущий баннер профиля', + set_new_profile_banner: 'Загрузить новый баннер профиля', + profile_background: 'Фон профиля', + set_new_profile_background: 'Загрузить новый фон профиля', + settings: 'Настройки', + theme: 'Тема', + presets: 'Пресеты', + theme_help: 'Используйте шестнадцатеричные коды цветов (#aabbcc) для настройки темы.', + background: 'Фон', + foreground: 'Передний план', + text: 'Текст', + links: 'Ссылки', + filtering: 'Фильтрация', + filtering_explanation: 'Все статусы, содержащие данные слова, будут игнорироваться, по одному в строке', + attachments: 'Вложения', + hide_attachments_in_tl: 'Прятать вложения в ленте', + hide_attachments_in_convo: 'Прятать вложения в разговорах', + nsfw_clickthrough: 'Включить скрытие NSFW вложений', + autoload: 'Включить автоматическую загрузку при прокрутке вниз', + streaming: 'Включить автоматическую загрузку новых сообщений при прокрутке вверх', + reply_link_preview: 'Включить предварительный просмотр ответа при наведении мыши', + follow_import: 'Импортировать читаемых', + import_followers_from_a_csv_file: 'Импортировать читаемых из файла .csv', + follows_imported: 'Список читаемых импортирован. Обработка займёт некоторое время..', + follow_import_error: 'Ошибка при импортировании читаемых.' + }, + notifications: { + notifications: 'Уведомления', + read: 'Прочесть', + followed_you: 'начал читать вас' + }, + login: { + login: 'Войти', + username: 'Имя пользователя', + password: 'Пароль', + register: 'Зарегистрироваться', + logout: 'Выйти' + }, + registration: { + registration: 'Регистрация', + fullname: 'Отображаемое имя', + email: 'Email', + bio: 'Описание', + password_confirm: 'Подтверждение пароля' + }, + post_status: { + posting: 'Отправляется', + default: 'Что нового?' + }, + finder: { + find_user: 'Найти пользователя', + error_fetching_user: 'Пользователь не найден' + }, + general: { + submit: 'Отправить', + apply: 'Применить' + }, + user_profile: { + timeline_title: 'Лента пользователя' + } +} + const messages = { de, fi, @@ -1050,7 +1156,8 @@ const messages = { it, pl, es, - pt + pt, + ru } export default messages diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 1f5b3ad2..f14bfd6d 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -128,7 +128,8 @@ const updateProfile = ({credentials, params}) => { const form = new FormData() each(params, (value, key) => { - if (value) { + if (key === 'description' || /* Always include description, because it might be empty */ + value) { form.append(key, value) } }) |
