,
- });
- },
- getContentAndAddress: function()
- {
- switch(this.state.viewingPage)
- {
- case 'search':
- return [this.state.pageArgs ? this.state.pageArgs : "Search", 'icon-search', ];
- case 'settings':
- return ["Settings", "icon-gear", ];
- case 'help':
- return ["Help", "icon-question", ];
- case 'report':
- return ['Report an Issue', 'icon-file', ];
- case 'downloaded':
- return ["Downloads & Purchases", "icon-folder", ];
- case 'published':
- return ["Publishes", "icon-folder", ];
- case 'start':
- return ["Start", "icon-file", ];
- case 'rewards':
- return ["Rewards", "icon-bank", ];
- case 'wallet':
- case 'send':
- case 'receive':
- return [this.state.viewingPage.charAt(0).toUpperCase() + this.state.viewingPage.slice(1), "icon-bank", ]
- case 'show':
- return [lbryuri.normalize(this.state.pageArgs), "icon-file", ];
- case 'publish':
- return ["Publish", "icon-upload", ];
- case 'developer':
- return ["Developer", "icon-file", ];
- case 'discover':
- default:
- return ["Home", "icon-home", ];
- }
- },
- render: function() {
- let [address, wunderBarIcon, mainContent] = this.getContentAndAddress();
-
- lbry.setTitle(address);
-
- if (this._storeHistoryOfNextRender) {
- this._storeHistoryOfNextRender = false;
- history.pushState({}, document.title, this.state.appUrl);
- }
-
- return (
- this._fullScreenPages.includes(this.state.viewingPage) ?
- mainContent :
-
-
-
- {mainContent}
-
-
- Your version of LBRY is out of date and may be unreliable or insecure.
-
-
- Downloading Update{this.state.downloadProgress ? `: ${this.state.downloadProgress}%` : null}
-
- {this.state.downloadComplete ? (
-
-
-
Click "Begin Upgrade" to start the upgrade process.
-
The app will close, and you will be prompted to install the latest version of LBRY.
-
After the install is complete, please reopen the app.
-
- ) : null }
-
- {this.state.downloadComplete
- ?
- : null}
-
-
-
-
-
Error
-
-
-
-
We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.
-
-
-
- );
+const env = process.env.NODE_ENV || 'development';
+const config = require(`./config/${env}`);
+const logs = [];
+const app = {
+ env: env,
+ config: config,
+ store: store,
+ logs: logs,
+ log: function(message) {
+ console.log(message);
+ logs.push(message);
}
-});
-
-
-export default App;
+}
+global.app = app;
+module.exports = app;
+//
+// import React from 'react';
+// import {Line} from 'rc-progress';
+//
+// import lbry from './lbry.js';
+// import SettingsPage from './page/settings.js';
+// import HelpPage from './page/help.js';
+// import WatchPage from './page/watch.js';
+// import ReportPage from './page/report.js';
+// import StartPage from './page/start.js';
+// import RewardsPage from './page/rewards.js';
+// import RewardPage from './page/reward.js';
+// import WalletPage from './page/wallet.js';
+// import ShowPage from './page/show.js';
+// import PublishPage from './page/publish.js';
+// import SearchPage from './page/search.js';
+// import DiscoverPage from './page/discover.js';
+// import DeveloperPage from './page/developer.js';
+// import lbryuri from './lbryuri.js';
+// import {FileListDownloaded, FileListPublished} from './page/file-list.js';
+// import Header from './component/header.js';
+// import {Modal, ExpandableModal} from './component/modal.js';
+// import {Link} from './component/link.js';
+//
+//
+// const {remote, ipcRenderer, shell} = require('electron');
+// const {download} = remote.require('electron-dl');
+// const path = require('path');
+// const app = require('electron').remote.app;
+// const fs = remote.require('fs');
+//
+//
+// var App = React.createClass({
+// _error_key_labels: {
+// connectionString: 'API connection string',
+// method: 'Method',
+// params: 'Parameters',
+// code: 'Error code',
+// message: 'Error message',
+// data: 'Error data',
+// },
+// _fullScreenPages: ['watch'],
+// _storeHistoryOfNextRender: false,
+//
+// _upgradeDownloadItem: null,
+// _isMounted: false,
+// _version: null,
+// getUpdateUrl: function() {
+// switch (process.platform) {
+// case 'darwin':
+// return 'https://lbry.io/get/lbry.dmg';
+// case 'linux':
+// return 'https://lbry.io/get/lbry.deb';
+// case 'win32':
+// return 'https://lbry.io/get/lbry.exe';
+// default:
+// throw 'Unknown platform';
+// }
+// },
+// // Hard code the filenames as a temporary workaround, because
+// // electron-dl throws errors when you try to get the filename
+// getUpgradeFilename: function() {
+// switch (process.platform) {
+// case 'darwin':
+// return `LBRY-${this._version}.dmg`;
+// case 'linux':
+// return `LBRY_${this._version}_amd64.deb`;
+// case 'windows':
+// return `LBRY.Setup.${this._version}.exe`;
+// default:
+// throw 'Unknown platform';
+// }
+// },
+// getViewingPageAndArgs: function(address) {
+// // For now, routes are in format ?page or ?page=args
+// let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/);
+// return {
+// viewingPage: viewingPage,
+// pageArgs: pageArgs === undefined ? null : decodeURIComponent(pageArgs)
+// };
+// },
+// getInitialState: function() {
+// return Object.assign(this.getViewingPageAndArgs(window.location.search), {
+// viewingPage: 'discover',
+// appUrl: null,
+// errorInfo: null,
+// modal: null,
+// downloadProgress: null,
+// downloadComplete: false,
+// });
+// },
+// componentWillMount: function() {
+// window.addEventListener("popstate", this.onHistoryPop);
+//
+// document.addEventListener('unhandledError', (event) => {
+// this.alertError(event.detail);
+// });
+//
+// //open links in external browser and skip full redraw on changing page
+// document.addEventListener('click', (event) => {
+// var target = event.target;
+// while (target && target !== document) {
+// if (target.matches('a[href^="http"]')) {
+// event.preventDefault();
+// shell.openExternal(target.href);
+// return;
+// }
+// if (target.matches('a[href^="?"]')) {
+// event.preventDefault();
+// if (this._isMounted) {
+// let appUrl = target.getAttribute('href');
+// this._storeHistoryOfNextRender = true;
+// this.setState(Object.assign({}, this.getViewingPageAndArgs(appUrl), { appUrl: appUrl }));
+// document.body.scrollTop = 0;
+// }
+// }
+// target = target.parentNode;
+// }
+// });
+//
+// if (!sessionStorage.getItem('upgradeSkipped')) {
+// lbry.getVersionInfo().then(({remoteVersion, upgradeAvailable}) => {
+// if (upgradeAvailable) {
+// this._version = remoteVersion;
+// this.setState({
+// modal: 'upgrade',
+// });
+// }
+// });
+// }
+// },
+// closeModal: function() {
+// this.setState({
+// modal: null,
+// });
+// },
+// componentDidMount: function() {
+// this._isMounted = true;
+// },
+// componentWillUnmount: function() {
+// this._isMounted = false;
+// window.removeEventListener("popstate", this.onHistoryPop);
+// },
+// onHistoryPop: function() {
+// this.setState(this.getViewingPageAndArgs(location.search));
+// },
+// onSearch: function(term) {
+// this._storeHistoryOfNextRender = true;
+// const isShow = term.startsWith('lbry://');
+// this.setState({
+// viewingPage: isShow ? "show" : "search",
+// appUrl: (isShow ? "?show=" : "?search=") + encodeURIComponent(term),
+// pageArgs: term
+// });
+// },
+// onSubmit: function(uri) {
+// this._storeHistoryOfNextRender = true;
+// this.setState({
+// address: uri,
+// appUrl: "?show=" + encodeURIComponent(uri),
+// viewingPage: "show",
+// pageArgs: uri
+// })
+// },
+// handleUpgradeClicked: function() {
+// // 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);
+//
+// let options = {
+// onProgress: (p) => this.setState({downloadProgress: Math.round(p * 100)}),
+// directory: dir,
+// };
+// download(remote.getCurrentWindow(), this.getUpdateUrl(), options)
+// .then(downloadItem => {
+// /**
+// * TODO: get the download path directly from the download object. It should just be
+// * downloadItem.getSavePath(), but the copy on the main process is being garbage collected
+// * too soon.
+// */
+//
+// this._upgradeDownloadItem = downloadItem;
+// this._upgradeDownloadPath = path.join(dir, this.getUpgradeFilename());
+// this.setState({
+// downloadComplete: true
+// });
+// });
+// this.setState({modal: 'downloading'});
+// },
+// handleStartUpgradeClicked: function() {
+// ipcRenderer.send('upgrade', this._upgradeDownloadPath);
+// },
+// cancelUpgrade: function() {
+// if (this._upgradeDownloadItem) {
+// /*
+// * Right now the remote reference to the download item gets garbage collected as soon as the
+// * the download is over (maybe even earlier), so trying to cancel a finished download may
+// * throw an error.
+// */
+// try {
+// this._upgradeDownloadItem.cancel();
+// } catch (err) {
+// // Do nothing
+// }
+// }
+// this.setState({
+// downloadProgress: null,
+// downloadComplete: false,
+// modal: null,
+// });
+// },
+// handleSkipClicked: function() {
+// sessionStorage.setItem('upgradeSkipped', true);
+// this.setState({
+// modal: null,
+// });
+// },
+// alertError: function(error) {
+// var errorInfoList = [];
+// for (let key of Object.keys(error)) {
+// let val = typeof error[key] == 'string' ? error[key] : JSON.stringify(error[key]);
+// let label = this._error_key_labels[key];
+// errorInfoList.push(
,
+// });
+// },
+// getContentAndAddress: function()
+// {
+// switch(this.state.viewingPage)
+// {
+// case 'search':
+// return [this.state.pageArgs ? this.state.pageArgs : "Search", 'icon-search', ];
+// case 'settings':
+// return ["Settings", "icon-gear", ];
+// case 'help':
+// return ["Help", "icon-question", ];
+// case 'report':
+// return ['Report an Issue', 'icon-file', ];
+// case 'downloaded':
+// return ["Downloads & Purchases", "icon-folder", ];
+// case 'published':
+// return ["Publishes", "icon-folder", ];
+// case 'start':
+// return ["Start", "icon-file", ];
+// case 'rewards':
+// return ["Rewards", "icon-bank", ];
+// case 'wallet':
+// case 'send':
+// case 'receive':
+// return [this.state.viewingPage.charAt(0).toUpperCase() + this.state.viewingPage.slice(1), "icon-bank", ]
+// case 'show':
+// return [lbryuri.normalize(this.state.pageArgs), "icon-file", ];
+// case 'publish':
+// return ["Publish", "icon-upload", ];
+// case 'developer':
+// return ["Developer", "icon-file", ];
+// case 'discover':
+// default:
+// return ["Home", "icon-home", ];
+// }
+// },
+// render: function() {
+// let [address, wunderBarIcon, mainContent] = this.getContentAndAddress();
+//
+// lbry.setTitle(address);
+//
+// if (this._storeHistoryOfNextRender) {
+// this._storeHistoryOfNextRender = false;
+// history.pushState({}, document.title, this.state.appUrl);
+// }
+//
+// return (
+// this._fullScreenPages.includes(this.state.viewingPage) ?
+// mainContent :
+//
+//
+//
+// {mainContent}
+//
+//
+// Your version of LBRY is out of date and may be unreliable or insecure.
+//
+//
+// Downloading Update{this.state.downloadProgress ? `: ${this.state.downloadProgress}%` : null}
+//
+// {this.state.downloadComplete ? (
+//
+//
+//
Click "Begin Upgrade" to start the upgrade process.
+//
The app will close, and you will be prompted to install the latest version of LBRY.
+//
After the install is complete, please reopen the app.
Other LBRY users may send credits to you by entering this address on the "Send" page.
-
You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.
Other LBRY users may send credits to you by entering this address on the "Send" page.
+
You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.
Other LBRY users may send credits to you by entering this address on the "Send" page.
-
You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.
Other LBRY users may send credits to you by entering this address on the "Send" page.
+
You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.
- {this.state.fileInfo !== null || this.state.fileInfo.isMine
+ {fileInfo !== null || fileInfo.isMine
? linkBlock
: null}
{ showMenu ?
-
-
+ openInFolder(fileInfo)} label={openInFolderMessage} />
+ openModal('confirmRemove')} label="Remove..." />
: '' }
-
- Are you sure you'd like to buy {title} for credits?
+
+ Are you sure you'd like to buy {title} for credits?
-
+
You don't have enough LBRY credits to pay for this stream.
-
+
LBRY was unable to download the stream {uri}.
-
+ deleteFile(uri, fileInfo, deleteChecked)}
+ onAborted={closeModal}>
Are you sure you'd like to remove {title} from LBRY?
+// {this.state.fileInfo !== null || this.state.fileInfo.isMine
+// ? linkBlock
+// : null}
+// { showMenu ?
+//
+//
+//
+// : '' }
+//
+// Are you sure you'd like to buy {title} for credits?
+//
+//
+// You don't have enough LBRY credits to pay for this stream.
+//
+//
+// LBRY was unable to download the stream {uri}.
+//
+//
+//
Are you sure you'd like to remove {title} from LBRY?
{metadata &&
diff --git a/ui/js/component/fileTileStream/view.jsx b/ui/js/component/fileTileStream/view.jsx
index 2130df284..7d2b0b360 100644
--- a/ui/js/component/fileTileStream/view.jsx
+++ b/ui/js/component/fileTileStream/view.jsx
@@ -5,7 +5,7 @@ import Link from 'component/link';
import FileActions from 'component/fileActions';
import {Thumbnail, TruncatedText,} from 'component/common.js';
import FilePrice from 'component/filePrice'
-import UriIndicator from 'component/channel-indicator.js';
+import UriIndicator from 'component/uriIndicator';
/*should be merged into FileTile once FileTile is refactored to take a single id*/
class FileTileStream extends React.Component {
@@ -71,6 +71,8 @@ class FileTileStream extends React.Component {
const title = isConfirmed ? metadata.title : uri;
const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw;
+ console.debug(this.props)
+
return (
This will open a browser window where you can authorize GitHub to link your account to LBRY. This will record your email (no spam) and star the LBRY repo.
-//
Once you're finished, you may confirm you've linked the account to receive your reward.
-// {this.state.fileInfo !== null || this.state.fileInfo.isMine
-// ? linkBlock
-// : null}
-// { showMenu ?
-//
-//
-//
-// : '' }
-//
-// Are you sure you'd like to buy {title} for credits?
-//
-//
-// You don't have enough LBRY credits to pay for this stream.
-//
-//
-// LBRY was unable to download the stream {uri}.
-//
-//
-//
Are you sure you'd like to remove {title} from LBRY?
-
-//
-//
-//
-// );
-// }
-// });
-
class FileActions extends React.Component {
constructor(props) {
super(props)
--
2.43.4
From d87fc4b09a5e39aa5fd9aca21de3a1c1763f7e82 Mon Sep 17 00:00:00 2001
From: 6ea86b96 <6ea86b96@gmail.com>
Date: Tue, 2 May 2017 15:21:00 +0700
Subject: [PATCH 43/56] Start on publish page
---
ui/js/page/publish/index.js | 17 +++++++++++++++++
ui/js/page/{publish.js => publish/view.jsx} | 10 +++++-----
2 files changed, 22 insertions(+), 5 deletions(-)
create mode 100644 ui/js/page/publish/index.js
rename ui/js/page/{publish.js => publish/view.jsx} (99%)
diff --git a/ui/js/page/publish/index.js b/ui/js/page/publish/index.js
new file mode 100644
index 000000000..29347b6ed
--- /dev/null
+++ b/ui/js/page/publish/index.js
@@ -0,0 +1,17 @@
+import React from 'react'
+import {
+ connect,
+} from 'react-redux'
+import {
+ doNavigate,
+} from 'actions/app'
+import PublishPage from './view'
+
+const select = (state) => ({
+})
+
+const perform = (dispatch) => ({
+ navigate: (path) => dispatch(doNavigate(path)),
+})
+
+export default connect(select, perform)(PublishPage)
diff --git a/ui/js/page/publish.js b/ui/js/page/publish/view.jsx
similarity index 99%
rename from ui/js/page/publish.js
rename to ui/js/page/publish/view.jsx
index d443d8737..23d7b8661 100644
--- a/ui/js/page/publish.js
+++ b/ui/js/page/publish/view.jsx
@@ -1,9 +1,9 @@
import React from 'react';
-import lbry from '../lbry.js';
-import FormField from '../component/form.js';
+import lbry from 'lbry';
+import {FormField, FormRow} from 'component/form.js';
import Link from 'component/link';
-import rewards from '../rewards.js';
-import Modal from '../component/modal.js';
+import rewards from 'rewards';
+import Modal from 'component/modal';
var PublishPage = React.createClass({
_requiredFields: ['meta_title', 'name', 'bid', 'tos_agree'],
@@ -147,7 +147,7 @@ var PublishPage = React.createClass({
});
},
handlePublishStartedConfirmed: function() {
- window.location.href = "?published";
+ this.props.navigate('published')
},
handlePublishError: function(error) {
this.setState({
--
2.43.4
From 44ad47c05ff50da439ce2efb3029625cd47ad812 Mon Sep 17 00:00:00 2001
From: 6ea86b96 <6ea86b96@gmail.com>
Date: Tue, 2 May 2017 15:39:06 +0700
Subject: [PATCH 44/56] Fix publish page link in router
---
ui/js/component/router/view.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx
index 8f9a258f8..9bc906d19 100644
--- a/ui/js/component/router/view.jsx
+++ b/ui/js/component/router/view.jsx
@@ -5,7 +5,7 @@ import ReportPage from 'page/report.js';
import StartPage from 'page/start.js';
import WalletPage from 'page/wallet';
import ShowPage from 'page/showPage';
-import PublishPage from 'page/publish.js';
+import PublishPage from 'page/publish';
import DiscoverPage from 'page/discover';
import SplashScreen from 'component/splash.js';
import DeveloperPage from 'page/developer.js';
--
2.43.4
From e1e172a11e560d1af582e8e5e661e4428051d377 Mon Sep 17 00:00:00 2001
From: 6ea86b96 <6ea86b96@gmail.com>
Date: Tue, 2 May 2017 20:58:35 +0700
Subject: [PATCH 45/56] Switch to https://github.com/Selz/plyr video player
(recommended by @fillerino).
---
ui/dist/index.html | 2 +-
ui/js/actions/content.js | 7 +++++
ui/js/component/video/view.jsx | 52 ++++++++++++++++++++++++++++++++++
ui/package.json | 4 +--
4 files changed, 62 insertions(+), 3 deletions(-)
diff --git a/ui/dist/index.html b/ui/dist/index.html
index b4239bc9a..8fbf0b5b6 100644
--- a/ui/dist/index.html
+++ b/ui/dist/index.html
@@ -7,7 +7,7 @@
-
+
diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js
index 91a227665..69886570b 100644
--- a/ui/js/actions/content.js
+++ b/ui/js/actions/content.js
@@ -268,8 +268,15 @@ export function doWatchVideo() {
const alreadyDownloading = !!downloadingByUri[uri]
const { cost } = costInfo
+ // BUG if you delete a file from the file system system you're going to be
+ // asked to pay for it again. We need to check if the file is in the blobs
+ // here and then dispatch doLoadVideo() which will reconstruct it again from
+ // the blobs. Or perhaps there's another way to see if a file was already
+ // purchased?
+
// we already fully downloaded the file
if (fileInfo && fileInfo.completed) {
+ dispatch(doLoadVideo())
return Promise.resolve()
}
diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx
index 1b559ae30..a1093cf40 100644
--- a/ui/js/component/video/view.jsx
+++ b/ui/js/component/video/view.jsx
@@ -7,6 +7,58 @@ import FilePrice from 'component/filePrice'
import Link from 'component/link';
import Modal from 'component/modal';
+class WatchLink extends React.Component {
+ confirmPurchaseClick() {
+ this.props.closeModal()
+ this.props.startPlaying()
+ this.props.loadVideo()
+ }
+
+ render() {
+ const {
+ button,
+ label,
+ className,
+ onWatchClick,
+ metadata,
+ metadata: {
+ title,
+ },
+ uri,
+ modal,
+ closeModal,
+ isLoading,
+ costInfo,
+ fileInfo,
+ } = this.props
+
+ return (
+
+ {modal}
+
+ You don't have enough LBRY credits to pay for this stream.
+
+
+ Are you sure you'd like to buy {this.props.metadata.title} for credits?
+
+
+ Sorry, your download timed out :(
+
+
,
-// });
-// },
-// getContentAndAddress: function()
-// {
-// switch(this.state.viewingPage)
-// {
-// case 'search':
-// return [this.state.pageArgs ? this.state.pageArgs : "Search", 'icon-search', ];
-// case 'settings':
-// return ["Settings", "icon-gear", ];
-// case 'help':
-// return ["Help", "icon-question", ];
-// case 'report':
-// return ['Report an Issue', 'icon-file', ];
-// case 'downloaded':
-// return ["Downloads & Purchases", "icon-folder", ];
-// case 'published':
-// return ["Publishes", "icon-folder", ];
-// case 'start':
-// return ["Start", "icon-file", ];
-// case 'rewards':
-// return ["Rewards", "icon-bank", ];
-// case 'wallet':
-// case 'send':
-// case 'receive':
-// return [this.state.viewingPage.charAt(0).toUpperCase() + this.state.viewingPage.slice(1), "icon-bank", ]
-// case 'show':
-// return [lbryuri.normalize(this.state.pageArgs), "icon-file", ];
-// case 'publish':
-// return ["Publish", "icon-upload", ];
-// case 'developer':
-// return ["Developer", "icon-file", ];
-// case 'discover':
-// default:
-// return ["Home", "icon-home", ];
-// }
-// },
-// render: function() {
-// let [address, wunderBarIcon, mainContent] = this.getContentAndAddress();
-//
-// lbry.setTitle(address);
-//
-// if (this._storeHistoryOfNextRender) {
-// this._storeHistoryOfNextRender = false;
-// history.pushState({}, document.title, this.state.appUrl);
-// }
-//
-// return (
-// this._fullScreenPages.includes(this.state.viewingPage) ?
-// mainContent :
-//
-//
-//
-// {mainContent}
-//
-//
-// Your version of LBRY is out of date and may be unreliable or insecure.
-//
-//
-// Downloading Update{this.state.downloadProgress ? `: ${this.state.downloadProgress}%` : null}
-//
-// {this.state.downloadComplete ? (
-//
-//
-//
Click "Begin Upgrade" to start the upgrade process.
-//
The app will close, and you will be prompted to install the latest version of LBRY.
-//
After the install is complete, please reopen the app.
+//
+//
+// )
+// }
\ No newline at end of file
diff --git a/ui/js/page/help/view.jsx b/ui/js/page/help/view.jsx
index b5fe61787..095435e20 100644
--- a/ui/js/page/help/view.jsx
+++ b/ui/js/page/help/view.jsx
@@ -2,6 +2,7 @@
import React from 'react';
import lbry from 'lbry.js';
import Link from 'component/link';
+import NavSettings from 'component/navSettings';
import {version as uiVersion} from 'json!../../../package.json';
var HelpPage = React.createClass({
@@ -49,58 +50,71 @@ var HelpPage = React.createClass({
return (
+
-
Read the FAQ
-
Our FAQ answers many common questions.
-
+
+
Read the FAQ
+
+
+
Our FAQ answers many common questions.
+
+
-
Get Live Help
-
- Live help is available most hours in the #help channel of our Slack chat room.
-
-
-
-
+
+
Get Live Help
+
+
+
+ Live help is available most hours in the #help channel of our Slack chat room.
+