From 4465de5241a6ed148d00d20de9e348f4991a4400 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 14 Aug 2021 21:55:38 +0300 Subject: [PATCH 1/5] fixed mentions line again --- src/components/status/status.js | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/components/status/status.js b/src/components/status/status.js index 54f304a26..ac4815346 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -166,23 +166,16 @@ const Status = { muteWordHits () { return muteWordHits(this.status, this.muteWords) }, - mentions () { - return this.status.attentions.filter(attn => { - return attn.screen_name !== this.replyToName && - attn.screen_name !== this.status.user.screen_name - }).map(attn => ({ - url: attn.statusnet_profile_url, - content: attn.screen_name, - userId: attn.id - })) - }, mentionsLine () { - const writtenMentions = this.headTailLinks ? this.headTailLinks.writtenMentions : [] - const set = new Set(writtenMentions.map(_ => _.url)) + if (!this.headTailLinks) return [] + const writtenSet = new Set(this.headTailLinks.writtenMentions.map(_ => _.url)) return this.status.attentions.filter(attn => { - return attn.screen_name !== this.replyToName && - attn.screen_name !== this.status.user.screen_name && - !set.has(attn.url) + // no reply user + return attn.id !== this.status.in_reply_to_user_id && + // no self-replies + attn.statusnet_profile_url !== this.status.user.statusnet_profile_url && + // don't include if mentions is written + !writtenSet.has(attn.statusnet_profile_url) }).map(attn => ({ url: attn.statusnet_profile_url, content: attn.screen_name, From 530ac4442b498c90c73533d2a03ae5b7d6875900 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 15 Aug 2021 02:41:53 +0300 Subject: [PATCH 2/5] removed useless code, review change, fixed bug with tall statuses --- src/components/mention_link/mention_link.js | 2 +- src/components/mentions_line/mentions_line.js | 9 ++- src/components/rich_content/rich_content.jsx | 69 ++++++++----------- src/components/status_body/status_body.js | 16 ++++- 4 files changed, 45 insertions(+), 51 deletions(-) diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js index 4d27fe6de..65c62baa0 100644 --- a/src/components/mention_link/mention_link.js +++ b/src/components/mention_link/mention_link.js @@ -45,7 +45,7 @@ const MentionLink = { }, isYou () { // FIXME why user !== currentUser??? - return this.user && this.user.screen_name === this.currentUser.screen_name + return this.user && this.user.id === this.currentUser.id }, userName () { return this.user && this.userNameFullUi.split('@')[0] diff --git a/src/components/mentions_line/mentions_line.js b/src/components/mentions_line/mentions_line.js index 83eeea4cc..a4a0c7246 100644 --- a/src/components/mentions_line/mentions_line.js +++ b/src/components/mentions_line/mentions_line.js @@ -1,6 +1,8 @@ import MentionLink from 'src/components/mention_link/mention_link.vue' import { mapGetters } from 'vuex' +export const MENTIONS_LIMIT = 5 + const MentionsLine = { name: 'MentionsLine', props: { @@ -14,14 +16,11 @@ const MentionsLine = { MentionLink }, computed: { - limit () { - return 5 - }, mentionsComputed () { - return this.mentions.slice(0, this.limit) + return this.mentions.slice(0, MENTIONS_LIMIT) }, extraMentions () { - return this.mentions.slice(this.limit) + return this.mentions.slice(MENTIONS_LIMIT) }, manyMentions () { return this.extraMentions.length > 0 diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index 32b737ec6..1353541fc 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -4,8 +4,7 @@ import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_con import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js' import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js' import StillImage from 'src/components/still-image/still-image.vue' -import MentionLink from 'src/components/mention_link/mention_link.vue' -import MentionsLine from 'src/components/mentions_line/mentions_line.vue' +import MentionsLine, { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.vue' import './rich_content.scss' @@ -13,12 +12,11 @@ import './rich_content.scss' * RichContent, The Über-powered component for rendering Post HTML. * * This takes post HTML and does multiple things to it: - * - Converts mention links to -s - * - Removes mentions from beginning and end (hellthread style only) + * - Groups all mentions into , this affects all mentions regardles + * of where they are (beginning/middle/end), even single mentions are converted + * to a containing single . * - Replaces emoji shortcodes with 'd images. * - * Stuff like removing mentions from beginning and end is done so that they could - * be either replaced by collapsible or moved to separate place. * There are two problems with this component's architecture: * 1. Parsing HTML and rendering are inseparable. Attempts to separate the two * proven to be a massive overcomplication due to amount of things done here. @@ -61,12 +59,13 @@ export default Vue.component('RichContent', { // NEVER EVER TOUCH DATA INSIDE RENDER render (h) { // Pre-process HTML - const { newHtml: html } = preProcessPerLine(this.html, this.greentext, this.handleLinks) + const { newHtml: html } = preProcessPerLine(this.html, this.greentext) let currentMentions = null // Current chain of mentions, we group all mentions together - // to collapse too many mentions in a row const lastTags = [] // Tags that appear at the end of post body const writtenMentions = [] // All mentions that appear in post body + const invisibleMentions = [] // All mentions that go beyond the limiter (see MentionsLine) + // to collapse too many mentions in a row const writtenTags = [] // All tags that appear in post body // unique index for vue "tag" property let mentionIndex = 0 @@ -99,6 +98,9 @@ export default Vue.component('RichContent', { currentMentions = [] } currentMentions.push(linkData) + if (currentMentions.length > MENTIONS_LIMIT) { + invisibleMentions.push(linkData) + } if (currentMentions.length === 1) { return } else { @@ -232,7 +234,8 @@ export default Vue.component('RichContent', { const event = { lastTags, writtenMentions, - writtenTags + writtenTags, + invisibleMentions } // DO NOT MOVE TO UPDATE. BAD IDEA. @@ -243,27 +246,32 @@ export default Vue.component('RichContent', { }) const getLinkData = (attrs, children, index) => { + const stripTags = (item) => { + if (typeof item === 'string') { + return item + } else { + return item[1].map(stripTags).join('') + } + } + const textContent = children.map(stripTags).join('') return { index, url: attrs.href, hashtag: attrs['data-tag'], - content: flattenDeep(children).join('') + content: flattenDeep(children).join(''), + textContent } } /** Pre-processing HTML * - * Currently this does two things: + * Currently this does one thing: * - add green/cyantexting - * - wrap and mark last line containing only mentions as ".lastMentionsLine" for - * more compact hellthreads. * * @param {String} html - raw HTML to process * @param {Boolean} greentext - whether to enable greentexting or not - * @param {Boolean} handleLinks - whether to handle links or not */ -export const preProcessPerLine = (html, greentext, handleLinks) => { - const lastMentions = [] +export const preProcessPerLine = (html, greentext) => { const greentextHandle = new Set(['p', 'div']) const lines = convertHtmlToLines(html) @@ -277,7 +285,7 @@ export const preProcessPerLine = (html, greentext, handleLinks) => { if ( // Only if greentext is engaged greentext && - // Only handle p's and divs. Don't want to affect blocquotes, code etc + // Only handle p's and divs. Don't want to affect blockquotes, code etc item.level.every(l => greentextHandle.has(l)) && // Only if line begins with '>' or '<' (string.includes('>') || string.includes('<')) @@ -292,31 +300,8 @@ export const preProcessPerLine = (html, greentext, handleLinks) => { } } - // Converting that line part into tree - const tree = convertHtmlToTree(string) - - const process = (item) => { - if (Array.isArray(item)) { - const [opener, children, closer] = item - const tag = getTagName(opener) - if (tag === 'span' || tag === 'p') { - // For span and p we need to go deeper - return [opener, [...children].map(process), closer] - } else { - return [opener, children, closer] - } - } - - if (typeof item === 'string') { - return item - } - } - - // We now processed our tree, now we need to mark line as lastMentions - const result = [...tree].map(process) - - return flattenDeep(result).join('') + return string }).reverse().join('') - return { newHtml, lastMentions } + return { newHtml } } diff --git a/src/components/status_body/status_body.js b/src/components/status_body/status_body.js index 8757154dd..b941765cf 100644 --- a/src/components/status_body/status_body.js +++ b/src/components/status_body/status_body.js @@ -32,7 +32,8 @@ const StatusContent = { showingTall: this.fullContent || (this.inConversation && this.focused), showingLongSubject: false, // not as computed because it sets the initial state which will be changed later - expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject + expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject, + postLength: this.status.text.length } }, computed: { @@ -47,7 +48,7 @@ const StatusContent = { // Using max-height + overflow: auto for status components resulted in false positives // very often with japanese characters, and it was very annoying. tallStatus () { - const lengthScore = this.status.raw_html.split(/ 20 }, longSubject () { @@ -86,7 +87,7 @@ const StatusContent = { methods: { onParseReady (event) { this.$emit('parseReady', event) - const { writtenMentions } = event + const { writtenMentions, invisibleMentions } = event writtenMentions .filter(mention => !mention.notifying) .forEach(mention => { @@ -97,6 +98,15 @@ const StatusContent = { const host = url.replace(/^https?:\/\//, '').replace(/\/.+?$/, '') this.$store.dispatch('fetchUserIfMissing', `${handle}@${host}`) }) + /* This is a bit of a hack to make current tall status detector work + * with rich mentions. Invisible mentions are detected at RichContent level + * and also we generate plaintext version of mentions by stripping tags + * so here we subtract from post length by each mention that became invisible + * via MentionsLine + */ + this.postLength = invisibleMentions.reduce((acc, mention) => { + return acc - mention.textContent.length - 1 + }, this.postLength) }, toggleShowMore () { if (this.mightHideBecauseTall) { From 7d67e8f1cccd1ef924a97b8285756590ac29224e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 15 Aug 2021 02:44:36 +0300 Subject: [PATCH 3/5] remove obsolete tests --- .../entity_normalizer.spec.js | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js index 8932bc7c4..03fb32c95 100644 --- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js +++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js @@ -244,35 +244,6 @@ describe('API Entities normalizer', () => { expect(parseUser(remote)).to.have.property('is_local', false) }) - it('adds emojis to user name', () => { - const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), display_name: 'The :thinking: thinker' }) - - const parsedUser = parseUser(user) - - expect(parsedUser).to.have.property('name_html').that.contains(' { - const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), note: 'Hello i like to :thinking: a lot' }) - - const parsedUser = parseUser(user) - - expect(parsedUser).to.have.property('description_html').that.contains(' { - const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), fields: [{ name: ':thinking:', value: ':image:' }] }) - - const parsedUser = parseUser(user) - - expect(parsedUser).to.have.property('fields_html').to.be.an('array') - - const field = parsedUser.fields_html[0] - - expect(field).to.have.property('name').that.contains(' { const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), fields: [{ name: 'user', value: '@user' }] }) From 68b432318198b6835790112d51f0572e19238158 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 15 Aug 2021 02:55:45 +0300 Subject: [PATCH 4/5] prevent infinite update loops --- src/components/status_body/status_body.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/status_body/status_body.js b/src/components/status_body/status_body.js index b941765cf..ef5423076 100644 --- a/src/components/status_body/status_body.js +++ b/src/components/status_body/status_body.js @@ -33,7 +33,8 @@ const StatusContent = { showingLongSubject: false, // not as computed because it sets the initial state which will be changed later expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject, - postLength: this.status.text.length + postLength: this.status.text.length, + parseReadyDone: false } }, computed: { @@ -86,6 +87,8 @@ const StatusContent = { }, methods: { onParseReady (event) { + if (this.parseReadyDone) return + this.parseReadyDone = true this.$emit('parseReady', event) const { writtenMentions, invisibleMentions } = event writtenMentions From f16658adfc897a3b07ed7f79d872acd2c3837cc8 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 15 Aug 2021 02:59:14 +0300 Subject: [PATCH 5/5] fix tests --- src/services/html_converter/html_line_converter.service.js | 2 +- src/services/html_converter/html_tree_converter.service.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/html_converter/html_line_converter.service.js b/src/services/html_converter/html_line_converter.service.js index 74103b02d..5eeaa7cb2 100644 --- a/src/services/html_converter/html_line_converter.service.js +++ b/src/services/html_converter/html_line_converter.service.js @@ -18,7 +18,7 @@ import { getTagName } from './utility.service.js' * @param {Object} input - input data * @return {(string|{ text: string })[]} processed html in form of a list. */ -export const convertHtmlToLines = (html) => { +export const convertHtmlToLines = (html = '') => { // Elements that are implicitly self-closing // https://developer.mozilla.org/en-US/docs/Glossary/empty_element const emptyElements = new Set([ diff --git a/src/services/html_converter/html_tree_converter.service.js b/src/services/html_converter/html_tree_converter.service.js index 804d35d7b..6a8796c4d 100644 --- a/src/services/html_converter/html_tree_converter.service.js +++ b/src/services/html_converter/html_tree_converter.service.js @@ -19,7 +19,7 @@ import { getTagName } from './utility.service.js' * @param {Object} input - input data * @return {string} processed html */ -export const convertHtmlToTree = (html) => { +export const convertHtmlToTree = (html = '') => { // Elements that are implicitly self-closing // https://developer.mozilla.org/en-US/docs/Glossary/empty_element const emptyElements = new Set([