2019-08-23 04:33:04 +00:00
< template >
< div >
< input type = "file" name = "media" class = "d-none file-input" multiple = "" v -bind :accept ="config.uploader.media_types" >
< div class = "timeline" >
< div class = "card status-card card-md-rounded-0" >
< div class = "card-header d-inline-flex align-items-center bg-white" >
< img v -bind :src ="profile.avatar" width = "32px" height = "32px" style = "border-radius: 32px;" class = "box-shadow" >
< a class = "username font-weight-bold pl-2 text-dark" v -bind :href ="profile.url" >
{ { profile . username } }
< / a >
< div class = "text-right" style = "flex-grow:1;" >
< div class = "dropdown" >
< button class = "btn btn-link text-dark no-caret dropdown-toggle" type = "button" data -toggle = " dropdown " aria -haspopup = " true " aria -expanded = " false " title = "Post options" >
< span class = "fas fa-ellipsis-v fa-lg text-muted" > < / span >
< / button >
< div class = "dropdown-menu dropdown-menu-right" aria -labelledby = " dropdownMenuButton " >
< div class = "dropdown-item small font-weight-bold" v -on :click ="createCollection" > Create Collection < / div >
< div class = "dropdown-divider" > < / div >
< div class = "dropdown-item small font-weight-bold" v -on :click ="about" > About < / div >
< div class = "dropdown-item small font-weight-bold" v -on :click ="closeModal" > Close < / div >
< / div >
< / div >
< / div >
< / div >
< div class = "postPresenterContainer" >
< div v-if ="uploading" >
< div class = "w-100 h-100 bg-light py-5" style = "border-bottom: 1px solid #f1f1f1" >
< div class = "p-5" >
< b -progress :value ="uploadProgress" :max ="100" striped :animated ="true" > < / b - p r o g r e s s >
< p class = "text-center mb-0 font-weight-bold" > Uploading ... ( { { uploadProgress } } % ) < / p >
< / div >
< / div >
< / div >
< div v-else >
< div v-if ="ids.length > 0 && ids.length != config.uploader.album_limit" class="card-header py-2 bg-primary m-2 rounded cursor-pointer" v-on:click="addMedia($event)" >
< p class = "text-center mb-0 font-weight-bold text-white" > < i class = "fas fa-plus mr-1" > < / i > Add Photo < / p >
< / div >
< div v-if ="ids.length == 0" class="w-100 h-100 bg-light py-5 cursor-pointer" style="border-bottom: 1px solid #f1f1f1" v-on:click="addMedia($event)" >
< div class = "p-5" >
< p class = "text-center font-weight-bold" > { { composeMessage ( ) } } < / p >
< p class = "text-muted mb-0 small text-center" > Accepted Formats : < b > { { acceptedFormats ( ) } } < / b > < / p >
< p class = "text-muted mb-0 small text-center" > Max File Size : < b > { { maxSize ( ) } } < / b > < / p >
< p class = "text-muted mb-0 small text-center" > Albums can contain up to < b > { { config . uploader . album _limit } } < / b > photos or videos < / p >
< / div >
< / div >
< div v-if ="ids.length > 0" >
< b -carousel id = "p-carousel"
style = "text-shadow: 1px 1px 2px #333;"
controls
indicators
background = "#ffffff"
: interval = "0"
v - model = "carouselCursor"
>
< b -carousel -slide v-if ="ids.length > 0" v-for="(preview, index) in media" :key="'preview_media_'+index" >
< div slot = "img" :class ="[media[index].filter_class?media[index].filter_class:'']" style = "display:flex;min-height: 320px;align-items: center;" >
< img class = "d-block img-fluid w-100" :src ="preview.url" :alt ="preview.description" :title ="preview.description" >
< / div >
< / b - c a r o u s e l - s l i d e >
< / b - c a r o u s e l >
< / div >
< div v-if ="ids.length > 0 && media[carouselCursor].type == 'Image'" class="bg-dark align-items-center" >
< ul class = "nav media-drawer-filters text-center" >
< li class = "nav-item" >
< div class = "p-1 pt-3" >
< img :src ="media[carouselCursor].url" width = "100px" height = "60px" v -on : click.prevent = " toggleFilter ( $ event , null ) " class = "cursor-pointer" >
< / div >
< a : class = "[media[carouselCursor].filter_class == null ? 'nav-link text-white active' : 'nav-link text-muted']" href = "#" v -on : click.prevent = " toggleFilter ( $ event , null ) " > No Filter < / a >
< / li >
< li class = "nav-item" v-for ="(filter, index) in filters" >
< div class = "p-1 pt-3" >
< img :src ="media[carouselCursor].url" width = "100px" height = "60px" :class ="filter[1]" v -on : click.prevent = " toggleFilter ( $ event , filter [ 1 ] ) " >
< / div >
< a : class = "[media[carouselCursor].filter_class == filter[1] ? 'nav-link text-white active' : 'nav-link text-muted']" href = "#" v -on : click.prevent = " toggleFilter ( $ event , filter [ 1 ] ) " > { { filter [ 0 ] } } < / a >
< / li >
< / ul >
< / div >
< / div >
< div v-if ="ids.length > 0 && ['Image', 'Video'].indexOf(media[carouselCursor].type) != -1" class="bg-lighter p-2 row" >
< div v-if ="media[carouselCursor].type == 'Image'" class="col-12" >
< div class = "form-group" >
< input type = "text" class = "form-control" v-model ="media[carouselCursor].alt" placeholder="Optional image description" >
< / div >
< div class = "form-group" >
< input type = "text" class = "form-control" v-model ="media[carouselCursor].license" placeholder="Optional media license" >
< / div >
< / div >
<!-- < div class = "col-6 pt-2" >
< button class = "btn btn-outline-secondary btn-sm mr-1" > < i class = "fas fa-map-marker-alt" > < / i > < / button >
< button class = "btn btn-outline-secondary btn-sm" > < i class = "fas fa-tools" > < / i > < / button >
< / div > -- >
< div class = "col-12 text-right pt-2" >
< button class = "btn btn-outline-danger btn-sm font-weight-bold mr-1" v -on :click ="deleteMedia()" > Delete Media < / button >
< / div >
< / div >
< / div >
< div class = "card-body p-0 border-top" >
< div class = "caption" >
< textarea class = "form-control mb-0 border-0 rounded-0" rows = "3" placeholder = "Add an optional caption" v-model ="composeText" > < / textarea >
< / div >
< / div >
< div class = "card-footer" >
< div class = "d-flex justify-content-between align-items-center" >
< div >
< div class = "custom-control custom-switch d-inline mr-3" >
< input type = "checkbox" class = "custom-control-input" id = "nsfwToggle" v-model ="nsfw" >
< label class = "custom-control-label small font-weight-bold text-muted pt-1" for = "nsfwToggle" > NSFW < / label >
< / div >
< div class = "dropdown d-inline" >
< button class = "btn btn-outline-secondary btn-sm py-0 dropdown-toggle" type = "button" id = "visibility" data -toggle = " dropdown " aria -haspopup = " true " aria -expanded = " false " >
{ { visibility [ 0 ] . toUpperCase ( ) + visibility . slice ( 1 ) } }
< / button >
< div class = "dropdown-menu" aria -labelledby = " visibility " style = "width: 200px;" >
< a : class = "[visibility=='public'?'dropdown-item active':'dropdown-item']" href = "#" data -id = " public " data -title = " Public " v -on : click.prevent = " visibility = 'public' " >
< div class = "row" >
< div class = "d-none d-block-sm col-sm-2 px-0 text-center" >
< i class = "fas fa-globe" > < / i >
< / div >
< div class = "col-12 col-sm-10 pl-2" >
< p class = "font-weight-bold mb-0" > Public < / p >
< p class = "small mb-0" > Anyone can see < / p >
< / div >
< / div >
< / a >
< a : class = "[visibility=='private'?'dropdown-item active':'dropdown-item']" href = "#" data -id = " private " data -title = " Followers Only " v -on : click.prevent = " visibility = 'private' " >
< div class = "row" >
< div class = "d-none d-block-sm col-sm-2 px-0 text-center" >
< i class = "fas fa-lock" > < / i >
< / div >
< div class = "col-12 col-sm-10 pl-2" >
< p class = "font-weight-bold mb-0" > Followers Only < / p >
< p class = "small mb-0" > Only followers can see < / p >
< / div >
< / div >
< / a >
< a : class = "[visibility=='unlisted'?'dropdown-item active':'dropdown-item']" href = "#" data -id = " private " data -title = " Unlisted " v -on : click.prevent = " visibility = 'unlisted' " >
< div class = "row" >
< div class = "d-none d-block-sm col-sm-2 px-0 text-center" >
< i class = "fas fa-lock" > < / i >
< / div >
< div class = "col-12 col-sm-10 pl-2" >
< p class = "font-weight-bold mb-0" > Unlisted < / p >
< p class = "small mb-0" > Not listed on public timelines < / p >
< / div >
< / div >
< / a >
<!-- < a class = "dropdown-item" href = "#" data -id = " circle " data -title = " Circle " >
< div class = "row" >
< div class = "col-12 col-sm-2 px-0 text-center" >
< i class = "far fa-circle" > < / i >
< / div >
< div class = "col-12 col-sm-10 pl-2" >
< p class = "font-weight-bold mb-0" > Circle < / p >
< p class = "small mb-0" > Select a circle < / p >
< / div >
< / div >
< / a >
< a class = "dropdown-item" href = "#" data -id = " direct " data -title = " Direct Message " >
< div class = "row" >
< div class = "col-12 col-sm-2 px-0 text-center" >
< i class = "fas fa-envelope" > < / i >
< / div >
< div class = "col-12 col-sm-10 pl-2" >
< p class = "font-weight-bold mb-0" > Direct Message < / p >
< p class = "small mb-0" > Recipients only < / p >
< / div >
< / div >
< / a > -- >
< / div >
< / div >
< / div >
< div class = "small text-muted font-weight-bold" >
{ { composeText . length } } / { { config . uploader . max _caption _length } }
< / div >
< div class = "pl-md-5" >
<!-- < div class = "btn-group" >
< button type = "button" class = "btn btn-primary btn-sm font-weight-bold" v -on :click ="compose()" > { { composeState [ 0 ] . toUpperCase ( ) + composeState . slice ( 1 ) } } < / button >
< button type = "button" class = "btn btn-primary btn-sm dropdown-toggle dropdown-toggle-split" data -toggle = " dropdown " aria -haspopup = " true " aria -expanded = " false " >
< span class = "sr-only" > Toggle Dropdown < / span >
< / button >
< div class = "dropdown-menu dropdown-menu-right" >
< a : class = "[composeState == 'publish' ?'dropdown-item font-weight-bold active':'dropdown-item font-weight-bold ']" href = "#" v -on : click.prevent = " composeState = 'publish' " > Publish now < / a >
< ! - - < a : class = "[composeState == 'draft' ?'dropdown-item font-weight-bold active':'dropdown-item font-weight-bold ']" href = "#" v -on : click.prevent = " composeState = 'draft' " > Save as draft < / a >
< a : class = "[composeState == 'schedule' ?'dropdown-item font-weight-bold active':'dropdown-item font-weight-bold ']" href = "#" v -on : click.prevent = " composeState = 'schedule' " > Schedule for later < / a >
< div class = "dropdown-divider" > < / div >
< a : class = "[composeState == 'delete' ?'dropdown-item font-weight-bold active':'dropdown-item font-weight-bold ']" href = "#" v -on : click.prevent = " composeState = 'delete' " > Delete < / a > - - >
< / div >
< / div > -- >
< button class = "btn btn-primary btn-sm font-weight-bold px-3" v -on :click ="compose()" > Publish < / button >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / template >
< style type = "text/css" scoped >
. media - drawer - filters {
overflow - x : scroll ;
flex - wrap : unset ;
}
. media - drawer - filters . nav - link {
min - width : 100 px ;
padding - top : 1 rem ;
padding - bottom : 1 rem ;
}
. media - drawer - filters . active {
color : # fff ;
font - weight : bold ;
}
@ media ( hover : none ) and ( pointer : coarse ) {
. media - drawer - filters : : - webkit - scrollbar {
display : none ;
}
}
< / style >
< script type = "text/javascript" >
export default {
data ( ) {
return {
config : window . App . config ,
profile : { } ,
composeText : '' ,
composeTextLength : 0 ,
nsfw : false ,
filters : [ ] ,
ids : [ ] ,
media : [ ] ,
carouselCursor : 0 ,
visibility : 'public' ,
mediaDrawer : false ,
composeState : 'publish' ,
uploading : false ,
uploadProgress : 0 ,
composeType : false
}
} ,
beforeMount ( ) {
this . fetchProfile ( ) ;
} ,
mounted ( ) {
this . mediaWatcher ( ) ;
this . filters = [
[ '1977' , 'filter-1977' ] ,
[ 'Aden' , 'filter-aden' ] ,
[ 'Amaro' , 'filter-amaro' ] ,
[ 'Ashby' , 'filter-ashby' ] ,
[ 'Brannan' , 'filter-brannan' ] ,
[ 'Brooklyn' , 'filter-brooklyn' ] ,
[ 'Charmes' , 'filter-charmes' ] ,
[ 'Clarendon' , 'filter-clarendon' ] ,
[ 'Crema' , 'filter-crema' ] ,
[ 'Dogpatch' , 'filter-dogpatch' ] ,
[ 'Earlybird' , 'filter-earlybird' ] ,
[ 'Gingham' , 'filter-gingham' ] ,
[ 'Ginza' , 'filter-ginza' ] ,
[ 'Hefe' , 'filter-hefe' ] ,
[ 'Helena' , 'filter-helena' ] ,
[ 'Hudson' , 'filter-hudson' ] ,
[ 'Inkwell' , 'filter-inkwell' ] ,
[ 'Kelvin' , 'filter-kelvin' ] ,
[ 'Kuno' , 'filter-juno' ] ,
[ 'Lark' , 'filter-lark' ] ,
[ 'Lo-Fi' , 'filter-lofi' ] ,
[ 'Ludwig' , 'filter-ludwig' ] ,
[ 'Maven' , 'filter-maven' ] ,
[ 'Mayfair' , 'filter-mayfair' ] ,
[ 'Moon' , 'filter-moon' ] ,
[ 'Nashville' , 'filter-nashville' ] ,
[ 'Perpetua' , 'filter-perpetua' ] ,
[ 'Poprocket' , 'filter-poprocket' ] ,
[ 'Reyes' , 'filter-reyes' ] ,
[ 'Rise' , 'filter-rise' ] ,
[ 'Sierra' , 'filter-sierra' ] ,
[ 'Skyline' , 'filter-skyline' ] ,
[ 'Slumber' , 'filter-slumber' ] ,
[ 'Stinson' , 'filter-stinson' ] ,
[ 'Sutro' , 'filter-sutro' ] ,
[ 'Toaster' , 'filter-toaster' ] ,
[ 'Valencia' , 'filter-valencia' ] ,
[ 'Vesper' , 'filter-vesper' ] ,
[ 'Walden' , 'filter-walden' ] ,
[ 'Willow' , 'filter-willow' ] ,
[ 'X-Pro II' , 'filter-xpro-ii' ]
] ;
} ,
methods : {
fetchProfile ( ) {
2019-09-16 00:06:04 +00:00
axios . get ( '/api/pixelfed/v1/accounts/verify_credentials' ) . then ( res => {
2019-08-23 04:33:04 +00:00
this . profile = res . data ;
if ( res . data . locked == true ) {
this . visibility = 'private' ;
}
} ) . catch ( err => {
} ) ;
} ,
addMedia ( event ) {
let el = $ ( event . target ) ;
el . attr ( 'disabled' , '' ) ;
let fi = $ ( '.file-input[name="media"]' ) ;
fi . trigger ( 'click' ) ;
el . blur ( ) ;
el . removeAttr ( 'disabled' ) ;
} ,
mediaWatcher ( ) {
let self = this ;
$ ( document ) . on ( 'change' , '.file-input' , function ( e ) {
let io = document . querySelector ( '.file-input' ) ;
Array . prototype . forEach . call ( io . files , function ( io , i ) {
self . uploading = true ;
if ( self . media && self . media . length + i >= self . config . uploader . album _limit ) {
swal ( 'Error' , 'You can only upload ' + self . config . uploader . album _limit + ' photos per album' , 'error' ) ;
return ;
}
let type = io . type ;
let acceptedMimes = self . config . uploader . media _types . split ( ',' ) ;
let validated = $ . inArray ( type , acceptedMimes ) ;
if ( validated == - 1 ) {
swal ( 'Invalid File Type' , 'The file you are trying to add is not a valid mime type. Please upload a ' + self . config . uploader . media _types + ' only.' , 'error' ) ;
return ;
}
let form = new FormData ( ) ;
form . append ( 'file' , io ) ;
let xhrConfig = {
onUploadProgress : function ( e ) {
let progress = Math . round ( ( e . loaded * 100 ) / e . total ) ;
self . uploadProgress = progress ;
}
} ;
2019-09-16 00:06:04 +00:00
axios . post ( '/api/pixelfed/v1/media' , form , xhrConfig )
2019-08-23 04:33:04 +00:00
. then ( function ( e ) {
self . uploadProgress = 100 ;
self . ids . push ( e . data . id ) ;
self . media . push ( e . data ) ;
setTimeout ( function ( ) {
self . uploading = false ;
} , 1000 ) ;
} ) . catch ( function ( e ) {
self . uploading = false ;
io . value = null ;
swal ( 'Oops, something went wrong!' , 'An unexpected error occurred.' , 'error' ) ;
} ) ;
io . value = null ;
self . uploadProgress = 0 ;
} ) ;
} ) ;
} ,
toggleFilter ( e , filter ) {
this . media [ this . carouselCursor ] . filter _class = filter ;
} ,
updateMedia ( ) {
this . mediaDrawer = false ;
} ,
deleteMedia ( ) {
if ( window . confirm ( 'Are you sure you want to delete this media?' ) == false ) {
return ;
}
let id = this . media [ this . carouselCursor ] . id ;
2019-09-16 00:06:04 +00:00
axios . delete ( '/api/pixelfed/v1/media' , {
2019-08-23 04:33:04 +00:00
params : {
id : id
}
} ) . then ( res => {
if ( this . media . length == 1 ) {
this . mediaDrawer = false ;
this . ids = [ ] ;
this . media = [ ] ;
this . carouselCursor = 0 ;
}
this . ids . splice ( this . carouselCursor , 1 ) ;
this . media . splice ( this . carouselCursor , 1 ) ;
} ) . catch ( err => {
swal ( 'Whoops!' , 'An error occured when attempting to delete this, please try again' , 'error' ) ;
} ) ;
} ,
mediaAltText ( ) {
return ;
// deprecate
swal ( {
text : 'Add a media description' ,
content : "input"
} ) . then ( val => {
let media = this . media [ this . carouselCursor ] ;
media . alt = val ;
} ) ;
} ,
mediaLicense ( ) {
return ;
// deprecate
swal ( {
text : 'Add a media license' ,
content : "input" ,
button : {
text : "Update" ,
closeModal : true ,
} ,
} ) . then ( val => {
let media = this . media [ this . carouselCursor ] ;
media . license = val ;
} ) ;
} ,
compose ( ) {
let state = this . composeState ;
if ( this . uploadProgress != 100 || this . ids . length == 0 ) {
return ;
}
if ( this . composeText . length > this . config . uploader . max _caption _length ) {
swal ( 'Error' , 'Caption is too long' , 'error' ) ;
return ;
}
switch ( state ) {
case 'publish' :
if ( this . media . length == 0 ) {
swal ( 'Whoops!' , 'You need to add media before you can save this!' , 'warning' ) ;
return ;
}
if ( this . composeText == 'Add optional caption...' ) {
this . composeText = '' ;
}
let data = {
media : this . media ,
caption : this . composeText ,
visibility : this . visibility ,
cw : this . nsfw
} ;
axios . post ( '/api/local/status/compose' , data )
. then ( res => {
let data = res . data ;
window . location . href = data ;
} ) . catch ( err => {
2019-12-15 04:29:00 +00:00
let msg = err . response . data . message ? err . response . data . message : 'An unexpected error occured.'
swal ( 'Oops, something went wrong!' , msg , 'error' ) ;
2019-08-23 04:33:04 +00:00
} ) ;
return ;
break ;
case 'delete' :
this . mediaDrawer = false ;
this . ids = [ ] ;
this . media = [ ] ;
this . carouselCursor = 0 ;
this . composeText = '' ;
this . composeTextLength = 0 ;
$ ( '#composeModal' ) . modal ( 'hide' ) ;
return ;
break ;
}
} ,
about ( ) {
let text = document . createElement ( 'div' ) ;
text . innerHTML = `
< p class = "small font-weight-bold" > Please visit the < a href = "/site/kb/sharing-media" > Sharing Media < / a > page for more info . < / p >
` ;
swal ( {
title : 'Compose UI v3' ,
content : text ,
icon : 'info'
} ) ;
} ,
closeModal ( ) {
this . composeType = '' ;
$ ( '#composeModal' ) . modal ( 'hide' ) ;
} ,
composeMessage ( ) {
let config = this . config ;
let composeType = this . composeType ;
let video = config . uploader . media _types . includes ( 'video/mp4' ) ;
return video ?
'Click here to add photos or videos' :
'Click here to add photos' ;
} ,
createCollection ( ) {
window . location . href = '/i/collections/create' ;
} ,
maxSize ( ) {
let limit = this . config . uploader . max _photo _size ;
return limit / 1000 + ' MB' ;
} ,
acceptedFormats ( ) {
let formats = this . config . uploader . media _types ;
return formats . split ( ',' ) . map ( f => {
return ' ' + f . split ( '/' ) [ 1 ] ;
} ) . toString ( ) ;
}
}
}
< / script >