diff --git a/ElectronClient/app/gui/SideBar.jsx b/ElectronClient/app/gui/SideBar.jsx index f37a080aac..0af9b8dc2a 100644 --- a/ElectronClient/app/gui/SideBar.jsx +++ b/ElectronClient/app/gui/SideBar.jsx @@ -3,6 +3,7 @@ const { connect } = require("react-redux"); const shared = require("lib/components/shared/side-menu-shared.js"); const { Synchronizer } = require("lib/synchronizer.js"); const BaseModel = require("lib/BaseModel.js"); +const Setting = require('lib/models/Setting.js'); const Folder = require("lib/models/Folder.js"); const Note = require("lib/models/Note.js"); const Tag = require("lib/models/Tag.js"); @@ -23,7 +24,7 @@ class SideBarComponent extends React.Component { this.onFolderDragStart_ = (event) => { const folderId = event.currentTarget.getAttribute('folderid'); if (!folderId) return; - + event.dataTransfer.setDragImage(new Image(), 1, 1); event.dataTransfer.clearData(); event.dataTransfer.setData('text/x-jop-folder-ids', JSON.stringify([folderId])); @@ -79,6 +80,11 @@ class SideBarComponent extends React.Component { id: folderId, }); }; + + this.state = { + tagHeaderIsExpanded: Setting.value('tagHeaderIsExpanded'), + folderHeaderIsExpanded: Setting.value('folderHeaderIsExpanded') + }; } style() { @@ -190,7 +196,7 @@ class SideBarComponent extends React.Component { if (shim.isLinux()) { // For some reason, the UI seems to sleep in some Linux distro during // sync. Cannot find the reason for it and cannot replicate, so here - // as a test force the update at regular intervals. + // as a test force the update at regular intervals. // https://github.com/laurent22/joplin/issues/312#issuecomment-429472193 if (!prevProps.syncStarted && this.props.syncStarted) { this.clearForceUpdateDuringSync(); @@ -201,7 +207,7 @@ class SideBarComponent extends React.Component { } if (prevProps.syncStarted && !this.props.syncStarted) this.clearForceUpdateDuringSync(); - } + } } componentWillUnmount() { @@ -303,7 +309,7 @@ class SideBarComponent extends React.Component { ); } - if (itemType === BaseModel.TYPE_TAG) { + if (itemType === BaseModel.TYPE_TAG) { menu.append( new MenuItem({ label: _('Rename'), @@ -440,14 +446,50 @@ class SideBarComponent extends React.Component { makeHeader(key, label, iconName, extraProps = {}) { const style = this.style().header; const icon = ; + + if (extraProps.toggleblock || extraProps.onClick) { + style.cursor = "pointer"; + } + + let headerClick = extraProps.onClick || null; + delete extraProps.onClick; + + // check if toggling option is set. + let toggleIcon = null; + const toggleKey = `${key}IsExpanded`; + if (extraProps.toggleblock) { + let isExpanded = this.state[toggleKey]; + toggleIcon = ; + } return ( -
+
{ + // if a custom click event is attached, trigger that. + if (headerClick) { + await headerClick(key, e); + } + await this.onHeaderClick_(key, e); + }}> {icon} - {label} + {label} + {toggleIcon}
); } + async onHeaderClick_(key, event) { + const currentHeader = event.currentTarget; + const toggleBlock = +currentHeader.getAttribute('toggleblock'); + if (toggleBlock) { + const toggleKey = `${key}IsExpanded`; + const isExpanded = this.state[toggleKey]; + this.setState({ + [toggleKey]: !isExpanded + }); + Setting.setValue(toggleKey, !isExpanded); + } + } + synchronizeButton(type) { const style = this.style().button; const iconName = type === "sync" ? "fa-refresh" : "fa-times"; @@ -477,24 +519,27 @@ class SideBarComponent extends React.Component { }); let items = []; - items.push(this.makeHeader("folderHeader", _("Notebooks"), "fa-folder-o", { onDrop: this.onFolderDrop_, folderid: '', + toggleblock: 1 })); if (this.props.folders.length) { const folderItems = shared.renderFolders(this.props, this.folderItem.bind(this)); - items = items.concat(folderItems); + items.push(
+ {folderItems}
); } - items.push(this.makeHeader("tagHeader", _("Tags"), "fa-tags")); + items.push(this.makeHeader("tagHeader", _("Tags"), "fa-tags", { + toggleblock: 1 + })); if (this.props.tags.length) { const tagItems = shared.renderTags(this.props, this.tagItem.bind(this)); items.push( -
+
{tagItems}
); diff --git a/ReactNativeClient/lib/models/Setting.js b/ReactNativeClient/lib/models/Setting.js index 9853f5a923..21c2946de5 100644 --- a/ReactNativeClient/lib/models/Setting.js +++ b/ReactNativeClient/lib/models/Setting.js @@ -96,7 +96,7 @@ class Setting extends BaseModel { 'startMinimized': { value: false, type: Setting.TYPE_BOOL, public: true, appTypes: ['desktop'], label: () => _('Start application minimised in the tray icon') }, 'collapsedFolderIds': { value: [], type: Setting.TYPE_ARRAY, public: false }, - + 'db.ftsEnabled': { value: -1, type: Setting.TYPE_INT, public: false }, 'encryption.enabled': { value: false, type: Setting.TYPE_BOOL, public: false }, 'encryption.activeMasterKeyId': { value: '', type: Setting.TYPE_STRING, public: false }, @@ -119,6 +119,8 @@ class Setting extends BaseModel { }}, 'noteVisiblePanes': { value: ['editor', 'viewer'], type: Setting.TYPE_ARRAY, public: false, appTypes: ['desktop'] }, 'sidebarVisibility': { value: true, type: Setting.TYPE_BOOL, public: false, appTypes: ['desktop'] }, + 'tagHeaderIsExpanded': { value: true, type: Setting.TYPE_BOOL, public: false, appTypes: ['desktop'] }, + 'folderHeaderIsExpanded': { value: true, type: Setting.TYPE_BOOL, public: false, appTypes: ['desktop'] }, 'editor': { value: '', type: Setting.TYPE_STRING, public: true, appTypes: ['cli', 'desktop'], label: () => _('Text editor command'), description: () => _('The editor command (may include arguments) that will be used to open a note. If none is provided it will try to auto-detect the default editor.') }, 'showAdvancedOptions': { value: false, type: Setting.TYPE_BOOL, public: true, appTypes: ['mobile' ], label: () => _('Show advanced options') }, 'sync.target': { value: SyncTargetRegistry.nameToId('dropbox'), type: Setting.TYPE_INT, isEnum: true, public: true, label: () => _('Synchronisation target'), description: (appType) => { return appType !== 'cli' ? null : _('The target to synchonise to. Each sync target may have additional parameters which are named as `sync.NUM.NAME` (all documented below).') }, options: () => { @@ -156,7 +158,7 @@ class Setting extends BaseModel { 'net.customCertificates': { value: '', type: Setting.TYPE_STRING, show: (settings) => { return [SyncTargetRegistry.nameToId('nextcloud'), SyncTargetRegistry.nameToId('webdav')].indexOf(settings['sync.target']) >= 0 }, public: true, appTypes: ['desktop', 'cli'], label: () => _('Custom TLS certificates'), description: () => _('Comma-separated list of paths to directories to load the certificates from, or path to individual cert files. For example: /my/cert_dir, /other/custom.pem. Note that if you make changes to the TLS settings, you must save your changes before clicking on "Check synchronisation configuration".') }, 'net.ignoreTlsErrors': { value: false, type: Setting.TYPE_BOOL, show: (settings) => { return [SyncTargetRegistry.nameToId('nextcloud'), SyncTargetRegistry.nameToId('webdav')].indexOf(settings['sync.target']) >= 0 }, public: true, appTypes: ['desktop', 'cli'], label: () => _('Ignore TLS certificate errors') }, - + 'api.token': { value: null, type: Setting.TYPE_STRING, public: false }, 'resourceService.lastProcessedChangeId': { value: 0, type: Setting.TYPE_INT, public: false },