diff --git a/flow-typed/Settings.js b/flow-typed/Settings.js
new file mode 100644
index 000000000..89fc59118
--- /dev/null
+++ b/flow-typed/Settings.js
@@ -0,0 +1,8 @@
+declare type CommentServerDetails = {
+ name: string,
+ url: string,
+}
+
+declare type WalletServerDetails = {
+
+};
diff --git a/static/app-strings.json b/static/app-strings.json
index 5da607612..9125daa73 100644
--- a/static/app-strings.json
+++ b/static/app-strings.json
@@ -2269,5 +2269,11 @@
"Automatic Hosting lets you delegate some amount of storage for the network to automatically download and host.": "Automatic Hosting lets you delegate some amount of storage for the network to automatically download and host.",
"Help improve the P2P data network (and make LBRY happy) by hosting data.": "Help improve the P2P data network (and make LBRY happy) by hosting data.",
"Limit Hosting of Content History": "Limit Hosting of Content History",
+ "Remove custom comment server": "Remove custom comment server",
+ "Use Https": "Use Https",
+ "Server URL": "Server URL",
+ "Use https": "Use https",
+ "Custom Servers": "Custom Servers",
+ "Add A Server": "Add A Server",
"--end--": "--end--"
}
diff --git a/ui/component/common/item-panel-input-row.jsx b/ui/component/common/item-panel-input-row.jsx
new file mode 100644
index 000000000..e561669e0
--- /dev/null
+++ b/ui/component/common/item-panel-input-row.jsx
@@ -0,0 +1,104 @@
+// @flow
+import React, { useState, useEffect } from 'react';
+import Button from 'component/button';
+import { Form, FormField } from 'component/common/form';
+
+type Props = {
+ update: (CommentServerDetails) => void,
+ onCancel: (boolean) => void,
+};
+
+const VALID_IPADDRESS_REGEX = new RegExp(
+ '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\.)){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'
+);
+const VALID_HOSTNAME_REGEX = new RegExp(
+ '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])(\\.))+([A-Za-z]|[A-Za-z][A-Za-z]*[A-Za-z])$'
+);
+
+const VALID_ENDPOINT_REGEX = new RegExp('^((\\/)([a-zA-Z0-9]+))+$');
+
+const isValidServerString = (serverString) => {
+ const si = serverString.indexOf('/');
+ const pi = serverString.indexOf(':');
+ const path = si === -1 ? '' : serverString.slice(si);
+ console.log('path', path);
+ const hostMaybePort = si === -1 ? serverString : serverString.slice(0, si);
+ const host = pi === -1 ? hostMaybePort : hostMaybePort.slice(0, pi);
+ const port = pi === -1 ? '' : hostMaybePort.slice(pi + 1);
+ console.log('port', port);
+ const portInt = parseInt(port);
+
+ return (
+ (host === 'localhost' || VALID_IPADDRESS_REGEX.test(host) || VALID_HOSTNAME_REGEX.test(host)) &&
+ (!path || VALID_ENDPOINT_REGEX.test(path)) &&
+ // eslint-disable-next-line
+ (pi === -1 || (port && typeof portInt === 'number' && portInt === portInt))
+ ); // NaN !== NaN
+};
+
+function ServerInputRow(props: Props) {
+ const { update, onCancel } = props;
+ const [nameString, setNameString] = useState('');
+ const [hostString, setHostString] = useState('');
+ const [useHttps, setUseHttps] = useState(true);
+
+ const getHostString = () => {
+ return `${useHttps ? 'https://' : 'http://'}${hostString}`;
+ };
+
+ const [validServerString, setValidServerString] = useState(false);
+
+ useEffect(() => {
+ setValidServerString(isValidServerString(hostString));
+ }, [hostString, validServerString, setValidServerString]);
+
+ function onSubmit() {
+ const updateValue = { url: getHostString(), name: nameString };
+ update(updateValue);
+ setHostString('');
+ setNameString('');
+ }
+
+ return (
+
+ );
+}
+
+export default ServerInputRow;
diff --git a/ui/component/common/item-panel.jsx b/ui/component/common/item-panel.jsx
new file mode 100644
index 000000000..b95caf60a
--- /dev/null
+++ b/ui/component/common/item-panel.jsx
@@ -0,0 +1,43 @@
+// @flow
+import React from 'react';
+import * as ICONS from 'constants/icons';
+import Button from 'component/button';
+import classnames from 'classnames';
+
+type Props = {
+ onClick: (CommentServerDetails) => void,
+ onRemove?: (CommentServerDetails) => void,
+ active: boolean,
+ serverDetails: CommentServerDetails,
+};
+
+/*
+ [ https://myserver.com x ]
+ [ https://myserver.com x (selected)]
+
+ [ https://myserver.com:50001 x (selected)]
+ */
+
+const ItemPanel = (props: Props) => {
+ const { onClick, active, serverDetails, onRemove } = props;
+
+ return (
+ onClick(serverDetails)} className={classnames('itemPanel', { 'itemPanel--active': active })}>
+
+
{`${serverDetails.name}`}
+
{`${serverDetails.url}`}
+
+ {onRemove && (
+
+ );
+};
+
+export default ItemPanel;
diff --git a/ui/component/settingCommentsServer/index.js b/ui/component/settingCommentsServer/index.js
index deee05b7f..eaac73ed3 100644
--- a/ui/component/settingCommentsServer/index.js
+++ b/ui/component/settingCommentsServer/index.js
@@ -7,11 +7,13 @@ import SettingCommentsServer from './view';
const select = (state) => ({
customServerEnabled: makeSelectClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVER_ENABLED)(state),
customServerUrl: makeSelectClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVER_URL)(state),
+ customCommentServers: makeSelectClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVERS)(state),
});
const perform = (dispatch) => ({
setCustomServerEnabled: (val) => dispatch(doSetClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVER_ENABLED, val, true)),
setCustomServerUrl: (url) => dispatch(doSetClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVER_URL, url, true)),
+ setCustomServers: (servers) => dispatch(doSetClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVERS, servers, true)),
});
export default connect(select, perform)(SettingCommentsServer);
diff --git a/ui/component/settingCommentsServer/view.jsx b/ui/component/settingCommentsServer/view.jsx
index 3cbde7e8c..14ce4ff27 100644
--- a/ui/component/settingCommentsServer/view.jsx
+++ b/ui/component/settingCommentsServer/view.jsx
@@ -1,70 +1,101 @@
// @flow
-import { COMMENT_SERVER_NAME } from 'config';
+import { COMMENT_SERVER_API } from 'config'; // COMMENT_SERVER_NAME,
import React from 'react';
import Comments from 'comments';
-import { FormField } from 'component/common/form';
-
-const DEBOUNCE_TEXT_INPUT_MS = 500;
+import ItemPanel from 'component/common/item-panel';
+import ItemInputRow from 'component/common/item-panel-input-row';
+import Button from 'component/button';
type Props = {
customServerEnabled: boolean,
customServerUrl: string,
setCustomServerEnabled: (boolean) => void,
setCustomServerUrl: (string) => void,
+ setCustomServers: (Array) => void,
+ customCommentServers: Array,
};
-
+const defaultServer = { name: 'Default', url: COMMENT_SERVER_API };
function SettingCommentsServer(props: Props) {
- const { customServerEnabled, customServerUrl, setCustomServerEnabled, setCustomServerUrl } = props;
- const [url, setUrl] = React.useState(customServerUrl);
+ const {
+ customServerEnabled,
+ customServerUrl,
+ setCustomServerEnabled,
+ setCustomServerUrl,
+ customCommentServers,
+ setCustomServers,
+ } = props;
+ const [addServer, setAddServer] = React.useState(false);
+ const customServersString = JSON.stringify(customCommentServers);
+
+ // "migrate" to make sure any currently set custom server is in saved list
React.useEffect(() => {
- const timer = setTimeout(() => {
- Comments.setServerUrl(customServerEnabled ? url : undefined);
- if (url !== customServerUrl) {
- setCustomServerUrl(url);
- }
- }, DEBOUNCE_TEXT_INPUT_MS);
+ // const servers = JSON.parse(customServersString);
+ // if customServerUrl is not in servers, make sure it is.
+ }, [customServerUrl, customServersString, setCustomServers]);
- return () => clearTimeout(timer);
- }, [url, customServerUrl, customServerEnabled, setCustomServerUrl]);
+ // React.useEffect(() => {
+ // const timer = setTimeout(() => {
+ // Comments.setServerUrl(customServerEnabled ? url : undefined);
+ // if (url !== customServerUrl) {
+ // setCustomServerUrl(url);
+ // }
+ // }, DEBOUNCE_TEXT_INPUT_MS);
+ //
+ // return () => clearTimeout(timer);
+ // }, [url, customServerUrl, customServerEnabled, setCustomServerUrl]);
+
+ const handleSelectServer = (serverItem: CommentServerDetails) => {
+ if (serverItem.url !== COMMENT_SERVER_API) {
+ alert(`set ${serverItem.url}`);
+ Comments.setServerUrl(serverItem.url);
+ setCustomServerUrl(serverItem.url);
+ setCustomServerEnabled(true);
+ } else {
+ alert('reset');
+ Comments.setServerUrl(undefined);
+ setCustomServerEnabled(false);
+ }
+ };
+
+ const handleAddServer = (serverItem: CommentServerDetails) => {
+ const newCustomServers = customCommentServers.slice();
+ newCustomServers.push(serverItem);
+ setCustomServers(newCustomServers);
+ handleSelectServer(serverItem);
+ };
+
+ const handleRemoveServer = (serverItem) => {
+ handleSelectServer(defaultServer);
+ const newCustomServers = customCommentServers.slice().filter((server) => {
+ return server.url !== serverItem.url;
+ });
+ setCustomServers(newCustomServers);
+ };
return (
-
- {
- if (e.target.checked) {
- setCustomServerEnabled(false);
- }
- }}
- />
- {
- if (e.target.checked) {
- setCustomServerEnabled(true);
- }
- }}
- />
-
- {customServerEnabled && (
-
-
setUrl(e.target.value)}
- />
+
+
+ {!!customCommentServers.length && }
+ {customCommentServers.map((server) => (
+
+ ))}
+
+
+ {!addServer && (
+
+
)}
-
+ {addServer &&
}
+
);
}
diff --git a/ui/constants/settings.js b/ui/constants/settings.js
index 72363de04..86d0b7e20 100644
--- a/ui/constants/settings.js
+++ b/ui/constants/settings.js
@@ -41,6 +41,7 @@ export const VIDEO_THEATER_MODE = 'video_theater_mode';
export const VIDEO_PLAYBACK_RATE = 'video_playback_rate';
export const CUSTOM_COMMENTS_SERVER_ENABLED = 'custom_comments_server_enabled';
export const CUSTOM_COMMENTS_SERVER_URL = 'custom_comments_server_url';
+export const CUSTOM_COMMENTS_SERVERS = 'custom_comments_servers';
export const CUSTOM_SHARE_URL_ENABLED = 'custom_share_url_enabled';
export const CUSTOM_SHARE_URL = 'custom_share_url';
export const ENABLE_PRERELEASE_UPDATES = 'enable_prerelease_updates';
diff --git a/ui/redux/reducers/settings.js b/ui/redux/reducers/settings.js
index 4e013e35e..bbdd22c09 100644
--- a/ui/redux/reducers/settings.js
+++ b/ui/redux/reducers/settings.js
@@ -45,6 +45,7 @@ const defaultState = {
[SETTINGS.DESKTOP_WINDOW_ZOOM]: 1,
[SETTINGS.CUSTOM_COMMENTS_SERVER_ENABLED]: false,
[SETTINGS.CUSTOM_COMMENTS_SERVER_URL]: '',
+ [SETTINGS.CUSTOM_COMMENTS_SERVERS]: [],
[SETTINGS.CUSTOM_SHARE_URL_ENABLED]: false,
[SETTINGS.CUSTOM_SHARE_URL]: '',
diff --git a/ui/scss/all.scss b/ui/scss/all.scss
index 8ecba7ad6..c68b9ab87 100644
--- a/ui/scss/all.scss
+++ b/ui/scss/all.scss
@@ -68,3 +68,4 @@
@import 'component/wallet-tip-send';
@import 'component/swipe-list';
@import 'component/utils';
+@import 'component/item-panel';
diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss
index 2d7a88c85..cddd06630 100644
--- a/ui/scss/component/_form-field.scss
+++ b/ui/scss/component/_form-field.scss
@@ -293,7 +293,9 @@ input[type='number'] {
fieldset-group {
+ fieldset-group {
- margin-top: var(--spacing-s);
+ &:not(.fieldset-group--row) {
+ margin-top: var(--spacing-s);
+ }
}
&.fieldset-group--smushed {
@@ -339,6 +341,9 @@ fieldset-group {
align-items: flex-end;
justify-content: center;
}
+
+ &:not(.fieldset-group--row) {
+ }
}
// This is a special case where the prefix appears "inside" the input
diff --git a/ui/scss/component/_item-panel.scss b/ui/scss/component/_item-panel.scss
new file mode 100644
index 000000000..51c2bdace
--- /dev/null
+++ b/ui/scss/component/_item-panel.scss
@@ -0,0 +1,42 @@
+.itemPanel {
+ padding: var(--spacing-m);
+ margin-bottom: var(--spacing-m);
+ width: 100%;
+ background-color: var(--color-card-background);
+ border-radius: var(--card-radius);
+ overflow: hidden;
+ border: 1px solid var(--color-border);
+ display: flex;
+ justify-content: space-between;
+ .button--close {
+ position: unset;
+ }
+ &:last-child {
+ margin-bottom: 0;
+ }
+}
+
+.itemPanel__details {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ @media (max-width: $breakpoint-small) {
+ flex-direction: column;
+ }
+}
+.itemPanel__name {
+ min-width: 100px;
+ width: 100px;
+}
+.itemPanel__url {
+ text-overflow: ellipsis;
+}
+
+.itemPanel--active {
+ color: var(--color-button-toggle-text);
+ background-color: var(--color-button-toggle-bg);
+}
+
+.itemPanel--input {
+ padding: 0 0 var(--spacing-s) 0;
+}
diff --git a/ui/scss/component/section.scss b/ui/scss/component/section.scss
index d66a716d0..56c4d12f9 100644
--- a/ui/scss/component/section.scss
+++ b/ui/scss/component/section.scss
@@ -250,7 +250,9 @@
}
fieldset-group {
- margin-top: var(--spacing-m);
+ &:not(.fieldset-group--row) {
+ margin-top: var(--spacing-m);
+ }
}
.tags__input-wrapper {