logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git
commit: 485f4b899ccad57694b7a45535936b7a5de8c4fb
parent 98cb9abac773e8fbce94eafea1025919b6f26360
Author: Shpuld Shpuldson <shp@cock.li>
Date:   Mon, 22 Feb 2021 18:11:27 +0200

changelog conflict

Diffstat:

MCHANGELOG.md6++++++
Msrc/App.scss6++++++
Msrc/boot/after_store.js1+
Msrc/components/chat/chat.js8++++++++
Msrc/components/media_modal/media_modal.vue10++++++++++
Msrc/components/poll/poll_form.vue1+
Msrc/components/registration/registration.js17+++++++++++++----
Msrc/components/registration/registration.vue17+++++++++++++++++
Msrc/i18n/en.json2++
Msrc/i18n/eo.json27++++++++++++++++++++-------
Msrc/i18n/it.json4+++-
Msrc/i18n/ja_pedantic.json4+++-
Msrc/i18n/ko.json98++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/i18n/zh.json20+++++++++++++++-----
Msrc/modules/chats.js6++++++
Msrc/services/chat_service/chat_service.js17+++++++++++++++++
Mtest/unit/specs/services/chat_service/chat_service.spec.js17+++++++++++++++++
17 files changed, 235 insertions(+), 26 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -7,11 +7,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Button to remove uploaded media in post status form is now properly placed and sized. - Fixed shoutbox not working in mobile layout +- Fixed some UI jumpiness when opening images particularly in chat view ### Changed - Display 'people voted' instead of 'votes' for multi-choice polls - Changed the "Timelines" link in side panel to toggle show all timeline options inside the panel - Renamed "Timeline" to "Home Timeline" to be more clear +- Optimized chat to not get horrible performance after keeping the same chat open for a long time + +### Added +- Added reason field for registration when approval is required +>>>>>>> develop ## [2.2.3] - 2021-01-18 ### Added diff --git 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 @@ -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/chat/chat.js b/src/components/chat/chat.js @@ -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/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/poll/poll_form.vue b/src/components/poll/poll_form.vue @@ -151,6 +151,7 @@ border: none; box-shadow: none; background-color: transparent; + padding-right: 0.75em; } } diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js @@ -10,7 +10,8 @@ const registration = { fullname: '', username: '', password: '', - confirm: '' + confirm: '', + reason: '' }, captcha: {} }), @@ -24,7 +25,8 @@ const registration = { confirm: { required, sameAsPassword: sameAs('password') - } + }, + reason: { required: requiredIf(() => this.accountApprovalRequired) } } } }, @@ -38,7 +40,10 @@ const registration = { computed: { token () { return this.$route.params.token }, bioPlaceholder () { - return this.$t('registration.bio_placeholder').replace(/\s*\n\s*/g, ' \n') + return this.replaceNewlines(this.$t('registration.bio_placeholder')) + }, + reasonPlaceholder () { + return this.replaceNewlines(this.$t('registration.reason_placeholder')) }, ...mapState({ registrationOpen: (state) => state.instance.registrationOpen, @@ -46,7 +51,8 @@ const registration = { isPending: (state) => state.users.signUpPending, serverValidationErrors: (state) => state.users.signUpErrors, termsOfService: (state) => state.instance.tos, - accountActivationRequired: (state) => state.instance.accountActivationRequired + accountActivationRequired: (state) => state.instance.accountActivationRequired, + accountApprovalRequired: (state) => state.instance.accountApprovalRequired }) }, methods: { @@ -73,6 +79,9 @@ const registration = { }, setCaptcha () { this.getCaptcha().then(cpt => { this.captcha = cpt }) + }, + replaceNewlines (str) { + return str.replace(/\s*\n\s*/g, ' \n') } } } diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue @@ -163,6 +163,23 @@ </div> <div + v-if="accountApprovalRequired" + class="form-group" + > + <label + class="form--label" + for="reason" + >{{ $t('registration.reason') }}</label> + <textarea + id="reason" + v-model="user.reason" + :disabled="isPending" + class="form-control" + :placeholder="reasonPlaceholder" + /> + </div> + + <div v-if="captcha.type != 'none'" id="captcha-group" class="form-group" diff --git a/src/i18n/en.json b/src/i18n/en.json @@ -229,6 +229,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", diff --git 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 @@ -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 @@ -201,7 +201,9 @@ "password_required": "必須", "password_confirmation_required": "必須", "password_confirmation_match": "パスワードが違います" - } + }, + "reason_placeholder": "このインスタンスは、新規登録を手動で受け付けています。\n登録したい理由を、インスタンスの管理者に教えてください。", + "reason": "登録するための目的" }, "selectable_list": { "select_all": "すべて選択" diff --git 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 @@ -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 @@ -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/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/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) + }) + }) })