diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index c8bff61cf..0c2516777 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,10 +1,25 @@ # Changelog +## [v1.10.0] + +### Enhancements + + - Add ability to select system as a theme option and make it the default + - Added support for the unicode bullet • in list items + - Display a notice that notes are loading when notes are loading + - In dev mode open Chrome Dev Tools in a separate window + +### Fixes + + - Rework WordPress.com signin to prevent infinite looping and login failures [#1627](https://github.com/Automattic/simplenote-electron/pull/1627) + - Update link to release-notes in updater config: CHANGELOG -> RELEASE_NOTES + - Stop showing that there are no notes when initially loading notes from the server. [#1680](https://github.com/Automattic/simplenote-electron/pull/1680) + ## [v1.9.1] ### Fixes - - Prevent ulimited duplication of changes after signing out and signing in [#1666](https://github.com/Automattic/simplenote-electron/pull/1666) + - Prevent ulimited duplication of changes after signing out and signing in [#1664](https://github.com/Automattic/simplenote-electron/pull/1664) ## [v1.9.0] diff --git a/after_sign_hook.js b/after_sign_hook.js index 542259b2c..a2d78ecab 100644 --- a/after_sign_hook.js +++ b/after_sign_hook.js @@ -7,7 +7,6 @@ module.exports = async function(params) { if (process.platform !== 'darwin') { return; } - console.log('afterSign hook triggered', params); if (!process.env.CIRCLE_TAG || process.env.CIRCLE_TAG.length === 0) { console.log('Not on a tag. Skipping notarization'); diff --git a/desktop/app.js b/desktop/app.js index bdafe3ec3..b0d222e7d 100644 --- a/desktop/app.js +++ b/desktop/app.js @@ -74,7 +74,7 @@ module.exports = function main() { } if (isDev || process.argv.includes('--devtools')) { - mainWindow.openDevTools(); + mainWindow.openDevTools({ mode: 'detach' }); } // Configure and set the application menu @@ -109,6 +109,11 @@ module.exports = function main() { }); }); + ipcMain.on('setAutoHideMenuBar', function(event, autoHideMenuBar) { + mainWindow.setAutoHideMenuBar(autoHideMenuBar || false); + mainWindow.setMenuBarVisibility(!autoHideMenuBar); + }); + mainWindowState.manage(mainWindow); mainWindow.webContents.on('new-window', function(event, linkUrl) { diff --git a/desktop/config-updater.json b/desktop/config-updater.json index 0b23af365..1e8b2420d 100644 --- a/desktop/config-updater.json +++ b/desktop/config-updater.json @@ -1,7 +1,7 @@ { "updater": { "downloadUrl": "https://github.com/Automattic/simplenote-electron/releases/latest", - "changelogUrl": "https://github.com/Automattic/simplenote-electron/blob/master/CHANGELOG.md", + "changelogUrl": "https://github.com/Automattic/simplenote-electron/blob/master/RELEASE-NOTES.txt", "apiUrl": "https://api.github.com/repos/automattic/simplenote-electron/releases/latest", "delay": 2000, "interval": 600000 diff --git a/lib/app.jsx b/lib/app.jsx index 6fb0ca6b9..8f68ba854 100644 --- a/lib/app.jsx +++ b/lib/app.jsx @@ -68,6 +68,7 @@ function mapDispatchToProps(dispatch, { noteBucket }) { 'setNoteDisplay', 'setMarkdown', 'setAccountName', + 'toggleAutoHideMenuBar', 'toggleFocusMode', 'toggleSpellCheck', ]), @@ -145,6 +146,7 @@ export const App = connect( componentDidMount() { ipc.on('appCommand', this.onAppCommand); + ipc.send('setAutoHideMenuBar', this.props.settings.autoHideMenuBar); ipc.send('settingsUpdate', this.props.settings); this.props.noteBucket @@ -309,8 +311,20 @@ export const App = connect( preferencesBucket: this.props.preferencesBucket, }); + getSystemColorMode = () => + window.matchMedia('(prefers-color-scheme: dark)').matches + ? 'dark' + : 'light'; + + getTheme = () => { + const { + settings: { theme }, + } = this.props; + return 'system' === theme ? this.getSystemColorMode() : theme; + }; + initializeElectron = () => { - const remote = __non_webpack_require__('electron').remote; // eslint-disable-line no-undef + const { remote } = __non_webpack_require__('electron'); // eslint-disable-line no-undef this.setState({ electron: { @@ -320,12 +334,28 @@ export const App = connect( }); }; - onUpdateContent = (note, content) => - this.props.actions.updateNoteContent({ - noteBucket: this.props.noteBucket, - note, - content, - }); + onUpdateContent = (note, content) => { + if (!note) { + return; + } + + const updatedNote = { + ...note, + data: { + ...note.data, + content, + modificationDate: Math.floor(Date.now() / 1000), + }, + }; + + // update the bucket but don't force sync right away + // as this happens per keystroke when the user is editing + // a note. The NoteEditor will notify via props when + // it's time to sync via Simperium + const { noteBucket } = this.props; + + noteBucket.update(note.id, updatedNote.data, {}, { sync: false }); + }; syncNote = noteId => { this.props.noteBucket.touch(noteId); @@ -388,7 +418,7 @@ export const App = connect( } = this.props; const isMacApp = isElectronMac(); - const themeClass = `theme-${settings.theme}`; + const themeClass = `theme-${this.getTheme()}`; const appClasses = classNames('app', themeClass, { 'is-line-length-full': settings.lineLength === 'full', @@ -407,7 +437,9 @@ export const App = connect( {isDevConfig && } {isAuthorized ? (
- {state.showNavigation && } + {state.showNavigation && ( + + )}
); diff --git a/lib/auth/index.jsx b/lib/auth/index.jsx index c01e2c19a..84524b5a2 100644 --- a/lib/auth/index.jsx +++ b/lib/auth/index.jsx @@ -216,84 +216,73 @@ export class Auth extends Component { }; setupAuthWindow = () => { - const remote = __non_webpack_require__('electron').remote; // eslint-disable-line no-undef - const BrowserWindow = remote.BrowserWindow; - const protocol = remote.protocol; + // eslint-disable-next-line no-undef + const { BrowserWindow, session } = __non_webpack_require__( + 'electron' + ).remote; + this.authWindow = new BrowserWindow({ width: 640, height: 640, show: false, webPreferences: { nodeIntegration: false, + session: session.fromPartition(`fresh-session-${Math.random()}`), }, }); - // Register simplenote:// protocol - protocol.registerHttpProtocol('simplenote', req => { - this.authWindow.loadURL(req.url); + this.authWindow.on('closed', () => { + // make sure to release this from memory + this.authWindow = null; }); - this.authWindow.webContents.on('will-navigate', (event, url) => - this.onBrowserNavigate(url) - ); - - this.authWindow.webContents.on( - 'did-get-redirect-request', - (event, oldUrl, newUrl) => this.onBrowserNavigate(newUrl) - ); - }; - - onBrowserNavigate = url => { - try { - this.authenticateWithUrl(new URL(url)); - } catch (error) { - // Do nothing if Url was invalid - } - }; - - authenticateWithUrl = url => { - // Bail out if the url is not the simplenote protocol - if (url.protocol !== 'simplenote:') { - return; - } - - const { authorizeUserWithToken, saveWPToken } = this.props; - const params = url.searchParams; - - // Display an error message if authorization failed. - if (params.get('error')) { - switch (params.get('code')) { - case '1': - return this.authError( - 'Please activate your WordPress.com account via email and try again.' - ); - default: - return this.authError('An error was encountered while signing in.'); + this.authWindow.webContents.session.protocol.registerHttpProtocol( + 'simplenote', + (req, callback) => { + const { searchParams } = new URL(req.url); + + // cancel the request by running callback() with no parameters + // we're going to close the window and continue processing the + // information we received in args of the simplenote://auth URL + callback(); + + const errorCode = searchParams.get('error') + ? searchParams.get('code') + : false; + const authState = searchParams.get('state'); + const userEmail = searchParams.get('user'); + const simpToken = searchParams.get('token'); + const wpccToken = searchParams.get('wp_token'); + + // Display an error message if authorization failed. + switch (errorCode) { + case false: + break; + case '1': + return this.authError( + 'Please activate your WordPress.com account via email and try again.' + ); + case '2': + return this.authError( + 'Please confirm your account with the confirmation email before signing in to Simplenote.' + ); + default: + return this.authError('An error was encountered while signing in.'); + } + + this.closeAuthWindow(); + + if (authState !== this.authState) { + return; + } + + const { authorizeUserWithToken, saveWPToken } = this.props; + authorizeUserWithToken(userEmail, simpToken); + if (wpccToken) { + saveWPToken(wpccToken); + } } - } - - const userEmail = params.get('user'); - const spToken = params.get('token'); - const state = params.get('state'); - - // Sanity check on params - if (!(spToken && userEmail && state)) { - return this.closeAuthWindow(); - } - - // Verify that the state strings match - if (state !== this.authState) { - return; - } - - authorizeUserWithToken(userEmail, spToken); - - const wpToken = params.get('wp_token'); - if (wpToken) { - saveWPToken(wpToken); - } - - this.closeAuthWindow(); + ); }; authError = errorMessage => { diff --git a/lib/dialog-renderer/index.jsx b/lib/dialog-renderer/index.jsx index 4660744c2..6560c6876 100644 --- a/lib/dialog-renderer/index.jsx +++ b/lib/dialog-renderer/index.jsx @@ -13,6 +13,7 @@ export const DialogRenderer = props => { closeDialog, dialogs, isElectron, + isMacApp, } = props; const renderDialog = dialog => { @@ -40,6 +41,7 @@ export const DialogRenderer = props => { dialog={dialog} requestClose={closeThisDialog} isElectron={isElectron} + isMacApp={isMacApp} {...appProps} /> @@ -56,6 +58,7 @@ DialogRenderer.propTypes = { closeDialog: PropTypes.func.isRequired, dialogs: PropTypes.array.isRequired, isElectron: PropTypes.bool.isRequired, + isMacApp: PropTypes.bool.isRequired, }; export default DialogRenderer; diff --git a/lib/dialogs/settings-group.jsx b/lib/dialogs/settings-group.jsx index 5aa2dc776..0749c0a1c 100644 --- a/lib/dialogs/settings-group.jsx +++ b/lib/dialogs/settings-group.jsx @@ -27,11 +27,13 @@ export const SettingsGroup = ({ ); + const childElements = React.Children.toArray(children).filter(o => o); + return (
{groupTitle}
- {React.Children.map(children, ({ props: { slug, title } }) => ( + {childElements.map(({ props: { slug, title } }) => (