aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHenry Jameson <me@hjkos.com>2021-08-15 16:27:41 +0300
committerHenry Jameson <me@hjkos.com>2021-08-15 16:27:41 +0300
commit17d2eed06a7c0c2a38129e36cca1e676c351abce (patch)
tree2521a727071db6f7cfe4e76f7e227dfe69b750d6 /src
parentb67db47c888dd45c49a49b82e7922c2bf40ed61c (diff)
parentf16658adfc897a3b07ed7f79d872acd2c3837cc8 (diff)
Merge branch 'better-still-emoji' into proper-attachments
* better-still-emoji: fix tests prevent infinite update loops remove obsolete tests removed useless code, review change, fixed bug with tall statuses fixed mentions line again remove old emoji added, everything emoji-bearing uses RichContent now richcontent support in polls, user cards and user profiles support richcontent in polls fix tests, add performance test (skipped, doesn't assert anything), tweak max mentions count made the code responsible for showing unwritten mentions actually work remove new options for style and separate line, now groups all chained mentions on a mentionsline regardless of placement. fixes spacing fix tests
Diffstat (limited to 'src')
-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
31 files changed, 174 insertions, 364 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([