diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf4be7da..eb6a4599 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,12 +4,29 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
+### Added
+- Added a quick settings to timeline header for easier access
+- Added option to mark posts as sensitive by default
+
+## [2.3.0] - 2021-03-01
### Fixed
- Button to remove uploaded media in post status form is now properly placed and sized.
- Fixed shoutbox not working in mobile layout
+- Fixed missing highlighted border in expanded conversations again
+- Fixed some UI jumpiness when opening images particularly in chat view
+- Fixed chat unread badge looking weird
+- Fixed punycode names not working properly
+- Fixed notifications crashing on an invalid notification
### Changed
- Display 'people voted' instead of 'votes' for multi-choice polls
+- Optimized chat to not get horrible performance after keeping the same chat open for a long time
+- When opening emoji picker or react picker, it automatically focuses the search field
+- Language picker now uses native language names
+
+### Added
+- Added reason field for registration when approval is required
+- Group staff members by role in the About page
## [2.2.3] - 2021-01-18
### Added
@@ -21,7 +38,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed
- Don't filter own posts when they hit your wordfilter
-- Language picker now uses native language names
## [2.2.2] - 2020-12-22
@@ -31,7 +47,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Added some missing unicode emoji
- Added the upload limit to the Features panel in the About page
- Support for solid color wallpaper, instance doesn't have to define a wallpaper anymore
-- Group staff members by role in the About page
### Fixed
- Fixed the occasional bug where screen would scroll 1px when typing into a reply form
diff --git a/package.json b/package.json
index e11396bf..372155df 100644
--- a/package.json
+++ b/package.json
@@ -103,7 +103,7 @@
"selenium-server": "2.53.1",
"semver": "^5.3.0",
"serviceworker-webpack-plugin": "^1.0.0",
- "shelljs": "^0.7.4",
+ "shelljs": "^0.8.4",
"sinon": "^2.1.0",
"sinon-chai": "^2.8.0",
"stylelint": "^13.6.1",
diff --git a/src/App.scss b/src/App.scss
index 8b91f3de..90d083bb 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -586,6 +586,7 @@ nav {
color: var(--faint, $fallback--faint);
box-shadow: 0px 0px 4px rgba(0,0,0,.6);
box-shadow: var(--topBarShadow);
+ box-sizing: border-box;
}
.fade-enter-active, .fade-leave-active {
@@ -878,6 +879,11 @@ nav {
overflow: hidden;
height: 100%;
+ // Get rid of scrollbar on body as scrolling happens on different element
+ body {
+ overflow: hidden;
+ }
+
// Ensures the fixed position of the mobile browser bars on scroll up / down events.
// Prevents the mobile browser bars from overlapping or hiding the message posting form.
@media all and (max-width: 800px) {
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index b472fcf6..45090e5d 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -51,6 +51,7 @@ const getInstanceConfig = async ({ store }) => {
const vapidPublicKey = data.pleroma.vapid_public_key
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
+ store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue
index 9e410610..c53f6a9c 100644
--- a/src/components/basic_user_card/basic_user_card.vue
+++ b/src/components/basic_user_card/basic_user_card.vue
@@ -42,7 +42,7 @@
class="basic-user-card-screen-name"
:to="userProfileLink(user)"
>
- @{{ user.screen_name }}
+ @{{ user.screen_name_ui }}
diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js
index e57fcb91..b54f5fb2 100644
--- a/src/components/chat/chat.js
+++ b/src/components/chat/chat.js
@@ -73,7 +73,7 @@ const Chat = {
},
formPlaceholder () {
if (this.recipient) {
- return this.$t('chats.message_user', { nickname: this.recipient.screen_name })
+ return this.$t('chats.message_user', { nickname: this.recipient.screen_name_ui })
} else {
return ''
}
@@ -234,6 +234,13 @@ const Chat = {
const scrollable = this.$refs.scrollable
return scrollable && scrollable.scrollTop <= 0
},
+ cullOlderCheck () {
+ window.setTimeout(() => {
+ if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
+ this.$store.dispatch('cullOlderMessages', this.currentChatMessageService.chatId)
+ }
+ }, 5000)
+ },
handleScroll: _.throttle(function () {
if (!this.currentChat) { return }
@@ -241,6 +248,7 @@ const Chat = {
this.fetchChat({ maxId: this.currentChatMessageService.minId })
} else if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
this.jumpToBottomButtonVisible = false
+ this.cullOlderCheck()
if (this.newMessageCount > 0) {
// Use a delay before marking as read to prevent situation where new messages
// arrive just as you're leaving the view and messages that you didn't actually
diff --git a/src/components/chat/chat.scss b/src/components/chat/chat.scss
index aef58495..3a26686c 100644
--- a/src/components/chat/chat.scss
+++ b/src/components/chat/chat.scss
@@ -98,10 +98,10 @@
.unread-message-count {
font-size: 0.8em;
left: 50%;
- transform: translate(-50%, 0);
- border-radius: 100%;
margin-top: -1rem;
- padding: 0;
+ padding: 0.1em;
+ border-radius: 50px;
+ position: absolute;
}
.chat-loading-error {
diff --git a/src/components/chat_title/chat_title.js b/src/components/chat_title/chat_title.js
index e424bb1f..edfbe7a4 100644
--- a/src/components/chat_title/chat_title.js
+++ b/src/components/chat_title/chat_title.js
@@ -12,7 +12,7 @@ export default Vue.component('chat-title', {
],
computed: {
title () {
- return this.user ? this.user.screen_name : ''
+ return this.user ? this.user.screen_name_ui : ''
},
htmlTitle () {
return this.user ? this.user.name_html : ''
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index 353859b8..3fb26d92 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -50,7 +50,6 @@
.Conversation {
.conversation-status {
- border-left: none;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: var(--border, $fallback--border);
diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
index 2068a598..dc03bc9f 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -194,11 +194,18 @@ const EmojiInput = {
}
},
methods: {
+ focusPickerInput () {
+ const pickerEl = this.$refs.picker.$el
+ if (!pickerEl) return
+ const pickerInput = pickerEl.querySelector('input')
+ if (pickerInput) pickerInput.focus()
+ },
triggerShowPicker () {
this.showPicker = true
this.$refs.picker.startEmojiLoad()
this.$nextTick(() => {
this.scrollIntoView()
+ this.focusPickerInput()
})
// This temporarily disables "click outside" handler
// since external trigger also means click originates
@@ -214,6 +221,7 @@ const EmojiInput = {
if (this.showPicker) {
this.scrollIntoView()
this.$refs.picker.startEmojiLoad()
+ this.$nextTick(this.focusPickerInput)
}
},
replace (replacement) {
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 1d6c760e..ad62484d 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -9,8 +9,8 @@
diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js
index 14a2b41e..e8efbd1e 100644
--- a/src/components/emoji_input/suggestor.js
+++ b/src/components/emoji_input/suggestor.js
@@ -116,8 +116,8 @@ export const suggestUsers = ({ dispatch, state }) => {
return diff + nameAlphabetically + screenNameAlphabetically
/* eslint-disable camelcase */
- }).map(({ screen_name, name, profile_image_url_original }) => ({
- displayText: screen_name,
+ }).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({
+ displayText: screen_name_ui,
detailText: name,
imageUrl: profile_image_url_original,
replacement: '@' + screen_name + ' '
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index ea7f7a7f..54bc5335 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -73,11 +73,21 @@
}
}
+@keyframes media-fadein {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
.modal-image {
max-width: 90%;
max-height: 90%;
box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
image-orientation: from-image; // NOTE: only FF supports this
+ animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
}
.modal-view-button-arrow {
diff --git a/src/components/mfa_form/recovery_form.vue b/src/components/mfa_form/recovery_form.vue
index 8457ec5d..7c594228 100644
--- a/src/components/mfa_form/recovery_form.vue
+++ b/src/components/mfa_form/recovery_form.vue
@@ -25,16 +25,16 @@
- {{ $t('user_reporting.title', [user.screen_name]) }}
+ {{ $t('user_reporting.title', [user.screen_name_ui]) }}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 7faff08e..09d87600 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -228,6 +228,8 @@
"username_placeholder": "e.g. lain",
"fullname_placeholder": "e.g. Lain Iwakura",
"bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.",
+ "reason": "Reason to register",
+ "reason_placeholder": "This instance approves registrations manually.\nLet the administration know why you want to register.",
"validations": {
"username_required": "cannot be left blank",
"fullname_required": "cannot be left blank",
@@ -325,6 +327,7 @@
"export_theme": "Save preset",
"filtering": "Filtering",
"filtering_explanation": "All statuses containing these words will be muted, one per line",
+ "word_filter": "Word filter",
"follow_export": "Follow export",
"follow_export_button": "Export your follows to a csv file",
"follow_import": "Follow import",
@@ -335,7 +338,9 @@
"general": "General",
"hide_attachments_in_convo": "Hide attachments in conversations",
"hide_attachments_in_tl": "Hide attachments in timeline",
+ "hide_media_previews": "Hide media previews",
"hide_muted_posts": "Hide posts of muted users",
+ "hide_all_muted_posts": "Hide muted posts",
"max_thumbnails": "Maximum amount of thumbnails per post",
"hide_isp": "Hide instance-specific panel",
"hide_wallpaper": "Hide instance wallpaper",
@@ -405,6 +410,8 @@
"reply_visibility_all": "Show all replies",
"reply_visibility_following": "Only show replies directed at me or users I'm following",
"reply_visibility_self": "Only show replies directed at me",
+ "reply_visibility_following_short": "Show replies to my follows",
+ "reply_visibility_self_short": "Show replies to self only",
"autohide_floating_post_button": "Automatically hide New Post button (mobile)",
"saving_err": "Error saving settings",
"saving_ok": "Settings saved",
@@ -431,6 +438,7 @@
"subject_line_mastodon": "Like mastodon: copy as is",
"subject_line_noop": "Do not copy",
"post_status_content_type": "Post status content type",
+ "sensitive_by_default": "Mark posts as sensitive by default",
"stop_gifs": "Play-on-hover GIFs",
"streaming": "Enable automatic streaming of new posts when scrolled to the top",
"user_mutes": "Users",
@@ -460,6 +468,7 @@
"notification_mutes": "To stop receiving notifications from a specific user, use a mute.",
"notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.",
"enable_web_push_notifications": "Enable web push notifications",
+ "more_settings": "More settings",
"style": {
"switcher": {
"keep_color": "Keep colors",
diff --git a/src/i18n/eo.json b/src/i18n/eo.json
index 58f8e125..9f0491fb 100644
--- a/src/i18n/eo.json
+++ b/src/i18n/eo.json
@@ -35,7 +35,11 @@
"retry": "Reprovi",
"error_retry": "Bonvolu reprovi",
"loading": "Enlegante…",
- "peek": "Antaŭmontri"
+ "peek": "Antaŭmontri",
+ "role": {
+ "moderator": "Reguligisto",
+ "admin": "Administranto"
+ }
},
"image_cropper": {
"crop_picture": "Tondi bildon",
@@ -365,7 +369,8 @@
"post": "Afiŝoj/Priskriboj de uzantoj",
"alert_neutral": "Neŭtrala",
"alert_warning": "Averto",
- "toggled": "Ŝaltita"
+ "toggled": "Ŝaltita",
+ "wallpaper": "Fonbildo"
},
"radii": {
"_tab_label": "Rondeco"
@@ -516,7 +521,9 @@
"mute_import_error": "Eraris enporto de silentigoj",
"mute_import": "Enporto de silentigoj",
"mute_export_button": "Elportu viajn silentigojn al CSV-dosiero",
- "mute_export": "Elporto de silentigoj"
+ "mute_export": "Elporto de silentigoj",
+ "hide_wallpaper": "Kaŝi fonbildon de nodo",
+ "setting_changed": "Agordo malsamas de la implicita"
},
"timeline": {
"collapse": "Maletendi",
@@ -586,7 +593,8 @@
"show_repeats": "Montri ripetojn",
"hide_repeats": "Kaŝi ripetojn",
"unsubscribe": "Ne ricevi sciigojn",
- "subscribe": "Ricevi sciigojn"
+ "subscribe": "Ricevi sciigojn",
+ "bot": "Roboto"
},
"user_profile": {
"timeline_title": "Historio de uzanto",
@@ -612,7 +620,8 @@
"error": {
"base": "Alŝuto malsukcesis.",
"file_too_big": "Dosiero estas tro granda [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
- "default": "Reprovu pli poste"
+ "default": "Reprovu pli poste",
+ "message": "Malsukcesis alŝuto: {0}"
},
"file_size_units": {
"B": "B",
@@ -645,7 +654,9 @@
"votes": "voĉoj",
"option": "Elekteblo",
"add_option": "Aldoni elekteblon",
- "add_poll": "Aldoni enketon"
+ "add_poll": "Aldoni enketon",
+ "votes_count": "{count} voĉdono | {count} voĉdonoj",
+ "people_voted_count": "{count} persono voĉdonis | {count} personoj voĉdonis"
},
"importer": {
"error": "Eraris enporto de ĉi tiu dosiero.",
@@ -732,7 +743,9 @@
"repeats": "Ripetoj",
"favorites": "Ŝatoj",
"status_deleted": "Ĉi tiu afiŝo foriĝis",
- "nsfw": "Konsterna"
+ "nsfw": "Konsterna",
+ "expand": "Etendi",
+ "external_source": "Ekstera fonto"
},
"time": {
"years_short": "{0}j",
diff --git a/src/i18n/it.json b/src/i18n/it.json
index 0a3d0ce2..32028262 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -584,7 +584,9 @@
"fullname_placeholder": "es. Lupo Lucio",
"username_placeholder": "es. mister_wolf",
"new_captcha": "Clicca l'immagine per avere un altro captcha",
- "captcha": "CAPTCHA"
+ "captcha": "CAPTCHA",
+ "reason_placeholder": "L'amministratore esamina ciascuna richiesta.\nFornisci il motivo della tua iscrizione.",
+ "reason": "Motivo dell'iscrizione"
},
"user_profile": {
"timeline_title": "Sequenza dell'Utente",
diff --git a/src/i18n/ja_pedantic.json b/src/i18n/ja_pedantic.json
index f54add7b..846c793a 100644
--- a/src/i18n/ja_pedantic.json
+++ b/src/i18n/ja_pedantic.json
@@ -201,7 +201,9 @@
"password_required": "必須",
"password_confirmation_required": "必須",
"password_confirmation_match": "パスワードが違います"
- }
+ },
+ "reason_placeholder": "このインスタンスは、新規登録を手動で受け付けています。\n登録したい理由を、インスタンスの管理者に教えてください。",
+ "reason": "登録するための目的"
},
"selectable_list": {
"select_all": "すべて選択"
@@ -411,8 +413,8 @@
"contrast": {
"hint": "コントラストは {ratio} です。{level}。({context})",
"level": {
- "aa": "AAレベルガイドライン (ミニマル) を満たします",
- "aaa": "AAAレベルガイドライン (レコメンデッド) を満たします",
+ "aa": "AAレベルガイドライン (最低限) を満たします",
+ "aaa": "AAAレベルガイドライン (推奨) を満たします",
"bad": "ガイドラインを満たしません"
},
"context": {
@@ -571,7 +573,8 @@
"mute_export": "ミュートのエクスポート",
"allow_following_move": "フォロー中のアカウントが引っ越したとき、自動フォローを許可する",
"setting_changed": "規定の設定と異なっています",
- "greentext": "引用を緑色で表示"
+ "greentext": "引用を緑色で表示",
+ "sensitive_by_default": "はじめから投稿をセンシティブとして設定"
},
"time": {
"day": "{0}日",
diff --git a/src/i18n/ko.json b/src/i18n/ko.json
index 101ef487..c8d213f3 100644
--- a/src/i18n/ko.json
+++ b/src/i18n/ko.json
@@ -35,7 +35,11 @@
"retry": "다시 시도하십시오",
"error_retry": "다시 시도하십시오",
"generic_error": "잘못되었습니다",
- "more": "더 보기"
+ "more": "더 보기",
+ "role": {
+ "moderator": "중재자",
+ "admin": "관리자"
+ }
},
"login": {
"login": "로그인",
@@ -85,7 +89,8 @@
"repeated_you": "당신의 게시물을 리핏",
"no_more_notifications": "알림이 없습니다",
"migrated_to": "이사했습니다",
- "reacted_with": "{0} 로 반응했습니다"
+ "reacted_with": "{0} 로 반응했습니다",
+ "error": "알림 불러오기 실패: {0}"
},
"post_status": {
"new_status": "새 게시물 게시",
@@ -93,7 +98,10 @@
"account_not_locked_warning_link": "잠김",
"attachments_sensitive": "첨부물을 민감함으로 설정",
"content_type": {
- "text/plain": "평문"
+ "text/plain": "평문",
+ "text/bbcode": "BBCode",
+ "text/markdown": "Markdown",
+ "text/html": "HTML"
},
"content_warning": "주제 (필수 아님)",
"default": "인천공항에 도착했습니다.",
@@ -106,7 +114,13 @@
"unlisted": "비공개 - 공개 타임라인에 게시 안 함"
},
"preview_empty": "아무것도 없습니다",
- "preview": "미리보기"
+ "preview": "미리보기",
+ "scope_notice": {
+ "public": "이 글은 누구나 볼 수 있습니다"
+ },
+ "media_description_error": "파일을 올리지 못하였습니다. 다시한번 시도하여 주십시오",
+ "empty_status_error": "글을 입력하십시오",
+ "media_description": "첨부파일 설명"
},
"registration": {
"bio": "소개",
@@ -288,7 +302,16 @@
"borders": "테두리",
"buttons": "버튼",
"inputs": "입력칸",
- "faint_text": "흐려진 텍스트"
+ "faint_text": "흐려진 텍스트",
+ "chat": {
+ "border": "경계선",
+ "outgoing": "송신",
+ "incoming": "수신"
+ },
+ "selectedMenu": "선택된 메뉴 요소",
+ "selectedPost": "선택된 글",
+ "icons": "아이콘",
+ "alert_warning": "경고"
},
"radii": {
"_tab_label": "둥글기"
@@ -364,9 +387,25 @@
"generate_new_recovery_codes": "새로운 복구 코드를 작성",
"title": "2단계인증",
"confirm_and_enable": "OTP 확인과 활성화",
- "setup_otp": "OTP 설치"
+ "setup_otp": "OTP 설치",
+ "otp": "OTP"
},
- "security": "보안"
+ "security": "보안",
+ "emoji_reactions_on_timeline": "이모지 반응을 타임라인으로 표시",
+ "avatar_size_instruction": "크기를 150x150 이상으로 설정할 것을 추장합니다.",
+ "blocks_tab": "차단",
+ "notification_setting_privacy": "보안",
+ "user_mutes": "사용자",
+ "notification_visibility_emoji_reactions": "반응",
+ "profile_fields": {
+ "value": "내용"
+ },
+ "mutes_and_blocks": "침묵과 차단",
+ "chatMessageRadius": "챗 메시지",
+ "change_email": "전자메일 주소 바꾸기",
+ "changed_email": "메일주소가 갱신되었습니다!",
+ "bot": "이 계정은 bot입니다",
+ "mutes_tab": "침묵"
},
"timeline": {
"collapse": "접기",
@@ -445,7 +484,11 @@
"votes": "표",
"vote": "투표",
"type": "투표 형식",
- "expiry": "투표 기간"
+ "expiry": "투표 기간",
+ "votes_count": "{count} 표 | {count} 표",
+ "people_voted_count": "{count} 명 투표 | {count} 명 투표",
+ "option": "선택지",
+ "add_option": "선택지 추가"
},
"media_modal": {
"next": "다음",
@@ -500,5 +543,44 @@
},
"federation": "연합"
}
+ },
+ "shoutbox": {
+ "title": "Shoutbox"
+ },
+ "time": {
+ "years_short": "{0} 년",
+ "year_short": "{0} 년",
+ "years": "{0} 년",
+ "year": "{0} 년",
+ "weeks_short": "{0} 주일",
+ "week_short": "{0} 주일",
+ "weeks": "{0} 주일",
+ "week": "{0} 주일",
+ "seconds_short": "{0} 초",
+ "second_short": "{0} 초",
+ "seconds": "{0} 초",
+ "second": "{0} 초",
+ "now_short": "방금",
+ "now": "방끔",
+ "months_short": "{0} 달 전",
+ "month_short": "{0} 달 전",
+ "months": "{0} 달 전",
+ "month": "{0} 달 전",
+ "minutes_short": "{0} 분",
+ "minute_short": "{0} 분",
+ "minutes": "{0} 분",
+ "minute": "{0} 분",
+ "in_past": "{0} 전",
+ "hours_short": "{0} 시간",
+ "hour_short": "{0} 시간",
+ "hours": "{0} 시간",
+ "hour": "{0} 시간",
+ "days_short": "{0} 일",
+ "day_short": "{0} 일",
+ "days": "{0} 일",
+ "day": "{0} 일"
+ },
+ "remote_user_resolver": {
+ "error": "찾을 수 없습니다."
}
}
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
index b4185b30..5e377f7c 100644
--- a/src/i18n/zh.json
+++ b/src/i18n/zh.json
@@ -39,7 +39,11 @@
"close": "关闭",
"retry": "重试",
"error_retry": "请重试",
- "loading": "载入中…"
+ "loading": "载入中…",
+ "role": {
+ "moderator": "监察员",
+ "admin": "管理员"
+ }
},
"image_cropper": {
"crop_picture": "裁剪图片",
@@ -120,7 +124,9 @@
"expiry": "投票期限",
"expires_in": "投票于 {0} 后结束",
"expired": "投票 {0} 前已结束",
- "not_enough_options": "投票的选项太少"
+ "not_enough_options": "投票的选项太少",
+ "votes_count": "{count} 票 | {count} 票",
+ "people_voted_count": "{count} 人已投票 | {count} 人已投票"
},
"stickers": {
"add_sticker": "添加贴纸"
@@ -183,7 +189,9 @@
"password_required": "不能留空",
"password_confirmation_required": "不能留空",
"password_confirmation_match": "密码不一致"
- }
+ },
+ "reason_placeholder": "此实例的注册需要手动批准。\n请让管理员知道您为什么想要注册。",
+ "reason": "注册理由"
},
"selectable_list": {
"select_all": "选择全部"
@@ -552,7 +560,8 @@
"mute_import": "隐藏名单导入",
"mute_export_button": "导出你的隐藏名单到一个 csv 文件",
"mute_export": "隐藏名单导出",
- "hide_wallpaper": "隐藏实例壁纸"
+ "hide_wallpaper": "隐藏实例壁纸",
+ "setting_changed": "与默认设置不同"
},
"time": {
"day": "{0} 天",
@@ -683,7 +692,8 @@
"show_repeats": "显示转发",
"hide_repeats": "隐藏转发",
"message": "消息",
- "mention": "提及"
+ "mention": "提及",
+ "bot": "机器人"
},
"user_profile": {
"timeline_title": "用户时间线",
diff --git a/src/modules/chats.js b/src/modules/chats.js
index 0a373d88..69d683bd 100644
--- a/src/modules/chats.js
+++ b/src/modules/chats.js
@@ -115,6 +115,9 @@ const chats = {
},
handleMessageError ({ commit }, value) {
commit('handleMessageError', { commit, ...value })
+ },
+ cullOlderMessages ({ commit }, chatId) {
+ commit('cullOlderMessages', chatId)
}
},
mutations: {
@@ -227,6 +230,9 @@ const chats = {
handleMessageError (state, { chatId, fakeId, isRetry }) {
const chatMessageService = state.openedChatMessageServices[chatId]
chatService.handleMessageError(chatMessageService, fakeId, isRetry)
+ },
+ cullOlderMessages (state, chatId) {
+ chatService.cullOlderMessages(state.openedChatMessageServices[chatId])
}
}
}
diff --git a/src/modules/config.js b/src/modules/config.js
index f992519e..eca58c12 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -67,7 +67,8 @@ export const defaultState = {
greentext: undefined, // instance default
hidePostStats: undefined, // instance default
hideUserStats: undefined, // instance default
- virtualScrolling: undefined // instance default
+ virtualScrolling: undefined, // instance default
+ sensitiveByDefault: undefined // instance default
}
// caching the instance default properties
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 411b1caa..96de73ca 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -43,6 +43,7 @@ const defaultState = {
subjectLineBehavior: 'email',
theme: 'pleroma-dark',
virtualScrolling: true,
+ sensitiveByDefault: false,
// Nasty stuff
customEmoji: [],
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 33c68c57..ac5d25c4 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -13,7 +13,11 @@ import {
omitBy
} from 'lodash'
import { set } from 'vue'
-import { isStatusNotification, maybeShowNotification } from '../services/notification_utils/notification_utils.js'
+import {
+ isStatusNotification,
+ isValidNotification,
+ maybeShowNotification
+} from '../services/notification_utils/notification_utils.js'
import apiService from '../services/api/api.service.js'
const emptyTl = (userId = 0) => ({
@@ -310,8 +314,24 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
}
}
+const updateNotificationsMinMaxId = (state, notification) => {
+ state.notifications.maxId = notification.id > state.notifications.maxId
+ ? notification.id
+ : state.notifications.maxId
+ state.notifications.minId = notification.id < state.notifications.minId
+ ? notification.id
+ : state.notifications.minId
+}
+
const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters, newNotificationSideEffects }) => {
each(notifications, (notification) => {
+ // If invalid notification, update ids but don't add it to store
+ if (!isValidNotification(notification)) {
+ console.error('Invalid notification:', notification)
+ updateNotificationsMinMaxId(state, notification)
+ return
+ }
+
if (isStatusNotification(notification.type)) {
notification.action = addStatusToGlobalStorage(state, notification.action).item
notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
@@ -323,12 +343,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
// Only add a new notification if we don't have one for the same action
if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
- state.notifications.maxId = notification.id > state.notifications.maxId
- ? notification.id
- : state.notifications.maxId
- state.notifications.minId = notification.id < state.notifications.minId
- ? notification.id
- : state.notifications.minId
+ updateNotificationsMinMaxId(state, notification)
state.notifications.data.push(notification)
state.notifications.idStore[notification.id] = notification
diff --git a/src/services/chat_service/chat_service.js b/src/services/chat_service/chat_service.js
index e653ebc1..92ff689d 100644
--- a/src/services/chat_service/chat_service.js
+++ b/src/services/chat_service/chat_service.js
@@ -48,6 +48,22 @@ const deleteMessage = (storage, messageId) => {
}
}
+const cullOlderMessages = (storage) => {
+ const maxIndex = storage.messages.length
+ const minIndex = maxIndex - 50
+ if (maxIndex <= 50) return
+
+ storage.messages = _.sortBy(storage.messages, ['id'])
+ storage.minId = storage.messages[minIndex].id
+ for (const message of storage.messages) {
+ if (message.id < storage.minId) {
+ delete storage.idIndex[message.id]
+ delete storage.idempotencyKeyIndex[message.idempotency_key]
+ }
+ }
+ storage.messages = storage.messages.slice(minIndex, maxIndex)
+}
+
const handleMessageError = (storage, fakeId, isRetry) => {
if (!storage) { return }
const fakeMessage = storage.idIndex[fakeId]
@@ -201,6 +217,7 @@ const ChatService = {
empty,
getView,
deleteMessage,
+ cullOlderMessages,
resetNewMessageCount,
clear,
handleMessageError
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 6ed663e1..9b2b30e6 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -203,7 +203,8 @@ export const parseUser = (data) => {
output.rights = output.rights || {}
output.notification_settings = output.notification_settings || {}
- // Convert punycode to unicode
+ // Convert punycode to unicode for UI
+ output.screen_name_ui = output.screen_name
if (output.screen_name.includes('@')) {
const parts = output.screen_name.split('@')
let unicodeDomain = punycode.toUnicode(parts[1])
@@ -211,7 +212,7 @@ export const parseUser = (data) => {
// Add some identifier so users can potentially spot spoofing attempts:
// lain.com and xn--lin-6cd.com would appear identical otherwise.
unicodeDomain = '🌏' + unicodeDomain
- output.screen_name = [parts[0], unicodeDomain].join('@')
+ output.screen_name_ui = [parts[0], unicodeDomain].join('@')
}
}
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
index d912d19f..6fef1022 100644
--- a/src/services/notification_utils/notification_utils.js
+++ b/src/services/notification_utils/notification_utils.js
@@ -22,6 +22,13 @@ const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reactio
export const isStatusNotification = (type) => includes(statusNotifications, type)
+export const isValidNotification = (notification) => {
+ if (isStatusNotification(notification.type) && !notification.status) {
+ return false
+ }
+ return true
+}
+
const sortById = (a, b) => {
const seqA = Number(a.id)
const seqB = Number(b.id)
diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js
index 80092b41..142db73c 100644
--- a/test/unit/specs/components/user_profile.spec.js
+++ b/test/unit/specs/components/user_profile.spec.js
@@ -31,13 +31,15 @@ const testGetters = {
const localUser = {
id: 100,
is_local: true,
- screen_name: 'testUser'
+ screen_name: 'testUser',
+ screen_name_ui: 'testUser'
}
const extUser = {
id: 100,
is_local: false,
- screen_name: 'testUser@test.instance'
+ screen_name: 'testUser@test.instance',
+ screen_name_ui: 'testUser@test.instance'
}
const externalProfileStore = new Vuex.Store({
diff --git a/test/unit/specs/services/chat_service/chat_service.spec.js b/test/unit/specs/services/chat_service/chat_service.spec.js
index 0251cae7..fbbca436 100644
--- a/test/unit/specs/services/chat_service/chat_service.spec.js
+++ b/test/unit/specs/services/chat_service/chat_service.spec.js
@@ -88,4 +88,21 @@ describe('chatService', () => {
expect(view.map(i => i.type)).to.eql(['date', 'message', 'message', 'date', 'message'])
})
})
+
+ describe('.cullOlderMessages', () => {
+ it('keeps 50 newest messages and idIndex matches', () => {
+ const chat = chatService.empty()
+
+ for (let i = 100; i > 0; i--) {
+ // Use decimal values with toFixed to hack together constant length predictable strings
+ chatService.add(chat, { messages: [{ ...message1, id: 'a' + (i / 1000).toFixed(3), idempotency_key: i }] })
+ }
+ chatService.cullOlderMessages(chat)
+ expect(chat.messages.length).to.eql(50)
+ expect(chat.messages[0].id).to.eql('a0.051')
+ expect(chat.minId).to.eql('a0.051')
+ expect(chat.messages[49].id).to.eql('a0.100')
+ expect(Object.keys(chat.idIndex).length).to.eql(50)
+ })
+ })
})
diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
index a3f49b2c..759539e0 100644
--- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
+++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
@@ -315,7 +315,7 @@ describe('API Entities normalizer', () => {
it('converts IDN to unicode and marks it as internatonal', () => {
const user = makeMockUserMasto({ acct: 'lain@xn--lin-6cd.com' })
- expect(parseUser(user)).to.have.property('screen_name').that.equal('lain@🌏lаin.com')
+ expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@🌏lаin.com')
})
})
diff --git a/yarn.lock b/yarn.lock
index 6682841b..6d0095db 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7842,9 +7842,10 @@ shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
-shelljs@^0.7.4:
- version "0.7.8"
- resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3"
+shelljs@^0.8.4:
+ version "0.8.4"
+ resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2"
+ integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==
dependencies:
glob "^7.0.0"
interpret "^1.0.0"