2020-04-27 09:53:04 +00:00
import Attachment from '../attachment/attachment.vue'
import Poll from '../poll/poll.vue'
import Gallery from '../gallery/gallery.vue'
2021-06-07 00:14:48 +00:00
import RichContent from 'src/components/rich_content/rich_content.jsx'
2020-04-27 09:53:04 +00:00
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'
2020-10-20 21:01:28 +00:00
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faCircleNotch ,
faFile ,
faMusic ,
faImage ,
faLink ,
faPollH
} from '@fortawesome/free-solid-svg-icons'
library . add (
faCircleNotch ,
faFile ,
faMusic ,
faImage ,
faLink ,
faPollH
)
2020-04-27 09:53:04 +00:00
const StatusContent = {
name : 'StatusContent' ,
props : [
'status' ,
'focused' ,
'noHeading' ,
2020-07-07 20:33:08 +00:00
'fullContent' ,
'singleLine'
2020-04-27 09:53:04 +00:00
] ,
data ( ) {
return {
2020-05-07 13:10:53 +00:00
showingTall : this . fullContent || ( this . inConversation && this . focused ) ,
2020-04-27 09:53:04 +00:00
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 ( ) {
2020-06-26 11:07:39 +00:00
return this . status . summary . length > 240
2020-04-27 09:53:04 +00:00
} ,
// 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 ( ) {
2020-06-26 15:20:32 +00:00
return ! ! this . status . summary && this . localCollapseSubjectDefault
2020-04-27 09:53:04 +00:00
} ,
mightHideBecauseTall ( ) {
2020-06-26 15:20:32 +00:00
return this . tallStatus && ! ( this . status . summary && this . localCollapseSubjectDefault )
2020-04-27 09:53:04 +00:00
} ,
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
}
if ( this . status . summary && this . localCollapseSubjectDefault ) {
return false
}
return true
} ,
attachmentSize ( ) {
if ( ( this . mergedConfig . hideAttachments && ! this . inConversation ) ||
( this . mergedConfig . hideAttachmentsInConv && this . inConversation ) ||
( this . status . attachments . length > this . maxThumbnails ) ) {
return 'hide'
} else if ( this . compact ) {
return 'small'
}
return 'normal'
} ,
galleryTypes ( ) {
if ( this . attachmentSize === 'hide' ) {
return [ ]
}
return this . mergedConfig . playVideosInModal
? [ 'image' , 'video' ]
: [ 'image' ]
} ,
galleryAttachments ( ) {
return this . status . attachments . filter (
file => fileType . fileMatchesSomeType ( this . galleryTypes , file )
)
} ,
nonGalleryAttachments ( ) {
return this . status . attachments . filter (
file => ! fileType . fileMatchesSomeType ( this . galleryTypes , file )
)
} ,
2020-07-06 11:01:03 +00:00
attachmentTypes ( ) {
return this . status . attachments . map ( file => fileType . fileType ( file . mimetype ) )
2020-04-27 09:53:04 +00:00
} ,
maxThumbnails ( ) {
return this . mergedConfig . maxThumbnails
} ,
postBodyHtml ( ) {
2021-06-07 00:14:48 +00:00
const html = this . status . raw _html
2020-04-27 09:53:04 +00:00
if ( this . mergedConfig . greentext ) {
try {
if ( html . includes ( '>' ) ) {
// This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works
return processHtml ( html , ( string ) => {
if ( string . includes ( '>' ) &&
string
. replace ( /<[^>]+?>/gi , '' ) // remove all tags
. replace ( /@\w+/gi , '' ) // remove mentions (even failed ones)
. trim ( )
. startsWith ( '>' ) ) {
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
} )
} ,
components : {
Attachment ,
Poll ,
Gallery ,
2021-06-07 00:14:48 +00:00
LinkPreview ,
RichContent
2020-04-27 09:53:04 +00:00
} ,
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/ ) ) {
2020-06-04 13:50:44 +00:00
// Extract tag name from dataset or link url
const tag = target . dataset . tag || extractTagFromUrl ( target . href )
2020-04-27 09:53:04 +00:00
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 )
}
}
}
export default StatusContent