2016-07-26 12:09:09 +02:00
var publishNumberStyle = {
2016-07-15 14:04:47 +02:00
width : '50px' ,
2016-07-20 08:25:22 +02:00
} , publishFieldLabelStyle = {
display : 'inline-block' ,
width : '118px' ,
textAlign : 'right' ,
verticalAlign : 'top' ,
} , publishFieldStyle = {
width : '330px' ,
2016-07-15 14:04:47 +02:00
} ;
2016-05-23 17:14:21 +02:00
var PublishPage = React . createClass ( {
2016-08-08 11:53:41 +02:00
_requiredFields : [ 'name' , 'bid' , 'meta_title' , 'meta_author' , 'meta_license' , 'meta_description' ] ,
2016-07-26 12:09:09 +02:00
2016-10-19 08:36:42 +02:00
handleSubmit : function ( event ) {
if ( typeof event !== 'undefined' ) {
event . preventDefault ( ) ;
}
2016-07-21 08:58:06 +02:00
this . setState ( {
submitting : true ,
} ) ;
2016-08-08 11:53:41 +02:00
var checkFields = this . _requiredFields . slice ( ) ;
2016-09-17 07:45:42 +02:00
if ( ! this . state . myClaimExists ) {
2016-08-08 11:53:41 +02:00
checkFields . push ( 'file' ) ;
}
2016-07-26 12:09:09 +02:00
var missingFieldFound = false ;
2016-08-08 11:53:41 +02:00
for ( let fieldName of checkFields ) {
2016-07-26 12:09:09 +02:00
var field = this . refs [ fieldName ] ;
2016-07-27 17:57:18 +02:00
if ( field . getValue ( ) === '' ) {
2016-07-26 12:09:09 +02:00
field . warnRequired ( ) ;
if ( ! missingFieldFound ) {
field . focus ( ) ;
missingFieldFound = true ;
}
}
}
if ( missingFieldFound ) {
this . setState ( {
submitting : false ,
} ) ;
2016-07-27 13:46:48 +02:00
return ;
2016-07-26 12:09:09 +02:00
}
2016-07-20 08:25:22 +02:00
2016-08-23 07:03:03 +02:00
if ( this . state . nameIsMine ) {
// Pre-populate with existing metadata
2016-09-17 07:45:42 +02:00
var metadata = Object . assign ( { } , this . state . myClaimMetadata ) ;
2016-08-23 07:03:03 +02:00
if ( this . refs . file . getValue ( ) !== '' ) {
delete metadata . sources ;
}
} else {
var metadata = { } ;
}
2016-08-27 08:18:19 +02:00
2016-07-27 17:57:18 +02:00
for ( let metaField of [ 'title' , 'author' , 'description' , 'thumbnail' , 'license' , 'license_url' , 'language' , 'nsfw' ] ) {
var value = this . refs [ 'meta_' + metaField ] . getValue ( ) ;
2016-07-27 19:54:25 +02:00
if ( value !== '' ) {
2016-07-26 12:09:09 +02:00
metadata [ metaField ] = value ;
}
}
2016-07-27 17:57:18 +02:00
var licenseUrl = this . refs . meta _license _url . getValue ( ) ;
if ( licenseUrl ) {
metadata . license _url = licenseUrl ;
}
2016-07-20 08:25:22 +02:00
2016-07-21 08:58:06 +02:00
var doPublish = ( ) => {
2016-08-08 11:53:41 +02:00
var publishArgs = {
name : this . state . name ,
2016-07-21 08:58:06 +02:00
bid : parseFloat ( this . state . bid ) ,
metadata : metadata ,
2016-08-08 11:53:41 +02:00
} ;
2016-08-23 07:03:03 +02:00
if ( this . refs . file . getValue ( ) !== '' ) {
2016-08-08 11:53:41 +02:00
publishArgs . file _path = this . _tempFilePath ;
}
console . log ( publishArgs ) ;
lbry . publish ( publishArgs , ( message ) => {
2016-09-16 17:23:39 +02:00
this . handlePublishStarted ( ) ;
2016-07-21 08:58:06 +02:00
this . setState ( {
submitting : false ,
} ) ;
2016-09-16 17:23:39 +02:00
} , null , ( error ) => {
2016-07-21 08:58:06 +02:00
this . handlePublishError ( error ) ;
this . setState ( {
submitting : false ,
} ) ;
} ) ;
} ;
2016-07-20 08:25:22 +02:00
if ( this . state . isFee ) {
2016-07-21 08:58:06 +02:00
lbry . getNewAddress ( ( address ) => {
2016-07-30 07:47:37 +02:00
metadata . fee = { } ;
metadata . fee [ this . state . feeCurrency ] = {
amount : parseFloat ( this . state . feeAmount ) ,
address : address ,
2016-07-21 08:58:06 +02:00
} ;
2016-07-20 08:25:22 +02:00
2016-07-21 08:58:06 +02:00
doPublish ( ) ;
} ) ;
} else {
doPublish ( ) ;
}
2016-05-23 17:14:21 +02:00
} ,
2016-07-15 14:04:47 +02:00
getInitialState : function ( ) {
2016-07-20 08:25:22 +02:00
this . _tempFilePath = null ;
2016-07-15 14:04:47 +02:00
return {
name : '' ,
bid : '' ,
2016-07-30 07:47:37 +02:00
feeAmount : '' ,
2016-08-08 11:47:20 +02:00
feeCurrency : 'USD' ,
2016-07-20 08:25:22 +02:00
nameResolved : false ,
2016-09-17 07:45:42 +02:00
topClaimValue : 0.0 ,
myClaimValue : 0.0 ,
myClaimMetadata : null ,
myClaimExists : null ,
2016-07-20 08:25:22 +02:00
fileInfo : null ,
2016-09-20 12:40:24 +02:00
copyrightNotice : '' ,
otherLicenseDescription : '' ,
otherLicenseUrl : '' ,
2016-07-20 08:25:22 +02:00
uploadProgress : 0.0 ,
uploaded : false ,
tempFileReady : false ,
2016-07-21 08:58:06 +02:00
submitting : false ,
2016-07-15 14:04:47 +02:00
} ;
} ,
2016-09-16 17:23:39 +02:00
handlePublishStarted : function ( ) {
2016-07-27 21:26:49 +02:00
alert ( ` Your file ${ this . refs . meta _title . getValue ( ) } has been published to LBRY at the address lbry:// ${ this . state . name } ! \n \n ` +
2016-08-03 12:08:37 +02:00
` You will now be taken to your My Files page, where your newly published file will be listed. Your file will take a few minutes to appear for other LBRY users; until then it will be listed as "pending." ` ) ;
2016-09-23 11:19:39 +02:00
window . location = "?published" ;
2016-07-21 08:58:06 +02:00
} ,
handlePublishError : function ( error ) {
alert ( ` The following error occurred when attempting to publish your file: \n \n ` +
error . message ) ;
} ,
2016-07-15 14:04:47 +02:00
handleNameChange : function ( event ) {
2016-09-01 09:30:12 +02:00
var rawName = event . target . value ;
2016-07-15 14:04:47 +02:00
2016-09-01 09:30:12 +02:00
if ( ! rawName ) {
2016-07-15 14:04:47 +02:00
this . setState ( {
name : '' ,
nameResolved : false ,
2016-07-20 08:25:22 +02:00
} ) ;
2016-07-15 14:04:47 +02:00
return ;
}
2016-09-01 09:30:12 +02:00
var name = lbry . formatName ( rawName ) ;
2016-07-15 14:04:47 +02:00
lbry . resolveName ( name , ( info ) => {
2016-09-01 09:30:12 +02:00
if ( name != lbry . formatName ( this . refs . name . getValue ( ) ) ) {
// A new name has been typed already, so bail
2016-08-10 09:05:17 +02:00
return ;
}
2016-07-15 14:04:47 +02:00
if ( ! info ) {
this . setState ( {
name : name ,
2016-07-20 08:25:22 +02:00
nameResolved : false ,
2016-07-15 14:04:47 +02:00
} ) ;
} else {
2016-09-17 07:45:42 +02:00
lbry . getMyClaim ( name , ( myClaimInfo ) => {
lbry . getClaimInfo ( name , ( claimInfo ) => {
if ( name != this . refs . name . getValue ( ) ) {
return ;
}
var topClaimIsMine = ( myClaimInfo && myClaimInfo . amount >= claimInfo . amount ) ;
var newState = {
name : name ,
nameResolved : true ,
topClaimValue : parseFloat ( claimInfo . amount ) ,
myClaimExists : ! ! myClaimInfo ,
myClaimValue : parseFloat ( myClaimInfo . amount ) ,
myClaimMetadata : myClaimInfo . value ,
topClaimIsMine : topClaimIsMine ,
} ;
if ( topClaimIsMine ) {
newState . bid = myClaimInfo . amount ;
} else if ( this . state . myClaimMetadata ) {
// Just changed away from a name we have a claim on, so clear pre-fill
newState . bid = '' ;
}
this . setState ( newState ) ;
} ) ;
2016-07-15 14:04:47 +02:00
} ) ;
}
} ) ;
} ,
2016-07-20 08:25:22 +02:00
handleBidChange : function ( event ) {
2016-07-15 14:04:47 +02:00
this . setState ( {
2016-07-20 08:25:22 +02:00
bid : event . target . value ,
2016-07-15 14:04:47 +02:00
} ) ;
} ,
2016-07-30 07:47:37 +02:00
handleFeeAmountChange : function ( event ) {
2016-07-15 14:04:47 +02:00
this . setState ( {
2016-07-30 07:47:37 +02:00
feeAmount : event . target . value ,
} ) ;
} ,
handleFeeCurrencyChange : function ( event ) {
this . setState ( {
feeCurrency : event . target . value ,
2016-07-20 08:25:22 +02:00
} ) ;
} ,
handleFileChange : function ( event ) {
event . preventDefault ( ) ;
var fileInput = event . target ;
this . _tempFilePath = null ;
if ( fileInput . files . length == 0 ) {
// File was removed
this . setState ( {
fileInfo : null ,
uploadProgress : 0.0 ,
uploaded : false ,
tempFileReady : false ,
} ) ;
} else {
var file = fileInput . files [ 0 ] ;
this . setState ( {
fileInfo : {
name : file . name ,
size : file . size ,
} ,
uploadProgress : 0.0 ,
uploaded : false ,
tempFileReady : false ,
} ) ;
var xhr = new XMLHttpRequest ( ) ;
xhr . upload . addEventListener ( 'progress' , ( event ) => {
this . setState ( {
uploadProgress : ( event . loaded / event . total ) ,
} ) ;
} ) ;
xhr . upload . addEventListener ( 'load' , ( event ) => {
this . setState ( {
uploaded : true ,
} ) ;
} ) ;
xhr . addEventListener ( 'load' , ( event ) => {
this . _tempFilePath = JSON . parse ( xhr . responseText ) ;
this . setState ( {
tempFileReady : true ,
} ) ;
} )
2016-10-19 04:30:57 +02:00
var formData = new FormData ( fileInput . form ) ;
formData . append ( 'file' , fileInput . files [ 0 ] ) ;
2016-07-20 08:25:22 +02:00
xhr . open ( 'POST' , '/upload' , true ) ;
2016-10-19 04:30:57 +02:00
xhr . send ( formData ) ;
2016-07-20 08:25:22 +02:00
}
} ,
handleFeePrefChange : function ( feeEnabled ) {
this . setState ( {
isFee : feeEnabled
2016-07-15 14:04:47 +02:00
} ) ;
} ,
2016-09-20 12:40:24 +02:00
handeLicenseChange : function ( event ) {
var licenseType = event . target . options [ event . target . selectedIndex ] . getAttribute ( 'data-license-type' ) ;
var newState = {
copyrightChosen : licenseType == 'copyright' ,
otherLicenseChosen : licenseType == 'other' ,
} ;
if ( licenseType == 'copyright' ) {
var author = this . refs . meta _author . getValue ( ) ;
2016-09-21 09:42:31 +02:00
newState . copyrightNotice = 'Copyright ' + ( new Date ( ) . getFullYear ( ) ) + ( author ? ' ' + author : '' ) ;
2016-09-20 12:40:24 +02:00
}
this . setState ( newState ) ;
} ,
handleCopyrightNoticeChange : function ( event ) {
this . setState ( {
copyrightNotice : event . target . value ,
} ) ;
} ,
handleOtherLicenseDescriptionChange : function ( event ) {
this . setState ( {
otherLicenseDescription : event . target . value ,
} ) ;
} ,
handleOtherLicenseUrlChange : function ( event ) {
this . setState ( {
otherLicenseUrl : event . target . value ,
} ) ;
} ,
getLicenseUrl : function ( ) {
if ( ! this . refs . meta _license ) {
return '' ;
} else if ( this . state . otherLicenseChosen ) {
return this . state . otherLicenseUrl ;
} else {
2016-09-21 08:57:20 +02:00
return this . refs . meta _license . getSelectedElement ( ) . getAttribute ( 'data-url' ) || '' ;
2016-09-20 12:40:24 +02:00
}
} ,
2016-08-08 00:13:17 +02:00
componentDidMount : function ( ) {
document . title = "Publish" ;
} ,
2016-07-27 17:57:18 +02:00
componentDidUpdate : function ( ) {
2016-07-20 08:25:22 +02:00
if ( this . state . fileInfo && ! this . state . tempFileReady ) {
// A file was chosen but the daemon hasn't finished processing it yet, i.e. it's loading, so
2016-07-27 17:57:18 +02:00
// we're displaying a progress bar and need a value for it.
// React can't unset the "value" prop (to show an "indeterminate" bar) after it's already
// been set, so we have to manage it manually.
2016-07-20 08:25:22 +02:00
if ( ! this . state . uploaded ) {
// Still uploading
2016-07-27 17:57:18 +02:00
this . refs . progress . setAttribute ( 'value' , this . state . uploadProgress ) ;
2016-07-20 08:25:22 +02:00
} else {
// Fully uploaded and waiting for server to finish processing, so set progress bar to "indeterminite"
2016-07-27 17:57:18 +02:00
this . refs . progress . removeAttribute ( 'value' ) ;
2016-07-20 08:25:22 +02:00
}
}
2016-07-27 17:57:18 +02:00
} ,
render : function ( ) {
2016-05-23 17:14:21 +02:00
return (
2016-08-08 05:31:21 +02:00
< main ref = "page" >
2016-10-19 04:30:57 +02:00
< form onSubmit = { this . handleSubmit } >
< section className = "card" >
< h4 > LBRY Name < / h 4 >
< div className = "form-row" >
lbry : //<FormField type="text" ref="name" onChange={this.handleNameChange} />
{
( ! this . state . name ? '' :
( ! this . state . nameResolved ? < em > The name < strong > { this . state . name } < / s t r o n g > i s a v a i l a b l e . < / e m >
: ( this . state . myClaimExists ? < em > You already have a claim on the name < strong > { this . state . name } < / s t r o n g > . Y o u c a n u s e t h i s p a g e t o u p d a t e y o u r c l a i m . < / e m >
: < em > The name < strong > { this . state . name } < / s t r o n g > i s c u r r e n t l y c l a i m e d f o r < s t r o n g > { l b r y . f o r m a t C r e d i t s ( t h i s . s t a t e . t o p C l a i m V a l u e ) } < / s t r o n g > c r e d i t s . < / e m > ) ) )
}
< div className = "help" > What LBRY name would you like to claim for this file ? < / d i v >
< / d i v >
< / s e c t i o n >
2016-05-23 17:14:21 +02:00
2016-10-19 04:30:57 +02:00
< section className = "card" >
< h4 > Choose File < / h 4 >
2016-07-26 12:09:09 +02:00
< FormField name = "file" ref = "file" type = "file" onChange = { this . handleFileChange } / >
2016-07-20 08:25:22 +02:00
{ ! this . state . fileInfo ? '' :
( ! this . state . tempFileReady ? < div >
2016-07-27 17:57:18 +02:00
< progress ref = 'progress' > < / p r o g r e s s >
2016-07-20 08:25:22 +02:00
{ ! this . state . uploaded ? < span > Importing file into LBRY ... < / s p a n > : < s p a n > P r o c e s s i n g f i l e . . . < / s p a n > }
< / d i v >
: < div > File ready for publishing ! < / d i v > ) }
2016-10-19 04:30:57 +02:00
{ this . state . myClaimExists ? < div className = "help" > If you don ' t choose a file , the file from your existing claim will be used . < / d i v > : n u l l }
< / s e c t i o n >
< section className = "card" >
< h4 > Bid Amount < / h 4 >
< div className = "form-row" >
Credits < FormField ref = "bid" style = { publishNumberStyle } type = "text" onChange = { this . handleBidChange } value = { this . state . bid } placeholder = { this . state . nameResolved ? lbry . formatCredits ( this . state . topClaimValue + 10 ) : 100 } / >
< div className = "help" > How much would you like to bid for this name ?
{ ! this . state . nameResolved ? < span > Since this name is not currently resolved , you may bid as low as you want , but higher bids help prevent others from claiming your name . < / s p a n >
: ( this . state . topClaimIsMine ? < span > You currently control this name with a bid of < strong > { lbry . formatCredits ( this . state . myClaimValue ) } < / s t r o n g > c r e d i t s . < / s p a n >
: ( this . state . myClaimExists ? < span > You have a non - winning bid on this name for < strong > { lbry . formatCredits ( this . state . myClaimValue ) } < / s t r o n g > c r e d i t s .
To control this name , you ' ll need to increase your bid to at least < strong > { lbry . formatCredits ( this . state . myClaimValue ) } < / s t r o n g > c r e d i t s . < / s p a n >
: < span > You must bid over < strong > { lbry . formatCredits ( this . state . topClaimValue ) } < / s t r o n g > c r e d i t s t o c l a i m t h i s n a m e . < / s p a n > ) ) }
< / d i v >
2016-08-08 05:31:21 +02:00
< / d i v >
2016-10-19 04:30:57 +02:00
< / s e c t i o n >
< section className = "card" >
< h4 > Fee < / h 4 >
< div className = "form-row" >
< label >
< FormField type = "radio" onChange = { ( ) => { this . handleFeePrefChange ( false ) } } checked = { ! this . state . isFee } / > No fee
< / l a b e l >
< label >
< FormField type = "radio" onChange = { ( ) => { this . handleFeePrefChange ( true ) } } checked = { this . state . isFee } / > { ! this . state . isFee ? 'Choose fee...' : 'Fee ' }
< span className = { ! this . state . isFee ? 'hidden' : '' } >
< FormField type = "text" onChange = { this . handleFeeAmountChange } style = { publishNumberStyle } / > < FormField type = "select" onChange = { this . handleFeeCurrencyChange } >
< option value = "USD" > US Dollars < / o p t i o n >
< option value = "LBC" > LBRY credits < / o p t i o n >
< / F o r m F i e l d >
< / s p a n >
< / l a b e l >
< div className = "help" >
< p > How much would you like to charge for this file ? < / p >
If you choose to price this content in dollars , the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase .
< / d i v >
2016-08-08 05:31:21 +02:00
< / d i v >
2016-10-19 04:30:57 +02:00
< / s e c t i o n >
2016-05-23 17:14:21 +02:00
2016-07-20 08:25:22 +02:00
2016-10-19 04:30:57 +02:00
< section className = "card" >
< h4 > Your Content < / h 4 >
2016-07-20 08:25:22 +02:00
2016-10-19 04:30:57 +02:00
< div className = "form-row" >
< label htmlFor = "title" > Title < /label><FormField type="text" ref="meta_title" name="title" placeholder="My Show, Episode 1" style={publishFieldStyle} / >
< / d i v >
< div className = "form-row" >
< label htmlFor = "author" > Author < /label><FormField type="text" ref="meta_author" name="author" placeholder="My Company, Inc." style={publishFieldStyle} / >
< / d i v >
< div className = "form-row" >
< label htmlFor = "license" > License < / l a b e l > < F o r m F i e l d t y p e = " s e l e c t " r e f = " m e t a _ l i c e n s e " n a m e = " l i c e n s e " o n C h a n g e = { t h i s . h a n d e L i c e n s e C h a n g e } >
< option data - url = "https://creativecommons.org/licenses/by/4.0/legalcode" > Creative Commons Attribution 4.0 International < / o p t i o n >
< option data - url = "https://creativecommons.org/licenses/by-sa/4.0/legalcode" > Creative Commons Attribution - ShareAlike 4.0 International < / o p t i o n >
< option data - url = "https://creativecommons.org/licenses/by-nd/4.0/legalcode" > Creative Commons Attribution - NoDerivatives 4.0 International < / o p t i o n >
< option data - url = "https://creativecommons.org/licenses/by-nc/4.0/legalcode" > Creative Commons Attribution - NonCommercial 4.0 International < / o p t i o n >
< option data - url = "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode" > Creative Commons Attribution - NonCommercial - ShareAlike 4.0 International < / o p t i o n >
< option data - url = "https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode" > Creative Commons Attribution - NonCommercial - NoDerivatives 4.0 International < / o p t i o n >
< option > Public Domain < / o p t i o n >
< option data - license - type = "copyright" { ... this . state . copyrightChosen ? { value : this . state . copyrightNotice } : { } } > Copyrighted ... < / o p t i o n >
< option data - license - type = "other" { ... this . state . otherLicenseChosen ? { value : this . state . otherLicenseDescription } : { } } > Other ... < / o p t i o n >
2016-07-26 12:09:09 +02:00
< / F o r m F i e l d >
2016-10-19 04:30:57 +02:00
< FormField type = "hidden" ref = "meta_license_url" name = "license_url" value = { this . getLicenseUrl ( ) } / >
< / d i v >
{ this . state . copyrightChosen
? < div className = "form-row" >
< label htmlFor = "copyright-notice" value = { this . state . copyrightNotice } > Copyright notice < /label><FormField type="text" name="copyright-notice" value={this.state.copyrightNotice} onChange={this.handleCopyrightNoticeChange} style={publishFieldStyle} / >
< / d i v >
: null }
{ this . state . otherLicenseChosen
? < div className = "form-row" >
< label htmlFor = "other-license-description" > License description < /label><FormField type="text" name="other-license-description" onChange={this.handleOtherLicenseDescriptionChange} style={publishFieldStyle} / >
< / d i v >
: null }
{ this . state . otherLicenseChosen
? < div className = "form-row" >
< label htmlFor = "other-license-url" > License URL < /label> <FormField type="text" name="other-license-url" style={publishFieldStyle} onChange={this.handleOtherLicenseUrlChange} / >
< / d i v >
: null }
< div className = "form-row" >
< label htmlFor = "language" > Language < / l a b e l > < F o r m F i e l d t y p e = " s e l e c t " d e f a u l t V a l u e = " e n " r e f = " m e t a _ l a n g u a g e " n a m e = " l a n g u a g e " >
< option value = "en" > English < / o p t i o n >
< option value = "zh" > Chinese < / o p t i o n >
< option value = "fr" > French < / o p t i o n >
< option value = "de" > German < / o p t i o n >
< option value = "jp" > Japanese < / o p t i o n >
< option value = "ru" > Russian < / o p t i o n >
< option value = "es" > Spanish < / o p t i o n >
< / F o r m F i e l d >
< / d i v >
< div className = "form-row" >
< label htmlFor = "description" > Description < /label> <FormField type="textarea" ref="meta_description" name="description" placeholder="Description of your content" style={publishFieldStyle} / >
< / d i v >
< div className = "form-row" >
< label > < FormField type = "checkbox" ref = "meta_nsfw" name = "nsfw" placeholder = "Description of your content" / > Not Safe For Work < / l a b e l >
< / d i v >
< / s e c t i o n >
2016-07-20 08:25:22 +02:00
2016-08-07 17:27:00 +02:00
2016-07-20 08:25:22 +02:00
2016-10-19 04:30:57 +02:00
< section className = "card" >
< h4 > Additional Content Information ( Optional ) < / h 4 >
< div className = "form-row" >
< label htmlFor = "meta_thumbnail" > Thumbnail URL < /label> <FormField type="text" ref="meta_thumbnail" name="thumbnail" placeholder="http:/ / mycompany . com / images / ep _1 . jpg " style = { publishFieldStyle } / >
< / d i v >
< / s e c t i o n >
2016-05-23 17:14:21 +02:00
2016-10-19 04:30:57 +02:00
< div className = "card-series-submit" >
< Link button = "primary" label = { ! this . state . submitting ? 'Publish' : 'Publishing...' } onClick = { this . handleSubmit } disabled = { this . state . submitting } / >
< Link button = "cancel" href = "/" label = "Cancel" / >
2016-10-19 08:36:42 +02:00
< input type = 'submit' className = 'hidden' / >
2016-10-19 04:30:57 +02:00
< / d i v >
< / f o r m >
< / m a i n >
2016-05-23 17:14:21 +02:00
) ;
}
2016-08-08 11:53:41 +02:00
} ) ;