diff --git a/ElectronClient/app/gui/ExtensionBadge.jsx b/ElectronClient/app/gui/ExtensionBadge.jsx
new file mode 100644
index 000000000..326086257
--- /dev/null
+++ b/ElectronClient/app/gui/ExtensionBadge.jsx
@@ -0,0 +1,45 @@
+const React = require('react');
+const { bridge } = require('electron').remote.require('./bridge');
+const styleSelector = require('./style/ExtensionBadge');
+const { _ } = require('lib/locale.js');
+
+function platformAssets(type) {
+ if (type === 'firefox') {
+ return {
+ logoImage: bridge().buildDir() + '/images/firefox-logo.svg',
+ locationLabel: _('Firefox Extension'),
+ };
+ }
+
+ if (type === 'chrome') {
+ return {
+ logoImage: bridge().buildDir() + '/images/chrome-logo.svg',
+ locationLabel: _('Chrome Web Store'),
+ };
+ }
+
+ throw new Error('Invalid type:' + type);
+}
+
+function ExtensionBadge(props) {
+ const style = styleSelector(null, props);
+ const assets = platformAssets(props.type);
+
+ const onClick = () => {
+ bridge().openExternal(props.url);
+ };
+
+ const rootStyle = props.style ? Object.assign({}, style.root, props.style) : style.root;
+
+ return (
+
+
+
+
{_('Get it now:')}
+
{assets.locationLabel}
+
+
+ );
+}
+
+module.exports = ExtensionBadge;
diff --git a/ElectronClient/app/gui/MainScreen.jsx b/ElectronClient/app/gui/MainScreen.jsx
index 6222918e5..c59013a5e 100644
--- a/ElectronClient/app/gui/MainScreen.jsx
+++ b/ElectronClient/app/gui/MainScreen.jsx
@@ -485,7 +485,10 @@ class MainScreenComponent extends React.Component {
const onViewMasterKeysClick = () => {
this.props.dispatch({
type: 'NAV_GO',
- routeName: 'EncryptionConfig',
+ routeName: 'Config',
+ props: {
+ defaultSection: 'encryption',
+ },
});
};
diff --git a/ElectronClient/app/gui/Navigator.jsx b/ElectronClient/app/gui/Navigator.jsx
index b69883376..abc3b4681 100644
--- a/ElectronClient/app/gui/Navigator.jsx
+++ b/ElectronClient/app/gui/Navigator.jsx
@@ -17,10 +17,7 @@ class NavigatorComponent extends Component {
updateWindowTitle(title) {
try {
- if (bridge().window())
- bridge()
- .window()
- .setTitle(title);
+ if (bridge().window()) bridge().window().setTitle(title);
} catch (error) {
console.warn('updateWindowTitle', error);
}
diff --git a/ElectronClient/app/gui/NoteText.jsx b/ElectronClient/app/gui/NoteText.jsx
index 222752669..06e0ca026 100644
--- a/ElectronClient/app/gui/NoteText.jsx
+++ b/ElectronClient/app/gui/NoteText.jsx
@@ -415,10 +415,10 @@ class NoteTextComponent extends React.Component {
}
componentDidUpdate() {
- if (Setting.value('env') === 'dev' && this.webviewRef()) {
- this.webviewRef().openDevTools();
- return;
- }
+ // if (Setting.value('env') === 'dev' && this.webviewRef()) {
+ // this.webviewRef().openDevTools();
+ // return;
+ // }
if (this.webviewRef() && this.props.noteDevToolsVisible !== this.webviewRef().isDevToolsOpened()) {
if (this.props.noteDevToolsVisible) {
diff --git a/ElectronClient/app/gui/Root.jsx b/ElectronClient/app/gui/Root.jsx
index f6d9a340d..bc01c024c 100644
--- a/ElectronClient/app/gui/Root.jsx
+++ b/ElectronClient/app/gui/Root.jsx
@@ -11,8 +11,6 @@ const { DropboxLoginScreen } = require('./DropboxLoginScreen.min.js');
const { StatusScreen } = require('./StatusScreen.min.js');
const { ImportScreen } = require('./ImportScreen.min.js');
const { ConfigScreen } = require('./ConfigScreen.min.js');
-const { EncryptionConfigScreen } = require('./EncryptionConfigScreen.min.js');
-const { ClipperConfigScreen } = require('./ClipperConfigScreen.min.js');
const { Navigator } = require('./Navigator.min.js');
const WelcomeUtils = require('lib/WelcomeUtils');
@@ -89,8 +87,6 @@ class RootComponent extends React.Component {
Import: { screen: ImportScreen, title: () => _('Import') },
Config: { screen: ConfigScreen, title: () => _('Options') },
Status: { screen: StatusScreen, title: () => _('Synchronisation Status') },
- EncryptionConfig: { screen: EncryptionConfigScreen, title: () => _('Encryption Options') },
- ClipperConfig: { screen: ClipperConfigScreen, title: () => _('Clipper Options') },
};
return
;
diff --git a/ElectronClient/app/gui/style/ConfigMenuBar.js b/ElectronClient/app/gui/style/ConfigMenuBar.js
new file mode 100644
index 000000000..543b99f5d
--- /dev/null
+++ b/ElectronClient/app/gui/style/ConfigMenuBar.js
@@ -0,0 +1,55 @@
+const { createSelector } = require('reselect');
+const { themeStyle } = require('../../theme.js');
+
+const themeSelector = (state, props) => themeStyle(props.theme);
+
+const style = createSelector(
+ themeSelector,
+ (theme) => {
+ const output = {
+ button: {
+ fontFamily: theme.fontFamily,
+ minWidth: 52,
+ border: 'none',
+ flexDirection: 'column',
+ display: 'flex',
+ alignItems: 'center',
+ padding: 9,
+ backgroundColor: theme.backgroundColor,
+ },
+ buttonIcon: {
+ fontSize: 24,
+ color: theme.colorFaded,
+ },
+ buttonLabel: {
+ display: 'flex',
+ flex: 1,
+ alignItems: 'flex-end',
+ color: theme.colorFaded,
+ },
+ root: {
+ minHeight: 58,
+ display: 'flex',
+ borderBottomWidth: 1,
+ borderBottomStyle: 'solid',
+ borderBottomColor: theme.dividerColor,
+ },
+ barButtons: {
+ display: 'flex',
+ flexDirection: 'row',
+ },
+ };
+
+ output.buttonIconSelected = Object.assign({}, output.buttonIcon, {
+ color: theme.highlightedColor,
+ });
+
+ output.buttonLabelSelected = Object.assign({}, output.buttonLabel, {
+ color: theme.color,
+ });
+
+ return output;
+ }
+);
+
+module.exports = style;
diff --git a/ElectronClient/app/gui/style/ExtensionBadge.js b/ElectronClient/app/gui/style/ExtensionBadge.js
new file mode 100644
index 000000000..66ee4095e
--- /dev/null
+++ b/ElectronClient/app/gui/style/ExtensionBadge.js
@@ -0,0 +1,49 @@
+const { createSelector } = require('reselect');
+const { themeStyle } = require('../../theme.js');
+
+const themeSelector = (state, props) => themeStyle(props.theme);
+
+const style = createSelector(
+ themeSelector,
+ (theme) => {
+ const output = {
+ root: {
+ width: 220,
+ height: 60,
+ borderRadius: 4,
+ border: '1px solid',
+ borderColor: theme.dividerColor,
+ backgroundColor: theme.backgroundColor,
+ paddingLeft: 14,
+ paddingRight: 14,
+ paddingTop: 8,
+ paddingBottom: 8,
+ boxSizing: 'border-box',
+ display: 'flex',
+ flexDirection: 'row',
+ boxShadow: '0px 1px 1px rgba(0,0,0,0.3)',
+ },
+ logo: {
+ width: 42,
+ height: 42,
+ },
+ labelGroup: {
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'center',
+ marginLeft: 14,
+ fontFamily: theme.fontFamily,
+ color: theme.color,
+ fontSize: theme.fontSize,
+ },
+ locationLabel: {
+ fontSize: theme.fontSize * 1.2,
+ fontWeight: 'bold',
+ },
+ };
+
+ return output;
+ }
+);
+
+module.exports = style;
diff --git a/ElectronClient/app/package-lock.json b/ElectronClient/app/package-lock.json
index 63b5e7110..2249d4026 100644
--- a/ElectronClient/app/package-lock.json
+++ b/ElectronClient/app/package-lock.json
@@ -5932,6 +5932,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
+ "reselect": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
+ "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
+ },
"resolve": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz",
diff --git a/ElectronClient/app/package.json b/ElectronClient/app/package.json
index d13fda287..f99557842 100644
--- a/ElectronClient/app/package.json
+++ b/ElectronClient/app/package.json
@@ -24,7 +24,8 @@
"appId": "net.cozic.joplin-desktop",
"npmRebuild": false,
"extraResources": [
- "build/icons/*"
+ "build/icons/*",
+ "build/images/*"
],
"win": {
"rfc3161TimeStampServer": "http://sha256timestamp.ws.symantec.com/sha256/timestamp",
@@ -141,6 +142,7 @@
"read-chunk": "^2.1.0",
"readability-node": "^0.1.0",
"redux": "^3.7.2",
+ "reselect": "^4.0.0",
"server-destroy": "^1.0.1",
"smalltalk": "^2.5.1",
"sprintf-js": "^1.1.1",
diff --git a/ElectronClient/app/style.css b/ElectronClient/app/style.css
index f05d36184..03ffb2de6 100644
--- a/ElectronClient/app/style.css
+++ b/ElectronClient/app/style.css
@@ -22,6 +22,10 @@ table td, table th {
border: 1px solid #ccc;
}
+a {
+ text-decoration: none;
+}
+
::-webkit-scrollbar {
width: 7px;
height: 7px;
@@ -140,3 +144,8 @@ table td, table th {
display: none;
}
}
+
+.config-menu-bar button:focus {
+ outline: 0 none;
+}
+
diff --git a/ElectronClient/app/theme.js b/ElectronClient/app/theme.js
index bb036471f..e0bca0f78 100644
--- a/ElectronClient/app/theme.js
+++ b/ElectronClient/app/theme.js
@@ -65,7 +65,11 @@ globalStyle.buttonStyle = {
maxWidth: 160,
paddingLeft: 12,
paddingRight: 12,
+ paddingTop: 6,
+ paddingBottom: 6,
boxShadow: '0px 1px 1px rgba(0,0,0,0.3)',
+ fontSize: globalStyle.fontSize,
+ borderRadius: 4,
};
const lightStyle = {
@@ -140,6 +144,8 @@ const darkStyle = {
editorTheme: 'twilight',
codeThemeCss: 'atom-one-dark-reasonable.css',
+
+ highlightedColor: '#0066C7',
};
// Solarized Styles
@@ -314,6 +320,11 @@ function addExtraStyles(style) {
style.dropdownList = Object.assign({}, style.inputStyle);
+ // In general the highlighted color, used to highlight text or icons, should be the same as selectedColor2
+ // but some times, depending on the theme, it might be too dark or too light, so it can be
+ // specified directly by the theme too.
+ if (!style.highlightedColor) style.highlightedColor = style.selectedColor2;
+
return style;
}
diff --git a/ReactNativeClient/lib/components/shared/config-shared.js b/ReactNativeClient/lib/components/shared/config-shared.js
index af96fc575..c2b732538 100644
--- a/ReactNativeClient/lib/components/shared/config-shared.js
+++ b/ReactNativeClient/lib/components/shared/config-shared.js
@@ -2,6 +2,7 @@ const Setting = require('lib/models/Setting.js');
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
const ObjectUtils = require('lib/ObjectUtils');
const { _ } = require('lib/locale.js');
+const { createSelector } = require('reselect');
const shared = {};
@@ -79,26 +80,51 @@ shared.settingsToComponents = function(comp, device, settings) {
return settingComps;
};
-shared.settingsToComponents2 = function(comp, device, settings) {
- const keys = Setting.keys(true, device);
- const sectionComps = [];
- const metadatas = [];
+const deviceSelector = (state) => state.device;
+const settingsSelector = (state) => state.settings;
- for (let i = 0; i < keys.length; i++) {
- const key = keys[i];
- if (!Setting.isPublic(key)) continue;
+shared.settingsSections = createSelector(
+ deviceSelector,
+ settingsSelector,
+ (device, settings) => {
+ const keys = Setting.keys(true, device);
+ const metadatas = [];
- const md = Setting.settingMetadata(key);
- if (md.show && !md.show(settings)) continue;
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ if (!Setting.isPublic(key)) continue;
- metadatas.push(md);
+ const md = Setting.settingMetadata(key);
+ if (md.show && !md.show(settings)) continue;
+
+ metadatas.push(md);
+ }
+
+ const output = Setting.groupMetadatasBySections(metadatas);
+
+ output.push({
+ name: 'encryption',
+ metadatas: [],
+ isScreen: true,
+ });
+
+ output.push({
+ name: 'server',
+ metadatas: [],
+ isScreen: true,
+ });
+
+ return output;
}
+);
- const sections = Setting.groupMetadatasBySections(metadatas);
+shared.settingsToComponents2 = function(comp, device, settings, selectedSectionName = '') {
+ const sectionComps = [];
+ const sections = shared.settingsSections({ device, settings });
for (let i = 0; i < sections.length; i++) {
const section = sections[i];
- const sectionComp = comp.sectionToComponent(section.name, section, settings);
+ const sectionComp = comp.sectionToComponent(section.name, section, settings, selectedSectionName === section.name);
if (!sectionComp) continue;
sectionComps.push(sectionComp);
}
diff --git a/ReactNativeClient/lib/models/Setting.js b/ReactNativeClient/lib/models/Setting.js
index c57b80a97..32d3b6d47 100644
--- a/ReactNativeClient/lib/models/Setting.js
+++ b/ReactNativeClient/lib/models/Setting.js
@@ -811,6 +811,21 @@ class Setting extends BaseModel {
if (name === 'plugins') return _('Plugins');
if (name === 'application') return _('Application');
if (name === 'revisionService') return _('Note History');
+ if (name === 'encryption') return _('Encryption');
+ if (name === 'server') return _('Web Clipper');
+ return name;
+ }
+
+ static sectionNameToIcon(name, platform) {
+ if (name === 'general') return 'fa-sliders';
+ if (name === 'sync') return 'fa-refresh';
+ if (name === 'appearance') return 'fa-pencil';
+ if (name === 'note') return 'fa-file-text-o';
+ if (name === 'plugins') return 'fa-puzzle-piece';
+ if (name === 'application') return 'fa-cog';
+ if (name === 'revisionService') return 'fa-archive-org';
+ if (name === 'encryption') return 'fa-key-modern';
+ if (name === 'server') return 'fa-hand-scissors-o';
return name;
}