From 11ad6c6bef32b8280597f806c7d15340a5c05089 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Tue, 14 Nov 2017 23:01:19 +0000 Subject: [PATCH] Removing need for including build files --- ElectronClient/app/compile-jsx.js | 40 ++ ElectronClient/app/gui/ConfigScreen.min.js | 147 ----- ElectronClient/app/gui/Header.min.js | 101 ---- ElectronClient/app/gui/IconButton.min.js | 41 -- ElectronClient/app/gui/ImportScreen.min.js | 143 ----- ElectronClient/app/gui/ItemList.min.js | 77 --- ElectronClient/app/gui/Main.min.js | 58 -- ElectronClient/app/gui/MainScreen.min.js | 256 --------- ElectronClient/app/gui/Navigator.min.js | 54 -- ElectronClient/app/gui/NoteList.min.js | 188 ------- ElectronClient/app/gui/NoteText.min.js | 525 ------------------ .../app/gui/OneDriveAuthScreen.min.js | 108 ---- .../app/gui/OneDriveLoginScreen.min.js | 111 ---- ElectronClient/app/gui/PromptDialog.min.js | 146 ----- ElectronClient/app/gui/Root.min.js | 92 --- ElectronClient/app/gui/SideBar.min.js | 262 --------- ElectronClient/app/package.json | 4 +- 17 files changed, 43 insertions(+), 2310 deletions(-) create mode 100644 ElectronClient/app/compile-jsx.js delete mode 100644 ElectronClient/app/gui/ConfigScreen.min.js delete mode 100644 ElectronClient/app/gui/Header.min.js delete mode 100644 ElectronClient/app/gui/IconButton.min.js delete mode 100644 ElectronClient/app/gui/ImportScreen.min.js delete mode 100644 ElectronClient/app/gui/ItemList.min.js delete mode 100644 ElectronClient/app/gui/Main.min.js delete mode 100644 ElectronClient/app/gui/MainScreen.min.js delete mode 100644 ElectronClient/app/gui/Navigator.min.js delete mode 100644 ElectronClient/app/gui/NoteList.min.js delete mode 100644 ElectronClient/app/gui/NoteText.min.js delete mode 100644 ElectronClient/app/gui/OneDriveAuthScreen.min.js delete mode 100644 ElectronClient/app/gui/OneDriveLoginScreen.min.js delete mode 100644 ElectronClient/app/gui/PromptDialog.min.js delete mode 100644 ElectronClient/app/gui/Root.min.js delete mode 100644 ElectronClient/app/gui/SideBar.min.js diff --git a/ElectronClient/app/compile-jsx.js b/ElectronClient/app/compile-jsx.js new file mode 100644 index 000000000..c84e490b0 --- /dev/null +++ b/ElectronClient/app/compile-jsx.js @@ -0,0 +1,40 @@ +const fs = require('fs-extra'); +const spawnSync = require('child_process').spawnSync; + +const babelPath = __dirname + '/node_modules/.bin/babel' + (process.platform === 'win32' ? '.cmd' : ''); +const guiPath = __dirname + '/gui'; + +function fileIsNewerThan(path1, path2) { + if (!fs.existsSync(path2)) return true; + + const stat1 = fs.statSync(path1); + const stat2 = fs.statSync(path2); + + return stat1.mtime > stat2.mtime; +} + +fs.readdirSync(guiPath).forEach((filename) => { + const jsxPath = guiPath + '/' + filename; + const p = jsxPath.split('.'); + if (p.length <= 1) return; + const ext = p[p.length - 1]; + if (ext !== 'jsx') return; + p.pop(); + + const basePath = p.join('/'); + + const jsPath = basePath + '.min.js'; + + if (fileIsNewerThan(jsxPath, jsPath)) { + console.info('Compiling ' + jsxPath + '...'); + const result = spawnSync(babelPath, ['--presets', 'react', '--out-file',jsPath, jsxPath]); + if (result.status !== 0) { + const msg = []; + if (result.stdout) msg.push(result.stdout.toString()); + if (result.stderr) msg.push(result.stderr.toString()); + console.error(msg.join('\n')); + if (result.error) console.error(result.error); + process.exit(result.status); + } + } +}); diff --git a/ElectronClient/app/gui/ConfigScreen.min.js b/ElectronClient/app/gui/ConfigScreen.min.js deleted file mode 100644 index 27a5149ba..000000000 --- a/ElectronClient/app/gui/ConfigScreen.min.js +++ /dev/null @@ -1,147 +0,0 @@ -const React = require('react'); -const { connect } = require('react-redux'); -const { reg } = require('lib/registry.js'); -const { Setting } = require('lib/models/setting.js'); -const { bridge } = require('electron').remote.require('./bridge'); -const { Header } = require('./Header.min.js'); -const { themeStyle } = require('../theme.js'); -const { _ } = require('lib/locale.js'); - -class ConfigScreenComponent extends React.Component { - - settingToComponent(key, value) { - const theme = themeStyle(this.props.theme); - - let output = null; - - const rowStyle = { - marginBottom: 10 - }; - - const labelStyle = Object.assign({}, theme.textStyle, { - display: 'inline-block', - marginRight: 10 - }); - - const controlStyle = { - display: 'inline-block' - }; - - const updateSettingValue = (key, value) => { - Setting.setValue(key, value); - }; - - const md = Setting.settingMetadata(key); - - if (md.isEnum) { - let items = []; - const settingOptions = md.options(); - for (let k in settingOptions) { - if (!settingOptions.hasOwnProperty(k)) continue; - items.push(React.createElement( - 'option', - { value: k.toString(), key: k }, - settingOptions[k] - )); - } - - return React.createElement( - 'div', - { key: key, style: rowStyle }, - React.createElement( - 'div', - { style: labelStyle }, - React.createElement( - 'label', - null, - md.label() - ) - ), - React.createElement( - 'select', - { value: value, style: controlStyle, onChange: event => { - updateSettingValue(key, event.target.value); - } }, - items - ) - ); - } else if (md.type === Setting.TYPE_BOOL) { - return React.createElement( - 'div', - { key: key, style: rowStyle }, - React.createElement( - 'div', - { style: controlStyle }, - React.createElement( - 'label', - null, - React.createElement('input', { type: 'checkbox', defaultChecked: !!value, onChange: event => { - updateSettingValue(key, !!event.target.checked); - } }), - React.createElement( - 'span', - { style: labelStyle }, - ' ', - md.label() - ) - ) - ) - ); - } - - return output; - } - - render() { - const theme = themeStyle(this.props.theme); - const style = this.props.style; - const settings = this.props.settings; - - const headerStyle = { - width: style.width - }; - - const containerStyle = { - padding: 10 - }; - - let settingComps = []; - let keys = Setting.keys(true, 'desktop'); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - if (key === 'sync.target') continue; - if (!(key in settings)) { - console.warn('Missing setting: ' + key); - continue; - } - const comp = this.settingToComponent(key, settings[key]); - if (!comp) continue; - settingComps.push(comp); - } - - return React.createElement( - 'div', - { style: style }, - React.createElement(Header, { style: headerStyle }), - React.createElement( - 'div', - { style: containerStyle }, - settingComps - ) - ); - } - -} - -const mapStateToProps = state => { - return { - theme: state.settings.theme, - settings: state.settings, - locale: state.settings.locale - }; -}; - -const ConfigScreen = connect(mapStateToProps)(ConfigScreenComponent); - -module.exports = { ConfigScreen }; - diff --git a/ElectronClient/app/gui/Header.min.js b/ElectronClient/app/gui/Header.min.js deleted file mode 100644 index a699253a2..000000000 --- a/ElectronClient/app/gui/Header.min.js +++ /dev/null @@ -1,101 +0,0 @@ -const React = require('react'); -const { connect } = require('react-redux'); -const { reg } = require('lib/registry.js'); -const { themeStyle } = require('../theme.js'); -const { _ } = require('lib/locale.js'); - -class HeaderComponent extends React.Component { - - back_click() { - this.props.dispatch({ type: 'NAV_BACK' }); - } - - makeButton(key, style, options) { - let icon = null; - if (options.iconName) { - const iconStyle = { - fontSize: Math.round(style.fontSize * 1.4), - color: style.color - }; - if (options.title) iconStyle.marginRight = 5; - icon = React.createElement('i', { style: iconStyle, className: "fa " + options.iconName }); - } - - const isEnabled = !('enabled' in options) || options.enabled; - let classes = ['button']; - if (!isEnabled) classes.push('disabled'); - - const finalStyle = Object.assign({}, style, { - opacity: isEnabled ? 1 : 0.4 - }); - - return React.createElement( - 'a', - { - className: classes.join(' '), - style: finalStyle, - key: key, - href: '#', - onClick: () => { - if (isEnabled) options.onClick(); - } - }, - icon, - options.title ? options.title : '' - ); - } - - render() { - const style = this.props.style; - const theme = themeStyle(this.props.theme); - const showBackButton = this.props.showBackButton === undefined || this.props.showBackButton === true; - style.height = theme.headerHeight; - style.display = 'flex'; - style.flexDirection = 'row'; - style.borderBottom = '1px solid ' + theme.dividerColor; - style.boxSizing = 'border-box'; - - const buttons = []; - - const buttonStyle = { - height: theme.headerHeight, - display: 'flex', - alignItems: 'center', - paddingLeft: theme.headerButtonHPadding, - paddingRight: theme.headerButtonHPadding, - color: theme.color, - textDecoration: 'none', - fontFamily: theme.fontFamily, - fontSize: theme.fontSize, - boxSizing: 'border-box', - cursor: 'default' - }; - - if (showBackButton) { - buttons.push(this.makeButton('back', buttonStyle, { title: _('Back'), onClick: () => this.back_click(), iconName: 'fa-chevron-left ' })); - } - - if (this.props.buttons) { - for (let i = 0; i < this.props.buttons.length; i++) { - const o = this.props.buttons[i]; - buttons.push(this.makeButton('btn_' + i + '_' + o.title, buttonStyle, o)); - } - } - - return React.createElement( - 'div', - { className: 'header', style: style }, - buttons - ); - } - -} - -const mapStateToProps = state => { - return { theme: state.settings.theme }; -}; - -const Header = connect(mapStateToProps)(HeaderComponent); - -module.exports = { Header }; - diff --git a/ElectronClient/app/gui/IconButton.min.js b/ElectronClient/app/gui/IconButton.min.js deleted file mode 100644 index 18d3d4d43..000000000 --- a/ElectronClient/app/gui/IconButton.min.js +++ /dev/null @@ -1,41 +0,0 @@ -const React = require('react'); -const { connect } = require('react-redux'); -const { themeStyle } = require('../theme.js'); - -class IconButton extends React.Component { - - render() { - const style = this.props.style; - const theme = themeStyle(this.props.theme); - const iconStyle = { - color: theme.color, - fontSize: theme.fontSize * 1.4 - }; - const icon = React.createElement('i', { style: iconStyle, className: "fa " + this.props.iconName }); - - const rootStyle = Object.assign({ - display: 'flex', - textDecoration: 'none', - padding: 10, - width: theme.buttonMinHeight, - height: theme.buttonMinHeight, - boxSizing: 'border-box', - alignItems: 'center', - justifyContent: 'center', - backgroundColor: theme.backgroundColor, - cursor: 'default' - }, style); - - return React.createElement( - 'a', - { href: '#', style: rootStyle, className: 'icon-button', onClick: () => { - if (this.props.onClick) this.props.onClick(); - } }, - icon - ); - } - -} - -module.exports = { IconButton }; - diff --git a/ElectronClient/app/gui/ImportScreen.min.js b/ElectronClient/app/gui/ImportScreen.min.js deleted file mode 100644 index 06d3c71ab..000000000 --- a/ElectronClient/app/gui/ImportScreen.min.js +++ /dev/null @@ -1,143 +0,0 @@ -const React = require('react'); -const { connect } = require('react-redux'); -const { reg } = require('lib/registry.js'); -const { Folder } = require('lib/models/folder.js'); -const { bridge } = require('electron').remote.require('./bridge'); -const { Header } = require('./Header.min.js'); -const { themeStyle } = require('../theme.js'); -const { _ } = require('lib/locale.js'); -const { filename, basename } = require('lib/path-utils.js'); -const { importEnex } = require('lib/import-enex'); - -class ImportScreenComponent extends React.Component { - - componentWillMount() { - this.setState({ - doImport: true, - filePath: this.props.filePath, - messages: [] - }); - } - - componentWillReceiveProps(newProps) { - if (newProps.filePath) { - this.setState({ - doImport: true, - filePath: newProps.filePath, - messages: [] - }); - - this.doImport(); - } - } - - componentDidMount() { - if (this.state.filePath && this.state.doImport) { - this.doImport(); - } - } - - addMessage(key, text) { - const messages = this.state.messages.slice(); - let found = false; - - for (let i = 0; i < messages.length; i++) { - if (messages[i].key === key) { - messages[i].text = text; - found = true; - break; - } - } - - if (!found) messages.push({ key: key, text: text }); - - this.setState({ messages: messages }); - } - - async doImport() { - const filePath = this.props.filePath; - const folderTitle = await Folder.findUniqueFolderTitle(filename(filePath)); - const messages = this.state.messages.slice(); - - this.addMessage('start', _('New notebook "%s" will be created and file "%s" will be imported into it', folderTitle, basename(filePath))); - - let lastProgress = ''; - let progressCount = 0; - - const options = { - onProgress: progressState => { - let line = []; - line.push(_('Found: %d.', progressState.loaded)); - line.push(_('Created: %d.', progressState.created)); - if (progressState.updated) line.push(_('Updated: %d.', progressState.updated)); - if (progressState.skipped) line.push(_('Skipped: %d.', progressState.skipped)); - if (progressState.resourcesCreated) line.push(_('Resources: %d.', progressState.resourcesCreated)); - if (progressState.notesTagged) line.push(_('Tagged: %d.', progressState.notesTagged)); - lastProgress = line.join(' '); - this.addMessage('progress', lastProgress); - }, - onError: error => { - const messages = this.state.messages.slice(); - let s = error.trace ? error.trace : error.toString(); - messages.push({ key: 'error_' + progressCount++, text: s }); - this.addMessage('error_' + progressCount++, lastProgress); - } - }; - - const folder = await Folder.save({ title: folderTitle }); - - await importEnex(folder.id, filePath, options); - - this.addMessage('done', _('The notes have been imported: %s', lastProgress)); - this.setState({ doImport: false }); - } - - render() { - const theme = themeStyle(this.props.theme); - const style = this.props.style; - const messages = this.state.messages; - - const messagesStyle = { - padding: 10, - fontSize: theme.fontSize, - fontFamily: theme.fontFamily, - backgroundColor: theme.backgroundColor - }; - - const headerStyle = { - width: style.width - }; - - const messageComps = []; - for (let i = 0; i < messages.length; i++) { - messageComps.push(React.createElement( - 'div', - { key: messages[i].key }, - messages[i].text - )); - } - - return React.createElement( - 'div', - { style: {} }, - React.createElement(Header, { style: headerStyle }), - React.createElement( - 'div', - { style: messagesStyle }, - messageComps - ) - ); - } - -} - -const mapStateToProps = state => { - return { - theme: state.settings.theme - }; -}; - -const ImportScreen = connect(mapStateToProps)(ImportScreenComponent); - -module.exports = { ImportScreen }; - diff --git a/ElectronClient/app/gui/ItemList.min.js b/ElectronClient/app/gui/ItemList.min.js deleted file mode 100644 index 4967dfbbc..000000000 --- a/ElectronClient/app/gui/ItemList.min.js +++ /dev/null @@ -1,77 +0,0 @@ -const React = require('react'); - -class ItemList extends React.Component { - - constructor() { - super(); - - this.scrollTop_ = 0; - } - - updateStateItemIndexes(props) { - if (typeof props === 'undefined') props = this.props; - - const topItemIndex = Math.floor(this.scrollTop_ / props.itemHeight); - const visibleItemCount = Math.ceil(props.style.height / props.itemHeight); - - let bottomItemIndex = topItemIndex + visibleItemCount; - if (bottomItemIndex >= props.items.length) bottomItemIndex = props.items.length - 1; - - this.setState({ - topItemIndex: topItemIndex, - bottomItemIndex: bottomItemIndex - }); - } - - componentWillMount() { - this.updateStateItemIndexes(); - } - - componentWillReceiveProps(newProps) { - this.updateStateItemIndexes(newProps); - } - - onScroll(scrollTop) { - this.scrollTop_ = scrollTop; - this.updateStateItemIndexes(); - } - - render() { - const items = this.props.items; - const style = Object.assign({}, this.props.style, { - overflowX: 'hidden', - overflowY: 'auto' - }); - - if (!this.props.itemHeight) throw new Error('itemHeight is required'); - - const blankItem = function (key, height) { - return React.createElement('div', { key: key, style: { height: height } }); - }; - - let itemComps = [blankItem('top', this.state.topItemIndex * this.props.itemHeight)]; - - for (let i = this.state.topItemIndex; i <= this.state.bottomItemIndex; i++) { - const itemComp = this.props.itemRenderer(items[i]); - itemComps.push(itemComp); - } - - itemComps.push(blankItem('bottom', (items.length - this.state.bottomItemIndex - 1) * this.props.itemHeight)); - - let classes = ['item-list']; - if (this.props.className) classes.push(this.props.className); - - const that = this; - - return React.createElement( - 'div', - { className: classes.join(' '), style: style, onScroll: event => { - this.onScroll(event.target.scrollTop); - } }, - itemComps - ); - } -} - -module.exports = { ItemList }; - diff --git a/ElectronClient/app/gui/Main.min.js b/ElectronClient/app/gui/Main.min.js deleted file mode 100644 index 015fd139a..000000000 --- a/ElectronClient/app/gui/Main.min.js +++ /dev/null @@ -1,58 +0,0 @@ -const React = require('react'); -const { render } = require('react-dom'); -const { createStore } = require('redux'); -const { connect, Provider } = require('react-redux'); - -const { SideBar } = require('./SideBar.min.js'); -const { NoteList } = require('./NoteList.min.js'); -const { NoteText } = require('./NoteText.min.js'); - -const { app } = require('../app'); - -const { bridge } = require('electron').remote.require('./bridge'); - -class MainComponent extends React.Component { - - render() { - const style = this.props.style; - - const noteListStyle = { - width: Math.floor(style.width / 3), - height: style.height, - display: 'inline-block', - verticalAlign: 'top' - }; - - const noteTextStyle = { - width: noteListStyle.width, - height: style.height, - display: 'inline-block', - verticalAlign: 'top' - }; - - const sideBarStyle = { - width: style.width - (noteTextStyle.width + noteListStyle.width), - height: style.height, - display: 'inline-block', - verticalAlign: 'top' - }; - - return React.createElement( - 'div', - { style: style }, - React.createElement(SideBar, { style: sideBarStyle }), - React.createElement(NoteList, { itemHeight: 40, style: noteListStyle }), - React.createElement(NoteText, { style: noteTextStyle }) - ); - } - -} - -const mapStateToProps = state => { - return {}; -}; - -const Main = connect(mapStateToProps)(MainComponent); - -module.exports = { Main }; - diff --git a/ElectronClient/app/gui/MainScreen.min.js b/ElectronClient/app/gui/MainScreen.min.js deleted file mode 100644 index 437aa6a81..000000000 --- a/ElectronClient/app/gui/MainScreen.min.js +++ /dev/null @@ -1,256 +0,0 @@ -const React = require('react'); -const { connect } = require('react-redux'); -const { Header } = require('./Header.min.js'); -const { SideBar } = require('./SideBar.min.js'); -const { NoteList } = require('./NoteList.min.js'); -const { NoteText } = require('./NoteText.min.js'); -const { PromptDialog } = require('./PromptDialog.min.js'); -const { Setting } = require('lib/models/setting.js'); -const { Tag } = require('lib/models/tag.js'); -const { Note } = require('lib/models/note.js'); -const { Folder } = require('lib/models/folder.js'); -const { themeStyle } = require('../theme.js'); -const { _ } = require('lib/locale.js'); -const layoutUtils = require('lib/layout-utils.js'); -const { bridge } = require('electron').remote.require('./bridge'); - -class MainScreenComponent extends React.Component { - - componentWillMount() { - this.setState({ - promptOptions: null - }); - } - - componentWillReceiveProps(newProps) { - if (newProps.windowCommand) { - this.doCommand(newProps.windowCommand); - } - } - - toggleVisiblePanes() { - this.props.dispatch({ - type: 'NOTE_VISIBLE_PANES_TOGGLE' - }); - } - - async doCommand(command) { - if (!command) return; - - const createNewNote = async (title, isTodo) => { - const folderId = Setting.value('activeFolderId'); - if (!folderId) return; - - const note = await Note.save({ - title: title, - parent_id: folderId, - is_todo: isTodo ? 1 : 0 - }); - Note.updateGeolocation(note.id); - - this.props.dispatch({ - type: 'NOTE_SELECT', - id: note.id - }); - }; - - let commandProcessed = true; - - if (command.name === 'newNote') { - if (!this.props.folders.length) { - bridge().showErrorMessageBox(_('Please create a notebook first.')); - return; - } - - this.setState({ - promptOptions: { - label: _('Note title:'), - onClose: async answer => { - if (answer) await createNewNote(answer, false); - this.setState({ promptOptions: null }); - } - } - }); - } else if (command.name === 'newTodo') { - if (!this.props.folders.length) { - bridge().showErrorMessageBox(_('Please create a notebook first')); - return; - } - - this.setState({ - promptOptions: { - label: _('To-do title:'), - onClose: async answer => { - if (answer) await createNewNote(answer, true); - this.setState({ promptOptions: null }); - } - } - }); - } else if (command.name === 'newNotebook') { - this.setState({ - promptOptions: { - label: _('Notebook title:'), - onClose: async answer => { - if (answer) { - let folder = null; - try { - folder = await Folder.save({ title: answer }, { userSideValidation: true }); - } catch (error) { - bridge().showErrorMessageBox(error.message); - return; - } - - this.props.dispatch({ - type: 'FOLDER_SELECT', - id: folder.id - }); - } - - this.setState({ promptOptions: null }); - } - } - }); - } else if (command.name === 'setTags') { - const tags = await Tag.tagsByNoteId(command.noteId); - const tagTitles = tags.map(a => { - return a.title; - }); - - this.setState({ - promptOptions: { - label: _('Add or remove tags:'), - description: _('Separate each tag by a comma.'), - value: tagTitles.join(', '), - onClose: async answer => { - if (answer !== null) { - const tagTitles = answer.split(',').map(a => { - return a.trim(); - }); - await Tag.setNoteTagsByTitles(command.noteId, tagTitles); - } - this.setState({ promptOptions: null }); - } - } - }); - } else { - commandProcessed = false; - } - - if (commandProcessed) { - this.props.dispatch({ - type: 'WINDOW_COMMAND', - name: null - }); - } - } - - render() { - const style = this.props.style; - const theme = themeStyle(this.props.theme); - const promptOptions = this.state.promptOptions; - const folders = this.props.folders; - const notes = this.props.notes; - - const headerStyle = { - width: style.width - }; - - const rowHeight = style.height - theme.headerHeight; - - const sideBarStyle = { - width: Math.floor(layoutUtils.size(style.width * .2, 150, 300)), - height: rowHeight, - display: 'inline-block', - verticalAlign: 'top' - }; - - const noteListStyle = { - width: Math.floor(layoutUtils.size(style.width * .2, 150, 300)), - height: rowHeight, - display: 'inline-block', - verticalAlign: 'top' - }; - - const noteTextStyle = { - width: Math.floor(layoutUtils.size(style.width - sideBarStyle.width - noteListStyle.width, 0)), - height: rowHeight, - display: 'inline-block', - verticalAlign: 'top' - }; - - const promptStyle = { - width: style.width, - height: style.height - }; - - const headerButtons = []; - - headerButtons.push({ - title: _('New note'), - iconName: 'fa-file-o', - enabled: !!folders.length, - onClick: () => { - this.doCommand({ name: 'newNote' }); - } - }); - - headerButtons.push({ - title: _('New to-do'), - iconName: 'fa-check-square-o', - enabled: !!folders.length, - onClick: () => { - this.doCommand({ name: 'newTodo' }); - } - }); - - headerButtons.push({ - title: _('New notebook'), - iconName: 'fa-folder-o', - onClick: () => { - this.doCommand({ name: 'newNotebook' }); - } - }); - - headerButtons.push({ - title: _('Layout'), - iconName: 'fa-columns', - enabled: !!notes.length, - onClick: () => { - this.toggleVisiblePanes(); - } - }); - - return React.createElement( - 'div', - { style: style }, - React.createElement(PromptDialog, { - value: promptOptions && promptOptions.value ? promptOptions.value : '', - theme: this.props.theme, - style: promptStyle, - onClose: answer => promptOptions.onClose(answer), - label: promptOptions ? promptOptions.label : '', - description: promptOptions ? promptOptions.description : null, - visible: !!this.state.promptOptions }), - React.createElement(Header, { style: headerStyle, showBackButton: false, buttons: headerButtons }), - React.createElement(SideBar, { style: sideBarStyle }), - React.createElement(NoteList, { style: noteListStyle }), - React.createElement(NoteText, { style: noteTextStyle, visiblePanes: this.props.noteVisiblePanes }) - ); - } - -} - -const mapStateToProps = state => { - return { - theme: state.settings.theme, - windowCommand: state.windowCommand, - noteVisiblePanes: state.noteVisiblePanes, - folders: state.folders, - notes: state.notes - }; -}; - -const MainScreen = connect(mapStateToProps)(MainScreenComponent); - -module.exports = { MainScreen }; - diff --git a/ElectronClient/app/gui/Navigator.min.js b/ElectronClient/app/gui/Navigator.min.js deleted file mode 100644 index 94de3de51..000000000 --- a/ElectronClient/app/gui/Navigator.min.js +++ /dev/null @@ -1,54 +0,0 @@ -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -const React = require('react');const Component = React.Component; -const { connect } = require('react-redux'); -const { app } = require('../app.js'); -const { bridge } = require('electron').remote.require('./bridge'); - -class NavigatorComponent extends Component { - - componentWillReceiveProps(newProps) { - if (newProps.route) { - const screenInfo = this.props.screens[newProps.route.routeName]; - let windowTitle = ['Joplin']; - if (screenInfo.title) { - windowTitle.push(screenInfo.title()); - } - this.updateWindowTitle(windowTitle.join(' - ')); - } - } - - updateWindowTitle(title) { - bridge().window().setTitle(title); - } - - render() { - if (!this.props.route) throw new Error('Route must not be null'); - - const route = this.props.route; - const screenProps = route.props ? route.props : {}; - const screenInfo = this.props.screens[route.routeName]; - const Screen = screenInfo.screen; - - const screenStyle = { - width: this.props.style.width, - height: this.props.style.height - }; - - return React.createElement( - 'div', - { style: this.props.style }, - React.createElement(Screen, _extends({ style: screenStyle }, screenProps)) - ); - } - -} - -const Navigator = connect(state => { - return { - route: state.route - }; -})(NavigatorComponent); - -module.exports = { Navigator }; - diff --git a/ElectronClient/app/gui/NoteList.min.js b/ElectronClient/app/gui/NoteList.min.js deleted file mode 100644 index 8d7250a86..000000000 --- a/ElectronClient/app/gui/NoteList.min.js +++ /dev/null @@ -1,188 +0,0 @@ -const { ItemList } = require('./ItemList.min.js'); -const React = require('react'); -const { connect } = require('react-redux'); -const { time } = require('lib/time-utils.js'); -const { themeStyle } = require('../theme.js'); -const { _ } = require('lib/locale.js'); -const { bridge } = require('electron').remote.require('./bridge'); -const Menu = bridge().Menu; -const MenuItem = bridge().MenuItem; - -class NoteListComponent extends React.Component { - - style() { - const theme = themeStyle(this.props.theme); - - const itemHeight = 34; - - let style = { - root: { - backgroundColor: theme.backgroundColor - }, - listItem: { - height: itemHeight, - boxSizing: 'border-box', - display: 'flex', - alignItems: 'stretch', - backgroundColor: theme.backgroundColor, - borderBottom: '1px solid ' + theme.dividerColor - }, - listItemSelected: { - backgroundColor: theme.selectedColor - }, - listItemTitle: { - fontFamily: theme.fontFamily, - fontSize: theme.fontSize, - textDecoration: 'none', - color: theme.color, - cursor: 'default', - whiteSpace: 'nowrap', - flex: 1, - display: 'flex', - alignItems: 'center', - overflow: 'hidden' - }, - listItemTitleCompleted: { - opacity: 0.5, - textDecoration: 'line-through' - } - }; - - return style; - } - - itemContextMenu(event) { - const noteId = event.target.getAttribute('data-id'); - if (!noteId) throw new Error('No data-id on element'); - - const menu = new Menu(); - - menu.append(new MenuItem({ label: _('Add or remove tags'), click: async () => { - this.props.dispatch({ - type: 'WINDOW_COMMAND', - name: 'setTags', - noteId: noteId - }); - } })); - - menu.append(new MenuItem({ label: _('Switch between note and to-do'), click: async () => { - const note = await Note.load(noteId); - await Note.save(Note.toggleIsTodo(note)); - } })); - - menu.append(new MenuItem({ label: _('Delete'), click: async () => { - const ok = bridge().showConfirmMessageBox(_('Delete note?')); - if (!ok) return; - await Note.delete(noteId); - } })); - - menu.popup(bridge().window()); - } - - itemRenderer(item, theme, width) { - const onTitleClick = async (event, item) => { - this.props.dispatch({ - type: 'NOTE_SELECT', - id: item.id - }); - }; - - const onCheckboxClick = async event => { - const checked = event.target.checked; - const newNote = { - id: item.id, - todo_completed: checked ? time.unixMs() : 0 - }; - await Note.save(newNote); - }; - - const hPadding = 10; - - let style = Object.assign({ width: width }, this.style().listItem); - if (this.props.selectedNoteId === item.id) style = Object.assign(style, this.style().listItemSelected); - - // Setting marginBottom = 1 because it makes the checkbox looks more centered, at least on Windows - // but don't know how it will look in other OSes. - const checkbox = item.is_todo ? React.createElement( - 'div', - { style: { display: 'flex', height: style.height, alignItems: 'center', paddingLeft: hPadding } }, - React.createElement('input', { style: { margin: 0, marginBottom: 1 }, type: 'checkbox', defaultChecked: !!item.todo_completed, onClick: event => { - onCheckboxClick(event, item); - } }) - ) : null; - - let listItemTitleStyle = Object.assign({}, this.style().listItemTitle); - listItemTitleStyle.paddingLeft = !checkbox ? hPadding : 4; - if (item.is_todo && !!item.todo_completed) listItemTitleStyle = Object.assign(listItemTitleStyle, this.style().listItemTitleCompleted); - - // Need to include "todo_completed" in key so that checkbox is updated when - // item is changed via sync. - return React.createElement( - 'div', - { key: item.id + '_' + item.todo_completed, style: style }, - checkbox, - React.createElement( - 'a', - { - 'data-id': item.id, - className: 'list-item', - onContextMenu: event => this.itemContextMenu(event), - href: '#', - style: listItemTitleStyle, - onClick: event => { - onTitleClick(event, item); - } - }, - item.title - ) - ); - } - - render() { - const theme = themeStyle(this.props.theme); - const style = this.props.style; - - if (!this.props.notes.length) { - const padding = 10; - const emptyDivStyle = Object.assign({ - padding: padding + 'px', - fontSize: theme.fontSize, - color: theme.color, - backgroundColor: theme.backgroundColor, - fontFamily: theme.fontFamily - }, style); - emptyDivStyle.width = emptyDivStyle.width - padding * 2; - emptyDivStyle.height = emptyDivStyle.height - padding * 2; - return React.createElement( - 'div', - { style: emptyDivStyle }, - _('No notes in here. Create one by clicking on "New note".') - ); - } - - return React.createElement(ItemList, { - itemHeight: this.style().listItem.height, - style: style, - className: "note-list", - items: this.props.notes, - itemRenderer: item => { - return this.itemRenderer(item, theme, style.width); - } - }); - } - -} - -const mapStateToProps = state => { - return { - notes: state.notes, - selectedNoteId: state.selectedNoteId, - theme: state.settings.theme - // uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop, - }; -}; - -const NoteList = connect(mapStateToProps)(NoteListComponent); - -module.exports = { NoteList }; - diff --git a/ElectronClient/app/gui/NoteText.min.js b/ElectronClient/app/gui/NoteText.min.js deleted file mode 100644 index e5b8e5b80..000000000 --- a/ElectronClient/app/gui/NoteText.min.js +++ /dev/null @@ -1,525 +0,0 @@ -const React = require('react'); -const { Note } = require('lib/models/note.js'); -const { Setting } = require('lib/models/setting.js'); -const { IconButton } = require('./IconButton.min.js'); -const { connect } = require('react-redux'); -const { _ } = require('lib/locale.js'); -const { reg } = require('lib/registry.js'); -const MdToHtml = require('lib/MdToHtml'); -const shared = require('lib/components/shared/note-screen-shared.js'); -const { bridge } = require('electron').remote.require('./bridge'); -const { themeStyle } = require('../theme.js'); -const AceEditor = require('react-ace').default; -const Menu = bridge().Menu; -const MenuItem = bridge().MenuItem; -const { shim } = require('lib/shim.js'); - -require('brace/mode/markdown'); -// https://ace.c9.io/build/kitchen-sink.html -// https://highlightjs.org/static/demo/ -require('brace/theme/chrome'); - -class NoteTextComponent extends React.Component { - - constructor() { - super(); - - this.state = { - note: null, - noteMetadata: '', - showNoteMetadata: false, - folder: null, - lastSavedNote: null, - isLoading: true, - webviewReady: false, - scrollHeight: null, - editorScrollTop: 0 - }; - - this.lastLoadedNoteId_ = null; - - this.webviewListeners_ = null; - this.ignoreNextEditorScroll_ = false; - this.scheduleSaveTimeout_ = null; - this.restoreScrollTop_ = null; - - // Complicated but reliable method to get editor content height - // https://github.com/ajaxorg/ace/issues/2046 - this.editorMaxScrollTop_ = 0; - this.onAfterEditorRender_ = () => { - const r = this.editor_.editor.renderer; - this.editorMaxScrollTop_ = Math.max(0, r.layerConfig.maxHeight - r.$size.scrollerHeight); - - if (this.restoreScrollTop_ !== null) { - this.editorSetScrollTop(this.restoreScrollTop_); - this.restoreScrollTop_ = null; - } - }; - } - - mdToHtml() { - if (this.mdToHtml_) return this.mdToHtml_; - this.mdToHtml_ = new MdToHtml({ supportsResourceLinks: true }); - return this.mdToHtml_; - } - - async componentWillMount() { - let note = null; - if (this.props.noteId) { - note = await Note.load(this.props.noteId); - } - - const folder = note ? Folder.byId(this.props.folders, note.parent_id) : null; - - this.setState({ - lastSavedNote: Object.assign({}, note), - note: note, - folder: folder, - isLoading: false - }); - - this.lastLoadedNoteId_ = note ? note.id : null; - } - - componentWillUnmount() { - this.saveIfNeeded(); - - this.mdToHtml_ = null; - this.destroyWebview(); - } - - async saveIfNeeded() { - if (this.scheduleSaveTimeout_) clearTimeout(this.scheduleSaveTimeout_); - this.scheduleSaveTimeout_ = null; - if (!shared.isModified(this)) return; - await shared.saveNoteButton_press(this); - } - - async saveOneProperty(name, value) { - await shared.saveOneProperty(this, name, value); - } - - scheduleSave() { - if (this.scheduleSaveTimeout_) clearTimeout(this.scheduleSaveTimeout_); - this.scheduleSaveTimeout_ = setTimeout(() => { - this.saveIfNeeded(); - }, 500); - } - - async reloadNote(props) { - this.mdToHtml_ = null; - - const noteId = props.noteId; - this.lastLoadedNoteId_ = noteId; - const note = noteId ? await Note.load(noteId) : null; - if (noteId !== this.lastLoadedNoteId_) return; // Race condition - current note was changed while this one was loading - - // If we are loading nothing (noteId == null), make sure to - // set webviewReady to false too because the webview component - // is going to be removed in render(). - const webviewReady = this.webview_ && this.state.webviewReady && noteId; - - this.editorMaxScrollTop_ = 0; - - // HACK: To go around a bug in Ace editor, we first set the scroll position to 1 - // and then (in the renderer callback) to the value we actually need. The first - // operation helps clear the scroll position cache. See: - // https://github.com/ajaxorg/ace/issues/2195 - this.editorSetScrollTop(1); - this.restoreScrollTop_ = 0; - - this.setState({ - note: note, - lastSavedNote: Object.assign({}, note), - webviewReady: webviewReady - }); - } - - async componentWillReceiveProps(nextProps) { - if ('noteId' in nextProps && nextProps.noteId !== this.props.noteId) { - await this.reloadNote(nextProps); - } - - if ('syncStarted' in nextProps && !nextProps.syncStarted && !this.isModified()) { - await this.reloadNote(nextProps); - } - } - - isModified() { - return shared.isModified(this); - } - - refreshNoteMetadata(force = null) { - return shared.refreshNoteMetadata(this, force); - } - - title_changeText(event) { - shared.noteComponent_change(this, 'title', event.target.value); - this.scheduleSave(); - } - - toggleIsTodo_onPress() { - shared.toggleIsTodo_onPress(this); - this.scheduleSave(); - } - - showMetadata_onPress() { - shared.showMetadata_onPress(this); - } - - webview_ipcMessage(event) { - const msg = event.channel ? event.channel : ''; - const args = event.args; - const arg0 = args && args.length >= 1 ? args[0] : null; - const arg1 = args && args.length >= 2 ? args[1] : null; - - reg.logger().debug('Got ipc-message: ' + msg, args); - - if (msg.indexOf('checkboxclick:') === 0) { - // Ugly hack because setting the body here will make the scrollbar - // go to some random position. So we save the scrollTop here and it - // will be restored after the editor ref has been reset, and the - // "afterRender" event has been called. - this.restoreScrollTop_ = this.editorScrollTop(); - - const newBody = this.mdToHtml_.handleCheckboxClick(msg, this.state.note.body); - this.saveOneProperty('body', newBody); - } else if (msg.toLowerCase().indexOf('http') === 0) { - require('electron').shell.openExternal(msg); - } else if (msg === 'percentScroll') { - this.ignoreNextEditorScroll_ = true; - this.setEditorPercentScroll(arg0); - } else if (msg.indexOf('joplin://') === 0) { - const resourceId = msg.substr('joplin://'.length); - Resource.load(resourceId).then(resource => { - const filePath = Resource.fullPath(resource); - bridge().openItem(filePath); - }); - } else { - bridge().showMessageBox({ - type: 'error', - message: _('Unsupported link or message: %s', msg) - }); - } - } - - editorMaxScroll() { - return this.editorMaxScrollTop_; - } - - editorScrollTop() { - return this.editor_.editor.getSession().getScrollTop(); - } - - editorSetScrollTop(v) { - if (!this.editor_) return; - this.editor_.editor.getSession().setScrollTop(v); - } - - setEditorPercentScroll(p) { - this.editorSetScrollTop(p * this.editorMaxScroll()); - } - - setViewerPercentScroll(p) { - this.webview_.send('setPercentScroll', p); - } - - editor_scroll() { - if (this.ignoreNextEditorScroll_) { - this.ignoreNextEditorScroll_ = false; - return; - } - - const m = this.editorMaxScroll(); - this.setViewerPercentScroll(m ? this.editorScrollTop() / m : 0); - } - - webview_domReady() { - if (!this.webview_) return; - - this.setState({ - webviewReady: true - }); - - // if (Setting.value('env') === 'dev') this.webview_.openDevTools(); - } - - webview_ref(element) { - if (this.webview_) { - if (this.webview_ === element) return; - this.destroyWebview(); - } - - if (!element) { - this.destroyWebview(); - } else { - this.initWebview(element); - } - } - - editor_ref(element) { - if (this.editor_ === element) return; - - if (this.editor_) { - this.editor_.editor.renderer.off('afterRender', this.onAfterEditorRender_); - } - - this.editor_ = element; - - if (this.editor_) { - this.editor_.editor.renderer.on('afterRender', this.onAfterEditorRender_); - } - } - - initWebview(wv) { - if (!this.webviewListeners_) { - this.webviewListeners_ = { - 'dom-ready': this.webview_domReady.bind(this), - 'ipc-message': this.webview_ipcMessage.bind(this) - }; - } - - for (let n in this.webviewListeners_) { - if (!this.webviewListeners_.hasOwnProperty(n)) continue; - const fn = this.webviewListeners_[n]; - wv.addEventListener(n, fn); - } - - this.webview_ = wv; - } - - destroyWebview() { - if (!this.webview_) return; - - for (let n in this.webviewListeners_) { - if (!this.webviewListeners_.hasOwnProperty(n)) continue; - const fn = this.webviewListeners_[n]; - this.webview_.removeEventListener(n, fn); - } - - this.webview_ = null; - } - - aceEditor_change(body) { - shared.noteComponent_change(this, 'body', body); - this.scheduleSave(); - } - - itemContextMenu(event) { - const noteId = this.props.noteId; - if (!noteId) return; - - const menu = new Menu(); - - menu.append(new MenuItem({ label: _('Attach file'), click: async () => { - const filePaths = bridge().showOpenDialog({ - properties: ['openFile', 'createDirectory'] - }); - if (!filePaths || !filePaths.length) return; - - await this.saveIfNeeded(); - const note = await Note.load(noteId); - const newNote = await shim.attachFileToNote(note, filePaths[0]); - - this.setState({ - note: newNote, - lastSavedNote: Object.assign({}, newNote) - }); - } })); - - menu.popup(bridge().window()); - } - - render() { - const style = this.props.style; - const note = this.state.note; - const body = note ? note.body : ''; - const theme = themeStyle(this.props.theme); - const visiblePanes = this.props.visiblePanes || ['editor', 'viewer']; - - const borderWidth = 1; - - const rootStyle = Object.assign({ - borderLeft: borderWidth + 'px solid ' + theme.dividerColor, - boxSizing: 'border-box', - paddingLeft: 10, - paddingRight: 0 - }, style); - - const innerWidth = rootStyle.width - rootStyle.paddingLeft - rootStyle.paddingRight - borderWidth; - - if (!note) { - const emptyDivStyle = Object.assign({ - backgroundColor: 'black', - opacity: 0.1 - }, rootStyle); - return React.createElement('div', { style: emptyDivStyle }); - } - - const titleBarStyle = { - width: innerWidth - rootStyle.paddingLeft, - height: 30, - boxSizing: 'border-box', - marginTop: 10, - marginBottom: 10, - display: 'flex', - flexDirection: 'row' - }; - - const titleEditorStyle = { - display: 'flex', - flex: 1, - display: 'inline-block', - paddingTop: 5, - paddingBottom: 5, - paddingLeft: 8, - paddingRight: 8, - marginRight: rootStyle.paddingLeft - }; - - const bottomRowHeight = rootStyle.height - titleBarStyle.height - titleBarStyle.marginBottom - titleBarStyle.marginTop; - - const viewerStyle = { - width: Math.floor(innerWidth / 2), - height: bottomRowHeight, - overflow: 'hidden', - float: 'left', - verticalAlign: 'top', - boxSizing: 'border-box' - }; - - const paddingTop = 14; - - const editorStyle = { - width: innerWidth - viewerStyle.width, - height: bottomRowHeight - paddingTop, - overflowY: 'hidden', - float: 'left', - verticalAlign: 'top', - paddingTop: paddingTop + 'px', - lineHeight: theme.textAreaLineHeight + 'px', - fontSize: theme.fontSize + 'px' - }; - - if (visiblePanes.indexOf('viewer') < 0) { - // Note: setting webview.display to "none" is currently not supported due - // to this bug: https://github.com/electron/electron/issues/8277 - // So instead setting the width 0. - viewerStyle.width = 0; - editorStyle.width = innerWidth; - } - - if (visiblePanes.indexOf('editor') < 0) { - editorStyle.display = 'none'; - viewerStyle.width = innerWidth; - } - - if (visiblePanes.indexOf('viewer') >= 0 && visiblePanes.indexOf('editor') >= 0) { - viewerStyle.borderLeft = '1px solid ' + theme.dividerColor; - } else { - viewerStyle.borderLeft = 'none'; - } - - if (this.state.webviewReady) { - const mdOptions = { - onResourceLoaded: () => { - this.forceUpdate(); - }, - postMessageSyntax: 'ipcRenderer.sendToHost' - }; - const html = this.mdToHtml().render(body, theme, mdOptions); - this.webview_.send('setHtml', html); - } - - const titleEditor = React.createElement('input', { - type: 'text', - style: titleEditorStyle, - value: note ? note.title : '', - onChange: event => { - this.title_changeText(event); - } - }); - - const titleBarMenuButton = React.createElement(IconButton, { style: { - display: 'flex' - }, iconName: 'fa-caret-down', theme: this.props.theme, onClick: () => { - this.itemContextMenu(); - } }); - - const viewer = React.createElement('webview', { - style: viewerStyle, - nodeintegration: '1', - src: 'gui/note-viewer/index.html', - ref: elem => { - this.webview_ref(elem); - } - }); - - const editorRootStyle = Object.assign({}, editorStyle); - delete editorRootStyle.width; - delete editorRootStyle.height; - delete editorRootStyle.fontSize; - - const editor = React.createElement(AceEditor, { - value: body, - mode: 'markdown', - theme: 'chrome', - style: editorRootStyle, - width: editorStyle.width + 'px', - height: editorStyle.height + 'px', - fontSize: editorStyle.fontSize, - showGutter: false, - name: 'note-editor', - wrapEnabled: true, - onScroll: event => { - this.editor_scroll(); - }, - ref: elem => { - this.editor_ref(elem); - }, - onChange: body => { - this.aceEditor_change(body); - }, - showPrintMargin: false - - // Disable warning: "Automatically scrolling cursor into view after - // selection change this will be disabled in the next version set - // editor.$blockScrolling = Infinity to disable this message" - , editorProps: { $blockScrolling: true } - - // This is buggy (gets outside the container) - , highlightActiveLine: false - }); - - return React.createElement( - 'div', - { style: rootStyle }, - React.createElement( - 'div', - { style: titleBarStyle }, - titleEditor, - titleBarMenuButton - ), - editor, - viewer - ); - } - -} - -const mapStateToProps = state => { - return { - noteId: state.selectedNoteId, - folderId: state.selectedFolderId, - itemType: state.selectedItemType, - folders: state.folders, - theme: state.settings.theme, - showAdvancedOptions: state.settings.showAdvancedOptions, - syncStarted: state.syncStarted - }; -}; - -const NoteText = connect(mapStateToProps)(NoteTextComponent); - -module.exports = { NoteText }; - diff --git a/ElectronClient/app/gui/OneDriveAuthScreen.min.js b/ElectronClient/app/gui/OneDriveAuthScreen.min.js deleted file mode 100644 index b3a8e43fc..000000000 --- a/ElectronClient/app/gui/OneDriveAuthScreen.min.js +++ /dev/null @@ -1,108 +0,0 @@ -const React = require('react'); -const { connect } = require('react-redux'); -const { reg } = require('lib/registry.js'); -const { bridge } = require('electron').remote.require('./bridge'); -const { Header } = require('./Header.min.js'); -const { themeStyle } = require('../theme.js'); -const { _ } = require('lib/locale.js'); - -class OneDriveAuthScreenComponent extends React.Component { - - constructor() { - super(); - this.webview_ = null; - this.authCode_ = null; - } - - refresh_click() { - if (!this.webview_) return; - this.webview_.src = this.startUrl(); - } - - componentWillMount() { - this.setState({ - webviewUrl: this.startUrl(), - webviewReady: false - }); - } - - componentDidMount() { - this.webview_.addEventListener('dom-ready', this.webview_domReady.bind(this)); - } - - componentWillUnmount() { - this.webview_.addEventListener('dom-ready', this.webview_domReady.bind(this)); - } - - webview_domReady() { - this.setState({ webviewReady: true }); - - this.webview_.addEventListener('did-navigate', async event => { - const url = event.url; - - if (this.authCode_) return; - if (url.indexOf(this.redirectUrl() + '?code=') !== 0) return; - - let code = url.split('?code='); - this.authCode_ = code[1]; - - try { - await reg.oneDriveApi().execTokenRequest(this.authCode_, this.redirectUrl(), true); - this.props.dispatch({ type: 'NAV_BACK' }); - reg.scheduleSync(0); - } catch (error) { - bridge().showMessageBox({ - type: 'error', - message: error.message - }); - } - - this.authCode_ = null; - }); - } - - startUrl() { - return reg.oneDriveApi().authCodeUrl(this.redirectUrl()); - } - - redirectUrl() { - return reg.oneDriveApi().nativeClientRedirectUrl(); - } - - render() { - const style = this.props.style; - const theme = themeStyle(this.props.theme); - - const headerStyle = { - width: style.width - }; - - const webviewStyle = { - width: this.props.style.width, - height: this.props.style.height - theme.headerHeight, - overflow: 'hidden' - }; - - const headerButtons = [{ - title: _('Refresh'), - onClick: () => this.refresh_click() - }]; - - return React.createElement( - 'div', - null, - React.createElement(Header, { style: headerStyle, buttons: headerButtons }), - React.createElement('webview', { src: this.startUrl(), style: webviewStyle, nodeintegration: '1', ref: elem => this.webview_ = elem }) - ); - } - -} - -const mapStateToProps = state => { - return {}; -}; - -const OneDriveAuthScreen = connect(mapStateToProps)(OneDriveAuthScreenComponent); - -module.exports = { OneDriveAuthScreen }; - diff --git a/ElectronClient/app/gui/OneDriveLoginScreen.min.js b/ElectronClient/app/gui/OneDriveLoginScreen.min.js deleted file mode 100644 index b44ee7b5e..000000000 --- a/ElectronClient/app/gui/OneDriveLoginScreen.min.js +++ /dev/null @@ -1,111 +0,0 @@ -const React = require('react'); -const { connect } = require('react-redux'); -const { reg } = require('lib/registry.js'); -const { bridge } = require('electron').remote.require('./bridge'); -const { Header } = require('./Header.min.js'); -const { themeStyle } = require('../theme.js'); -const { _ } = require('lib/locale.js'); - -class OneDriveLoginScreenComponent extends React.Component { - - constructor() { - super(); - this.webview_ = null; - this.authCode_ = null; - } - - refresh_click() { - if (!this.webview_) return; - this.webview_.src = this.startUrl(); - } - - componentWillMount() { - this.setState({ - webviewUrl: this.startUrl(), - webviewReady: false - }); - } - - componentDidMount() { - this.webview_.addEventListener('dom-ready', this.webview_domReady.bind(this)); - } - - componentWillUnmount() { - this.webview_.addEventListener('dom-ready', this.webview_domReady.bind(this)); - } - - webview_domReady() { - this.setState({ webviewReady: true }); - - this.webview_.addEventListener('did-navigate', async event => { - const url = event.url; - - if (this.authCode_) return; - if (url.indexOf(this.redirectUrl() + '?code=') !== 0) return; - - let code = url.split('?code='); - this.authCode_ = code[1]; - - try { - await reg.oneDriveApi().execTokenRequest(this.authCode_, this.redirectUrl(), true); - this.props.dispatch({ type: 'NAV_BACK' }); - reg.scheduleSync(0); - } catch (error) { - bridge().showMessageBox({ - type: 'error', - message: error.message - }); - } - - this.authCode_ = null; - }); - } - - startUrl() { - return reg.oneDriveApi().authCodeUrl(this.redirectUrl()); - } - - redirectUrl() { - return reg.oneDriveApi().nativeClientRedirectUrl(); - } - - render() { - const style = this.props.style; - const theme = themeStyle(this.props.theme); - - const headerStyle = { - width: style.width - }; - - const webviewStyle = { - width: this.props.style.width, - height: this.props.style.height - theme.headerHeight, - overflow: 'hidden' - }; - - const headerButtons = [{ - title: _('Refresh'), - onClick: () => this.refresh_click(), - iconName: 'fa-refresh' - }]; - - return React.createElement( - 'div', - null, - React.createElement(Header, { style: headerStyle, buttons: headerButtons }), - React.createElement('webview', { src: this.startUrl(), style: webviewStyle, nodeintegration: '1', ref: elem => this.webview_ = elem }) - ); - } - -} - -const mapStateToProps = state => { - return { - theme: state.settings.theme - }; -}; - -const OneDriveLoginScreen = connect(mapStateToProps)(OneDriveLoginScreenComponent); - -module.exports = { OneDriveLoginScreen }; - diff --git a/ElectronClient/app/gui/PromptDialog.min.js b/ElectronClient/app/gui/PromptDialog.min.js deleted file mode 100644 index 6c8f65c46..000000000 --- a/ElectronClient/app/gui/PromptDialog.min.js +++ /dev/null @@ -1,146 +0,0 @@ -const React = require('react'); -const { connect } = require('react-redux'); -const { _ } = require('lib/locale.js'); -const { themeStyle } = require('../theme.js'); - -class PromptDialog extends React.Component { - - componentWillMount() { - this.setState({ - visible: false, - answer: this.props.value ? this.props.value : '' - }); - this.focusInput_ = true; - } - - componentWillReceiveProps(newProps) { - if ('visible' in newProps) { - this.setState({ visible: newProps.visible }); - if (newProps.visible) this.focusInput_ = true; - } - - if ('value' in newProps) { - this.setState({ answer: newProps.value }); - } - } - - componentDidUpdate() { - if (this.focusInput_ && this.answerInput_) this.answerInput_.focus(); - this.focusInput_ = false; - } - - render() { - const style = this.props.style; - const theme = themeStyle(this.props.theme); - - const modalLayerStyle = { - zIndex: 9999, - position: 'absolute', - top: 0, - left: 0, - width: style.width, - height: style.height, - backgroundColor: 'rgba(0,0,0,0.6)', - display: this.state.visible ? 'flex' : 'none', - alignItems: 'center', - justifyContent: 'center' - }; - - const promptDialogStyle = { - backgroundColor: 'white', - padding: 16, - display: 'inline-block', - boxShadow: '6px 6px 20px rgba(0,0,0,0.5)' - }; - - const buttonStyle = { - minWidth: theme.buttonMinWidth, - minHeight: theme.buttonMinHeight, - marginLeft: 5 - }; - - const labelStyle = { - marginRight: 5, - fontSize: theme.fontSize, - color: theme.color, - fontFamily: theme.fontFamily, - verticalAlign: 'top' - }; - - const inputStyle = { - width: 0.5 * style.width, - maxWidth: 400 - }; - - const descStyle = Object.assign({}, theme.textStyle, { - marginTop: 10 - }); - - const onClose = accept => { - if (this.props.onClose) this.props.onClose(accept ? this.state.answer : null); - this.setState({ visible: false, answer: '' }); - }; - - const onChange = event => { - this.setState({ answer: event.target.value }); - }; - - const onKeyDown = event => { - if (event.key === 'Enter') { - onClose(true); - } else if (event.key === 'Escape') { - onClose(false); - } - }; - - const descComp = this.props.description ? React.createElement( - 'div', - { style: descStyle }, - this.props.description - ) : null; - - return React.createElement( - 'div', - { style: modalLayerStyle }, - React.createElement( - 'div', - { style: promptDialogStyle }, - React.createElement( - 'label', - { style: labelStyle }, - this.props.label ? this.props.label : '' - ), - React.createElement( - 'div', - { style: { display: 'inline-block' } }, - React.createElement('input', { - style: inputStyle, - ref: input => this.answerInput_ = input, - value: this.state.answer, - type: 'text', - onChange: event => onChange(event), - onKeyDown: event => onKeyDown(event) }), - descComp - ), - React.createElement( - 'div', - { style: { textAlign: 'right', marginTop: 10 } }, - React.createElement( - 'button', - { style: buttonStyle, onClick: () => onClose(true) }, - 'OK' - ), - React.createElement( - 'button', - { style: buttonStyle, onClick: () => onClose(false) }, - 'Cancel' - ) - ) - ) - ); - } - -} - -module.exports = { PromptDialog }; - diff --git a/ElectronClient/app/gui/Root.min.js b/ElectronClient/app/gui/Root.min.js deleted file mode 100644 index f16ba2ff4..000000000 --- a/ElectronClient/app/gui/Root.min.js +++ /dev/null @@ -1,92 +0,0 @@ -const React = require('react'); -const { render } = require('react-dom'); -const { createStore } = require('redux'); -const { connect, Provider } = require('react-redux'); - -const { _ } = require('lib/locale.js'); -const { Setting } = require('lib/models/setting.js'); - -const { MainScreen } = require('./MainScreen.min.js'); -const { OneDriveLoginScreen } = require('./OneDriveLoginScreen.min.js'); -const { ImportScreen } = require('./ImportScreen.min.js'); -const { ConfigScreen } = require('./ConfigScreen.min.js'); -const { Navigator } = require('./Navigator.min.js'); - -const { app } = require('../app'); - -const { bridge } = require('electron').remote.require('./bridge'); - -async function initialize(dispatch) { - this.wcsTimeoutId_ = null; - - bridge().window().on('resize', function () { - if (this.wcsTimeoutId_) clearTimeout(this.wcsTimeoutId_); - - this.wcsTimeoutId_ = setTimeout(() => { - store.dispatch({ - type: 'WINDOW_CONTENT_SIZE_SET', - size: bridge().windowContentSize() - }); - this.wcsTimeoutId_ = null; - }, 10); - }); - - store.dispatch({ - type: 'NOTE_VISIBLE_PANES_SET', - panes: Setting.value('noteVisiblePanes') - }); -} - -class RootComponent extends React.Component { - - async componentDidMount() { - if (this.props.appState == 'starting') { - this.props.dispatch({ - type: 'APP_STATE_SET', - state: 'initializing' - }); - - await initialize(this.props.dispatch); - - this.props.dispatch({ - type: 'APP_STATE_SET', - state: 'ready' - }); - } - } - - render() { - const navigatorStyle = { - width: this.props.size.width, - height: this.props.size.height - }; - - const screens = { - Main: { screen: MainScreen }, - OneDriveLogin: { screen: OneDriveLoginScreen, title: () => _('OneDrive Login') }, - Import: { screen: ImportScreen, title: () => _('Import') }, - Config: { screen: ConfigScreen, title: () => _('Configuration') } - }; - - return React.createElement(Navigator, { style: navigatorStyle, screens: screens }); - } - -} - -const mapStateToProps = state => { - return { - size: state.windowContentSize, - appState: state.appState - }; -}; - -const Root = connect(mapStateToProps)(RootComponent); - -const store = app().store(); - -render(React.createElement( - Provider, - { store: store }, - React.createElement(Root, null) -), document.getElementById('react-root')); - diff --git a/ElectronClient/app/gui/SideBar.min.js b/ElectronClient/app/gui/SideBar.min.js deleted file mode 100644 index 8b8ad0ed1..000000000 --- a/ElectronClient/app/gui/SideBar.min.js +++ /dev/null @@ -1,262 +0,0 @@ -const React = require('react'); -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/base-model.js'); -const { Folder } = require('lib/models/folder.js'); -const { Tag } = require('lib/models/tag.js'); -const { _ } = require('lib/locale.js'); -const { themeStyle } = require('../theme.js'); -const { bridge } = require('electron').remote.require('./bridge'); -const Menu = bridge().Menu; -const MenuItem = bridge().MenuItem; - -class SideBarComponent extends React.Component { - - style() { - const theme = themeStyle(this.props.theme); - - const itemHeight = 25; - - let style = { - root: { - backgroundColor: theme.backgroundColor2 - }, - listItem: { - height: itemHeight, - fontFamily: theme.fontFamily, - fontSize: theme.fontSize, - textDecoration: 'none', - boxSizing: 'border-box', - color: theme.color2, - paddingLeft: 14, - display: 'flex', - alignItems: 'center', - cursor: 'default', - opacity: 0.8 - }, - listItemSelected: { - backgroundColor: theme.selectedColor2 - }, - conflictFolder: { - color: theme.colorError2, - fontWeight: 'bold' - }, - header: { - height: itemHeight * 1.8, - fontFamily: theme.fontFamily, - fontSize: theme.fontSize * 1.3, - textDecoration: 'none', - boxSizing: 'border-box', - color: theme.color2, - paddingLeft: 8, - display: 'flex', - alignItems: 'center' - }, - button: { - padding: 6, - fontFamily: theme.fontFamily, - fontSize: theme.fontSize, - textDecoration: 'none', - boxSizing: 'border-box', - color: theme.color2, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - border: "1px solid rgba(255,255,255,0.2)", - marginTop: 10, - marginLeft: 5, - marginRight: 5, - cursor: 'default' - }, - syncReport: { - fontFamily: theme.fontFamily, - fontSize: Math.round(theme.fontSize * .9), - color: theme.color2, - opacity: .5, - display: 'flex', - alignItems: 'left', - justifyContent: 'top', - flexDirection: 'column', - marginTop: 10, - marginLeft: 5, - marginRight: 5, - minHeight: 70 - } - }; - - return style; - } - - itemContextMenu(event) { - const itemId = event.target.getAttribute('data-id'); - if (itemId === Folder.conflictFolderId()) return; - - const itemType = Number(event.target.getAttribute('data-type')); - if (!itemId || !itemType) throw new Error('No data on element'); - - let deleteMessage = ''; - if (itemType === BaseModel.TYPE_FOLDER) { - deleteMessage = _('Delete notebook?'); - } else if (itemType === BaseModel.TYPE_TAG) { - deleteMessage = _('Remove this tag from all the notes?'); - } - - const menu = new Menu(); - - menu.append(new MenuItem({ label: _('Delete'), click: async () => { - const ok = bridge().showConfirmMessageBox(deleteMessage); - if (!ok) return; - - if (itemType === BaseModel.TYPE_FOLDER) { - await Folder.delete(itemId); - } else if (itemType === BaseModel.TYPE_TAG) { - await Tag.untagAll(itemId); - } - } })); - - menu.popup(bridge().window()); - } - - folderItem_click(folder) { - this.props.dispatch({ - type: 'FOLDER_SELECT', - id: folder ? folder.id : null - }); - } - - tagItem_click(tag) { - this.props.dispatch({ - type: 'TAG_SELECT', - id: tag ? tag.id : null - }); - } - - async sync_click() { - await shared.synchronize_press(this); - } - - folderItem(folder, selected) { - let style = Object.assign({}, this.style().listItem); - if (selected) style = Object.assign(style, this.style().listItemSelected); - if (folder.id === Folder.conflictFolderId()) style = Object.assign(style, this.style().conflictFolder); - return React.createElement( - 'a', - { className: 'list-item', href: '#', 'data-id': folder.id, 'data-type': BaseModel.TYPE_FOLDER, onContextMenu: event => this.itemContextMenu(event), key: folder.id, style: style, onClick: () => { - this.folderItem_click(folder); - } }, - folder.title - ); - } - - tagItem(tag, selected) { - let style = Object.assign({}, this.style().listItem); - if (selected) style = Object.assign(style, this.style().listItemSelected); - return React.createElement( - 'a', - { className: 'list-item', href: '#', 'data-id': tag.id, 'data-type': BaseModel.TYPE_TAG, onContextMenu: event => this.itemContextMenu(event), key: tag.id, style: style, onClick: () => { - this.tagItem_click(tag); - } }, - tag.title - ); - } - - makeDivider(key) { - return React.createElement('div', { style: { height: 2, backgroundColor: 'blue' }, key: key }); - } - - makeHeader(key, label, iconName) { - const style = this.style().header; - const icon = React.createElement('i', { style: { fontSize: style.fontSize * 1.2, marginRight: 5 }, className: "fa " + iconName }); - return React.createElement( - 'div', - { style: style, key: key }, - icon, - label - ); - } - - synchronizeButton(label) { - const style = this.style().button; - return React.createElement( - 'a', - { className: 'synchronize-button', style: style, href: '#', key: 'sync_button', onClick: () => { - this.sync_click(); - } }, - label - ); - } - - render() { - const theme = themeStyle(this.props.theme); - const style = Object.assign({}, this.style().root, this.props.style, { - overflowX: 'hidden', - overflowY: 'auto' - }); - - let items = []; - - items.push(this.makeHeader('folderHeader', _('Notebooks'), 'fa-folder-o')); - - if (this.props.folders.length) { - const folderItems = shared.renderFolders(this.props, this.folderItem.bind(this)); - items = items.concat(folderItems); - } - - items.push(this.makeHeader('tagHeader', _('Tags'), 'fa-tags')); - - if (this.props.tags.length) { - const tagItems = shared.renderTags(this.props, this.tagItem.bind(this)); - - items.push(React.createElement( - 'div', - { className: 'tags', key: 'tag_items' }, - tagItems - )); - } - - let lines = Synchronizer.reportToLines(this.props.syncReport); - const syncReportText = []; - for (let i = 0; i < lines.length; i++) { - syncReportText.push(React.createElement( - 'div', - { key: i }, - lines[i] - )); - } - - items.push(this.synchronizeButton(this.props.syncStarted ? _('Cancel') : _('Synchronise'))); - - items.push(React.createElement( - 'div', - { style: this.style().syncReport, key: 'sync_report' }, - syncReportText - )); - - return React.createElement( - 'div', - { className: 'side-bar', style: style }, - items - ); - } - -} - -const mapStateToProps = state => { - return { - folders: state.folders, - tags: state.tags, - syncStarted: state.syncStarted, - syncReport: state.syncReport, - selectedFolderId: state.selectedFolderId, - selectedTagId: state.selectedTagId, - notesParentType: state.notesParentType, - locale: state.settings.locale, - theme: state.settings.theme - }; -}; - -const SideBar = connect(mapStateToProps)(SideBarComponent); - -module.exports = { SideBar }; - diff --git a/ElectronClient/app/package.json b/ElectronClient/app/package.json index a5b55aca6..df550737f 100644 --- a/ElectronClient/app/package.json +++ b/ElectronClient/app/package.json @@ -7,7 +7,9 @@ "test": "echo \"Error: no test specified\" && exit 1", "pack": "node_modules/.bin/electron-builder --dir", "dist": "node_modules/.bin/electron-builder", - "publish": "build -p always" + "publish": "build -p always", + "postinstall": "node compile-jsx.js", + "compile": "node compile-jsx.js" }, "repository": { "type": "git",