diff --git a/CHANGELOG.md b/CHANGELOG.md index d08da09e59..2719edcf0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,15 @@ # Changelog 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/), +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added - Ability to hide/show repeats from user -- User profile button clutter organized into a menu +- User profile button clutter organized into a menu - Emoji picker - Started changelog anew +- Ability to change user's email ### Changed - changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes ### Fixed diff --git a/src/boot/routes.js b/src/boot/routes.js index 5670236c77..7400a682ca 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -18,6 +18,7 @@ import AuthForm from 'components/auth_form/auth_form.js' import ChatPanel from 'components/chat_panel/chat_panel.vue' import WhoToFollow from 'components/who_to_follow/who_to_follow.vue' import About from 'components/about/about.vue' +import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue' export default (store) => { const validateAuthenticatedRoute = (to, from, next) => { @@ -42,6 +43,16 @@ export default (store) => { { name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute }, { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline }, { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, + { name: 'remote-user-profile-acct', + path: '/remote-users/(@?):username([^/@]+)@:hostname([^/@]+)', + component: RemoteUserResolver, + beforeEnter: validateAuthenticatedRoute + }, + { name: 'remote-user-profile', + path: '/remote-users/:hostname/:username', + component: RemoteUserResolver, + beforeEnter: validateAuthenticatedRoute + }, { name: 'external-user-profile', path: '/users/:id', component: UserProfile }, { name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute }, { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute }, diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue index 5917598afc..1113f81d86 100644 --- a/src/components/checkbox/checkbox.vue +++ b/src/components/checkbox/checkbox.vue @@ -12,8 +12,8 @@ > diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 0d5ffc9b74..0f397b59f3 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -1,8 +1,11 @@ -import { set } from 'vue' +import Checkbox from '../checkbox/checkbox.vue' -const LOAD_EMOJI_BY = 50 -const LOAD_EMOJI_INTERVAL = 100 -const LOAD_EMOJI_SANE_AMOUNT = 500 +// At widest, approximately 20 emoji are visible in a row, +// loading 3 rows, could be overkill for narrow picker +const LOAD_EMOJI_BY = 60 + +// When to start loading new batch emoji, in pixels +const LOAD_EMOJI_MARGIN = 64 const filterByKeyword = (list, keyword = '') => { return list.filter(x => x.displayText.includes(keyword)) @@ -18,27 +21,37 @@ const EmojiPicker = { }, data () { return { - labelKey: String(Math.random() * 100000), keyword: '', activeGroup: 'custom', showingStickers: false, groupsScrolledClass: 'scrolled-top', keepOpen: false, - customEmojiBuffer: (this.$store.state.instance.customEmoji || []) - .slice(0, LOAD_EMOJI_BY), + customEmojiBufferSlice: LOAD_EMOJI_BY, customEmojiTimeout: null, - customEmojiCounter: LOAD_EMOJI_BY, customEmojiLoadAllConfirmed: false } }, components: { - StickerPicker: () => import('../sticker_picker/sticker_picker.vue') + StickerPicker: () => import('../sticker_picker/sticker_picker.vue'), + Checkbox }, methods: { + onStickerUploaded (e) { + this.$emit('sticker-uploaded', e) + }, + onStickerUploadFailed (e) { + this.$emit('sticker-upload-failed', e) + }, onEmoji (emoji) { const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen }) }, + onScroll (e) { + const target = (e && e.target) || this.$refs['emoji-groups'] + this.updateScrolledClass(target) + this.scrolledGroup(target) + this.triggerLoadMore(target) + }, highlight (key) { const ref = this.$refs['group-' + key] const top = ref[0].offsetTop @@ -48,9 +61,7 @@ const EmojiPicker = { this.$refs['emoji-groups'].scrollTop = top + 1 }) }, - scrolledGroup (e) { - const target = (e && e.target) || this.$refs['emoji-groups'] - const top = target.scrollTop + 5 + updateScrolledClass (target) { if (target.scrollTop <= 5) { this.groupsScrolledClass = 'scrolled-top' } else if (target.scrollTop >= target.scrollTopMax - 5) { @@ -58,6 +69,28 @@ const EmojiPicker = { } else { this.groupsScrolledClass = 'scrolled-middle' } + }, + triggerLoadMore (target) { + const ref = this.$refs['group-end-custom'][0] + if (!ref) return + const bottom = ref.offsetTop + ref.offsetHeight + + const scrollerBottom = target.scrollTop + target.clientHeight + const scrollerTop = target.scrollTop + const scrollerMax = target.scrollHeight + + // Loads more emoji when they come into view + const approachingBottom = bottom - scrollerBottom < LOAD_EMOJI_MARGIN + // Always load when at the very top in case there's no scroll space yet + const atTop = scrollerTop < 5 + // Don't load when looking at unicode category or at the very bottom + const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax + if (!bottomAboveViewport && (approachingBottom || atTop)) { + this.loadEmoji() + } + }, + scrolledGroup (target) { + const top = target.scrollTop + 5 this.$nextTick(() => { this.emojisView.forEach(group => { const ref = this.$refs['group-' + group.id] @@ -67,67 +100,40 @@ const EmojiPicker = { }) }) }, - loadEmojiInsane () { - this.customEmojiLoadAllConfirmed = true - this.continueEmojiLoad() - }, loadEmoji () { const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length - const saneLoaded = this.customEmojiBuffer.length >= LOAD_EMOJI_SANE_AMOUNT && - !this.customEmojiLoadAllConfirmed - if (allLoaded || saneLoaded) { + if (allLoaded) { return } - this.customEmojiBuffer.push( - ...this.filteredEmoji.slice( - this.customEmojiCounter, - this.customEmojiCounter + LOAD_EMOJI_BY - ) - ) - this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL) - this.customEmojiCounter += LOAD_EMOJI_BY + this.customEmojiBufferSlice += LOAD_EMOJI_BY }, startEmojiLoad (forceUpdate = false) { + if (!forceUpdate) { + this.keyword = '' + } + this.$nextTick(() => { + this.$refs['emoji-groups'].scrollTop = 0 + }) const bufferSize = this.customEmojiBuffer.length - const bufferPrefilledSane = bufferSize === LOAD_EMOJI_SANE_AMOUNT && !this.customEmojiLoadAllConfirmed const bufferPrefilledAll = bufferSize === this.filteredEmoji.length - if (forceUpdate || bufferPrefilledSane || bufferPrefilledAll) { + if (bufferPrefilledAll && !forceUpdate) { return } - if (this.customEmojiTimeout) { - window.clearTimeout(this.customEmojiTimeout) - } - - set( - this, - 'customEmojiBuffer', - this.filteredEmoji.slice(0, LOAD_EMOJI_BY) - ) - this.customEmojiCounter = LOAD_EMOJI_BY - this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL) - }, - continueEmojiLoad () { - this.customEmojiTimeout = window.setTimeout(this.loadEmoji, LOAD_EMOJI_INTERVAL) + this.customEmojiBufferSlice = LOAD_EMOJI_BY }, toggleStickers () { this.showingStickers = !this.showingStickers }, setShowStickers (value) { this.showingStickers = value - }, - onStickerUploaded (e) { - this.$emit('sticker-uploaded', e) - }, - onStickerUploadFailed (e) { - this.$emit('sticker-upload-failed', e) } }, watch: { keyword () { this.customEmojiLoadAllConfirmed = false - this.scrolledGroup() + this.onScroll() this.startEmojiLoad(true) } }, @@ -141,19 +147,14 @@ const EmojiPicker = { } return 0 }, - saneAmount () { - // for UI - return LOAD_EMOJI_SANE_AMOUNT - }, filteredEmoji () { return filterByKeyword( this.$store.state.instance.customEmoji || [], this.keyword ) }, - askForSanity () { - return this.customEmojiBuffer.length >= LOAD_EMOJI_SANE_AMOUNT && - !this.customEmojiLoadAllConfirmed + customEmojiBuffer () { + return this.filteredEmoji.slice(0, this.customEmojiBufferSlice) }, emojis () { const standardEmojis = this.$store.state.instance.emoji || [] diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue index 43da6aa238..191b9fa1b1 100644 --- a/src/components/emoji_picker/emoji_picker.vue +++ b/src/components/emoji_picker/emoji_picker.vue @@ -47,7 +47,7 @@ ref="emoji-groups" class="emoji-groups" :class="groupsScrolledClass" - @scroll="scrolledGroup" + @scroll="onScroll" >
+
@@ -80,20 +81,6 @@ {{ $t('emoji.keep_open') }}
-
-
- {{ $t('emoji.load_all_hint', { saneAmount } ) }} -
- -