From 284ab8a01af4bc802e8d9fe5ab405582d2d6f4b7 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 10 Jul 2017 21:49:12 +0700 Subject: [PATCH 01/26] Move fetching my channels into redux --- ui/js/actions/content.js | 17 ++++++++++ ui/js/constants/action_types.js | 4 +++ ui/js/page/publish/index.js | 11 ++++-- ui/js/page/publish/view.jsx | 59 +++++++++++++++++---------------- ui/js/reducers/claims.js | 35 ++++++++++--------- ui/js/selectors/claims.js | 18 ++++++++++ 6 files changed, 99 insertions(+), 45 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index b80f8fc0c..d9566b091 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -339,3 +339,20 @@ export function doFetchClaimListMine() { }); }; } + +export function doFetchChannelListMine() { + return function(dispatch, getState) { + dispatch({ + type: types.FETCH_CHANNEL_LIST_MINE_STARTED, + }); + + const callback = channels => { + dispatch({ + type: types.FETCH_CHANNEL_LIST_MINE_COMPLETED, + data: { claims: channels }, + }); + }; + + lbry.channel_list_mine().then(callback); + }; +} diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 216c84762..e278bac3d 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -64,6 +64,10 @@ export const FETCH_AVAILABILITY_COMPLETED = "FETCH_AVAILABILITY_COMPLETED"; export const FILE_DELETE = "FILE_DELETE"; export const ABANDON_CLAIM_STARTED = "ABANDON_CLAIM_STARTED"; export const ABANDON_CLAIM_COMPLETED = "ABANDON_CLAIM_COMPLETED"; +export const FETCH_CHANNEL_LIST_MINE_STARTED = + "FETCH_CHANNEL_LIST_MINE_STARTED"; +export const FETCH_CHANNEL_LIST_MINE_COMPLETED = + "FETCH_CHANNEL_LIST_MINE_COMPLETED"; // Search export const SEARCH_STARTED = "SEARCH_STARTED"; diff --git a/ui/js/page/publish/index.js b/ui/js/page/publish/index.js index fac4419c1..bfe1f06e0 100644 --- a/ui/js/page/publish/index.js +++ b/ui/js/page/publish/index.js @@ -2,13 +2,19 @@ import React from "react"; import { connect } from "react-redux"; import { doNavigate, doHistoryBack } from "actions/app"; import { doClaimRewardType } from "actions/rewards"; -import { selectMyClaims } from "selectors/claims"; -import { doFetchClaimListMine } from "actions/content"; +import { + selectMyClaims, + selectFetchingMyChannels, + selectMyChannelClaims, +} from "selectors/claims"; +import { doFetchClaimListMine, doFetchChannelListMine } from "actions/content"; import rewards from "rewards"; import PublishPage from "./view"; const select = state => ({ myClaims: selectMyClaims(state), + fetchingChannels: selectFetchingMyChannels(state), + channels: selectMyChannelClaims(state), }); const perform = dispatch => ({ @@ -17,6 +23,7 @@ const perform = dispatch => ({ fetchClaimListMine: () => dispatch(doFetchClaimListMine()), claimFirstChannelReward: () => dispatch(doClaimRewardType(rewards.TYPE_FIRST_CHANNEL)), + fetchChannelListMine: () => dispatch(doFetchChannelListMine()), }); export default connect(select, perform)(PublishPage); diff --git a/ui/js/page/publish/view.jsx b/ui/js/page/publish/view.jsx index 0693d61cf..cb9e938c8 100644 --- a/ui/js/page/publish/view.jsx +++ b/ui/js/page/publish/view.jsx @@ -5,6 +5,7 @@ import { FormField, FormRow } from "component/form.js"; import Link from "component/link"; import rewards from "rewards"; import Modal from "component/modal"; +import { BusyMessage } from "component/common"; class PublishPage extends React.PureComponent { constructor(props) { @@ -13,7 +14,6 @@ class PublishPage extends React.PureComponent { this._requiredFields = ["meta_title", "name", "bid", "tos_agree"]; this.state = { - channels: null, rawName: "", name: "", bid: 10, @@ -41,15 +41,18 @@ class PublishPage extends React.PureComponent { } _updateChannelList(channel) { + const { fetchingChannels, fetchChannelListMine } = this.props; + + if (!fetchingChannels) fetchChannelListMine(); // Calls API to update displayed list of channels. If a channel name is provided, will select // that channel at the same time (used immediately after creating a channel) - lbry.channel_list_mine().then(channels => { - this.props.claimFirstChannelReward(); - this.setState({ - channels: channels, - ...(channel ? { channel } : {}), - }); - }); + // lbry.channel_list_mine().then(channels => { + // this.props.claimFirstChannelReward(); + // this.setState({ + // channels: channels, + // ...(channel ? { channel } : {}), + // }); + // }); } handleSubmit(event) { @@ -465,10 +468,6 @@ class PublishPage extends React.PureComponent { } render() { - if (this.state.channels === null) { - return null; - } - const lbcInputHelp = __( "This LBC remains yours and the deposit can be undone at any time." ); @@ -729,22 +728,26 @@ class PublishPage extends React.PureComponent {
- { - this.handleChannelChange(event); - }} - value={this.state.channel} - > - - {this.state.channels.map(({ name }) => - - )} - - + {this.props.fetchingChannels + ? + : { + this.handleChannelChange(event); + }} + value={this.state.channel} + > + + {this.props.channels.map(({ name }) => + + )} + + }
{this.state.channel == "new" ?
diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 7417bc2fb..f71e2cadd 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -50,21 +50,26 @@ reducers[types.FETCH_CLAIM_LIST_MINE_COMPLETED] = function(state, action) { }); }; -// reducers[types.FETCH_CHANNEL_CLAIMS_STARTED] = function(state, action) { -// const { -// uri, -// } = action.data -// -// const newClaims = Object.assign({}, state.claimsByChannel) -// -// if (claims !== undefined) { -// newClaims[uri] = claims -// } -// -// return Object.assign({}, state, { -// claimsByChannel: newClaims -// }) -// } +reducers[types.FETCH_CHANNEL_LIST_MINE_STARTED] = function(state, action) { + return Object.assign({}, state, { fetchingMyChannels: true }); +}; + +reducers[types.FETCH_CHANNEL_LIST_MINE_COMPLETED] = function(state, action) { + const { claims } = action.data; + const myChannelClaims = new Set(state.myChannelClaims); + const byId = Object.assign({}, state.byId); + + claims.forEach(claim => { + myChannelClaims.add(claim.claim_id); + byId[claims.claim_id] = claim; + }); + + return Object.assign({}, state, { + byId, + fetchingMyChannels: false, + myChannelClaims, + }); +}; reducers[types.FETCH_CHANNEL_CLAIMS_COMPLETED] = function(state, action) { const { uri, claims } = action.data; diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index b966375e9..1792db453 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -124,3 +124,21 @@ export const selectMyClaimsOutpoints = createSelector( return outpoints; } ); + +export const selectFetchingMyChannels = createSelector( + _selectState, + state => !!state.fetchingMyChannels +); + +export const selectMyChannelClaims = createSelector( + _selectState, + selectClaimsById, + (state, byId) => { + const ids = state.myChannelClaims || []; + const claims = []; + + ids.forEach(id => claims.push(byId[id])); + + return claims; + } +); From e01868d29b80e8394f29c1acfa4dda0d3b264401 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 12 Jun 2017 15:01:22 +0700 Subject: [PATCH 02/26] Move claim lookup on publish page into redux --- ui/js/page/publish/index.js | 11 ++- ui/js/page/publish/view.jsx | 133 ++++++++++++++++-------------------- ui/js/selectors/claims.js | 26 +++++-- 3 files changed, 88 insertions(+), 82 deletions(-) diff --git a/ui/js/page/publish/index.js b/ui/js/page/publish/index.js index bfe1f06e0..d959b04bd 100644 --- a/ui/js/page/publish/index.js +++ b/ui/js/page/publish/index.js @@ -6,8 +6,14 @@ import { selectMyClaims, selectFetchingMyChannels, selectMyChannelClaims, + selectClaimsByUri, } from "selectors/claims"; -import { doFetchClaimListMine, doFetchChannelListMine } from "actions/content"; +import { selectResolvingUris } from "selectors/content"; +import { + doFetchClaimListMine, + doFetchChannelListMine, + doResolveUri, +} from "actions/content"; import rewards from "rewards"; import PublishPage from "./view"; @@ -15,6 +21,8 @@ const select = state => ({ myClaims: selectMyClaims(state), fetchingChannels: selectFetchingMyChannels(state), channels: selectMyChannelClaims(state), + claimsByUri: selectClaimsByUri(state), + resolvingUris: selectResolvingUris(state), }); const perform = dispatch => ({ @@ -24,6 +32,7 @@ const perform = dispatch => ({ claimFirstChannelReward: () => dispatch(doClaimRewardType(rewards.TYPE_FIRST_CHANNEL)), fetchChannelListMine: () => dispatch(doFetchChannelListMine()), + resolveUri: uri => dispatch(doResolveUri(uri)), }); export default connect(select, perform)(PublishPage); diff --git a/ui/js/page/publish/view.jsx b/ui/js/page/publish/view.jsx index cb9e938c8..a76d4eb71 100644 --- a/ui/js/page/publish/view.jsx +++ b/ui/js/page/publish/view.jsx @@ -23,9 +23,6 @@ class PublishPage extends React.PureComponent { channel: "anonymous", newChannelName: "@", newChannelBid: 10, - nameResolved: null, - myClaimExists: null, - topClaimValue: 0.0, myClaimValue: 0.0, myClaimMetadata: null, copyrightNotice: "", @@ -44,15 +41,6 @@ class PublishPage extends React.PureComponent { const { fetchingChannels, fetchChannelListMine } = this.props; if (!fetchingChannels) fetchChannelListMine(); - // Calls API to update displayed list of channels. If a channel name is provided, will select - // that channel at the same time (used immediately after creating a channel) - // lbry.channel_list_mine().then(channels => { - // this.props.claimFirstChannelReward(); - // this.setState({ - // channels: channels, - // ...(channel ? { channel } : {}), - // }); - // }); } handleSubmit(event) { @@ -65,7 +53,7 @@ class PublishPage extends React.PureComponent { }); let checkFields = this._requiredFields; - if (!this.state.myClaimExists) { + if (!this.myClaimExists()) { checkFields.unshift("file"); } @@ -182,6 +170,49 @@ class PublishPage extends React.PureComponent { }); } + claim() { + const { claimsByUri } = this.props; + const { uri } = this.state; + + return claimsByUri[uri]; + } + + topClaimValue() { + if (!this.claim()) return null; + + return parseFloat(this.claim().amount); + } + + myClaimExists() { + const { myClaims } = this.props; + const { name } = this.state; + + if (!name) return false; + + return !!myClaims.find(claim => claim.name === name); + } + + topClaimIsMine() { + const myClaimInfo = this.myClaimInfo(); + const { claimsByUri } = this.props; + const { uri } = this.state; + + if (!uri) return null; + + const claim = claimsByUri[uri]; + + if (!claim) return true; + if (!myClaimInfo) return false; + + return myClaimInfo.amount >= claimInfo.amount; + } + + myClaimInfo() { + return Object.values(this.props.myClaims).find( + claim => claim.name === name + ); + } + handleNameChange(event) { var rawName = event.target.value; @@ -189,7 +220,7 @@ class PublishPage extends React.PureComponent { this.setState({ rawName: "", name: "", - nameResolved: false, + uri: "", }); return; @@ -203,61 +234,14 @@ class PublishPage extends React.PureComponent { } const name = rawName.toLowerCase(); + const uri = lbryuri.normalize(name); this.setState({ rawName: rawName, name: name, - nameResolved: null, - myClaimExists: null, + uri, }); - const myClaimInfo = Object.values(this.props.myClaims).find( - claim => claim.name === name - ); - - this.setState({ - myClaimExists: !!myClaimInfo, - }); - lbry.resolve({ uri: name }).then( - claimInfo => { - if (name != this.state.name) { - return; - } - - if (!claimInfo) { - this.setState({ - nameResolved: false, - }); - } else { - const topClaimIsMine = - myClaimInfo && myClaimInfo.amount >= claimInfo.amount; - const newState = { - nameResolved: true, - topClaimValue: parseFloat(claimInfo.amount), - myClaimExists: !!myClaimInfo, - myClaimValue: myClaimInfo ? parseFloat(myClaimInfo.amount) : null, - myClaimMetadata: myClaimInfo ? myClaimInfo.value : null, - 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); - } - }, - () => { - // Assume an error means the name is available - this.setState({ - name: name, - nameResolved: false, - myClaimExists: false, - }); - } - ); + this.props.resolveUri(uri); } handleBidChange(event) { @@ -427,11 +411,16 @@ class PublishPage extends React.PureComponent { } getNameBidHelpText() { - if (!this.state.name) { + if ( + this.state.uri && + this.props.resolvingUris.indexOf(this.state.uri) !== -1 + ) { + return ; + } else if (!this.state.name) { return __("Select a URL for this publish."); - } else if (this.state.nameResolved === false) { + } else if (!this.claim()) { return __("This URL is unused."); - } else if (this.state.myClaimExists) { + } else if (this.myClaimExists()) { return __( "You have already used this URL. Publishing to it again will update your previous publish." ); @@ -496,7 +485,7 @@ class PublishPage extends React.PureComponent { this.onFileChange(event); }} helper={ - this.state.myClaimExists + this.myClaimExists() ? __( "If you don't choose a file, the file from your existing claim will be used." ) @@ -829,11 +818,7 @@ class PublishPage extends React.PureComponent { this.handleBidChange(event); }} value={this.state.bid} - placeholder={ - this.state.nameResolved - ? this.state.topClaimValue + 10 - : 100 - } + placeholder={this.claim() ? this.topClaimValue() + 10 : 100} helper={lbcInputHelp} />
@@ -898,7 +883,7 @@ class PublishPage extends React.PureComponent { >

{__("Your file has been published to LBRY at the address")} - {" "}lbry://{this.state.name}! + {" "}{this.state.uri}!

{__( diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index 1792db453..3478c9fc4 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -105,21 +105,33 @@ export const selectClaimListMineIsPending = createSelector( state => state.isClaimListMinePending ); -export const selectMyClaims = createSelector( +export const selectMyClaimsRaw = createSelector( _selectState, state => new Set(state.myClaims) ); +export const selectMyClaims = createSelector( + selectMyClaimsRaw, + selectClaimsById, + (myClaimIds, byId) => { + const claims = []; + + myClaimIds.forEach(id => { + const claim = byId[id]; + + if (claim) claims.push(claim); + }); + + return claims; + } +); + export const selectMyClaimsOutpoints = createSelector( selectMyClaims, - selectClaimsById, - (claimIds, byId) => { + myClaims => { const outpoints = []; - claimIds.forEach(claimId => { - const claim = byId[claimId]; - if (claim) outpoints.push(`${claim.txid}:${claim.nout}`); - }); + myClaims.forEach(claim => outpoints.push(`${claim.txid}:${claim.nout}`)); return outpoints; } From df954882bc9484b85eb0fa4033918281669b0371 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 16 Jun 2017 10:06:01 +0700 Subject: [PATCH 03/26] Cache channel claims --- ui/js/store.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/js/store.js b/ui/js/store.js index 0ec06c017..1bb5ec058 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -90,6 +90,7 @@ const saveClaimsFilter = createFilter("claims", [ "byId", "claimsByUri", "myClaims", + "myChannelClaims", ]); const persistOptions = { From 8325828f6e18d63fd3cdb04924a196ab983ed066 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sun, 18 Jun 2017 00:59:18 +0700 Subject: [PATCH 04/26] Progress towards working publish --- ui/js/actions/content.js | 90 +++ ui/js/component/common.js | 34 ++ ui/js/constants/action_types.js | 5 + ui/js/lbryuri.js | 2 + ui/js/page/publish/index.js | 4 + ui/js/page/publish/view.jsx | 344 ++++++++---- ui/js/reducers/claims.js | 36 +- ui/js/reducers/file_info.js | 59 +- ui/js/selectors/file_info.js | 19 +- ui/js/store.js | 8 +- ui/yarn.lock | 957 +++++++++++++++++++++++++++++++- 11 files changed, 1399 insertions(+), 159 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index d9566b091..ca6f1850d 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -15,6 +15,7 @@ import { selectBadgeNumber } from "selectors/app"; import { selectTotalDownloadProgress } from "selectors/file_info"; import setBadge from "util/setBadge"; import setProgressBar from "util/setProgressBar"; +import { doFileList } from "actions/file_info"; import batchActions from "util/batchActions"; const { ipcRenderer } = require("electron"); @@ -356,3 +357,92 @@ export function doFetchChannelListMine() { lbry.channel_list_mine().then(callback); }; } + +export function doCreateChannel(name, amount) { + return function(dispatch, getState) { + dispatch({ + type: types.CREATE_CHANNEL_STARTED, + }); + + return new Promise((resolve, reject) => { + lbry + .channel_new({ + channel_name: name, + amount: parseFloat(amount), + }) + .then( + channelClaim => { + channelClaim.name = name; + dispatch({ + type: types.CREATE_CHANNEL_COMPLETED, + data: { channelClaim }, + }); + resolve(channelClaim); + }, + err => { + resolve(err); + } + ); + }); + }; +} + +export function doPublish(params) { + return function(dispatch, getState) { + let uri; + const { name, channel_name } = params; + if (channel_name) { + uri = lbryuri.build({ name: channel_name, path: name }, false); + } else { + uri = lbryuri.build({ name: name }, false); + } + const pendingPublish = { + name, + channel_name, + claim_id: "pending_claim_" + uri, + txid: "pending_" + uri, + nout: 0, + outpoint: "pending_" + uri + ":0", + time: Date.now(), + }; + + dispatch({ + type: types.PUBLISH_STARTED, + data: { + params, + pendingPublish, + }, + }); + + return new Promise((resolve, reject) => { + const success = claim => { + claim.name = params.name; + claim.channel_name = params.channel_name; + dispatch({ + type: types.PUBLISH_COMPLETED, + data: { + claim, + uri, + pendingPublish, + }, + }); + dispatch(doFileList()); + resolve(claim); + }; + const failure = error => { + dispatch({ + type: types.PUBLISH_FAILED, + data: { + error, + params, + uri, + pendingPublish, + }, + }); + reject(error); + }; + + lbry.publish(params).then(success, failure); + }); + }; +} diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 38dbf83fd..1b48bc4df 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -42,6 +42,40 @@ export class TruncatedText extends React.PureComponent { } } +export class TruncatedMarkdown extends React.PureComponent { + static propTypes = { + lines: React.PropTypes.number, + }; + + static defaultProps = { + lines: null, + }; + + transformMarkdown(text) { + // render markdown to html string then trim html tag + let htmlString = ReactDOMServer.renderToStaticMarkup( + + ); + var txt = document.createElement("textarea"); + txt.innerHTML = htmlString; + return txt.value.replace(/<(?:.|\n)*?>/gm, ""); + } + + render() { + let content = this.props.children && typeof this.props.children === "string" + ? this.transformMarkdown(this.props.children) + : this.props.children; + return ( + + {content} + + ); + } +} + export class BusyMessage extends React.PureComponent { static propTypes = { message: React.PropTypes.string, diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index e278bac3d..457761441 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -68,6 +68,11 @@ export const FETCH_CHANNEL_LIST_MINE_STARTED = "FETCH_CHANNEL_LIST_MINE_STARTED"; export const FETCH_CHANNEL_LIST_MINE_COMPLETED = "FETCH_CHANNEL_LIST_MINE_COMPLETED"; +export const CREATE_CHANNEL_STARTED = "CREATE_CHANNEL_STARTED"; +export const CREATE_CHANNEL_COMPLETED = "CREATE_CHANNEL_COMPLETED"; +export const PUBLISH_STARTED = "PUBLISH_STARTED"; +export const PUBLISH_COMPLETED = "PUBLISH_COMPLETED"; +export const PUBLISH_FAILED = "PUBLISH_FAILED"; // Search export const SEARCH_STARTED = "SEARCH_STARTED"; diff --git a/ui/js/lbryuri.js b/ui/js/lbryuri.js index 1fcfdec58..42a825949 100644 --- a/ui/js/lbryuri.js +++ b/ui/js/lbryuri.js @@ -203,6 +203,8 @@ lbryuri.build = function(uriObj, includeProto = true, allowExtraProps = false) { /* Takes a parseable LBRY URI and converts it to standard, canonical format (currently this just * consists of adding the lbry:// prefix if needed) */ lbryuri.normalize = function(uri) { + if (uri.match(/pending_claim/)) return uri; + const { name, path, bidPosition, claimSequence, claimId } = lbryuri.parse( uri ); diff --git a/ui/js/page/publish/index.js b/ui/js/page/publish/index.js index d959b04bd..f296f1687 100644 --- a/ui/js/page/publish/index.js +++ b/ui/js/page/publish/index.js @@ -13,6 +13,8 @@ import { doFetchClaimListMine, doFetchChannelListMine, doResolveUri, + doCreateChannel, + doPublish, } from "actions/content"; import rewards from "rewards"; import PublishPage from "./view"; @@ -33,6 +35,8 @@ const perform = dispatch => ({ dispatch(doClaimRewardType(rewards.TYPE_FIRST_CHANNEL)), fetchChannelListMine: () => dispatch(doFetchChannelListMine()), resolveUri: uri => dispatch(doResolveUri(uri)), + createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)), + publish: params => dispatch(doPublish(params)), }); export default connect(select, perform)(PublishPage); diff --git a/ui/js/page/publish/view.jsx b/ui/js/page/publish/view.jsx index a76d4eb71..12b38cbd7 100644 --- a/ui/js/page/publish/view.jsx +++ b/ui/js/page/publish/view.jsx @@ -125,16 +125,11 @@ class PublishPage extends React.PureComponent { publishArgs.file_path = this.refs.file.getValue(); } - lbry.publishDeprecated( - publishArgs, - message => { - this.handlePublishStarted(); - }, - null, - error => { - this.handlePublishError(error); - } - ); + const success = claim => {}; + const failure = error => this.handlePublishError(error); + + this.handlePublishStarted(); + this.props.publish(publishArgs).then(success, failure); }; if (this.state.isFee) { @@ -216,6 +211,10 @@ class PublishPage extends React.PureComponent { handleNameChange(event) { var rawName = event.target.value; + this.nameChanged(rawName); + } + + nameChanged(rawName) { if (!rawName) { this.setState({ rawName: "", @@ -233,15 +232,26 @@ class PublishPage extends React.PureComponent { return; } + let channel = ""; + if (this.state.channel !== "anonymous") channel = this.state.channel; + const name = rawName.toLowerCase(); - const uri = lbryuri.normalize(name); + const uri = lbryuri.build({ contentName: name, channelName: channel }); this.setState({ rawName: rawName, name: name, uri, }); - this.props.resolveUri(uri); + if (this.resolveUriTimeout) { + clearTimeout(this.resolveUriTimeout); + this.resolveUriTimeout = undefined; + } + const resolve = () => this.props.resolveUri(uri); + + this.resolveUriTimeout = setTimeout(resolve.bind(this), 500, { + once: true, + }); } handleBidChange(event) { @@ -302,40 +312,12 @@ class PublishPage extends React.PureComponent { }); } - handleChannelChange(event) { - const channel = event.target.value; - + handleChannelChange(channelName) { this.setState({ - channel: channel, - }); - } - - handleNewChannelNameChange(event) { - const newChannelName = event.target.value.startsWith("@") - ? event.target.value - : "@" + event.target.value; - - if ( - newChannelName.length > 1 && - !lbryuri.isValidName(newChannelName.substr(1), false) - ) { - this.refs.newChannelName.showError( - __("LBRY channel names must contain only letters, numbers and dashes.") - ); - return; - } else { - this.refs.newChannelName.clearError(); - } - - this.setState({ - newChannelName: newChannelName, - }); - } - - handleNewChannelBidChange(event) { - this.setState({ - newChannelBid: event.target.value, + channel: channelName, }); + const nameChanged = () => this.nameChanged(this.state.rawName); + setTimeout(nameChanged.bind(this), 500, { once: true }); } handleTOSChange(event) { @@ -413,19 +395,26 @@ class PublishPage extends React.PureComponent { getNameBidHelpText() { if ( this.state.uri && - this.props.resolvingUris.indexOf(this.state.uri) !== -1 + this.props.resolvingUris.indexOf(this.state.uri) !== -1 && + this.claim() === undefined ) { return ; } else if (!this.state.name) { return __("Select a URL for this publish."); } else if (!this.claim()) { return __("This URL is unused."); - } else if (this.myClaimExists()) { - return __( - "You have already used this URL. Publishing to it again will update your previous publish." + } else if (this.myClaimExists() && !this.state.prefillDone) { + return ( + + {__("You already have a claim with this name.")}{" "} + this.handlePrefillClicked()} + /> + ); - } else if (this.state.topClaimValue) { - if (this.state.topClaimValue === 1) { + } else if (this.claim()) { + if (this.topClaimValue() === 1) { return ( {__( @@ -439,7 +428,7 @@ class PublishPage extends React.PureComponent { {__( 'A deposit of at least "%s" credits is required to win "%s". However, you can still get a permanent URL for any amount.', - this.state.topClaimValue, + this.topClaimValue(), this.state.name )} @@ -709,77 +698,11 @@ class PublishPage extends React.PureComponent { -

-
-

{__("Identity")}

-
- {__("Who created this content?")} -
-
-
- {this.props.fetchingChannels - ? - : { - this.handleChannelChange(event); - }} - value={this.state.channel} - > - - {this.props.channels.map(({ name }) => - - )} - - } -
- {this.state.channel == "new" - ?
- { - this.handleNewChannelNameChange(event); - }} - ref={newChannelName => { - this.refs.newChannelName = newChannelName; - }} - value={this.state.newChannelName} - /> - { - this.handleNewChannelBidChange(event); - }} - value={this.state.newChannelBid} - /> -
- { - this.handleCreateChannelClick(event); - }} - disabled={this.state.creatingChannel} - /> -
-
- : null} -
+
@@ -795,7 +718,9 @@ class PublishPage extends React.PureComponent {
1 && + !lbryuri.isValidName(newChannelName.substr(1), false) + ) { + this.refs.newChannelName.showError( + __("LBRY channel names must contain only letters, numbers and dashes.") + ); + return; + } else { + this.refs.newChannelName.clearError(); + } + + this.setState({ + newChannelName, + }); + } + + handleNewChannelBidChange(event) { + this.setState({ + newChannelBid: event.target.value, + }); + } + + handleCreateChannelClick(event) { + if (this.state.newChannelName.length < 5) { + this.refs.newChannelName.showError( + __("LBRY channel names must be at least 4 characters in length.") + ); + return; + } + + this.setState({ + creatingChannel: true, + }); + + const newChannelName = this.state.newChannelName; + const amount = parseFloat(this.state.newChannelBid); + this.setState({ + creatingChannel: true, + }); + const success = (() => { + this.setState({ + creatingChannel: false, + addingChannel: false, + channel: newChannelName, + }); + this.props.handleChannelChange(newChannelName); + }).bind(this); + const failure = (err => { + this.setState({ + creatingChannel: false, + }); + this.refs.newChannelName.showError( + __("Unable to create channel due to an internal error.") + ); + }).bind(this); + this.props.createChannel(newChannelName, amount).then(success, failure); + } + + render() { + const lbcInputHelp = __( + "This LBC remains yours and the deposit can be undone at any time." + ); + + const { fetchingChannels, channels } = this.props; + + let channelContent = []; + if (channels.length > 0) { + channelContent.push( + + + {this.props.channels.map(({ name }) => + + )} + + + ); + if (fetchingChannels) { + channelContent.push( + + ); + } + } else if (fetchingChannels) { + channelContent.push( + + ); + } + + return ( +
+
+

{__("Identity")}

+
+ {__("Who created this content?")} +
+
+
+ {channelContent} +
+ {this.state.addingChannel && +
+ { + this.handleNewChannelNameChange(event); + }} + value={this.state.newChannelName} + /> + +
+ +
+
} +
+ ); + } +} + export default PublishPage; diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index f71e2cadd..75bfc4a52 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -15,7 +15,13 @@ reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { byUri[uri] = claim.claim_id; } else if (claim === undefined && certificate !== undefined) { byId[certificate.claim_id] = certificate; - byUri[uri] = certificate.claim_id; + // Don't point URI at the channel certificate unless it actually is + // a channel URI. This is brittle. + if (!uri.split(certificate.name)[1].match(/\//)) { + byUri[uri] = certificate.claim_id; + } else { + byUri[uri] = null; + } } else { byUri[uri] = null; } @@ -108,6 +114,34 @@ reducers[types.ABANDON_CLAIM_COMPLETED] = function(state, action) { }); }; +reducers[types.CREATE_CHANNEL_COMPLETED] = function(state, action) { + const { channelClaim } = action.data; + const byId = Object.assign({}, state.byId); + const myChannelClaims = new Set(state.myChannelClaims); + + byId[channelClaim.claim_id] = channelClaim; + myChannelClaims.add(channelClaim.claim_id); + + return Object.assign({}, state, { + byId, + myChannelClaims, + }); +}; + +reducers[types.PUBLISH_COMPLETED] = function(state, action) { + const { claim } = action.data; + const byId = Object.assign({}, state.byId); + const myClaims = new Set(state.myClaims); + + byId[claim.claim_id] = claim; + myClaims.add(claim.claim_id); + + return Object.assign({}, state, { + byId, + myClaims, + }); +}; + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/reducers/file_info.js b/ui/js/reducers/file_info.js index 0f6b7a63d..fe6979045 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -12,8 +12,9 @@ reducers[types.FILE_LIST_STARTED] = function(state, action) { reducers[types.FILE_LIST_COMPLETED] = function(state, action) { const { fileInfos } = action.data; - const newByOutpoint = Object.assign({}, state.byOutpoint); + const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); + fileInfos.forEach(fileInfo => { const { outpoint } = fileInfo; @@ -23,6 +24,7 @@ reducers[types.FILE_LIST_COMPLETED] = function(state, action) { return Object.assign({}, state, { isFileListPending: false, byOutpoint: newByOutpoint, + pendingByOutpoint, }); }; @@ -136,6 +138,61 @@ reducers[types.LOADING_VIDEO_FAILED] = function(state, action) { }); }; +reducers[types.PUBLISH_STARTED] = function(state, action) { + const { pendingPublish } = action.data; + const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); + + pendingByOutpoint[pendingPublish.outpoint] = pendingPublish; + + return Object.assign({}, state, { + pendingByOutpoint, + }); +}; + +reducers[types.PUBLISH_COMPLETED] = function(state, action) { + const { pendingPublish } = action.data; + const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); + + delete pendingByOutpoint[pendingPublish.outpoint]; + + return Object.assign({}, state, { + pendingByOutpoint, + }); +}; + +reducers[types.PUBLISH_FAILED] = function(state, action) { + const { pendingPublish } = action.data; + const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); + + delete pendingByOutpoint[pendingPublish.outpoint]; + + return Object.assign({}, state, { + pendingByOutpoint, + }); +}; + +// reducers[types.PUBLISH_COMPLETED] = function(state, action) { +// const { claim } = action.data; +// const uri = lbryuri.build({ +// txid: claim.txId +// }) +// const newPendingPublish = { +// name, +// channel_name, +// claim_id: "pending_claim_" + uri, +// txid: "pending_" + uri, +// nout: 0, +// outpoint: "pending_" + uri + ":0", +// time: Date.now(), +// }; +// const fileInfos = Object.assign({}, state.fileInfos) +// fileInfos[newPendingPublish.outpoint] = newPendingPublish + +// return Object.assign({}, state, { +// fileInfos, +// }) +// } + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index 552368f7e..5b0e4941b 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -69,6 +69,11 @@ export const makeSelectLoadingForUri = () => { return createSelector(selectLoadingForUri, loading => !!loading); }; +export const selectFileInfosPendingPublish = createSelector( + _selectState, + state => Object.values(state.pendingByOutpoint || {}) +); + export const selectFileInfosDownloaded = createSelector( selectFileInfosByOutpoint, selectMyClaimsOutpoints, @@ -87,24 +92,17 @@ export const selectFileInfosDownloaded = createSelector( } ); -export const selectFileInfosPendingPublish = createSelector( - _selectState, - state => { - return lbry.getPendingPublishes(); - } -); - export const selectFileInfosPublished = createSelector( selectFileInfosByOutpoint, - selectFileInfosPendingPublish, selectMyClaimsOutpoints, - (byOutpoint, pendingFileInfos, outpoints) => { + selectFileInfosPendingPublish, + (byOutpoint, outpoints, pendingPublish) => { const fileInfos = []; outpoints.forEach(outpoint => { const fileInfo = byOutpoint[outpoint]; if (fileInfo) fileInfos.push(fileInfo); }); - return [...fileInfos, ...pendingFileInfos]; + return fileInfos; } ); @@ -133,7 +131,6 @@ export const selectFileInfosByUri = createSelector( if (fileInfo) fileInfos[uri] = fileInfo; } }); - return fileInfos; } ); diff --git a/ui/js/store.js b/ui/js/store.js index 1bb5ec058..8e6c11949 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -92,12 +92,16 @@ const saveClaimsFilter = createFilter("claims", [ "myClaims", "myChannelClaims", ]); +const saveFileInfosFilter = createFilter("fileInfo", [ + "fileInfos", + "pendingByOutpoint", +]); const persistOptions = { - whitelist: ["claims"], + whitelist: ["claims", "fileInfo"], // Order is important. Needs to be compressed last or other transforms can't // read the data - transforms: [saveClaimsFilter, compressor], + transforms: [saveClaimsFilter, saveFileInfosFilter, compressor], debounce: 1000, storage: localForage, }; diff --git a/ui/yarn.lock b/ui/yarn.lock index 78dcb8080..cb2ca85fb 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -52,6 +52,15 @@ ajv@^4.7.0, ajv@^4.9.1: co "^4.6.0" json-stable-stringify "^1.0.1" +ajv@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.0.tgz#c1735024c5da2ef75cc190713073d44f098bf486" + dependencies: + co "^4.6.0" + fast-deep-equal "^0.1.0" + json-schema-traverse "^0.3.0" + json-stable-stringify "^1.0.1" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -60,6 +69,10 @@ align-text@^0.1.1, align-text@^0.1.3: longest "^1.0.1" repeat-string "^1.5.2" +alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -84,6 +97,10 @@ ansicolors@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.2.1.tgz#be089599097b74a5c9c4a84a0cdbcdb62bd87aef" +any-promise@^1.0.0, any-promise@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + anymatch@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" @@ -193,6 +210,10 @@ ast-types@0.8.15: version "0.8.15" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.8.15.tgz#8eef0827f04dff0ec8857ba925abe3fea6194e52" +ast-types@0.9.6: + version "0.9.6" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" + async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" @@ -223,6 +244,17 @@ asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" +autoprefixer@^6.3.1: + version "6.7.7" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" + dependencies: + browserslist "^1.7.6" + caniuse-db "^1.0.30000634" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^5.2.16" + postcss-value-parser "^3.2.3" + aws-sign2@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" @@ -252,7 +284,7 @@ babel-cli@^6.24.1: optionalDependencies: chokidar "^1.6.1" -babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: +babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" dependencies: @@ -887,6 +919,10 @@ babylon@^6.11.5, babylon@^6.17.2: version "6.17.4" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a" +balanced-match@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -895,6 +931,10 @@ base62@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/base62/-/base62-0.1.1.tgz#7b4174c2f94449753b11c2651c083da841a7b084" +base62@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.0.tgz#31e7e560dc846c9f44c1a531df6514da35474157" + base64-js@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" @@ -1024,6 +1064,13 @@ browserify-zlib@^0.1.4: dependencies: pako "~0.2.0" +browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: + version "1.7.7" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" + dependencies: + caniuse-db "^1.0.30000639" + electron-to-chromium "^1.2.7" + buffer-indexof@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982" @@ -1081,6 +1128,19 @@ camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" +caniuse-api@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" + dependencies: + browserslist "^1.3.6" + caniuse-db "^1.0.30000529" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: + version "1.0.30000694" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000694.tgz#02009f4f82d2f0126e4c691b7cd5adb351935c01" + cardinal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-1.0.0.tgz#50e21c1b0aa37729f9377def196b5a9cec932ee9" @@ -1138,16 +1198,32 @@ circular-json@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" +clap@^1.0.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.0.tgz#59c90fe3e137104746ff19469a27a634ff68c857" + dependencies: + chalk "^1.1.3" + cli-cursor@^1.0.1, cli-cursor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" dependencies: restore-cursor "^1.0.1" +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + cli-spinners@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" +cli-spinners@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.0.0.tgz#ef987ed3d48391ac3dab9180b406a742180d6e6a" + cli-table@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" @@ -1196,21 +1272,73 @@ co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" +coa@~1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.3.tgz#1b54a5e1dcf77c990455d4deea98c564416dc893" + dependencies: + q "^1.1.2" + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" +codemirror-spell-checker@*: + version "1.1.2" + resolved "https://registry.yarnpkg.com/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz#1c660f9089483ccb5113b9ba9ca19c3f4993371e" + dependencies: + typo-js "*" + +codemirror@*: + version "5.27.2" + resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.27.2.tgz#a292d42f079d5b98c68c3146fab99844f3d8776c" + +color-convert@^1.3.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" + dependencies: + color-name "^1.1.1" + +color-name@^1.0.0, color-name@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d" + +color-string@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" + dependencies: + color-name "^1.0.0" + +color@^0.11.0: + version "0.11.4" + resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" + dependencies: + clone "^1.0.2" + color-convert "^1.3.0" + color-string "^0.3.0" + +colormin@^1.0.5: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133" + dependencies: + color "^0.11.0" + css-color-names "0.0.4" + has "^1.0.1" + colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" +colors@^1.1.2, colors@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" dependencies: delayed-stream "~1.0.0" -commander@^2.8.1, commander@^2.9.0: +commander@^2.5.0, commander@^2.8.1, commander@^2.9.0: version "2.10.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.10.0.tgz#e1f5d3245de246d1a5ca04702fa1ad1bd7e405fe" dependencies: @@ -1220,6 +1348,38 @@ commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" +commoner@^0.10.1: + version "0.10.8" + resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" + dependencies: + commander "^2.5.0" + detective "^4.3.1" + glob "^5.0.15" + graceful-fs "^4.1.2" + iconv-lite "^0.4.5" + mkdirp "^0.5.0" + private "^0.1.6" + q "^1.1.2" + recast "^0.11.17" + +commonmark-react-renderer@^4.2.4: + version "4.3.3" + resolved "https://registry.yarnpkg.com/commonmark-react-renderer/-/commonmark-react-renderer-4.3.3.tgz#9c4bca138bc83287bae792ccf133738be9cbc6fa" + dependencies: + in-publish "^2.0.0" + lodash.assign "^4.2.0" + lodash.isplainobject "^4.0.6" + pascalcase "^0.1.1" + xss-filters "^1.2.6" + +commonmark@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/commonmark/-/commonmark-0.24.0.tgz#b80de0182c546355643aa15db12bfb282368278f" + dependencies: + entities "~ 1.1.1" + mdurl "~ 1.0.1" + string.prototype.repeat "^0.2.0" + compressible@~2.0.8: version "2.0.10" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.10.tgz#feda1c7f7617912732b29bf8cf26252a20b9eecd" @@ -1396,6 +1556,85 @@ crypto-browserify@^3.11.0: public-encrypt "^4.0.0" randombytes "^2.0.0" +css-color-names@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + +css-loader@^0.28.4: + version "0.28.4" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.4.tgz#6cf3579192ce355e8b38d5f42dd7a1f2ec898d0f" + dependencies: + babel-code-frame "^6.11.0" + css-selector-tokenizer "^0.7.0" + cssnano ">=2.6.1 <4" + icss-utils "^2.1.0" + loader-utils "^1.0.2" + lodash.camelcase "^4.3.0" + object-assign "^4.0.1" + postcss "^5.0.6" + postcss-modules-extract-imports "^1.0.0" + postcss-modules-local-by-default "^1.0.1" + postcss-modules-scope "^1.0.0" + postcss-modules-values "^1.1.0" + postcss-value-parser "^3.3.0" + source-list-map "^0.1.7" + +css-selector-tokenizer@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86" + dependencies: + cssesc "^0.1.0" + fastparse "^1.1.1" + regexpu-core "^1.0.0" + +cssesc@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" + +"cssnano@>=2.6.1 <4": + version "3.10.0" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" + dependencies: + autoprefixer "^6.3.1" + decamelize "^1.1.2" + defined "^1.0.0" + has "^1.0.1" + object-assign "^4.0.1" + postcss "^5.0.14" + postcss-calc "^5.2.0" + postcss-colormin "^2.1.8" + postcss-convert-values "^2.3.4" + postcss-discard-comments "^2.0.4" + postcss-discard-duplicates "^2.0.1" + postcss-discard-empty "^2.0.1" + postcss-discard-overridden "^0.1.1" + postcss-discard-unused "^2.2.1" + postcss-filter-plugins "^2.0.0" + postcss-merge-idents "^2.1.5" + postcss-merge-longhand "^2.0.1" + postcss-merge-rules "^2.0.3" + postcss-minify-font-values "^1.0.2" + postcss-minify-gradients "^1.0.1" + postcss-minify-params "^1.0.4" + postcss-minify-selectors "^2.0.4" + postcss-normalize-charset "^1.1.0" + postcss-normalize-url "^3.0.7" + postcss-ordered-values "^2.1.0" + postcss-reduce-idents "^2.2.2" + postcss-reduce-initial "^1.0.0" + postcss-reduce-transforms "^1.0.3" + postcss-svgo "^2.1.1" + postcss-unique-selectors "^2.0.2" + postcss-value-parser "^3.2.3" + postcss-zindex "^2.0.1" + +csso@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" + dependencies: + clap "^1.0.9" + source-map "^0.5.3" + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -1432,7 +1671,7 @@ debug@2.6.7: dependencies: ms "2.0.0" -debug@2.6.8, debug@^2.1.1, debug@^2.2.0, debug@^2.6.8: +debug@2.6.8, debug@^2.1.1, debug@^2.2.0, debug@^2.5.1, debug@^2.6.3, debug@^2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: @@ -1471,6 +1710,10 @@ define-properties@^1.1.2: foreach "^2.0.5" object-keys "^1.0.8" +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + del@^2.0.2: version "2.2.2" resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" @@ -1527,6 +1770,13 @@ detect-node@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" +detective@^4.3.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-4.5.0.tgz#6e5a8c6b26e6c7a254b1c6b6d7490d98ec91edd1" + dependencies: + acorn "^4.0.3" + defined "^1.0.0" + diffie-hellman@^5.0.0: version "5.0.2" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e" @@ -1580,6 +1830,24 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" +electron-rebuild@^1.5.11: + version "1.5.11" + resolved "https://registry.yarnpkg.com/electron-rebuild/-/electron-rebuild-1.5.11.tgz#6ea660deb546a516e7efaa81cd5985d5664f245c" + dependencies: + colors "^1.1.2" + debug "^2.6.3" + fs-promise "^2.0.2" + node-abi "^2.0.0" + node-gyp "^3.6.0" + ora "^1.2.0" + rimraf "^2.6.1" + spawn-rx "^2.0.10" + yargs "^7.0.2" + +electron-to-chromium@^1.2.7: + version "1.3.14" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.14.tgz#64af0f9efd3c3c6acd57d71f83b49ca7ee9c4b43" + elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" @@ -1637,6 +1905,17 @@ enhanced-resolve@~0.9.0: memory-fs "^0.2.0" tapable "^0.1.8" +"entities@~ 1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + +envify@^3.0.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/envify/-/envify-3.4.1.tgz#d7122329e8df1688ba771b12501917c9ce5cbce8" + dependencies: + jstransform "^11.0.3" + through "~2.3.4" + errno@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" @@ -1862,6 +2141,10 @@ espree@^3.4.0: acorn "^5.0.1" acorn-jsx "^3.0.0" +esprima-fb@^15001.1.0-dev-harmony-fb: + version "15001.1.0-dev-harmony-fb" + resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901" + esprima-fb@~15001.1001.0-dev-harmony-fb: version "15001.1001.0-dev-harmony-fb" resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz#43beb57ec26e8cf237d3dd8b33e42533577f2659" @@ -1870,7 +2153,11 @@ esprima-fb@~3001.0001.0000-dev-harmony-fb, esprima-fb@~3001.1.0-dev-harmony-fb: version "3001.1.0-dev-harmony-fb" resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz#b77d37abcd38ea0b77426bb8bc2922ce6b426411" -esprima@^3.1.1: +esprima@^2.6.0: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + +esprima@^3.1.1, esprima@~3.1.0: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" @@ -2018,10 +2305,18 @@ falafel@^1.0.1: isarray "0.0.1" object-keys "^1.0.6" +fast-deep-equal@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-0.1.0.tgz#5c6f4599aba6b333ee3342e2ed978672f1001f8d" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" +fastparse@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" + faye-websocket@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" @@ -2034,6 +2329,16 @@ faye-websocket@~0.11.0: dependencies: websocket-driver ">=0.5.1" +fbjs@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.6.1.tgz#9636b7705f5ba9684d44b72f78321254afc860f7" + dependencies: + core-js "^1.0.0" + loose-envify "^1.0.0" + promise "^7.0.3" + ua-parser-js "^0.7.9" + whatwg-fetch "^0.9.0" + fbjs@^0.8.9: version "0.8.12" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" @@ -2120,6 +2425,10 @@ flat-cache@^1.2.1: graceful-fs "^4.1.2" write "^0.2.1" +flatten@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" + for-in@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -2161,6 +2470,22 @@ from2@^2.3.0: inherits "^2.0.1" readable-stream "^2.0.0" +fs-extra@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-2.1.2.tgz#046c70163cef9aad46b0e4a7fa467fb22d71de35" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + +fs-promise@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/fs-promise/-/fs-promise-2.0.3.tgz#f64e4f854bcf689aa8bddcba268916db3db46854" + dependencies: + any-promise "^1.3.0" + fs-extra "^2.0.0" + mz "^2.6.0" + thenify-all "^1.6.0" + fs-readdir-recursive@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz#8cd1745c8b4f8a29c8caec392476921ba195f560" @@ -2263,6 +2588,16 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" +glob@^5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@~7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" @@ -2307,7 +2642,7 @@ globule@^1.0.0: lodash "~4.17.4" minimatch "~3.0.2" -graceful-fs@^4.1.2, graceful-fs@^4.1.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -2344,6 +2679,10 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -2412,6 +2751,10 @@ hpack.js@^2.1.6: readable-stream "^2.0.1" wbuf "^1.1.0" +html-comment-regex@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" + html-entities@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" @@ -2475,10 +2818,20 @@ i18n-extract@^0.4.4: gettext-parser "^1.2.0" glob "^7.1.1" -iconv-lite@~0.4.13: +iconv-lite@^0.4.5, iconv-lite@~0.4.13: version "0.4.18" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" +icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + +icss-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.1.0.tgz#83f0a0ec378bf3246178b6c2ad9136f135b1c962" + dependencies: + postcss "^6.0.1" + ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" @@ -2509,6 +2862,10 @@ indent-string@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.1.0.tgz#08ff4334603388399b329e6b9538dc7a3cf5de7d" +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" @@ -2589,6 +2946,10 @@ ipaddr.js@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -2714,6 +3075,10 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + is-posix-bracket@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" @@ -2746,6 +3111,12 @@ is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" +is-svg@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" + dependencies: + html-comment-regex "^1.1.0" + is-symbol@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" @@ -2787,7 +3158,7 @@ isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" -js-base64@^2.1.8: +js-base64@^2.1.8, js-base64@^2.1.9: version "2.1.9" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" @@ -2802,6 +3173,13 @@ js-yaml@^3.4.3, js-yaml@^3.5.1: argparse "^1.0.7" esprima "^3.1.1" +js-yaml@~3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" + dependencies: + argparse "^1.0.7" + esprima "^2.6.0" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -2822,6 +3200,10 @@ json-loader@^0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de" +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -2844,6 +3226,12 @@ json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" +jsonfile@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -2861,6 +3249,16 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.3.6" +jstransform@^11.0.3: + version "11.0.3" + resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-11.0.3.tgz#09a78993e0ae4d4ef4487f6155a91f6190cb4223" + dependencies: + base62 "^1.1.0" + commoner "^0.10.1" + esprima-fb "^15001.1.0-dev-harmony-fb" + object-assign "^2.0.0" + source-map "^0.4.2" + jstransform@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-3.0.0.tgz#a2591ab6cee8d97bf3be830dbfa2313b87cd640b" @@ -3082,6 +3480,10 @@ lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + lodash.chunk@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.chunk/-/lodash.chunk-4.2.0.tgz#66e5ce1f76ed27b4303d8c6512e8d1216e8106bc" @@ -3121,6 +3523,10 @@ lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + lodash.keys@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" @@ -3129,6 +3535,10 @@ lodash.keys@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + lodash.mergewith@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55" @@ -3141,6 +3551,10 @@ lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + lodash.unset@^4.5.2: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.unset/-/lodash.unset-4.5.2.tgz#370d1d3e85b72a7e1b0cdf2d272121306f23e4ed" @@ -3194,6 +3608,10 @@ lz-string@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" +macaddress@^0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" + map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" @@ -3208,10 +3626,18 @@ marked-terminal@^1.6.2: lodash.assign "^4.2.0" node-emoji "^1.4.1" -marked@^0.3.6: +marked@*, marked@^0.3.6: version "0.3.6" resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7" +math-expression-evaluator@^1.2.14: + version "1.2.17" + resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" + +"mdurl@~ 1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -3308,6 +3734,10 @@ mime@^1.3.4: version "1.3.6" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0" +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + minimalistic-assert@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" @@ -3316,7 +3746,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" -minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -3330,7 +3760,7 @@ minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" -mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: +mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -3381,6 +3811,14 @@ mute-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" +mz@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.6.0.tgz#c8b8521d958df0a4f2768025db69c719ee4ef1ce" + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + nan@^2.3.0, nan@^2.3.2: version "2.5.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2" @@ -3397,6 +3835,10 @@ next-event@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/next-event/-/next-event-1.0.0.tgz#e7778acde2e55802e0ad1879c39cf6f75eda61d8" +node-abi@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.0.3.tgz#0ca67e5e667b8e1343549ca17153a815d0bbfdaa" + node-emoji@^1.4.1: version "1.5.1" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.5.1.tgz#fd918e412769bf8c448051238233840b2aff16a1" @@ -3414,7 +3856,7 @@ node-forge@0.6.33: version "0.6.33" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc" -node-gyp@^3.3.1: +node-gyp@^3.3.1, node-gyp@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60" dependencies: @@ -3573,6 +4015,19 @@ normalize-path@^2.0.1: dependencies: remove-trailing-separator "^1.0.1" +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + +normalize-url@^1.4.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + npm-path@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.3.tgz#15cff4e1c89a38da77f56f6055b24f975dfb2bbe" @@ -3602,6 +4057,10 @@ npm-which@^3.0.1: gauge "~2.7.3" set-blocking "~2.0.0" +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -3610,6 +4069,10 @@ oauth-sign@~0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +object-assign@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" + object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -3661,6 +4124,12 @@ onetime@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + dependencies: + mimic-fn "^1.0.0" + opn@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/opn/-/opn-4.0.2.tgz#7abc22e644dff63b0a96d5ab7f2790c0f01abc95" @@ -3695,6 +4164,15 @@ ora@^0.2.3: cli-spinners "^0.1.2" object-assign "^4.0.1" +ora@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-1.3.0.tgz#80078dd2b92a934af66a3ad72a5b910694ede51a" + dependencies: + chalk "^1.1.1" + cli-cursor "^2.1.0" + cli-spinners "^1.0.0" + log-symbols "^1.0.2" + original@>=0.0.5: version "1.0.0" resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b" @@ -3785,6 +4263,10 @@ parseurl@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + path-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" @@ -3887,10 +4369,260 @@ portfinder@^1.0.9: debug "^2.2.0" mkdirp "0.5.x" +postcss-calc@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" + dependencies: + postcss "^5.0.2" + postcss-message-helpers "^2.0.0" + reduce-css-calc "^1.2.6" + +postcss-colormin@^2.1.8: + version "2.2.2" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b" + dependencies: + colormin "^1.0.5" + postcss "^5.0.13" + postcss-value-parser "^3.2.3" + +postcss-convert-values@^2.3.4: + version "2.6.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d" + dependencies: + postcss "^5.0.11" + postcss-value-parser "^3.1.2" + +postcss-discard-comments@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d" + dependencies: + postcss "^5.0.14" + +postcss-discard-duplicates@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932" + dependencies: + postcss "^5.0.4" + +postcss-discard-empty@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5" + dependencies: + postcss "^5.0.14" + +postcss-discard-overridden@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58" + dependencies: + postcss "^5.0.16" + +postcss-discard-unused@^2.2.1: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433" + dependencies: + postcss "^5.0.14" + uniqs "^2.0.0" + +postcss-filter-plugins@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c" + dependencies: + postcss "^5.0.4" + uniqid "^4.0.0" + +postcss-merge-idents@^2.1.5: + version "2.1.7" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270" + dependencies: + has "^1.0.1" + postcss "^5.0.10" + postcss-value-parser "^3.1.1" + +postcss-merge-longhand@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658" + dependencies: + postcss "^5.0.4" + +postcss-merge-rules@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721" + dependencies: + browserslist "^1.5.2" + caniuse-api "^1.5.2" + postcss "^5.0.4" + postcss-selector-parser "^2.2.2" + vendors "^1.0.0" + +postcss-message-helpers@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e" + +postcss-minify-font-values@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69" + dependencies: + object-assign "^4.0.1" + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-minify-gradients@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1" + dependencies: + postcss "^5.0.12" + postcss-value-parser "^3.3.0" + +postcss-minify-params@^1.0.4: + version "1.2.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.2" + postcss-value-parser "^3.0.2" + uniqs "^2.0.0" + +postcss-minify-selectors@^2.0.4: + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf" + dependencies: + alphanum-sort "^1.0.2" + has "^1.0.1" + postcss "^5.0.14" + postcss-selector-parser "^2.0.0" + +postcss-modules-extract-imports@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz#66140ecece38ef06bf0d3e355d69bf59d141ea85" + dependencies: + postcss "^6.0.1" + +postcss-modules-local-by-default@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-scope@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-values@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" + dependencies: + icss-replace-symbols "^1.1.0" + postcss "^6.0.1" + +postcss-normalize-charset@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1" + dependencies: + postcss "^5.0.5" + +postcss-normalize-url@^3.0.7: + version "3.0.8" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222" + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^1.4.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + +postcss-ordered-values@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.1" + +postcss-reduce-idents@^2.2.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-reduce-initial@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea" + dependencies: + postcss "^5.0.4" + +postcss-reduce-transforms@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1" + dependencies: + has "^1.0.1" + postcss "^5.0.8" + postcss-value-parser "^3.0.1" + +postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-svgo@^2.1.1: + version "2.1.6" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" + dependencies: + is-svg "^2.0.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + svgo "^0.7.0" + +postcss-unique-selectors@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" + +postcss-zindex@^2.0.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22" + dependencies: + has "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16: + version "5.2.17" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.17.tgz#cf4f597b864d65c8a492b2eabe9d706c879c388b" + dependencies: + chalk "^1.1.3" + js-base64 "^2.1.9" + source-map "^0.5.6" + supports-color "^3.2.3" + +postcss@^6.0.1: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.3.tgz#b7f565b3d956fbb8565ca7c1e239d0506e427d8b" + dependencies: + chalk "^1.1.3" + source-map "^0.5.6" + supports-color "^4.0.0" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" +prepend-http@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" @@ -3915,13 +4647,13 @@ progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" -promise@^7.1.1: +promise@^7.0.3, promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" dependencies: asap "~2.0.3" -prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8: +prop-types@^15.5.1, prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8: version "15.5.10" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" dependencies: @@ -3968,10 +4700,21 @@ punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" +q@^1.1.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" + qs@6.4.0, qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -4041,6 +4784,15 @@ react-dom@^15.4.0: object-assign "^4.1.0" prop-types "^15.5.10" +react-markdown@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-2.5.0.tgz#b1c61904fee5895886803bd9df7db23c3dc3a89e" + dependencies: + commonmark "^0.24.0" + commonmark-react-renderer "^4.2.4" + in-publish "^2.0.0" + prop-types "^15.5.1" + react-modal@^1.5.2: version "1.9.7" resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-1.9.7.tgz#07ef56790b953e3b98ef1e2989e347983c72871d" @@ -4064,6 +4816,20 @@ react-redux@^5.0.3: loose-envify "^1.1.0" prop-types "^15.5.10" +react-simplemde-editor@^3.6.11: + version "3.6.11" + resolved "https://registry.yarnpkg.com/react-simplemde-editor/-/react-simplemde-editor-3.6.11.tgz#4b9e136f6d4d00218e8ece3d87949e23b14e21dc" + dependencies: + react "^0.14.2" + simplemde "^1.11.2" + +react@^0.14.2: + version "0.14.9" + resolved "https://registry.yarnpkg.com/react/-/react-0.14.9.tgz#9110a6497c49d44ba1c0edd317aec29c2e0d91d1" + dependencies: + envify "^3.0.0" + fbjs "^0.6.1" + react@^15.4.0: version "15.6.1" resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df" @@ -4151,6 +4917,15 @@ recast@^0.10.1: private "~0.1.5" source-map "~0.5.0" +recast@^0.11.17: + version "0.11.23" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" + dependencies: + ast-types "0.9.6" + esprima "~3.1.0" + private "~0.1.5" + source-map "~0.5.0" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -4170,6 +4945,20 @@ redeyed@~1.0.0: dependencies: esprima "~3.0.0" +reduce-css-calc@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" + dependencies: + balanced-match "^0.4.2" + math-expression-evaluator "^1.2.14" + reduce-function-call "^1.0.1" + +reduce-function-call@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99" + dependencies: + balanced-match "^0.4.2" + redux-action-buffer@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/redux-action-buffer/-/redux-action-buffer-1.1.0.tgz#9c692ab6532b042d0d43a9f01a48ada120fc941a" @@ -4242,6 +5031,14 @@ regex-cache@^0.4.2: is-equal-shallow "^0.1.3" is-primitive "^2.0.0" +regexpu-core@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + regexpu-core@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" @@ -4359,6 +5156,13 @@ restore-cursor@^1.0.1: exit-hook "^1.0.0" onetime "^1.0.0" +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" @@ -4392,7 +5196,7 @@ rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" -rxjs@^5.0.0-beta.11: +rxjs@^5.0.0-beta.11, rxjs@^5.1.1: version "5.4.1" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.1.tgz#b62f757f279445d265a18a58fb0a70dc90e91626" dependencies: @@ -4411,6 +5215,16 @@ sass-graph@^2.1.1: scss-tokenizer "^0.2.3" yargs "^7.0.0" +sax@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + +schema-utils@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" + dependencies: + ajv "^5.0.0" + scss-tokenizer@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" @@ -4519,10 +5333,18 @@ shellwords@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.0.tgz#66afd47b6a12932d9071cbfd98a52e785cd0ba14" -signal-exit@^3.0.0: +signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" +simplemde@^1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/simplemde/-/simplemde-1.11.2.tgz#a23a35d978d2c40ef07dec008c92f070d8e080e3" + dependencies: + codemirror "*" + codemirror-spell-checker "*" + marked "*" + slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" @@ -4555,14 +5377,20 @@ sockjs@0.3.18: faye-websocket "^0.10.0" uuid "^2.0.2" +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + +source-list-map@^0.1.7, source-list-map@~0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" + source-list-map@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.2.tgz#9889019d1024cce55cdc069498337ef6186a11a1" -source-list-map@~0.1.7: - version "0.1.8" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" - source-map-support@^0.4.2: version "0.4.15" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" @@ -4585,6 +5413,14 @@ source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, sour version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" +spawn-rx@^2.0.10: + version "2.0.11" + resolved "https://registry.yarnpkg.com/spawn-rx/-/spawn-rx-2.0.11.tgz#65451ad65662801daea75549832a782de0048dbf" + dependencies: + debug "^2.5.1" + lodash.assign "^4.2.0" + rxjs "^5.1.1" + spdx-correct@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" @@ -4687,6 +5523,10 @@ stream-to-observable@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe" +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -4706,6 +5546,10 @@ string.prototype.codepointat@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz#6b26e9bd3afcaa7be3b4269b526de1b82000ac78" +string.prototype.repeat@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz#aba36de08dcee6a5a337d49b2ea1da1b28fc0ecf" + string_decoder@^0.10.25, string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -4750,16 +5594,41 @@ strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" +style-loader@^0.18.2: + version "0.18.2" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.18.2.tgz#cc31459afbcd6d80b7220ee54b291a9fd66ff5eb" + dependencies: + loader-utils "^1.0.2" + schema-utils "^0.3.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.1.0, supports-color@^3.1.1: +supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: has-flag "^1.0.0" +supports-color@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.0.0.tgz#33a7c680aa512c9d03ef929cacbb974d203d2790" + dependencies: + has-flag "^2.0.0" + +svgo@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" + dependencies: + coa "~1.0.1" + colors "~1.1.2" + csso "~2.3.1" + js-yaml "~3.7.0" + mkdirp "~0.5.1" + sax "~1.2.1" + whet.extend "~0.9.9" + symbol-observable@^1.0.1, symbol-observable@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" @@ -4808,6 +5677,18 @@ text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" +thenify-all@^1.0.0, thenify-all@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.0" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" + dependencies: + any-promise "^1.0.0" + through2@^0.6.2, through2@^0.6.5: version "0.6.5" resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" @@ -4886,6 +5767,10 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +typo-js@*: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typo-js/-/typo-js-1.0.3.tgz#54d8ebc7949f1a7810908b6002c6841526c99d5a" + ua-parser-js@^0.7.9: version "0.7.13" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.13.tgz#cd9dd2f86493b3f44dbeeef3780fda74c5ee14be" @@ -4920,6 +5805,20 @@ uint64be@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uint64be/-/uint64be-1.0.1.tgz#1f7154202f2a1b8af353871dda651bf34ce93e95" +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + +uniqid@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-4.1.1.tgz#89220ddf6b751ae52b5f72484863528596bb84c1" + dependencies: + macaddress "^0.2.8" + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -5002,6 +5901,10 @@ vary@~1.1.0, vary@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" +vendors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" + verror@1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" @@ -5172,6 +6075,14 @@ whatwg-fetch@>=0.10.0: version "2.0.3" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" +whatwg-fetch@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz#0e3684c6cb9995b43efc9df03e4c365d95fd9cc0" + +whet.extend@~0.9.9: + version "0.9.9" + resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" + which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" @@ -5227,6 +6138,10 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" +xss-filters@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/xss-filters/-/xss-filters-1.2.7.tgz#59fa1de201f36f2f3470dcac5f58ccc2830b0a9a" + "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -5269,7 +6184,7 @@ yargs@^6.0.0: y18n "^3.2.1" yargs-parser "^4.2.0" -yargs@^7.0.0: +yargs@^7.0.0, yargs@^7.0.2: version "7.1.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" dependencies: From b09d71ecffa1c4a43c074f526e667addc7739d3d Mon Sep 17 00:00:00 2001 From: Le Long Date: Thu, 15 Jun 2017 21:30:56 +0200 Subject: [PATCH 05/26] Markdown Support --- .gitignore | 1 + CHANGELOG.md | 7 +- ui/js/component/common.js | 2 + ui/js/component/fileCard/view.jsx | 9 +- ui/js/component/form.js | 24 ++- ui/js/page/filePage/view.jsx | 7 +- ui/js/page/publish/view.jsx | 272 ++++++++++++++++++----------- ui/package.json | 5 + ui/scss/component/_form-field.scss | 7 + 9 files changed, 224 insertions(+), 110 deletions(-) diff --git a/.gitignore b/.gitignore index c212707ad..233924d55 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ build/daemon.zip .vimrc package-lock.json +ui/yarn.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index bcbe49bfb..d8f65e2ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,14 +10,17 @@ Web UI version numbers should always match the corresponding version of LBRY App ### Added * Added option to release claim when deleting a file * Added transition to card hovers to smooth animation + * Support markdown makeup in claim description + * ### Changed - * + * Publishes now uses claims rather than files * ### Fixed * Fixed bug with download notice when switching window focus - * + * Fixed newly published files appearing twice + * Fixed unconfirmed published files missing channel name ### Deprecated * diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 1b48bc4df..57314bdd3 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -1,5 +1,7 @@ import React from "react"; +import ReactDOMServer from "react-dom/server"; import lbry from "../lbry.js"; +import ReactMarkdown from "react-markdown"; //component/icon.js export class Icon extends React.PureComponent { diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index 42c0e5b68..256bc9014 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -1,7 +1,12 @@ import React from "react"; import lbryuri from "lbryuri.js"; import Link from "component/link"; -import { TruncatedText, Icon } from "component/common"; +import { + Thumbnail, + TruncatedText, + Icon, + TruncatedMarkdown, +} from "component/common"; import FilePrice from "component/filePrice"; import UriIndicator from "component/uriIndicator"; import NsfwOverlay from "component/nsfwOverlay"; @@ -94,7 +99,7 @@ class FileCard extends React.PureComponent { style={{ backgroundImage: "url('" + metadata.thumbnail + "')" }} />}
- {description} + {description}
diff --git a/ui/js/component/form.js b/ui/js/component/form.js index 7ab78325c..6a65218e4 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -1,8 +1,9 @@ import React from "react"; import FileSelector from "./file-selector.js"; -import { Icon } from "./common.js"; +import SimpleMDE from "react-simplemde-editor"; +import style from "react-simplemde-editor/dist/simplemde.min.css"; -var formFieldCounter = 0, +let formFieldCounter = 0, formFieldFileSelectorTypes = ["file", "directory"], formFieldNestedLabelTypes = ["radio", "checkbox"]; @@ -24,6 +25,7 @@ export class FormField extends React.PureComponent { this._fieldRequiredText = __("This field is required"); this._type = null; this._element = null; + this._extraElementProps = {}; this.state = { isError: null, @@ -38,6 +40,12 @@ export class FormField extends React.PureComponent { } else if (this.props.type == "text-number") { this._element = "input"; this._type = "text"; + } else if (this.props.type == "SimpleMDE") { + this._element = SimpleMDE; + this._type = "textarea"; + this._extraElementProps.options = { + hideIcons: ["guide", "heading", "image", "fullscreen"], + }; } else if (formFieldFileSelectorTypes.includes(this.props.type)) { this._element = "input"; this._type = "hidden"; @@ -81,6 +89,8 @@ export class FormField extends React.PureComponent { getValue() { if (this.props.type == "checkbox") { return this.refs.field.checked; + } else if (this.props.type == "SimpleMDE") { + return this.refs.field.simplemde.value(); } else { return this.refs.field.value; } @@ -90,6 +100,10 @@ export class FormField extends React.PureComponent { return this.refs.field.options[this.refs.field.selectedIndex]; } + getOptions() { + return this.refs.field.options; + } + render() { // Pass all unhandled props to the field element const otherProps = Object.assign({}, this.props), @@ -106,7 +120,6 @@ export class FormField extends React.PureComponent { delete otherProps.className; delete otherProps.postfix; delete otherProps.prefix; - const element = ( {this.props.children} @@ -220,6 +234,10 @@ export class FormRow extends React.PureComponent { return this.refs.field.getSelectedElement(); } + getOptions() { + return this.refs.field.getOptions(); + } + focus() { this.refs.field.focus(); } diff --git a/ui/js/page/filePage/view.jsx b/ui/js/page/filePage/view.jsx index adb478bdc..82ac64c57 100644 --- a/ui/js/page/filePage/view.jsx +++ b/ui/js/page/filePage/view.jsx @@ -1,4 +1,5 @@ import React from "react"; +import ReactMarkdown from "react-markdown"; import lbry from "lbry.js"; import lbryuri from "lbryuri.js"; import Video from "component/video"; @@ -119,7 +120,11 @@ class FilePage extends React.PureComponent {
- {metadata && metadata.description} +
{metadata diff --git a/ui/js/page/publish/view.jsx b/ui/js/page/publish/view.jsx index 12b38cbd7..b76d018a1 100644 --- a/ui/js/page/publish/view.jsx +++ b/ui/js/page/publish/view.jsx @@ -5,13 +5,16 @@ import { FormField, FormRow } from "component/form.js"; import Link from "component/link"; import rewards from "rewards"; import Modal from "component/modal"; +import Notice from "component/notice"; import { BusyMessage } from "component/common"; class PublishPage extends React.PureComponent { constructor(props) { super(props); - this._requiredFields = ["meta_title", "name", "bid", "tos_agree"]; + this._requiredFields = ["name", "bid", "meta_title", "tosAgree"]; + + this._defaultCopyrightNotice = "All rights reserved."; this.state = { rawName: "", @@ -23,11 +26,17 @@ class PublishPage extends React.PureComponent { channel: "anonymous", newChannelName: "@", newChannelBid: 10, - myClaimValue: 0.0, - myClaimMetadata: null, - copyrightNotice: "", + meta_title: "", + meta_thumbnail: "", + meta_description: "", + meta_language: "en", + meta_nsfw: "0", + licenseType: "", + copyrightNotice: this._defaultCopyrightNotice, otherLicenseDescription: "", otherLicenseUrl: "", + tosAgree: false, + prefillDone: false, uploadProgress: 0.0, uploaded: false, errorMessage: null, @@ -80,36 +89,18 @@ class PublishPage extends React.PureComponent { return; } - if (this.state.nameIsMine) { - // Pre-populate with existing metadata - var metadata = Object.assign({}, this.state.myClaimMetadata); - if (this.refs.file.getValue() !== "") { - delete metadata.sources; - } - } else { - var metadata = {}; - } + let metadata = {}; - for (let metaField of [ - "title", - "description", - "thumbnail", - "license", - "license_url", - "language", - ]) { - var value = this.refs["meta_" + metaField].getValue(); - if (value !== "") { + for (let metaField of ["title", "description", "thumbnail", "language"]) { + const value = this.state["meta_" + metaField]; + if (value) { metadata[metaField] = value; } } - metadata.nsfw = parseInt(this.refs.meta_nsfw.getValue()) === 1; - - const licenseUrl = this.refs.meta_license_url.getValue(); - if (licenseUrl) { - metadata.license_url = licenseUrl; - } + metadata.license = this.getLicense(); + metadata.licenseUrl = this.getLicenseUrl(); + metadata.nsfw = !!parseInt(this.state.meta_nsfw); var doPublish = () => { var publishArgs = { @@ -203,6 +194,8 @@ class PublishPage extends React.PureComponent { } myClaimInfo() { + const { name } = this.state; + return Object.values(this.props.myClaims).find( claim => claim.name === name ); @@ -240,6 +233,7 @@ class PublishPage extends React.PureComponent { this.setState({ rawName: rawName, name: name, + prefillDone: false, uri, }); @@ -254,6 +248,43 @@ class PublishPage extends React.PureComponent { }); } + handlePrefillClicked() { + const {license, licenseUrl, title, thumbnail, description, + language, nsfw} = this.myClaimInfo().value.stream.metadata; + + let newState = { + meta_title: title, + meta_thumbnail: thumbnail, + meta_description: description, + meta_language: language, + meta_nsfw: nsfw, + }; + + if (license == this._defaultCopyrightNotice) { + newState.licenseType = "copyright"; + newState.copyrightNotice = this._defaultCopyrightNotice; + } else { + // If the license URL or description matches one of the drop-down options, use that + let licenseType = "other"; // Will be overridden if we find a match + for (let option of this._meta_license.getOptions()) { + if ( + option.getAttribute("data-url") === licenseUrl || + option.text === license + ) { + licenseType = option.value; + } + } + + if (licenseType == "other") { + newState.otherLicenseDescription = license; + newState.otherLicenseUrl = licenseUrl; + } + newState.licenseType = licenseType; + } + + this.setState(newState); + } + handleBidChange(event) { this.setState({ bid: event.target.value, @@ -278,20 +309,21 @@ class PublishPage extends React.PureComponent { }); } - handleLicenseChange(event) { - var licenseType = event.target.options[ - event.target.selectedIndex - ].getAttribute("data-license-type"); - var newState = { - copyrightChosen: licenseType == "copyright", - otherLicenseChosen: licenseType == "other", - }; + handleMetadataChange(event) { + /** + * This function is used for all metadata inputs that store the final value directly into state. + * The only exceptions are inputs related to license description and license URL, which require + * more complex logic and the final value is determined at submit time. + */ + this.setState({ + ["meta_" + event.target.name]: event.target.value, + }); + } - if (licenseType == "copyright") { - newState.copyrightNotice = __("All rights reserved."); - } - - this.setState(newState); + handleLicenseTypeChange(event) { + this.setState({ + licenseType: event.target.value, + }); } handleCopyrightNoticeChange(event) { @@ -322,7 +354,7 @@ class PublishPage extends React.PureComponent { handleTOSChange(event) { this.setState({ - TOSAgreed: event.target.checked, + tosAgree: event.target.checked, }); } @@ -366,16 +398,25 @@ class PublishPage extends React.PureComponent { ); } + getLicense() { + switch (this.state.licenseType) { + case "copyright": + return this.state.copyrightNotice; + case "other": + return this.state.otherLicenseDescription; + default: + return this._meta_license.getSelectedElement().text; + } + } + getLicenseUrl() { - if (!this.refs.meta_license) { - return ""; - } else if (this.state.otherLicenseChosen) { - return this.state.otherLicenseUrl; - } else { - return ( - this.refs.meta_license.getSelectedElement().getAttribute("data-url") || - "" - ); + switch (this.state.licenseType) { + case "copyright": + return ""; + case "other": + return this.state.otherLicenseUrl; + default: + return this._meta_license.getSelectedElement().getAttribute("data-url"); } } @@ -398,7 +439,7 @@ class PublishPage extends React.PureComponent { this.props.resolvingUris.indexOf(this.state.uri) !== -1 && this.claim() === undefined ) { - return ; + return __("Checking..."); } else if (!this.state.name) { return __("Select a URL for this publish."); } else if (!this.claim()) { @@ -482,43 +523,55 @@ class PublishPage extends React.PureComponent { } /> - {!this.state.hasFile - ? "" + {!this.state.hasFile && !this.myClaimExists() + ? null :
{ + this.handleMetadataChange(event); + }} />
{ + this.handleMetadataChange(event); + }} />
{ + this.handleMetadataChange(event); + }} />
{ + this.handleMetadataChange(event); + }} > @@ -533,9 +586,11 @@ class PublishPage extends React.PureComponent { { + this.handleMetadataChange(event); + }} > {/* */} @@ -583,8 +638,7 @@ class PublishPage extends React.PureComponent { placeholder="1.00" min="0.01" onChange={event => this.handleFeeAmountChange(event)} - /> - {" "} + />{" "} { @@ -605,66 +659,71 @@ class PublishPage extends React.PureComponent { { + this._meta_license = row; + }} onChange={event => { - this.handleLicenseChange(event); + this.handleLicenseTypeChange(event); }} > - + - - - - - - - - - {this.state.copyrightChosen + + {this.state.licenseType == "copyright" ? : null} - {this.state.otherLicenseChosen + + {this.state.licenseType == "other" ? { - this.handleOtherLicenseDescriptionChange(); + this.handleOtherLicenseDescriptionChange(event); }} /> : null} - {this.state.otherLicenseChosen + + {this.state.licenseType == "other" ? { this.handleOtherLicenseUrlChange(event); }} @@ -730,6 +793,15 @@ class PublishPage extends React.PureComponent { }} helper={this.getNameBidHelpText()} /> + {this.myClaimExists() && !this.state.prefillDone + ? + {__("You already have a claim with this name.")}{" "} + this.handlePrefillClicked()} + /> + + : null}
{this.state.rawName ?
@@ -763,15 +835,11 @@ class PublishPage extends React.PureComponent { } type="checkbox" - name="tos_agree" - ref={field => { - this.refs.tos_agree = field; - }} + checked={this.state.tosAgree} onChange={event => { this.handleTOSChange(event); }} diff --git a/ui/package.json b/ui/package.json index d7aa3535a..4c4986ec2 100644 --- a/ui/package.json +++ b/ui/package.json @@ -29,8 +29,10 @@ "rc-progress": "^2.0.6", "react": "^15.4.0", "react-dom": "^15.4.0", + "react-markdown": "^2.5.0", "react-modal": "^1.5.2", "react-redux": "^5.0.3", + "react-simplemde-editor": "^3.6.11", "redux": "^3.6.0", "redux-action-buffer": "^1.1.0", "redux-logger": "^3.0.1", @@ -52,6 +54,8 @@ "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.18.0", + "electron-rebuild": "^1.5.11", + "css-loader": "^0.28.4", "eslint": "^3.10.2", "eslint-config-airbnb": "^13.0.0", "eslint-loader": "^1.6.1", @@ -64,6 +68,7 @@ "lint-staged": "^3.6.0", "node-loader": "^0.6.0", "prettier": "^1.4.2", + "style-loader": "^0.18.2", "webpack": "^2.6.1", "webpack-dev-server": "^2.4.4", "webpack-notifier": "^1.5.0", diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss index 8fd86efef..f701ebe06 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -117,6 +117,9 @@ input[type="text"].input-copyable { border: $width-input-border solid $color-form-border; } } +.form-field--SimpleMDE { + display: block; +} .form-field__label { &[for] { cursor: pointer; } @@ -163,4 +166,8 @@ input[type="text"].input-copyable { } .form-field__helper { color: $color-help; +} + +.form-field__input.form-field__input-SimpleMDE .CodeMirror-scroll { + height: auto; } \ No newline at end of file From 7c5187c4e4d8e1cef4d6a478f12a475fcf47adcf Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Fri, 30 Jun 2017 15:45:54 +0700 Subject: [PATCH 06/26] Create publish form component, fix markdown editor, fix prefill --- ui/js/component/publishForm/index.js | 5 + .../publishForm/internal/channelSection.jsx | 179 +++ ui/js/component/publishForm/view.jsx | 920 ++++++++++++++ ui/js/page/publish/view.jsx | 1076 +---------------- 4 files changed, 1108 insertions(+), 1072 deletions(-) create mode 100644 ui/js/component/publishForm/index.js create mode 100644 ui/js/component/publishForm/internal/channelSection.jsx create mode 100644 ui/js/component/publishForm/view.jsx diff --git a/ui/js/component/publishForm/index.js b/ui/js/component/publishForm/index.js new file mode 100644 index 000000000..3e2d02b42 --- /dev/null +++ b/ui/js/component/publishForm/index.js @@ -0,0 +1,5 @@ +import React from "react"; +import { connect } from "react-redux"; +import PublishForm from "./view"; + +export default connect()(PublishForm); diff --git a/ui/js/component/publishForm/internal/channelSection.jsx b/ui/js/component/publishForm/internal/channelSection.jsx new file mode 100644 index 000000000..c0c4bf473 --- /dev/null +++ b/ui/js/component/publishForm/internal/channelSection.jsx @@ -0,0 +1,179 @@ +import React from "react"; +import lbryuri from "lbryuri"; +import { FormField, FormRow } from "component/form.js"; +import { BusyMessage } from "component/common"; + +class ChannelSection extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + newChannelName: "@", + newChannelBid: 10, + addingChannel: false, + }; + } + + handleChannelChange(event) { + const channel = event.target.value; + if (channel === "new") this.setState({ addingChannel: true }); + else { + this.setState({ addingChannel: false }); + this.props.handleChannelChange(event.target.value); + } + } + + handleNewChannelNameChange(event) { + const newChannelName = event.target.value.startsWith("@") + ? event.target.value + : "@" + event.target.value; + + if ( + newChannelName.length > 1 && + !lbryuri.isValidName(newChannelName.substr(1), false) + ) { + this.refs.newChannelName.showError( + __("LBRY channel names must contain only letters, numbers and dashes.") + ); + return; + } else { + this.refs.newChannelName.clearError(); + } + + this.setState({ + newChannelName, + }); + } + + handleNewChannelBidChange(event) { + this.setState({ + newChannelBid: event.target.value, + }); + } + + handleCreateChannelClick(event) { + if (this.state.newChannelName.length < 5) { + this.refs.newChannelName.showError( + __("LBRY channel names must be at least 4 characters in length.") + ); + return; + } + + this.setState({ + creatingChannel: true, + }); + + const newChannelName = this.state.newChannelName; + const amount = parseFloat(this.state.newChannelBid); + this.setState({ + creatingChannel: true, + }); + const success = (() => { + this.setState({ + creatingChannel: false, + addingChannel: false, + channel: newChannelName, + }); + this.props.handleChannelChange(newChannelName); + }).bind(this); + const failure = (err => { + this.setState({ + creatingChannel: false, + }); + this.refs.newChannelName.showError( + __("Unable to create channel due to an internal error.") + ); + }).bind(this); + this.props.createChannel(newChannelName, amount).then(success, failure); + } + + render() { + const lbcInputHelp = __( + "This LBC remains yours and the deposit can be undone at any time." + ); + + const { fetchingChannels, channels } = this.props; + + let channelContent = []; + if (channels.length > 0) { + channelContent.push( + + + {this.props.channels.map(({ name }) => + + )} + + + ); + if (fetchingChannels) { + channelContent.push( + + ); + } + } else if (fetchingChannels) { + channelContent.push( + + ); + } + + return ( +
+
+

{__("Identity")}

+
+ {__("Who created this content?")} +
+
+
+ {channelContent} +
+ {this.state.addingChannel && +
+ { + this.handleNewChannelNameChange(event); + }} + value={this.state.newChannelName} + /> + +
+ +
+
} +
+ ); + } +} + +export default ChannelSection; diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx new file mode 100644 index 000000000..91fd5eb09 --- /dev/null +++ b/ui/js/component/publishForm/view.jsx @@ -0,0 +1,920 @@ +import React from "react"; +import lbry from "lbry"; +import lbryuri from "lbryuri"; +import { FormField, FormRow } from "component/form.js"; +import Link from "component/link"; +import Modal from "component/modal"; +import Notice from "component/notice"; +import { BusyMessage } from "component/common"; +import ChannelSection from "./internal/ChannelSection"; + +class PublishForm extends React.PureComponent { + constructor(props) { + super(props); + + this._requiredFields = ["name", "bid", "meta_title", "tosAgree"]; + + this._defaultCopyrightNotice = "All rights reserved."; + + this.state = { + rawName: "", + name: "", + bid: 10, + hasFile: false, + feeAmount: "", + feeCurrency: "USD", + channel: "anonymous", + newChannelName: "@", + newChannelBid: 10, + meta_title: "", + meta_thumbnail: "", + meta_description: "", + meta_language: "en", + meta_nsfw: "0", + licenseType: "", + copyrightNotice: this._defaultCopyrightNotice, + otherLicenseDescription: "", + otherLicenseUrl: "", + tosAgree: false, + prefillDone: false, + uploadProgress: 0.0, + uploaded: false, + errorMessage: null, + submitting: false, + creatingChannel: false, + modal: null, + }; + } + + _updateChannelList(channel) { + const { fetchingChannels, fetchChannelListMine } = this.props; + + if (!fetchingChannels) fetchChannelListMine(); + } + + handleSubmit(event) { + if (typeof event !== "undefined") { + event.preventDefault(); + } + + this.setState({ + submitting: true, + }); + + let checkFields = this._requiredFields; + if (!this.myClaimExists()) { + checkFields.unshift("file"); + } + + let missingFieldFound = false; + for (let fieldName of checkFields) { + const field = this.refs[fieldName]; + if (field) { + if (field.getValue() === "" || field.getValue() === false) { + field.showRequiredError(); + if (!missingFieldFound) { + field.focus(); + missingFieldFound = true; + } + } else { + field.clearError(); + } + } + } + + if (missingFieldFound) { + this.setState({ + submitting: false, + }); + return; + } + + let metadata = {}; + + for (let metaField of ["title", "description", "thumbnail", "language"]) { + const value = this.state["meta_" + metaField]; + if (value) { + metadata[metaField] = value; + } + } + + metadata.license = this.getLicense(); + metadata.licenseUrl = this.getLicenseUrl(); + metadata.nsfw = !!parseInt(this.state.meta_nsfw); + + var doPublish = () => { + var publishArgs = { + name: this.state.name, + bid: parseFloat(this.state.bid), + metadata: metadata, + ...(this.state.channel != "new" && this.state.channel != "anonymous" + ? { channel_name: this.state.channel } + : {}), + }; + + if (this.refs.file.getValue() !== "") { + publishArgs.file_path = this.refs.file.getValue(); + } + + const success = claim => {}; + const failure = error => this.handlePublishError(error); + + this.handlePublishStarted(); + this.props.publish(publishArgs).then(success, failure); + }; + + if (this.state.isFee) { + lbry.wallet_unused_address().then(address => { + metadata.fee = { + currency: this.state.feeCurrency, + amount: parseFloat(this.state.feeAmount), + address: address, + }; + + doPublish(); + }); + } else { + doPublish(); + } + } + + handlePublishStarted() { + this.setState({ + modal: "publishStarted", + }); + } + + handlePublishStartedConfirmed() { + this.props.navigate("/published"); + } + + handlePublishError(error) { + this.setState({ + submitting: false, + modal: "error", + errorMessage: error.message, + }); + } + + claim() { + const { claimsByUri } = this.props; + const { uri } = this.state; + + return claimsByUri[uri]; + } + + topClaimValue() { + if (!this.claim()) return null; + + return parseFloat(this.claim().amount); + } + + myClaimExists() { + const { myClaims } = this.props; + const { name } = this.state; + + if (!name) return false; + + return !!myClaims.find(claim => claim.name === name); + } + + topClaimIsMine() { + const myClaimInfo = this.myClaimInfo(); + const { claimsByUri } = this.props; + const { uri } = this.state; + + if (!uri) return null; + + const claim = claimsByUri[uri]; + + if (!claim) return true; + if (!myClaimInfo) return false; + + return myClaimInfo.amount >= claimInfo.amount; + } + + myClaimInfo() { + const { name } = this.state; + + return Object.values(this.props.myClaims).find( + claim => claim.name === name + ); + } + + handleNameChange(event) { + var rawName = event.target.value; + + this.nameChanged(rawName); + } + + nameChanged(rawName) { + if (!rawName) { + this.setState({ + rawName: "", + name: "", + uri: "", + prefillDone: false, + }); + + return; + } + + if (!lbryuri.isValidName(rawName, false)) { + this.refs.name.showError( + __("LBRY names must contain only letters, numbers and dashes.") + ); + return; + } + + let channel = ""; + if (this.state.channel !== "anonymous") channel = this.state.channel; + + const name = rawName.toLowerCase(); + const uri = lbryuri.build({ contentName: name, channelName: channel }); + this.setState({ + rawName: rawName, + name: name, + prefillDone: false, + uri, + }); + + if (this.resolveUriTimeout) { + clearTimeout(this.resolveUriTimeout); + this.resolveUriTimeout = undefined; + } + const resolve = () => this.props.resolveUri(uri); + + this.resolveUriTimeout = setTimeout(resolve.bind(this), 500, { + once: true, + }); + } + + handlePrefillClicked() { + const claimInfo = this.myClaimInfo(); + const { + license, + licenseUrl, + title, + thumbnail, + description, + language, + nsfw, + } = claimInfo.value.stream.metadata; + + let newState = { + meta_title: title, + meta_thumbnail: thumbnail, + meta_description: description, + meta_language: language, + meta_nsfw: nsfw, + prefillDone: true, + bid: claimInfo.amount, + }; + + if (license == this._defaultCopyrightNotice) { + newState.licenseType = "copyright"; + newState.copyrightNotice = this._defaultCopyrightNotice; + } else { + // If the license URL or description matches one of the drop-down options, use that + let licenseType = "other"; // Will be overridden if we find a match + for (let option of this._meta_license.getOptions()) { + if ( + option.getAttribute("data-url") === licenseUrl || + option.text === license + ) { + licenseType = option.value; + } + } + + if (licenseType == "other") { + newState.otherLicenseDescription = license; + newState.otherLicenseUrl = licenseUrl; + } + newState.licenseType = licenseType; + } + + console.log(newState); + this.setState(newState); + } + + handleBidChange(event) { + this.setState({ + bid: event.target.value, + }); + } + + handleFeeAmountChange(event) { + this.setState({ + feeAmount: event.target.value, + }); + } + + handleFeeCurrencyChange(event) { + this.setState({ + feeCurrency: event.target.value, + }); + } + + handleFeePrefChange(feeEnabled) { + this.setState({ + isFee: feeEnabled, + }); + } + + handleMetadataChange(event) { + /** + * This function is used for all metadata inputs that store the final value directly into state. + * The only exceptions are inputs related to license description and license URL, which require + * more complex logic and the final value is determined at submit time. + */ + this.setState({ + ["meta_" + event.target.name]: event.target.value, + }); + } + + handleDescriptionChanged(text) { + this.setState({ + meta_description: text, + }); + } + + handleLicenseTypeChange(event) { + this.setState({ + licenseType: event.target.value, + }); + } + + handleCopyrightNoticeChange(event) { + this.setState({ + copyrightNotice: event.target.value, + }); + } + + handleOtherLicenseDescriptionChange(event) { + this.setState({ + otherLicenseDescription: event.target.value, + }); + } + + handleOtherLicenseUrlChange(event) { + this.setState({ + otherLicenseUrl: event.target.value, + }); + } + + handleChannelChange(channelName) { + this.setState({ + channel: channelName, + }); + const nameChanged = () => this.nameChanged(this.state.rawName); + setTimeout(nameChanged.bind(this), 500, { once: true }); + } + + handleTOSChange(event) { + this.setState({ + tosAgree: event.target.checked, + }); + } + + handleCreateChannelClick(event) { + if (this.state.newChannelName.length < 5) { + this.refs.newChannelName.showError( + __("LBRY channel names must be at least 4 characters in length.") + ); + return; + } + + this.setState({ + creatingChannel: true, + }); + + const newChannelName = this.state.newChannelName; + lbry + .channel_new({ + channel_name: newChannelName, + amount: parseFloat(this.state.newChannelBid), + }) + .then( + () => { + setTimeout(() => { + this.setState({ + creatingChannel: false, + }); + + this._updateChannelList(newChannelName); + }, 10000); + }, + error => { + // TODO: better error handling + this.refs.newChannelName.showError( + __("Unable to create channel due to an internal error.") + ); + this.setState({ + creatingChannel: false, + }); + } + ); + } + + getLicense() { + switch (this.state.licenseType) { + case "copyright": + return this.state.copyrightNotice; + case "other": + return this.state.otherLicenseDescription; + default: + return this._meta_license.getSelectedElement().text; + } + } + + getLicenseUrl() { + switch (this.state.licenseType) { + case "copyright": + return ""; + case "other": + return this.state.otherLicenseUrl; + default: + return this._meta_license.getSelectedElement().getAttribute("data-url"); + } + } + + componentWillMount() { + this.props.fetchClaimListMine(); + this._updateChannelList(); + } + + onFileChange() { + if (this.refs.file.getValue()) { + this.setState({ hasFile: true }); + } else { + this.setState({ hasFile: false }); + } + } + + getNameBidHelpText() { + if (this.state.prefillDone) { + return ( + + {__("Existing claim data was prefilled")} + + ); + } + + if ( + this.state.uri && + this.props.resolvingUris.indexOf(this.state.uri) !== -1 && + this.claim() === undefined + ) { + return __("Checking..."); + } else if (!this.state.name) { + return __("Select a URL for this publish."); + } else if (!this.claim()) { + return __("This URL is unused."); + } else if (this.myClaimExists() && !this.state.prefillDone) { + return ( + + {__("You already have a claim with this name.")}{" "} + this.handlePrefillClicked()} + /> + + ); + } else if (this.claim()) { + if (this.topClaimValue() === 1) { + return ( + + {__( + 'A deposit of at least one credit is required to win "%s". However, you can still get a permanent URL for any amount.', + this.state.name + )} + + ); + } else { + return ( + + {__( + 'A deposit of at least "%s" credits is required to win "%s". However, you can still get a permanent URL for any amount.', + this.topClaimValue(), + this.state.name + )} + + ); + } + } else { + return ""; + } + } + + closeModal() { + this.setState({ + modal: null, + }); + } + + render() { + const lbcInputHelp = __( + "This LBC remains yours and the deposit can be undone at any time." + ); + + return ( +
+
{ + this.handleSubmit(event); + }} + > +
+
+

{__("Content")}

+
+ {__("What are you publishing?")} +
+
+
+ { + this.onFileChange(event); + }} + helper={ + this.myClaimExists() + ? __( + "If you don't choose a file, the file from your existing claim will be used." + ) + : null + } + /> +
+ {!this.state.hasFile && !this.myClaimExists() + ? null + :
+
+ { + this.handleMetadataChange(event); + }} + /> +
+
+ { + this.handleMetadataChange(event); + }} + /> +
+
+ { + this.handleDescriptionChanged(text); + }} + /> +
+
+ { + this.handleMetadataChange(event); + }} + > + + + + + + + + +
+
+ { + this.handleMetadataChange(event); + }} + > + {/* */} + + + +
+
} +
+ +
+
+

{__("Access")}

+
+ {__("How much does this content cost?")} +
+
+
+
+ +
+ { + this.handleFeePrefChange(false); + }} + defaultChecked={!this.state.isFee} + /> + { + this.handleFeePrefChange(true); + }} + defaultChecked={this.state.isFee} + /> + + this.handleFeeAmountChange(event)} + />{" "} + { + this.handleFeeCurrencyChange(event); + }} + > + + + + + {this.state.isFee + ?
+ {__( + "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." + )} +
+ : ""} + { + this._meta_license = row; + }} + onChange={event => { + this.handleLicenseTypeChange(event); + }} + > + + + + + + + + + + + + {this.state.licenseType == "copyright" + ? { + this.handleCopyrightNoticeChange(event); + }} + /> + : null} + + {this.state.licenseType == "other" + ? { + this.handleOtherLicenseDescriptionChange(event); + }} + /> + : null} + + {this.state.licenseType == "other" + ? { + this.handleOtherLicenseUrlChange(event); + }} + /> + : null} +
+
+ + + +
+
+

{__("Address")}

+
+ {__("Where should this content permanently reside?")} + {" "} + . +
+
+
+ { + this.handleNameChange(event); + }} + helper={this.getNameBidHelpText()} + /> +
+ {this.state.rawName + ?
+ { + this.handleBidChange(event); + }} + value={this.state.bid} + placeholder={this.claim() ? this.topClaimValue() + 10 : 100} + helper={lbcInputHelp} + /> +
+ : ""} +
+ +
+
+

{__("Terms of Service")}

+
+
+ + {__("I agree to the")} + {" "} + + + } + type="checkbox" + checked={this.state.tosAgree} + onChange={event => { + this.handleTOSChange(event); + }} + /> +
+
+ +
+ { + this.handleSubmit(event); + }} + disabled={this.state.submitting} + /> + + +
+ + + { + this.handlePublishStartedConfirmed(event); + }} + > +

+ {__("Your file has been published to LBRY at the address")} + {" "}{this.state.uri}! +

+

+ {__( + 'The file will take a few minutes to appear for other LBRY users. Until then it will be listed as "pending" under your published files.' + )} +

+
+ { + this.closeModal(event); + }} + > + {__( + "The following error occurred when attempting to publish your file" + )}: {this.state.errorMessage} + +
+ ); + } +} + +export default PublishForm; diff --git a/ui/js/page/publish/view.jsx b/ui/js/page/publish/view.jsx index b76d018a1..ab39fcec8 100644 --- a/ui/js/page/publish/view.jsx +++ b/ui/js/page/publish/view.jsx @@ -1,1076 +1,8 @@ import React from "react"; -import lbry from "lbry"; -import lbryuri from "lbryuri"; -import { FormField, FormRow } from "component/form.js"; -import Link from "component/link"; -import rewards from "rewards"; -import Modal from "component/modal"; -import Notice from "component/notice"; -import { BusyMessage } from "component/common"; +import PublishForm from "component/publishForm"; -class PublishPage extends React.PureComponent { - constructor(props) { - super(props); - - this._requiredFields = ["name", "bid", "meta_title", "tosAgree"]; - - this._defaultCopyrightNotice = "All rights reserved."; - - this.state = { - rawName: "", - name: "", - bid: 10, - hasFile: false, - feeAmount: "", - feeCurrency: "USD", - channel: "anonymous", - newChannelName: "@", - newChannelBid: 10, - meta_title: "", - meta_thumbnail: "", - meta_description: "", - meta_language: "en", - meta_nsfw: "0", - licenseType: "", - copyrightNotice: this._defaultCopyrightNotice, - otherLicenseDescription: "", - otherLicenseUrl: "", - tosAgree: false, - prefillDone: false, - uploadProgress: 0.0, - uploaded: false, - errorMessage: null, - submitting: false, - creatingChannel: false, - modal: null, - }; - } - - _updateChannelList(channel) { - const { fetchingChannels, fetchChannelListMine } = this.props; - - if (!fetchingChannels) fetchChannelListMine(); - } - - handleSubmit(event) { - if (typeof event !== "undefined") { - event.preventDefault(); - } - - this.setState({ - submitting: true, - }); - - let checkFields = this._requiredFields; - if (!this.myClaimExists()) { - checkFields.unshift("file"); - } - - let missingFieldFound = false; - for (let fieldName of checkFields) { - const field = this.refs[fieldName]; - if (field) { - if (field.getValue() === "" || field.getValue() === false) { - field.showRequiredError(); - if (!missingFieldFound) { - field.focus(); - missingFieldFound = true; - } - } else { - field.clearError(); - } - } - } - - if (missingFieldFound) { - this.setState({ - submitting: false, - }); - return; - } - - let metadata = {}; - - for (let metaField of ["title", "description", "thumbnail", "language"]) { - const value = this.state["meta_" + metaField]; - if (value) { - metadata[metaField] = value; - } - } - - metadata.license = this.getLicense(); - metadata.licenseUrl = this.getLicenseUrl(); - metadata.nsfw = !!parseInt(this.state.meta_nsfw); - - var doPublish = () => { - var publishArgs = { - name: this.state.name, - bid: parseFloat(this.state.bid), - metadata: metadata, - ...(this.state.channel != "new" && this.state.channel != "anonymous" - ? { channel_name: this.state.channel } - : {}), - }; - - if (this.refs.file.getValue() !== "") { - publishArgs.file_path = this.refs.file.getValue(); - } - - const success = claim => {}; - const failure = error => this.handlePublishError(error); - - this.handlePublishStarted(); - this.props.publish(publishArgs).then(success, failure); - }; - - if (this.state.isFee) { - lbry.wallet_unused_address().then(address => { - metadata.fee = { - currency: this.state.feeCurrency, - amount: parseFloat(this.state.feeAmount), - address: address, - }; - - doPublish(); - }); - } else { - doPublish(); - } - } - - handlePublishStarted() { - this.setState({ - modal: "publishStarted", - }); - } - - handlePublishStartedConfirmed() { - this.props.navigate("/published"); - } - - handlePublishError(error) { - this.setState({ - submitting: false, - modal: "error", - errorMessage: error.message, - }); - } - - claim() { - const { claimsByUri } = this.props; - const { uri } = this.state; - - return claimsByUri[uri]; - } - - topClaimValue() { - if (!this.claim()) return null; - - return parseFloat(this.claim().amount); - } - - myClaimExists() { - const { myClaims } = this.props; - const { name } = this.state; - - if (!name) return false; - - return !!myClaims.find(claim => claim.name === name); - } - - topClaimIsMine() { - const myClaimInfo = this.myClaimInfo(); - const { claimsByUri } = this.props; - const { uri } = this.state; - - if (!uri) return null; - - const claim = claimsByUri[uri]; - - if (!claim) return true; - if (!myClaimInfo) return false; - - return myClaimInfo.amount >= claimInfo.amount; - } - - myClaimInfo() { - const { name } = this.state; - - return Object.values(this.props.myClaims).find( - claim => claim.name === name - ); - } - - handleNameChange(event) { - var rawName = event.target.value; - - this.nameChanged(rawName); - } - - nameChanged(rawName) { - if (!rawName) { - this.setState({ - rawName: "", - name: "", - uri: "", - }); - - return; - } - - if (!lbryuri.isValidName(rawName, false)) { - this.refs.name.showError( - __("LBRY names must contain only letters, numbers and dashes.") - ); - return; - } - - let channel = ""; - if (this.state.channel !== "anonymous") channel = this.state.channel; - - const name = rawName.toLowerCase(); - const uri = lbryuri.build({ contentName: name, channelName: channel }); - this.setState({ - rawName: rawName, - name: name, - prefillDone: false, - uri, - }); - - if (this.resolveUriTimeout) { - clearTimeout(this.resolveUriTimeout); - this.resolveUriTimeout = undefined; - } - const resolve = () => this.props.resolveUri(uri); - - this.resolveUriTimeout = setTimeout(resolve.bind(this), 500, { - once: true, - }); - } - - handlePrefillClicked() { - const {license, licenseUrl, title, thumbnail, description, - language, nsfw} = this.myClaimInfo().value.stream.metadata; - - let newState = { - meta_title: title, - meta_thumbnail: thumbnail, - meta_description: description, - meta_language: language, - meta_nsfw: nsfw, - }; - - if (license == this._defaultCopyrightNotice) { - newState.licenseType = "copyright"; - newState.copyrightNotice = this._defaultCopyrightNotice; - } else { - // If the license URL or description matches one of the drop-down options, use that - let licenseType = "other"; // Will be overridden if we find a match - for (let option of this._meta_license.getOptions()) { - if ( - option.getAttribute("data-url") === licenseUrl || - option.text === license - ) { - licenseType = option.value; - } - } - - if (licenseType == "other") { - newState.otherLicenseDescription = license; - newState.otherLicenseUrl = licenseUrl; - } - newState.licenseType = licenseType; - } - - this.setState(newState); - } - - handleBidChange(event) { - this.setState({ - bid: event.target.value, - }); - } - - handleFeeAmountChange(event) { - this.setState({ - feeAmount: event.target.value, - }); - } - - handleFeeCurrencyChange(event) { - this.setState({ - feeCurrency: event.target.value, - }); - } - - handleFeePrefChange(feeEnabled) { - this.setState({ - isFee: feeEnabled, - }); - } - - handleMetadataChange(event) { - /** - * This function is used for all metadata inputs that store the final value directly into state. - * The only exceptions are inputs related to license description and license URL, which require - * more complex logic and the final value is determined at submit time. - */ - this.setState({ - ["meta_" + event.target.name]: event.target.value, - }); - } - - handleLicenseTypeChange(event) { - this.setState({ - licenseType: event.target.value, - }); - } - - handleCopyrightNoticeChange(event) { - this.setState({ - copyrightNotice: event.target.value, - }); - } - - handleOtherLicenseDescriptionChange(event) { - this.setState({ - otherLicenseDescription: event.target.value, - }); - } - - handleOtherLicenseUrlChange(event) { - this.setState({ - otherLicenseUrl: event.target.value, - }); - } - - handleChannelChange(channelName) { - this.setState({ - channel: channelName, - }); - const nameChanged = () => this.nameChanged(this.state.rawName); - setTimeout(nameChanged.bind(this), 500, { once: true }); - } - - handleTOSChange(event) { - this.setState({ - tosAgree: event.target.checked, - }); - } - - handleCreateChannelClick(event) { - if (this.state.newChannelName.length < 5) { - this.refs.newChannelName.showError( - __("LBRY channel names must be at least 4 characters in length.") - ); - return; - } - - this.setState({ - creatingChannel: true, - }); - - const newChannelName = this.state.newChannelName; - lbry - .channel_new({ - channel_name: newChannelName, - amount: parseFloat(this.state.newChannelBid), - }) - .then( - () => { - setTimeout(() => { - this.setState({ - creatingChannel: false, - }); - - this._updateChannelList(newChannelName); - }, 10000); - }, - error => { - // TODO: better error handling - this.refs.newChannelName.showError( - __("Unable to create channel due to an internal error.") - ); - this.setState({ - creatingChannel: false, - }); - } - ); - } - - getLicense() { - switch (this.state.licenseType) { - case "copyright": - return this.state.copyrightNotice; - case "other": - return this.state.otherLicenseDescription; - default: - return this._meta_license.getSelectedElement().text; - } - } - - getLicenseUrl() { - switch (this.state.licenseType) { - case "copyright": - return ""; - case "other": - return this.state.otherLicenseUrl; - default: - return this._meta_license.getSelectedElement().getAttribute("data-url"); - } - } - - componentWillMount() { - this.props.fetchClaimListMine(); - this._updateChannelList(); - } - - onFileChange() { - if (this.refs.file.getValue()) { - this.setState({ hasFile: true }); - } else { - this.setState({ hasFile: false }); - } - } - - getNameBidHelpText() { - if ( - this.state.uri && - this.props.resolvingUris.indexOf(this.state.uri) !== -1 && - this.claim() === undefined - ) { - return __("Checking..."); - } else if (!this.state.name) { - return __("Select a URL for this publish."); - } else if (!this.claim()) { - return __("This URL is unused."); - } else if (this.myClaimExists() && !this.state.prefillDone) { - return ( - - {__("You already have a claim with this name.")}{" "} - this.handlePrefillClicked()} - /> - - ); - } else if (this.claim()) { - if (this.topClaimValue() === 1) { - return ( - - {__( - 'A deposit of at least one credit is required to win "%s". However, you can still get a permanent URL for any amount.', - this.state.name - )} - - ); - } else { - return ( - - {__( - 'A deposit of at least "%s" credits is required to win "%s". However, you can still get a permanent URL for any amount.', - this.topClaimValue(), - this.state.name - )} - - ); - } - } else { - return ""; - } - } - - closeModal() { - this.setState({ - modal: null, - }); - } - - render() { - const lbcInputHelp = __( - "This LBC remains yours and the deposit can be undone at any time." - ); - - return ( -
-
{ - this.handleSubmit(event); - }} - > -
-
-

{__("Content")}

-
- {__("What are you publishing?")} -
-
-
- { - this.onFileChange(event); - }} - helper={ - this.myClaimExists() - ? __( - "If you don't choose a file, the file from your existing claim will be used." - ) - : null - } - /> -
- {!this.state.hasFile && !this.myClaimExists() - ? null - :
-
- { - this.handleMetadataChange(event); - }} - /> -
-
- { - this.handleMetadataChange(event); - }} - /> -
-
- { - this.handleMetadataChange(event); - }} - /> -
-
- { - this.handleMetadataChange(event); - }} - > - - - - - - - - -
-
- { - this.handleMetadataChange(event); - }} - > - {/* */} - - - -
-
} -
- -
-
-

{__("Access")}

-
- {__("How much does this content cost?")} -
-
-
-
- -
- { - this.handleFeePrefChange(false); - }} - defaultChecked={!this.state.isFee} - /> - { - this.handleFeePrefChange(true); - }} - defaultChecked={this.state.isFee} - /> - - this.handleFeeAmountChange(event)} - />{" "} - { - this.handleFeeCurrencyChange(event); - }} - > - - - - - {this.state.isFee - ?
- {__( - "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." - )} -
- : ""} - { - this._meta_license = row; - }} - onChange={event => { - this.handleLicenseTypeChange(event); - }} - > - - - - - - - - - - - - {this.state.licenseType == "copyright" - ? { - this.handleCopyrightNoticeChange(event); - }} - /> - : null} - - {this.state.licenseType == "other" - ? { - this.handleOtherLicenseDescriptionChange(event); - }} - /> - : null} - - {this.state.licenseType == "other" - ? { - this.handleOtherLicenseUrlChange(event); - }} - /> - : null} -
-
- - - -
-
-

{__("Address")}

-
- {__("Where should this content permanently reside?")} - {" "} - . -
-
-
- { - this.handleNameChange(event); - }} - helper={this.getNameBidHelpText()} - /> - {this.myClaimExists() && !this.state.prefillDone - ? - {__("You already have a claim with this name.")}{" "} - this.handlePrefillClicked()} - /> - - : null} -
- {this.state.rawName - ?
- { - this.handleBidChange(event); - }} - value={this.state.bid} - placeholder={this.claim() ? this.topClaimValue() + 10 : 100} - helper={lbcInputHelp} - /> -
- : ""} -
- -
-
-

{__("Terms of Service")}

-
-
- - {__("I agree to the")} - {" "} - - - } - type="checkbox" - checked={this.state.tosAgree} - onChange={event => { - this.handleTOSChange(event); - }} - /> -
-
- -
- { - this.handleSubmit(event); - }} - disabled={this.state.submitting} - /> - - -
- - - { - this.handlePublishStartedConfirmed(event); - }} - > -

- {__("Your file has been published to LBRY at the address")} - {" "}{this.state.uri}! -

-

- {__( - 'The file will take a few minutes to appear for other LBRY users. Until then it will be listed as "pending" under your published files.' - )} -

-
- { - this.closeModal(event); - }} - > - {__( - "The following error occurred when attempting to publish your file" - )}: {this.state.errorMessage} - -
- ); - } -} - -class ChannelSection extends React.PureComponent { - constructor(props) { - super(props); - - this.state = { - newChannelName: "@", - newChannelBid: 10, - addingChannel: false, - }; - } - - handleChannelChange(event) { - const channel = event.target.value; - if (channel === "new") this.setState({ addingChannel: true }); - else { - this.setState({ addingChannel: false }); - this.props.handleChannelChange(event.target.value); - } - } - - handleNewChannelNameChange(event) { - const newChannelName = event.target.value.startsWith("@") - ? event.target.value - : "@" + event.target.value; - - if ( - newChannelName.length > 1 && - !lbryuri.isValidName(newChannelName.substr(1), false) - ) { - this.refs.newChannelName.showError( - __("LBRY channel names must contain only letters, numbers and dashes.") - ); - return; - } else { - this.refs.newChannelName.clearError(); - } - - this.setState({ - newChannelName, - }); - } - - handleNewChannelBidChange(event) { - this.setState({ - newChannelBid: event.target.value, - }); - } - - handleCreateChannelClick(event) { - if (this.state.newChannelName.length < 5) { - this.refs.newChannelName.showError( - __("LBRY channel names must be at least 4 characters in length.") - ); - return; - } - - this.setState({ - creatingChannel: true, - }); - - const newChannelName = this.state.newChannelName; - const amount = parseFloat(this.state.newChannelBid); - this.setState({ - creatingChannel: true, - }); - const success = (() => { - this.setState({ - creatingChannel: false, - addingChannel: false, - channel: newChannelName, - }); - this.props.handleChannelChange(newChannelName); - }).bind(this); - const failure = (err => { - this.setState({ - creatingChannel: false, - }); - this.refs.newChannelName.showError( - __("Unable to create channel due to an internal error.") - ); - }).bind(this); - this.props.createChannel(newChannelName, amount).then(success, failure); - } - - render() { - const lbcInputHelp = __( - "This LBC remains yours and the deposit can be undone at any time." - ); - - const { fetchingChannels, channels } = this.props; - - let channelContent = []; - if (channels.length > 0) { - channelContent.push( - - - {this.props.channels.map(({ name }) => - - )} - - - ); - if (fetchingChannels) { - channelContent.push( - - ); - } - } else if (fetchingChannels) { - channelContent.push( - - ); - } - - return ( -
-
-

{__("Identity")}

-
- {__("Who created this content?")} -
-
-
- {channelContent} -
- {this.state.addingChannel && -
- { - this.handleNewChannelNameChange(event); - }} - value={this.state.newChannelName} - /> - -
- -
-
} -
- ); - } -} +const PublishPage = props => { + return ; +}; export default PublishPage; From 7c3953ac517803c2ac348f420aca42dc99abe662 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 3 Jul 2017 14:53:56 +0700 Subject: [PATCH 07/26] Fix selectClaimForUriIsMine --- ui/js/selectors/claims.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index 3478c9fc4..aa19a6e80 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -51,7 +51,7 @@ export const makeSelectClaimForUri = () => { const selectClaimForUriIsMine = (state, props) => { const uri = lbryuri.normalize(props.uri); const claim = selectClaimsByUri(state)[uri]; - const myClaims = selectMyClaims(state); + const myClaims = selectMyClaimsRaw(state); return myClaims.has(claim.claim_id); }; From 35a5cb0918e813f67857fe5626621996cef04bab Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 4 Jul 2017 13:48:52 +0700 Subject: [PATCH 08/26] Fix file info selector --- ui/js/selectors/file_info.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index 5b0e4941b..4010b95c8 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -102,7 +102,7 @@ export const selectFileInfosPublished = createSelector( const fileInfo = byOutpoint[outpoint]; if (fileInfo) fileInfos.push(fileInfo); }); - return fileInfos; + return [...fileInfos, ...pendingPublish]; } ); From 6c6f1beb19b02faa03180c12f4d57295e7080b3e Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sat, 1 Jul 2017 18:03:51 +0700 Subject: [PATCH 09/26] Change redux-persist debounce to 10 seconds --- ui/js/store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/store.js b/ui/js/store.js index 8e6c11949..35f7ab2ec 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -102,7 +102,7 @@ const persistOptions = { // Order is important. Needs to be compressed last or other transforms can't // read the data transforms: [saveClaimsFilter, saveFileInfosFilter, compressor], - debounce: 1000, + debounce: 10000, storage: localForage, }; window.cacheStore = persistStore(reduxStore, persistOptions); From f3fdf5e84177cc894904c1146075dcdd24787cac Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 4 Jul 2017 18:05:35 +0700 Subject: [PATCH 10/26] Rename pending to fetching in selectors to avoid confusion --- ui/js/actions/file_info.js | 16 ++++++++-------- ui/js/page/fileListDownloaded/index.js | 4 ++-- ui/js/page/fileListDownloaded/view.jsx | 8 ++++---- ui/js/page/fileListPublished/index.js | 4 ++-- ui/js/page/fileListPublished/view.jsx | 8 ++++---- ui/js/reducers/claims.js | 4 ++-- ui/js/reducers/file_info.js | 26 ++------------------------ ui/js/selectors/claims.js | 4 ++-- ui/js/selectors/file_info.js | 16 ++++++++-------- 9 files changed, 34 insertions(+), 56 deletions(-) diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index 9d3861fb7..70db7244d 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -3,11 +3,11 @@ import lbry from "lbry"; import { doFetchClaimListMine } from "actions/content"; import { selectClaimsByUri, - selectClaimListMineIsPending, + selectIsFetchingClaimListMine, selectMyClaimsOutpoints, } from "selectors/claims"; import { - selectFileListIsPending, + selectIsFetchingFileList, selectFileInfosByOutpoint, selectUrisLoading, } from "selectors/file_info"; @@ -48,9 +48,9 @@ export function doFetchFileInfo(uri) { export function doFileList() { return function(dispatch, getState) { const state = getState(); - const isPending = selectFileListIsPending(state); + const isFetching = selectIsFetchingFileList(state); - if (!isPending) { + if (!isFetching) { dispatch({ type: types.FILE_LIST_STARTED, }); @@ -128,10 +128,10 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) { export function doFetchFileInfosAndPublishedClaims() { return function(dispatch, getState) { const state = getState(), - isClaimListMinePending = selectClaimListMineIsPending(state), - isFileInfoListPending = selectFileListIsPending(state); + isFetchingClaimListMine = selectIsFetchingClaimListMine(state), + isFetchingFileInfo = selectIsFetchingFileList(state); - dispatch(doFetchClaimListMine()); - dispatch(doFileList()); + if (!isFetchingClaimListMine) dispatch(doFetchClaimListMine()); + if (!isFetchingFileInfo) dispatch(doFileList()); }; } diff --git a/ui/js/page/fileListDownloaded/index.js b/ui/js/page/fileListDownloaded/index.js index 86d26d851..e51d5389f 100644 --- a/ui/js/page/fileListDownloaded/index.js +++ b/ui/js/page/fileListDownloaded/index.js @@ -3,7 +3,7 @@ import { connect } from "react-redux"; import { doFetchFileInfosAndPublishedClaims } from "actions/file_info"; import { selectFileInfosDownloaded, - selectFileListDownloadedOrPublishedIsPending, + selectIsFetchingFileListDownloadedOrPublished, } from "selectors/file_info"; import { doNavigate } from "actions/app"; import { doCancelAllResolvingUris } from "actions/content"; @@ -11,7 +11,7 @@ import FileListDownloaded from "./view"; const select = state => ({ fileInfos: selectFileInfosDownloaded(state), - isPending: selectFileListDownloadedOrPublishedIsPending(state), + isFetching: selectIsFetchingFileListDownloadedOrPublished(state), }); const perform = dispatch => ({ diff --git a/ui/js/page/fileListDownloaded/view.jsx b/ui/js/page/fileListDownloaded/view.jsx index 03665847c..c1501ec78 100644 --- a/ui/js/page/fileListDownloaded/view.jsx +++ b/ui/js/page/fileListDownloaded/view.jsx @@ -12,7 +12,7 @@ import SubHeader from "component/subHeader"; class FileListDownloaded extends React.PureComponent { componentWillMount() { - if (!this.props.isPending) this.props.fetchFileInfosDownloaded(); + if (!this.props.isFetching) this.props.fetchFileInfosDownloaded(); } componentWillUnmount() { @@ -20,13 +20,13 @@ class FileListDownloaded extends React.PureComponent { } render() { - const { fileInfos, isPending, navigate } = this.props; + const { fileInfos, isFetching, navigate } = this.props; let content; if (fileInfos && fileInfos.length > 0) { - content = ; + content = ; } else { - if (isPending) { + if (isFetching) { content = ; } else { content = ( diff --git a/ui/js/page/fileListPublished/index.js b/ui/js/page/fileListPublished/index.js index 7e5e349c3..9b8b2b80b 100644 --- a/ui/js/page/fileListPublished/index.js +++ b/ui/js/page/fileListPublished/index.js @@ -4,7 +4,7 @@ import { connect } from "react-redux"; import { doFetchFileInfosAndPublishedClaims } from "actions/file_info"; import { selectFileInfosPublished, - selectFileListDownloadedOrPublishedIsPending, + selectIsFetchingFileListDownloadedOrPublished, } from "selectors/file_info"; import { doClaimRewardType } from "actions/rewards"; import { doNavigate } from "actions/app"; @@ -13,7 +13,7 @@ import FileListPublished from "./view"; const select = state => ({ fileInfos: selectFileInfosPublished(state), - isPending: selectFileListDownloadedOrPublishedIsPending(state), + isFetching: selectIsFetchingFileListDownloadedOrPublished(state), }); const perform = dispatch => ({ diff --git a/ui/js/page/fileListPublished/view.jsx b/ui/js/page/fileListPublished/view.jsx index 822cfeb7d..fdcbe97cd 100644 --- a/ui/js/page/fileListPublished/view.jsx +++ b/ui/js/page/fileListPublished/view.jsx @@ -12,7 +12,7 @@ import SubHeader from "component/subHeader"; class FileListPublished extends React.PureComponent { componentWillMount() { - if (!this.props.isPending) this.props.fetchFileListPublished(); + if (!this.props.isFetching) this.props.fetchFileListPublished(); } componentDidUpdate() { @@ -24,7 +24,7 @@ class FileListPublished extends React.PureComponent { } render() { - const { fileInfos, isPending, navigate } = this.props; + const { fileInfos, isFetching, navigate } = this.props; let content; @@ -32,12 +32,12 @@ class FileListPublished extends React.PureComponent { content = ( ); } else { - if (isPending) { + if (isFetching) { content = ; } else { content = ( diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 75bfc4a52..3ee992f94 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -34,7 +34,7 @@ reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { reducers[types.FETCH_CLAIM_LIST_MINE_STARTED] = function(state, action) { return Object.assign({}, state, { - isClaimListMinePending: true, + isFetchingClaimListMine: true, }); }; @@ -50,7 +50,7 @@ reducers[types.FETCH_CLAIM_LIST_MINE_COMPLETED] = function(state, action) { }); return Object.assign({}, state, { - isClaimListMinePending: false, + isFetchingClaimListMine: false, myClaims: myClaims, byId, }); diff --git a/ui/js/reducers/file_info.js b/ui/js/reducers/file_info.js index fe6979045..e462f3cf6 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -6,7 +6,7 @@ const defaultState = {}; reducers[types.FILE_LIST_STARTED] = function(state, action) { return Object.assign({}, state, { - isFileListPending: true, + isFetchingFileList: true, }); }; @@ -22,7 +22,7 @@ reducers[types.FILE_LIST_COMPLETED] = function(state, action) { }); return Object.assign({}, state, { - isFileListPending: false, + isFetchingFileList: false, byOutpoint: newByOutpoint, pendingByOutpoint, }); @@ -171,28 +171,6 @@ reducers[types.PUBLISH_FAILED] = function(state, action) { }); }; -// reducers[types.PUBLISH_COMPLETED] = function(state, action) { -// const { claim } = action.data; -// const uri = lbryuri.build({ -// txid: claim.txId -// }) -// const newPendingPublish = { -// name, -// channel_name, -// claim_id: "pending_claim_" + uri, -// txid: "pending_" + uri, -// nout: 0, -// outpoint: "pending_" + uri + ":0", -// time: Date.now(), -// }; -// const fileInfos = Object.assign({}, state.fileInfos) -// fileInfos[newPendingPublish.outpoint] = newPendingPublish - -// return Object.assign({}, state, { -// fileInfos, -// }) -// } - export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index aa19a6e80..f0708cf86 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -100,9 +100,9 @@ export const makeSelectContentTypeForUri = () => { ); }; -export const selectClaimListMineIsPending = createSelector( +export const selectIsFetchingClaimListMine = createSelector( _selectState, - state => state.isClaimListMinePending + state => !!state.isFetchingClaimListMine ); export const selectMyClaimsRaw = createSelector( diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index 4010b95c8..7b8a10769 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -2,7 +2,7 @@ import lbry from "lbry"; import { createSelector } from "reselect"; import { selectClaimsByUri, - selectClaimListMineIsPending, + selectIsFetchingClaimListMine, selectMyClaimsOutpoints, } from "selectors/claims"; @@ -13,16 +13,16 @@ export const selectFileInfosByOutpoint = createSelector( state => state.byOutpoint || {} ); -export const selectFileListIsPending = createSelector( +export const selectIsFetchingFileList = createSelector( _selectState, - state => state.isFileListPending + state => !!state.isFetchingFileList ); -export const selectFileListDownloadedOrPublishedIsPending = createSelector( - selectFileListIsPending, - selectClaimListMineIsPending, - (isFileListPending, isClaimListMinePending) => - isFileListPending || isClaimListMinePending +export const selectIsFetchingFileListDownloadedOrPublished = createSelector( + selectIsFetchingFileList, + selectIsFetchingClaimListMine, + (isFetchingFileList, isFetchingClaimListMine) => + isFetchingFileList || isFetchingClaimListMine ); export const selectFileInfoForUri = (state, props) => { From 253932113f11e55694490f11c1f5b07299946328 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 4 Jul 2017 19:51:05 +0700 Subject: [PATCH 11/26] Fix adding new identities --- .../publishForm/internal/channelSection.jsx | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/ui/js/component/publishForm/internal/channelSection.jsx b/ui/js/component/publishForm/internal/channelSection.jsx index c0c4bf473..274f30bdf 100644 --- a/ui/js/component/publishForm/internal/channelSection.jsx +++ b/ui/js/component/publishForm/internal/channelSection.jsx @@ -2,6 +2,7 @@ import React from "react"; import lbryuri from "lbryuri"; import { FormField, FormRow } from "component/form.js"; import { BusyMessage } from "component/common"; +import Link from "component/link"; class ChannelSection extends React.PureComponent { constructor(props) { @@ -92,37 +93,31 @@ class ChannelSection extends React.PureComponent { "This LBC remains yours and the deposit can be undone at any time." ); - const { fetchingChannels, channels } = this.props; + const { fetchingChannels, channels = [] } = this.props; let channelContent = []; - if (channels.length > 0) { + channelContent.push( + + + {this.props.channels.map(({ name }) => + + )} + + + ); + if (fetchingChannels) { channelContent.push( - - - {this.props.channels.map(({ name }) => - - )} - - - ); - if (fetchingChannels) { - channelContent.push( - - ); - } - } else if (fetchingChannels) { - channelContent.push( - + ); } From 5012d44384e8da43cbba400f1d06caf468b72b93 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Wed, 5 Jul 2017 13:38:17 +0700 Subject: [PATCH 12/26] Start using claims instead of file info for published files --- ui/js/component/fileList/view.jsx | 4 +++- ui/js/page/fileListPublished/index.js | 14 +++++++------- ui/js/page/fileListPublished/view.jsx | 10 +++++----- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx index 20631b59e..8785972d9 100644 --- a/ui/js/component/fileList/view.jsx +++ b/ui/js/component/fileList/view.jsx @@ -67,7 +67,9 @@ class FileList extends React.PureComponent { const content = []; this._sortFunctions[sortBy](fileInfos).forEach(fileInfo => { - let uriParams = {}; + let uriParams = { + claimId: fileInfo.claim_id, + }; if (fileInfo.channel_name) { uriParams.channelName = fileInfo.channel_name; uriParams.contentName = fileInfo.name; diff --git a/ui/js/page/fileListPublished/index.js b/ui/js/page/fileListPublished/index.js index 9b8b2b80b..bd00db2ed 100644 --- a/ui/js/page/fileListPublished/index.js +++ b/ui/js/page/fileListPublished/index.js @@ -1,24 +1,24 @@ import React from "react"; import rewards from "rewards"; import { connect } from "react-redux"; -import { doFetchFileInfosAndPublishedClaims } from "actions/file_info"; +import { doFetchClaimListMine } from "actions/content"; import { - selectFileInfosPublished, - selectIsFetchingFileListDownloadedOrPublished, -} from "selectors/file_info"; + selectMyClaims, + selectIsFetchingClaimListMine, +} from "selectors/claims"; import { doClaimRewardType } from "actions/rewards"; import { doNavigate } from "actions/app"; import { doCancelAllResolvingUris } from "actions/content"; import FileListPublished from "./view"; const select = state => ({ - fileInfos: selectFileInfosPublished(state), - isFetching: selectIsFetchingFileListDownloadedOrPublished(state), + claims: selectMyClaims(state), + isFetching: selectIsFetchingClaimListMine(state), }); const perform = dispatch => ({ navigate: path => dispatch(doNavigate(path)), - fetchFileListPublished: () => dispatch(doFetchFileInfosAndPublishedClaims()), + fetchClaims: () => dispatch(doFetchClaimListMine()), claimFirstPublishReward: () => dispatch(doClaimRewardType(rewards.TYPE_FIRST_PUBLISH)), cancelResolvingUris: () => dispatch(doCancelAllResolvingUris()), diff --git a/ui/js/page/fileListPublished/view.jsx b/ui/js/page/fileListPublished/view.jsx index fdcbe97cd..a7b500b29 100644 --- a/ui/js/page/fileListPublished/view.jsx +++ b/ui/js/page/fileListPublished/view.jsx @@ -12,11 +12,11 @@ import SubHeader from "component/subHeader"; class FileListPublished extends React.PureComponent { componentWillMount() { - if (!this.props.isFetching) this.props.fetchFileListPublished(); + if (!this.props.isFetching) this.props.fetchClaims(); } componentDidUpdate() { - if (this.props.fileInfos.length > 0) this.props.claimFirstPublishReward(); + // if (this.props.claims.length > 0) this.props.fetchClaims(); } componentWillUnmount() { @@ -24,14 +24,14 @@ class FileListPublished extends React.PureComponent { } render() { - const { fileInfos, isFetching, navigate } = this.props; + const { claims, isFetching, navigate } = this.props; let content; - if (fileInfos && fileInfos.length > 0) { + if (claims && claims.length > 0) { content = ( From f9b9221471239fb541663cfb315accdb75e0eee5 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Thu, 6 Jul 2017 14:47:02 +0700 Subject: [PATCH 13/26] Fix show page being blank for unconfirmed claims --- ui/js/page/showPage/view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/page/showPage/view.jsx b/ui/js/page/showPage/view.jsx index 05b8b0b91..687cb5498 100644 --- a/ui/js/page/showPage/view.jsx +++ b/ui/js/page/showPage/view.jsx @@ -24,7 +24,7 @@ class ShowPage extends React.PureComponent { let innerContent = ""; - if (isResolvingUri && !claim) { + if ((isResolvingUri && !claim) || !claim) { innerContent = (
From 470f61da9d0eace14caac61d56bec590b8d7ac15 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Sat, 8 Jul 2017 15:03:12 +0700 Subject: [PATCH 14/26] commit little and often fail --- ui/js/actions/content.js | 6 ++- ui/js/actions/file_info.js | 22 ++++++---- ui/js/component/fileList/view.jsx | 1 - ui/js/component/fileTile/view.jsx | 11 ++++- ui/js/component/publishForm/view.jsx | 6 ++- ui/js/page/fileListPublished/index.js | 4 +- ui/js/reducers/claims.js | 62 +++++++++++++++++++++++++-- ui/js/reducers/file_info.js | 33 -------------- ui/js/selectors/claims.js | 21 +++++++-- ui/js/store.js | 9 ++-- 10 files changed, 114 insertions(+), 61 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index ca6f1850d..291dc4d81 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -396,14 +396,16 @@ export function doPublish(params) { } else { uri = lbryuri.build({ name: name }, false); } + const fakeId = "pending"; const pendingPublish = { name, channel_name, - claim_id: "pending_claim_" + uri, + claim_id: fakeId, txid: "pending_" + uri, nout: 0, - outpoint: "pending_" + uri + ":0", + outpoint: fakeId + ":0", time: Date.now(), + pending: true, }; dispatch({ diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index 70db7244d..eb4a6753f 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -102,14 +102,20 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) { }, }); - const success = () => { - dispatch({ - type: types.ABANDON_CLAIM_COMPLETED, - data: { - claimId: fileInfo.claim_id, - }, - }); - }; + // We need to run this after a few seconds or the claim gets added back + // to the store again by an already running fetch claims query. + const success = setTimeout( + () => { + dispatch({ + type: types.ABANDON_CLAIM_COMPLETED, + data: { + claimId: fileInfo.claim_id, + }, + }); + }, + 10000, + { once: true } + ); lbry.claim_abandon({ claim_id: fileInfo.claim_id }).then(success); } } diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx index 8785972d9..edd64b993 100644 --- a/ui/js/component/fileList/view.jsx +++ b/ui/js/component/fileList/view.jsx @@ -96,7 +96,6 @@ class FileList extends React.PureComponent { - {content} diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index c0c25a202..eb865d1ac 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -64,9 +64,16 @@ class FileTile extends React.PureComponent { const isClaimable = lbryuri.isClaimable(uri); const title = isClaimed && metadata && metadata.title ? metadata.title - : uri; + : lbryuri.parse(uri).contentName; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; - let onClick = () => navigate("/show", { uri }); + let onClick; + if (isClaimed) { + onClick = () => navigate("/show", { uri }); + } else { + onClick = () => { + return false; + }; + } let description = ""; if (isClaimed) { diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index 91fd5eb09..446081f6d 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -873,7 +873,11 @@ class PublishForm extends React.PureComponent { onClick={event => { this.handleSubmit(event); }} - disabled={this.state.submitting} + disabled={ + this.state.submitting || + (this.state.uri && + this.props.resolvingUris.indexOf(this.state.uri) !== -1) + } /> ({ - claims: selectMyClaims(state), + claims: selectMyClaimsWithoutChannels(state), isFetching: selectIsFetchingClaimListMine(state), }); diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 3ee992f94..8f60bf9a1 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -40,19 +40,39 @@ reducers[types.FETCH_CLAIM_LIST_MINE_STARTED] = function(state, action) { reducers[types.FETCH_CLAIM_LIST_MINE_COMPLETED] = function(state, action) { const { claims } = action.data; - const myClaims = new Set(state.myClaims); const byUri = Object.assign({}, state.claimsByUri); const byId = Object.assign({}, state.byId); + const pendingById = Object.assign({}, state.pendingById); + + const myClaims = new Set(claims.map(claim => claim.claim_id)); claims.forEach(claim => { - myClaims.add(claim.claim_id); byId[claim.claim_id] = claim; + + const pending = Object.values(pendingById).find(pendingClaim => { + return ( + pendingClaim.name == claim.name && + pendingClaim.channel_name == claim.channel_name + ); + }); + + if (pending) { + delete pendingById[pending.claim_id]; + } }); + // Remove old timed out pending publishes + const old = Object.values(pendingById) + .filter(pendingClaim => Date.now() - pendingClaim.time >= 20 * 60 * 1000) + .forEach(pendingClaim => { + delete pendingById[pendingClaim.claim_id]; + }); + return Object.assign({}, state, { isFetchingClaimListMine: false, myClaims: myClaims, byId, + pendingById, }); }; @@ -91,6 +111,17 @@ reducers[types.FETCH_CHANNEL_CLAIMS_COMPLETED] = function(state, action) { }); }; +reducers[types.ABANDON_CLAIM_STARTED] = function(state, action) { + const { claimId } = action.data; + const abandoningById = Object.assign({}, state.abandoningById); + + abandoningById[claimId] = true; + + return Object.assign({}, state, { + abandoningById, + }); +}; + reducers[types.ABANDON_CLAIM_COMPLETED] = function(state, action) { const { claimId } = action.data; const myClaims = new Set(state.myClaims); @@ -128,17 +159,42 @@ reducers[types.CREATE_CHANNEL_COMPLETED] = function(state, action) { }); }; +reducers[types.PUBLISH_STARTED] = function(state, action) { + const { pendingPublish } = action.data; + const pendingById = Object.assign({}, state.pendingById); + + pendingById[pendingPublish.claim_id] = pendingPublish; + + return Object.assign({}, state, { + pendingById, + }); +}; + reducers[types.PUBLISH_COMPLETED] = function(state, action) { - const { claim } = action.data; + const { claim, pendingPublish } = action.data; const byId = Object.assign({}, state.byId); const myClaims = new Set(state.myClaims); + const pendingById = Object.assign({}, state.pendingById); byId[claim.claim_id] = claim; myClaims.add(claim.claim_id); + delete pendingById[pendingPublish.claim_id]; return Object.assign({}, state, { byId, myClaims, + pendingById, + }); +}; + +reducers[types.PUBLISH_FAILED] = function(state, action) { + const { pendingPublish } = action.data; + const pendingById = Object.assign({}, state.pendingById); + + delete pendingById[pendingPublish.claim_id]; + + return Object.assign({}, state, { + pendingById, }); }; diff --git a/ui/js/reducers/file_info.js b/ui/js/reducers/file_info.js index e462f3cf6..c9e2817b3 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -138,39 +138,6 @@ reducers[types.LOADING_VIDEO_FAILED] = function(state, action) { }); }; -reducers[types.PUBLISH_STARTED] = function(state, action) { - const { pendingPublish } = action.data; - const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); - - pendingByOutpoint[pendingPublish.outpoint] = pendingPublish; - - return Object.assign({}, state, { - pendingByOutpoint, - }); -}; - -reducers[types.PUBLISH_COMPLETED] = function(state, action) { - const { pendingPublish } = action.data; - const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); - - delete pendingByOutpoint[pendingPublish.outpoint]; - - return Object.assign({}, state, { - pendingByOutpoint, - }); -}; - -reducers[types.PUBLISH_FAILED] = function(state, action) { - const { pendingPublish } = action.data; - const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); - - delete pendingByOutpoint[pendingPublish.outpoint]; - - return Object.assign({}, state, { - pendingByOutpoint, - }); -}; - export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index f0708cf86..3c10ec931 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -110,22 +110,37 @@ export const selectMyClaimsRaw = createSelector( state => new Set(state.myClaims) ); +export const selectAbandoningIds = createSelector(_selectState, state => + Object.keys(state.abandoningById || {}) +); + +export const selectPendingClaims = createSelector(_selectState, state => + Object.values(state.pendingById || {}) +); + export const selectMyClaims = createSelector( selectMyClaimsRaw, selectClaimsById, - (myClaimIds, byId) => { + selectAbandoningIds, + selectPendingClaims, + (myClaimIds, byId, abandoningIds, pendingClaims) => { const claims = []; myClaimIds.forEach(id => { const claim = byId[id]; - if (claim) claims.push(claim); + if (claim && abandoningIds.indexOf(id) == -1) claims.push(claim); }); - return claims; + return [...claims, ...pendingClaims]; } ); +export const selectMyClaimsWithoutChannels = createSelector( + selectMyClaims, + myClaims => myClaims.filter(claim => !claim.name.match(/^@/)) +); + export const selectMyClaimsOutpoints = createSelector( selectMyClaims, myClaims => { diff --git a/ui/js/store.js b/ui/js/store.js index 35f7ab2ec..124174bc1 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -91,17 +91,14 @@ const saveClaimsFilter = createFilter("claims", [ "claimsByUri", "myClaims", "myChannelClaims", -]); -const saveFileInfosFilter = createFilter("fileInfo", [ - "fileInfos", - "pendingByOutpoint", + "pendingById", ]); const persistOptions = { - whitelist: ["claims", "fileInfo"], + whitelist: ["claims"], // Order is important. Needs to be compressed last or other transforms can't // read the data - transforms: [saveClaimsFilter, saveFileInfosFilter, compressor], + transforms: [saveClaimsFilter, compressor], debounce: 10000, storage: localForage, }; From 76fe44e519635e30cf0d34a2da15a5eb6d1d86da Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 10 Jul 2017 21:44:49 +0700 Subject: [PATCH 15/26] Refactor back to lbry.js localStorage for pending publishes --- ui/js/actions/content.js | 61 +++++-------------------------- ui/js/component/fileTile/view.jsx | 9 +---- ui/js/lbry.js | 53 ++++++++++++--------------- ui/js/reducers/claims.js | 39 -------------------- ui/js/store.js | 8 +--- 5 files changed, 34 insertions(+), 136 deletions(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 291dc4d81..fb58d0a74 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -389,62 +389,19 @@ export function doCreateChannel(name, amount) { export function doPublish(params) { return function(dispatch, getState) { - let uri; - const { name, channel_name } = params; - if (channel_name) { - uri = lbryuri.build({ name: channel_name, path: name }, false); - } else { - uri = lbryuri.build({ name: name }, false); - } - const fakeId = "pending"; - const pendingPublish = { - name, - channel_name, - claim_id: fakeId, - txid: "pending_" + uri, - nout: 0, - outpoint: fakeId + ":0", - time: Date.now(), - pending: true, - }; - - dispatch({ - type: types.PUBLISH_STARTED, - data: { - params, - pendingPublish, - }, - }); - return new Promise((resolve, reject) => { const success = claim => { - claim.name = params.name; - claim.channel_name = params.channel_name; - dispatch({ - type: types.PUBLISH_COMPLETED, - data: { - claim, - uri, - pendingPublish, - }, - }); - dispatch(doFileList()); resolve(claim); - }; - const failure = error => { - dispatch({ - type: types.PUBLISH_FAILED, - data: { - error, - params, - uri, - pendingPublish, - }, - }); - reject(error); - }; - lbry.publish(params).then(success, failure); + if (claim === true) dispatch(doFetchClaimListMine()); + else + setTimeout(() => dispatch(doFetchClaimListMine()), 20000, { + once: true, + }); + }; + const failure = err => reject(err); + + lbry.publishDeprecated(params, null, success, failure); }); }; } diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index eb865d1ac..ef4654ce8 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -66,14 +66,7 @@ class FileTile extends React.PureComponent { ? metadata.title : lbryuri.parse(uri).contentName; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; - let onClick; - if (isClaimed) { - onClick = () => navigate("/show", { uri }); - } else { - onClick = () => { - return false; - }; - } + let onClick = () => navigate("/show", { uri }); let description = ""; if (isClaimed) { diff --git a/ui/js/lbry.js b/ui/js/lbry.js index f13665e09..7409bdde4 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -223,45 +223,38 @@ lbry.publishDeprecated = function( ) { lbry.publish(params).then( result => { - if (returnedPending) { - return; - } - - clearTimeout(returnPendingTimeout); + if (returnPendingTimeout) clearTimeout(returnPendingTimeout); publishedCallback(result); }, err => { - if (returnedPending) { - return; - } - - clearTimeout(returnPendingTimeout); + if (returnPendingTimeout) clearTimeout(returnPendingTimeout); errorCallback(err); } ); - let returnedPending = false; // Give a short grace period in case publish() returns right away or (more likely) gives an error - const returnPendingTimeout = setTimeout(() => { - returnedPending = true; + const returnPendingTimeout = setTimeout( + () => { + if (publishedCallback) { + savePendingPublish({ + name: params.name, + channel_name: params.channel_name, + }); + publishedCallback(true); + } - if (publishedCallback) { - savePendingPublish({ - name: params.name, - channel_name: params.channel_name, - }); - publishedCallback(true); - } - - if (fileListedCallback) { - const { name, channel_name } = params; - savePendingPublish({ - name: params.name, - channel_name: params.channel_name, - }); - fileListedCallback(true); - } - }, 2000); + if (fileListedCallback) { + const { name, channel_name } = params; + savePendingPublish({ + name: params.name, + channel_name: params.channel_name, + }); + fileListedCallback(true); + } + }, + 2000, + { once: true } + ); }; lbry.getClientSettings = function() { diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 8f60bf9a1..2470e5f9c 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -159,45 +159,6 @@ reducers[types.CREATE_CHANNEL_COMPLETED] = function(state, action) { }); }; -reducers[types.PUBLISH_STARTED] = function(state, action) { - const { pendingPublish } = action.data; - const pendingById = Object.assign({}, state.pendingById); - - pendingById[pendingPublish.claim_id] = pendingPublish; - - return Object.assign({}, state, { - pendingById, - }); -}; - -reducers[types.PUBLISH_COMPLETED] = function(state, action) { - const { claim, pendingPublish } = action.data; - const byId = Object.assign({}, state.byId); - const myClaims = new Set(state.myClaims); - const pendingById = Object.assign({}, state.pendingById); - - byId[claim.claim_id] = claim; - myClaims.add(claim.claim_id); - delete pendingById[pendingPublish.claim_id]; - - return Object.assign({}, state, { - byId, - myClaims, - pendingById, - }); -}; - -reducers[types.PUBLISH_FAILED] = function(state, action) { - const { pendingPublish } = action.data; - const pendingById = Object.assign({}, state.pendingById); - - delete pendingById[pendingPublish.claim_id]; - - return Object.assign({}, state, { - pendingById, - }); -}; - export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/store.js b/ui/js/store.js index 124174bc1..5eb84d4cf 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -86,13 +86,7 @@ const createStoreWithMiddleware = redux.compose( const reduxStore = createStoreWithMiddleware(enableBatching(reducers)); const compressor = createCompressor(); -const saveClaimsFilter = createFilter("claims", [ - "byId", - "claimsByUri", - "myClaims", - "myChannelClaims", - "pendingById", -]); +const saveClaimsFilter = createFilter("claims", ["byId", "claimsByUri"]); const persistOptions = { whitelist: ["claims"], From 443caa27406311fc8063e062a2f53d2fac61624e Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 11 Jul 2017 13:01:44 +0700 Subject: [PATCH 16/26] Fix promise reject when creating a channel --- ui/js/actions/content.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index fb58d0a74..f46a1fa33 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -380,7 +380,7 @@ export function doCreateChannel(name, amount) { resolve(channelClaim); }, err => { - resolve(err); + reject(err); } ); }); From 6f336c96c5a89e82d42f886964ab3fab49567272 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 11 Jul 2017 13:15:51 +0700 Subject: [PATCH 17/26] Remove unnecessary binds --- ui/js/component/publishForm/internal/channelSection.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/js/component/publishForm/internal/channelSection.jsx b/ui/js/component/publishForm/internal/channelSection.jsx index 274f30bdf..6c7802625 100644 --- a/ui/js/component/publishForm/internal/channelSection.jsx +++ b/ui/js/component/publishForm/internal/channelSection.jsx @@ -69,22 +69,22 @@ class ChannelSection extends React.PureComponent { this.setState({ creatingChannel: true, }); - const success = (() => { + const success = () => { this.setState({ creatingChannel: false, addingChannel: false, channel: newChannelName, }); this.props.handleChannelChange(newChannelName); - }).bind(this); - const failure = (err => { + }; + const failure = err => { this.setState({ creatingChannel: false, }); this.refs.newChannelName.showError( __("Unable to create channel due to an internal error.") ); - }).bind(this); + }; this.props.createChannel(newChannelName, amount).then(success, failure); } From d3c621ed1265645cf18cbea7bb10ee8801a71fe1 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 11 Jul 2017 14:44:45 +0700 Subject: [PATCH 18/26] use _SUCCEEDED for abandom claim and file list action types --- ui/js/actions/file_info.js | 4 ++-- ui/js/constants/action_types.js | 4 ++-- ui/js/reducers/claims.js | 2 +- ui/js/reducers/file_info.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index eb4a6753f..b36cb784e 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -57,7 +57,7 @@ export function doFileList() { lbry.file_list().then(fileInfos => { dispatch({ - type: types.FILE_LIST_COMPLETED, + type: types.FILE_LIST_SUCCEEDED, data: { fileInfos, }, @@ -107,7 +107,7 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) { const success = setTimeout( () => { dispatch({ - type: types.ABANDON_CLAIM_COMPLETED, + type: types.ABANDON_CLAIM_SUCCEEDED, data: { claimId: fileInfo.claim_id, }, diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 457761441..7d38568f3 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -47,7 +47,7 @@ export const FETCH_CLAIM_LIST_MINE_STARTED = "FETCH_CLAIM_LIST_MINE_STARTED"; export const FETCH_CLAIM_LIST_MINE_COMPLETED = "FETCH_CLAIM_LIST_MINE_COMPLETED"; export const FILE_LIST_STARTED = "FILE_LIST_STARTED"; -export const FILE_LIST_COMPLETED = "FILE_LIST_COMPLETED"; +export const FILE_LIST_SUCCEEDED = "FILE_LIST_SUCCEEDED"; export const FETCH_FILE_INFO_STARTED = "FETCH_FILE_INFO_STARTED"; export const FETCH_FILE_INFO_COMPLETED = "FETCH_FILE_INFO_COMPLETED"; export const FETCH_COST_INFO_STARTED = "FETCH_COST_INFO_STARTED"; @@ -63,7 +63,7 @@ export const FETCH_AVAILABILITY_STARTED = "FETCH_AVAILABILITY_STARTED"; export const FETCH_AVAILABILITY_COMPLETED = "FETCH_AVAILABILITY_COMPLETED"; export const FILE_DELETE = "FILE_DELETE"; export const ABANDON_CLAIM_STARTED = "ABANDON_CLAIM_STARTED"; -export const ABANDON_CLAIM_COMPLETED = "ABANDON_CLAIM_COMPLETED"; +export const ABANDON_CLAIM_SUCCEEDED = "ABANDON_CLAIM_SUCCEEDED"; export const FETCH_CHANNEL_LIST_MINE_STARTED = "FETCH_CHANNEL_LIST_MINE_STARTED"; export const FETCH_CHANNEL_LIST_MINE_COMPLETED = diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 2470e5f9c..adc0eb2ed 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -122,7 +122,7 @@ reducers[types.ABANDON_CLAIM_STARTED] = function(state, action) { }); }; -reducers[types.ABANDON_CLAIM_COMPLETED] = function(state, action) { +reducers[types.ABANDON_CLAIM_SUCCEEDED] = function(state, action) { const { claimId } = action.data; const myClaims = new Set(state.myClaims); const byId = Object.assign({}, state.byId); diff --git a/ui/js/reducers/file_info.js b/ui/js/reducers/file_info.js index c9e2817b3..500fbdf82 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -10,7 +10,7 @@ reducers[types.FILE_LIST_STARTED] = function(state, action) { }); }; -reducers[types.FILE_LIST_COMPLETED] = function(state, action) { +reducers[types.FILE_LIST_SUCCEEDED] = function(state, action) { const { fileInfos } = action.data; const newByOutpoint = Object.assign({}, state.byOutpoint); const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); From d0e3dd8f990a438e3177f6b770d58da017b0d11f Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 11 Jul 2017 14:57:56 +0700 Subject: [PATCH 19/26] Extract TruncatedMarkdown component --- ui/js/component/common.js | 36 -------------------- ui/js/component/fileCard/view.jsx | 8 ++--- ui/js/component/truncatedMarkdown/index.js | 5 +++ ui/js/component/truncatedMarkdown/view.jsx | 39 ++++++++++++++++++++++ 4 files changed, 46 insertions(+), 42 deletions(-) create mode 100644 ui/js/component/truncatedMarkdown/index.js create mode 100644 ui/js/component/truncatedMarkdown/view.jsx diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 57314bdd3..38dbf83fd 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -1,7 +1,5 @@ import React from "react"; -import ReactDOMServer from "react-dom/server"; import lbry from "../lbry.js"; -import ReactMarkdown from "react-markdown"; //component/icon.js export class Icon extends React.PureComponent { @@ -44,40 +42,6 @@ export class TruncatedText extends React.PureComponent { } } -export class TruncatedMarkdown extends React.PureComponent { - static propTypes = { - lines: React.PropTypes.number, - }; - - static defaultProps = { - lines: null, - }; - - transformMarkdown(text) { - // render markdown to html string then trim html tag - let htmlString = ReactDOMServer.renderToStaticMarkup( - - ); - var txt = document.createElement("textarea"); - txt.innerHTML = htmlString; - return txt.value.replace(/<(?:.|\n)*?>/gm, ""); - } - - render() { - let content = this.props.children && typeof this.props.children === "string" - ? this.transformMarkdown(this.props.children) - : this.props.children; - return ( - - {content} - - ); - } -} - export class BusyMessage extends React.PureComponent { static propTypes = { message: React.PropTypes.string, diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index 256bc9014..c6e179af7 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -1,15 +1,11 @@ import React from "react"; import lbryuri from "lbryuri.js"; import Link from "component/link"; -import { - Thumbnail, - TruncatedText, - Icon, - TruncatedMarkdown, -} from "component/common"; +import { Thumbnail, TruncatedText, Icon } from "component/common"; import FilePrice from "component/filePrice"; import UriIndicator from "component/uriIndicator"; import NsfwOverlay from "component/nsfwOverlay"; +import TruncatedMarkdown from "component/truncatedMarkdown"; class FileCard extends React.PureComponent { constructor(props) { diff --git a/ui/js/component/truncatedMarkdown/index.js b/ui/js/component/truncatedMarkdown/index.js new file mode 100644 index 000000000..7cec6defe --- /dev/null +++ b/ui/js/component/truncatedMarkdown/index.js @@ -0,0 +1,5 @@ +import React from "react"; +import { connect } from "react-redux"; +import TruncatedMarkdown from "./view"; + +export default connect()(TruncatedMarkdown); diff --git a/ui/js/component/truncatedMarkdown/view.jsx b/ui/js/component/truncatedMarkdown/view.jsx new file mode 100644 index 000000000..59e42d6af --- /dev/null +++ b/ui/js/component/truncatedMarkdown/view.jsx @@ -0,0 +1,39 @@ +import React from "react"; +import ReactMarkdown from "react-markdown"; +import ReactDOMServer from "react-dom/server"; + +class TruncatedMarkdown extends React.PureComponent { + static propTypes = { + lines: React.PropTypes.number, + }; + + static defaultProps = { + lines: null, + }; + + transformMarkdown(text) { + // render markdown to html string then trim html tag + let htmlString = ReactDOMServer.renderToStaticMarkup( + + ); + var txt = document.createElement("textarea"); + txt.innerHTML = htmlString; + return txt.value.replace(/<(?:.|\n)*?>/gm, ""); + } + + render() { + let content = this.props.children && typeof this.props.children === "string" + ? this.transformMarkdown(this.props.children) + : this.props.children; + return ( + + {content} + + ); + } +} + +export default TruncatedMarkdown; From f1c45775ab33b48f88eed0b41bd872035c7e7ecf Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 11 Jul 2017 15:30:28 +0700 Subject: [PATCH 20/26] Cleaner way of filtering published claims while abandoning --- ui/js/actions/file_info.js | 18 +++++------------- ui/js/reducers/claims.js | 8 ++++++-- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index b36cb784e..d21df8d2a 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -102,20 +102,12 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) { }, }); - // We need to run this after a few seconds or the claim gets added back - // to the store again by an already running fetch claims query. - const success = setTimeout( - () => { - dispatch({ - type: types.ABANDON_CLAIM_SUCCEEDED, - data: { - claimId: fileInfo.claim_id, - }, - }); + const success = dispatch({ + type: types.ABANDON_CLAIM_SUCCEEDED, + data: { + claimId: fileInfo.claim_id, }, - 10000, - { once: true } - ); + }); lbry.claim_abandon({ claim_id: fileInfo.claim_id }).then(success); } } diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index adc0eb2ed..9e072f1ce 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -43,8 +43,12 @@ reducers[types.FETCH_CLAIM_LIST_MINE_COMPLETED] = function(state, action) { const byUri = Object.assign({}, state.claimsByUri); const byId = Object.assign({}, state.byId); const pendingById = Object.assign({}, state.pendingById); - - const myClaims = new Set(claims.map(claim => claim.claim_id)); + const abandoningById = Object.assign({}, state.abandoningById); + const myClaims = new Set( + claims + .map(claim => claim.claim_id) + .filter(claimId => Object.keys(abandoningById).indexOf(claimId) === -1) + ); claims.forEach(claim => { byId[claim.claim_id] = claim; From 103e4302d46c5a05dbc905378c51605f9a1b162e Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Wed, 12 Jul 2017 13:36:08 +0700 Subject: [PATCH 21/26] Disable publish button if bid is too low --- ui/js/component/publishForm/view.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index 446081f6d..95abbaa96 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -876,7 +876,10 @@ class PublishForm extends React.PureComponent { disabled={ this.state.submitting || (this.state.uri && - this.props.resolvingUris.indexOf(this.state.uri) !== -1) + this.props.resolvingUris.indexOf(this.state.uri) !== -1) || + (this.claim() && + !this.topClaimIsMine() && + this.state.bid <= this.topClaimValue()) } /> Date: Wed, 12 Jul 2017 14:05:10 +0700 Subject: [PATCH 22/26] Drop notice component --- ui/js/component/notice.js | 27 --------------------------- ui/js/component/publishForm/view.jsx | 13 ++++--------- 2 files changed, 4 insertions(+), 36 deletions(-) delete mode 100644 ui/js/component/notice.js diff --git a/ui/js/component/notice.js b/ui/js/component/notice.js deleted file mode 100644 index 623ed51ec..000000000 --- a/ui/js/component/notice.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react"; - -export class Notice extends React.PureComponent { - static propTypes = { - isError: React.PropTypes.bool, - }; - - static defaultProps = { - isError: false, - }; - - render() { - return ( -
- {this.props.children} -
- ); - } -} - -export default Notice; diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index 95abbaa96..26eadf74b 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -4,7 +4,6 @@ import lbryuri from "lbryuri"; import { FormField, FormRow } from "component/form.js"; import Link from "component/link"; import Modal from "component/modal"; -import Notice from "component/notice"; import { BusyMessage } from "component/common"; import ChannelSection from "./internal/ChannelSection"; @@ -190,7 +189,7 @@ class PublishForm extends React.PureComponent { if (!claim) return true; if (!myClaimInfo) return false; - return myClaimInfo.amount >= claimInfo.amount; + return myClaimInfo.amount >= claim.amount; } myClaimInfo() { @@ -453,11 +452,7 @@ class PublishForm extends React.PureComponent { getNameBidHelpText() { if (this.state.prefillDone) { - return ( - - {__("Existing claim data was prefilled")} - - ); + return __("Existing claim data was prefilled"); } if ( @@ -472,13 +467,13 @@ class PublishForm extends React.PureComponent { return __("This URL is unused."); } else if (this.myClaimExists() && !this.state.prefillDone) { return ( - + {__("You already have a claim with this name.")}{" "} this.handlePrefillClicked()} /> - + ); } else if (this.claim()) { if (this.topClaimValue() === 1) { From cd9fcf550aaf7642e66ee49ed1b8cc0680bbba88 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Wed, 12 Jul 2017 14:05:44 +0700 Subject: [PATCH 23/26] Remove console.log --- ui/js/component/publishForm/view.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index 26eadf74b..61ef71b1c 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -292,7 +292,6 @@ class PublishForm extends React.PureComponent { newState.licenseType = licenseType; } - console.log(newState); this.setState(newState); } From 0419399decd3a8b19e5dc47ba39af985629c42a0 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Wed, 12 Jul 2017 14:28:28 +0700 Subject: [PATCH 24/26] Disable markdown editor side-by-side mode --- ui/js/component/form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/component/form.js b/ui/js/component/form.js index 6a65218e4..805afac70 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -44,7 +44,7 @@ export class FormField extends React.PureComponent { this._element = SimpleMDE; this._type = "textarea"; this._extraElementProps.options = { - hideIcons: ["guide", "heading", "image", "fullscreen"], + hideIcons: ["guide", "heading", "image", "fullscreen", "side-by-side"], }; } else if (formFieldFileSelectorTypes.includes(this.props.type)) { this._element = "input"; From d1eb8f5de375d91e03f77ef5afaf694a782dc2ec Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Wed, 12 Jul 2017 15:08:59 +0700 Subject: [PATCH 25/26] Stop old file infos from updated claims appearing in downloaded file list --- CHANGELOG.md | 1 + ui/js/component/fileList/view.jsx | 2 +- ui/js/page/fileListDownloaded/index.js | 8 ++++++++ ui/js/page/fileListDownloaded/view.jsx | 1 + ui/js/selectors/file_info.js | 19 +++++++++---------- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8f65e2ee..0dd35a53c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * Fixed bug with download notice when switching window focus * Fixed newly published files appearing twice * Fixed unconfirmed published files missing channel name + * Fixed old files from updated published claims appearing in downloaded list ### Deprecated * diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx index edd64b993..f910e9500 100644 --- a/ui/js/component/fileList/view.jsx +++ b/ui/js/component/fileList/view.jsx @@ -81,7 +81,7 @@ class FileList extends React.PureComponent { content.push( ({ fileInfos: selectFileInfosDownloaded(state), isFetching: selectIsFetchingFileListDownloadedOrPublished(state), + claims: selectMyClaimsWithoutChannels(state), + isFetchingClaims: selectIsFetchingClaimListMine(state), }); const perform = dispatch => ({ @@ -19,6 +26,7 @@ const perform = dispatch => ({ fetchFileInfosDownloaded: () => dispatch(doFetchFileInfosAndPublishedClaims()), cancelResolvingUris: () => dispatch(doCancelAllResolvingUris()), + fetchClaims: () => dispatch(doFetchClaimListMine()), }); export default connect(select, perform)(FileListDownloaded); diff --git a/ui/js/page/fileListDownloaded/view.jsx b/ui/js/page/fileListDownloaded/view.jsx index c1501ec78..8eb18e9d5 100644 --- a/ui/js/page/fileListDownloaded/view.jsx +++ b/ui/js/page/fileListDownloaded/view.jsx @@ -12,6 +12,7 @@ import SubHeader from "component/subHeader"; class FileListDownloaded extends React.PureComponent { componentWillMount() { + if (!this.props.isFetchingClaims) this.props.fetchClaims(); if (!this.props.isFetching) this.props.fetchFileInfosDownloaded(); } diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index 7b8a10769..ef469ab02 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -3,6 +3,7 @@ import { createSelector } from "reselect"; import { selectClaimsByUri, selectIsFetchingClaimListMine, + selectMyClaims, selectMyClaimsOutpoints, } from "selectors/claims"; @@ -76,19 +77,17 @@ export const selectFileInfosPendingPublish = createSelector( export const selectFileInfosDownloaded = createSelector( selectFileInfosByOutpoint, - selectMyClaimsOutpoints, - (byOutpoint, myClaimOutpoints) => { - const fileInfoList = []; - Object.values(byOutpoint).forEach(fileInfo => { - if ( + selectMyClaims, + (byOutpoint, myClaims) => { + return Object.values(byOutpoint).filter(fileInfo => { + const myClaimIds = myClaims.map(claim => claim.claim_id); + + return ( fileInfo && - myClaimOutpoints.indexOf(fileInfo.outpoint) === -1 && + myClaimIds.indexOf(fileInfo.claim_id) === -1 && (fileInfo.completed || fileInfo.written_bytes) - ) { - fileInfoList.push(fileInfo); - } + ); }); - return fileInfoList; } ); From 3950f7e9b24d3eae66862665ad1d679f4bd241f9 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 13 Jul 2017 10:19:27 -0400 Subject: [PATCH 26/26] fix import case --- ui/js/component/publishForm/view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index 61ef71b1c..da6e6eb39 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -5,7 +5,7 @@ import { FormField, FormRow } from "component/form.js"; import Link from "component/link"; import Modal from "component/modal"; import { BusyMessage } from "component/common"; -import ChannelSection from "./internal/ChannelSection"; +import ChannelSection from "./internal/channelSection"; class PublishForm extends React.PureComponent { constructor(props) {