Merge pull request #24 from lbryio/publish-page-july
Publishing support
This commit is contained in:
commit
007a5e55d1
9 changed files with 475 additions and 19 deletions
2
dist/index.html
vendored
2
dist/index.html
vendored
|
@ -5,6 +5,7 @@
|
|||
<title>LBRY</title>
|
||||
|
||||
<link href='https://fonts.googleapis.com/css?family=Raleway:600,300' rel='stylesheet' type='text/css'>
|
||||
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400italic,600italic,600' rel='stylesheet' type='text/css'>
|
||||
<link href="./css/all.css" rel="stylesheet" type="text/css" media="screen,print" />
|
||||
<link href="./js/mediaelement/mediaelementplayer.css" rel="stylesheet" type="text/css" />
|
||||
<link rel="icon" type="image/png" href="./img/fav/favicon-32x32.png" sizes="32x32">
|
||||
|
@ -33,6 +34,7 @@
|
|||
<script src="./js/page/watch.js"></script>
|
||||
<script src="./js/page/report.js"></script>
|
||||
<script src="./js/page/my_files.js"></script>
|
||||
<script src="./js/page/publish.js"></script>
|
||||
<script src="./js/page/start.js"></script>
|
||||
<script src="./js/page/claim_code.js"></script>
|
||||
<script src="./js/page/wallet.js"></script>
|
||||
|
|
|
@ -4,7 +4,7 @@ var App = React.createClass({
|
|||
var match, param, val;
|
||||
[match, param, val] = window.location.search.match(/\??([^=]*)(?:=(.*))?/);
|
||||
|
||||
if (['settings', 'help', 'start', 'watch', 'report', 'files', 'claim', 'show', 'wallet'].indexOf(param) != -1) {
|
||||
if (['settings', 'help', 'start', 'watch', 'report', 'files', 'claim', 'show', 'wallet', 'publish'].indexOf(param) != -1) {
|
||||
var viewingPage = param;
|
||||
} else {
|
||||
var viewingPage = 'home';
|
||||
|
@ -69,6 +69,8 @@ var App = React.createClass({
|
|||
return <DetailPage name={this.state.pageArgs}/>;
|
||||
} else if (this.state.viewingPage == 'wallet') {
|
||||
return <WalletPage />;
|
||||
} else if (this.state.viewingPage == 'publish') {
|
||||
return <PublishPage />;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -169,6 +169,81 @@ var WatchLink = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
var requiredFieldWarningStyle = {
|
||||
color: '#cc0000',
|
||||
transition: 'opacity 400ms ease-in',
|
||||
};
|
||||
var FormField = React.createClass({
|
||||
_type: null,
|
||||
_element: null,
|
||||
|
||||
propTypes: {
|
||||
type: React.PropTypes.string.isRequired,
|
||||
hidden: React.PropTypes.bool,
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
warningState: 'hidden',
|
||||
}
|
||||
},
|
||||
componentWillMount: function() {
|
||||
if (['text', 'radio', 'checkbox', 'file'].indexOf(this.props.type) != -1) {
|
||||
this._element = 'input';
|
||||
this._type = this.props.type;
|
||||
} else {
|
||||
// Non <input> field, e.g. <select>, <textarea>
|
||||
this._element = this.props.type;
|
||||
}
|
||||
},
|
||||
warnRequired: function() {
|
||||
this.setState({
|
||||
warningState: 'shown',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
warningState: 'fading',
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
warningState: 'hidden',
|
||||
});
|
||||
}, 450);
|
||||
}, 5000);
|
||||
},
|
||||
focus: function() {
|
||||
this.refs.field.focus();
|
||||
},
|
||||
getValue: function() {
|
||||
if (this.props.type == 'checkbox') {
|
||||
return this.refs.field.checked;
|
||||
} else {
|
||||
return this.refs.field.value;
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var warningStyle = Object.assign({}, requiredFieldWarningStyle);
|
||||
if (this.state.warningState == 'fading') {
|
||||
warningStyle.opacity = '0';
|
||||
}
|
||||
|
||||
// Pass all unhandled props to the field element
|
||||
var otherProps = Object.assign({}, this.props);
|
||||
delete otherProps.type;
|
||||
delete otherProps.hidden;
|
||||
|
||||
return (
|
||||
<span className={this.props.hidden ? 'hidden' : ''}>
|
||||
<this._element type={this._type} name={this.props.name} ref="field" placeholder={this.props.placeholder}
|
||||
{...otherProps}>
|
||||
{this.props.children}
|
||||
</this._element>
|
||||
<span className={this.state.warningState == 'hidden' ? 'hidden' : ''} style={warningStyle}> This field is required</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Generic menu styles
|
||||
var menuStyle = {
|
||||
border: '1px solid #aaa',
|
||||
|
|
17
js/lbry.js
17
js/lbry.js
|
@ -108,8 +108,11 @@ lbry.search = function(query, callback)
|
|||
lbry.call("search_nametrie", { "search": query }, callback);
|
||||
}
|
||||
|
||||
lbry.resolveName = function(name, callback, ec) {
|
||||
lbry.call('resolve_name', { 'name': name }, callback);
|
||||
lbry.resolveName = function(name, callback) {
|
||||
lbry.call('resolve_name', { 'name': name }, callback, () => {
|
||||
// For now, assume any error means the name was not resolved
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
lbry.getStream = function(name, callback) {
|
||||
|
@ -151,6 +154,16 @@ lbry.revealFile = function(path, callback) {
|
|||
lbry.call('reveal', { path: path }, callback);
|
||||
}
|
||||
|
||||
lbry.publish = function(params, callback, errorCallback) {
|
||||
// Use ES6 named arguments instead of directly passing param dict?
|
||||
lbry.call('publish', params, callback, (errorInfo) => {
|
||||
errorCallback({
|
||||
name: fault.fault,
|
||||
message: fault.faultString,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
lbry.getVersionInfo = function(callback) {
|
||||
lbry.call('version', {}, callback);
|
||||
};
|
||||
|
|
|
@ -34,7 +34,7 @@ var SearchNoResults = React.createClass({
|
|||
return (
|
||||
<section style={searchNoResultsStyle}>
|
||||
<span style={searchNoResultsMessageStyle}>No one has checked anything in for {this.props.query} yet.</span>
|
||||
<Link label="Be the first" href="javascript:alert('aww I do nothing')" />
|
||||
<Link label="Be the first" href="?publish" />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
@ -382,6 +382,7 @@ var MainMenu = React.createClass({
|
|||
<Menu {...this.props}>
|
||||
<MenuItem href='/?files' label="My Files" icon='icon-cloud-download' />
|
||||
<MenuItem href='/?wallet' label="My Wallet" icon='icon-bank' />
|
||||
<MenuItem href='/?publish' label="Publish" icon='icon-upload' />
|
||||
<MenuItem href='/?settings' label="Settings" icon='icon-gear' />
|
||||
<MenuItem href='/?help' label="Help" icon='icon-question-circle' />
|
||||
{isLinux ? <MenuItem href="/?start" label="Exit LBRY" icon="icon-close" />
|
||||
|
|
|
@ -94,23 +94,31 @@ var MyFilesRow = React.createClass({
|
|||
return (
|
||||
<div className="row-fluid">
|
||||
<div className="span3">
|
||||
<img src={this.props.imgUrl} alt={'Photo for ' + this.props.title} style={artStyle} />
|
||||
{this.props.imgUrl ? <img src={this.props.imgUrl} alt={'Photo for ' + this.props.title} style={artStyle} /> : null}
|
||||
</div>
|
||||
<div className="span6">
|
||||
<h2><a href={'/?show=' + this.props.lbryUri}>{this.props.title}</a></h2>
|
||||
<div className={this.props.completed ? 'hidden' : ''} style={curProgressBarStyle}></div>
|
||||
{ ' ' }
|
||||
{this.props.completed ? 'Download complete' : (parseInt(this.props.ratioLoaded * 100) + '%')}
|
||||
<div>{ pauseLink }</div>
|
||||
<div>{ watchButton }</div>
|
||||
<h2>{this.props.pending ? this.props.title : <a href={'/?show=' + this.props.lbryUri}>{this.props.title}</a>}</h2>
|
||||
{this.props.pending ? <em>This file is pending confirmation</em>
|
||||
: (
|
||||
<div>
|
||||
<div className={this.props.completed ? 'hidden' : ''} style={curProgressBarStyle}></div>
|
||||
{ ' ' }
|
||||
{this.props.completed ? 'Download complete' : (parseInt(this.props.ratioLoaded * 100) + '%')}
|
||||
<div>{ pauseLink }</div>
|
||||
<div>{ watchButton }</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="span1" style={moreButtonColumnStyle}>
|
||||
<div style={moreButtonContainerStyle}>
|
||||
<Link style={moreButtonStyle} ref="moreButton" icon="icon-ellipsis-h" title="More Options" />
|
||||
<MyFilesRowMoreMenu toggleButton={this.refs.moreButton} title={this.props.title}
|
||||
lbryUri={this.props.lbryUri} fileName={this.props.fileName}
|
||||
path={this.props.path}/>
|
||||
</div>
|
||||
{this.props.pending ? null :
|
||||
<div style={moreButtonContainerStyle}>
|
||||
<Link style={moreButtonStyle} ref="moreButton" icon="icon-ellipsis-h" title="More Options" />
|
||||
<MyFilesRowMoreMenu toggleButton={this.refs.moreButton} title={this.props.title}
|
||||
lbryUri={this.props.lbryUri} fileName={this.props.fileName}
|
||||
path={this.props.path}/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -155,12 +163,21 @@ var MyFilesPage = React.createClass({
|
|||
|
||||
let {title, thumbnail} = metadata;
|
||||
|
||||
if (!fileInfo.pending && typeof metadata == 'object') {
|
||||
var {title, thumbnail} = metadata;
|
||||
var pending = false;
|
||||
} else {
|
||||
var title = null;
|
||||
var thumbnail = null;
|
||||
var pending = true;
|
||||
}
|
||||
|
||||
var ratioLoaded = written_bytes / total_bytes;
|
||||
var showWatchButton = (lbry.getMediaType(file_name) == 'video' || lbry.getMediaType(file_name) == 'audio');
|
||||
|
||||
content.push(<MyFilesRow lbryUri={lbry_uri} title={title || ('lbry://' + lbry_uri)} completed={completed} stopped={stopped}
|
||||
ratioLoaded={ratioLoaded} imgUrl={thumbnail} path={download_path}
|
||||
showWatchButton={showWatchButton}/>);
|
||||
showWatchButton={showWatchButton} pending={pending} />);
|
||||
}
|
||||
}
|
||||
return (
|
||||
|
|
337
js/page/publish.js
Normal file
337
js/page/publish.js
Normal file
|
@ -0,0 +1,337 @@
|
|||
var publishNumberStyle = {
|
||||
width: '50px',
|
||||
}, publishFieldLabelStyle = {
|
||||
display: 'inline-block',
|
||||
width: '118px',
|
||||
textAlign: 'right',
|
||||
verticalAlign: 'top',
|
||||
}, publishFieldStyle = {
|
||||
width: '330px',
|
||||
};
|
||||
|
||||
var PublishPage = React.createClass({
|
||||
_requiredFields: ['name', 'file', 'bid', 'meta_title', 'meta_author', 'meta_license', 'meta_description'],
|
||||
|
||||
handleSubmit: function() {
|
||||
this.setState({
|
||||
submitting: true,
|
||||
});
|
||||
|
||||
var missingFieldFound = false;
|
||||
for (let fieldName of this._requiredFields) {
|
||||
var field = this.refs[fieldName];
|
||||
if (field.getValue() === '') {
|
||||
field.warnRequired();
|
||||
if (!missingFieldFound) {
|
||||
field.focus();
|
||||
missingFieldFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (missingFieldFound) {
|
||||
this.setState({
|
||||
submitting: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var metadata = {ver: '0.0.2'};
|
||||
for (let metaField of ['title', 'author', 'description', 'thumbnail', 'license', 'license_url', 'language', 'nsfw']) {
|
||||
var value = this.refs['meta_' + metaField].getValue();
|
||||
if (value !== '') {
|
||||
metadata[metaField] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
metadata.license = {};
|
||||
metadata.license.name = this.refs.meta_license.getValue();
|
||||
|
||||
var licenseUrl = this.refs.meta_license_url.getValue();
|
||||
if (licenseUrl != '') {
|
||||
metadata.license.url = licenseUrl;
|
||||
}
|
||||
*/
|
||||
|
||||
var licenseUrl = this.refs.meta_license_url.getValue();
|
||||
if (licenseUrl) {
|
||||
metadata.license_url = licenseUrl;
|
||||
}
|
||||
|
||||
var doPublish = () => {
|
||||
console.log({name: this.state.name,
|
||||
file_path: this._tempFilePath,
|
||||
bid: parseFloat(this.state.bid),
|
||||
metadata: metadata,
|
||||
});
|
||||
lbry.publish({
|
||||
name: this.state.name,
|
||||
file_path: this._tempFilePath,
|
||||
bid: parseFloat(this.state.bid),
|
||||
metadata: metadata,
|
||||
}, (message) => {
|
||||
this.handlePublishSuccess();
|
||||
this.setState({
|
||||
submitting: false,
|
||||
});
|
||||
}, (error) => {
|
||||
this.handlePublishError(error);
|
||||
this.setState({
|
||||
submitting: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (this.state.isFee) {
|
||||
lbry.getNewAddress((address) => {
|
||||
metadata.fee = {};
|
||||
metadata.fee[this.state.feeCurrency] = {
|
||||
amount: parseFloat(this.state.feeAmount),
|
||||
address: address,
|
||||
};
|
||||
|
||||
doPublish();
|
||||
});
|
||||
} else {
|
||||
doPublish();
|
||||
}
|
||||
},
|
||||
getInitialState: function() {
|
||||
this._tempFilePath = null;
|
||||
|
||||
return {
|
||||
name: '',
|
||||
bid: '',
|
||||
feeAmount: '',
|
||||
feeCurrency: '',
|
||||
nameResolved: false,
|
||||
claimValue: 0.0,
|
||||
fileInfo: null,
|
||||
uploadProgress: 0.0,
|
||||
uploaded: false,
|
||||
tempFileReady: false,
|
||||
submitting: false,
|
||||
};
|
||||
},
|
||||
handlePublishSuccess: function() {
|
||||
alert(`Your file ${this.refs.meta_title.getValue()} has been published to LBRY at the address lbry://${this.state.name}!\n\n` +
|
||||
`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."`);
|
||||
window.location = "?files";
|
||||
},
|
||||
handlePublishError: function(error) {
|
||||
alert(`The following error occurred when attempting to publish your file:\n\n` +
|
||||
error.message);
|
||||
},
|
||||
handleNameChange: function(event) {
|
||||
var name = event.target.value;
|
||||
|
||||
if (!name) {
|
||||
this.setState({
|
||||
name: '',
|
||||
nameResolved: false,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lbry.resolveName(name, (info) => {
|
||||
if (!info) {
|
||||
this.setState({
|
||||
name: name,
|
||||
nameResolved: false,
|
||||
});
|
||||
} else {
|
||||
lbry.getClaimInfo(name, (claimInfo) => {
|
||||
this.setState({
|
||||
name: name,
|
||||
nameResolved: true,
|
||||
claimValue: parseFloat(claimInfo.amount),
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
handleBidChange: function(event) {
|
||||
this.setState({
|
||||
bid: event.target.value,
|
||||
});
|
||||
},
|
||||
handleFeeAmountChange: function(event) {
|
||||
this.setState({
|
||||
feeAmount: event.target.value,
|
||||
});
|
||||
},
|
||||
handleFeeCurrencyChange: function(event) {
|
||||
this.setState({
|
||||
feeCurrency: event.target.value,
|
||||
});
|
||||
},
|
||||
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,
|
||||
});
|
||||
})
|
||||
|
||||
xhr.open('POST', '/upload', true);
|
||||
xhr.send(new FormData(fileInput.form));
|
||||
}
|
||||
},
|
||||
handleFeePrefChange: function(feeEnabled) {
|
||||
this.setState({
|
||||
isFee: feeEnabled
|
||||
});
|
||||
},
|
||||
componentDidUpdate: function() {
|
||||
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
|
||||
// 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.
|
||||
|
||||
if (!this.state.uploaded) {
|
||||
// Still uploading
|
||||
this.refs.progress.setAttribute('value', this.state.uploadProgress);
|
||||
} else {
|
||||
// Fully uploaded and waiting for server to finish processing, so set progress bar to "indeterminite"
|
||||
this.refs.progress.removeAttribute('value');
|
||||
}
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<main className="page" ref="page">
|
||||
<SubPageLogo />
|
||||
<h1>Publish Content</h1>
|
||||
<section className="section-block">
|
||||
<h4>LBRY Name</h4>
|
||||
lbry://<FormField type="text" ref="name" onChange={this.handleNameChange} />
|
||||
{
|
||||
(!this.state.name ? '' :
|
||||
(this.state.nameResolved ? <em> This name is currently claimed for <strong>{lbry.formatCredits(this.state.claimValue)}</strong> credits</em>
|
||||
: <em> This name is available</em>))
|
||||
}
|
||||
<div className="help">What LBRY name would you like to claim for this file?</div>
|
||||
</section>
|
||||
|
||||
<section className="section-block">
|
||||
<h4>Choose File</h4>
|
||||
<form>
|
||||
<FormField name="file" ref="file" type="file" onChange={this.handleFileChange} />
|
||||
{ !this.state.fileInfo ? '' :
|
||||
(!this.state.tempFileReady ? <div>
|
||||
<progress ref='progress'></progress>
|
||||
{!this.state.uploaded ? <span> Importing file into LBRY...</span> : <span> Processing file...</span>}
|
||||
</div>
|
||||
: <div>File ready for publishing!</div>) }
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section className="section-block">
|
||||
<h4>Bid Amount</h4>
|
||||
Credits <FormField ref="bid" style={publishNumberStyle} type="text" onChange={this.handleBidChange} value={this.state.bid} placeholder={this.state.nameResolved ? lbry.formatCredits(this.state.claimValue + 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.</span>
|
||||
: <span> You must bid over <strong>{lbry.formatCredits(this.state.claimValue)}</strong> credits to claim this name.</span> }
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section-block">
|
||||
<h4>Fee</h4>
|
||||
<div className="spacer-bottom--sm">
|
||||
<label>
|
||||
<FormField type="radio" onChange={ () => { this.handleFeePrefChange(false) } } checked={!this.state.isFee} /> No fee
|
||||
</label>
|
||||
<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">dollars</option>
|
||||
<option value="LBC">LBRY credits</option>
|
||||
</FormField>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<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.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section className="section-block">
|
||||
<h4>Your Content</h4>
|
||||
|
||||
<label htmlFor="title">Title</label><FormField type="text" ref="meta_title" name="title" placeholder="My Show, Episode 1" style={publishFieldStyle} />
|
||||
<label htmlFor="author">Author</label><FormField type="text" ref="meta_author" name="author" placeholder="My Company, Inc." style={publishFieldStyle} />
|
||||
<label htmlFor="license">License info</label><FormField type="text" ref="meta_license" name="license" defaultValue="Creative Commons Attribution 3.0 United States" style={publishFieldStyle} />
|
||||
<label htmlFor="language">Language</label> <FormField type="select" ref="meta_language" name="language">
|
||||
<option value="en" selected>English</option>
|
||||
<option value="zh">Chinese</option>
|
||||
<option value="fr">French</option>
|
||||
<option value="de">German</option>
|
||||
<option value="jp">Japanese</option>
|
||||
<option value="ru">Russian</option>
|
||||
<option value="es">Spanish</option>
|
||||
</FormField>
|
||||
|
||||
<label htmlFor="description">Description</label> <FormField type="textarea" ref="meta_description" name="description" placeholder="Description of your content" style={publishFieldStyle} />
|
||||
|
||||
<div><label><FormField type="checkbox" ref="meta_nsfw" name="nsfw" placeholder="Description of your content" /> Not Safe For Work</label></div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<section className="section-block">
|
||||
<h4>Additional Content Information (Optional)</h4>
|
||||
<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} />
|
||||
<label htmlFor="meta_license_url">License URL</label> <FormField type="text" ref="meta_license_url" name="license_url" defaultValue="https://creativecommons.org/licenses/by/3.0/us/legalcode" style={publishFieldStyle} />
|
||||
</section>
|
||||
|
||||
<div className="footer-buttons">
|
||||
<Link button="alt" href="/" label="Cancel"/>
|
||||
<Link button="primary" label={!this.state.submitting ? 'Publish' : 'Publishing...'} onClick={this.handleSubmit} disabled={this.state.submitting} />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -5,7 +5,7 @@ $spacing-vertical: 24px;
|
|||
$color-primary: #155B4A;
|
||||
$color-light-alt: hsl(hue($color-primary), 15, 85);
|
||||
$color-text-dark: #000;
|
||||
$color-help: #666;
|
||||
$color-help: rgba(0,0,0,.6);
|
||||
$color-money: #216C2A;
|
||||
$color-meta-light: #505050;
|
||||
|
||||
|
@ -106,6 +106,12 @@ $max-text-width: 660px;
|
|||
background-size: $size;
|
||||
}
|
||||
|
||||
@mixin placeholder {
|
||||
&::-webkit-input-placeholder {@content}
|
||||
&:-moz-placeholder {@content}
|
||||
&:-ms-input-placeholder {@content}
|
||||
}
|
||||
|
||||
@mixin offscreen() {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
|
|
|
@ -119,6 +119,9 @@ input[type="search"]
|
|||
|
||||
input[type="text"], textarea
|
||||
{
|
||||
@include placeholder {
|
||||
color: lighten($color-text-dark, 60%);
|
||||
}
|
||||
border: 0 none;
|
||||
border: 2px solid rgba(160,160,160,.5);
|
||||
padding-left: 5px;
|
||||
|
|
Loading…
Reference in a new issue