From b43ed0b64f6b89d0d2fd289d1d929439e3f72645 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sat, 1 Jul 2017 21:33:23 +0100 Subject: [PATCH 01/84] Issue #311 playback input improvements --- ui/js/component/video/internal/player.jsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index 1a86c3b20..4385278c0 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -33,6 +33,7 @@ class VideoPlayer extends React.PureComponent { renderMediaCallback.bind(this) ); + document.addEventListener("keydown", this.togglePlay.bind(this)); const mediaElement = this.refs.media.children[0]; if (mediaElement) { mediaElement.addEventListener( @@ -42,6 +43,23 @@ class VideoPlayer extends React.PureComponent { once: true, } ); + mediaElement.addEventListener("click", this.togglePlay.bind(this)); + } + } + + togglePlay(event) { + // ignore all events except click and spacebar keydown + if ("keydown" === event.type && event.keyCode !== 32) { + return; + } + event.preventDefault(); + const mediaElement = this.refs.media.children[0]; + if (mediaElement) { + if (!mediaElement.paused) { + mediaElement.pause(); + } else { + mediaElement.play(); + } } } From fb3785feed9552c271442007ba43073676b1b0ec Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sat, 1 Jul 2017 21:48:12 +0100 Subject: [PATCH 02/84] Implemented the ability for the spacebar to initiate video playback --- .../component/video/internal/play-button.jsx | 26 ++++++++++++++----- ui/js/component/video/internal/player.jsx | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/ui/js/component/video/internal/play-button.jsx b/ui/js/component/video/internal/play-button.jsx index fb297904a..f696ab539 100644 --- a/ui/js/component/video/internal/play-button.jsx +++ b/ui/js/component/video/internal/play-button.jsx @@ -4,12 +4,23 @@ import Link from "component/link"; import Modal from "component/modal"; class VideoPlayButton extends React.PureComponent { + componentDidMount() { + document.addEventListener("keydown", this.onKeyDown.bind(this)); + } + onPurchaseConfirmed() { this.props.closeModal(); this.props.startPlaying(); this.props.loadVideo(this.props.uri); } + onKeyDown(event) { + if (event.keyCode === 32) { + event.preventDefault(); + this.onWatchClick(); + } + } + onWatchClick() { this.props.purchaseUri(this.props.uri).then(() => { if (!this.props.modal) { @@ -45,9 +56,10 @@ class VideoPlayButton extends React.PureComponent { isLoading || fileInfo === undefined || (fileInfo === null && (!costInfo || costInfo.cost === undefined)); - const icon = ["audio", "video"].indexOf(mediaType) !== -1 - ? "icon-play" - : "icon-folder-o"; + const icon = + ["audio", "video"].indexOf(mediaType) !== -1 + ? "icon-play" + : "icon-folder-o"; return (
@@ -73,9 +85,11 @@ class VideoPlayButton extends React.PureComponent { onConfirmed={this.onPurchaseConfirmed.bind(this)} onAborted={closeModal} > - {__("This will purchase")} {title} {__("for")} - {" "} - {" "}{__("credits")}. + {__("This will purchase")} {title} {__("for")}{" "} + + + {" "} + {__("credits")}. Date: Sat, 1 Jul 2017 22:57:14 +0100 Subject: [PATCH 03/84] Added requested event handling changes --- ui/js/component/video/internal/play-button.jsx | 16 ++++++++++------ ui/js/component/video/internal/player.jsx | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/ui/js/component/video/internal/play-button.jsx b/ui/js/component/video/internal/play-button.jsx index f696ab539..aa352ddf7 100644 --- a/ui/js/component/video/internal/play-button.jsx +++ b/ui/js/component/video/internal/play-button.jsx @@ -5,7 +5,12 @@ import Modal from "component/modal"; class VideoPlayButton extends React.PureComponent { componentDidMount() { - document.addEventListener("keydown", this.onKeyDown.bind(this)); + this.keyDownListener = this.onKeyDown.bind(this); + document.addEventListener("keydown", this.keyDownListener); + } + + componentWillUnmount() { + document.removeEventListener("keydown", this.keyDownListener); } onPurchaseConfirmed() { @@ -15,7 +20,7 @@ class VideoPlayButton extends React.PureComponent { } onKeyDown(event) { - if (event.keyCode === 32) { + if ("Space" === event.code) { event.preventDefault(); this.onWatchClick(); } @@ -56,10 +61,9 @@ class VideoPlayButton extends React.PureComponent { isLoading || fileInfo === undefined || (fileInfo === null && (!costInfo || costInfo.cost === undefined)); - const icon = - ["audio", "video"].indexOf(mediaType) !== -1 - ? "icon-play" - : "icon-folder-o"; + const icon = ["audio", "video"].indexOf(mediaType) !== -1 + ? "icon-play" + : "icon-folder-o"; return (
diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index 6b7c04eed..bf2a6dcc3 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -13,6 +13,8 @@ class VideoPlayer extends React.PureComponent { startedPlaying: false, unplayable: false, }; + + this.togglePlayListener = this.togglePlay.bind(this); } componentDidMount() { @@ -33,7 +35,7 @@ class VideoPlayer extends React.PureComponent { renderMediaCallback.bind(this) ); - document.addEventListener("keydown", this.togglePlay.bind(this)); + document.addEventListener("keydown", this.togglePlayListener); const mediaElement = this.refs.media.children[0]; if (mediaElement) { mediaElement.addEventListener( @@ -43,13 +45,21 @@ class VideoPlayer extends React.PureComponent { once: true, } ); - mediaElement.addEventListener("click", this.togglePlay.bind(this)); + mediaElement.addEventListener("click", this.togglePlayListener); + } + } + + componentWillUnmount() { + document.removeEventListener("keydown", this.togglePlayListener); + const mediaElement = this.refs.media.children[0]; + if (mediaElement) { + mediaElement.removeEventListener("click", this.togglePlayListener); } } togglePlay(event) { // ignore all events except click and spacebar keydown - if ("keydown" === event.type && event.keyCode !== 32) { + if ("keydown" === event.type && "Space" !== event.code) { return; } event.preventDefault(); From c111af6579d611104e72b792a5e5e7e7f01f5531 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sun, 2 Jul 2017 11:09:26 +0100 Subject: [PATCH 04/84] Issue #253 mp3 audio seek fix --- ui/js/component/video/internal/player.jsx | 29 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index 1a86c3b20..f1c20d0fb 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -16,11 +16,31 @@ class VideoPlayer extends React.PureComponent { } componentDidMount() { + const component = this; const container = this.refs.media; - const { mediaType } = this.props; + const { downloadPath, mediaType } = this.props; const loadedMetadata = e => { - this.setState({ hasMetadata: true, startedPlaying: true }); - this.refs.media.children[0].play(); + const media = this.refs.media.children[0]; + if ("audio" === media.tagName.toLowerCase()) { + // Load the entire audio file so that the length is available before playing + let xhr = new XMLHttpRequest(); + const xhrLoaded = () => { + if (xhr.status === 200) { + media.src = URL.createObjectURL(xhr.response); + + this.setState({ hasMetadata: true, startedPlaying: true }); + media.play(); + } + }; + + xhr.open("GET", downloadPath, true); + xhr.onload = xhrLoaded.bind(this); + xhr.responseType = "blob"; + xhr.send(); + } else { + this.setState({ hasMetadata: true, startedPlaying: true }); + media.play(); + } }; const renderMediaCallback = err => { if (err) this.setState({ unplayable: true }); @@ -66,8 +86,7 @@ class VideoPlayer extends React.PureComponent { }, }; } - - playableType() { + *playableType() { const { mediaType } = this.props; return ["audio", "video"].indexOf(mediaType) !== -1; From 2cc24318c10512d665b66c67d5947f84ab7036f1 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Mon, 3 Jul 2017 13:54:03 +0700 Subject: [PATCH 05/84] Stop progress bar code blowing up if window is defocused before download starts --- ui/js/util/setProgressBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/util/setProgressBar.js b/ui/js/util/setProgressBar.js index 304a54f17..b5d869f3d 100644 --- a/ui/js/util/setProgressBar.js +++ b/ui/js/util/setProgressBar.js @@ -1,6 +1,6 @@ const { remote } = require("electron"); const application = remote.app; -const win = remote.BrowserWindow.getFocusedWindow(); +const win = remote.getCurrentWindow(); const setProgressBar = progress => { win.setProgressBar(progress); From 48c10b6c53a937b2aa2a90708acf662efce9ed3d Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 4 Jul 2017 13:06:09 -0400 Subject: [PATCH 06/84] update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c6a2184e..9c1829aa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added - * + * Added option to release claim when deleting a file * ### Changed @@ -16,7 +16,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * ### Fixed - * + * Fixed bug with download notice when switching window focus * ### Deprecated From 52b404b552038da52096268c71e6a82840509126 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 6 Jul 2017 04:14:45 +0100 Subject: [PATCH 07/84] Issue #312 home page scroll position on back navigation --- ui/js/page/discover/view.jsx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index d6c506dfd..99387949d 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -3,6 +3,7 @@ import lbryio from "lbryio.js"; import lbryuri from "lbryuri"; import FileCard from "component/fileCard"; import { BusyMessage } from "component/common.js"; +import { setSession, getSession } from "utils"; import ToolTip from "component/tooltip.js"; const FeaturedCategory = props => { @@ -37,10 +38,27 @@ const FeaturedCategory = props => { class DiscoverPage extends React.PureComponent { componentWillMount() { this.props.fetchFeaturedUris(); + this.scrollListener = this.handleScroll.bind(this); + } + + componentDidMount() { + const scrollY = parseInt(getSession("prefs_scrolly")); + if (!isNaN(scrollY)) { + const restoreScrollPosition = () => { + window.scrollTo(0, scrollY); + }; + setTimeout(restoreScrollPosition, 100); + } + window.addEventListener("scroll", this.scrollListener); + } + + handleScroll() { + setSession("prefs_scrolly", window.scrollY); } componentWillUnmount() { this.props.cancelResolvingUris(); + window.removeEventListener("scroll", this.scrollListener); } render() { @@ -51,7 +69,7 @@ class DiscoverPage extends React.PureComponent { (featuredUris !== undefined && Object.keys(featuredUris).length === 0)); return ( -
+
{fetchingFeaturedUris && } {typeof featuredUris === "object" && From 89b58aa588bcf4f9e57ef1066a3999820f3506b3 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 6 Jul 2017 04:20:25 +0100 Subject: [PATCH 08/84] removed unused ref --- ui/js/page/discover/view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index 99387949d..b68d026b9 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -69,7 +69,7 @@ class DiscoverPage extends React.PureComponent { (featuredUris !== undefined && Object.keys(featuredUris).length === 0)); return ( -
+
{fetchingFeaturedUris && } {typeof featuredUris === "object" && From 49b1399926b327e9ffba001f3a524bf36114b2b3 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 6 Jul 2017 18:33:44 +0100 Subject: [PATCH 09/84] MP3 audio seek fix #2 --- ui/js/component/video/internal/player.jsx | 67 ++++++++++++----------- ui/js/component/video/view.jsx | 1 + 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index f1c20d0fb..91de346d5 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -5,6 +5,8 @@ import fs from "fs"; import LoadingScreen from "./loading-screen"; class VideoPlayer extends React.PureComponent { + static MP3_CONTENT_TYPES = ["audio/mpeg3", "audio/mpeg"]; + constructor(props) { super(props); @@ -18,40 +20,26 @@ class VideoPlayer extends React.PureComponent { componentDidMount() { const component = this; const container = this.refs.media; - const { downloadPath, mediaType } = this.props; + const { contentType, downloadPath, mediaType } = this.props; const loadedMetadata = e => { - const media = this.refs.media.children[0]; - if ("audio" === media.tagName.toLowerCase()) { - // Load the entire audio file so that the length is available before playing - let xhr = new XMLHttpRequest(); - const xhrLoaded = () => { - if (xhr.status === 200) { - media.src = URL.createObjectURL(xhr.response); - - this.setState({ hasMetadata: true, startedPlaying: true }); - media.play(); - } - }; - - xhr.open("GET", downloadPath, true); - xhr.onload = xhrLoaded.bind(this); - xhr.responseType = "blob"; - xhr.send(); - } else { - this.setState({ hasMetadata: true, startedPlaying: true }); - media.play(); - } + this.setState({ hasMetadata: true, startedPlaying: true }); + this.refs.media.children[0].play(); }; const renderMediaCallback = err => { if (err) this.setState({ unplayable: true }); }; - player.append( - this.file(), - container, - { autoplay: false, controls: true }, - renderMediaCallback.bind(this) - ); + // use renderAudio override for mp3 + if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { + this.renderAudio(container, false); + } else { + player.append( + this.file(), + container, + { autoplay: false, controls: true }, + renderMediaCallback.bind(this) + ); + } const mediaElement = this.refs.media.children[0]; if (mediaElement) { @@ -65,25 +53,42 @@ class VideoPlayer extends React.PureComponent { } } + renderAudio(container, autoplay) { + const { downloadPath } = this.props; + const audio = document.createElement("audio"); + audio.autoplay = autoplay; + audio.controls = true; + audio.src = downloadPath; + container.appendChild(audio); + } + componentDidUpdate() { - const { mediaType, downloadCompleted } = this.props; + const { contentType, downloadCompleted } = this.props; const { startedPlaying } = this.state; if (this.playableType() && !startedPlaying && downloadCompleted) { const container = this.refs.media.children[0]; - player.render(this.file(), container, { autoplay: true, controls: true }); + if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { + this.renderAudio(container, true); + } else { + player.render(this.file(), container, { + autoplay: true, + controls: true, + }); + } } } file() { const { downloadPath, filename } = this.props; - + const stat = fs.statSync(downloadPath); return { name: filename, createReadStream: opts => { return fs.createReadStream(downloadPath, opts); }, + length: stat.size, }; } *playableType() { diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx index c3cb499b9..10df6ea9a 100644 --- a/ui/js/component/video/view.jsx +++ b/ui/js/component/video/view.jsx @@ -98,6 +98,7 @@ class Video extends React.PureComponent { poster={poster} downloadPath={fileInfo.download_path} mediaType={mediaType} + contentType={contentType} downloadCompleted={fileInfo.completed} />)} {!isPlaying && From f61a8a4372a12396f4795d42199192fd8d47e8d8 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 6 Jul 2017 18:42:39 +0100 Subject: [PATCH 10/84] removed unused code --- ui/js/component/video/internal/player.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index eee756ccb..ab05daac0 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -137,13 +137,11 @@ class VideoPlayer extends React.PureComponent { file() { const { downloadPath, filename } = this.props; - const stat = fs.statSync(downloadPath); return { name: filename, createReadStream: opts => { return fs.createReadStream(downloadPath, opts); }, - length: stat.size, }; } *playableType() { From 913d642b523b513f14576c82ba78a994233e8bb3 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 6 Jul 2017 18:44:52 +0100 Subject: [PATCH 11/84] removed additional unused code --- ui/js/component/video/internal/player.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index ab05daac0..48fdaa25c 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -137,6 +137,7 @@ class VideoPlayer extends React.PureComponent { file() { const { downloadPath, filename } = this.props; + return { name: filename, createReadStream: opts => { @@ -144,7 +145,8 @@ class VideoPlayer extends React.PureComponent { }, }; } - *playableType() { + + playableType() { const { mediaType } = this.props; return ["audio", "video"].indexOf(mediaType) !== -1; From 95145b57e3da683c68d1accf8708dd5b508eba62 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 6 Jul 2017 20:03:31 +0100 Subject: [PATCH 12/84] additional tweaks to renderAudio --- ui/js/component/video/internal/player.jsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ui/js/component/video/internal/player.jsx b/ui/js/component/video/internal/player.jsx index 48fdaa25c..28cb554d0 100644 --- a/ui/js/component/video/internal/player.jsx +++ b/ui/js/component/video/internal/player.jsx @@ -22,7 +22,6 @@ class VideoPlayer extends React.PureComponent { } componentDidMount() { - const component = this; const container = this.refs.media; const { contentType, downloadPath, mediaType } = this.props; const loadedMetadata = e => { @@ -44,7 +43,7 @@ class VideoPlayer extends React.PureComponent { // use renderAudio override for mp3 if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { - this.renderAudio(container, false); + this.renderAudio(container, null, false); } else { player.append( this.file(), @@ -85,6 +84,11 @@ class VideoPlayer extends React.PureComponent { } renderAudio(container, autoplay) { + if (container.firstChild) { + container.firstChild.remove(); + } + + // clear the container const { downloadPath } = this.props; const audio = document.createElement("audio"); audio.autoplay = autoplay; @@ -125,7 +129,7 @@ class VideoPlayer extends React.PureComponent { const container = this.refs.media.children[0]; if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { - this.renderAudio(container, true); + this.renderAudio(this.refs.media, true); } else { player.render(this.file(), container, { autoplay: true, From 034860685947d3a8a2d80769bcc2506eaca29de5 Mon Sep 17 00:00:00 2001 From: Josh Finer Date: Thu, 6 Jul 2017 21:14:55 -0400 Subject: [PATCH 13/84] Update view.jsx --- ui/js/page/rewards/view.jsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx index 3b6964761..237994667 100644 --- a/ui/js/page/rewards/view.jsx +++ b/ui/js/page/rewards/view.jsx @@ -73,11 +73,6 @@ class RewardsPage extends React.PureComponent { content = (

{__("You are not eligible to claim rewards.")}

-

- {__("To become eligible, email")} - {" "}{" "} - {__("with a link to a public social media profile.")} -

); } else if (fetching) { From a7d8d1a7b0701d5c66ecbaa0b956958f50d09efa Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Mon, 10 Jul 2017 13:19:42 -0400 Subject: [PATCH 14/84] fix misnamed modal --- ui/js/component/app/view.jsx | 8 ++++---- ui/js/component/modalWelcome/index.js | 4 ++-- ui/js/component/modalWelcome/view.jsx | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index 727f4e39a..bc8264f21 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -3,8 +3,8 @@ import Router from "component/router"; import Header from "component/header"; import ModalError from "component/modalError"; import ModalDownloading from "component/modalDownloading"; -import UpgradeModal from "component/modalUpgrade"; -import WelcomeModal from "component/modalWelcome"; +import ModalUpgrade from "component/modalUpgrade"; +import ModalWelcome from "component/modalWelcome"; import lbry from "lbry"; import { Line } from "rc-progress"; @@ -32,10 +32,10 @@ class App extends React.PureComponent {
- {modal == "upgrade" && } + {modal == "upgrade" && } {modal == "downloading" && } {modal == "error" && } - {modal == "welcome" && } + {modal == "welcome" && }
); } diff --git a/ui/js/component/modalWelcome/index.js b/ui/js/component/modalWelcome/index.js index bbfc5b6a7..12ec5c0be 100644 --- a/ui/js/component/modalWelcome/index.js +++ b/ui/js/component/modalWelcome/index.js @@ -8,7 +8,7 @@ import { makeSelectClaimRewardError, makeSelectRewardByType, } from "selectors/rewards"; -import WelcomeModal from "./view"; +import ModalWelcome from "./view"; const select = (state, props) => { const selectHasClaimed = makeSelectHasClaimedReward(), @@ -25,4 +25,4 @@ const perform = dispatch => ({ closeModal: () => dispatch(doCloseModal()), }); -export default connect(select, perform)(WelcomeModal); +export default connect(select, perform)(ModalWelcome); diff --git a/ui/js/component/modalWelcome/view.jsx b/ui/js/component/modalWelcome/view.jsx index 9b6930bc6..82448c3ae 100644 --- a/ui/js/component/modalWelcome/view.jsx +++ b/ui/js/component/modalWelcome/view.jsx @@ -4,7 +4,7 @@ import { CreditAmount } from "component/common"; import Link from "component/link"; import RewardLink from "component/rewardLink"; -class WelcomeModal extends React.PureComponent { +class ModalWelcome extends React.PureComponent { render() { const { closeModal, hasClaimed, isRewardApproved, reward } = this.props; @@ -78,4 +78,4 @@ class WelcomeModal extends React.PureComponent { } } -export default WelcomeModal; +export default ModalWelcome; From 7c380ce01b7e9d45399f993a4ade39f9a4adce9d Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 11 Jul 2017 15:41:37 -0400 Subject: [PATCH 15/84] add transition to card hovers --- CHANGELOG.md | 2 +- ui/scss/component/_card.scss | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c1829aa4..bcbe49bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added * Added option to release claim when deleting a file - * + * Added transition to card hovers to smooth animation ### Changed * diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index 60b128afd..a112bd232 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -79,6 +79,9 @@ $card-link-scaling: 1.1; .card__link { display: block; } +.card--link { + transition: transform 120ms ease-in-out; +} .card--link:hover { position: relative; z-index: 1; From 877586a00ae43a5f8257ad43e0024d53335b0c71 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Wed, 12 Jul 2017 13:40:36 +0100 Subject: [PATCH 16/84] Issue #333 media switch fix --- ui/js/component/video/view.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx index 10df6ea9a..4d52883ab 100644 --- a/ui/js/component/video/view.jsx +++ b/ui/js/component/video/view.jsx @@ -14,6 +14,13 @@ class Video extends React.PureComponent { }; } + componentWillReceiveProps(nextProps) { + // reset playing state upon change path action + if (this.state.isPlaying) { + this.state.isPlaying = false; + } + } + startPlaying() { this.setState({ isPlaying: true, From 0198c991e3b4d6a93f10aaac55a35f6391ffbd6f Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Wed, 12 Jul 2017 21:56:18 +0100 Subject: [PATCH 17/84] check that fileInfo outpoint is different before resetting media play state --- ui/js/component/video/view.jsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx index 4d52883ab..f5cd375bd 100644 --- a/ui/js/component/video/view.jsx +++ b/ui/js/component/video/view.jsx @@ -16,11 +16,19 @@ class Video extends React.PureComponent { componentWillReceiveProps(nextProps) { // reset playing state upon change path action - if (this.state.isPlaying) { + if (!this.isMediaSame(nextProps) && this.state.isPlaying) { this.state.isPlaying = false; } } + isMediaSame(nextProps) { + return ( + this.props.fileInfo && + nextProps.fileInfo && + this.props.fileInfo.outpoint === nextProps.fileInfo.outpoint + ); + } + startPlaying() { this.setState({ isPlaying: true, 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 18/84] 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 19/84] 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 20/84] 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 21/84] 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 22/84] 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 23/84] 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 24/84] 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 25/84] 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 26/84] 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 27/84] 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 28/84] 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 29/84] 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 30/84] 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 31/84] 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 32/84] 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 33/84] 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 34/84] 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 35/84] 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 36/84] 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 37/84] 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 38/84] 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 39/84] 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 40/84] 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 41/84] 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 42/84] 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 43/84] 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) { From 187ac405fe44aa7551b91fb54fda4db0a2199cd3 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 13 Jul 2017 16:32:32 +0100 Subject: [PATCH 44/84] Modified to use redux store and return to previous scroll position only upon back navigation --- ui/js/actions/app.js | 11 +++++++++++ ui/js/constants/action_types.js | 1 + ui/js/page/discover/index.js | 4 ++++ ui/js/page/discover/view.jsx | 20 ++++++++++++-------- ui/js/reducers/app.js | 12 ++++++++++++ ui/js/selectors/app.js | 5 +++++ 6 files changed, 45 insertions(+), 8 deletions(-) diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 35049d435..5eba212f1 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -64,6 +64,17 @@ export function doHistoryBack() { if (!history.state) return; history.back(); + dispatch({ + type: types.HISTORY_BACK, + }); + }; +} + +export function doHistoryBackCompleted() { + return function(dispatch, getState) { + dispatch({ + type: types.HISTORY_BACK_COMPLETED, + }); }; } diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 216c84762..eca5dc928 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -2,6 +2,7 @@ export const CHANGE_PATH = "CHANGE_PATH"; export const OPEN_MODAL = "OPEN_MODAL"; export const CLOSE_MODAL = "CLOSE_MODAL"; export const HISTORY_BACK = "HISTORY_BACK"; +export const HISTORY_BACK_COMPLETED = "HISTORY_BACK_COMPLETED"; export const SHOW_SNACKBAR = "SHOW_SNACKBAR"; export const REMOVE_SNACKBAR_SNACK = "REMOVE_SNACKBAR_SNACK"; export const WINDOW_FOCUSED = "WINDOW_FOCUSED"; diff --git a/ui/js/page/discover/index.js b/ui/js/page/discover/index.js index be49b892d..eb8d4d0bc 100644 --- a/ui/js/page/discover/index.js +++ b/ui/js/page/discover/index.js @@ -1,20 +1,24 @@ import React from "react"; import { connect } from "react-redux"; +import { doHistoryBackCompleted } from "actions/app"; import { doFetchFeaturedUris, doCancelAllResolvingUris } from "actions/content"; import { selectFeaturedUris, selectFetchingFeaturedUris, } from "selectors/content"; +import { selectNavigatingBack } from "selectors/app"; import DiscoverPage from "./view"; const select = state => ({ featuredUris: selectFeaturedUris(state), fetchingFeaturedUris: selectFetchingFeaturedUris(state), + isNavigatingBack: selectNavigatingBack(state), }); const perform = dispatch => ({ fetchFeaturedUris: () => dispatch(doFetchFeaturedUris()), cancelResolvingUris: () => dispatch(doCancelAllResolvingUris()), + finishedNavigatingBack: () => dispatch(doHistoryBackCompleted()), }); export default connect(select, perform)(DiscoverPage); diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index b68d026b9..ea3e7b858 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -1,9 +1,9 @@ import React from "react"; +import lbry from "lbry.js"; import lbryio from "lbryio.js"; import lbryuri from "lbryuri"; import FileCard from "component/fileCard"; import { BusyMessage } from "component/common.js"; -import { setSession, getSession } from "utils"; import ToolTip from "component/tooltip.js"; const FeaturedCategory = props => { @@ -42,18 +42,22 @@ class DiscoverPage extends React.PureComponent { } componentDidMount() { - const scrollY = parseInt(getSession("prefs_scrolly")); - if (!isNaN(scrollY)) { - const restoreScrollPosition = () => { - window.scrollTo(0, scrollY); - }; - setTimeout(restoreScrollPosition, 100); + if (this.props.isNavigatingBack) { + const scrollY = parseInt(lbry.getClientSetting("prefs_scrolly")); + if (!isNaN(scrollY)) { + const restoreScrollPosition = () => { + window.scrollTo(0, scrollY); + }; + setTimeout(restoreScrollPosition, 100); + } + + this.props.finishedNavigatingBack(); } window.addEventListener("scroll", this.scrollListener); } handleScroll() { - setSession("prefs_scrolly", window.scrollY); + lbry.setClientSetting("prefs_scrolly", window.scrollY); } componentWillUnmount() { diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index fe8c9adae..f2c50d2ec 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -141,6 +141,18 @@ reducers[types.WINDOW_FOCUSED] = function(state, action) { }); }; +reducers[types.HISTORY_BACK] = function(state, action) { + return Object.assign({}, state, { + navigatingBack: true, + }); +}; + +reducers[types.HISTORY_BACK_COMPLETED] = function(state, action) { + return Object.assign({}, state, { + navigatingBack: false, + }); +}; + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index f6acd6d07..c36ccc18c 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -191,3 +191,8 @@ export const selectBadgeNumber = createSelector( _selectState, state => state.badgeNumber ); + +export const selectNavigatingBack = createSelector( + _selectState, + state => state.navigatingBack +); From f1b0c272cb4467ced737453324988738198657f6 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Fri, 14 Jul 2017 00:18:28 +0100 Subject: [PATCH 45/84] CardMedia component --- ui/js/component/cardMedia/index.js | 8 +++++ ui/js/component/cardMedia/view.jsx | 54 ++++++++++++++++++++++++++++++ ui/js/component/fileCard/view.jsx | 11 +++--- ui/js/component/fileTile/view.jsx | 16 +++------ ui/scss/component/_card.scss | 46 +++++++++++++++++++++++++ 5 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 ui/js/component/cardMedia/index.js create mode 100644 ui/js/component/cardMedia/view.jsx diff --git a/ui/js/component/cardMedia/index.js b/ui/js/component/cardMedia/index.js new file mode 100644 index 000000000..3616b0331 --- /dev/null +++ b/ui/js/component/cardMedia/index.js @@ -0,0 +1,8 @@ +import React from "react"; +import { connect } from "react-redux"; +import CardMedia from "./view"; + +const select = state => ({}); +const perform = dispatch => ({}); + +export default connect(select, perform)(CardMedia); diff --git a/ui/js/component/cardMedia/view.jsx b/ui/js/component/cardMedia/view.jsx new file mode 100644 index 000000000..d0f45f4ac --- /dev/null +++ b/ui/js/component/cardMedia/view.jsx @@ -0,0 +1,54 @@ +import React from "react"; + +class CardMedia extends React.PureComponent { + static AUTO_THUMB_CLASSES = [ + "purple", + "red", + "pink", + "indigo", + "blue", + "light-blue", + "cyan", + "teal", + "green", + "yellow", + "orange", + ]; + + componentWillMount() { + this.setState({ + autoThumbClass: + CardMedia.AUTO_THUMB_CLASSES[ + Math.floor(Math.random() * CardMedia.AUTO_THUMB_CLASSES.length) + ], + }); + } + + render() { + const { title, thumbnail } = this.props; + const atClass = this.state.autoThumbClass; + + if (thumbnail) { + return ( +
+ ); + } + + return ( +
+
+ {title && + title + .replace(/\s+/g, "") + .substring(0, Math.min(title.replace(" ", "").length, 5)) + .toUpperCase()} +
+
+ ); + } +} + +export default CardMedia; diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index c6e179af7..5a35d740d 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -1,5 +1,6 @@ import React from "react"; import lbryuri from "lbryuri.js"; +import CardMedia from "component/cardMedia"; import Link from "component/link"; import { Thumbnail, TruncatedText, Icon } from "component/common"; import FilePrice from "component/filePrice"; @@ -49,6 +50,9 @@ class FileCard extends React.PureComponent { const uri = lbryuri.normalize(this.props.uri); const title = metadata && metadata.title ? metadata.title : uri; + const thumbnail = metadata && metadata.thumbnail + ? metadata.thumbnail + : null; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; let description = ""; @@ -88,12 +92,7 @@ class FileCard extends React.PureComponent {
- {metadata && - metadata.thumbnail && -
} +
{description}
diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index ef4654ce8..0bdcce64c 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -1,6 +1,7 @@ import React from "react"; import lbry from "lbry.js"; import lbryuri from "lbryuri.js"; +import CardMedia from "component/cardMedia"; import Link from "component/link"; import { TruncatedText } from "component/common.js"; import FilePrice from "component/filePrice"; @@ -65,6 +66,9 @@ class FileTile extends React.PureComponent { const title = isClaimed && metadata && metadata.title ? metadata.title : lbryuri.parse(uri).contentName; + const thumbnail = metadata && metadata.thumbnail + ? metadata.thumbnail + : null; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; let onClick = () => navigate("/show", { uri }); @@ -98,17 +102,7 @@ class FileTile extends React.PureComponent { >
-
+
{!hidePrice ? : null} diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index a112bd232..da73b21bd 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -98,6 +98,52 @@ $card-link-scaling: 1.1; background-position: 50% 50%; } +.card__media--autothumb { + position: relative +} +.card__media--autothumb.purple { + background-color: #9c27b0 +} +.card__media--autothumb.red { + background-color: #e53935 +} +.card__media--autothumb.pink { + background-color: #e91e63 +} +.card__media--autothumb.indigo { + background-color: #3f51b5 +} +.card__media--autothumb.blue { + background-color: #2196f3 +} +.card__media--autothumb.light-blue { + background-color: #039be5 +} +.card__media--autothumb.cyan { + background-color: #00acc1 +} +.card__media--autothumb.teal { + background-color: #009688 +} +.card__media--autothumb.green { + background-color: #43a047 +} +.card__media--autothumb.yellow { + background-color: #ffeb3b +} +.card__media--autothumb.orange { + background-color: #ffa726 +} + +.card__media--autothumb .card__autothumb__text { + font-size: 2.0em; + width: 100%; + color: #ffffff; + text-align: center; + position: absolute; + top: 36% +} + $width-card-small: $spacing-vertical * 12; $height-card-small: $spacing-vertical * 15; From e918e9b17d57fd847f212bd779a3d82233afe161 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Fri, 14 Jul 2017 15:34:53 -0400 Subject: [PATCH 46/84] fix search bug and update changelog --- CHANGELOG.md | 6 ++++-- ui/js/component/fileListSearch/view.jsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dd35a53c..8b08dd887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,17 +11,19 @@ Web UI version numbers should always match the corresponding version of LBRY App * Added option to release claim when deleting a file * Added transition to card hovers to smooth animation * Support markdown makeup in claim description - * + * Replaced free speech flag (used when image is missing) with labeled color tiles ### Changed * Publishes now uses claims rather than files - * + * Publishing revamped. Editing claims is much easier. ### Fixed * 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 + * Fixed inappropriate text showing on searches + * Restored feedback on claim amounts ### Deprecated * diff --git a/ui/js/component/fileListSearch/view.jsx b/ui/js/component/fileListSearch/view.jsx index bd8efeb00..682d07e7c 100644 --- a/ui/js/component/fileListSearch/view.jsx +++ b/ui/js/component/fileListSearch/view.jsx @@ -67,7 +67,7 @@ class FileListSearch extends React.PureComponent { {results && !!results.length ? - : } + : !isSearching && }
); } From 25098bfab85af061615043bed432c2d6c5326125 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Fri, 14 Jul 2017 17:41:49 -0400 Subject: [PATCH 47/84] upgrade daemon --- build/DAEMON_URL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/DAEMON_URL b/build/DAEMON_URL index b3b581882..e7d65abfa 100644 --- a/build/DAEMON_URL +++ b/build/DAEMON_URL @@ -1 +1 @@ -https://github.com/lbryio/lbry/releases/download/v0.13.1/lbrynet-daemon-v0.13.1-OSNAME.zip +https://github.com/lbryio/lbry/releases/download/v0.14.1/lbrynet-daemon-v0.14.1-OSNAME.zip From a7195d12e1d2f217a49143971bde1fc29c771cd0 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 15 Jul 2017 15:15:17 -0400 Subject: [PATCH 48/84] discover page vertical jumping fix --- CHANGELOG.md | 1 + ui/js/page/discover/view.jsx | 17 ++++++++++++----- ui/scss/_gui.scss | 13 +++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b08dd887..9a6a82780 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * Fixed unconfirmed published files missing channel name * Fixed old files from updated published claims appearing in downloaded list * Fixed inappropriate text showing on searches + * Stop discover page from pushing jumping vertically while loading * Restored feedback on claim amounts ### Deprecated diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index d6c506dfd..c347a971f 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -48,13 +48,20 @@ class DiscoverPage extends React.PureComponent { const failedToLoad = !fetchingFeaturedUris && (featuredUris === undefined || - (featuredUris !== undefined && Object.keys(featuredUris).length === 0)); + (featuredUris !== undefined && Object.keys(featuredUris).length === 0)), + hasContent = + typeof featuredUris === "object" && Object.keys(featuredUris).length; return ( -
- {fetchingFeaturedUris && - } - {typeof featuredUris === "object" && +
+ {!hasContent && + fetchingFeaturedUris && + } + {hasContent && Object.keys(featuredUris).map( category => featuredUris[category].length diff --git a/ui/scss/_gui.scss b/ui/scss/_gui.scss index a902fe2da..057027771 100644 --- a/ui/scss/_gui.scss +++ b/ui/scss/_gui.scss @@ -47,6 +47,19 @@ body width: $width-page-constrained; } } +main.main--refreshing { + &:before { + $width: 30px; + position: absolute; + background: url('../img/busy.gif') no-repeat center center; + width: $width; + height: $spacing-vertical; + content: ""; + left: 50%; + margin-left: -1 / 2 * $width; + display: inline-block; + } +} .icon-fixed-width { /* This borrowed is from a component of Font Awesome we're not using, maybe add it? */ From 902b7f203c3a8260c788b0694d130079b584e073 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 15 Jul 2017 15:24:57 -0400 Subject: [PATCH 49/84] cleanup previous commit --- ui/js/page/discover/view.jsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index c347a971f..fc57a5ba8 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -45,12 +45,9 @@ class DiscoverPage extends React.PureComponent { render() { const { featuredUris, fetchingFeaturedUris } = this.props; - const failedToLoad = - !fetchingFeaturedUris && - (featuredUris === undefined || - (featuredUris !== undefined && Object.keys(featuredUris).length === 0)), - hasContent = - typeof featuredUris === "object" && Object.keys(featuredUris).length; + const hasContent = + typeof featuredUris === "object" && Object.keys(featuredUris).length, + failedToLoad = !fetchingFeaturedUris && !hasContent; return (
{!hasContent && fetchingFeaturedUris && - } + } {hasContent && Object.keys(featuredUris).map( category => From b3de4ceee9d2100175758af1ad91e56de574f2d8 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 15 Jul 2017 14:44:50 -0400 Subject: [PATCH 50/84] beginnings of new flow --- ui/js/actions/app.js | 6 ++++ ui/js/actions/content.js | 4 +-- ui/js/component/app/view.jsx | 12 ++++---- ui/js/component/fileActions/view.jsx | 7 ----- .../modalInsufficientCredits/index.js | 16 ++++++++++ .../modalInsufficientCredits/view.jsx | 24 +++++++++++++++ ui/js/component/modalUpgrade/view.jsx | 1 - ui/js/component/modalWelcome/index.js | 21 ++++++++++---- ui/js/component/modalWelcome/view.jsx | 29 ++++++++++++++----- .../component/video/internal/play-button.jsx | 7 ----- ui/js/constants/modal_types.js | 6 +++- ui/js/main.js | 16 +--------- ui/js/selectors/app.js | 5 ++++ ui/package.json | 3 +- 14 files changed, 104 insertions(+), 53 deletions(-) create mode 100644 ui/js/component/modalInsufficientCredits/index.js create mode 100644 ui/js/component/modalInsufficientCredits/view.jsx diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 35049d435..d812172b6 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -8,11 +8,13 @@ import { selectPageTitle, selectCurrentPage, selectCurrentParams, + selectWelcomeModalAcknowledged, } from "selectors/app"; import { doSearch } from "actions/search"; import { doFetchDaemonSettings } from "actions/settings"; import { doAuthenticate } from "actions/user"; import { doFileList } from "actions/file_info"; +import * as modals from "constants/modal_types"; const { remote, ipcRenderer, shell } = require("electron"); const path = require("path"); @@ -219,12 +221,16 @@ export function doAlertError(errorList) { export function doDaemonReady() { return function(dispatch, getState) { + const showWelcome = !selectWelcomeModalAcknowledged(getState()); dispatch(doAuthenticate()); dispatch({ type: types.DAEMON_READY, }); dispatch(doFetchDaemonSettings()); dispatch(doFileList()); + if (showWelcome) { + dispatch(doOpenModal(modals.WELCOME)); + } }; } diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index f46a1fa33..9d6f55408 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -15,8 +15,8 @@ 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"; +import * as modals from "constants/modal_types"; const { ipcRenderer } = require("electron"); @@ -293,7 +293,7 @@ export function doPurchaseUri(uri, purchaseModalName) { } if (cost > balance) { - dispatch(doOpenModal("notEnoughCredits")); + dispatch(doOpenModal(modals.INSUFFICIENT_CREDITS)); } else { dispatch(doOpenModal(purchaseModalName)); } diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index bc8264f21..8f4c54cb0 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -3,10 +3,11 @@ import Router from "component/router"; import Header from "component/header"; import ModalError from "component/modalError"; import ModalDownloading from "component/modalDownloading"; +import ModalInsufficientCredits from "component/modalInsufficientCredits"; import ModalUpgrade from "component/modalUpgrade"; import ModalWelcome from "component/modalWelcome"; import lbry from "lbry"; -import { Line } from "rc-progress"; +import * as modals from "constants/modal_types"; class App extends React.PureComponent { componentWillMount() { @@ -32,10 +33,11 @@ class App extends React.PureComponent {
- {modal == "upgrade" && } - {modal == "downloading" && } - {modal == "error" && } - {modal == "welcome" && } + {modal == modals.UPGRADE && } + {modal == modals.DOWNLOADING && } + {modal == modals.ERROR && } + {modal == modals.INSUFFICIENT_CREDITS && } + {modal == modals.WELCOME && }
); } diff --git a/ui/js/component/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index 53188a1f5..0d4dbbd6b 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -176,13 +176,6 @@ class FileActions extends React.PureComponent { {" "} {__("credits")}. - - {__("You don't have enough LBRY credits to pay for this stream.")} - ({}); + +const perform = dispatch => ({ + addFunds: () => { + dispatch(doNavigate("/rewards")); + dispatch(doCloseModal()); + }, + closeModal: () => dispatch(doCloseModal()), +}); + +export default connect(select, perform)(ModalInsufficientCredits); diff --git a/ui/js/component/modalInsufficientCredits/view.jsx b/ui/js/component/modalInsufficientCredits/view.jsx new file mode 100644 index 000000000..fd214cd11 --- /dev/null +++ b/ui/js/component/modalInsufficientCredits/view.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import { Modal } from "component/modal"; + +class ModalInsufficientCredits extends React.PureComponent { + render() { + const { addFunds, closeModal } = this.props; + + return ( + + {__("More LBRY credits are required to purchase this.")} + + ); + } +} + +export default ModalInsufficientCredits; diff --git a/ui/js/component/modalUpgrade/view.jsx b/ui/js/component/modalUpgrade/view.jsx index 544fd96b7..2d364bd31 100644 --- a/ui/js/component/modalUpgrade/view.jsx +++ b/ui/js/component/modalUpgrade/view.jsx @@ -1,6 +1,5 @@ import React from "react"; import { Modal } from "component/modal"; -import { downloadUpgrade, skipUpgrade } from "actions/app"; class ModalUpgrade extends React.PureComponent { render() { diff --git a/ui/js/component/modalWelcome/index.js b/ui/js/component/modalWelcome/index.js index 12ec5c0be..5ad4b0bc9 100644 --- a/ui/js/component/modalWelcome/index.js +++ b/ui/js/component/modalWelcome/index.js @@ -1,11 +1,11 @@ import React from "react"; import rewards from "rewards"; import { connect } from "react-redux"; -import { doCloseModal } from "actions/app"; +import { doCloseModal, doNavigate } from "actions/app"; +import { doSetClientSetting } from "actions/settings"; import { selectUserIsRewardApproved } from "selectors/user"; import { makeSelectHasClaimedReward, - makeSelectClaimRewardError, makeSelectRewardByType, } from "selectors/rewards"; import ModalWelcome from "./view"; @@ -21,8 +21,19 @@ const select = (state, props) => { }; }; -const perform = dispatch => ({ - closeModal: () => dispatch(doCloseModal()), -}); +const perform = dispatch => () => { + const closeModal = () => { + dispatch(doSetClientSetting("welcome_acknowledged", true)); + dispatch(doCloseModal()); + }; + + return { + verifyAccount: () => { + closeModal(); + dispatch(doNavigate("/rewards")); + }, + closeModal: closeModal, + }; +}; export default connect(select, perform)(ModalWelcome); diff --git a/ui/js/component/modalWelcome/view.jsx b/ui/js/component/modalWelcome/view.jsx index 82448c3ae..cfd3d2c67 100644 --- a/ui/js/component/modalWelcome/view.jsx +++ b/ui/js/component/modalWelcome/view.jsx @@ -6,7 +6,13 @@ import RewardLink from "component/rewardLink"; class ModalWelcome extends React.PureComponent { render() { - const { closeModal, hasClaimed, isRewardApproved, reward } = this.props; + const { + closeModal, + hasClaimed, + isRewardApproved, + reward, + verifyAccount, + } = this.props; return !hasClaimed ? @@ -29,13 +35,20 @@ class ModalWelcome extends React.PureComponent { {" "}{isRewardApproved ? __("Here's a nickel, kid.") : ""}

- {isRewardApproved - ? - : } + {isRewardApproved && + } + {!isRewardApproved && + } + {!isRewardApproved && + }
diff --git a/ui/js/component/video/internal/play-button.jsx b/ui/js/component/video/internal/play-button.jsx index d104fcdeb..f5223231c 100644 --- a/ui/js/component/video/internal/play-button.jsx +++ b/ui/js/component/video/internal/play-button.jsx @@ -78,13 +78,6 @@ class VideoPlayButton extends React.PureComponent { icon={icon} onClick={this.onWatchClick.bind(this)} /> - - {__("You don't have enough LBRY credits to pay for this stream.")} - { const initialState = app.store.getState(); -// import whyDidYouUpdate from "why-did-you-update"; -// if (env === "development") { -// /* -// https://github.com/garbles/why-did-you-update -// "A function that monkey patches React and notifies you in the console when -// potentially unnecessary re-renders occur." -// -// Just checks if props change between updates. Can be fixed by manually -// adding a check in shouldComponentUpdate or using React.PureComponent -// */ -// whyDidYouUpdate(React); -// } - var init = function() { function onDaemonReady() { window.sessionStorage.setItem("loaded", "y"); //once we've made it here once per session, we don't need to show splash again @@ -117,7 +103,7 @@ var init = function() { ReactDOM.render( -
+
, canvas ); diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index f6acd6d07..209b428c1 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -187,6 +187,11 @@ export const selectSnackBarSnacks = createSelector( snackBar => snackBar.snacks || [] ); +export const selectWelcomeModalAcknowledged = createSelector( + _selectState, + state => lbry.getClientSetting("welcome_acknowledged") +); + export const selectBadgeNumber = createSelector( _selectState, state => state.badgeNumber diff --git a/ui/package.json b/ui/package.json index 4c4986ec2..7e7491ed9 100644 --- a/ui/package.json +++ b/ui/package.json @@ -72,8 +72,7 @@ "webpack": "^2.6.1", "webpack-dev-server": "^2.4.4", "webpack-notifier": "^1.5.0", - "webpack-target-electron-renderer": "^0.4.0", - "why-did-you-update": "0.0.8" + "webpack-target-electron-renderer": "^0.4.0" }, "lint-staged": { "gitDir": "../", From d07a0bd8ad974c1c3f1cae1e5f45cb12ce8afc5f Mon Sep 17 00:00:00 2001 From: hackrush Date: Sun, 16 Jul 2017 15:35:57 +0530 Subject: [PATCH 51/84] A quick fix for oscuring cards for nsfw content. The download cards in page were not obscured when opening a link directly(e.g. lbry://jacki2). --- ui/js/page/filePage/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/page/filePage/index.js b/ui/js/page/filePage/index.js index f6629214c..ec151ab50 100644 --- a/ui/js/page/filePage/index.js +++ b/ui/js/page/filePage/index.js @@ -25,7 +25,7 @@ const makeSelect = () => { contentType: selectContentType(state, props), costInfo: selectCostInfo(state, props), metadata: selectMetadata(state, props), - showNsfw: !selectShowNsfw(state), + obscureNsfw: !selectShowNsfw(state), fileInfo: selectFileInfo(state, props), }); From 16abedbf3a27c039ea6b5642a4d10e05aea2ec60 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 16 Jul 2017 12:29:46 -0400 Subject: [PATCH 52/84] moderate progress --- ui/js/actions/app.js | 8 +-- ui/js/component/app/index.js | 32 +++++++-- ui/js/component/app/view.jsx | 32 ++++++++- ui/js/component/auth/index.js | 2 + ui/js/component/auth/view.jsx | 9 ++- ui/js/component/authOverlay/view.jsx | 2 +- ui/js/component/common.js | 5 +- ui/js/component/header/index.js | 4 +- ui/js/component/modalWelcome/view.jsx | 17 +++-- .../userEmailNew/{index.jsx => index.js} | 0 ui/js/component/userEmailNew/view.jsx | 1 - .../userEmailVerify/{index.jsx => index.js} | 0 ui/js/component/userEmailVerify/view.jsx | 1 - ui/js/component/userVerify/index.js | 21 ++++++ ui/js/component/userVerify/view.jsx | 69 +++++++++++++++++++ ui/js/lbry.js | 5 -- ui/js/page/rewards/index.js | 27 +++++--- ui/js/page/rewards/view.jsx | 56 +++++++++------ ui/js/selectors/user.js | 7 +- ui/js/utils.js | 4 ++ ui/scss/component/_form-field.scss | 4 -- 21 files changed, 228 insertions(+), 78 deletions(-) rename ui/js/component/userEmailNew/{index.jsx => index.js} (100%) rename ui/js/component/userEmailVerify/{index.jsx => index.js} (100%) create mode 100644 ui/js/component/userVerify/index.js create mode 100644 ui/js/component/userVerify/view.jsx diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index d812172b6..309e21b01 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -8,13 +8,11 @@ import { selectPageTitle, selectCurrentPage, selectCurrentParams, - selectWelcomeModalAcknowledged, } from "selectors/app"; import { doSearch } from "actions/search"; import { doFetchDaemonSettings } from "actions/settings"; import { doAuthenticate } from "actions/user"; import { doFileList } from "actions/file_info"; -import * as modals from "constants/modal_types"; const { remote, ipcRenderer, shell } = require("electron"); const path = require("path"); @@ -220,17 +218,13 @@ export function doAlertError(errorList) { } export function doDaemonReady() { - return function(dispatch, getState) { - const showWelcome = !selectWelcomeModalAcknowledged(getState()); + return function(dispatch) { dispatch(doAuthenticate()); dispatch({ type: types.DAEMON_READY, }); dispatch(doFetchDaemonSettings()); dispatch(doFileList()); - if (showWelcome) { - dispatch(doOpenModal(modals.WELCOME)); - } }; } diff --git a/ui/js/component/app/index.js b/ui/js/component/app/index.js index 4c966cb56..0d4e1c093 100644 --- a/ui/js/component/app/index.js +++ b/ui/js/component/app/index.js @@ -1,18 +1,40 @@ import React from "react"; import { connect } from "react-redux"; - import { selectCurrentModal } from "selectors/app"; -import { doCheckUpgradeAvailable, doAlertError } from "actions/app"; +import { + doCheckUpgradeAvailable, + doOpenModal, + doAlertError, +} from "actions/app"; import { doUpdateBalance } from "actions/wallet"; +import { selectWelcomeModalAcknowledged } from "selectors/app"; +import rewards from "rewards"; +import { + selectFetchingRewards, + makeSelectHasClaimedReward, +} from "selectors/rewards"; +import { selectUser } from "selectors/user"; import App from "./view"; +import * as modals from "constants/modal_types"; -const select = state => ({ - modal: selectCurrentModal(state), -}); +const select = (state, props) => { + const selectHasClaimed = makeSelectHasClaimedReward(); + + return { + modal: selectCurrentModal(state), + isWelcomeAcknowledged: selectWelcomeModalAcknowledged(state), + isFetchingRewards: selectFetchingRewards(state), + isWelcomeRewardClaimed: selectHasClaimed(state, { + reward_type: rewards.TYPE_NEW_USER, + }), + user: selectUser(state), + }; +}; const perform = dispatch => ({ alertError: errorList => dispatch(doAlertError(errorList)), checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()), + openWelcomeModal: () => dispatch(doOpenModal(modals.WELCOME)), updateBalance: balance => dispatch(doUpdateBalance(balance)), }); diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index 8f4c54cb0..7c6b17eca 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -11,17 +11,43 @@ import * as modals from "constants/modal_types"; class App extends React.PureComponent { componentWillMount() { + const { alertError, checkUpgradeAvailable, updateBalance } = this.props; + document.addEventListener("unhandledError", event => { - this.props.alertError(event.detail); + alertError(event.detail); }); if (!this.props.upgradeSkipped) { - this.props.checkUpgradeAvailable(); + checkUpgradeAvailable(); } lbry.balanceSubscribe(balance => { - this.props.updateBalance(balance); + updateBalance(balance); }); + + this.showWelcome(this.props); + } + + componentWillReceiveProps(nextProps) { + this.showWelcome(nextProps); + } + + showWelcome(props) { + const { + isFetchingRewards, + isWelcomeAcknowledged, + isWelcomeRewardClaimed, + openWelcomeModal, + user, + } = props; + + if ( + !isWelcomeAcknowledged && + user && + (!isFetchingRewards || !isWelcomeRewardClaimed) + ) { + openWelcomeModal(); + } } render() { diff --git a/ui/js/component/auth/index.js b/ui/js/component/auth/index.js index 37af9f90f..f448d6e82 100644 --- a/ui/js/component/auth/index.js +++ b/ui/js/component/auth/index.js @@ -4,12 +4,14 @@ import { selectAuthenticationIsPending, selectEmailToVerify, selectUserIsVerificationCandidate, + selectUser, } from "selectors/user"; import Auth from "./view"; const select = state => ({ isPending: selectAuthenticationIsPending(state), email: selectEmailToVerify(state), + user: selectUser(state), isVerificationCandidate: selectUserIsVerificationCandidate(state), }); diff --git a/ui/js/component/auth/view.jsx b/ui/js/component/auth/view.jsx index 551113ffa..1c49c513f 100644 --- a/ui/js/component/auth/view.jsx +++ b/ui/js/component/auth/view.jsx @@ -2,17 +2,20 @@ import React from "react"; import { BusyMessage } from "component/common"; import UserEmailNew from "component/userEmailNew"; import UserEmailVerify from "component/userEmailVerify"; +import UserVerify from "component/userVerify"; export class Auth extends React.PureComponent { render() { - const { isPending, email, isVerificationCandidate } = this.props; + const { email, isPending, isVerificationCandidate, user } = this.props; if (isPending) { return ; - } else if (!email) { + } else if (user && !user.has_verified_email && !email) { return ; - } else if (isVerificationCandidate) { + } else if (user && !user.has_verified_email) { return ; + } else if (user && !user.is_identity_verified) { + return ; } else { return {__("No further steps.")}; } diff --git a/ui/js/component/authOverlay/view.jsx b/ui/js/component/authOverlay/view.jsx index 753fe2fe4..fe8b9151d 100644 --- a/ui/js/component/authOverlay/view.jsx +++ b/ui/js/component/authOverlay/view.jsx @@ -54,7 +54,7 @@ export class AuthOverlay extends React.PureComponent { ? "" :
{!hasEmail && this.state.showNoEmailConfirm - ?
+ ?

{__( "If you continue without an email, you will be ineligible to earn free LBC rewards, as well as unable to receive security related communications." diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 38dbf83fd..8e7279248 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -1,4 +1,5 @@ import React from "react"; +import { formatCredits } from "utils"; import lbry from "../lbry.js"; //component/icon.js @@ -78,7 +79,7 @@ export class CreditAmount extends React.PureComponent { }; render() { - const formattedAmount = lbry.formatCredits( + const formattedAmount = formatCredits( this.props.amount, this.props.precision ); @@ -140,7 +141,7 @@ export class Address extends React.PureComponent { }} style={addressStyle} readOnly="readonly" - value={this.props.address} + value={this.props.address || ""} /> ); } diff --git a/ui/js/component/header/index.js b/ui/js/component/header/index.js index d05e7800b..eda8923d3 100644 --- a/ui/js/component/header/index.js +++ b/ui/js/component/header/index.js @@ -1,12 +1,12 @@ import React from "react"; -import lbry from "lbry"; +import { formatCredits } from "utils"; import { connect } from "react-redux"; import { selectBalance } from "selectors/wallet"; import { doNavigate, doHistoryBack } from "actions/app"; import Header from "./view"; const select = state => ({ - balance: lbry.formatCredits(selectBalance(state), 1), + balance: formatCredits(selectBalance(state), 1), publish: __("Publish"), }); diff --git a/ui/js/component/modalWelcome/view.jsx b/ui/js/component/modalWelcome/view.jsx index cfd3d2c67..3dec74cfa 100644 --- a/ui/js/component/modalWelcome/view.jsx +++ b/ui/js/component/modalWelcome/view.jsx @@ -31,8 +31,11 @@ class ModalWelcome extends React.PureComponent { )}

- {__("Thank you for making content freedom possible!")} - {" "}{isRewardApproved ? __("Here's a nickel, kid.") : ""} + {__("Please have")} {" "} + {reward && + } + {!reward && {__("??")}} + {" "} {__("as a thank you for building content freedom.")}

{isRewardApproved && @@ -40,15 +43,11 @@ class ModalWelcome extends React.PureComponent { {!isRewardApproved && } {!isRewardApproved && - } + }
diff --git a/ui/js/component/userEmailNew/index.jsx b/ui/js/component/userEmailNew/index.js similarity index 100% rename from ui/js/component/userEmailNew/index.jsx rename to ui/js/component/userEmailNew/index.js diff --git a/ui/js/component/userEmailNew/view.jsx b/ui/js/component/userEmailNew/view.jsx index 5391bdb3f..cc553f63e 100644 --- a/ui/js/component/userEmailNew/view.jsx +++ b/ui/js/component/userEmailNew/view.jsx @@ -27,7 +27,6 @@ class UserEmailNew extends React.PureComponent { return (
{ this.handleSubmit(event); }} diff --git a/ui/js/component/userEmailVerify/index.jsx b/ui/js/component/userEmailVerify/index.js similarity index 100% rename from ui/js/component/userEmailVerify/index.jsx rename to ui/js/component/userEmailVerify/index.js diff --git a/ui/js/component/userEmailVerify/view.jsx b/ui/js/component/userEmailVerify/view.jsx index 5aa656a61..de9cda5c3 100644 --- a/ui/js/component/userEmailVerify/view.jsx +++ b/ui/js/component/userEmailVerify/view.jsx @@ -27,7 +27,6 @@ class UserEmailVerify extends React.PureComponent { return ( { this.handleSubmit(event); }} diff --git a/ui/js/component/userVerify/index.js b/ui/js/component/userVerify/index.js new file mode 100644 index 000000000..b24dbdb8f --- /dev/null +++ b/ui/js/component/userVerify/index.js @@ -0,0 +1,21 @@ +import React from "react"; +import { connect } from "react-redux"; +import { doUserEmailVerify } from "actions/user"; +import { + selectEmailVerifyIsPending, + selectEmailToVerify, + selectEmailVerifyErrorMessage, +} from "selectors/user"; +import UserVerify from "./view"; + +const select = state => ({ + isPending: selectEmailVerifyIsPending(state), + email: selectEmailToVerify(state), + errorMessage: selectEmailVerifyErrorMessage(state), +}); + +const perform = dispatch => ({ + verifyUserEmail: code => dispatch(doUserEmailVerify(code)), +}); + +export default connect(select, perform)(UserVerify); diff --git a/ui/js/component/userVerify/view.jsx b/ui/js/component/userVerify/view.jsx new file mode 100644 index 000000000..4ff9e1192 --- /dev/null +++ b/ui/js/component/userVerify/view.jsx @@ -0,0 +1,69 @@ +import React from "react"; +import Link from "component/link"; +import { FormRow } from "component/form.js"; + +class UserVerify extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + code: "", + }; + } + + handleCodeChanged(event) { + this.setState({ + code: event.target.value, + }); + } + + handleSubmit(event) { + event.preventDefault(); + this.props.verifyUserEmail(this.state.code); + } + + render() { + const { errorMessage, isPending } = this.props; + return

VERIFY

; + return ( + { + this.handleSubmit(event); + }} + > + zzzzzzzzzzzzzzzzzzzzzzzzzzzzz + { + this.handleCodeChanged(event); + }} + errorMessage={errorMessage} + /> + {/* render help separately so it always shows */} +
+

+ {__("Email")}{" "} + {" "} + {__("if you did not receive or are having trouble with your code.")} +

+
+
+ { + this.handleSubmit(event); + }} + /> +
+ + ); + } +} + +export default UserVerify; diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 7409bdde4..6b4730762 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -288,11 +288,6 @@ lbry.setClientSetting = function(setting, value) { return localStorage.setItem("setting_" + setting, JSON.stringify(value)); }; -//utilities -lbry.formatCredits = function(amount, precision) { - return amount.toFixed(precision || 1).replace(/\.?0+$/, ""); -}; - lbry.formatName = function(name) { // Converts LBRY name to standard format (all lower case, no special characters, spaces replaced by dashes) name = name.replace("/s+/g", "-"); diff --git a/ui/js/page/rewards/index.js b/ui/js/page/rewards/index.js index 8e7abb1fc..14029ff26 100644 --- a/ui/js/page/rewards/index.js +++ b/ui/js/page/rewards/index.js @@ -1,22 +1,31 @@ import React from "react"; import { connect } from "react-redux"; -import { doNavigate } from "actions/app"; -import { selectFetchingRewards, selectRewards } from "selectors/rewards"; +import { + makeSelectRewardByType, + selectFetchingRewards, + selectRewards, +} from "selectors/rewards"; import { selectUserIsRewardEligible, selectUserHasEmail, selectUserIsVerificationCandidate, } from "selectors/user"; import { doRewardList } from "actions/rewards"; +import rewards from "rewards"; import RewardsPage from "./view"; -const select = state => ({ - fetching: selectFetchingRewards(state), - rewards: selectRewards(state), - hasEmail: selectUserHasEmail(state), - isEligible: selectUserIsRewardEligible(state), - isVerificationCandidate: selectUserIsVerificationCandidate(state), -}); +const select = (state, props) => { + const selectReward = makeSelectRewardByType(); + + return { + fetching: selectFetchingRewards(state), + rewards: selectRewards(state), + hasEmail: selectUserHasEmail(state), + isEligible: selectUserIsRewardEligible(state), + isVerificationCandidate: selectUserIsVerificationCandidate(state), + newUserReward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), + }; +}; const perform = dispatch => ({ fetchRewards: () => dispatch(doRewardList()), diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx index 237994667..67c8b1a27 100644 --- a/ui/js/page/rewards/view.jsx +++ b/ui/js/page/rewards/view.jsx @@ -1,9 +1,7 @@ import React from "react"; -import lbryio from "lbryio"; import { BusyMessage, CreditAmount, Icon } from "component/common"; import SubHeader from "component/subHeader"; import Auth from "component/auth"; -import Link from "component/link"; import RewardLink from "component/rewardLink"; const RewardTile = props => { @@ -41,7 +39,9 @@ class RewardsPage extends React.PureComponent { fetchRewards(props) { const { fetching, rewards, fetchRewards } = props; - if (!fetching && Object.keys(rewards).length < 1) fetchRewards(); + if (!fetching && (!rewards || !rewards.length)) { + fetchRewards(); + } } render() { @@ -51,6 +51,7 @@ class RewardsPage extends React.PureComponent { isVerificationCandidate, hasEmail, rewards, + newUserReward, } = this.props; let content, @@ -59,42 +60,55 @@ class RewardsPage extends React.PureComponent { if (!hasEmail || isVerificationCandidate) { content = (
-

- {__( - "Additional information is required to be eligible for the rewards program." - )} -

- +
+ {newUserReward && + } +

Welcome to LBRY

+
+
+

+ {" "}{__( + "Claim your welcome credits to be able to publish content, pay creators, and have a say over the LBRY network." + )} +

+
+
); isCard = true; } else if (!isEligible) { isCard = true; content = ( -
+

{__("You are not eligible to claim rewards.")}

); } else if (fetching) { - content = ; + content = ( +
+ +
+ ); } else if (rewards.length > 0) { - content = rewards.map(reward => - + content = ( +
+ {rewards.map(reward => + + )} +
); } else { - content =
{__("Failed to load rewards.")}
; + content = ( +
+ {__("Failed to load rewards.")} +
+ ); } return (
- {isCard - ?
-
- {content} -
-
- : content} + {isCard ?
{content}
: content}
); } diff --git a/ui/js/selectors/user.js b/ui/js/selectors/user.js index d17485aa6..a7ff1dc9f 100644 --- a/ui/js/selectors/user.js +++ b/ui/js/selectors/user.js @@ -12,10 +12,7 @@ export const selectUserIsPending = createSelector( state => state.userIsPending ); -export const selectUser = createSelector( - _selectState, - state => state.user || {} -); +export const selectUser = createSelector(_selectState, state => state.user); export const selectEmailToVerify = createSelector( _selectState, @@ -65,7 +62,7 @@ export const selectEmailVerifyErrorMessage = createSelector( export const selectUserIsVerificationCandidate = createSelector( selectUser, - user => user && !user.has_verified_email + user => user && (!user.has_verified_email || !user.is_identity_verified) ); export const selectUserIsAuthRequested = createSelector( diff --git a/ui/js/utils.js b/ui/js/utils.js index 783f85113..b41a2e0d4 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -29,3 +29,7 @@ export function getSession(key, fallback = undefined) { export function setSession(key, value) { sessionStorage.setItem(key, JSON.stringify(value)); } + +export function formatCredits(amount, precision) { + return amount.toFixed(precision || 1).replace(/\.?0+$/, ""); +} diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss index f701ebe06..8f918cc7d 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -3,10 +3,6 @@ $width-input-border: 2px; $width-input-text: 330px; -.form-input-width { - width: $width-input-text -} - .form-row-submit { margin-top: $spacing-vertical; From 0854fb70045e00d235b385d745e90fd72fc1469a Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Mon, 17 Jul 2017 15:50:07 +0100 Subject: [PATCH 53/84] Discover page UI tweaks --- ui/js/component/fileCard/view.jsx | 4 ++-- ui/scss/component/_card.scss | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index 5a35d740d..12f0218e2 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -78,8 +78,9 @@ class FileCard extends React.PureComponent { onClick={() => navigate("/show", { uri })} className="card__link" > +
-
+
{title}
@@ -92,7 +93,6 @@ class FileCard extends React.PureComponent {
-
{description}
diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index da73b21bd..72032fbb4 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -28,6 +28,9 @@ $padding-card-horizontal: $spacing-vertical * 2/3; margin-top: $spacing-vertical * 1/3; margin-bottom: $spacing-vertical * 1/3; } +.card__title-identity .card__title { + font-size: 0.95em +} .card__actions { padding: 0 $padding-card-horizontal; } @@ -51,8 +54,8 @@ $padding-card-horizontal: $spacing-vertical * 2/3; color: #444; margin-top: 12px; font-size: 0.9em; - margin-top: $spacing-vertical * 2/3; - margin-bottom: $spacing-vertical * 2/3; + margin-top: $spacing-vertical * 1/3; + margin-bottom: $spacing-vertical * 1/3; padding: 0 $padding-card-horizontal; } .card__subtext--allow-newlines { @@ -60,6 +63,8 @@ $padding-card-horizontal: $spacing-vertical * 2/3; } .card__subtext--two-lines { height: $font-size * 0.9 * $font-line-height * 2; + font-size: 0.82em; + color: #515151 } .card-overlay { position: absolute; @@ -144,7 +149,7 @@ $card-link-scaling: 1.1; top: 36% } -$width-card-small: $spacing-vertical * 12; +$width-card-small: $spacing-vertical * 10; $height-card-small: $spacing-vertical * 15; .card--small { From 95c5ddbfda155d0e87678418622a51429f974ed5 Mon Sep 17 00:00:00 2001 From: 6ea86b96 <6ea86b96@gmail.com> Date: Tue, 18 Jul 2017 13:45:00 +0700 Subject: [PATCH 54/84] Fix hiding price input when free is checked on publish form --- ui/js/component/publishForm/view.jsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index da6e6eb39..4d765e6d6 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -42,6 +42,7 @@ class PublishForm extends React.PureComponent { submitting: false, creatingChannel: false, modal: null, + isFee: false, }; } @@ -635,11 +636,8 @@ class PublishForm extends React.PureComponent { label={__("Free")} type="radio" name="isFree" - value="1" - onChange={() => { - this.handleFeePrefChange(false); - }} - defaultChecked={!this.state.isFee} + onChange={() => this.handleFeePrefChange(false)} + checked={!this.state.isFee} /> { this.handleFeePrefChange(true); }} - defaultChecked={this.state.isFee} + checked={this.state.isFee} /> Date: Tue, 18 Jul 2017 13:53:45 +0700 Subject: [PATCH 55/84] Fix hiding new channel fields on publish form --- ui/js/component/publishForm/internal/channelSection.jsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ui/js/component/publishForm/internal/channelSection.jsx b/ui/js/component/publishForm/internal/channelSection.jsx index 6c7802625..76f442af1 100644 --- a/ui/js/component/publishForm/internal/channelSection.jsx +++ b/ui/js/component/publishForm/internal/channelSection.jsx @@ -93,6 +93,7 @@ class ChannelSection extends React.PureComponent { "This LBC remains yours and the deposit can be undone at any time." ); + const channel = this.state.addingChannel ? "new" : this.props.channel; const { fetchingChannels, channels = [] } = this.props; let channelContent = []; @@ -102,7 +103,7 @@ class ChannelSection extends React.PureComponent { type="select" tabIndex="1" onChange={this.handleChannelChange.bind(this)} - value={this.props.channel} + value={channel} >