logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git
commit: 50aa379038771da3725e6864c40007ae12896232
parent 04fa1f0b2d1a92b1c653cd55f51ee7e1434b2bd7
Author: Henry Jameson <me@hjkos.com>
Date:   Mon,  7 Jun 2021 18:41:55 +0300

new component - StatusText, to separate post's text from its attachments

Diffstat:

Msrc/components/status_content/status_content.js127++-----------------------------------------------------------------------------
Msrc/components/status_content/status_content.vue157++++++++++++++++++-------------------------------------------------------------
Asrc/components/status_text/status_text.js147+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/status_text/status_text.vue95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 280 insertions(+), 246 deletions(-)

diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js @@ -1,12 +1,9 @@ import Attachment from '../attachment/attachment.vue' import Poll from '../poll/poll.vue' import Gallery from '../gallery/gallery.vue' -import RichContent from 'src/components/rich_content/rich_content.jsx' +import StatusText from 'src/components/status_text/status_text.vue' import LinkPreview from '../link-preview/link-preview.vue' -import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import fileType from 'src/services/file_type/file_type.service' -import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js' -import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js' import { mapGetters, mapState } from 'vuex' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -36,52 +33,11 @@ const StatusContent = { 'fullContent', '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 - } - }, computed: { - localCollapseSubjectDefault () { - return this.mergedConfig.collapseMessageWithSubject - }, hideAttachments () { return (this.mergedConfig.hideAttachments && !this.inConversation) || (this.mergedConfig.hideAttachmentsInConv && this.inConversation) }, - // This is a bit hacky, but we want to approximate post height before rendering - // so we count newlines (masto uses <p> for paragraphs, GS uses <br> between them) - // as well as approximate line count by counting characters and approximating ~80 - // per line. - // - // Using max-height + overflow: auto for status components resulted in false positives - // very often with japanese characters, and it was very annoying. - tallStatus () { - const lengthScore = this.status.statusnet_html.split(/<p|<br/).length + this.status.text.length / 80 - return lengthScore > 20 - }, - longSubject () { - return this.status.summary.length > 240 - }, - // When a status has a subject and is also tall, we should only have one show more/less button. If the default is to collapse statuses with subjects, we just treat it like a status with a subject; otherwise, we just treat it like a tall status. - mightHideBecauseSubject () { - return !!this.status.summary && this.localCollapseSubjectDefault - }, - mightHideBecauseTall () { - return this.tallStatus && !(this.status.summary && this.localCollapseSubjectDefault) - }, - hideSubjectStatus () { - return this.mightHideBecauseSubject && !this.expandingSubject - }, - hideTallStatus () { - return this.mightHideBecauseTall && !this.showingTall - }, - showingMore () { - return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject) - }, nsfwClickthrough () { if (!this.status.nsfw) { return false @@ -119,45 +75,11 @@ const StatusContent = { file => !fileType.fileMatchesSomeType(this.galleryTypes, file) ) }, - attachmentTypes () { - return this.status.attachments.map(file => fileType.fileType(file.mimetype)) - }, maxThumbnails () { return this.mergedConfig.maxThumbnails }, - 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.err('Failed to process status html', e) - return html - } - } else { - return html - } - }, ...mapGetters(['mergedConfig']), ...mapState({ - betterShadow: state => state.interface.browserSupport.cssFilter, currentUser: state => state.users.currentUser }) }, @@ -166,54 +88,9 @@ const StatusContent = { Poll, Gallery, LinkPreview, - RichContent - }, - mounted () { - this.status.attentions.forEach(attn => { - const { id } = attn - this.$store.dispatch('fetchUserIfMissing', id) - }) + StatusText }, methods: { - linkClicked (event) { - const target = event.target.closest('.status-content a') - if (target) { - if (target.className.match(/mention/)) { - const href = target.href - const attn = this.status.attentions.find(attn => mentionMatchesUrl(attn, href)) - if (attn) { - event.stopPropagation() - event.preventDefault() - const link = this.generateUserProfileLink(attn.id, attn.screen_name) - this.$router.push(link) - return - } - } - 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 - } else if (this.mightHideBecauseSubject) { - this.expandingSubject = !this.expandingSubject - } - }, - generateUserProfileLink (id, name) { - return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames) - }, - generateTagLink (tag) { - return `/tag/${tag}` - }, setMedia () { const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments return () => this.$store.dispatch('setMedia', attachments) diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue @@ -1,131 +1,46 @@ <template> <div class="StatusContent"> <slot name="header" /> - <div - v-if="status.summary_html" - class="summary-wrapper" - :class="{ 'tall-subject': (longSubject && !showingLongSubject) }" - > - <RichContent - class="media-body summary" - :html="status.summary_raw_html" - :emoji="status.emojis" - @click.prevent="linkClicked" - /> - <button - v-if="longSubject && showingLongSubject" - class="button-unstyled -link tall-subject-hider" - @click.prevent="showingLongSubject=false" + <StatusText :status="status"> + <div v-if="status.poll && status.poll.options"> + <poll :base-poll="status.poll" /> + </div> + + <div + v-if="status.attachments.length !== 0" + class="attachments media-body" > - {{ $t("status.hide_full_subject") }} - </button> - <button - v-else-if="longSubject" - class="button-unstyled -link tall-subject-hider" - :class="{ 'tall-subject-hider_focused': focused }" - @click.prevent="showingLongSubject=true" - > - {{ $t("status.show_full_subject") }} - </button> - </div> - <div - :class="{'tall-status': hideTallStatus}" - class="status-content-wrapper" - > - <button - v-if="hideTallStatus" - class="button-unstyled -link tall-status-hider" - :class="{ 'tall-status-hider_focused': focused }" - @click.prevent="toggleShowMore" - > - {{ $t("general.show_more") }} - </button> - <RichContent - v-if="!hideSubjectStatus" - :class="{ 'single-line': singleLine }" - class="status-content media-body" - :html="postBodyHtml" - :emoji="status.emojis" - @click.prevent="linkClicked" - /> - <button - v-if="hideSubjectStatus" - class="button-unstyled -link cw-status-hider" - @click.prevent="toggleShowMore" - > - {{ $t("status.show_content") }} - <FAIcon - v-if="attachmentTypes.includes('image')" - icon="image" - /> - <FAIcon - v-if="attachmentTypes.includes('video')" - icon="video" - /> - <FAIcon - v-if="attachmentTypes.includes('audio')" - icon="music" + <attachment + v-for="attachment in nonGalleryAttachments" + :key="attachment.id" + class="non-gallery" + :size="attachmentSize" + :nsfw="nsfwClickthrough" + :attachment="attachment" + :allow-play="true" + :set-media="setMedia()" + @play="$emit('mediaplay', attachment.id)" + @pause="$emit('mediapause', attachment.id)" /> - <FAIcon - v-if="attachmentTypes.includes('unknown')" - icon="file" + <gallery + v-if="galleryAttachments.length > 0" + :nsfw="nsfwClickthrough" + :attachments="galleryAttachments" + :set-media="setMedia()" /> - <FAIcon - v-if="status.poll && status.poll.options" - icon="poll-h" - /> - <FAIcon - v-if="status.card" - icon="link" - /> - </button> - <button - v-if="showingMore && !fullContent" - class="button-unstyled -link status-unhider" - @click.prevent="toggleShowMore" + </div> + + <div + v-if="status.card && !noHeading" + class="link-preview media-body" > - {{ tallStatus ? $t("general.show_less") : $t("status.hide_content") }} - </button> - </div> - - <div v-if="status.poll && status.poll.options && !hideSubjectStatus"> - <poll :base-poll="status.poll" /> - </div> - - <div - v-if="status.attachments.length !== 0 && (!hideSubjectStatus || showingLongSubject)" - class="attachments media-body" - > - <attachment - v-for="attachment in nonGalleryAttachments" - :key="attachment.id" - class="non-gallery" - :size="attachmentSize" - :nsfw="nsfwClickthrough" - :attachment="attachment" - :allow-play="true" - :set-media="setMedia()" - @play="$emit('mediaplay', attachment.id)" - @pause="$emit('mediapause', attachment.id)" - /> - <gallery - v-if="galleryAttachments.length > 0" - :nsfw="nsfwClickthrough" - :attachments="galleryAttachments" - :set-media="setMedia()" - /> - </div> - - <div - v-if="status.card && !hideSubjectStatus && !noHeading" - class="link-preview media-body" - > - <link-preview - :card="status.card" - :size="attachmentSize" - :nsfw="nsfwClickthrough" - /> - </div> + <link-preview + :card="status.card" + :size="attachmentSize" + :nsfw="nsfwClickthrough" + /> + </div> + </StatusText> <slot name="footer" /> </div> </template> diff --git a/src/components/status_text/status_text.js b/src/components/status_text/status_text.js @@ -0,0 +1,147 @@ +import fileType from 'src/services/file_type/file_type.service' +import RichContent from 'src/components/rich_content/rich_content.jsx' +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 { + faFile, + faMusic, + faImage, + faLink, + faPollH +} from '@fortawesome/free-solid-svg-icons' + +library.add( + faFile, + faMusic, + faImage, + faLink, + faPollH +) + +const StatusContent = { + name: 'StatusContent', + props: [ + 'status', + 'focused', + 'noHeading', + 'fullContent', + '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 + } + }, + computed: { + localCollapseSubjectDefault () { + return this.mergedConfig.collapseMessageWithSubject + }, + // This is a bit hacky, but we want to approximate post height before rendering + // so we count newlines (masto uses <p> for paragraphs, GS uses <br> between them) + // as well as approximate line count by counting characters and approximating ~80 + // per line. + // + // Using max-height + overflow: auto for status components resulted in false positives + // very often with japanese characters, and it was very annoying. + tallStatus () { + const lengthScore = this.status.statusnet_html.split(/<p|<br/).length + this.status.text.length / 80 + return lengthScore > 20 + }, + longSubject () { + return this.status.summary.length > 240 + }, + // When a status has a subject and is also tall, we should only have one show more/less button. If the default is to collapse statuses with subjects, we just treat it like a status with a subject; otherwise, we just treat it like a tall status. + mightHideBecauseSubject () { + return !!this.status.summary && this.localCollapseSubjectDefault + }, + mightHideBecauseTall () { + return this.tallStatus && !(this.status.summary && this.localCollapseSubjectDefault) + }, + hideSubjectStatus () { + return this.mightHideBecauseSubject && !this.expandingSubject + }, + hideTallStatus () { + return this.mightHideBecauseTall && !this.showingTall + }, + 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.err('Failed to process status html', e) + return html + } + } else { + return html + } + }, + attachmentTypes () { + return this.status.attachments.map(file => fileType.fileType(file.mimetype)) + }, + ...mapGetters(['mergedConfig']), + }, + components: { + RichContent + }, + mounted () { + this.status.attentions.forEach(attn => { + const { id } = attn + this.$store.dispatch('fetchUserIfMissing', id) + }) + }, + 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 + } else if (this.mightHideBecauseSubject) { + this.expandingSubject = !this.expandingSubject + } + }, + generateTagLink (tag) { + return `/tag/${tag}` + } + } +} + +export default StatusContent diff --git a/src/components/status_text/status_text.vue b/src/components/status_text/status_text.vue @@ -0,0 +1,95 @@ +<template> + <div class="StatusText"> + <div + v-if="status.summary_html" + class="summary-wrapper" + :class="{ 'tall-subject': (longSubject && !showingLongSubject) }" + > + <RichContent + class="media-body summary" + :html="status.summary_raw_html" + :emoji="status.emojis" + @click.prevent="linkClicked" + /> + <button + v-if="longSubject && showingLongSubject" + class="button-unstyled -link tall-subject-hider" + @click.prevent="showingLongSubject=false" + > + {{ $t("status.hide_full_subject") }} + </button> + <button + v-else-if="longSubject" + class="button-unstyled -link tall-subject-hider" + :class="{ 'tall-subject-hider_focused': focused }" + @click.prevent="showingLongSubject=true" + > + {{ $t("status.show_full_subject") }} + </button> + </div> + <div + :class="{'tall-status': hideTallStatus}" + class="status-content-wrapper" + > + <button + v-if="hideTallStatus" + class="button-unstyled -link tall-status-hider" + :class="{ 'tall-status-hider_focused': focused }" + @click.prevent="toggleShowMore" + > + {{ $t("general.show_more") }} + </button> + <RichContent + v-if="!hideSubjectStatus" + :class="{ 'single-line': singleLine }" + class="status-content media-body" + :html="postBodyHtml" + :emoji="status.emojis" + :handleLinks="true" + @click.prevent="linkClicked" + /> + <button + v-if="hideSubjectStatus" + class="button-unstyled -link cw-status-hider" + @click.prevent="toggleShowMore" + > + {{ $t("status.show_content") }} + <FAIcon + v-if="attachmentTypes.includes('image')" + icon="image" + /> + <FAIcon + v-if="attachmentTypes.includes('video')" + icon="video" + /> + <FAIcon + v-if="attachmentTypes.includes('audio')" + icon="music" + /> + <FAIcon + v-if="attachmentTypes.includes('unknown')" + icon="file" + /> + <FAIcon + v-if="status.poll && status.poll.options" + icon="poll-h" + /> + <FAIcon + v-if="status.card" + icon="link" + /> + </button> + <button + v-if="showingMore && !fullContent" + class="button-unstyled -link status-unhider" + @click.prevent="toggleShowMore" + > + {{ tallStatus ? $t("general.show_less") : $t("status.hide_content") }} + </button> + </div> + <div v-if="!hideSubjectStatus"> + <slot/> + </div> + </div> +</template> +<script src="./status_text.js" ></script>