remove new options for style and separate line, now groups all chained

mentions on a mentionsline regardless of placement. fixes spacing
This commit is contained in:
Henry Jameson 2021-08-12 02:49:37 +03:00
parent a0eaac2216
commit 2cfff1b8b9
16 changed files with 55 additions and 584 deletions

View file

@ -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
]

View file

@ -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 {

View file

@ -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 -->

View file

@ -14,11 +14,8 @@ const MentionsLine = {
MentionLink
},
computed: {
oldStyle () {
return !this.mergedConfig.mentionsNewStyle
},
limit () {
return 6
return 2
},
mentionsComputed () {
return this.mentions.slice(0, this.limit)
@ -29,16 +26,6 @@ const MentionsLine = {
manyMentions () {
return this.extraMentions.length > 0
},
buttonClasses () {
return [
this.oldStyle
? 'button-unstyled'
: 'button-default -sublime',
this.oldStyle
? '-oldStyle'
: '-newStyle'
]
},
...mapGetters(['mergedConfig'])
},
methods: {

View file

@ -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;
}
}

View file

@ -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') }}

View file

@ -56,25 +56,21 @@ 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, this.handleLinks)
let currentMentions = null // Current chain of mentions, we group all mentions together
// to collapse too many mentions in a row
const lastTags = [] // Tags that appear at the end of post body
const writtenMentions = [] // All mentions that appear in post body
const 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 +94,32 @@ 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 === 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 (item.includes('\n')) {
currentMentions = null
}
if (emptyText) {
return encounteredText ? item : item.trim()
}
if (!encounteredText) {
item = item.trimStart()
encounteredText = true
// 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 +143,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 (attrs['class'].includes('h-card')) {
return ['', children.map(processItem), '']
}
}
if (children !== undefined) {
@ -246,8 +230,6 @@ export default Vue.component('RichContent', {
</span>
const event = {
firstMentions,
lastMentions,
lastTags,
writtenMentions,
writtenTags
@ -284,15 +266,12 @@ export const preProcessPerLine = (html, greentext, handleLinks) => {
const lastMentions = []
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 (
@ -316,42 +295,19 @@ 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') {
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
}
}
@ -359,33 +315,7 @@ export const preProcessPerLine = (html, greentext, handleLinks) => {
// 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 flattenDeep(result).join('')
}).reverse().join('')
return { newHtml, lastMentions }

View file

@ -41,16 +41,6 @@
{{ $t('settings.collapse_subject') }}
</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') }}

View file

@ -184,10 +184,7 @@ const Status = {
})
},
mentionsLine () {
return this.mentionsOwnLine ? this.mentions : this.alsoMentions
},
mentionsOwnLine () {
return this.mergedConfig.mentionsOwnLine
return this.alsoMentions
},
hasMentionsLine () {
return this.mentionsLine.length > 0

View file

@ -306,7 +306,6 @@
:no-heading="noHeading"
:highlight="highlight"
:focused="isFocused"
:hide-mentions="mentionsOwnLine && (isReply || true)"
@mediaplay="addMediaPlaying($event)"
@mediapause="removeMediaPlaying($event)"
@parseReady="setHeadTailLinks"

View file

@ -25,8 +25,7 @@ const StatusContent = {
'focused',
'noHeading',
'fullContent',
'singleLine',
'hideMentions'
'singleLine'
],
data () {
return {

View file

@ -45,7 +45,6 @@
:html="status.raw_html"
:emoji="status.emojis"
:handle-links="true"
:hide-mentions="hideMentions"
:greentext="mergedConfig.greentext"
:attentions="status.attentions"
@parseReady="onParseReady"

View file

@ -31,8 +31,7 @@ const StatusContent = {
'focused',
'noHeading',
'fullContent',
'singleLine',
'hideMentions'
'singleLine'
],
computed: {
hideAttachments () {

View file

@ -4,7 +4,6 @@
<StatusBody
:status="status"
:single-line="singleLine"
:hide-mentions="hideMentions"
@parseReady="$emit('parseReady', $event)"
>
<div v-if="status.poll && status.poll.options">

View file

@ -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

View file

@ -8,11 +8,13 @@ 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 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 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', () => {
@ -21,7 +23,6 @@ describe('RichContent', () => {
localVue,
propsData: {
attentions,
hideMentions: true,
handleLinks: true,
greentext: true,
emoji: [],
@ -45,7 +46,6 @@ describe('RichContent', () => {
localVue,
propsData: {
attentions,
hideMentions: true,
handleLinks: true,
greentext: true,
emoji: [],
@ -56,20 +56,15 @@ describe('RichContent', () => {
expect(wrapper.html()).to.eql(compwrap(expected))
})
it('removes mentions from the beginning of post', () => {
it('replaces first mention with mentionsline', () => {
const html = p(
makeMention('John'),
' how are you doing thoday?'
)
const expected = p(
removedMentionSpan,
'how are you doing thoday?'
' how are you doing today?'
)
const wrapper = shallowMount(RichContent, {
localVue,
propsData: {
attentions,
hideMentions: true,
handleLinks: true,
greentext: true,
emoji: [],
@ -77,70 +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 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: {
attentions,
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: {
attentions,
hideMentions: true,
handleLinks: true,
greentext: true,
emoji: [],
html
}
})
expect(wrapper.html()).to.eql(compwrap(expected))
})
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(
@ -167,189 +105,6 @@ describe('RichContent', () => {
localVue,
propsData: {
attentions,
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: {
attentions,
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: {
attentions,
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: {
attentions,
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: {
attentions,
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: {
attentions,
hideMentions: true,
handleLinks: true,
greentext: true,
emoji: [],
@ -378,7 +133,6 @@ describe('RichContent', () => {
localVue,
propsData: {
attentions,
hideMentions: true,
handleLinks: false,
greentext: true,
emoji: [],
@ -403,7 +157,6 @@ describe('RichContent', () => {
localVue,
propsData: {
attentions,
hideMentions: true,
handleLinks: false,
greentext: true,
emoji: [],
@ -424,7 +177,6 @@ describe('RichContent', () => {
localVue,
propsData: {
attentions,
hideMentions: true,
handleLinks: false,
greentext: false,
emoji: [],
@ -446,7 +198,6 @@ describe('RichContent', () => {
localVue,
propsData: {
attentions,
hideMentions: true,
handleLinks: false,
greentext: false,
emoji: [{ url: 'about:blank', shortcode: 'spurdo' }],
@ -464,7 +215,6 @@ describe('RichContent', () => {
localVue,
propsData: {
attentions,
hideMentions: true,
handleLinks: false,
greentext: false,
emoji: [],
@ -484,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')
@ -517,11 +267,7 @@ describe('RichContent', () => {
const expected = [
'Bruh',
'Bruh',
[
stubMention('foo'),
stubMention('bar'),
stubMention('baz')
].join(''),
mentionsLine(3)
'Bruh'
].join('<br>')
@ -529,119 +275,6 @@ describe('RichContent', () => {
localVue,
propsData: {
attentions,
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: {
attentions,
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: {
attentions,
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: {
attentions,
hideMentions: true,
handleLinks: true,
greentext: true,
emoji: [],
@ -678,7 +311,6 @@ describe('RichContent', () => {
localVue,
propsData: {
attentions,
hideMentions: true,
handleLinks: true,
greentext: true,
emoji: [],
@ -726,7 +358,6 @@ describe('RichContent', () => {
localVue,
propsData: {
attentions,
hideMentions: false,
handleLinks: true,
greentext: true,
emoji: [],
@ -781,7 +412,6 @@ describe('RichContent', () => {
},
propsData: {
attentions,
hideMentions: false,
handleLinks: true,
greentext: true,
emoji: [],
@ -826,7 +456,6 @@ describe('RichContent', () => {
localVue,
propsData: {
attentions,
hideMentions: false,
handleLinks: true,
greentext: true,
emoji: [],