aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/post_status_form/post_status_form.js54
-rw-r--r--src/components/post_status_form/post_status_form.vue24
-rw-r--r--src/components/user_profile/user_profile.vue2
-rw-r--r--src/i18n/messages.js109
-rw-r--r--src/services/api/api.service.js3
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)
}
})