diff --git a/src/components/chat_list_item/chat_list_item.js b/src/components/chat_list_item/chat_list_item.js index e01b9bd4d..e5032176f 100644 --- a/src/components/chat_list_item/chat_list_item.js +++ b/src/components/chat_list_item/chat_list_item.js @@ -40,12 +40,11 @@ const ChatListItem = { const message = this.chat.lastMessage const messageEmojis = message ? message.emojis : [] const isYou = message && message.account_id === this.currentUser.id - const content = message ? (this.attachmentInfo || message.content_raw) : '' + const content = message ? (this.attachmentInfo || message.content) : '' const messagePreview = isYou ? `${this.$t('chats.you')} ${content}` : content return { summary: '', emojis: messageEmojis, - statusnet_html: messagePreview, raw_html: messagePreview, text: messagePreview, attachments: [] diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js index d126d453a..9a2d1e7de 100644 --- a/src/components/chat_message/chat_message.js +++ b/src/components/chat_message/chat_message.js @@ -58,8 +58,7 @@ const ChatMessage = { return { summary: '', emojis: this.message.emojis, - raw_html: this.message.content_raw, - statusnet_html: this.message.content, + raw_html: this.message.content, text: this.message.content, attachments: this.message.attachments } diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index 78af0d9e5..ef15aaeba 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -1,6 +1,7 @@ import Vue from 'vue' import { unescape, flattenDeep } from 'lodash' -import { convertHtmlToTree, getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/html_tree_converter.service.js' +import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js' +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' @@ -31,18 +32,12 @@ export default Vue.component('RichContent', { required: false, type: Boolean, default: false - }, - // Whether to hide last mentions (hellthreads) - hideMentions: { - required: false, - type: Boolean, - default: false } }, // NEVER EVER TOUCH DATA INSIDE RENDER render (h) { // Pre-process HTML - const { newHtml: html, lastMentions } = preProcessPerLine(this.html, this.greentext, this.hideMentions) + const { newHtml: html, lastMentions } = preProcessPerLine(this.html, this.greentext, this.handleLinks) const firstMentions = [] // Mentions that appear in the beginning of post body const lastTags = [] // Tags that appear at the end of post body const writtenMentions = [] // All mentions that appear in post body @@ -126,7 +121,7 @@ export default Vue.component('RichContent', { switch (Tag) { case 'span': // replace images with StillImage if (attrs['class'] && attrs['class'].includes('lastMentions')) { - if (firstMentions.length > 0) { + if (firstMentions.length > 1) { break } else { return '' @@ -141,6 +136,7 @@ export default Vue.component('RichContent', { if (attrs['class'] && attrs['class'].includes('mention')) { return renderMention(attrs, children, encounteredText) } else if (attrs['class'] && attrs['class'].includes('hashtag')) { + encounteredText = true return item // We'll handle it later } else { attrs.target = '_blank' @@ -167,7 +163,7 @@ export default Vue.component('RichContent', { // Handle text nodes - just add emoji if (typeof item === 'string') { const emptyText = item.trim() === '' - if (emptyText) return encounteredTextReverse ? item : item.trim() + if (emptyText) return item if (!encounteredTextReverse) encounteredTextReverse = true return item } else if (Array.isArray(item)) { @@ -227,10 +223,12 @@ const getLinkData = (attrs, children, index) => { * * @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) => { +export const preProcessPerLine = (html, greentext, handleLinks) => { const lastMentions = [] + let nonEmptyIndex = 0 const newHtml = convertHtmlToLines(html).reverse().map((item, index, array) => { // Going over each line in reverse to detect last mentions, // keeping non-text stuff as-is @@ -262,6 +260,7 @@ export const preProcessPerLine = (html, greentext) => { const tag = getTagName(opener) // If we have a link we probably have mentions if (tag === 'a') { + if (!handleLinks) return [opener, children, closer] const attrs = getAttrs(opener) if (attrs['class'] && attrs['class'].includes('mention')) { // Got mentions @@ -295,7 +294,7 @@ export const preProcessPerLine = (html, greentext) => { const result = [...tree].map(process) // Only check last (first since list is reversed) line - if (hasMentions && !hasLooseText && index === 0) { + if (handleLinks && hasMentions && !hasLooseText && nonEmptyIndex++ === 0) { let mentionIndex = 0 const process = (item) => { if (Array.isArray(item)) { diff --git a/src/components/status/status.scss b/src/components/status/status.scss index e68bc62cd..3805aa309 100644 --- a/src/components/status/status.scss +++ b/src/components/status/status.scss @@ -4,6 +4,7 @@ $status-margin: 0.75em; .Status { min-width: 0; + white-space: normal; &:hover { --_still-image-img-visibility: visible; @@ -166,6 +167,7 @@ $status-margin: 0.75em; line-height: 160%; max-width: 100%; align-items: stretch; + z-index: 2; } & .reply-to-popover, @@ -211,7 +213,9 @@ $status-margin: 0.75em; padding-right: 0.25em; } + & .mentions-text, & .reply-to-text { + color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/src/components/status/status.vue b/src/components/status/status.vue index be6458ae9..a5f347a64 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -247,7 +247,7 @@ flip="horizontal" /> {{ $t('status.reply_to') }} @@ -281,7 +281,7 @@ @click.prevent="gotoOriginal(status.in_reply_to_status_id)" > {{ $t('status.mentions') }} diff --git a/src/components/status_body/status_body.js b/src/components/status_body/status_body.js index 7433619ba..26491e1b4 100644 --- a/src/components/status_body/status_body.js +++ b/src/components/status_body/status_body.js @@ -1,5 +1,5 @@ import fileType from 'src/services/file_type/file_type.service' -import RichContent, { getHeadTailLinks } from 'src/components/rich_content/rich_content.jsx' +import RichContent from 'src/components/rich_content/rich_content.jsx' import MentionsLine from 'src/components/mentions_line/mentions_line.vue' import { mapGetters } from 'vuex' import { library } from '@fortawesome/fontawesome-svg-core' @@ -53,7 +53,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.statusnet_html.split(/
20
},
longSubject () {
diff --git a/src/components/status_body/status_body.scss b/src/components/status_body/status_body.scss
index 310185ae8..da5d4dd32 100644
--- a/src/components/status_body/status_body.scss
+++ b/src/components/status_body/status_body.scss
@@ -62,7 +62,7 @@
overflow-y: hidden;
z-index: 1;
- .text-wrapper {
+ .rich-content-wrapper {
min-height: 0;
mask:
linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
@@ -123,5 +123,4 @@
vertical-align: middle;
object-fit: contain;
}
-
}
diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue
index 45d899fbd..aac44e42c 100644
--- a/src/components/status_body/status_body.vue
+++ b/src/components/status_body/status_body.vue
@@ -38,7 +38,10 @@
>
{{ $t("general.show_more") }}
-
+
${data.join('')} )', () => {
+ const html = [
+ p('How are you doing today, fine gentlemen?'),
+ p(
+ makeMention('John'),
+ makeMention('Josh'),
+ makeMention('Jeremy')
+ )
+ ].join('')
+ const expected = [
+ p(
+ 'How are you doing today, fine gentlemen?'
+ ),
+ // TODO fix this extra line somehow?
+ p()
+ ].join('')
+
+ const wrapper = shallowMount(RichContent, {
+ localVue,
+ propsData: {
+ handleLinks: true,
+ greentext: true,
+ emoji: [],
+ html
+ }
+ })
+
+ expect(wrapper.html()).to.eql(compwrap(expected))
+ })
+
+ it('removes mentions from the end of the hellpost ( haha benis 2
)', () => {
+ const html = [
+ 'How are you doing today, fine gentlemen?',
+ [
+ makeMention('John'),
+ makeMention('Josh'),
+ makeMention('Jeremy')
+ ].join('')
+ ].join('
')
+ const expected = [
+ 'How are you doing today, fine gentlemen?',
+ // TODO fix this extra line somehow?
+ '
'
+ ].join('')
+
+ const wrapper = shallowMount(RichContent, {
+ localVue,
+ propsData: {
+ handleLinks: true,
+ greentext: true,
+ emoji: [],
+ html
+ }
+ })
+
+ expect(wrapper.html()).to.eql(compwrap(expected))
+ })
+
+ it('removes mentions from the end of the hellpost (\\n)', () => {
+ const html = [
+ 'How are you doing today, fine gentlemen?',
+ [
+ makeMention('John'),
+ makeMention('Josh'),
+ makeMention('Jeremy')
+ ].join('')
+ ].join('\n')
+ const expected = [
+ 'How are you doing today, fine gentlemen?',
+ // TODO fix this extra line somehow?
+ ''
+ ].join('\n')
+
+ const wrapper = shallowMount(RichContent, {
+ localVue,
+ propsData: {
+ handleLinks: true,
+ greentext: true,
+ emoji: [],
+ html
+ }
+ })
+
+ expect(wrapper.html()).to.eql(compwrap(expected))
+ })
+
+ it('Does not remove mentions in the middle or at the end of text string', () => {
+ const html = [
+ [
+ makeMention('Jack'),
+ 'let\'s meet up with ',
+ makeMention('Janet')
+ ].join(''),
+ [
+ 'cc: ',
+ makeMention('John'),
+ makeMention('Josh'),
+ makeMention('Jeremy')
+ ].join('')
+ ].join('\n')
+ const expected = [
+ [
+ removedMentionSpan,
+ 'let\'s meet up with ',
+ stubMention('Janet')
+ ].join(''),
+ [
+ 'cc: ',
+ stubMention('John'),
+ stubMention('Josh'),
+ stubMention('Jeremy')
+ ].join('')
+ ].join('\n')
+
+ const wrapper = shallowMount(RichContent, {
+ localVue,
+ propsData: {
+ handleLinks: true,
+ greentext: true,
+ emoji: [],
+ html
+ }
+ })
+
+ expect(wrapper.html()).to.eql(compwrap(expected))
+ })
+
+ it('removes mentions from the end if there\'s only one first mention', () => {
+ const html = [
+ p(
+ makeMention('Todd'),
+ 'so anyway you are wrong'
+ ),
+ p(
+ makeMention('Tom'),
+ makeMention('Trace'),
+ makeMention('Theodor')
+ )
+ ].join('')
+ const expected = [
+ p(
+ removedMentionSpan,
+ 'so anyway you are wrong'
+ ),
+ // TODO fix this extra line somehow?
+ p()
+ ].join('')
+
+ const wrapper = shallowMount(RichContent, {
+ localVue,
+ propsData: {
+ handleLinks: true,
+ greentext: true,
+ emoji: [],
+ html
+ }
+ })
+
+ expect(wrapper.html()).to.eql(compwrap(expected))
+ })
+
+ it('does not remove mentions from the end if there\'s more than one first mention', () => {
+ const html = [
+ p(
+ makeMention('Zacharie'),
+ makeMention('Zinaide'),
+ 'you guys have cool names, and so do these guys: '
+ ),
+ p(
+ makeMention('Watson'),
+ makeMention('Wallace'),
+ makeMention('Wakamoto')
+ )
+ ].join('')
+ const expected = [
+ p(
+ removedMentionSpan,
+ removedMentionSpan,
+ 'you guys have cool names, and so do these guys: '
+ ),
+ p(
+ lastMentions(
+ stubMention('Watson'),
+ stubMention('Wallace'),
+ stubMention('Wakamoto')
+ )
+ )
+ ].join('')
+
+ const wrapper = shallowMount(RichContent, {
+ localVue,
+ propsData: {
+ handleLinks: true,
+ greentext: true,
+ emoji: [],
+ html
+ }
+ })
+
+ expect(wrapper.html()).to.eql(compwrap(expected))
+ })
+
+ it('Does not touch links if link handling is disabled', () => {
+ const html = [
+ [
+ makeMention('Jack'),
+ 'let\'s meet up with ',
+ makeMention('Janet')
+ ].join(''),
+ [
+ makeMention('John'),
+ makeMention('Josh'),
+ makeMention('Jeremy')
+ ].join('')
+ ].join('\n')
+
+ const wrapper = shallowMount(RichContent, {
+ localVue,
+ propsData: {
+ handleLinks: false,
+ greentext: true,
+ emoji: [],
+ html
+ }
+ })
+
+ expect(wrapper.html()).to.eql(compwrap(html))
+ })
+
+ it('Adds greentext and cyantext to the post', () => {
+ const html = [
+ '>preordering videogames',
+ '>any year'
+ ].join('\n')
+ const expected = [
+ '>preordering videogames',
+ '>any year'
+ ].join('\n')
+
+ const wrapper = shallowMount(RichContent, {
+ localVue,
+ propsData: {
+ handleLinks: false,
+ greentext: true,
+ emoji: [],
+ html
+ }
+ })
+
+ expect(wrapper.html()).to.eql(compwrap(expected))
+ })
+
+ it('Does not add greentext and cyantext if setting is set to false', () => {
+ const html = [
+ '>preordering videogames',
+ '>any year'
+ ].join('\n')
+
+ const wrapper = shallowMount(RichContent, {
+ localVue,
+ propsData: {
+ handleLinks: false,
+ greentext: false,
+ emoji: [],
+ html
+ }
+ })
+
+ expect(wrapper.html()).to.eql(compwrap(html))
+ })
+
+ it('Adds emoji to post', () => {
+ const html = p('Ebin :DDDD :spurdo:')
+ const expected = p(
+ 'Ebin :DDDD ',
+ ' {
const post = makeMockStatusMasto({ emojis: makeMockEmojiMasto(), spoiler_text: 'CW: 300 IQ :thinking:' })
diff --git a/test/unit/specs/services/html_converter/html_line_converter.spec.js b/test/unit/specs/services/html_converter/html_line_converter.spec.js
index 532ea187b..9485233fc 100644
--- a/test/unit/specs/services/html_converter/html_line_converter.spec.js
+++ b/test/unit/specs/services/html_converter/html_line_converter.spec.js
@@ -2,7 +2,7 @@ import { convertHtmlToLines } from 'src/services/html_converter/html_line_conver
const mapOnlyText = (processor) => (input) => input.text ? processor(input.text) : input
-describe('TinyPostHTMLProcessor', () => {
+describe('html_line_converter', () => {
describe('with processor that keeps original line should not make any changes to HTML when', () => {
const processorKeep = (line) => line
it('fed with regular HTML with newlines', () => {
diff --git a/test/unit/specs/services/html_converter/html_tree_converter.spec.js b/test/unit/specs/services/html_converter/html_tree_converter.spec.js
index a54745c34..7283021b2 100644
--- a/test/unit/specs/services/html_converter/html_tree_converter.spec.js
+++ b/test/unit/specs/services/html_converter/html_tree_converter.spec.js
@@ -1,6 +1,6 @@
-import { convertHtmlToTree, processTextForEmoji, getAttrs } from 'src/services/html_converter/html_tree_converter.service.js'
+import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js'
-describe('MiniHtmlConverter', () => {
+describe('html_tree_converter', () => {
describe('convertHtmlToTree', () => {
it('converts html into a tree structure', () => {
const input = '1
45'
@@ -129,38 +129,4 @@ describe('MiniHtmlConverter', () => {
])
})
})
-
- describe('processTextForEmoji', () => {
- it('processes all emoji in text', () => {
- const input = 'Hello from finland! :lol: We have best water! :lmao:'
- const emojis = [
- { shortcode: 'lol', src: 'LOL' },
- { shortcode: 'lmao', src: 'LMAO' }
- ]
- const processor = ({ shortcode, src }) => ({ shortcode, src })
- expect(processTextForEmoji(input, emojis, processor)).to.eql([
- 'Hello from finland! ',
- { shortcode: 'lol', src: 'LOL' },
- ' We have best water! ',
- { shortcode: 'lmao', src: 'LMAO' }
- ])
- })
- it('leaves text as is', () => {
- const input = 'Number one: that\'s terror'
- const emojis = []
- const processor = ({ shortcode, src }) => ({ shortcode, src })
- expect(processTextForEmoji(input, emojis, processor)).to.eql([
- 'Number one: that\'s terror'
- ])
- })
- })
-
- describe('getAttrs', () => {
- it('extracts arguments from tag', () => {
- const input = '
'
- const output = { src: 'boop', cool: true, ebin: 'true' }
-
- expect(getAttrs(input)).to.eql(output)
- })
- })
})
diff --git a/test/unit/specs/services/html_converter/utility.spec.js b/test/unit/specs/services/html_converter/utility.spec.js
new file mode 100644
index 000000000..cf6fd99b4
--- /dev/null
+++ b/test/unit/specs/services/html_converter/utility.spec.js
@@ -0,0 +1,37 @@
+import { processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js'
+
+describe('html_converter utility', () => {
+ describe('processTextForEmoji', () => {
+ it('processes all emoji in text', () => {
+ const input = 'Hello from finland! :lol: We have best water! :lmao:'
+ const emojis = [
+ { shortcode: 'lol', src: 'LOL' },
+ { shortcode: 'lmao', src: 'LMAO' }
+ ]
+ const processor = ({ shortcode, src }) => ({ shortcode, src })
+ expect(processTextForEmoji(input, emojis, processor)).to.eql([
+ 'Hello from finland! ',
+ { shortcode: 'lol', src: 'LOL' },
+ ' We have best water! ',
+ { shortcode: 'lmao', src: 'LMAO' }
+ ])
+ })
+ it('leaves text as is', () => {
+ const input = 'Number one: that\'s terror'
+ const emojis = []
+ const processor = ({ shortcode, src }) => ({ shortcode, src })
+ expect(processTextForEmoji(input, emojis, processor)).to.eql([
+ 'Number one: that\'s terror'
+ ])
+ })
+ })
+
+ describe('getAttrs', () => {
+ it('extracts arguments from tag', () => {
+ const input = '
'
+ const output = { src: 'boop', cool: true, ebin: 'true' }
+
+ expect(getAttrs(input)).to.eql(output)
+ })
+ })
+})