logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git
commit: aec867b30036fd039113e4197ca98566447efec6
parent 566964992a394f1d0462557f70e284f2493d82bf
Author: Henry Jameson <me@hjkos.com>
Date:   Thu, 10 Jun 2021 12:08:31 +0300

Moved greentext to RichContent, improved how first mentions are
restored, now shows mentions not uh, mention in post body

Diffstat:

Msrc/components/mention_link/mention_link.js1+
Msrc/components/mentions_line/mentions_line.js8++++----
Msrc/components/mentions_line/mentions_line.vue14+++++++-------
Msrc/components/rich_content/rich_content.jsx112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/components/status/status.js25+++++++++++++++++++++----
Msrc/components/status/status.vue4++--
Msrc/components/status_body/status_body.js60++++++++++--------------------------------------------------
Msrc/components/status_body/status_body.vue20+++++++++++---------
8 files changed, 166 insertions(+), 78 deletions(-)

diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js @@ -6,6 +6,7 @@ const MentionLink = { name: 'MentionLink', props: { url: { + required: true, type: String }, content: { diff --git a/src/components/mentions_line/mentions_line.js b/src/components/mentions_line/mentions_line.js @@ -4,7 +4,7 @@ import { mapGetters } from 'vuex' const MentionsLine = { name: 'MentionsLine', props: { - attentions: { + mentions: { required: true, type: Array } @@ -20,11 +20,11 @@ const MentionsLine = { limit () { return 6 }, - mentions () { - return this.attentions.slice(0, this.limit) + mentionsComputed () { + return this.mentions.slice(0, this.limit) }, extraMentions () { - return this.attentions.slice(this.limit) + return this.mentions.slice(this.limit) }, manyMentions () { return this.extraMentions.length > 0 diff --git a/src/components/mentions_line/mentions_line.vue b/src/components/mentions_line/mentions_line.vue @@ -1,11 +1,11 @@ <template> <span class="MentionsLine"> <MentionLink - v-for="mention in mentions" - :key="mention.statusnet_profile_url" + v-for="mention in mentionsComputed" + :key="mention.index" class="mention-link" - :content="mention.statusnet_profile_url" - :url="mention.statusnet_profile_url" + :content="mention.content" + :url="mention.url" :first-mention="false" /><span v-if="manyMentions" @@ -17,10 +17,10 @@ > <MentionLink v-for="mention in extraMentions" - :key="mention.statusnet_profile_url" + :key="mention.index" class="mention-link" - :content="mention.statusnet_profile_url" - :url="mention.statusnet_profile_url" + :content="mention.content" + :url="mention.url" :first-mention="false" /> </span><button diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx @@ -1,6 +1,7 @@ import Vue from 'vue' import { unescape, flattenDeep } from 'lodash' import { convertHtml, getTagName, processTextForEmoji, getAttrs } from 'src/services/mini_html_converter/mini_html_converter.service.js' +import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js' import StillImage from 'src/components/still-image/still-image.vue' import MentionLink from 'src/components/mention_link/mention_link.vue' @@ -24,15 +25,25 @@ export default Vue.component('RichContent', { required: false, type: Boolean, default: false + }, + // Meme arrows + greentext: { + required: false, + type: Boolean, + default: false } }, render (h) { + // Pre-process HTML + const html = this.greentext ? addGreentext(this.html) : this.html + const renderImage = (tag) => { return <StillImage {...{ attrs: getAttrs(tag) }} class="img" /> } + const renderMention = (attrs, children, encounteredText) => { return <MentionLink url={attrs.href} @@ -41,10 +52,12 @@ export default Vue.component('RichContent', { /> } + // We stop treating mentions as "first" ones when we encounter + // non-whitespace text let encounteredText = false // Processor to use with mini_html_converter const processItem = (item) => { - // Handle text noes - just add emoji + // Handle text nodes - just add emoji if (typeof item === 'string') { const emptyText = item.trim() === '' if (emptyText) { @@ -72,6 +85,7 @@ export default Vue.component('RichContent', { return unescapedItem } } + // Handle tag nodes if (Array.isArray(item)) { const [opener, children] = item @@ -84,8 +98,14 @@ export default Vue.component('RichContent', { const attrs = getAttrs(opener) if (attrs['class'] && attrs['class'].includes('mention')) { return renderMention(attrs, children, encounteredText) + } else { + attrs.target = '_blank' + return <a {...{ attrs }}> + { children.map(processItem) } + </a> } } + // Render tag as is if (children !== undefined) { return <Tag {...{ attrs: getAttrs(opener) }}> @@ -97,7 +117,95 @@ export default Vue.component('RichContent', { } } return <span class="RichContent"> - { convertHtml(this.html).map(processItem) } + { this.$slots.prefix } + { convertHtml(html).map(processItem) } + { this.$slots.suffix } </span> } }) + +export const addGreentext = (html) => { + try { + if (html.includes('&gt;')) { + // This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works + return processHtml(html, (string) => { + if ( + string.includes('&gt;') && string + .replace(/<[^>]+?>/gi, '') // remove all tags + .replace(/@\w+/gi, '') // remove mentions (even failed ones) + .trim() + .startsWith('&gt;') + ) { + return `<span class='greentext'>${string}</span>` + } else { + return string + } + }) + } else { + return html + } + } catch (e) { + console.error('Failed to process status html', e) + return html + } +} + +export const getHeadTailLinks = (html) => { + // Exported object properties + const firstMentions = [] // Mentions that appear in the beginning of post body + const lastTags = [] // Tags that appear at the end of post body + const writtenMentions = [] // All mentions that appear in post body + const writtenTags = [] // All tags that appear in post body + + let encounteredText = false + let processingFirstMentions = true + let index = 0 // unique index for vue "tag" property + + const getLinkData = (attrs, children, index) => { + return { + index, + url: attrs.href, + hashtag: attrs['data-tag'], + content: flattenDeep(children).join('') + } + } + + // Processor to use with mini_html_converter + const processItem = (item) => { + // Handle text nodes - stop treating mentions as "first" when text encountered + if (typeof item === 'string') { + const emptyText = item.trim() === '' + if (emptyText) return + if (!encounteredText) { + encounteredText = true + processingFirstMentions = false + } + // Encountered text? That means tags we've been collectings aren't "last"! + lastTags.splice(0) + return + } + // Handle tag nodes + if (Array.isArray(item)) { + const [opener, children] = item + const Tag = getTagName(opener) + if (Tag !== 'a') return children && children.forEach(processItem) + const attrs = getAttrs(opener) + if (attrs['class']) { + const linkData = getLinkData(attrs, children, index++) + if (attrs['class'].includes('mention')) { + if (processingFirstMentions) { + firstMentions.push(linkData) + } + writtenMentions.push(linkData) + } else if (attrs['class'].includes('hashtag')) { + lastTags.push(linkData) + writtenTags.push(linkData) + } + return // Stop processing, we don't care about link's contents + } + children && children.forEach(processItem) + } + } + convertHtml(html).forEach(processItem) + return { firstMentions, writtenMentions, writtenTags, lastTags } +} diff --git a/src/components/status/status.js b/src/components/status/status.js @@ -19,6 +19,7 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_p import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { muteWordHits } from '../../services/status_parser/status_parser.js' import { unescape, uniqBy } from 'lodash' +import { getHeadTailLinks } from 'src/components/rich_content/rich_content.jsx' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -166,17 +167,33 @@ const Status = { muteWordHits () { return muteWordHits(this.status, this.muteWords) }, - mentionsOwnLine () { - return this.mergedConfig.mentionsOwnLine + headTailLinks () { + return getHeadTailLinks(this.status.raw_html) }, mentions () { return this.status.attentions.filter(attn => { return attn.screen_name !== this.replyToName && attn.screen_name !== this.status.user.screen_name + }).map(attn => ({ + url: attn.statusnet_profile_url, + content: attn.screen_name, + userId: attn.id + })) + }, + alsoMentions () { + const set = new Set(this.headTailLinks.writtenMentions.map(m => m.url)) + return this.headTailLinks.writtenMentions.filter(mention => { + return !set.has(mention.url) }) }, - hasMentions () { - return this.mentions.length > 0 + mentionsLine () { + return this.mentionsOwnLine ? this.mentions : this.alsoMentions + }, + mentionsOwnLine () { + return this.mergedConfig.mentionsOwnLine + }, + hasMentionsLine () { + return this.mentionsLine.length > 0 }, muted () { if (this.statusoid.user.id === this.currentUser.id) return false diff --git a/src/components/status/status.vue b/src/components/status/status.vue @@ -294,7 +294,7 @@ </div> <div - v-if="hasMentions && mentionsOwnLine" + v-if="hasMentionsLine" class="heading-mentions-row" > <div @@ -316,7 +316,7 @@ </span> </span> <MentionsLine - :attentions="mentions" + :mentions="mentionsLine" class="mentions-line" /> </div> diff --git a/src/components/status_body/status_body.js b/src/components/status_body/status_body.js @@ -1,8 +1,6 @@ import fileType from 'src/services/file_type/file_type.service' -import RichContent from 'src/components/rich_content/rich_content.jsx' +import RichContent, { getHeadTailLinks } from 'src/components/rich_content/rich_content.jsx' import MentionsLine from 'src/components/mentions_line/mentions_line.vue' -import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js' -import { extractTagFromUrl } from 'src/services/matcher/matcher.service.js' import { mapGetters } from 'vuex' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -28,7 +26,10 @@ const StatusContent = { 'focused', 'noHeading', 'fullContent', - 'singleLine' + 'singleLine', + // if this was computed at upper level it can be passed here, otherwise + // it will be in this component + 'headTailLinks' ], data () { return { @@ -72,44 +73,18 @@ const StatusContent = { showingMore () { return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject) }, - postBodyHtml () { - const html = this.status.raw_html - - if (this.mergedConfig.greentext) { - try { - if (html.includes('&gt;')) { - // This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works - return processHtml(html, (string) => { - if (string.includes('&gt;') && - string - .replace(/<[^>]+?>/gi, '') // remove all tags - .replace(/@\w+/gi, '') // remove mentions (even failed ones) - .trim() - .startsWith('&gt;')) { - return `<span class='greentext'>${string}</span>` - } else { - return string - } - }) - } else { - return html - } - } catch (e) { - console.error('Failed to process status html', e) - return html - } - } else { - return html - } - }, attachmentTypes () { return this.status.attachments.map(file => fileType.fileType(file.mimetype)) }, mentionsOwnLine () { return this.mergedConfig.mentionsOwnLine }, + headTailLinksComputed () { + if (this.headTailLinks) return this.headTailLinks + return getHeadTailLinks(this.status.raw_html) + }, mentions () { - return this.status.attentions + return this.headTailLinksComputed.firstMentions }, ...mapGetters(['mergedConfig']) }, @@ -124,21 +99,6 @@ const StatusContent = { }) }, methods: { - linkClicked (event) { - const target = event.target.closest('.status-content a') - if (target) { - if (target.rel.match(/(?:^|\s)tag(?:$|\s)/) || target.className.match(/hashtag/)) { - // Extract tag name from dataset or link url - const tag = target.dataset.tag || extractTagFromUrl(target.href) - if (tag) { - const link = this.generateTagLink(tag) - this.$router.push(link) - return - } - } - window.open(target.href, '_blank') - } - }, toggleShowMore () { if (this.mightHideBecauseTall) { this.showingTall = !this.showingTall diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue @@ -10,7 +10,6 @@ class="media-body summary" :html="status.summary_raw_html" :emoji="status.emojis" - @click.prevent="linkClicked" /> <button v-if="longSubject && showingLongSubject" @@ -43,19 +42,22 @@ class="text-wrapper" v-if="!hideSubjectStatus && !(singleLine && status.summary_html)" > - <MentionsLine - v-if="!mentionsOwnLine" - :attentions="status.attentions" - class="mentions-line" - /> <RichContent :class="{ '-single-line': singleLine }" class="text media-body" - :html="postBodyHtml" + :html="status.raw_html" :emoji="status.emojis" :handle-links="true" - @click.prevent="linkClicked" - /> + :greentext="mergedConfig.greentext" + > + <template v-slot:prefix> + <MentionsLine + v-if="!mentionsOwnLine" + :mentions="mentions" + class="mentions-line" + /> + </template> + </RichContent> </span> <button