From 16f01e1d3ff64300c89060b3374d28ed8fb2a751 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 14 Aug 2021 21:10:24 -0400 Subject: [PATCH 01/34] Group custom emojis by pack in emoji picker --- src/components/emoji_picker/emoji_picker.js | 29 ++++++++++++++----- src/components/emoji_picker/emoji_picker.scss | 13 +++++++++ src/components/emoji_picker/emoji_picker.vue | 10 +++++++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index f69202087..9e3981761 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -183,17 +183,32 @@ const EmojiPicker = { customEmojiBuffer () { return this.filteredEmoji.slice(0, this.customEmojiBufferSlice) }, + groupedCustomEmojis () { + const packOf = emoji => (emoji.tags.filter(k => k.startsWith('pack:'))[0] || '').slice(5) + return this.customEmojiBuffer.reduce((res, emoji) => { + const pack = packOf(emoji) + if (!res[pack]) { + res[pack] = { + id: `custom-${pack}`, + text: pack, + /// FIXME + // icon: 'smile-beam', + image: emoji.imageUrl, + emojis: [] + } + } + res[pack].emojis.push(emoji) + return res + }, {}) + }, emojis () { const standardEmojis = this.$store.state.instance.emoji || [] - const customEmojis = this.customEmojiBuffer + // const customEmojis = this.customEmojiBuffer return [ - { - id: 'custom', - text: this.$t('emoji.custom'), - icon: 'smile-beam', - emojis: customEmojis - }, + ...Object + .keys(this.groupedCustomEmojis) + .map(k => this.groupedCustomEmojis[k]), { id: 'standard', text: this.$t('emoji.unicode'), diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss index a2f17c514..ccb12a2a4 100644 --- a/src/components/emoji_picker/emoji_picker.scss +++ b/src/components/emoji_picker/emoji_picker.scss @@ -19,6 +19,19 @@ --lightText: var(--popoverLightText, $fallback--lightText); --icon: var(--popoverIcon, $fallback--icon); + &-header-image { + display: inline-flex; + justify-content: center; + align-items: center; + width: 30px; + height: 24px; + img { + max-width: 100%; + max-height: 100%; + object-fit: contain; + } + } + .keep-open, .too-many-emoji { padding: 7px; diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue index a72691207..16549c08d 100644 --- a/src/components/emoji_picker/emoji_picker.vue +++ b/src/components/emoji_picker/emoji_picker.vue @@ -13,7 +13,17 @@ :title="group.text" @click.prevent="highlight(group.id)" > + + + From d0b052a17e588b9838b136211c5db506bfd71b53 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 14 Aug 2021 21:23:45 -0400 Subject: [PATCH 02/34] Fix load more emoji action --- src/components/emoji_picker/emoji_picker.js | 5 ++++- src/modules/instance.js | 12 +++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 9e3981761..7d5a3d8f3 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -98,7 +98,7 @@ const EmojiPicker = { } }, triggerLoadMore (target) { - const ref = this.$refs['group-end-custom'] + const ref = this.$refs[`group-end-${this.lastNonUnicodeGroupId}`][0] if (!ref) return const bottom = ref.offsetTop + ref.offsetHeight @@ -217,6 +217,9 @@ const EmojiPicker = { } ] }, + lastNonUnicodeGroupId () { + return this.emojis[this.emojis.length - 2].id + }, emojisView () { return this.emojis.filter(value => value.emojis.length > 0) }, diff --git a/src/modules/instance.js b/src/modules/instance.js index bfce6f384..23f534c34 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -164,6 +164,16 @@ const instance = { if (res.ok) { const result = await res.json() const values = Array.isArray(result) ? Object.assign({}, ...result) : result + const caseInsensitiveStrCmp = (a, b) => { + const la = a.toLowerCase() + const lb = b.toLowerCase() + return la > lb ? 1 : (la < lb ? -1 : 0) + } + const byPackThenByName = (a, b) => { + const packOf = emoji => (emoji.tags.filter(k => k.startsWith('pack:'))[0] || '').slice(5) + return caseInsensitiveStrCmp(packOf(a), packOf(b)) || caseInsensitiveStrCmp(a.displayText, b.displayText) + } + const emoji = Object.entries(values).map(([key, value]) => { const imageUrl = value.image_url return { @@ -174,7 +184,7 @@ const instance = { } // Technically could use tags but those are kinda useless right now, // should have been "pack" field, that would be more useful - }).sort((a, b) => a.displayText.toLowerCase() > b.displayText.toLowerCase() ? 1 : -1) + }).sort(byPackThenByName) commit('setInstanceOption', { name: 'customEmoji', value: emoji }) } else { throw (res) From c9557ef28c6f723695854b5e95f7b62c63a6e5e4 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 14 Aug 2021 21:50:58 -0400 Subject: [PATCH 03/34] Display all emoji groups on emoji picker header --- src/components/emoji_picker/emoji_picker.js | 28 +++++++++++++++++-- src/components/emoji_picker/emoji_picker.scss | 6 +++- src/components/emoji_picker/emoji_picker.vue | 4 +-- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 7d5a3d8f3..d04649dcc 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -38,6 +38,8 @@ const filterByKeyword = (list, keyword = '') => { return orderedEmojiList.flat() } +const packOf = emoji => (emoji.tags.filter(k => k.startsWith('pack:'))[0] || '').slice(5) + const EmojiPicker = { props: { enableStickerPicker: { @@ -174,9 +176,12 @@ const EmojiPicker = { } return 0 }, + allEmojis () { + return this.$store.state.instance.customEmoji || [] + }, filteredEmoji () { return filterByKeyword( - this.$store.state.instance.customEmoji || [], + this.allEmojis, trim(this.keyword) ) }, @@ -184,7 +189,6 @@ const EmojiPicker = { return this.filteredEmoji.slice(0, this.customEmojiBufferSlice) }, groupedCustomEmojis () { - const packOf = emoji => (emoji.tags.filter(k => k.startsWith('pack:'))[0] || '').slice(5) return this.customEmojiBuffer.reduce((res, emoji) => { const pack = packOf(emoji) if (!res[pack]) { @@ -201,6 +205,26 @@ const EmojiPicker = { return res }, {}) }, + allEmojiGroups () { + return this.allEmojis + .reduce((res, emoji) => { + const packName = packOf(emoji) + const packId = `custom-${packName}` + if (res.filter(k => k.id === packId).length === 0) { + res.push({ + id: packId, + text: packName, + image: emoji.imageUrl + }) + } + return res + }, []) + .concat({ + id: 'standard', + text: this.$t('emoji.unicode'), + icon: 'box-open' + }) + }, emojis () { const standardEmojis = this.$store.state.instance.emoji || [] // const customEmojis = this.customEmojiBuffer diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss index ccb12a2a4..0bd4363cf 100644 --- a/src/components/emoji_picker/emoji_picker.scss +++ b/src/components/emoji_picker/emoji_picker.scss @@ -52,6 +52,7 @@ display: flex; height: 32px; padding: 10px 7px 5px; + overflow-x: auto; } .content { @@ -63,6 +64,9 @@ .emoji-tabs { flex-grow: 1; + display: flex; + flex-direction: row; + flex-wrap: nowrap; } .emoji-groups { @@ -70,6 +74,7 @@ } .additional-tabs { + display: block; border-left: 1px solid; border-left-color: $fallback--icon; border-left-color: var(--icon, $fallback--icon); @@ -79,7 +84,6 @@ .additional-tabs, .emoji-tabs { - display: block; min-width: 0; flex-basis: auto; flex-shrink: 1; diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue index 16549c08d..fe60cb5ef 100644 --- a/src/components/emoji_picker/emoji_picker.vue +++ b/src/components/emoji_picker/emoji_picker.vue @@ -3,12 +3,12 @@
Date: Sat, 14 Aug 2021 23:37:00 -0400 Subject: [PATCH 04/34] Load visible emoji groups when scrolling --- src/components/emoji_picker/emoji_picker.js | 101 ++++++++++++++----- src/components/emoji_picker/emoji_picker.vue | 4 +- 2 files changed, 75 insertions(+), 30 deletions(-) diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index d04649dcc..cab952f8e 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -57,7 +57,8 @@ const EmojiPicker = { keepOpen: false, customEmojiBufferSlice: LOAD_EMOJI_BY, customEmojiTimeout: null, - customEmojiLoadAllConfirmed: false + customEmojiLoadAllConfirmed: false, + groupLoadedCount: {} } }, components: { @@ -79,7 +80,9 @@ const EmojiPicker = { const target = (e && e.target) || this.$refs['emoji-groups'] this.updateScrolledClass(target) this.scrolledGroup(target) - this.triggerLoadMore(target) + this.$nextTick(() => { + this.triggerLoadMore(target) + }) }, highlight (key) { const ref = this.$refs['group-' + key] @@ -88,6 +91,7 @@ const EmojiPicker = { this.activeGroup = key this.$nextTick(() => { this.$refs['emoji-groups'].scrollTop = top + 1 + this.loadEmoji(key) }) }, updateScrolledClass (target) { @@ -100,28 +104,40 @@ const EmojiPicker = { } }, triggerLoadMore (target) { - const ref = this.$refs[`group-end-${this.lastNonUnicodeGroupId}`][0] - if (!ref) return - const bottom = ref.offsetTop + ref.offsetHeight + Object.keys(this.allCustomGroups) + .map(groupId => { + const ref = this.$refs[`group-end-${groupId}`][0] + if (!ref) return undefined - const scrollerBottom = target.scrollTop + target.clientHeight - const scrollerTop = target.scrollTop - const scrollerMax = target.scrollHeight + const bottom = ref.offsetTop + ref.offsetHeight - // 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() - } + const group = this.$refs[`group-${groupId}`][0] + const top = group.offsetTop + + 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 < top + target.clientHeight / 2 && top < scrollerBottom + // Don't load when looking at unicode category or at the very bottom + const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax + if (!bottomAboveViewport && (approachingBottom || atTop)) { + return groupId + } + return undefined + }) + .filter(k => k) + .map(k => { + this.loadEmoji(k) + }) }, scrolledGroup (target) { const top = target.scrollTop + 5 this.$nextTick(() => { - this.emojisView.forEach(group => { + this.allEmojiGroups.forEach(group => { const ref = this.$refs['group-' + group.id] if (ref.offsetTop <= top) { this.activeGroup = group.id @@ -129,14 +145,21 @@ const EmojiPicker = { }) }) }, - loadEmoji () { - const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length + loadEmoji (loadGroup) { + if (!this.allCustomGroups[loadGroup]) { + return + } + + const allLoaded = this.loadedCount[loadGroup] >= this.allCustomGroups[loadGroup].emojis.length if (allLoaded) { return } - this.customEmojiBufferSlice += LOAD_EMOJI_BY + this.groupLoadedCount = { + ...this.groupLoadedCount, + [loadGroup]: this.loadedCount[loadGroup] + LOAD_EMOJI_BY + } }, startEmojiLoad (forceUpdate = false) { if (!forceUpdate) { @@ -157,6 +180,9 @@ const EmojiPicker = { }, setShowStickers (value) { this.showingStickers = value + }, + limitedEmojis (list, groupId) { + return list.slice(0, this.loadedCount[groupId]) } }, watch: { @@ -205,24 +231,36 @@ const EmojiPicker = { return res }, {}) }, - allEmojiGroups () { - return this.allEmojis + allCustomGroups () { + return this.filteredEmoji .reduce((res, emoji) => { const packName = packOf(emoji) const packId = `custom-${packName}` - if (res.filter(k => k.id === packId).length === 0) { - res.push({ + if (!res[packId]) { + res[packId] = ({ id: packId, text: packName, - image: emoji.imageUrl + image: emoji.imageUrl, + emojis: [] }) } + res[packId].emojis.push(emoji) return res - }, []) + }, {}) + }, + sensibleInitialAmountForAGroup () { + const groupCount = Object.keys(this.allCustomGroups).length + return Math.max(Math.floor(LOAD_EMOJI_BY / Math.max(groupCount, 1)), 1) + }, + allEmojiGroups () { + const standardEmojis = this.$store.state.instance.emoji || [] + return Object.entries(this.allCustomGroups) + .map(([_, v]) => v) .concat({ id: 'standard', text: this.$t('emoji.unicode'), - icon: 'box-open' + icon: 'box-open', + emojis: filterByKeyword(standardEmojis, this.keyword) }) }, emojis () { @@ -241,6 +279,13 @@ const EmojiPicker = { } ] }, + loadedCount () { + return Object.keys(this.allCustomGroups) + .reduce((res, groupId) => { + res[groupId] = this.groupLoadedCount[groupId] || this.sensibleInitialAmountForAGroup + return res + }, {}) + }, lastNonUnicodeGroupId () { return this.emojis[this.emojis.length - 2].id }, diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue index fe60cb5ef..277d5bf62 100644 --- a/src/components/emoji_picker/emoji_picker.vue +++ b/src/components/emoji_picker/emoji_picker.vue @@ -67,7 +67,7 @@ @scroll="onScroll" >
@@ -78,7 +78,7 @@ {{ group.text }} Date: Sun, 15 Aug 2021 00:03:31 -0400 Subject: [PATCH 05/34] Load emoji properly on first showing --- src/components/emoji_picker/emoji_picker.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index cab952f8e..322bb8baa 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -58,7 +58,8 @@ const EmojiPicker = { customEmojiBufferSlice: LOAD_EMOJI_BY, customEmojiTimeout: null, customEmojiLoadAllConfirmed: false, - groupLoadedCount: {} + groupLoadedCount: {}, + firstLoaded: false } }, components: { @@ -167,6 +168,13 @@ const EmojiPicker = { } this.$nextTick(() => { this.$refs['emoji-groups'].scrollTop = 0 + this.$nextTick(() => { + if (this.firstLoaded) { + return + } + this.triggerLoadMore(this.$refs['emoji-groups']) + this.firstLoaded = true + }) }) const bufferSize = this.customEmojiBuffer.length const bufferPrefilledAll = bufferSize === this.filteredEmoji.length From 1cf2191593dc1379b3ac8a9fd7b7f4de81ee2249 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 15 Aug 2021 00:43:35 -0400 Subject: [PATCH 06/34] Optimise emoji picker loading process --- src/components/emoji_picker/emoji_picker.js | 83 ++++---------------- src/components/emoji_picker/emoji_picker.vue | 4 +- src/modules/instance.js | 18 +++++ 3 files changed, 34 insertions(+), 71 deletions(-) diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 322bb8baa..67c6d0ccb 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -106,6 +106,7 @@ const EmojiPicker = { }, triggerLoadMore (target) { Object.keys(this.allCustomGroups) + .filter(id => this.filteredEmojiGroups.filter(group => group.id === id).length > 0) .map(groupId => { const ref = this.$refs[`group-end-${groupId}`][0] if (!ref) return undefined @@ -123,9 +124,10 @@ const EmojiPicker = { 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 < top + target.clientHeight / 2 && top < scrollerBottom + const unscrollable = top - bottom < target.clientHeight // Don't load when looking at unicode category or at the very bottom const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax - if (!bottomAboveViewport && (approachingBottom || atTop)) { + if (!bottomAboveViewport && (approachingBottom || atTop || unscrollable)) { return groupId } return undefined @@ -176,12 +178,6 @@ const EmojiPicker = { this.firstLoaded = true }) }) - const bufferSize = this.customEmojiBuffer.length - const bufferPrefilledAll = bufferSize === this.filteredEmoji.length - if (bufferPrefilledAll && !forceUpdate) { - return - } - this.customEmojiBufferSlice = LOAD_EMOJI_BY }, toggleStickers () { this.showingStickers = !this.showingStickers @@ -191,6 +187,9 @@ const EmojiPicker = { }, limitedEmojis (list, groupId) { return list.slice(0, this.loadedCount[groupId]) + }, + filterByKeyword (list, keyword) { + return filterByKeyword(list, keyword) } }, watch: { @@ -210,51 +209,8 @@ const EmojiPicker = { } return 0 }, - allEmojis () { - return this.$store.state.instance.customEmoji || [] - }, - filteredEmoji () { - return filterByKeyword( - this.allEmojis, - trim(this.keyword) - ) - }, - customEmojiBuffer () { - return this.filteredEmoji.slice(0, this.customEmojiBufferSlice) - }, - groupedCustomEmojis () { - return this.customEmojiBuffer.reduce((res, emoji) => { - const pack = packOf(emoji) - if (!res[pack]) { - res[pack] = { - id: `custom-${pack}`, - text: pack, - /// FIXME - // icon: 'smile-beam', - image: emoji.imageUrl, - emojis: [] - } - } - res[pack].emojis.push(emoji) - return res - }, {}) - }, allCustomGroups () { - return this.filteredEmoji - .reduce((res, emoji) => { - const packName = packOf(emoji) - const packId = `custom-${packName}` - if (!res[packId]) { - res[packId] = ({ - id: packId, - text: packName, - image: emoji.imageUrl, - emojis: [] - }) - } - res[packId].emojis.push(emoji) - return res - }, {}) + return this.$store.getters.groupedCustomEmojis }, sensibleInitialAmountForAGroup () { const groupCount = Object.keys(this.allCustomGroups).length @@ -271,21 +227,13 @@ const EmojiPicker = { emojis: filterByKeyword(standardEmojis, this.keyword) }) }, - emojis () { - const standardEmojis = this.$store.state.instance.emoji || [] - // const customEmojis = this.customEmojiBuffer - - return [ - ...Object - .keys(this.groupedCustomEmojis) - .map(k => this.groupedCustomEmojis[k]), - { - id: 'standard', - text: this.$t('emoji.unicode'), - icon: 'box-open', - emojis: filterByKeyword(standardEmojis, trim(this.keyword)) - } - ] + filteredEmojiGroups () { + return this.allEmojiGroups + .map(group => ({ + ...group, + emojis: filterByKeyword(group.emojis, this.keyword) + })) + .filter(group => group.emojis.length > 0) }, loadedCount () { return Object.keys(this.allCustomGroups) @@ -297,9 +245,6 @@ const EmojiPicker = { lastNonUnicodeGroupId () { return this.emojis[this.emojis.length - 2].id }, - emojisView () { - return this.emojis.filter(value => value.emojis.length > 0) - }, stickerPickerEnabled () { return (this.$store.state.instance.stickers || []).length !== 0 } diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue index 277d5bf62..7b2b7fc83 100644 --- a/src/components/emoji_picker/emoji_picker.vue +++ b/src/components/emoji_picker/emoji_picker.vue @@ -3,7 +3,7 @@
diff --git a/src/modules/instance.js b/src/modules/instance.js index 23f534c34..8aadce77e 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -115,6 +115,24 @@ const instance = { .map(key => [key, state[key]]) .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) }, + groupedCustomEmojis (state) { + return state.customEmoji + .reduce((res, emoji) => { + emoji.tags.forEach(packName => { + const packId = `custom-${packName}` + if (!res[packId]) { + res[packId] = ({ + id: packId, + text: packName, + image: emoji.imageUrl, + emojis: [] + }) + } + res[packId].emojis.push(emoji) + }) + return res + }, {}) + }, instanceDomain (state) { return new URL(state.server).hostname } From 5fb65a4b111afcfab3be3ee52661fba6561d4030 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 15 Aug 2021 00:53:57 -0400 Subject: [PATCH 07/34] Lint --- src/components/emoji_picker/emoji_picker.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 67c6d0ccb..8e11f83f6 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -38,8 +38,6 @@ const filterByKeyword = (list, keyword = '') => { return orderedEmojiList.flat() } -const packOf = emoji => (emoji.tags.filter(k => k.startsWith('pack:'))[0] || '').slice(5) - const EmojiPicker = { props: { enableStickerPicker: { From d9901d39140db5e076a302a66ea0005092208fe4 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 7 Oct 2021 23:23:58 -0400 Subject: [PATCH 08/34] Use lozad for lazy image loading Ref: grouped-emoji-picker --- package.json | 1 + src/components/emoji_picker/emoji_picker.js | 6 +++++- src/components/emoji_picker/emoji_picker.vue | 3 ++- src/directives/lazy_image_container.js | 13 +++++++++++++ yarn.lock | 5 +++++ 5 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 src/directives/lazy_image_container.js diff --git a/package.json b/package.json index 723e472b3..677d03b52 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "js-cookie": "3.0.1", "localforage": "1.10.0", "parse-link-header": "2.0.0", + "lozad": "^1.16.0", "phoenix": "1.6.2", "punycode.js": "2.1.0", "qrcode": "1.5.0", diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 8e11f83f6..82e5ad0b7 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -1,5 +1,6 @@ import { defineAsyncComponent } from 'vue' import Checkbox from '../checkbox/checkbox.vue' +import LazyImageContainer from '../../directives/lazy_image_container' import { library } from '@fortawesome/fontawesome-svg-core' import { faBoxOpen, @@ -64,6 +65,9 @@ const EmojiPicker = { StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')), Checkbox }, + directives: { + LazyImageContainer + }, methods: { onStickerUploaded (e) { this.$emit('sticker-uploaded', e) @@ -184,7 +188,7 @@ const EmojiPicker = { this.showingStickers = value }, limitedEmojis (list, groupId) { - return list.slice(0, this.loadedCount[groupId]) + return list // list.slice(0, this.loadedCount[groupId]) }, filterByKeyword (list, keyword) { return filterByKeyword(list, keyword) diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue index 7b2b7fc83..0e6c7e41e 100644 --- a/src/components/emoji_picker/emoji_picker.vue +++ b/src/components/emoji_picker/emoji_picker.vue @@ -62,6 +62,7 @@
{{ emoji.replacement }} diff --git a/src/directives/lazy_image_container.js b/src/directives/lazy_image_container.js new file mode 100644 index 000000000..44adc828d --- /dev/null +++ b/src/directives/lazy_image_container.js @@ -0,0 +1,13 @@ + +import lozad from 'lozad' + +const LazyImageContainer = { + inserted (el) { + const images = el.querySelectorAll('img') + console.log(images.length) + el.$observer = lozad(images) + el.$observer.observe() + } +} + +export default LazyImageContainer diff --git a/yarn.lock b/yarn.lock index 1529a091e..d861f8264 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5924,6 +5924,11 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lozad@^1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/lozad/-/lozad-1.16.0.tgz#86ce732c64c69926ccdebb81c8c90bb3735948b4" + integrity sha512-JBr9WjvEFeKoyim3svo/gsQPTkgG/mOHJmDctZ/+U9H3ymUuvEkqpn8bdQMFsvTMcyRJrdJkLv0bXqGm0sP72w== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" From cbe8a2a5d204808e4b01d0f531928034c5b2e9b4 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 8 Oct 2021 01:02:16 -0400 Subject: [PATCH 09/34] Clean up legacy code in emoji picker Ref: grouped-emoji-picker --- src/components/emoji_input/emoji_input.js | 1 - src/components/emoji_picker/emoji_picker.js | 130 ++++--------------- src/components/emoji_picker/emoji_picker.vue | 3 +- src/directives/lazy_image_container.js | 13 -- 4 files changed, 28 insertions(+), 119 deletions(-) delete mode 100644 src/directives/lazy_image_container.js diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index 5ba3907ff..67f955553 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -205,7 +205,6 @@ const EmojiInput = { }, triggerShowPicker () { this.showPicker = true - this.$refs.picker.startEmojiLoad() this.$nextTick(() => { this.scrollIntoView() this.focusPickerInput() diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 82e5ad0b7..b0162479a 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -1,6 +1,6 @@ import { defineAsyncComponent } from 'vue' import Checkbox from '../checkbox/checkbox.vue' -import LazyImageContainer from '../../directives/lazy_image_container' +import lozad from 'lozad' import { library } from '@fortawesome/fontawesome-svg-core' import { faBoxOpen, @@ -54,7 +54,6 @@ const EmojiPicker = { showingStickers: false, groupsScrolledClass: 'scrolled-top', keepOpen: false, - customEmojiBufferSlice: LOAD_EMOJI_BY, customEmojiTimeout: null, customEmojiLoadAllConfirmed: false, groupLoadedCount: {}, @@ -65,9 +64,6 @@ const EmojiPicker = { StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')), Checkbox }, - directives: { - LazyImageContainer - }, methods: { onStickerUploaded (e) { this.$emit('sticker-uploaded', e) @@ -82,10 +78,6 @@ const EmojiPicker = { onScroll (e) { const target = (e && e.target) || this.$refs['emoji-groups'] this.updateScrolledClass(target) - this.scrolledGroup(target) - this.$nextTick(() => { - this.triggerLoadMore(target) - }) }, highlight (key) { const ref = this.$refs['group-' + key] @@ -94,7 +86,6 @@ const EmojiPicker = { this.activeGroup = key this.$nextTick(() => { this.$refs['emoji-groups'].scrollTop = top + 1 - this.loadEmoji(key) }) }, updateScrolledClass (target) { @@ -106,101 +97,48 @@ const EmojiPicker = { this.groupsScrolledClass = 'scrolled-middle' } }, - triggerLoadMore (target) { - Object.keys(this.allCustomGroups) - .filter(id => this.filteredEmojiGroups.filter(group => group.id === id).length > 0) - .map(groupId => { - const ref = this.$refs[`group-end-${groupId}`][0] - if (!ref) return undefined - - const bottom = ref.offsetTop + ref.offsetHeight - - const group = this.$refs[`group-${groupId}`][0] - const top = group.offsetTop - - 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 < top + target.clientHeight / 2 && top < scrollerBottom - const unscrollable = top - bottom < target.clientHeight - // Don't load when looking at unicode category or at the very bottom - const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax - if (!bottomAboveViewport && (approachingBottom || atTop || unscrollable)) { - return groupId - } - return undefined - }) - .filter(k => k) - .map(k => { - this.loadEmoji(k) - }) - }, - scrolledGroup (target) { - const top = target.scrollTop + 5 - this.$nextTick(() => { - this.allEmojiGroups.forEach(group => { - const ref = this.$refs['group-' + group.id] - if (ref.offsetTop <= top) { - this.activeGroup = group.id - } - }) - }) - }, - loadEmoji (loadGroup) { - if (!this.allCustomGroups[loadGroup]) { - return - } - - const allLoaded = this.loadedCount[loadGroup] >= this.allCustomGroups[loadGroup].emojis.length - - if (allLoaded) { - return - } - - this.groupLoadedCount = { - ...this.groupLoadedCount, - [loadGroup]: this.loadedCount[loadGroup] + LOAD_EMOJI_BY - } - }, - startEmojiLoad (forceUpdate = false) { - if (!forceUpdate) { - this.keyword = '' - } - this.$nextTick(() => { - this.$refs['emoji-groups'].scrollTop = 0 - this.$nextTick(() => { - if (this.firstLoaded) { - return - } - this.triggerLoadMore(this.$refs['emoji-groups']) - this.firstLoaded = true - }) - }) - }, toggleStickers () { this.showingStickers = !this.showingStickers }, setShowStickers (value) { this.showingStickers = value }, - limitedEmojis (list, groupId) { - return list // list.slice(0, this.loadedCount[groupId]) - }, filterByKeyword (list, keyword) { return filterByKeyword(list, keyword) + }, + initializeLazyLoad () { + this.destroyLazyLoad() + this.$lozad = lozad('img', {}) + this.$lozad.observe() + }, + destroyLazyLoad () { + if (this.$lozad) { + if (this.$lozad.observer) { + this.$lozad.observer.disconnect() + } + if (this.$lozad.mutationObserver) { + this.$lozad.mutationObserver.disconnect() + } + } } }, watch: { keyword () { this.customEmojiLoadAllConfirmed = false this.onScroll() - this.startEmojiLoad(true) + // Wait for the dom to change + this.$nextTick(() => this.initializeLazyLoad()) + }, + allCustomGroups () { + this.$nextTick(() => this.initializeLazyLoad()) } }, + mounted () { + this.initializeLazyLoad() + }, + destroyed () { + this.destroyLazyLoad() + }, computed: { activeGroupView () { return this.showingStickers ? '' : this.activeGroup @@ -214,10 +152,6 @@ const EmojiPicker = { allCustomGroups () { return this.$store.getters.groupedCustomEmojis }, - sensibleInitialAmountForAGroup () { - const groupCount = Object.keys(this.allCustomGroups).length - return Math.max(Math.floor(LOAD_EMOJI_BY / Math.max(groupCount, 1)), 1) - }, allEmojiGroups () { const standardEmojis = this.$store.state.instance.emoji || [] return Object.entries(this.allCustomGroups) @@ -237,16 +171,6 @@ const EmojiPicker = { })) .filter(group => group.emojis.length > 0) }, - loadedCount () { - return Object.keys(this.allCustomGroups) - .reduce((res, groupId) => { - res[groupId] = this.groupLoadedCount[groupId] || this.sensibleInitialAmountForAGroup - return res - }, {}) - }, - lastNonUnicodeGroupId () { - return this.emojis[this.emojis.length - 2].id - }, stickerPickerEnabled () { return (this.$store.state.instance.stickers || []).length !== 0 } diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue index 0e6c7e41e..0df33c241 100644 --- a/src/components/emoji_picker/emoji_picker.vue +++ b/src/components/emoji_picker/emoji_picker.vue @@ -62,7 +62,6 @@
Date: Fri, 8 Oct 2021 01:11:32 -0400 Subject: [PATCH 10/34] Fix scrol->highlight behaviour Ref: grouped-emoji-picker --- src/components/emoji_picker/emoji_picker.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index b0162479a..31a455fde 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -78,6 +78,18 @@ const EmojiPicker = { onScroll (e) { const target = (e && e.target) || this.$refs['emoji-groups'] this.updateScrolledClass(target) + this.scrolledGroup(target) + }, + scrolledGroup (target) { + const top = target.scrollTop + 5 + this.$nextTick(() => { + this.allEmojiGroups.forEach(group => { + const ref = this.$refs['group-' + group.id] + if (ref[0].offsetTop <= top) { + this.activeGroup = group.id + } + }) + }) }, highlight (key) { const ref = this.$refs['group-' + key] @@ -134,6 +146,9 @@ const EmojiPicker = { } }, mounted () { + if (this.defaultGroup) { + this.highlight(this.defaultGroup) + } this.initializeLazyLoad() }, destroyed () { @@ -152,6 +167,9 @@ const EmojiPicker = { allCustomGroups () { return this.$store.getters.groupedCustomEmojis }, + defaultGroup () { + return Object.keys(this.allCustomGroups)[0] + }, allEmojiGroups () { const standardEmojis = this.$store.state.instance.emoji || [] return Object.entries(this.allCustomGroups) From e6fa4104e54266fdb93ff8411e49c696fde13205 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 8 Oct 2021 01:20:35 -0400 Subject: [PATCH 11/34] Clean up unused variables Ref: grouped-emoji-picker --- src/components/emoji_picker/emoji_picker.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 31a455fde..d60daab76 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -15,13 +15,6 @@ library.add( faSmileBeam ) -// 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 = '') => { if (keyword === '') return list @@ -54,10 +47,7 @@ const EmojiPicker = { showingStickers: false, groupsScrolledClass: 'scrolled-top', keepOpen: false, - customEmojiTimeout: null, - customEmojiLoadAllConfirmed: false, - groupLoadedCount: {}, - firstLoaded: false + customEmojiTimeout: null } }, components: { @@ -136,7 +126,6 @@ const EmojiPicker = { }, watch: { keyword () { - this.customEmojiLoadAllConfirmed = false this.onScroll() // Wait for the dom to change this.$nextTick(() => this.initializeLazyLoad()) From c06b01db85d0eb9d3f9ba54131823a40564687b4 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 8 Oct 2021 13:06:03 -0400 Subject: [PATCH 12/34] Remove useless class `disabled` in emoji picker Ref: grouped-emoji-picker --- src/components/emoji_picker/emoji_picker.vue | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue index 0df33c241..fb2eef250 100644 --- a/src/components/emoji_picker/emoji_picker.vue +++ b/src/components/emoji_picker/emoji_picker.vue @@ -7,8 +7,7 @@ :key="group.id" class="emoji-tabs-item" :class="{ - active: activeGroupView === group.id, - disabled: false + active: activeGroupView === group.id }" :title="group.text" @click.prevent="highlight(group.id)" From b9fcf8da11a289c9cda4b5209d149cd7f31272e8 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 8 Oct 2021 13:17:47 -0400 Subject: [PATCH 13/34] Use StillImage for emoji group header Ref: grouped-emoji-picker --- src/components/emoji_picker/emoji_picker.js | 4 +++- src/components/emoji_picker/emoji_picker.vue | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index d60daab76..5b90c31e7 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -1,5 +1,6 @@ import { defineAsyncComponent } from 'vue' import Checkbox from '../checkbox/checkbox.vue' +import StillImage from '../still-image/still-image.vue' import lozad from 'lozad' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -52,7 +53,8 @@ const EmojiPicker = { }, components: { StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')), - Checkbox + Checkbox, + StillImage }, methods: { onStickerUploaded (e) { diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue index fb2eef250..ed196066d 100644 --- a/src/components/emoji_picker/emoji_picker.vue +++ b/src/components/emoji_picker/emoji_picker.vue @@ -16,10 +16,10 @@ v-if="group.image" class="emoji-picker-header-image" > - + /> Date: Fri, 8 Oct 2021 14:10:17 -0400 Subject: [PATCH 14/34] Fix vertical scrollbar of emoji picker header Ref: grouped-emoji-picker --- src/components/emoji_picker/emoji_picker.scss | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss index 0bd4363cf..e315d2d7f 100644 --- a/src/components/emoji_picker/emoji_picker.scss +++ b/src/components/emoji_picker/emoji_picker.scss @@ -1,5 +1,10 @@ @import '../../_variables.scss'; +$emoji-picker-header-height: 36px; +$emoji-picker-header-picture-width: 32px; +$emoji-picker-header-picture-height: 32px; +$emoji-picker-emoji-size: 32px; + .emoji-picker { display: flex; flex-direction: column; @@ -23,9 +28,11 @@ display: inline-flex; justify-content: center; align-items: center; - width: 30px; - height: 24px; - img { + width: $emoji-picker-header-picture-width; + max-width: $emoji-picker-header-picture-width; + height: $emoji-picker-header-picture-height; + max-height: $emoji-picker-header-picture-height; + .still-image { max-width: 100%; max-height: 100%; object-fit: contain; @@ -50,7 +57,7 @@ .heading { display: flex; - height: 32px; + //height: $emoji-picker-header-height; padding: 10px 7px 5px; overflow-x: auto; } @@ -87,11 +94,19 @@ min-width: 0; flex-basis: auto; flex-shrink: 1; + display: flex; + align-content: center; &-item { padding: 0 7px; cursor: pointer; font-size: 1.85em; + width: $emoji-picker-header-picture-width; + max-width: $emoji-picker-header-picture-width; + height: $emoji-picker-header-picture-height; + max-height: $emoji-picker-header-picture-height; + display: flex; + align-items: center; &.disabled { opacity: 0.5; @@ -181,11 +196,11 @@ } &-item { - width: 32px; - height: 32px; + width: $emoji-picker-emoji-size; + height: $emoji-picker-emoji-size; box-sizing: border-box; display: flex; - font-size: 32px; + font-size: $emoji-picker-emoji-size; align-items: center; justify-content: center; margin: 4px; From f92a25cfab6a39af015d5bda663b885c668718bb Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 8 Oct 2021 14:46:00 -0400 Subject: [PATCH 15/34] Fix sticker picker heading tab Ref: grouped-emoji-picker --- src/components/emoji_picker/emoji_picker.scss | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss index e315d2d7f..ea8b60378 100644 --- a/src/components/emoji_picker/emoji_picker.scss +++ b/src/components/emoji_picker/emoji_picker.scss @@ -57,9 +57,7 @@ $emoji-picker-emoji-size: 32px; .heading { display: flex; - //height: $emoji-picker-header-height; padding: 10px 7px 5px; - overflow-x: auto; } .content { @@ -74,6 +72,7 @@ $emoji-picker-emoji-size: 32px; display: flex; flex-direction: row; flex-wrap: nowrap; + overflow-x: auto; } .emoji-groups { @@ -81,7 +80,8 @@ $emoji-picker-emoji-size: 32px; } .additional-tabs { - display: block; + display: flex; + flex: 1; border-left: 1px solid; border-left-color: $fallback--icon; border-left-color: var(--icon, $fallback--icon); @@ -91,9 +91,8 @@ $emoji-picker-emoji-size: 32px; .additional-tabs, .emoji-tabs { - min-width: 0; flex-basis: auto; - flex-shrink: 1; + // flex-shrink: 1; display: flex; align-content: center; From 2161067423993bef7f559fbd7d1fb10a3b851524 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 8 Oct 2021 15:09:24 -0400 Subject: [PATCH 16/34] Lazy-load emoji picker in post form When clicking the reply button, we used to load the whole emoji picker. This causes a considerable delay even if the user is not going to use the emoji picker. Now the content of the emoji picker is loaded only after the user has explicitly opened the emoji picker. Ref: grouped-emoji-picker --- src/components/emoji_input/emoji_input.vue | 1 + src/components/emoji_picker/emoji_picker.js | 24 ++++++++++++++++---- src/components/emoji_picker/emoji_picker.vue | 9 ++++++-- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue index 7d95ab7e5..3c9d72120 100644 --- a/src/components/emoji_input/emoji_input.vue +++ b/src/components/emoji_input/emoji_input.vue @@ -19,6 +19,7 @@ v-if="enableEmojiPicker" ref="picker" :class="{ hide: !showPicker }" + :showing="showPicker" :enable-sticker-picker="enableStickerPicker" class="emoji-picker-panel" @emoji="insert" diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 5b90c31e7..8b4f302f8 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -39,6 +39,10 @@ const EmojiPicker = { required: false, type: Boolean, default: false + }, + showing: { + required: true, + type: Boolean } }, data () { @@ -48,7 +52,9 @@ const EmojiPicker = { showingStickers: false, groupsScrolledClass: 'scrolled-top', keepOpen: false, - customEmojiTimeout: null + customEmojiTimeout: null, + // Lazy-load only after the first time `showing` becomes true. + contentLoaded: false } }, components: { @@ -115,6 +121,9 @@ const EmojiPicker = { this.$lozad = lozad('img', {}) this.$lozad.observe() }, + waitForDomAndInitializeLazyLoad() { + this.$nextTick(() => this.initializeLazyLoad()) + }, destroyLazyLoad () { if (this.$lozad) { if (this.$lozad.observer) { @@ -129,18 +138,23 @@ const EmojiPicker = { watch: { keyword () { this.onScroll() - // Wait for the dom to change - this.$nextTick(() => this.initializeLazyLoad()) + this.waitForDomAndInitializeLazyLoad() }, allCustomGroups () { - this.$nextTick(() => this.initializeLazyLoad()) + this.waitForDomAndInitializeLazyLoad() + }, + showing (val) { + if (val) { + this.contentLoaded = true + this.waitForDomAndInitializeLazyLoad() + } } }, mounted () { if (this.defaultGroup) { this.highlight(this.defaultGroup) } - this.initializeLazyLoad() + this.waitForDomAndInitializeLazyLoad() }, destroyed () { this.destroyLazyLoad() diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue index ed196066d..b92bccd74 100644 --- a/src/components/emoji_picker/emoji_picker.vue +++ b/src/components/emoji_picker/emoji_picker.vue @@ -1,5 +1,7 @@