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 8c6a2184e..702c75b47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,24 +8,40 @@ 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 + * Support markdown makeup in claim description + * Replaced free speech flag (used when image is missing) with labeled color tiles + * Added a loading message to file actions + * URL is auto suggested in Publish Page ### 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 + * Stop discover page from pushing jumping vertically while loading + * Restored feedback on claim amounts + * Fixed hiding price input when Free is checked on publish form + * Fixed hiding new identity fields on publish form + * Fixed files on downloaded tab not showing download progress + * Fixed downloading files that are deleted not being removed from the downloading list + * Fixed download progress bar not being cleared when a downloading file is deleted + * Fixed refresh regression after adding scroll position to history state + * Fixed app thinking downloads with 0 progress were downloaded after restart ### Deprecated * * ### Removed - * - * + * Removed bandwidth caps from settings, because the daemon was not respecting them anyway. + * ## [0.13.0] - 2017-06-30 diff --git a/app/main.js b/app/main.js index 35a50f224..69fad7d92 100644 --- a/app/main.js +++ b/app/main.js @@ -27,7 +27,7 @@ const {version: localVersion} = require(app.getAppPath() + '/package.json'); const VERSION_CHECK_INTERVAL = 30 * 60 * 1000; const LATEST_RELEASE_API_URL = 'https://api.github.com/repos/lbryio/lbry-app/releases/latest'; - +const DAEMON_PATH = process.env.LBRY_DAEMON || path.join(__dirname, 'dist', 'lbrynet-daemon'); let client = jayson.client.http({ host: 'localhost', @@ -207,13 +207,8 @@ function handleDaemonSubprocessExited() { function launchDaemon() { assert(!daemonSubprocess, 'Tried to launch daemon twice'); - if (process.env.LBRY_DAEMON) { - executable = process.env.LBRY_DAEMON; - } else { - executable = path.join(__dirname, 'dist', 'lbrynet-daemon'); - } - console.log('Launching daemon:', executable) - daemonSubprocess = child_process.spawn(executable) + console.log('Launching daemon:', DAEMON_PATH) + daemonSubprocess = child_process.spawn(DAEMON_PATH) // Need to handle the data event instead of attaching to // process.stdout because the latter doesn't work. I believe on // windows it buffers stdout and we don't get any meaningful output diff --git a/app/package.json b/app/package.json index aee4e6085..ced806d22 100644 --- a/app/package.json +++ b/app/package.json @@ -18,5 +18,8 @@ }, "devDependencies": { "electron-rebuild": "^1.5.11" + }, + "lbrySettings": { + "lbrynetDaemonVersion": "0.14.1" } } diff --git a/build/DAEMON_URL b/build/DAEMON_URL deleted file mode 100644 index b3b581882..000000000 --- a/build/DAEMON_URL +++ /dev/null @@ -1 +0,0 @@ -https://github.com/lbryio/lbry/releases/download/v0.13.1/lbrynet-daemon-v0.13.1-OSNAME.zip diff --git a/build/build.ps1 b/build/build.ps1 index 6c93b172c..569fbd875 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -46,8 +46,8 @@ dir dist # verify that binary was built/named correctly # sign binary nuget install secure-file -ExcludeVersion -secure-file\tools\secure-file -decrypt build\lbry2.pfx.enc -secret "$env:pfx_key" -& ${env:SIGNTOOL_PATH} sign /f build\lbry2.pfx /p "$env:key_pass" /tr http://tsa.starfieldtech.com /td SHA256 /fd SHA256 dist\*.exe +secure-file\tools\secure-file -decrypt build\lbry3.pfx.enc -secret "$env:pfx_key" +& ${env:SIGNTOOL_PATH} sign /f build\lbry3.pfx /p "$env:key_pass" /tr http://tsa.starfieldtech.com /td SHA256 /fd SHA256 dist\*.exe -python build\upload_assets.py \ No newline at end of file +python build\upload_assets.py diff --git a/build/build.sh b/build/build.sh index c5ccba34f..9fa139877 100755 --- a/build/build.sh +++ b/build/build.sh @@ -79,11 +79,14 @@ if $OSX; then else OSNAME="linux" fi -DAEMON_URL="$(cat "$BUILD_DIR/DAEMON_URL" | sed "s/OSNAME/${OSNAME}/")" +DAEMON_VER=$(node -e "console.log(require(\"$ROOT/app/package.json\").lbrySettings.lbrynetDaemonVersion)") +DAEMON_URL="https://github.com/lbryio/lbry/releases/download/v${DAEMON_VER}/lbrynet-daemon-v${DAEMON_VER}-${OSNAME}.zip" wget --quiet "$DAEMON_URL" -O "$BUILD_DIR/daemon.zip" unzip "$BUILD_DIR/daemon.zip" -d "$ROOT/app/dist/" rm "$BUILD_DIR/daemon.zip" + + ################### # Build the app # ################### diff --git a/build/lbry2.pfx.enc b/build/lbry2.pfx.enc deleted file mode 100644 index 46e52260a..000000000 Binary files a/build/lbry2.pfx.enc and /dev/null differ diff --git a/build/lbry3.pfx.enc b/build/lbry3.pfx.enc new file mode 100644 index 000000000..330cfc05b Binary files /dev/null and b/build/lbry3.pfx.enc differ diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 35049d435..e6d5879f8 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -4,7 +4,6 @@ import { selectUpdateUrl, selectUpgradeDownloadPath, selectUpgradeDownloadItem, - selectUpgradeFilename, selectPageTitle, selectCurrentPage, selectCurrentParams, @@ -13,31 +12,43 @@ import { doSearch } from "actions/search"; import { doFetchDaemonSettings } from "actions/settings"; import { doAuthenticate } from "actions/user"; import { doFileList } from "actions/file_info"; +import { toQueryString } from "util/query_params"; +import { parseQueryParams } from "util/query_params"; const { remote, ipcRenderer, shell } = require("electron"); const path = require("path"); -const app = require("electron").remote.app; const { download } = remote.require("electron-dl"); const fs = remote.require("fs"); - -const queryStringFromParams = params => { - return Object.keys(params).map(key => `${key}=${params[key]}`).join("&"); -}; +const { lbrySettings: config } = require("../../../app/package.json"); export function doNavigate(path, params = {}) { return function(dispatch, getState) { let url = path; - if (params) url = `${url}?${queryStringFromParams(params)}`; + if (params) url = `${url}?${toQueryString(params)}`; dispatch(doChangePath(url)); const state = getState(); const pageTitle = selectPageTitle(state); - dispatch(doHistoryPush(params, pageTitle, url)); + dispatch(doHistoryPush({ params }, pageTitle, url)); }; } -export function doChangePath(path) { +export function doAuthNavigate(pathAfterAuth = null, params = {}) { + return function(dispatch, getState) { + if (pathAfterAuth) { + dispatch({ + type: types.CHANGE_AFTER_AUTH_PATH, + data: { + path: `${pathAfterAuth}?${toQueryString(params)}`, + }, + }); + } + dispatch(doNavigate("/auth")); + }; +} + +export function doChangePath(path, options = {}) { return function(dispatch, getState) { dispatch({ type: types.CHANGE_PATH, @@ -48,8 +59,12 @@ export function doChangePath(path) { const state = getState(); const pageTitle = selectPageTitle(state); + const scrollY = options.scrollY; + window.document.title = pageTitle; - window.scrollTo(0, 0); + + if (scrollY) window.scrollTo(0, scrollY); + else window.scrollTo(0, 0); const currentPage = selectCurrentPage(state); if (currentPage === "search") { @@ -62,15 +77,32 @@ export function doChangePath(path) { export function doHistoryBack() { return function(dispatch, getState) { if (!history.state) return; + if (history.state.index === 0) return; history.back(); }; } -export function doHistoryPush(params, title, relativeUrl) { +export function doHistoryPush(currentState, title, relativeUrl) { return function(dispatch, getState) { title += " - LBRY"; - history.pushState(params, title, `#${relativeUrl}`); + history.pushState(currentState, title, `#${relativeUrl}`); + }; +} + +export function doRecordScroll(scroll) { + return function(dispatch, getState) { + const state = getState(); + const historyState = history.state; + + if (!historyState) return; + + historyState.scrollY = scroll; + history.replaceState( + historyState, + document.title, + `#${state.app.currentPath}` + ); }; } @@ -117,8 +149,9 @@ export function doDownloadUpgrade() { return function(dispatch, getState) { const state = getState(); // Make a new directory within temp directory so the filename is guaranteed to be available - const dir = fs.mkdtempSync(app.getPath("temp") + require("path").sep); - const upgradeFilename = selectUpgradeFilename(state); + const dir = fs.mkdtempSync( + remote.app.getPath("temp") + require("path").sep + ); let options = { onProgress: p => dispatch(doUpdateDownloadProgress(Math.round(p * 100))), @@ -202,11 +235,21 @@ export function doCheckUpgradeAvailable() { }; } +export function doCheckDaemonVersion() { + return function(dispatch, getState) { + lbry.version().then(({ lbrynet_version }) => { + dispatch({ + type: config.lbrynetDaemonVersion == lbrynet_version + ? types.DAEMON_VERSION_MATCH + : types.DAEMON_VERSION_MISMATCH, + }); + }); + }; +} + export function doAlertError(errorList) { return function(dispatch, getState) { const state = getState(); - console.log("do alert error"); - console.log(errorList); dispatch({ type: types.OPEN_MODAL, data: { @@ -219,6 +262,9 @@ export function doAlertError(errorList) { export function doDaemonReady() { return function(dispatch, getState) { + const path = window.location.hash || "#/discover"; + const params = parseQueryParams(path.split("?")[1] || ""); + history.replaceState({ params, index: 0 }, document.title, `${path}`); dispatch(doAuthenticate()); dispatch({ type: types.DAEMON_READY, @@ -248,3 +294,10 @@ export function doClearCache() { return Promise.resolve(); }; } + +export function doQuitAndLaunchDaemonHelp() { + return function(dispatch, getState) { + shell.openExternal("https://lbry.io/faq/incompatible-protocol-version"); + remote.app.quit(); + }; +} diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index b80f8fc0c..b0e40ffa8 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -5,7 +5,7 @@ import lbryuri from "lbryuri"; import { selectBalance } from "selectors/wallet"; import { selectFileInfoForUri, - selectUrisDownloading, + selectDownloadingByOutpoint, } from "selectors/file_info"; import { selectResolvingUris } from "selectors/content"; import { selectCostInfoForUri } from "selectors/cost_info"; @@ -16,6 +16,7 @@ import { selectTotalDownloadProgress } from "selectors/file_info"; import setBadge from "util/setBadge"; import setProgressBar from "util/setProgressBar"; import batchActions from "util/batchActions"; +import * as modals from "constants/modal_types"; const { ipcRenderer } = require("electron"); @@ -264,8 +265,9 @@ export function doPurchaseUri(uri, purchaseModalName) { const state = getState(); const balance = selectBalance(state); const fileInfo = selectFileInfoForUri(state, { uri }); - const downloadingByUri = selectUrisDownloading(state); - const alreadyDownloading = !!downloadingByUri[uri]; + const downloadingByOutpoint = selectDownloadingByOutpoint(state); + const alreadyDownloading = + fileInfo && !!downloadingByOutpoint[fileInfo.outpoint]; // we already fully downloaded the file. if (fileInfo && fileInfo.completed) { @@ -292,7 +294,7 @@ export function doPurchaseUri(uri, purchaseModalName) { } if (cost > balance) { - dispatch(doOpenModal("notEnoughCredits")); + dispatch(doOpenModal(modals.INSUFFICIENT_CREDITS)); } else { dispatch(doOpenModal(purchaseModalName)); } @@ -339,3 +341,68 @@ 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); + }; +} + +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 => { + reject(err); + } + ); + }); + }; +} + +export function doPublish(params) { + return function(dispatch, getState) { + return new Promise((resolve, reject) => { + const success = claim => { + resolve(claim); + + 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/actions/file_info.js b/ui/js/actions/file_info.js index 9d3861fb7..9ed2df5a7 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -3,15 +3,18 @@ import lbry from "lbry"; import { doFetchClaimListMine } from "actions/content"; import { selectClaimsByUri, - selectClaimListMineIsPending, + selectIsFetchingClaimListMine, selectMyClaimsOutpoints, } from "selectors/claims"; import { - selectFileListIsPending, + selectIsFetchingFileList, selectFileInfosByOutpoint, selectUrisLoading, + selectTotalDownloadProgress, } from "selectors/file_info"; -import { doCloseModal } from "actions/app"; +import { doCloseModal, doHistoryBack } from "actions/app"; +import setProgressBar from "util/setProgressBar"; +import batchActions from "util/batchActions"; const { shell } = require("electron"); @@ -48,16 +51,16 @@ 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, }); lbry.file_list().then(fileInfos => { dispatch({ - type: types.FILE_LIST_COMPLETED, + type: types.FILE_LIST_SUCCEEDED, data: { fileInfos, }, @@ -102,14 +105,12 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) { }, }); - const success = () => { - dispatch({ - type: types.ABANDON_CLAIM_COMPLETED, - data: { - claimId: fileInfo.claim_id, - }, - }); - }; + const success = dispatch({ + type: types.ABANDON_CLAIM_SUCCEEDED, + data: { + claimId: fileInfo.claim_id, + }, + }); lbry.claim_abandon({ claim_id: fileInfo.claim_id }).then(success); } } @@ -121,17 +122,32 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) { }, }); - dispatch(doCloseModal()); + const totalProgress = selectTotalDownloadProgress(getState()); + setProgressBar(totalProgress); + }; +} + +export function doDeleteFileAndGoBack( + fileInfo, + deleteFromComputer, + abandonClaim +) { + return function(dispatch, getState) { + const actions = []; + actions.push(doCloseModal()); + actions.push(doHistoryBack()); + actions.push(doDeleteFile(fileInfo, deleteFromComputer, abandonClaim)); + dispatch(batchActions(...actions)); }; } 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/actions/rewards.js b/ui/js/actions/rewards.js index 9c5d5143a..95ae3b25b 100644 --- a/ui/js/actions/rewards.js +++ b/ui/js/actions/rewards.js @@ -1,4 +1,5 @@ import * as types from "constants/action_types"; +import * as modals from "constants/modal_types"; import lbryio from "lbryio"; import rewards from "rewards"; import { selectRewardsByType } from "selectors/rewards"; @@ -58,6 +59,12 @@ export function doClaimReward(reward, saveError = false) { reward, }, }); + if (reward.reward_type == rewards.TYPE_NEW_USER) { + dispatch({ + type: types.OPEN_MODAL, + data: { modal: modals.FIRST_REWARD }, + }); + } }; const failure = error => { @@ -99,9 +106,7 @@ export function doClaimEligiblePurchaseRewards() { if (unclaimedType) { dispatch(doClaimRewardType(unclaimedType)); } - if (types[rewards.TYPE_FEATURED_DOWNLOAD] === false) { - dispatch(doClaimRewardType(rewards.TYPE_FEATURED_DOWNLOAD)); - } + dispatch(doClaimRewardType(rewards.TYPE_FEATURED_DOWNLOAD)); }; } diff --git a/ui/js/actions/user.js b/ui/js/actions/user.js index ebfa8ed4b..e2fa9b917 100644 --- a/ui/js/actions/user.js +++ b/ui/js/actions/user.js @@ -1,8 +1,9 @@ import * as types from "constants/action_types"; import lbryio from "lbryio"; import { setLocal } from "utils"; -import { doRewardList } from "actions/rewards"; -import { selectEmailToVerify } from "selectors/user"; +import { doRewardList, doClaimRewardType } from "actions/rewards"; +import { selectEmailToVerify, selectUser } from "selectors/user"; +import rewards from "rewards"; export function doAuthenticate() { return function(dispatch, getState) { @@ -136,3 +137,45 @@ export function doUserEmailVerify(verificationToken) { }); }; } + +export function doUserIdentityVerify(stripeToken) { + return function(dispatch, getState) { + dispatch({ + type: types.USER_IDENTITY_VERIFY_STARTED, + token: stripeToken, + }); + + lbryio + .call("user", "verify_identity", { stripe_token: stripeToken }, "post") + .then(user => { + if (user.is_identity_verified) { + dispatch({ + type: types.USER_IDENTITY_VERIFY_SUCCESS, + data: { user }, + }); + dispatch(doClaimRewardType(rewards.TYPE_NEW_USER)); + } else { + throw new Error( + "Your identity is still not verified. This should not happen." + ); //shouldn't happen + } + }) + .catch(error => { + dispatch({ + type: types.USER_IDENTITY_VERIFY_FAILURE, + data: { error: error.toString() }, + }); + }); + }; +} + +export function doFetchAccessToken() { + return function(dispatch, getState) { + const success = token => + dispatch({ + type: types.FETCH_ACCESS_TOKEN_SUCCESS, + data: { token }, + }); + lbryio.getAuthToken().then(success); + }; +} diff --git a/ui/js/app.js b/ui/js/app.js index 41701ee18..c5bfdcfbd 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -2,7 +2,9 @@ import store from "store.js"; import lbry from "./lbry.js"; const env = ENV; -const config = require(`./config/${env}`); +const config = { + ...require(`./config/${env}`), +}; const language = lbry.getClientSetting("language") ? lbry.getClientSetting("language") : "en"; diff --git a/ui/js/component/app/index.js b/ui/js/component/app/index.js index 4c966cb56..ff4940f88 100644 --- a/ui/js/component/app/index.js +++ b/ui/js/component/app/index.js @@ -1,19 +1,43 @@ import React from "react"; import { connect } from "react-redux"; - import { selectCurrentModal } from "selectors/app"; -import { doCheckUpgradeAvailable, doAlertError } from "actions/app"; +import { + doCheckUpgradeAvailable, + doOpenModal, + doAlertError, + doRecordScroll, +} 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)), + recordScroll: scrollPosition => dispatch(doRecordScroll(scrollPosition)), }); export default connect(select, perform)(App); diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index 727f4e39a..e364c0db0 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -3,24 +3,60 @@ 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 ModalInsufficientCredits from "component/modalInsufficientCredits"; +import ModalUpgrade from "component/modalUpgrade"; +import ModalWelcome from "component/modalWelcome"; +import ModalFirstReward from "component/modalFirstReward"; import lbry from "lbry"; -import { Line } from "rc-progress"; +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); + + this.scrollListener = () => this.props.recordScroll(window.scrollY); + + window.addEventListener("scroll", this.scrollListener); + } + + componentWillReceiveProps(nextProps) { + this.showWelcome(nextProps); + } + + showWelcome(props) { + const { + isFetchingRewards, + isWelcomeAcknowledged, + isWelcomeRewardClaimed, + openWelcomeModal, + user, + } = props; + + if ( + !isWelcomeAcknowledged && + user && + (isFetchingRewards === false && isWelcomeRewardClaimed === false) + ) { + openWelcomeModal(); + } + } + + componentWillUnmount() { + window.removeEventListener("scroll", this.scrollListener); } render() { @@ -32,10 +68,12 @@ 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 && } + {modal == modals.FIRST_REWARD && } ); } diff --git a/ui/js/component/auth/index.js b/ui/js/component/auth/index.js deleted file mode 100644 index 37af9f90f..000000000 --- a/ui/js/component/auth/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from "react"; -import { connect } from "react-redux"; -import { - selectAuthenticationIsPending, - selectEmailToVerify, - selectUserIsVerificationCandidate, -} from "selectors/user"; -import Auth from "./view"; - -const select = state => ({ - isPending: selectAuthenticationIsPending(state), - email: selectEmailToVerify(state), - isVerificationCandidate: selectUserIsVerificationCandidate(state), -}); - -export default connect(select, null)(Auth); diff --git a/ui/js/component/auth/view.jsx b/ui/js/component/auth/view.jsx deleted file mode 100644 index 551113ffa..000000000 --- a/ui/js/component/auth/view.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import { BusyMessage } from "component/common"; -import UserEmailNew from "component/userEmailNew"; -import UserEmailVerify from "component/userEmailVerify"; - -export class Auth extends React.PureComponent { - render() { - const { isPending, email, isVerificationCandidate } = this.props; - - if (isPending) { - return ; - } else if (!email) { - return ; - } else if (isVerificationCandidate) { - return ; - } else { - return {__("No further steps.")}; - } - } -} - -export default Auth; diff --git a/ui/js/component/authOverlay/index.jsx b/ui/js/component/authOverlay/index.jsx deleted file mode 100644 index 28b49333c..000000000 --- a/ui/js/component/authOverlay/index.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from "react"; -import * as modal from "constants/modal_types"; -import rewards from "rewards.js"; -import { connect } from "react-redux"; -import { doUserEmailDecline } from "actions/user"; -import { doOpenModal } from "actions/app"; -import { - selectAuthenticationIsPending, - selectUserHasEmail, - selectUserIsAuthRequested, -} from "selectors/user"; -import { makeSelectHasClaimedReward } from "selectors/rewards"; -import AuthOverlay from "./view"; - -const select = (state, props) => { - const selectHasClaimed = makeSelectHasClaimedReward(); - - return { - hasEmail: selectUserHasEmail(state), - isPending: selectAuthenticationIsPending(state), - isShowing: selectUserIsAuthRequested(state), - hasNewUserReward: selectHasClaimed(state, { - reward_type: rewards.TYPE_NEW_USER, - }), - }; -}; - -const perform = dispatch => ({ - userEmailDecline: () => dispatch(doUserEmailDecline()), - openWelcomeModal: () => dispatch(doOpenModal(modal.WELCOME)), -}); - -export default connect(select, perform)(AuthOverlay); diff --git a/ui/js/component/authOverlay/view.jsx b/ui/js/component/authOverlay/view.jsx deleted file mode 100644 index 753fe2fe4..000000000 --- a/ui/js/component/authOverlay/view.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from "react"; -import lbryio from "lbryio.js"; -import ModalPage from "component/modal-page.js"; -import Auth from "component/auth"; -import Link from "component/link"; -import { getLocal, setLocal } from "utils"; - -export class AuthOverlay extends React.PureComponent { - constructor(props) { - super(props); - - this.state = { - showNoEmailConfirm: false, - }; - } - - componentWillReceiveProps(nextProps) { - if ( - this.props.isShowing && - !this.props.isPending && - !nextProps.hasNewUserReward && - !nextProps.isShowing /* && !getLocal("welcome_screen_shown")*/ - ) { - setLocal("welcome_screen_shown", true); - setTimeout(() => this.props.openWelcomeModal(), 1); - } - } - - onEmailSkipClick() { - this.setState({ showNoEmailConfirm: true }); - } - - onEmailSkipConfirm() { - this.props.userEmailDecline(); - } - - render() { - if (!lbryio.enabled) { - return null; - } - - const { isPending, isShowing, hasEmail } = this.props; - - if (isShowing) { - return ( - -

LBRY Early Access

- - {isPending - ? "" - :
- {!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." - )} -

- { - this.onEmailSkipConfirm(); - }} - label={__("Continue without email")} - /> -
- : { - hasEmail - ? this.onEmailSkipConfirm() - : this.onEmailSkipClick(); - }} - label={ - hasEmail ? __("Skip for now") : __("Do I have to?") - } - />} -
} -
- ); - } - - return null; - } -} - -export default AuthOverlay; 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/cardVerify/index.js b/ui/js/component/cardVerify/index.js new file mode 100644 index 000000000..32cd22aef --- /dev/null +++ b/ui/js/component/cardVerify/index.js @@ -0,0 +1,12 @@ +import React from "react"; +import { connect } from "react-redux"; +import { selectUserEmail } from "selectors/user"; +import CardVerify from "./view"; + +const select = state => ({ + email: selectUserEmail(state), +}); + +const perform = dispatch => ({}); + +export default connect(select, perform)(CardVerify); diff --git a/ui/js/component/cardVerify/view.jsx b/ui/js/component/cardVerify/view.jsx new file mode 100644 index 000000000..79d4370a4 --- /dev/null +++ b/ui/js/component/cardVerify/view.jsx @@ -0,0 +1,377 @@ +import React from "react"; +import PropTypes from "prop-types"; +import Link from "component/link"; + +let scriptLoading = false; +let scriptLoaded = false; +let scriptDidError = false; + +class CardVerify extends React.Component { + static defaultProps = { + label: "Verify", + locale: "auto", + }; + + static propTypes = { + // If included, will render the default blue button with label text. + // (Requires including stripe-checkout.css or adding the .styl file + // to your pipeline) + label: PropTypes.string, + + // ===================================================== + // Required by stripe + // see Stripe docs for more info: + // https://stripe.com/docs/checkout#integration-custom + // ===================================================== + + // Your publishable key (test or live). + // can't use "key" as a prop in react, so have to change the keyname + stripeKey: PropTypes.string.isRequired, + + // The callback to invoke when the Checkout process is complete. + // function(token) + // token is the token object created. + // token.id can be used to create a charge or customer. + // token.email contains the email address entered by the user. + token: PropTypes.func.isRequired, + + // ========================== + // Highly Recommended Options + // ========================== + + // Name of the company or website. + name: PropTypes.string, + + // A description of the product or service being purchased. + description: PropTypes.string, + + // Specify auto to display Checkout in the user's preferred language, if + // available. English will be used by default. + // + // https://stripe.com/docs/checkout#supported-languages + // for more info. + locale: PropTypes.oneOf([ + "auto", // (Default) Automatically chosen by checkout + "zh", // Simplified Chinese + "da", // Danish + "nl", // Dutch + "en", // English + "fr", // French + "de", // German + "it", // Italian + "ja", // Japanease + "no", // Norwegian + "es", // Spanish + "sv", // Swedish + ]), + + // ============== + // Optional Props + // ============== + + // The currency of the amount (3-letter ISO code). The default is USD. + currency: PropTypes.oneOf([ + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BWP", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EEK", + "EGP", + "ETB", + "EUR", + "FJD", + "FKP", + "GBP", + "GEL", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "INR", + "ISK", + "JMD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KRW", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LTL", + "LVL", + "MAD", + "MDL", + "MGA", + "MKD", + "MNT", + "MOP", + "MRO", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SRD", + "STD", + "SVC", + "SZL", + "THB", + "TJS", + "TOP", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VND", + "VUV", + "WST", + "XAF", + "XCD", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMW", + ]), + + // The label of the payment button in the Checkout form (e.g. “Subscribe”, + // “Pay {{amount}}”, etc.). If you include {{amount}}, it will be replaced + // by the provided amount. Otherwise, the amount will be appended to the + // end of your label. + panelLabel: PropTypes.string, + }; + + constructor(props) { + super(props); + this.state = { + open: false, + }; + } + + componentDidMount() { + if (scriptLoaded) { + return; + } + + if (scriptLoading) { + return; + } + + scriptLoading = true; + + const script = document.createElement("script"); + script.src = "https://checkout.stripe.com/checkout.js"; + script.async = 1; + + this.loadPromise = (() => { + let canceled = false; + const promise = new Promise((resolve, reject) => { + script.onload = () => { + scriptLoaded = true; + scriptLoading = false; + resolve(); + this.onScriptLoaded(); + }; + script.onerror = event => { + scriptDidError = true; + scriptLoading = false; + reject(event); + this.onScriptError(event); + }; + }); + const wrappedPromise = new Promise((accept, cancel) => { + promise.then( + () => (canceled ? cancel({ isCanceled: true }) : accept()) + ); + promise.catch( + error => (canceled ? cancel({ isCanceled: true }) : cancel(error)) + ); + }); + + return { + promise: wrappedPromise, + cancel() { + canceled = true; + }, + }; + })(); + + this.loadPromise.promise + .then(this.onScriptLoaded) + .catch(this.onScriptError); + + document.body.appendChild(script); + } + + componentDidUpdate() { + if (!scriptLoading) { + this.updateStripeHandler(); + } + } + + componentWillUnmount() { + if (this.loadPromise) { + this.loadPromise.cancel(); + } + if (CardVerify.stripeHandler && this.state.open) { + CardVerify.stripeHandler.close(); + } + } + + onScriptLoaded = () => { + if (!CardVerify.stripeHandler) { + CardVerify.stripeHandler = StripeCheckout.configure({ + key: this.props.stripeKey, + }); + if (this.hasPendingClick) { + this.showStripeDialog(); + } + } + }; + + onScriptError = (...args) => { + throw new Error("Unable to load credit validation script."); + }; + + onClosed = () => { + this.setState({ open: false }); + }; + + getConfig = () => + ["token", "name", "description"].reduce( + (config, key) => + Object.assign( + {}, + config, + this.props.hasOwnProperty(key) && { + [key]: this.props[key], + } + ), + { + allowRememberMe: false, + closed: this.onClosed, + description: __("Confirm Identity"), + email: this.props.email, + panelLabel: "Verify", + } + ); + + updateStripeHandler() { + if (!CardVerify.stripeHandler) { + CardVerify.stripeHandler = StripeCheckout.configure({ + key: this.props.stripeKey, + }); + } + } + + showStripeDialog() { + this.setState({ open: true }); + CardVerify.stripeHandler.open(this.getConfig()); + } + + onClick = () => { + if (scriptDidError) { + try { + throw new Error( + "Tried to call onClick, but StripeCheckout failed to load" + ); + } catch (x) {} + } else if (CardVerify.stripeHandler) { + this.showStripeDialog(); + } else { + this.hasPendingClick = true; + } + }; + + render() { + return ( + + ); + } +} + +export default CardVerify; 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/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index 53188a1f5..9ff0fbaa1 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -118,7 +118,10 @@ class FileActions extends React.PureComponent { />
); - } else if (fileInfo === null && !downloading) { + } else if ( + (fileInfo === null || (fileInfo && fileInfo.written_bytes === 0)) && + !downloading + ) { if (!costInfo) { content = ; } else { @@ -142,6 +145,8 @@ class FileActions extends React.PureComponent { onClick={() => openInShell(fileInfo)} /> ); + } else if (!fileInfo) { + content = ; } else { console.log("handle this case of file action props?"); } @@ -176,13 +181,6 @@ class FileActions extends React.PureComponent { {" "} {__("credits")}. - - {__("You don't have enough LBRY credits to pay for this stream.")} - navigate("/show", { uri })} className="card__link" > +
-
+
{title} -
+
@@ -87,14 +93,8 @@ class FileCard extends React.PureComponent {
- {metadata && - metadata.thumbnail && -
}
- {description} + {description}
diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx index 20631b59e..f910e9500 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; @@ -79,7 +81,7 @@ class FileList extends React.PureComponent { content.push( - {content} 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 && } ); } diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index c0c25a202..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"; @@ -64,7 +65,10 @@ class FileTile extends React.PureComponent { const isClaimable = lbryuri.isClaimable(uri); const title = isClaimed && metadata && metadata.title ? metadata.title - : uri; + : 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/js/component/form.js b/ui/js/component/form.js index 7ab78325c..59b9bce50 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", "side-by-side"], + }; } 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} @@ -170,6 +184,10 @@ export class FormRow extends React.PureComponent { React.PropTypes.string, React.PropTypes.element, ]), + errorMessage: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.object, + ]), // helper: React.PropTypes.html, }; @@ -190,7 +208,9 @@ export class FormRow extends React.PureComponent { isError: !!props.errorMessage, errorMessage: typeof props.errorMessage === "string" ? props.errorMessage - : "", + : props.errorMessage instanceof Error + ? props.errorMessage.toString() + : "", }; } @@ -220,6 +240,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/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/link/view.jsx b/ui/js/component/link/view.jsx index 2f9c7478e..a39e9947c 100644 --- a/ui/js/component/link/view.jsx +++ b/ui/js/component/link/view.jsx @@ -11,7 +11,6 @@ const Link = props => { icon, badge, button, - hidden, disabled, children, } = props; diff --git a/ui/js/component/modal-page.js b/ui/js/component/modal-page.js deleted file mode 100644 index f63d120f5..000000000 --- a/ui/js/component/modal-page.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; -import ReactModal from "react-modal"; - -export class ModalPage extends React.PureComponent { - render() { - return ( - -
- {this.props.children} -
-
- ); - } -} - -export default ModalPage; diff --git a/ui/js/component/modalFirstReward/index.js b/ui/js/component/modalFirstReward/index.js new file mode 100644 index 000000000..5993a990e --- /dev/null +++ b/ui/js/component/modalFirstReward/index.js @@ -0,0 +1,20 @@ +import React from "react"; +import rewards from "rewards"; +import { connect } from "react-redux"; +import { doCloseModal } from "actions/app"; +import { makeSelectRewardByType } from "selectors/rewards"; +import ModalFirstReward from "./view"; + +const select = (state, props) => { + const selectReward = makeSelectRewardByType(); + + return { + reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), + }; +}; + +const perform = dispatch => ({ + closeModal: () => dispatch(doCloseModal()), +}); + +export default connect(select, perform)(ModalFirstReward); diff --git a/ui/js/component/modalFirstReward/view.jsx b/ui/js/component/modalFirstReward/view.jsx new file mode 100644 index 000000000..fc0aead0f --- /dev/null +++ b/ui/js/component/modalFirstReward/view.jsx @@ -0,0 +1,50 @@ +import React from "react"; +import { Modal } from "component/modal"; +import { CreditAmount } from "component/common"; + +class ModalFirstReward extends React.PureComponent { + render() { + const { closeModal, reward } = this.props; + + return ( + +
+

{__("About Your Reward")}

+

+ {__("You earned a reward of")} + {" "} + {" "}{__("LBRY credits, or")} {__("LBC")}. +

+

+ {__( + "This reward will show in your Wallet momentarily, shown in the top right, probably while you are reading this message." + )} +

+

+ {__( + "LBC is used to compensate creators, to publish, and to have say in how the network works." + )} +

+

+ {__( + "No need to understand it all just yet! Try watching or downloading something next." + )} +

+

+ {__( + "Finally, pleaseh know that LBRY is an early beta and that it earns the name." + )} +

+
+
+ ); + } +} + +export default ModalFirstReward; diff --git a/ui/js/component/modalIncompatibleDaemon/index.jsx b/ui/js/component/modalIncompatibleDaemon/index.jsx new file mode 100644 index 000000000..27ddecd8d --- /dev/null +++ b/ui/js/component/modalIncompatibleDaemon/index.jsx @@ -0,0 +1,13 @@ +import React from "react"; +import { connect } from "react-redux"; +import { doQuit, doSkipWrongDaemonNotice } from "actions/app"; +import { doQuitAndLaunchDaemonHelp } from "actions/app"; +import ModalIncompatibleDaemon from "./view"; + +const select = state => ({}); + +const perform = dispatch => ({ + quitAndLaunchDaemonHelp: () => dispatch(doQuitAndLaunchDaemonHelp()), +}); + +export default connect(select, perform)(ModalIncompatibleDaemon); diff --git a/ui/js/component/modalIncompatibleDaemon/view.jsx b/ui/js/component/modalIncompatibleDaemon/view.jsx new file mode 100644 index 000000000..851a2ec88 --- /dev/null +++ b/ui/js/component/modalIncompatibleDaemon/view.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import { Modal } from "component/modal"; + +class ModalIncompatibleDaemon extends React.PureComponent { + render() { + const { quitAndLaunchDaemonHelp } = this.props; + + return ( + + {__( + "This browser is running with an incompatible version of the LBRY protocol and your install must be repaired." + )} + + ); + } +} + +export default ModalIncompatibleDaemon; diff --git a/ui/js/component/modalInsufficientCredits/index.js b/ui/js/component/modalInsufficientCredits/index.js new file mode 100644 index 000000000..0ece4f1fe --- /dev/null +++ b/ui/js/component/modalInsufficientCredits/index.js @@ -0,0 +1,16 @@ +import React from "react"; +import { connect } from "react-redux"; +import { doCloseModal, doNavigate } from "actions/app"; +import ModalInsufficientCredits from "./view"; + +const select = state => ({}); + +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/modalRemoveFile/index.js b/ui/js/component/modalRemoveFile/index.js index de54514d5..211bdff26 100644 --- a/ui/js/component/modalRemoveFile/index.js +++ b/ui/js/component/modalRemoveFile/index.js @@ -1,8 +1,9 @@ import React from "react"; import { connect } from "react-redux"; import { doCloseModal, doHistoryBack } from "actions/app"; -import { doDeleteFile } from "actions/file_info"; +import { doDeleteFileAndGoBack } from "actions/file_info"; import { makeSelectClaimForUriIsMine } from "selectors/claims"; +import batchActions from "util/batchActions"; import ModalRemoveFile from "./view"; @@ -19,8 +20,7 @@ const makeSelect = () => { const perform = dispatch => ({ closeModal: () => dispatch(doCloseModal()), deleteFile: (fileInfo, deleteFromComputer, abandonClaim) => { - dispatch(doHistoryBack()); - dispatch(doDeleteFile(fileInfo, deleteFromComputer, abandonClaim)); + dispatch(doDeleteFileAndGoBack(fileInfo, deleteFromComputer, abandonClaim)); }, }); 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 bbfc5b6a7..4d8962a70 100644 --- a/ui/js/component/modalWelcome/index.js +++ b/ui/js/component/modalWelcome/index.js @@ -1,28 +1,38 @@ import React from "react"; import rewards from "rewards"; import { connect } from "react-redux"; -import { doCloseModal } from "actions/app"; +import { doCloseModal, doAuthNavigate } from "actions/app"; +import { doSetClientSetting } from "actions/settings"; import { selectUserIsRewardApproved } from "selectors/user"; import { makeSelectHasClaimedReward, - makeSelectClaimRewardError, makeSelectRewardByType, } from "selectors/rewards"; -import WelcomeModal from "./view"; +import ModalWelcome from "./view"; const select = (state, props) => { const selectHasClaimed = makeSelectHasClaimedReward(), selectReward = makeSelectRewardByType(); return { - hasClaimed: selectHasClaimed(state, { reward_type: rewards.TYPE_NEW_USER }), isRewardApproved: selectUserIsRewardApproved(state), reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), }; }; -const perform = dispatch => ({ - closeModal: () => dispatch(doCloseModal()), -}); +const perform = dispatch => () => { + const closeModal = () => { + dispatch(doSetClientSetting("welcome_acknowledged", true)); + dispatch(doCloseModal()); + }; -export default connect(select, perform)(WelcomeModal); + return { + verifyAccount: () => { + closeModal(); + dispatch(doAuthNavigate("/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 9b6930bc6..c87df9da7 100644 --- a/ui/js/component/modalWelcome/view.jsx +++ b/ui/js/component/modalWelcome/view.jsx @@ -4,78 +4,48 @@ 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; + const { closeModal, isRewardApproved, reward, verifyAccount } = this.props; - return !hasClaimed - ? -
-

{__("Welcome to LBRY.")}

-

- {__( - "Using LBRY is like dating a centaur. Totally normal up top, and" - )} - {" "}{__("way different")} {__("underneath.")} -

-

{__("Up top, LBRY is similar to popular media sites.")}

-

- {__( - "Below, LBRY is controlled by users -- you -- via blockchain and decentralization." - )} -

-

- {__("Thank you for making content freedom possible!")} - {" "}{isRewardApproved ? __("Here's a nickel, kid.") : ""} -

-
- {isRewardApproved - ? - : } -
-
-
- : -
-

{__("About Your Reward")}

-

- {__("You earned a reward of")} - {" "} - {" "}{__("LBRY credits, or")} {__("LBC")}. -

-

- {__( - "This reward will show in your Wallet momentarily, probably while you are reading this message." - )} -

-

- {__( - "LBC is used to compensate creators, to publish, and to have say in how the network works." - )} -

-

- {__( - "No need to understand it all just yet! Try watching or downloading something next." - )} -

-

- {__( - "Finally, know that LBRY is an early beta and that it earns the name." - )} -

-
-
; + return ( + +
+

{__("Welcome to LBRY.")}

+

+ {__( + "Using LBRY is like dating a centaur. Totally normal up top, and" + )} + {" "}{__("way different")} {__("underneath.")} +

+

{__("Up top, LBRY is similar to popular media sites.")}

+

+ {__( + "Below, LBRY is controlled by users -- you -- via blockchain and decentralization." + )} +

+

+ {__("Please have")} {" "} + {reward && + } + {!reward && {__("??")}} + {" "} {__("as a thank you for building content freedom.")} +

+
+ {isRewardApproved && + } + {!isRewardApproved && + } + +
+
+
+ ); } } -export default WelcomeModal; +export default ModalWelcome; 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/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..4e598a9de --- /dev/null +++ b/ui/js/component/publishForm/internal/channelSection.jsx @@ -0,0 +1,174 @@ +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) { + 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); + }; + const failure = err => { + this.setState({ + creatingChannel: false, + }); + this.refs.newChannelName.showError( + __("Unable to create channel due to an internal error.") + ); + }; + 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 channel = this.state.addingChannel ? "new" : this.props.channel; + const { fetchingChannels, channels = [] } = this.props; + + let channelContent = []; + channelContent.push( + + + {this.props.channels.map(({ name }) => + + )} + + + ); + if (fetchingChannels) { + channelContent.push( + + ); + } + + return ( +
+
+

{__("Channel Name")}

+
+ {__("This is the channel that broadcasts your content.")} + {__("Ex. @Marvel, @TheBeatles, @BooksByJoe")} +
+
+
+ {channelContent} +
+ {this.state.addingChannel && +
+ + +
+ +
+
} +
+ ); + } +} + +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..970787205 --- /dev/null +++ b/ui/js/component/publishForm/view.jsx @@ -0,0 +1,938 @@ +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 { 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, + isFee: false, + customUrl: false, + }; + } + + _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 >= claim.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.setState({ + customUrl: Boolean(rawName.length), + }); + + 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; + } + + 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 }); + if (!this.state.customUrl) { + let fileName = this._getFileName(this.refs.file.getValue()); + this.nameChanged(fileName); + } + } else { + this.setState({ hasFile: false }); + } + } + + _getFileName(fileName) { + const path = require("path"); + const extension = path.extname(fileName); + + fileName = path.basename(fileName, extension); + fileName = fileName.replace(lbryuri.REGEXP_INVALID_URI, ""); + return fileName; + } + + 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)} + checked={!this.state.isFee} + /> + { + this.handleFeePrefChange(true); + }} + checked={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} +
+
+ + + +
+
+

{__("Content URL")}

+
+ {__( + "This is the exact address where people find your content (ex. lbry://myvideo)." + )} + {" "} + . +
+
+
+ { + 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.state.uri && + this.props.resolvingUris.indexOf(this.state.uri) !== -1) || + (this.claim() && + !this.topClaimIsMine() && + this.state.bid <= this.topClaimValue()) + } + /> + + +
+ + + { + 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/component/rewardLink/index.js b/ui/js/component/rewardLink/index.js index 81b01488b..cf53b2367 100644 --- a/ui/js/component/rewardLink/index.js +++ b/ui/js/component/rewardLink/index.js @@ -1,7 +1,6 @@ import React from "react"; import { connect } from "react-redux"; import { - makeSelectHasClaimedReward, makeSelectClaimRewardError, makeSelectRewardByType, makeSelectIsRewardClaimPending, @@ -11,13 +10,11 @@ import { doClaimReward, doClaimRewardClearError } from "actions/rewards"; import RewardLink from "./view"; const makeSelect = () => { - const selectHasClaimedReward = makeSelectHasClaimedReward(); const selectIsPending = makeSelectIsRewardClaimPending(); const selectReward = makeSelectRewardByType(); const selectError = makeSelectClaimRewardError(); const select = (state, props) => ({ - isClaimed: selectHasClaimedReward(state, props), errorMessage: selectError(state, props), isPending: selectIsPending(state, props), reward: selectReward(state, props), diff --git a/ui/js/component/rewardLink/view.jsx b/ui/js/component/rewardLink/view.jsx index be3a72f35..36a504d3e 100644 --- a/ui/js/component/rewardLink/view.jsx +++ b/ui/js/component/rewardLink/view.jsx @@ -1,5 +1,4 @@ import React from "react"; -import { Icon } from "component/common"; import Modal from "component/modal"; import Link from "component/link"; @@ -10,22 +9,19 @@ const RewardLink = props => { claimReward, clearError, errorMessage, - isClaimed, isPending, } = props; return (
- {isClaimed - ? Reward claimed. - : { - claimReward(reward); - }} - />} + { + claimReward(reward); + }} + /> {errorMessage ? { const component = routesMap[page]; @@ -24,22 +25,23 @@ const Router = props => { const { currentPage, params } = props; return route(currentPage, { - settings: , - help: , - report: , - downloaded: , - published: , - start: , - wallet: , - send: , - receive: , - show: , + auth: , channel: , - publish: , developer: , discover: , + downloaded: , + help: , + publish: , + published: , + receive: , + report: , rewards: , search: , + send: , + settings: , + show: , + start: , + wallet: , }); }; diff --git a/ui/js/component/splash/index.js b/ui/js/component/splash/index.js new file mode 100644 index 000000000..1a87f476b --- /dev/null +++ b/ui/js/component/splash/index.js @@ -0,0 +1,19 @@ +import React from "react"; +import { connect } from "react-redux"; + +import { selectCurrentModal, selectDaemonVersionMatched } from "selectors/app"; +import { doCheckDaemonVersion } from "actions/app"; +import SplashScreen from "./view"; + +const select = state => { + return { + modal: selectCurrentModal(state), + daemonVersionMatched: selectDaemonVersionMatched(state), + }; +}; + +const perform = dispatch => ({ + checkDaemonVersion: () => dispatch(doCheckDaemonVersion()), +}); + +export default connect(select, perform)(SplashScreen); diff --git a/ui/js/component/splash.js b/ui/js/component/splash/view.jsx similarity index 59% rename from ui/js/component/splash.js rename to ui/js/component/splash/view.jsx index eb67340e1..7cccd5bf0 100644 --- a/ui/js/component/splash.js +++ b/ui/js/component/splash/view.jsx @@ -1,6 +1,9 @@ import React from "react"; -import lbry from "../lbry.js"; -import LoadScreen from "./load_screen.js"; +import lbry from "../../lbry.js"; +import LoadScreen from "../load_screen.js"; +import ModalIncompatibleDaemon from "../modalIncompatibleDaemon"; +import ModalUpgrade from "component/modalUpgrade"; +import ModalDownloading from "component/modalDownloading"; export class SplashScreen extends React.PureComponent { static propTypes = { @@ -14,6 +17,7 @@ export class SplashScreen extends React.PureComponent { this.state = { details: __("Starting daemon"), message: __("Connecting"), + isRunning: false, isLagging: false, }; } @@ -35,10 +39,16 @@ export class SplashScreen extends React.PureComponent { message: __("Testing Network"), details: __("Waiting for name resolution"), isLagging: false, + isRunning: true, }); lbry.resolve({ uri: "lbry://one" }).then(() => { - this.props.onLoadDone(); + // Only leave the load screen if the daemon version matched; + // otherwise we'll notify the user at the end of the load screen. + + if (this.props.daemonVersionMatched) { + this.props.onReadyToLaunch(); + } }); return; } @@ -54,6 +64,7 @@ export class SplashScreen extends React.PureComponent { componentDidMount() { lbry .connect() + .then(this.props.checkDaemonVersion) .then(() => { this.updateStatus(); }) @@ -69,12 +80,24 @@ export class SplashScreen extends React.PureComponent { } render() { + const { modal } = this.props; + return ( - +
+ + {/* Temp hack: don't show any modals on splash screen daemon is running; + daemon doesn't let you quit during startup, so the "Quit" buttons + in the modals won't work. */} + {modal == "incompatibleDaemon" && + this.state.isRunning && + } + {modal == "upgrade" && this.state.isRunning && } + {modal == "downloading" && this.state.isRunning && } +
); } } 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; 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..e1bcfdfd1 100644 --- a/ui/js/component/userEmailVerify/view.jsx +++ b/ui/js/component/userEmailVerify/view.jsx @@ -13,7 +13,7 @@ class UserEmailVerify extends React.PureComponent { handleCodeChanged(event) { this.setState({ - code: event.target.value, + code: String(event.target.value).trim(), }); } @@ -24,18 +24,16 @@ class UserEmailVerify extends React.PureComponent { render() { const { errorMessage, isPending } = this.props; - return ( { this.handleSubmit(event); }} > +

{__("Please enter the verification code emailed to you.")}

{ @@ -46,7 +44,7 @@ class UserEmailVerify extends React.PureComponent { {/* render help separately so it always shows */}

- {__("Email")}{" "} + {__("Check your email for a verification code. Email")}{" "} {" "} {__("if you did not receive or are having trouble with your code.")}

diff --git a/ui/js/component/userVerify/index.js b/ui/js/component/userVerify/index.js new file mode 100644 index 000000000..18d29c34f --- /dev/null +++ b/ui/js/component/userVerify/index.js @@ -0,0 +1,26 @@ +import React from "react"; +import { connect } from "react-redux"; +import { doUserIdentityVerify } from "actions/user"; +import rewards from "rewards"; +import { makeSelectRewardByType } from "selectors/rewards"; +import { + selectIdentityVerifyIsPending, + selectIdentityVerifyErrorMessage, +} from "selectors/user"; +import UserVerify from "./view"; + +const select = (state, props) => { + const selectReward = makeSelectRewardByType(); + + return { + isPending: selectIdentityVerifyIsPending(state), + errorMessage: selectIdentityVerifyErrorMessage(state), + reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), + }; +}; + +const perform = dispatch => ({ + verifyUserIdentity: token => dispatch(doUserIdentityVerify(token)), +}); + +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..d09a97d34 --- /dev/null +++ b/ui/js/component/userVerify/view.jsx @@ -0,0 +1,48 @@ +import React from "react"; +import { CreditAmount } from "component/common"; +import CardVerify from "component/cardVerify"; + +class UserVerify extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + code: "", + }; + } + + handleCodeChanged(event) { + this.setState({ + code: event.target.value, + }); + } + + onToken(data) { + this.props.verifyUserIdentity(data.id); + } + + render() { + const { errorMessage, isPending, reward } = this.props; + return ( +
+ {(!reward || !reward.transaction_id) && +

+ Please link a credit card to confirm your identity and receive{" "} + {reward + ? + : your reward} +

} +

{__("This is to prevent abuse. You will not be charged.")}

+ {errorMessage &&

{errorMessage}

} + +
+ ); + } +} + +export default UserVerify; diff --git a/ui/js/component/video/internal/play-button.jsx b/ui/js/component/video/internal/play-button.jsx index fb297904a..f5223231c 100644 --- a/ui/js/component/video/internal/play-button.jsx +++ b/ui/js/component/video/internal/play-button.jsx @@ -4,12 +4,31 @@ import Link from "component/link"; import Modal from "component/modal"; class VideoPlayButton extends React.PureComponent { + componentDidMount() { + this.keyDownListener = this.onKeyDown.bind(this); + document.addEventListener("keydown", this.keyDownListener); + } + + componentWillUnmount() { + document.removeEventListener("keydown", this.keyDownListener); + } + onPurchaseConfirmed() { this.props.closeModal(); this.props.startPlaying(); this.props.loadVideo(this.props.uri); } + onKeyDown(event) { + if ( + "input" !== event.target.tagName.toLowerCase() && + "Space" === event.code + ) { + event.preventDefault(); + this.onWatchClick(); + } + } + onWatchClick() { this.props.purchaseUri(this.props.uri).then(() => { if (!this.props.modal) { @@ -59,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.")} - - {__("This will purchase")} {title} {__("for")} - {" "} - {" "}{__("credits")}. + {__("This will purchase")} {title} {__("for")}{" "} + + + {" "} + {__("credits")}. { this.setState({ hasMetadata: true, startedPlaying: true }); this.refs.media.children[0].play(); @@ -37,15 +41,22 @@ class VideoPlayer extends React.PureComponent { } }; - 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, null, false); + } else { + player.append( + this.file(), + container, + { autoplay: false, controls: true }, + renderMediaCallback.bind(this) + ); + } + document.addEventListener("keydown", this.togglePlayListener); const mediaElement = this.refs.media.children[0]; if (mediaElement) { + mediaElement.addEventListener("click", this.togglePlayListener); mediaElement.addEventListener( "loadedmetadata", loadedMetadata.bind(this), @@ -53,7 +64,6 @@ class VideoPlayer extends React.PureComponent { once: true, } ); - mediaElement.addEventListener( "webkitfullscreenchange", win32FullScreenChange.bind(this) @@ -65,19 +75,67 @@ class VideoPlayer extends React.PureComponent { } } + componentWillUnmount() { + document.removeEventListener("keydown", this.togglePlayListener); + const mediaElement = this.refs.media.children[0]; + if (mediaElement) { + mediaElement.removeEventListener("click", this.togglePlayListener); + } + } + + renderAudio(container, autoplay) { + if (container.firstChild) { + container.firstChild.remove(); + } + + // clear the container + const { downloadPath } = this.props; + const audio = document.createElement("audio"); + audio.autoplay = autoplay; + audio.controls = true; + audio.src = downloadPath; + container.appendChild(audio); + } + + togglePlay(event) { + // ignore all events except click and spacebar keydown, or input events in a form control + if ( + "keydown" === event.type && + ("Space" !== event.code || "input" === event.target.tagName.toLowerCase()) + ) { + return; + } + event.preventDefault(); + const mediaElement = this.refs.media.children[0]; + if (mediaElement) { + if (!mediaElement.paused) { + mediaElement.pause(); + } else { + mediaElement.play(); + } + } + } + getPreferredVolume() { const volumePreference = parseFloat(getSession("prefs_volume")); return isNaN(volumePreference) ? 1 : volumePreference; } 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(this.refs.media, true); + } else { + player.render(this.file(), container, { + autoplay: true, + controls: true, + }); + } } } diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx index c3cb499b9..f5cd375bd 100644 --- a/ui/js/component/video/view.jsx +++ b/ui/js/component/video/view.jsx @@ -14,6 +14,21 @@ class Video extends React.PureComponent { }; } + componentWillReceiveProps(nextProps) { + // reset playing state upon change path action + 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, @@ -98,6 +113,7 @@ class Video extends React.PureComponent { poster={poster} downloadPath={fileInfo.download_path} mediaType={mediaType} + contentType={contentType} downloadCompleted={fileInfo.completed} />)} {!isPlaying && diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 216c84762..904ea9a5f 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -5,8 +5,10 @@ export const HISTORY_BACK = "HISTORY_BACK"; export const SHOW_SNACKBAR = "SHOW_SNACKBAR"; export const REMOVE_SNACKBAR_SNACK = "REMOVE_SNACKBAR_SNACK"; export const WINDOW_FOCUSED = "WINDOW_FOCUSED"; - +export const CHANGE_AFTER_AUTH_PATH = "CHANGE_AFTER_AUTH_PATH"; export const DAEMON_READY = "DAEMON_READY"; +export const DAEMON_VERSION_MATCH = "DAEMON_VERSION_MATCH"; +export const DAEMON_VERSION_MISMATCH = "DAEMON_VERSION_MISMATCH"; // Upgrades export const UPGRADE_CANCELLED = "UPGRADE_CANCELLED"; @@ -47,7 +49,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 +65,16 @@ 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 = + "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"; @@ -86,9 +97,13 @@ export const USER_EMAIL_NEW_FAILURE = "USER_EMAIL_NEW_FAILURE"; export const USER_EMAIL_VERIFY_STARTED = "USER_EMAIL_VERIFY_STARTED"; export const USER_EMAIL_VERIFY_SUCCESS = "USER_EMAIL_VERIFY_SUCCESS"; export const USER_EMAIL_VERIFY_FAILURE = "USER_EMAIL_VERIFY_FAILURE"; +export const USER_IDENTITY_VERIFY_STARTED = "USER_IDENTITY_VERIFY_STARTED"; +export const USER_IDENTITY_VERIFY_SUCCESS = "USER_IDENTITY_VERIFY_SUCCESS"; +export const USER_IDENTITY_VERIFY_FAILURE = "USER_IDENTITY_VERIFY_FAILURE"; export const USER_FETCH_STARTED = "USER_FETCH_STARTED"; export const USER_FETCH_SUCCESS = "USER_FETCH_SUCCESS"; export const USER_FETCH_FAILURE = "USER_FETCH_FAILURE"; +export const FETCH_ACCESS_TOKEN_SUCCESS = "FETCH_ACCESS_TOKEN_SUCCESS"; // Rewards export const FETCH_REWARDS_STARTED = "FETCH_REWARDS_STARTED"; diff --git a/ui/js/constants/modal_types.js b/ui/js/constants/modal_types.js index 787dabaaf..13db30f3d 100644 --- a/ui/js/constants/modal_types.js +++ b/ui/js/constants/modal_types.js @@ -1,2 +1,8 @@ -export const WELCOME = "welcome"; export const CONFIRM_FILE_REMOVE = "confirmFileRemove"; +export const INCOMPATIBLE_DAEMON = "incompatibleDaemon"; +export const DOWNLOADING = "downloading"; +export const ERROR = "error"; +export const INSUFFICIENT_CREDITS = "insufficient_credits"; +export const UPGRADE = "upgrade"; +export const WELCOME = "welcome"; +export const FIRST_REWARD = "first_reward"; diff --git a/ui/js/lbry.js b/ui/js/lbry.js index accc5152c..07dec7e5e 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -223,55 +223,47 @@ 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() { var outSettings = {}; for (let setting of Object.keys(lbry.defaultClientSettings)) { var localStorageVal = localStorage.getItem("setting_" + setting); - outSettings[setting] = - localStorageVal === null - ? lbry.defaultClientSettings[setting] - : JSON.parse(localStorageVal); + outSettings[setting] = localStorageVal === null + ? lbry.defaultClientSettings[setting] + : JSON.parse(localStorageVal); } return outSettings; }; @@ -296,15 +288,10 @@ 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", "-"); - name = name.toLowerCase().replace(/[^a-z0-9\-]/g, ""); + name = name.toLowerCase().replace(lbryuri.REGEXP_INVALID_URI, ""); return name; }; diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 36c559bdf..8d785ee2a 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -136,8 +136,9 @@ lbryio.authenticate = function() { resolve({ id: 1, language: "en", - has_email: true, + primary_email: "disabled@lbry.io", has_verified_email: true, + is_identity_verified: true, is_reward_approved: false, is_reward_eligible: false, }); diff --git a/ui/js/lbryuri.js b/ui/js/lbryuri.js index 1fcfdec58..a7f43890a 100644 --- a/ui/js/lbryuri.js +++ b/ui/js/lbryuri.js @@ -3,6 +3,8 @@ const CLAIM_ID_MAX_LEN = 40; const lbryuri = {}; +lbryuri.REGEXP_INVALID_URI = /[^A-Za-z0-9-]/g; + /** * Parses a LBRY name into its component parts. Throws errors with user-friendly * messages for invalid names. @@ -70,7 +72,7 @@ lbryuri.parse = function(uri, requireProto = false) { contentName = path; } - const nameBadChars = (channelName || name).match(/[^A-Za-z0-9-]/g); + const nameBadChars = (channelName || name).match(lbryuri.REGEXP_INVALID_URI); if (nameBadChars) { throw new Error( __( @@ -119,7 +121,7 @@ lbryuri.parse = function(uri, requireProto = false) { throw new Error(__("Only channel URIs may have a path.")); } - const pathBadChars = path.match(/[^A-Za-z0-9-]/g); + const pathBadChars = path.match(lbryuri.REGEXP_INVALID_URI); if (pathBadChars) { throw new Error( __(`Invalid character in path: %s`, pathBadChars.join(", ")) @@ -203,6 +205,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/main.js b/ui/js/main.js index 9bef9aae1..48f838331 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -5,11 +5,9 @@ import App from "component/app/index.js"; import SnackBar from "component/snackBar"; import { Provider } from "react-redux"; import store from "store.js"; -import SplashScreen from "component/splash.js"; -import AuthOverlay from "component/authOverlay"; +import SplashScreen from "component/splash"; import { doChangePath, doNavigate, doDaemonReady } from "actions/app"; import { toQueryString } from "util/query_params"; -import { selectBadgeNumber } from "selectors/app"; import * as types from "constants/action_types"; const env = ENV; @@ -37,10 +35,10 @@ window.addEventListener("popstate", (event, param) => { if (hash !== "") { const url = hash.split("#")[1]; - const params = event.state; + const { params, scrollY } = event.state || {}; const queryString = toQueryString(params); - app.store.dispatch(doChangePath(`${url}?${queryString}`)); + app.store.dispatch(doChangePath(`${url}?${queryString}`, { scrollY })); } else { app.store.dispatch(doChangePath("/discover")); } @@ -97,19 +95,6 @@ const updateProgress = () => { 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 +102,7 @@ var init = function() { ReactDOM.render( -
+
, canvas ); @@ -126,7 +111,12 @@ var init = function() { if (window.sessionStorage.getItem("loaded") == "y") { onDaemonReady(); } else { - ReactDOM.render(, canvas); + ReactDOM.render( + + + , + canvas + ); } }; diff --git a/ui/js/page/auth/index.js b/ui/js/page/auth/index.js new file mode 100644 index 000000000..541504dbb --- /dev/null +++ b/ui/js/page/auth/index.js @@ -0,0 +1,30 @@ +import React from "react"; +import { doNavigate } from "actions/app"; +import { connect } from "react-redux"; +import { selectPathAfterAuth } from "selectors/app"; +import { + selectAuthenticationIsPending, + selectEmailToVerify, + selectUserIsVerificationCandidate, + selectUser, + selectUserIsPending, + selectIdentityVerifyIsPending, +} from "selectors/user"; +import AuthPage from "./view"; + +const select = state => ({ + isPending: + selectAuthenticationIsPending(state) || + selectUserIsPending(state) || + selectIdentityVerifyIsPending(state), + email: selectEmailToVerify(state), + pathAfterAuth: selectPathAfterAuth(state), + user: selectUser(state), + isVerificationCandidate: selectUserIsVerificationCandidate(state), +}); + +const perform = dispatch => ({ + navigate: path => dispatch(doNavigate(path)), +}); + +export default connect(select, perform)(AuthPage); diff --git a/ui/js/page/auth/view.jsx b/ui/js/page/auth/view.jsx new file mode 100644 index 000000000..fac6e13a0 --- /dev/null +++ b/ui/js/page/auth/view.jsx @@ -0,0 +1,89 @@ +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 AuthPage extends React.PureComponent { + componentWillMount() { + this.navigateIfAuthenticated(this.props); + } + + componentWillReceiveProps(nextProps) { + this.navigateIfAuthenticated(nextProps); + } + + navigateIfAuthenticated(props) { + const { isPending, user } = props; + if ( + !isPending && + user && + user.has_verified_email && + user.is_identity_verified + ) { + props.navigate(props.pathAfterAuth); + } + } + + getTitle() { + const { email, isPending, isVerificationCandidate, user } = this.props; + + if (isPending || (user && !user.has_verified_email && !email)) { + return __("Welcome to LBRY"); + } else if (user && !user.has_verified_email) { + return __("Confirm Email"); + } else if (user && !user.is_identity_verified) { + return __("Confirm Identity"); + } else { + return __("Welcome to LBRY"); + } + } + + renderMain() { + const { email, isPending, isVerificationCandidate, user } = this.props; + + if (isPending) { + return ; + } else if (user && !user.has_verified_email && !email) { + return ; + } else if (user && !user.has_verified_email) { + return ; + } else if (user && !user.is_identity_verified) { + return ; + } else { + return {__("No further steps.")}; + } + } + + render() { + const { email, user, isPending } = this.props; + + return ( +
+
+
+

{this.getTitle()}

+
+
+ {!isPending && + !email && + !user.has_verified_email && +

+ {__("Create a verified identity and receive LBC rewards.")} +

} + {this.renderMain()} +
+
+
+ {__( + "This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is optional and only collected to provide communication and prevent abuse. You may use LBRY without providing this information." + )} +
+
+
+
+ ); + } +} + +export default AuthPage; diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index d6c506dfd..fc57a5ba8 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -45,16 +45,20 @@ class DiscoverPage extends React.PureComponent { render() { const { featuredUris, fetchingFeaturedUris } = this.props; - const failedToLoad = - !fetchingFeaturedUris && - (featuredUris === undefined || - (featuredUris !== undefined && Object.keys(featuredUris).length === 0)); + const hasContent = + typeof featuredUris === "object" && Object.keys(featuredUris).length, + failedToLoad = !fetchingFeaturedUris && !hasContent; return ( -
- {fetchingFeaturedUris && +
+ {!hasContent && + fetchingFeaturedUris && } - {typeof featuredUris === "object" && + {hasContent && Object.keys(featuredUris).map( category => featuredUris[category].length diff --git a/ui/js/page/fileListDownloaded/index.js b/ui/js/page/fileListDownloaded/index.js index 86d26d851..c2473e02a 100644 --- a/ui/js/page/fileListDownloaded/index.js +++ b/ui/js/page/fileListDownloaded/index.js @@ -3,15 +3,22 @@ import { connect } from "react-redux"; import { doFetchFileInfosAndPublishedClaims } from "actions/file_info"; import { selectFileInfosDownloaded, - selectFileListDownloadedOrPublishedIsPending, + selectIsFetchingFileListDownloadedOrPublished, } from "selectors/file_info"; +import { + selectMyClaimsWithoutChannels, + selectIsFetchingClaimListMine, +} from "selectors/claims"; +import { doFetchClaimListMine } from "actions/content"; import { doNavigate } from "actions/app"; import { doCancelAllResolvingUris } from "actions/content"; import FileListDownloaded from "./view"; const select = state => ({ fileInfos: selectFileInfosDownloaded(state), - isPending: selectFileListDownloadedOrPublishedIsPending(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 03665847c..8eb18e9d5 100644 --- a/ui/js/page/fileListDownloaded/view.jsx +++ b/ui/js/page/fileListDownloaded/view.jsx @@ -12,7 +12,8 @@ import SubHeader from "component/subHeader"; class FileListDownloaded extends React.PureComponent { componentWillMount() { - if (!this.props.isPending) this.props.fetchFileInfosDownloaded(); + if (!this.props.isFetchingClaims) this.props.fetchClaims(); + if (!this.props.isFetching) this.props.fetchFileInfosDownloaded(); } componentWillUnmount() { @@ -20,13 +21,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..8100c2977 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, - selectFileListDownloadedOrPublishedIsPending, -} from "selectors/file_info"; + selectMyClaimsWithoutChannels, + 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), - isPending: selectFileListDownloadedOrPublishedIsPending(state), + claims: selectMyClaimsWithoutChannels(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 822cfeb7d..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.isPending) 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,20 +24,20 @@ class FileListPublished extends React.PureComponent { } render() { - const { fileInfos, isPending, navigate } = this.props; + const { claims, isFetching, navigate } = this.props; let content; - if (fileInfos && fileInfos.length > 0) { + if (claims && claims.length > 0) { content = ( ); } else { - if (isPending) { + if (isFetching) { content = ; } else { content = ( 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), }); 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/help/index.js b/ui/js/page/help/index.js index 31bb1ed51..c4ea548b2 100644 --- a/ui/js/page/help/index.js +++ b/ui/js/page/help/index.js @@ -1,10 +1,18 @@ import React from "react"; import { doNavigate } from "actions/app"; import { connect } from "react-redux"; +import { doFetchAccessToken } from "actions/user"; +import { selectAccessToken, selectUser } from "selectors/user"; import HelpPage from "./view"; +const select = state => ({ + user: selectUser(state), + accessToken: selectAccessToken(state), +}); + const perform = dispatch => ({ navigate: (path, params) => dispatch(doNavigate(path, params)), + fetchAccessToken: () => dispatch(doFetchAccessToken()), }); -export default connect(null, perform)(HelpPage); +export default connect(select, perform)(HelpPage); diff --git a/ui/js/page/help/view.jsx b/ui/js/page/help/view.jsx index b8a59a619..f56d058e8 100644 --- a/ui/js/page/help/view.jsx +++ b/ui/js/page/help/view.jsx @@ -14,6 +14,7 @@ class HelpPage extends React.PureComponent { lbryId: null, uiVersion: null, upgradeAvailable: null, + accessTokenHidden: true, }; } @@ -36,12 +37,20 @@ class HelpPage extends React.PureComponent { lbryId: info.lbry_id, }); }); + + if (!this.props.accessToken) this.props.fetchAccessToken(); + } + + showAccessToken() { + this.setState({ + accessTokenHidden: false, + }); } render() { let ver, osName, platform, newVerLink; - const { navigate } = this.props; + const { navigate, user } = this.props; if (this.state.versionInfo) { ver = this.state.versionInfo; @@ -121,6 +130,7 @@ class HelpPage extends React.PureComponent {
+

{__("About")}

@@ -136,16 +146,24 @@ class HelpPage extends React.PureComponent { ? - + + + + + - + - - + + @@ -155,6 +173,18 @@ class HelpPage extends React.PureComponent { + + + +
{__("daemon (lbrynet)")}{__("App")}{this.state.uiVersion}
{__("Daemon (lbrynet)")} {ver.lbrynet_version}
{__("wallet (lbryum)")}{__("Wallet (lbryum)")} {ver.lbryum_version}
{__("interface")}{this.state.uiVersion}{__("Connected Email")} + {user && user.primary_email + ? user.primary_email + : {__("none")}} +
{__("Platform")}{__("Installation ID")} {this.state.lbryId}
{__("Access Token")} + {this.state.accessTokenHidden && + } + {!this.state.accessTokenHidden && + this.props.accessToken} +
: } diff --git a/ui/js/page/publish/index.js b/ui/js/page/publish/index.js index fac4419c1..f296f1687 100644 --- a/ui/js/page/publish/index.js +++ b/ui/js/page/publish/index.js @@ -2,13 +2,29 @@ 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, + selectClaimsByUri, +} from "selectors/claims"; +import { selectResolvingUris } from "selectors/content"; +import { + doFetchClaimListMine, + doFetchChannelListMine, + doResolveUri, + doCreateChannel, + doPublish, +} from "actions/content"; import rewards from "rewards"; import PublishPage from "./view"; const select = state => ({ myClaims: selectMyClaims(state), + fetchingChannels: selectFetchingMyChannels(state), + channels: selectMyChannelClaims(state), + claimsByUri: selectClaimsByUri(state), + resolvingUris: selectResolvingUris(state), }); const perform = dispatch => ({ @@ -17,6 +33,10 @@ const perform = dispatch => ({ fetchClaimListMine: () => dispatch(doFetchClaimListMine()), claimFirstChannelReward: () => 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 0693d61cf..ab39fcec8 100644 --- a/ui/js/page/publish/view.jsx +++ b/ui/js/page/publish/view.jsx @@ -1,922 +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 PublishForm from "component/publishForm"; -class PublishPage extends React.PureComponent { - constructor(props) { - super(props); - - this._requiredFields = ["meta_title", "name", "bid", "tos_agree"]; - - this.state = { - channels: null, - rawName: "", - name: "", - bid: 10, - hasFile: false, - feeAmount: "", - feeCurrency: "USD", - channel: "anonymous", - newChannelName: "@", - newChannelBid: 10, - nameResolved: null, - myClaimExists: null, - topClaimValue: 0.0, - myClaimValue: 0.0, - myClaimMetadata: null, - copyrightNotice: "", - otherLicenseDescription: "", - otherLicenseUrl: "", - uploadProgress: 0.0, - uploaded: false, - errorMessage: null, - submitting: false, - creatingChannel: false, - modal: null, - }; - } - - _updateChannelList(channel) { - // 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) { - if (typeof event !== "undefined") { - event.preventDefault(); - } - - this.setState({ - submitting: true, - }); - - let checkFields = this._requiredFields; - if (!this.state.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; - } - - 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 = {}; - } - - for (let metaField of [ - "title", - "description", - "thumbnail", - "license", - "license_url", - "language", - ]) { - var value = this.refs["meta_" + metaField].getValue(); - 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; - } - - 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(); - } - - lbry.publishDeprecated( - publishArgs, - message => { - this.handlePublishStarted(); - }, - null, - error => { - this.handlePublishError(error); - } - ); - }; - - 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, - }); - } - - handleNameChange(event) { - var rawName = event.target.value; - - if (!rawName) { - this.setState({ - rawName: "", - name: "", - nameResolved: false, - }); - - return; - } - - if (!lbryuri.isValidName(rawName, false)) { - this.refs.name.showError( - __("LBRY names must contain only letters, numbers and dashes.") - ); - return; - } - - const name = rawName.toLowerCase(); - this.setState({ - rawName: rawName, - name: name, - nameResolved: null, - myClaimExists: null, - }); - - 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, - }); - } - ); - } - - 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, - }); - } - - handleLicenseChange(event) { - var licenseType = event.target.options[ - event.target.selectedIndex - ].getAttribute("data-license-type"); - var newState = { - copyrightChosen: licenseType == "copyright", - otherLicenseChosen: licenseType == "other", - }; - - if (licenseType == "copyright") { - newState.copyrightNotice = __("All rights reserved."); - } - - this.setState(newState); - } - - 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(event) { - const channel = event.target.value; - - 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, - }); - } - - handleTOSChange(event) { - this.setState({ - TOSAgreed: 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, - }); - } - ); - } - - 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") || - "" - ); - } - } - - 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.name) { - return __("Select a URL for this publish."); - } else if (this.state.nameResolved === false) { - return __("This URL is unused."); - } else if (this.state.myClaimExists) { - return __( - "You have already used this URL. Publishing to it again will update your previous publish." - ); - } else if (this.state.topClaimValue) { - if (this.state.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.state.topClaimValue, - this.state.name - )} - - ); - } - } else { - return ""; - } - } - - closeModal() { - this.setState({ - modal: null, - }); - } - - render() { - if (this.state.channels === null) { - return null; - } - - 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.state.myClaimExists - ? __( - "If you don't choose a file, the file from your existing claim will be used." - ) - : null - } - /> -
- {!this.state.hasFile - ? "" - :
-
- -
-
- -
-
- -
-
- - - - - - - - - -
-
- - {/* */} - - - -
-
} -
- -
-
-

{__("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.handleLicenseChange(event); - }} - > - - - - - - - - - - - - {this.state.copyrightChosen - ? { - this.handleCopyrightNoticeChange(event); - }} - /> - : null} - {this.state.otherLicenseChosen - ? { - this.handleOtherLicenseDescriptionChange(); - }} - /> - : null} - {this.state.otherLicenseChosen - ? { - this.handleOtherLicenseUrlChange(event); - }} - /> - : null} -
-
- -
-
-

{__("Identity")}

-
- {__("Who created this content?")} -
-
-
- { - this.handleChannelChange(event); - }} - value={this.state.channel} - > - - {this.state.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} -
- -
-
-

{__("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.state.nameResolved - ? this.state.topClaimValue + 10 - : 100 - } - helper={lbcInputHelp} - /> -
- : ""} -
- -
-
-

{__("Terms of Service")}

-
-
- - {__("I agree to the")} - {" "} - - - } - type="checkbox" - name="tos_agree" - ref={field => { - this.refs.tos_agree = field; - }} - 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")} - {" "}lbry://{this.state.name}! -

-

- {__( - '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} - -
- ); - } -} +const PublishPage = props => { + return ; +}; export default PublishPage; diff --git a/ui/js/page/rewards/index.js b/ui/js/page/rewards/index.js index 8e7abb1fc..8a7a29d98 100644 --- a/ui/js/page/rewards/index.js +++ b/ui/js/page/rewards/index.js @@ -1,25 +1,33 @@ import React from "react"; import { connect } from "react-redux"; -import { doNavigate } from "actions/app"; -import { selectFetchingRewards, selectRewards } from "selectors/rewards"; import { - selectUserIsRewardEligible, - selectUserHasEmail, - selectUserIsVerificationCandidate, -} from "selectors/user"; + makeSelectRewardByType, + selectFetchingRewards, + selectRewards, +} from "selectors/rewards"; +import { selectUser } from "selectors/user"; +import { doAuthNavigate, doNavigate } from "actions/app"; 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), + newUserReward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), + user: selectUser(state), + }; +}; const perform = dispatch => ({ fetchRewards: () => dispatch(doRewardList()), + navigate: path => dispatch(doNavigate(path)), + doAuth: () => { + dispatch(doAuthNavigate("/rewards")); + }, }); export default connect(select, perform)(RewardsPage); diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx index 3b6964761..f69f3c61b 100644 --- a/ui/js/page/rewards/view.jsx +++ b/ui/js/page/rewards/view.jsx @@ -1,8 +1,6 @@ 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"; @@ -41,65 +39,91 @@ 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() { - const { - fetching, - isEligible, - isVerificationCandidate, - hasEmail, - rewards, - } = this.props; + const { doAuth, fetching, navigate, rewards, user } = this.props; - let content, - isCard = false; + let content, cardHeader; - if (!hasEmail || isVerificationCandidate) { + if (fetching) { + content = ( +
+ +
+ ); + } else if (rewards.length > 0) { content = (
-

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

- + {rewards.map(reward => + + )}
); - isCard = true; - } else if (!isEligible) { - isCard = true; - content = ( -
-

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

-

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

-
- ); - } else if (fetching) { - content = ; - } else if (rewards.length > 0) { - content = rewards.map(reward => - - ); } else { - content =
{__("Failed to load rewards.")}
; + content = ( +
+ {__("Failed to load rewards.")} +
+ ); + } + + if ( + user && + (!user.primary_email || + !user.has_verified_email || + !user.is_identity_verified) + ) { + cardHeader = ( +
+
+

{__("Only verified accounts are eligible to earn rewards.")}

+
+
+ +
+
+ ); + } else if (user && !user.is_reward_approved) { + cardHeader = ( +
+

+ {__( + "This account must undergo review before you can participate in the rewards program." + )} + {" "} + {__("This can take anywhere from several minutes to several days.")} +

+ +

+ {__( + "We apologize for this inconvenience, but have added this additional step to prevent fraud." + )} +

+

+ {__("You will receive an email when this process is complete.") + + " " + + __("Please enjoy free content in the meantime!")} +

+

+ navigate("/discover")} + button="primary" + label="Return Home" + /> +

+
+ ); } return (
- {isCard - ?
-
- {content} -
-
- : content} + {cardHeader &&
{cardHeader}
} + {content}
); } diff --git a/ui/js/page/settings/view.jsx b/ui/js/page/settings/view.jsx index c30246e79..fbe5ebb94 100644 --- a/ui/js/page/settings/view.jsx +++ b/ui/js/page/settings/view.jsx @@ -13,8 +13,8 @@ class SettingsPage extends React.PureComponent { const { daemonSettings } = this.props; this.state = { - isMaxUpload: daemonSettings && daemonSettings.max_upload != 0, - isMaxDownload: daemonSettings && daemonSettings.max_download != 0, + // isMaxUpload: daemonSettings && daemonSettings.max_upload != 0, + // isMaxDownload: daemonSettings && daemonSettings.max_download != 0, showUnavailable: lbry.getClientSetting("showUnavailable"), language: lbry.getClientSetting("language"), clearingCache: false, @@ -55,15 +55,6 @@ class SettingsPage extends React.PureComponent { this.setDaemonSetting("download_directory", event.target.value); } - onMaxUploadPrefChange(isLimited) { - if (!isLimited) { - this.setDaemonSetting("max_upload", 0.0); - } - this.setState({ - isMaxUpload: isLimited, - }); - } - onKeyFeeChange(event) { var oldSettings = this.props.daemonSettings.max_key_fee; var newSettings = { @@ -89,23 +80,32 @@ class SettingsPage extends React.PureComponent { onKeyFeeDisableChange(isDisabled) { this.setDaemonSetting("disable_max_key_fee", isDisabled); } - - onMaxUploadFieldChange(event) { - this.setDaemonSetting("max_upload", Number(event.target.value)); - } - - onMaxDownloadPrefChange(isLimited) { - if (!isLimited) { - this.setDaemonSetting("max_download", 0.0); - } - this.setState({ - isMaxDownload: isLimited, - }); - } - - onMaxDownloadFieldChange(event) { - this.setDaemonSetting("max_download", Number(event.target.value)); - } + + // onMaxUploadPrefChange(isLimited) { + // if (!isLimited) { + // this.setDaemonSetting("max_upload", 0.0); + // } + // this.setState({ + // isMaxUpload: isLimited, + // }); + // } + // + // onMaxUploadFieldChange(event) { + // this.setDaemonSetting("max_upload", Number(event.target.value)); + // } + // + // onMaxDownloadPrefChange(isLimited) { + // if (!isLimited) { + // this.setDaemonSetting("max_download", 0.0); + // } + // this.setState({ + // isMaxDownload: isLimited, + // }); + // } + // + // onMaxDownloadFieldChange(event) { + // this.setDaemonSetting("max_download", Number(event.target.value)); + // } onShowNsfwChange(event) { this.props.setClientSetting("showNsfw", event.target.checked); @@ -174,6 +174,96 @@ class SettingsPage extends React.PureComponent {
*/ + + /* +
+
+

{__("Bandwidth Limits")}

+
+
+
+
{__("Max Upload")}
+
+ { + this.onMaxUploadPrefChange(false); + }} + defaultChecked={!this.state.isMaxUpload} + label={__("Unlimited")} + /> +
+ { + this.onMaxUploadPrefChange(true); + }} + defaultChecked={this.state.isMaxUpload} + label={ + this.state.isMaxUpload ? __("Up to") : __("Choose limit...") + } + /> + {this.state.isMaxUpload + ? + : ""} + {this.state.isMaxUpload + ? MB/s + : ""} +
+
+
+
+
{__("Max Download")}
+
+ { + this.onMaxDownloadPrefChange(false); + }} + defaultChecked={!this.state.isMaxDownload} + /> +
+ { + this.onMaxDownloadPrefChange(true); + }} + defaultChecked={this.state.isMaxDownload} + label={ + this.state.isMaxDownload ? __("Up to") : __("Choose limit...") + } + /> + {this.state.isMaxDownload + ? + : ""} + {this.state.isMaxDownload + ? MB/s + : ""} +
+
+
+ */ return (
@@ -191,98 +281,9 @@ class SettingsPage extends React.PureComponent { />
-
-

{__("Bandwidth Limits")}

-
-
-
-
{__("Max Upload")}
-
- { - this.onMaxUploadPrefChange(false); - }} - defaultChecked={!this.state.isMaxUpload} - label={__("Unlimited")} - /> -
- { - this.onMaxUploadPrefChange(true); - }} - defaultChecked={this.state.isMaxUpload} - label={ - this.state.isMaxUpload ? __("Up to") : __("Choose limit...") - } - /> - {this.state.isMaxUpload - ? - : ""} - {this.state.isMaxUpload - ? MB/s - : ""} -
-
-
-
-
{__("Max Download")}
-
- { - this.onMaxDownloadPrefChange(false); - }} - defaultChecked={!this.state.isMaxDownload} - /> -
- { - this.onMaxDownloadPrefChange(true); - }} - defaultChecked={this.state.isMaxDownload} - label={ - this.state.isMaxDownload ? __("Up to") : __("Choose limit...") - } - /> - {this.state.isMaxDownload - ? - : ""} - {this.state.isMaxDownload - ? MB/s - : ""} -
-
-
- -
-
-

{__("Key Fee")}

+

{__("Max Purchase Price")}

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 = (
diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index fe8c9adae..6ee007b86 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -1,4 +1,5 @@ import * as types from "constants/action_types"; +import * as modalTypes from "constants/modal_types"; import lbry from "lbry"; const currentPath = () => { @@ -15,8 +16,10 @@ const reducers = {}; const defaultState = { isLoaded: false, currentPath: currentPath(), + pathAfterAuth: "/discover", platform: process.platform, upgradeSkipped: sessionStorage.getItem("upgradeSkipped"), + daemonVersionMatched: null, daemonReady: false, hasSignature: false, badgeNumber: 0, @@ -28,12 +31,31 @@ reducers[types.DAEMON_READY] = function(state, action) { }); }; +reducers[types.DAEMON_VERSION_MATCH] = function(state, action) { + return Object.assign({}, state, { + daemonVersionMatched: true, + }); +}; + +reducers[types.DAEMON_VERSION_MISMATCH] = function(state, action) { + return Object.assign({}, state, { + daemonVersionMatched: false, + modal: modalTypes.INCOMPATIBLE_DAEMON, + }); +}; + reducers[types.CHANGE_PATH] = function(state, action) { return Object.assign({}, state, { currentPath: action.data.path, }); }; +reducers[types.CHANGE_AFTER_AUTH_PATH] = function(state, action) { + return Object.assign({}, state, { + pathAfterAuth: action.data.path, + }); +}; + reducers[types.UPGRADE_CANCELLED] = function(state, action) { return Object.assign({}, state, { downloadProgress: null, diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 7417bc2fb..9e072f1ce 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; } @@ -28,43 +34,72 @@ 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, }); }; 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 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 => { - 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, { - isClaimListMinePending: false, + isFetchingClaimListMine: false, myClaims: myClaims, byId, + pendingById, }); }; -// 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; @@ -80,7 +115,18 @@ reducers[types.FETCH_CHANNEL_CLAIMS_COMPLETED] = function(state, action) { }); }; -reducers[types.ABANDON_CLAIM_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_SUCCEEDED] = function(state, action) { const { claimId } = action.data; const myClaims = new Set(state.myClaims); const byId = Object.assign({}, state.byId); @@ -103,6 +149,20 @@ 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, + }); +}; + 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..cb5221c98 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -6,14 +6,15 @@ const defaultState = {}; reducers[types.FILE_LIST_STARTED] = function(state, action) { return Object.assign({}, state, { - isFileListPending: true, + isFetchingFileList: true, }); }; -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); + fileInfos.forEach(fileInfo => { const { outpoint } = fileInfo; @@ -21,8 +22,9 @@ reducers[types.FILE_LIST_COMPLETED] = function(state, action) { }); return Object.assign({}, state, { - isFileListPending: false, + isFetchingFileList: false, byOutpoint: newByOutpoint, + pendingByOutpoint, }); }; @@ -56,15 +58,15 @@ reducers[types.DOWNLOADING_STARTED] = function(state, action) { const { uri, outpoint, fileInfo } = action.data; const newByOutpoint = Object.assign({}, state.byOutpoint); - const newDownloading = Object.assign({}, state.urisDownloading); + const newDownloading = Object.assign({}, state.downloadingByOutpoin); const newLoading = Object.assign({}, state.urisLoading); - newDownloading[uri] = true; + newDownloading[outpoint] = true; newByOutpoint[outpoint] = fileInfo; delete newLoading[uri]; return Object.assign({}, state, { - urisDownloading: newDownloading, + downloadingByOutpoint: newDownloading, urisLoading: newLoading, byOutpoint: newByOutpoint, }); @@ -74,14 +76,14 @@ reducers[types.DOWNLOADING_PROGRESSED] = function(state, action) { const { uri, outpoint, fileInfo } = action.data; const newByOutpoint = Object.assign({}, state.byOutpoint); - const newDownloading = Object.assign({}, state.urisDownloading); + const newDownloading = Object.assign({}, state.downloadingByOutpoint); newByOutpoint[outpoint] = fileInfo; - newDownloading[uri] = true; + newDownloading[outpoint] = true; return Object.assign({}, state, { byOutpoint: newByOutpoint, - urisDownloading: newDownloading, + downloadingByOutpoint: newDownloading, }); }; @@ -89,14 +91,14 @@ reducers[types.DOWNLOADING_COMPLETED] = function(state, action) { const { uri, outpoint, fileInfo } = action.data; const newByOutpoint = Object.assign({}, state.byOutpoint); - const newDownloading = Object.assign({}, state.urisDownloading); + const newDownloading = Object.assign({}, state.downloadingByOutpoint); newByOutpoint[outpoint] = fileInfo; - delete newDownloading[uri]; + delete newDownloading[outpoint]; return Object.assign({}, state, { byOutpoint: newByOutpoint, - urisDownloading: newDownloading, + downloadingByOutpoint: newDownloading, }); }; @@ -104,11 +106,14 @@ reducers[types.FILE_DELETE] = function(state, action) { const { outpoint } = action.data; const newByOutpoint = Object.assign({}, state.byOutpoint); + const downloadingByOutpoint = Object.assign({}, state.downloadingByOutpoint); delete newByOutpoint[outpoint]; + delete downloadingByOutpoint[outpoint]; return Object.assign({}, state, { byOutpoint: newByOutpoint, + downloadingByOutpoint, }); }; diff --git a/ui/js/reducers/user.js b/ui/js/reducers/user.js index 4e4ffda40..56720b2f7 100644 --- a/ui/js/reducers/user.js +++ b/ui/js/reducers/user.js @@ -73,7 +73,7 @@ reducers[types.USER_EMAIL_NEW_STARTED] = function(state, action) { reducers[types.USER_EMAIL_NEW_SUCCESS] = function(state, action) { let user = Object.assign({}, state.user); - user.has_email = true; + user.primary_email = action.data.email; return Object.assign({}, state, { emailToVerify: action.data.email, emailNewIsPending: false, @@ -105,7 +105,7 @@ reducers[types.USER_EMAIL_VERIFY_STARTED] = function(state, action) { reducers[types.USER_EMAIL_VERIFY_SUCCESS] = function(state, action) { let user = Object.assign({}, state.user); - user.has_email = true; + user.primary_email = action.data.email; return Object.assign({}, state, { emailToVerify: "", emailVerifyIsPending: false, @@ -120,6 +120,36 @@ reducers[types.USER_EMAIL_VERIFY_FAILURE] = function(state, action) { }); }; +reducers[types.USER_IDENTITY_VERIFY_STARTED] = function(state, action) { + return Object.assign({}, state, { + identityVerifyIsPending: true, + identityVerifyErrorMessage: "", + }); +}; + +reducers[types.USER_IDENTITY_VERIFY_SUCCESS] = function(state, action) { + return Object.assign({}, state, { + identityVerifyIsPending: false, + identityVerifyErrorMessage: "", + user: action.data.user, + }); +}; + +reducers[types.USER_IDENTITY_VERIFY_FAILURE] = function(state, action) { + return Object.assign({}, state, { + identityVerifyIsPending: false, + identityVerifyErrorMessage: action.data.error, + }); +}; + +reducers[types.FETCH_ACCESS_TOKEN_SUCCESS] = function(state, action) { + const { token } = action.data; + + return Object.assign({}, state, { + accessToken: token, + }); +}; + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/rewards.js b/ui/js/rewards.js index 64f8a6f65..fd952032c 100644 --- a/ui/js/rewards.js +++ b/ui/js/rewards.js @@ -30,6 +30,10 @@ function rewardMessage(type, amount) { "You earned %s LBC for making your first publication.", amount ), + featured_download: __( + "You earned %s LBC for watching a featured download.", + amount + ), }[type]; } diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index f6acd6d07..3951a8216 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -177,6 +177,11 @@ export const selectDaemonReady = createSelector( state => state.daemonReady ); +export const selectDaemonVersionMatched = createSelector( + _selectState, + state => state.daemonVersionMatched +); + export const selectSnackBar = createSelector( _selectState, state => state.snackBar || {} @@ -187,7 +192,17 @@ 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 ); + +export const selectPathAfterAuth = createSelector( + _selectState, + state => state.pathAfterAuth +); diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index b966375e9..3c10ec931 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); }; @@ -100,27 +100,72 @@ export const makeSelectContentTypeForUri = () => { ); }; -export const selectClaimListMineIsPending = createSelector( +export const selectIsFetchingClaimListMine = createSelector( _selectState, - state => state.isClaimListMinePending + state => !!state.isFetchingClaimListMine ); -export const selectMyClaims = createSelector( +export const selectMyClaimsRaw = createSelector( _selectState, 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, + selectAbandoningIds, + selectPendingClaims, + (myClaimIds, byId, abandoningIds, pendingClaims) => { + const claims = []; + + myClaimIds.forEach(id => { + const claim = byId[id]; + + if (claim && abandoningIds.indexOf(id) == -1) claims.push(claim); + }); + + return [...claims, ...pendingClaims]; + } +); + +export const selectMyClaimsWithoutChannels = createSelector( + selectMyClaims, + myClaims => myClaims.filter(claim => !claim.name.match(/^@/)) +); + 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; } ); + +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; + } +); diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index 552368f7e..1b3dc7982 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -2,7 +2,8 @@ import lbry from "lbry"; import { createSelector } from "reselect"; import { selectClaimsByUri, - selectClaimListMineIsPending, + selectIsFetchingClaimListMine, + selectMyClaims, selectMyClaimsOutpoints, } from "selectors/claims"; @@ -13,16 +14,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) => { @@ -38,14 +39,18 @@ export const makeSelectFileInfoForUri = () => { return createSelector(selectFileInfoForUri, fileInfo => fileInfo); }; -export const selectUrisDownloading = createSelector( +export const selectDownloadingByOutpoint = createSelector( _selectState, - state => state.urisDownloading || {} + state => state.downloadingByOutpoint || {} ); const selectDownloadingForUri = (state, props) => { - const byUri = selectUrisDownloading(state); - return byUri[props.uri]; + const byOutpoint = selectDownloadingByOutpoint(state); + const fileInfo = selectFileInfoForUri(state, props); + + if (!fileInfo) return false; + + return byOutpoint[fileInfo.outpoint]; }; export const makeSelectDownloadingForUri = () => { @@ -69,42 +74,38 @@ export const makeSelectLoadingForUri = () => { return createSelector(selectLoadingForUri, loading => !!loading); }; -export const selectFileInfosDownloaded = createSelector( - selectFileInfosByOutpoint, - selectMyClaimsOutpoints, - (byOutpoint, myClaimOutpoints) => { - const fileInfoList = []; - Object.values(byOutpoint).forEach(fileInfo => { - if ( - fileInfo && - myClaimOutpoints.indexOf(fileInfo.outpoint) === -1 && - (fileInfo.completed || fileInfo.written_bytes) - ) { - fileInfoList.push(fileInfo); - } - }); - return fileInfoList; - } -); - export const selectFileInfosPendingPublish = createSelector( _selectState, - state => { - return lbry.getPendingPublishes(); + state => Object.values(state.pendingByOutpoint || {}) +); + +export const selectFileInfosDownloaded = createSelector( + selectFileInfosByOutpoint, + selectMyClaims, + (byOutpoint, myClaims) => { + return Object.values(byOutpoint).filter(fileInfo => { + const myClaimIds = myClaims.map(claim => claim.claim_id); + + return ( + fileInfo && + myClaimIds.indexOf(fileInfo.claim_id) === -1 && + (fileInfo.completed || fileInfo.written_bytes) + ); + }); } ); 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, ...pendingPublish]; } ); @@ -133,20 +134,19 @@ export const selectFileInfosByUri = createSelector( if (fileInfo) fileInfos[uri] = fileInfo; } }); - return fileInfos; } ); export const selectDownloadingFileInfos = createSelector( - selectUrisDownloading, - selectFileInfosByUri, - (urisDownloading, byUri) => { - const uris = Object.keys(urisDownloading); + selectDownloadingByOutpoint, + selectFileInfosByOutpoint, + (downloadingByOutpoint, fileInfosByOutpoint) => { + const outpoints = Object.keys(downloadingByOutpoint); const fileInfos = []; - uris.forEach(uri => { - const fileInfo = byUri[uri]; + outpoints.forEach(outpoint => { + const fileInfo = fileInfosByOutpoint[outpoint]; if (fileInfo) fileInfos.push(fileInfo); }); diff --git a/ui/js/selectors/user.js b/ui/js/selectors/user.js index d17485aa6..68554d825 100644 --- a/ui/js/selectors/user.js +++ b/ui/js/selectors/user.js @@ -12,25 +12,16 @@ 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, state => state.emailToVerify ); -export const selectUserHasEmail = createSelector( +export const selectUserEmail = createSelector( selectUser, - selectEmailToVerify, - (user, email) => (user && user.has_email) || !!email -); - -export const selectUserIsRewardEligible = createSelector( - selectUser, - user => user && user.is_reward_eligible + user => (user ? user.primary_email : null) ); export const selectUserIsRewardApproved = createSelector( @@ -63,16 +54,22 @@ export const selectEmailVerifyErrorMessage = createSelector( state => state.emailVerifyErrorMessage ); -export const selectUserIsVerificationCandidate = createSelector( - selectUser, - user => user && !user.has_verified_email +export const selectIdentityVerifyIsPending = createSelector( + _selectState, + state => state.identityVerifyIsPending ); -export const selectUserIsAuthRequested = createSelector( - selectEmailNewDeclined, - selectAuthenticationIsPending, - selectUserIsVerificationCandidate, - selectUserHasEmail, - (isEmailDeclined, isPending, isVerificationCandidate, hasEmail) => - !isEmailDeclined && (isPending || !hasEmail || isVerificationCandidate) +export const selectIdentityVerifyErrorMessage = createSelector( + _selectState, + state => state.identityVerifyErrorMessage +); + +export const selectUserIsVerificationCandidate = createSelector( + selectUser, + user => user && (!user.has_verified_email || !user.is_identity_verified) +); + +export const selectAccessToken = createSelector( + _selectState, + state => state.accessToken ); diff --git a/ui/js/store.js b/ui/js/store.js index 0ec06c017..5eb84d4cf 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -86,18 +86,14 @@ const createStoreWithMiddleware = redux.compose( const reduxStore = createStoreWithMiddleware(enableBatching(reducers)); const compressor = createCompressor(); -const saveClaimsFilter = createFilter("claims", [ - "byId", - "claimsByUri", - "myClaims", -]); +const saveClaimsFilter = createFilter("claims", ["byId", "claimsByUri"]); const persistOptions = { whitelist: ["claims"], // Order is important. Needs to be compressed last or other transforms can't // read the data transforms: [saveClaimsFilter, compressor], - debounce: 1000, + debounce: 10000, storage: localForage, }; window.cacheStore = persistStore(reduxStore, persistOptions); 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); diff --git a/ui/js/utils.js b/ui/js/utils.js index 783f85113..18e4ca21e 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -1,3 +1,5 @@ +const { remote } = require("electron"); + /** * Thin wrapper around localStorage.getItem(). Parses JSON and returns undefined if the value * is not set yet. @@ -29,3 +31,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/package.json b/ui/package.json index d7aa3535a..7e7491ed9 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,11 +68,11 @@ "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", - "webpack-target-electron-renderer": "^0.4.0", - "why-did-you-update": "0.0.8" + "webpack-target-electron-renderer": "^0.4.0" }, "lint-staged": { "gitDir": "../", diff --git a/ui/scss/_global.scss b/ui/scss/_global.scss index 74dc364c1..d30ae6dc5 100644 --- a/ui/scss/_global.scss +++ b/ui/scss/_global.scss @@ -29,6 +29,7 @@ $max-content-width: 1000px; $max-text-width: 660px; $width-page-constrained: 800px; +$width-input-text: 330px; $height-header: $spacing-vertical * 2.5; $height-button: $spacing-vertical * 1.5; 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? */ diff --git a/ui/scss/all.scss b/ui/scss/all.scss index 6b421196a..61612d7df 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -15,7 +15,6 @@ @import "component/_channel-indicator.scss"; @import "component/_notice.scss"; @import "component/_modal.scss"; -@import "component/_modal-page.scss"; @import "component/_snack-bar.scss"; @import "component/_video.scss"; @import "page/_developer.scss"; diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index 60b128afd..d8416d226 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -19,18 +19,29 @@ $padding-card-horizontal: $spacing-vertical * 2/3; .card--obscured .card__inner { filter: blur($blur-intensity-nsfw); } -.card__title-primary { +.card__title-primary, +.card__title-identity, +.card__actions, +.card__content, +.card__subtext { padding: 0 $padding-card-horizontal; +} +.card--small { + .card__title-primary, + .card__title-identity, + .card__actions, + .card__content, + .card__subtext { + padding: 0 $padding-card-horizontal / 2; + } +} +.card__title-primary { margin-top: $spacing-vertical * 2/3; } .card__title-identity { - padding: 0 $padding-card-horizontal; margin-top: $spacing-vertical * 1/3; margin-bottom: $spacing-vertical * 1/3; } -.card__actions { - padding: 0 $padding-card-horizontal; -} .card__actions { margin-top: $spacing-vertical * 2/3; } @@ -45,21 +56,18 @@ $padding-card-horizontal: $spacing-vertical * 2/3; .card__content { margin-top: $spacing-vertical * 2/3; margin-bottom: $spacing-vertical * 2/3; - padding: 0 $padding-card-horizontal; } .card__subtext { - color: #444; - margin-top: 12px; - font-size: 0.9em; - margin-top: $spacing-vertical * 2/3; - margin-bottom: $spacing-vertical * 2/3; - padding: 0 $padding-card-horizontal; + color: $color-meta-light; + font-size: 0.82em; + margin-top: $spacing-vertical * 1/3; + margin-bottom: $spacing-vertical * 1/3; } .card__subtext--allow-newlines { white-space: pre-wrap; } .card__subtext--two-lines { - height: $font-size * 0.9 * $font-line-height * 2; + height: $font-size * 0.82 * $font-line-height * 2; /*this is so one line text still has the proper height*/ } .card-overlay { position: absolute; @@ -79,6 +87,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; @@ -95,7 +106,53 @@ $card-link-scaling: 1.1; background-position: 50% 50%; } -$width-card-small: $spacing-vertical * 12; +.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 * 10; $height-card-small: $spacing-vertical * 15; .card--small { @@ -107,6 +164,10 @@ $height-card-small: $spacing-vertical * 15; height: $width-card-small * 9 / 16; } +.card--form { + width: $width-input-text + $padding-card-horizontal * 2; +} + .card__subtitle { color: $color-help; font-size: 0.85em; diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss index 8fd86efef..9dee7240b 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -1,11 +1,6 @@ @import "../global"; $width-input-border: 2px; -$width-input-text: 330px; - -.form-input-width { - width: $width-input-text -} .form-row-submit { @@ -117,6 +112,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 +161,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 diff --git a/ui/scss/component/_modal-page.scss b/ui/scss/component/_modal-page.scss deleted file mode 100644 index ada366f61..000000000 --- a/ui/scss/component/_modal-page.scss +++ /dev/null @@ -1,54 +0,0 @@ -@import "../global"; - -.modal-page { - position: fixed; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - border: 1px solid rgb(204, 204, 204); - background: rgb(255, 255, 255); - overflow: auto; -} - -.modal-page--full { - left: 0; - right: 0; - top: 0; - bottom: 0; - .modal-page__content { - max-width: 500px; - } -} - -/* -.modal-page { - position: fixed; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - border: 1px solid rgb(204, 204, 204); - background: rgb(255, 255, 255); - overflow: auto; - border-radius: 4px; - outline: none; - padding: 36px; - - top: 25px; - left: 25px; - right: 25px; - bottom: 25px; -} -*/ - -.modal-page__content { - h1, h2 { - margin-bottom: $spacing-vertical / 2; - } - h3, h4 { - margin-bottom: $spacing-vertical / 4; - } -} \ No newline at end of file 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: