aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/components/basic_user_card/basic_user_card.js4
-rw-r--r--src/components/basic_user_card/basic_user_card.vue12
-rw-r--r--src/components/mention_link/mention_link.js10
-rw-r--r--src/components/mention_link/mention_link.scss41
-rw-r--r--src/components/mention_link/mention_link.vue3
-rw-r--r--src/components/mentions_line/mentions_line.js22
-rw-r--r--src/components/mentions_line/mentions_line.scss15
-rw-r--r--src/components/mentions_line/mentions_line.vue6
-rw-r--r--src/components/notification/notification.js4
-rw-r--r--src/components/notification/notification.scss2
-rw-r--r--src/components/notification/notification.vue14
-rw-r--r--src/components/notifications/notifications.scss7
-rw-r--r--src/components/poll/poll.js10
-rw-r--r--src/components/poll/poll.vue14
-rw-r--r--src/components/rich_content/rich_content.jsx189
-rw-r--r--src/components/rich_content/rich_content.scss1
-rw-r--r--src/components/settings_modal/tabs/general_tab.vue10
-rw-r--r--src/components/status/status.js25
-rw-r--r--src/components/status/status.vue1
-rw-r--r--src/components/status_body/status_body.js22
-rw-r--r--src/components/status_body/status_body.vue1
-rw-r--r--src/components/status_content/status_content.js3
-rw-r--r--src/components/status_content/status_content.vue6
-rw-r--r--src/components/user_card/user_card.js4
-rw-r--r--src/components/user_card/user_card.vue61
-rw-r--r--src/components/user_profile/user_profile.js4
-rw-r--r--src/components/user_profile/user_profile.vue20
-rw-r--r--src/modules/config.js2
-rw-r--r--src/services/entity_normalizer/entity_normalizer.service.js21
-rw-r--r--src/services/html_converter/html_line_converter.service.js2
-rw-r--r--src/services/html_converter/html_tree_converter.service.js2
-rw-r--r--test/unit/specs/components/rich_content.spec.js529
-rw-r--r--test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js66
33 files changed, 275 insertions, 858 deletions
diff --git a/src/components/basic_user_card/basic_user_card.js b/src/components/basic_user_card/basic_user_card.js
index 87085a28..8f41e2fb 100644
--- a/src/components/basic_user_card/basic_user_card.js
+++ b/src/components/basic_user_card/basic_user_card.js
@@ -1,5 +1,6 @@
import UserCard from '../user_card/user_card.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
+import RichContent from 'src/components/rich_content/rich_content.jsx'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const BasicUserCard = {
@@ -13,7 +14,8 @@ const BasicUserCard = {
},
components: {
UserCard,
- UserAvatar
+ UserAvatar,
+ RichContent
},
methods: {
toggleUserExpanded () {
diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue
index c53f6a9c..53deb1df 100644
--- a/src/components/basic_user_card/basic_user_card.vue
+++ b/src/components/basic_user_card/basic_user_card.vue
@@ -25,17 +25,11 @@
:title="user.name"
class="basic-user-card-user-name"
>
- <!-- eslint-disable vue/no-v-html -->
- <span
- v-if="user.name_html"
+ <RichContent
class="basic-user-card-user-name-value"
- v-html="user.name_html"
+ :html="user.name"
+ :emoji="user.emoji"
/>
- <!-- eslint-enable vue/no-v-html -->
- <span
- v-else
- class="basic-user-card-user-name-value"
- >{{ user.name }}</span>
</div>
<div>
<router-link
diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js
index eec116db..65c62baa 100644
--- a/src/components/mention_link/mention_link.js
+++ b/src/components/mention_link/mention_link.js
@@ -41,11 +41,11 @@ const MentionLink = {
},
computed: {
user () {
- return this.url && this.$store.getters.findUserByUrl(this.url)
+ return this.url && this.$store && this.$store.getters.findUserByUrl(this.url)
},
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]
@@ -65,9 +65,6 @@ const MentionLink = {
highlightClass () {
if (this.highlight) return highlightClass(this.user)
},
- oldStyle () {
- return !this.mergedConfig.mentionsNewStyle
- },
style () {
if (this.highlight) {
const {
@@ -83,8 +80,7 @@ const MentionLink = {
return [
{
'-you': this.isYou,
- '-highlighted': this.highlight,
- '-oldStyle': this.oldStyle
+ '-highlighted': this.highlight
},
this.highlightType
]
diff --git a/src/components/mention_link/mention_link.scss b/src/components/mention_link/mention_link.scss
index 5f5da98f..ec2689f8 100644
--- a/src/components/mention_link/mention_link.scss
+++ b/src/components/mention_link/mention_link.scss
@@ -10,10 +10,6 @@
border-radius: 2px;
}
- .original {
- margin-right: 0.25em;
- }
-
.full {
position: absolute;
display: inline-block;
@@ -41,8 +37,6 @@
}
.new {
- margin-right: 0.25em;
-
&.-you {
& .shortName,
& .full {
@@ -61,41 +55,6 @@
margin: 0;
}
- &:not(.-oldStyle) {
- .short {
- padding-left: 0.25em;
- padding-right: 0;
- padding-top: 0;
- padding-bottom: 0;
- line-height: 1.5;
- font-size: inherit;
-
- .at {
- color: var(--faint);
- opacity: 0.8;
- padding-right: 0.25em;
- vertical-align: -20%;
- }
- }
-
- .you {
- padding-right: 0.25em;
- }
-
- .userName {
- display: inline-block;
- color: var(--link);
- line-height: inherit;
- margin-left: 0;
- padding-left: 0.125em;
- padding-right: 0.25em;
- padding-top: 0;
- padding-bottom: 0;
- border-top-right-radius: var(--btnRadius);
- border-bottom-right-radius: var(--btnRadius);
- }
- }
-
&.-striped {
& .userName,
& .full {
diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue
index 514b7475..625eb727 100644
--- a/src/components/mention_link/mention_link.vue
+++ b/src/components/mention_link/mention_link.vue
@@ -18,8 +18,7 @@
:class="classnames"
>
<button
- class="short"
- :class="[{ '-sublime': !highlight }, oldStyle ? 'button-unstyled' : 'button-default']"
+ class="short button-unstyled"
@click.prevent="onClick"
>
<!-- eslint-disable vue/no-v-html -->
diff --git a/src/components/mentions_line/mentions_line.js b/src/components/mentions_line/mentions_line.js
index e52045ec..a4a0c724 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,31 +16,15 @@ const MentionsLine = {
MentionLink
},
computed: {
- oldStyle () {
- return !this.mergedConfig.mentionsNewStyle
- },
- limit () {
- return 6
- },
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
},
- buttonClasses () {
- return [
- this.oldStyle
- ? 'button-unstyled'
- : 'button-default -sublime',
- this.oldStyle
- ? '-oldStyle'
- : '-newStyle'
- ]
- },
...mapGetters(['mergedConfig'])
},
methods: {
diff --git a/src/components/mentions_line/mentions_line.scss b/src/components/mentions_line/mentions_line.scss
index 90d1e0a4..222940c8 100644
--- a/src/components/mentions_line/mentions_line.scss
+++ b/src/components/mentions_line/mentions_line.scss
@@ -1,17 +1,10 @@
.MentionsLine {
.showMoreLess {
white-space: normal;
+ color: var(--link);
+ }
- &.-newStyle {
- line-height: 1.5;
- font-size: inherit;
- display: inline-block;
- padding-top: 0;
- padding-bottom: 0;
- }
-
- &.-oldStyle {
- color: var(--link);
- }
+ .mention-link:not(:last-child) {
+ margin-right: 0.25em;
}
}
diff --git a/src/components/mentions_line/mentions_line.vue b/src/components/mentions_line/mentions_line.vue
index f4b3abb9..f375e3b0 100644
--- a/src/components/mentions_line/mentions_line.vue
+++ b/src/components/mentions_line/mentions_line.vue
@@ -25,15 +25,13 @@
/>
</span><button
v-if="!expanded"
- class="showMoreLess"
- :class="buttonClasses"
+ class="button-unstyled showMoreLess"
@click="toggleShowMore"
>
{{ $t('status.plus_more', { number: extraMentions.length }) }}
</button><button
v-if="expanded"
- class="showMoreLess"
- :class="buttonClasses"
+ class="button-unstyled showMoreLess"
@click="toggleShowMore"
>
{{ $t('general.show_less') }}
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 4aa9affd..398bb7a9 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -4,6 +4,7 @@ import Status from '../status/status.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import UserCard from '../user_card/user_card.vue'
import Timeago from '../timeago/timeago.vue'
+import RichContent from 'src/components/rich_content/rich_content.jsx'
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@@ -44,7 +45,8 @@ const Notification = {
UserAvatar,
UserCard,
Timeago,
- Status
+ Status,
+ RichContent
},
methods: {
toggleUserExpanded () {
diff --git a/src/components/notification/notification.scss b/src/components/notification/notification.scss
index f5905560..ec291547 100644
--- a/src/components/notification/notification.scss
+++ b/src/components/notification/notification.scss
@@ -2,6 +2,8 @@
// TODO Copypaste from Status, should unify it somehow
.Notification {
+ --emoji-size: 14px;
+
&.-muted {
padding: 0.25em 0.6em;
height: 1.2em;
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 396ae0e1..eb02bed1 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -51,12 +51,14 @@
<span class="notification-details">
<div class="name-and-action">
<!-- eslint-disable vue/no-v-html -->
- <bdi
- v-if="!!notification.from_profile.name_html"
- class="username"
- :title="'@'+notification.from_profile.screen_name_ui"
- v-html="notification.from_profile.name_html"
- />
+ <bdi v-if="!!notification.from_profile.name_html">
+ <RichContent
+ class="username"
+ :title="'@'+notification.from_profile.screen_name_ui"
+ :html="notification.from_profile.name_html"
+ :emoji="notification.from_profile.emoji"
+ />
+ </bdi>
<!-- eslint-enable vue/no-v-html -->
<span
v-else
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 2bb627a8..77b3c438 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -148,13 +148,6 @@
max-width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
-
- img {
- width: 14px;
- height: 14px;
- vertical-align: middle;
- object-fit: contain
- }
}
.timeago {
diff --git a/src/components/poll/poll.js b/src/components/poll/poll.js
index 98db5582..a69b7886 100644
--- a/src/components/poll/poll.js
+++ b/src/components/poll/poll.js
@@ -1,10 +1,14 @@
-import Timeago from '../timeago/timeago.vue'
+import Timeago from 'components/timeago/timeago.vue'
+import RichContent from 'components/rich_content/rich_content.jsx'
import { forEach, map } from 'lodash'
export default {
name: 'Poll',
- props: ['basePoll'],
- components: { Timeago },
+ props: ['basePoll', 'emoji'],
+ components: {
+ Timeago,
+ RichContent
+ },
data () {
return {
loading: false,
diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue
index 187d1829..63b44e4f 100644
--- a/src/components/poll/poll.vue
+++ b/src/components/poll/poll.vue
@@ -17,8 +17,11 @@
<span class="result-percentage">
{{ percentageForOption(option.votes_count) }}%
</span>
- <!-- eslint-disable-next-line vue/no-v-html -->
- <span v-html="option.title_html" />
+ <RichContent
+ :html="option.title_html"
+ :handle-links="false"
+ :emoji="emoji"
+ />
</div>
<div
class="result-fill"
@@ -42,8 +45,11 @@
:value="index"
>
<label class="option-vote">
- <!-- eslint-disable-next-line vue/no-v-html -->
- <div v-html="option.title_html" />
+ <RichContent
+ :html="option.title_html"
+ :handle-links="false"
+ :emoji="emoji"
+ />
</label>
</div>
</div>
diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx
index cd73f2e5..1353541f 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 <MentionLink>-s
- * - Removes mentions from beginning and end (hellthread style only)
+ * - Groups all mentions into <MentionsLine>, this affects all mentions regardles
+ * of where they are (beginning/middle/end), even single mentions are converted
+ * to a <MentionsLine> containing single <MentionLink>.
* - Replaces emoji shortcodes with <StillImage>'d images.
*
- * Stuff like removing mentions from beginning and end is done so that they could
- * be either replaced by collapsible <MentionsLine> 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.
@@ -56,25 +54,22 @@ export default Vue.component('RichContent', {
required: false,
type: Boolean,
default: false
- },
- 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.handleLinks)
- const firstMentions = [] // Mentions that appear in the beginning of post body
+ const { newHtml: html } = preProcessPerLine(this.html, this.greentext)
+ let currentMentions = null // Current chain of mentions, we group all mentions together
+
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
let tagsIndex = 0
- let firstMentionReplaced = false
const renderImage = (tag) => {
return <StillImage
@@ -98,41 +93,35 @@ export default Vue.component('RichContent', {
const renderMention = (attrs, children) => {
const linkData = getLinkData(attrs, children, mentionIndex++)
linkData.notifying = this.attentions.some(a => a.statusnet_profile_url === linkData.url)
- if (!linkData.notifying) {
- encounteredText = true
- }
writtenMentions.push(linkData)
- if (!encounteredText) {
- firstMentions.push(linkData)
- if (!firstMentionReplaced && !this.hideMentions) {
- firstMentionReplaced = true
- return <MentionsLine mentions={ firstMentions } />
- } else {
- return ''
- }
+ if (currentMentions === null) {
+ currentMentions = []
+ }
+ currentMentions.push(linkData)
+ if (currentMentions.length > MENTIONS_LIMIT) {
+ invisibleMentions.push(linkData)
+ }
+ if (currentMentions.length === 1) {
+ return <MentionsLine mentions={ currentMentions } />
} else {
- return <MentionLink
- url={attrs.href}
- content={flattenDeep(children).join('')}
- />
+ return ''
}
}
- // We stop treating mentions as "first" ones when we encounter
- // non-whitespace text
- let encounteredText = false
// Processor to use with html_tree_converter
const processItem = (item, index, array, what) => {
// Handle text nodes - just add emoji
if (typeof item === 'string') {
const emptyText = item.trim() === ''
- if (emptyText) {
- return encounteredText ? item : item.trim()
+ if (item.includes('\n')) {
+ currentMentions = null
}
- if (!encounteredText) {
- item = item.trimStart()
- encounteredText = true
+ if (emptyText) {
+ // don't include spaces when processing mentions - we'll include them
+ // in MentionsLine
+ return currentMentions !== null ? item.trim() : item
}
+ currentMentions = null
if (item.includes(':')) {
item = ['', processTextForEmoji(
item,
@@ -156,28 +145,25 @@ export default Vue.component('RichContent', {
const Tag = getTagName(opener)
const attrs = getAttrs(opener)
switch (Tag) {
- case 'span': // Replace last mentions class with mentionsline
- if (attrs['class'] && attrs['class'].includes('lastMentions')) {
- if (firstMentions.length > 1 && lastMentions.length > 1) {
- break
- } else {
- return !this.hideMentions ? <MentionsLine mentions={lastMentions} /> : ''
- }
- } else {
- break
- }
+ case 'br':
+ currentMentions = null
+ break
case 'img': // replace images with StillImage
return renderImage(opener)
case 'a': // replace mentions with MentionLink
if (!this.handleLinks) break
if (attrs['class'] && attrs['class'].includes('mention')) {
// Handling mentions here
- return renderMention(attrs, children, encounteredText)
+ return renderMention(attrs, children)
} else {
// Everything else will be handled in reverse pass
- encounteredText = true
+ currentMentions = null
return item // We'll handle it later
}
+ case 'span':
+ if (this.handleLinks && attrs['class'] && attrs['class'].includes('h-card')) {
+ return ['', children.map(processItem), '']
+ }
}
if (children !== undefined) {
@@ -246,11 +232,10 @@ export default Vue.component('RichContent', {
</span>
const event = {
- firstMentions,
- lastMentions,
lastTags,
writtenMentions,
- writtenTags
+ writtenTags,
+ invisibleMentions
}
// DO NOT MOVE TO UPDATE. BAD IDEA.
@@ -261,44 +246,46 @@ 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'])
- let nonEmptyIndex = -1
const lines = convertHtmlToLines(html)
- const linesNum = lines.filter(c => c.text).length
const newHtml = lines.reverse().map((item, index, array) => {
// Going over each line in reverse to detect last mentions,
// keeping non-text stuff as-is
if (!item.text) return item
const string = item.text
- nonEmptyIndex += 1
// Greentext stuff
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('&gt;') || string.includes('&lt;'))
@@ -313,80 +300,8 @@ export const preProcessPerLine = (html, greentext, handleLinks) => {
}
}
- // Converting that line part into tree
- const tree = convertHtmlToTree(string)
-
- // If line has loose text, i.e. text outside a mention or a tag
- // we won't touch mentions.
- let hasLooseText = false
- let mentionsNum = 0
- const process = (item) => {
- if (Array.isArray(item)) {
- const [opener, children, closer] = item
- 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
- mentionsNum++
- return [opener, children, closer]
- } else {
- // Not a mention? Means we have loose text or whatever
- hasLooseText = true
- return [opener, children, closer]
- }
- } else if (tag === 'span' || tag === 'p') {
- // For span and p we need to go deeper
- return [opener, [...children].map(process), closer]
- } else {
- // Everything else equals to a loose text
- hasLooseText = true
- return [opener, children, closer]
- }
- }
-
- if (typeof item === 'string') {
- if (item.trim() !== '') {
- // only meaningful strings are loose text
- hasLooseText = true
- }
- return item
- }
- }
-
- // We now processed our tree, now we need to mark line as lastMentions
- const result = [...tree].map(process)
-
- if (
- handleLinks && // Do we handle links at all?
- mentionsNum > 1 && // Does it have more than one mention?
- !hasLooseText && // Don't do anything if it has something besides mentions
- nonEmptyIndex === 0 && // Only check last (first since list is reversed) line
- nonEmptyIndex !== linesNum - 1 // Don't do anything if there's only one line
- ) {
- let mentionIndex = 0
- const process = (item) => {
- if (Array.isArray(item)) {
- const [opener, children] = item
- const tag = getTagName(opener)
- if (tag === 'a') {
- const attrs = getAttrs(opener)
- lastMentions.push(getLinkData(attrs, children, mentionIndex++))
- } else if (children) {
- children.forEach(process)
- }
- }
- }
- result.forEach(process)
- // we DO need mentions here so that we conditionally remove them if don't
- // have first mentions
- return ['<span class="lastMentions">', flattenDeep(result).join(''), '</span>'].join('')
- } else {
- return flattenDeep(result).join('')
- }
+ return string
}).reverse().join('')
- return { newHtml, lastMentions }
+ return { newHtml }
}
diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss
index 12cb9776..db08ef1e 100644
--- a/src/components/rich_content/rich_content.scss
+++ b/src/components/rich_content/rich_content.scss
@@ -49,6 +49,7 @@
}
.emoji {
+ display: inline-block;
width: var(--emoji-size, 32px);
height: var(--emoji-size, 32px);
}
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 71780e00..d3e71b31 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -42,16 +42,6 @@
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="mentionsOwnLine">
- {{ $t('settings.mentions_new_place') }}
- </BooleanSetting>
- </li>
- <li>
- <BooleanSetting path="mentionsNewStyle">
- {{ $t('settings.mentions_new_style') }}
- </BooleanSetting>
- </li>
- <li>
<BooleanSetting path="streaming">
{{ $t('settings.streaming') }}
</BooleanSetting>
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 3c21cb76..ac481534 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -166,29 +166,22 @@ const Status = {
muteWordHits () {
return muteWordHits(this.status, this.muteWords)
},
- mentions () {
+ mentionsLine () {
+ 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
+ // 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,
userId: attn.id
}))
},
- alsoMentions () {
- if (!this.headTailLinks) return []
- const set = new Set(this.headTailLinks.writtenMentions.map(m => m.url))
- return this.headTailLinks.writtenMentions.filter(mention => {
- return !set.has(mention.url)
- })
- },
- mentionsLine () {
- return this.mentionsOwnLine ? this.mentions : this.alsoMentions
- },
- mentionsOwnLine () {
- return this.mergedConfig.mentionsOwnLine
- },
hasMentionsLine () {
return this.mentionsLine.length > 0
},
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index a5f347a6..2684e415 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -306,7 +306,6 @@
:no-heading="noHeading"
:highlight="highlight"
:focused="isFocused"
- :hide-mentions="mentionsOwnLine && (isReply || true)"
@mediaplay="addMediaPlaying($event)"
@mediapause="removeMediaPlaying($event)"
@parseReady="setHeadTailLinks"
diff --git a/src/components/status_body/status_body.js b/src/components/status_body/status_body.js
index 7ee965d9..f4167ac1 100644
--- a/src/components/status_body/status_body.js
+++ b/src/components/status_body/status_body.js
@@ -26,15 +26,16 @@ const StatusContent = {
'focused',
'noHeading',
'fullContent',
- 'singleLine',
- 'hideMentions'
+ 'singleLine'
],
data () {
return {
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,
+ parseReadyDone: false
}
},
computed: {
@@ -49,7 +50,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(/<p|<br/).length + this.status.text.length / 80
+ const lengthScore = this.status.raw_html.split(/<p|<br/).length + this.postLength / 80
return lengthScore > 20
},
longSubject () {
@@ -87,8 +88,10 @@ const StatusContent = {
},
methods: {
onParseReady (event) {
+ if (this.parseReadyDone) return
+ this.parseReadyDone = true
this.$emit('parseReady', event)
- const { writtenMentions } = event
+ const { writtenMentions, invisibleMentions } = event
writtenMentions
.filter(mention => !mention.notifying)
.forEach(mention => {
@@ -99,6 +102,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) {
diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue
index 1001508c..a088e6bc 100644
--- a/src/components/status_body/status_body.vue
+++ b/src/components/status_body/status_body.vue
@@ -48,7 +48,6 @@
:html="status.raw_html"
:emoji="status.emojis"
:handle-links="true"
- :hide-mentions="hideMentions"
:greentext="mergedConfig.greentext"
:attentions="status.attentions"
@parseReady="onParseReady"
diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js
index 9059642e..184f2783 100644
--- a/src/components/status_content/status_content.js
+++ b/src/components/status_content/status_content.js
@@ -31,8 +31,7 @@ const StatusContent = {
'focused',
'noHeading',
'fullContent',
- 'singleLine',
- 'hideMentions'
+ 'singleLine'
],
computed: {
hideAttachments () {
diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue
index 86b3c52a..9db176f0 100644
--- a/src/components/status_content/status_content.vue
+++ b/src/components/status_content/status_content.vue
@@ -8,11 +8,13 @@
:status="status"
:compact="compact"
:single-line="singleLine"
- :hide-mentions="hideMentions"
@parseReady="$emit('parseReady', $event)"
>
<div v-if="status.poll && status.poll.options">
- <poll :base-poll="status.poll" />
+ <Poll
+ :base-poll="status.poll"
+ :emoji="status.emojis"
+ />
</div>
<gallery
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index 23e6358f..a453ce79 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -5,6 +5,7 @@ import FollowButton from '../follow_button/follow_button.vue'
import ModerationTools from '../moderation_tools/moderation_tools.vue'
import AccountActions from '../account_actions/account_actions.vue'
import Select from '../select/select.vue'
+import RichContent from 'src/components/rich_content/rich_content.jsx'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
@@ -118,7 +119,8 @@ export default {
AccountActions,
ProgressButton,
FollowButton,
- Select
+ Select,
+ RichContent
},
methods: {
muteUser () {
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index a16f7873..794a2350 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -38,21 +38,12 @@
</router-link>
<div class="user-summary">
<div class="top-line">
- <!-- eslint-disable vue/no-v-html -->
- <div
- v-if="user.name_html"
+ <RichContent
:title="user.name"
class="user-name"
- v-html="user.name_html"
+ :html="user.name"
+ :emoji="user.emoji"
/>
- <!-- eslint-enable vue/no-v-html -->
- <div
- v-else
- :title="user.name"
- class="user-name"
- >
- {{ user.name }}
- </div>
<button
v-if="isOtherUser && !user.is_local"
:href="user.statusnet_profile_url"
@@ -255,20 +246,12 @@
<span>{{ hideFollowersCount ? $t('user_card.hidden') : user.followers_count }}</span>
</div>
</div>
- <!-- eslint-disable vue/no-v-html -->
- <p
- v-if="!hideBio && user.description_html"
+ <RichContent
+ v-if="!hideBio"
class="user-card-bio"
- @click.prevent="linkClicked"
- v-html="user.description_html"
+ :html="user.description_html"
+ :emoji="user.emoji"
/>
- <!-- eslint-enable vue/no-v-html -->
- <p
- v-else-if="!hideBio"
- class="user-card-bio"
- >
- {{ user.description }}
- </p>
</div>
</div>
</template>
@@ -281,9 +264,10 @@
.user-card {
position: relative;
- &:hover .Avatar {
+ &:hover {
--_still-image-img-visibility: visible;
--_still-image-canvas-visibility: hidden;
+ --_still-image-label-visibility: hidden;
}
.panel-heading {
@@ -327,12 +311,12 @@
}
}
- p {
- margin-bottom: 0;
- }
-
&-bio {
text-align: center;
+ display: block;
+ line-height: 18px;
+ padding: 1em;
+ margin: 0;
a {
color: $fallback--link;
@@ -344,11 +328,6 @@
vertical-align: middle;
max-width: 100%;
max-height: 400px;
-
- &.emoji {
- width: 32px;
- height: 32px;
- }
}
}
@@ -450,13 +429,6 @@
// big one
z-index: 1;
- img {
- width: 26px;
- height: 26px;
- vertical-align: middle;
- object-fit: contain
- }
-
.top-line {
display: flex;
}
@@ -469,12 +441,7 @@
margin-right: 1em;
font-size: 15px;
- img {
- object-fit: contain;
- height: 16px;
- width: 16px;
- vertical-align: middle;
- }
+ --emoji-size: 14px;
}
.bottom-line {
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index c0b55a6c..7a475609 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -4,6 +4,7 @@ import FollowCard from '../follow_card/follow_card.vue'
import Timeline from '../timeline/timeline.vue'
import Conversation from '../conversation/conversation.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
+import RichContent from 'src/components/rich_content/rich_content.jsx'
import List from '../list/list.vue'
import withLoadMore from '../../hocs/with_load_more/with_load_more'
import { library } from '@fortawesome/fontawesome-svg-core'
@@ -164,7 +165,8 @@ const UserProfile = {
FriendList,
FollowCard,
TabSwitcher,
- Conversation
+ Conversation,
+ RichContent
}
}
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index aef897ae..726216ff 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -20,20 +20,24 @@
:key="index"
class="user-profile-field"
>
- <!-- eslint-disable vue/no-v-html -->
<dt
:title="user.fields_text[index].name"
class="user-profile-field-name"
- @click.prevent="linkClicked"
- v-html="field.name"
- />
+ >
+ <RichContent
+ :html="field.name"
+ :emoji="user.emoji"
+ />
+ </dt>
<dd
:title="user.fields_text[index].value"
class="user-profile-field-value"
- @click.prevent="linkClicked"
- v-html="field.value"
- />
- <!-- eslint-enable vue/no-v-html -->
+ >
+ <RichContent
+ :html="field.value"
+ :emoji="user.emoji"
+ />
+ </dd>
</dl>
</div>
<tab-switcher
diff --git a/src/modules/config.js b/src/modules/config.js
index db9d5ffb..bdab3f4d 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -55,8 +55,6 @@ export const defaultState = {
interfaceLanguage: browserLocale,
hideScopeNotice: false,
useStreamingApi: false,
- mentionsOwnLine: false,
- mentionsNewStyle: false,
sidebarRight: undefined, // instance default
scopeCopy: undefined, // instance default
subjectLineBehavior: undefined, // instance default
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 477b861f..04bb45a4 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -56,16 +56,17 @@ export const parseUser = (data) => {
output.emoji = data.emojis
output.name = data.display_name
- output.name_html = addEmojis(escape(data.display_name), data.emojis)
+ output.name_html = escape(data.display_name)
output.description = data.note
- output.description_html = addEmojis(data.note, data.emojis)
+ // TODO cleanup this shit, output.description is overriden with source data
+ output.description_html = data.note
output.fields = data.fields
output.fields_html = data.fields.map(field => {
return {
- name: addEmojis(escape(field.name), data.emojis),
- value: addEmojis(field.value, data.emojis)
+ name: escape(field.name),
+ value: field.value
}
})
output.fields_text = data.fields.map(field => {
@@ -240,16 +241,6 @@ export const parseAttachment = (data) => {
return output
}
-export const addEmojis = (string, emojis) => {
- const matchOperatorsRegex = /[|\\{}()[\]^$+*?.-]/g
- return emojis.reduce((acc, emoji) => {
- const regexSafeShortCode = emoji.shortcode.replace(matchOperatorsRegex, '\\$&')
- return acc.replace(
- new RegExp(`:${regexSafeShortCode}:`, 'g'),
- `<img src='${emoji.url}' alt=':${emoji.shortcode}:' title=':${emoji.shortcode}:' class='emoji' />`
- )
- }, string)
-}
export const parseStatus = (data) => {
const output = {}
@@ -301,7 +292,7 @@ export const parseStatus = (data) => {
if (output.poll) {
output.poll.options = (output.poll.options || []).map(field => ({
...field,
- title_html: addEmojis(escape(field.title), data.emojis)
+ title_html: escape(field.title)
}))
}
output.pinned = data.pinned
diff --git a/src/services/html_converter/html_line_converter.service.js b/src/services/html_converter/html_line_converter.service.js
index 74103b02..5eeaa7cb 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 804d35d7..6a8796c4 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([
diff --git a/test/unit/specs/components/rich_content.spec.js b/test/unit/specs/components/rich_content.spec.js
index fbf8973d..b29edeab 100644
--- a/test/unit/specs/components/rich_content.spec.js
+++ b/test/unit/specs/components/rich_content.spec.js
@@ -2,13 +2,19 @@ import { mount, shallowMount, createLocalVue } from '@vue/test-utils'
import RichContent from 'src/components/rich_content/rich_content.jsx'
const localVue = createLocalVue()
+const attentions = []
-const makeMention = (who) => `<span class="h-card"><a class="u-url mention" href="https://fake.tld/@${who}">@<span>${who}</span></a></span>`
-const stubMention = (who) => `<span class="h-card"><mentionlink-stub url="https://fake.tld/@${who}" content="@<span>${who}</span>"></mentionlink-stub></span>`
-const lastMentions = (...data) => `<span class="lastMentions">${data.join('')}</span>`
+const makeMention = (who) => {
+ attentions.push({ statusnet_profile_url: `https://fake.tld/@${who}` })
+ return `<span class="h-card"><a class="u-url mention" href="https://fake.tld/@${who}">@<span>${who}</span></a></span>`
+}
const p = (...data) => `<p>${data.join('')}</p>`
const compwrap = (...data) => `<span class="RichContent">${data.join('')}</span>`
-const removedMentionSpan = '<span class="h-card"></span>'
+const mentionsLine = (times) => [
+ '<mentionsline-stub mentions="',
+ new Array(times).fill('[object Object]').join(','),
+ '"></mentionsline-stub>'
+].join('')
describe('RichContent', () => {
it('renders simple post without exploding', () => {
@@ -16,7 +22,7 @@ describe('RichContent', () => {
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
- hideMentions: true,
+ attentions,
handleLinks: true,
greentext: true,
emoji: [],
@@ -39,7 +45,7 @@ describe('RichContent', () => {
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
- hideMentions: true,
+ attentions,
handleLinks: true,
greentext: true,
emoji: [],
@@ -50,77 +56,15 @@ describe('RichContent', () => {
expect(wrapper.html()).to.eql(compwrap(expected))
})
- it('removes mentions from the beginning of post', () => {
+ it('replaces mention with mentionsline', () => {
const html = p(
makeMention('John'),
- ' how are you doing thoday?'
+ ' how are you doing today?'
)
- const expected = p(
- removedMentionSpan,
- 'how are you doing thoday?'
- )
- const wrapper = shallowMount(RichContent, {
- localVue,
- propsData: {
- hideMentions: true,
- handleLinks: true,
- greentext: true,
- emoji: [],
- html
- }
- })
-
- expect(wrapper.html()).to.eql(compwrap(expected))
- })
-
- it('replaces first mention with mentionsline if hideMentions=false', () => {
- const html = p(
- makeMention('John'),
- ' how are you doing thoday?'
- )
- const expected = p(
- '<span class="h-card">',
- '<mentionsline-stub mentions="',
- '[object Object]',
- '"></mentionsline-stub>',
- '</span>',
- 'how are you doing thoday?'
- )
- const wrapper = shallowMount(RichContent, {
- localVue,
- propsData: {
- hideMentions: false,
- handleLinks: true,
- greentext: true,
- emoji: [],
- html
- }
- })
-
- expect(wrapper.html()).to.eql(compwrap(expected))
- })
-
- it('removes mentions from the end of the hellpost (<p>)', () => {
- 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: {
- hideMentions: true,
+ attentions,
handleLinks: true,
greentext: true,
emoji: [],
@@ -128,10 +72,13 @@ describe('RichContent', () => {
}
})
- expect(wrapper.html()).to.eql(compwrap(expected))
+ expect(wrapper.html()).to.eql(compwrap(p(
+ mentionsLine(1),
+ ' how are you doing today?'
+ )))
})
- it('replaces mentions at the end of the hellpost if hideMentions=false (<p>)', () => {
+ it('replaces mentions at the end of the hellpost', () => {
const html = [
p('How are you doing today, fine gentlemen?'),
p(
@@ -157,184 +104,7 @@ describe('RichContent', () => {
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
- hideMentions: false,
- handleLinks: true,
- greentext: true,
- emoji: [],
- html
- }
- })
-
- expect(wrapper.html()).to.eql(compwrap(expected))
- })
-
- it('removes mentions from the end of the hellpost (<br>)', () => {
- const html = [
- 'How are you doing today, fine gentlemen?',
- [
- makeMention('John'),
- makeMention('Josh'),
- makeMention('Jeremy')
- ].join('')
- ].join('<br>')
- const expected = [
- 'How are you doing today, fine gentlemen?',
- // TODO fix this extra line somehow?
- '<br>'
- ].join('')
-
- const wrapper = shallowMount(RichContent, {
- localVue,
- propsData: {
- hideMentions: true,
- 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: {
- hideMentions: true,
- 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: {
- hideMentions: true,
- 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: {
- hideMentions: true,
- 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: {
- hideMentions: true,
+ attentions,
handleLinks: true,
greentext: true,
emoji: [],
@@ -362,7 +132,7 @@ describe('RichContent', () => {
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
- hideMentions: true,
+ attentions,
handleLinks: false,
greentext: true,
emoji: [],
@@ -386,7 +156,7 @@ describe('RichContent', () => {
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
- hideMentions: true,
+ attentions,
handleLinks: false,
greentext: true,
emoji: [],
@@ -406,7 +176,7 @@ describe('RichContent', () => {
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
- hideMentions: true,
+ attentions,
handleLinks: false,
greentext: false,
emoji: [],
@@ -427,7 +197,7 @@ describe('RichContent', () => {
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
- hideMentions: true,
+ attentions,
handleLinks: false,
greentext: false,
emoji: [{ url: 'about:blank', shortcode: 'spurdo' }],
@@ -444,7 +214,7 @@ describe('RichContent', () => {
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
- hideMentions: true,
+ attentions,
handleLinks: false,
greentext: false,
emoji: [],
@@ -464,7 +234,7 @@ describe('RichContent', () => {
].join('\n')
const expected = [
'<span class="greentext">&gt;quote</span>',
- stubMention('lol'),
+ mentionsLine(1),
'<span class="greentext">&gt;quote</span>',
'<span class="greentext">&gt;quote</span>'
].join('\n')
@@ -472,6 +242,7 @@ describe('RichContent', () => {
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
+ attentions,
handleLinks: true,
greentext: true,
emoji: [],
@@ -496,127 +267,14 @@ describe('RichContent', () => {
const expected = [
'Bruh',
'Bruh',
- [
- stubMention('foo'),
- stubMention('bar'),
- stubMention('baz')
- ].join(''),
+ mentionsLine(3),
'Bruh'
].join('<br>')
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
- hideMentions: true,
- handleLinks: true,
- greentext: true,
- emoji: [],
- html
- }
- })
-
- expect(wrapper.html()).to.eql(compwrap(expected))
- })
-
- it('Don\'t remove last mention if it\'s the only one', () => {
- const html = [
- 'Bruh',
- 'Bruh',
- makeMention('foo'),
- makeMention('bar'),
- makeMention('baz')
- ].join('<br>')
- const expected = [
- 'Bruh',
- 'Bruh',
- stubMention('foo'),
- stubMention('bar'),
- stubMention('baz')
- ].join('<br>')
-
- const wrapper = shallowMount(RichContent, {
- localVue,
- propsData: {
- hideMentions: true,
- handleLinks: true,
- greentext: true,
- emoji: [],
- html
- }
- })
-
- expect(wrapper.html()).to.eql(compwrap(expected))
- })
-
- it('Don\'t remove last mentions if there are more than one first mention - remove first instead', () => {
- const html = [
- [
- makeMention('foo'),
- makeMention('bar')
- ].join(' '),
- 'Bruh',
- 'Bruh',
- [
- makeMention('foo'),
- makeMention('bar'),
- makeMention('baz')
- ].join(' ')
- ].join('\n')
-
- const expected = [
- [
- removedMentionSpan,
- removedMentionSpan,
- 'Bruh' // Due to trim we remove extra newline
- ].join(''),
- 'Bruh',
- lastMentions([
- stubMention('foo'),
- stubMention('bar'),
- stubMention('baz')
- ].join(' '))
- ].join('\n')
-
- const wrapper = shallowMount(RichContent, {
- localVue,
- propsData: {
- hideMentions: true,
- handleLinks: true,
- greentext: true,
- emoji: [],
- html
- }
- })
-
- expect(wrapper.html()).to.eql(compwrap(expected))
- })
-
- it('Remove last mentions if there\'s just one first mention - remove all', () => {
- const html = [
- [
- makeMention('foo')
- ].join(' '),
- 'Bruh',
- 'Bruh',
- [
- makeMention('foo'),
- makeMention('bar'),
- makeMention('baz')
- ].join(' ')
- ].join('\n')
-
- const expected = [
- [
- removedMentionSpan,
- 'Bruh' // Due to trim we remove extra newline
- ].join(''),
- 'Bruh\n' // Can't remove this one yet
- ].join('\n')
-
- const wrapper = shallowMount(RichContent, {
- localVue,
- propsData: {
- hideMentions: true,
+ attentions,
handleLinks: true,
greentext: true,
emoji: [],
@@ -652,7 +310,7 @@ describe('RichContent', () => {
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
- hideMentions: true,
+ attentions,
handleLinks: true,
greentext: true,
emoji: [],
@@ -664,53 +322,7 @@ describe('RichContent', () => {
})
it('rich contents of a mention are handled properly', () => {
- const html = [
- p(
- 'Testing'
- ),
- p(
- '<a href="lol" class="mention">',
- '<span>',
- 'https://</span>',
- '<span>',
- 'lol.tld/</span>',
- '<span>',
- '</span>',
- '</a>'
- )
- ].join('')
- const expected = [
- p(
- 'Testing'
- ),
- p(
- '<mentionlink-stub url="lol" content="',
- '<span>',
- 'https://</span>',
- '<span>',
- 'lol.tld/</span>',
- '<span>',
- '</span>',
- '">',
- '</mentionlink-stub>'
- )
- ].join('')
-
- const wrapper = shallowMount(RichContent, {
- localVue,
- propsData: {
- hideMentions: false,
- handleLinks: true,
- greentext: true,
- emoji: [],
- html
- }
- })
-
- expect(wrapper.html()).to.eql(compwrap(expected))
- })
-
- it('rich contents of a mention in beginning are handled properly', () => {
+ attentions.push({ statusnet_profile_url: 'lol' })
const html = [
p(
'<a href="lol" class="mention">',
@@ -729,16 +341,19 @@ describe('RichContent', () => {
const expected = [
p(
'<span class="MentionsLine">',
- '<mentionlink-stub content="',
+ '<span class="MentionLink mention-link">',
+ '<a href="lol" target="_blank" class="original">',
'<span>',
'https://</span>',
'<span>',
'lol.tld/</span>',
'<span>',
'</span>',
- '" url="lol" class="mention-link">',
- '</mentionlink-stub>',
- '<!---->', // v-if placeholder
+ '</a>',
+ ' ',
+ '<!---->', // v-if placeholder, mentionlink's "new" (i.e. rich) display
+ '</span>',
+ '<!---->', // v-if placeholder, mentionsline's extra mentions and stuff
'</span>'
),
p(
@@ -748,11 +363,8 @@ describe('RichContent', () => {
const wrapper = mount(RichContent, {
localVue,
- stubs: {
- MentionLink: true
- },
propsData: {
- hideMentions: false,
+ attentions,
handleLinks: true,
greentext: true,
emoji: [],
@@ -796,7 +408,7 @@ describe('RichContent', () => {
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
- hideMentions: false,
+ attentions,
handleLinks: true,
greentext: true,
emoji: [],
@@ -806,4 +418,63 @@ describe('RichContent', () => {
expect(wrapper.html()).to.eql(compwrap(expected))
})
+
+ it.skip('[INFORMATIVE] Performance testing, 10 000 simple posts', () => {
+ const amount = 20
+
+ const onePost = p(
+ makeMention('Lain'),
+ makeMention('Lain'),
+ makeMention('Lain'),
+ makeMention('Lain'),
+ makeMention('Lain'),
+ makeMention('Lain'),
+ makeMention('Lain'),
+ makeMention('Lain'),
+ makeMention('Lain'),
+ makeMention('Lain'),
+ ' i just landed in l a where are you'
+ )
+
+ const TestComponent = {
+ template: `
+ <div v-if="!vhtml">
+ ${new Array(amount).fill(`<RichContent html="${onePost}" :greentext="true" :handleLinks="handeLinks" :emoji="[]" :attentions="attentions"/>`)}
+ </div>
+ <div v-else="vhtml">
+ ${new Array(amount).fill(`<div v-html="${onePost}"/>`)}
+ </div>
+ `,
+ props: ['handleLinks', 'attentions', 'vhtml']
+ }
+ console.log(1)
+
+ const ptest = (handleLinks, vhtml) => {
+ const t0 = performance.now()
+
+ const wrapper = mount(TestComponent, {
+ localVue,
+ propsData: {
+ attentions,
+ handleLinks,
+ vhtml
+ }
+ })
+
+ const t1 = performance.now()
+
+ wrapper.destroy()
+
+ const t2 = performance.now()
+
+ return `Mount: ${t1 - t0}ms, destroy: ${t2 - t1}ms, avg ${(t1 - t0) / amount}ms - ${(t2 - t1) / amount}ms per item`
+ }
+
+ console.log(`${amount} items with links handling:`)
+ console.log(ptest(true))
+ console.log(`${amount} items without links handling:`)
+ console.log(ptest(false))
+ console.log(`${amount} items plain v-html:`)
+ console.log(ptest(false, true))
+ })
})
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 8a5a6ef9..03fb32c9 100644
--- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
+++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
@@ -1,4 +1,4 @@
-import { parseStatus, parseUser, parseNotification, addEmojis, parseLinkHeaderPagination } from '../../../../../src/services/entity_normalizer/entity_normalizer.service.js'
+import { parseStatus, parseUser, parseNotification, parseLinkHeaderPagination } from '../../../../../src/services/entity_normalizer/entity_normalizer.service.js'
import mastoapidata from '../../../../fixtures/mastoapi.json'
import qvitterapidata from '../../../../fixtures/statuses.json'
@@ -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('<img')
- })
-
- it('adds emojis to user bio', () => {
- 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('<img')
- })
-
- it('adds emojis to user profile fields', () => {
- 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('<img')
- expect(field).to.have.property('value').that.contains('<img')
- })
-
it('removes html tags from user profile fields', () => {
const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), fields: [{ name: 'user', value: '<a rel="me" href="https://example.com/@user">@user</a>' }] })
@@ -338,41 +309,6 @@ describe('API Entities normalizer', () => {
})
})
- describe('MastoAPI emoji adder', () => {
- const emojis = makeMockEmojiMasto()
- const imageHtml = '<img src="https://example.com/image.png" alt=":image:" title=":image:" class="emoji" />'
- .replace(/"/g, '\'')
- const thinkHtml = '<img src="https://example.com/think.png" alt=":thinking:" title=":thinking:" class="emoji" />'
- .replace(/"/g, '\'')
-
- it('correctly replaces shortcodes in supplied string', () => {
- const result = addEmojis('This post has :image: emoji and :thinking: emoji', emojis)
- expect(result).to.include(thinkHtml)
- expect(result).to.include(imageHtml)
- })
-
- it('handles consecutive emojis correctly', () => {
- const result = addEmojis('Lelel emoji spam :thinking::thinking::thinking::thinking:', emojis)
- expect(result).to.include(thinkHtml + thinkHtml + thinkHtml + thinkHtml)
- })
-
- it('Doesn\'t replace nonexistent emojis', () => {
- const result = addEmojis('Admin add the :tenshi: emoji', emojis)
- expect(result).to.equal('Admin add the :tenshi: emoji')
- })
-
- it('Doesn\'t blow up on regex special characters', () => {
- const emojis = makeMockEmojiMasto([{
- shortcode: 'c++'
- }, {
- shortcode: '[a-z] {|}*'
- }])
- const result = addEmojis('This post has :c++: emoji and :[a-z] {|}*: emoji', emojis)
- expect(result).to.include('title=\':c++:\'')
- expect(result).to.include('title=\':[a-z] {|}*:\'')
- })
- })
-
describe('Link header pagination', () => {
it('Parses min and max ids as integers', () => {
const linkHeader = '<https://example.com/api/v1/notifications?max_id=861676>; rel="next", <https://example.com/api/v1/notifications?min_id=861741>; rel="prev"'