From 69b3102fb2396edb63abe98b4a69ebe311e22a70 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 14 Aug 2021 21:10:24 -0400 Subject: [PATCH 01/45] 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 ff2242e85dc89aa7479000cf469ca2bce5d60157 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 14 Aug 2021 21:23:45 -0400 Subject: [PATCH 02/45] 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 992d57ef69540f4c63939fbc5abed9b1ea28ed2f Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 14 Aug 2021 21:50:58 -0400 Subject: [PATCH 03/45] 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/45] 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/45] 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 123913f34ffd91917a9ed4dd1c9d406fb547ef87 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 15 Aug 2021 00:43:35 -0400 Subject: [PATCH 06/45] 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 90f757cc6d9e1e29c2567979d3c27765f84cdc6c Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 15 Aug 2021 00:53:57 -0400 Subject: [PATCH 07/45] 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 c70cdbb873eb77bc1aaf7edb9defdda59bdba1e1 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 7 Oct 2021 23:23:58 -0400 Subject: [PATCH 08/45] 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 50a6b55f8..06fb49163 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 2fefe76f2..041fc1dc4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5733,6 +5733,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 8777b6eadd7deadf010dc36bb90514f75fc0da16 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 8 Oct 2021 01:02:16 -0400 Subject: [PATCH 09/45] 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 b664d6b31..fb2096c93 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -207,7 +207,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/45] 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 f1d6e6afce6a0a6d709ae6ede5f14bdf3cf48e2b Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 8 Oct 2021 01:20:35 -0400 Subject: [PATCH 11/45] 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 031a01be7920e4cf3bda6ed5e4f3e589f9821121 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 8 Oct 2021 13:06:03 -0400 Subject: [PATCH 12/45] 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 5ab51613b738bf089770c2ad6d0ae9354d49bcee Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 8 Oct 2021 13:17:47 -0400 Subject: [PATCH 13/45] 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/45] 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 9aeffd7634e049123d3ffc8addf9c223652b0bbb Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 8 Oct 2021 14:46:00 -0400 Subject: [PATCH 15/45] 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 06a636db3732ce2808c54d3b74eb4aabd866dbf6 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 8 Oct 2021 15:09:24 -0400 Subject: [PATCH 16/45] 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 81b819132..eedde9aa2 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 @@