diff --git a/ElectronClient/app/ElectronAppWrapper.js b/ElectronClient/app/ElectronAppWrapper.js
index 0e884a520..0e2c1e1c1 100644
--- a/ElectronClient/app/ElectronAppWrapper.js
+++ b/ElectronClient/app/ElectronAppWrapper.js
@@ -2,6 +2,7 @@ const { _ } = require('lib/locale.js');
const { BrowserWindow } = require('electron');
const url = require('url')
const path = require('path')
+const urlUtils = require('lib/urlUtils.js');
class ElectronAppWrapper {
@@ -27,20 +28,6 @@ class ElectronAppWrapper {
return this.win_;
}
- // store() {
- // return this.store_;
- // }
-
- // dispatch(action) {
- // return this.store().dispatch(action);
- // }
-
- // windowContentSize() {
- // if (!this.win_) return { width: 0, height: 0 };
- // const s = this.win_.getContentSize();
- // return { width: s[0], height: s[1] };
- // }
-
createWindow() {
this.win_ = new BrowserWindow({width: 800, height: 600})
@@ -55,18 +42,6 @@ class ElectronAppWrapper {
this.win_.on('closed', () => {
this.win_ = null
})
-
- this.win_.on('resize', () => {
- // this.dispatch({
- // type: 'WINDOW_CONTENT_SIZE_SET',
- // size: this.windowContentSize(),
- // });
- });
-
- // this.dispatch({
- // type: 'WINDOW_CONTENT_SIZE_SET',
- // size: this.windowContentSize(),
- // });
}
async waitForElectronAppReady() {
diff --git a/ElectronClient/app/bridge.js b/ElectronClient/app/bridge.js
index 1bdb4ed47..c29709950 100644
--- a/ElectronClient/app/bridge.js
+++ b/ElectronClient/app/bridge.js
@@ -1,3 +1,5 @@
+const { _ } = require('lib/locale.js');
+
class Bridge {
constructor(electronWrapper) {
@@ -23,6 +25,30 @@ class Bridge {
return dialog.showMessageBox(options);
}
+ showErrorMessageBox(message) {
+ return this.showMessageBox({
+ type: 'error',
+ message: message,
+ });
+ }
+
+ showConfirmMessageBox(message) {
+ const result = this.showMessageBox({
+ type: 'question',
+ message: message,
+ buttons: [_('OK'), _('Cancel')],
+ });
+ return result === 0;
+ }
+
+ get Menu() {
+ return require('electron').Menu;
+ }
+
+ get MenuItem() {
+ return require('electron').MenuItem;
+ }
+
}
let bridge_ = null;
diff --git a/ElectronClient/app/gui/Header.jsx b/ElectronClient/app/gui/Header.jsx
index adf33f80f..a170a8000 100644
--- a/ElectronClient/app/gui/Header.jsx
+++ b/ElectronClient/app/gui/Header.jsx
@@ -11,7 +11,6 @@ class HeaderComponent extends React.Component {
}
makeButton(key, options) {
- console.info(key, options);
return {options.onClick()}}>{options.title}
}
@@ -42,7 +41,7 @@ class HeaderComponent extends React.Component {
}
const mapStateToProps = (state) => {
- return { theme: state.theme };
+ return { theme: state.settings.theme };
};
const Header = connect(mapStateToProps)(HeaderComponent);
diff --git a/ElectronClient/app/gui/MainScreen.jsx b/ElectronClient/app/gui/MainScreen.jsx
index 7d602500a..5e7e25ee2 100644
--- a/ElectronClient/app/gui/MainScreen.jsx
+++ b/ElectronClient/app/gui/MainScreen.jsx
@@ -4,11 +4,20 @@ 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 { Note } = require('lib/models/note.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({ newNotePromptVisible: false });
+ }
+
render() {
const style = this.props.style;
const theme = themeStyle(this.props.theme);
@@ -40,9 +49,40 @@ class MainScreenComponent extends React.Component {
verticalAlign: 'top',
};
+ const promptStyle = {
+ width: style.width,
+ height: style.height,
+ };
+
+ const headerButtons = [
+ {
+ title: _('New note'),
+ onClick: () => {
+ this.setState({ newNotePromptVisible: true });
+ },
+ },
+ ];
+
+ const newNotePromptOnAccept = async (answer) => {
+ const folderId = Setting.value('activeFolderId');
+ if (!folderId) return;
+
+ const note = await Note.save({
+ title: answer,
+ parent_id: folderId,
+ });
+ Note.updateGeolocation(note.id);
+
+ this.props.dispatch({
+ type: 'NOTES_SELECT',
+ noteId: note.id,
+ });
+ }
+
return (
-
+
newNotePromptOnAccept(answer)} message={_('Note title:')} visible={this.state.newNotePromptVisible}/>
+
diff --git a/ElectronClient/app/gui/NoteList.jsx b/ElectronClient/app/gui/NoteList.jsx
index d67aaaa82..8b836d988 100644
--- a/ElectronClient/app/gui/NoteList.jsx
+++ b/ElectronClient/app/gui/NoteList.jsx
@@ -1,10 +1,28 @@
const { ItemList } = require('./ItemList.min.js');
const React = require('react');
const { connect } = require('react-redux');
+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 {
- itemRenderer(index, item) {
+ 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: _('Delete'), async click() {
+ const ok = bridge().showConfirmMessageBox(_('Delete note?'));
+ if (!ok) return;
+ await Note.delete(noteId);
+ }}))
+ menu.popup(bridge().window());
+ }
+
+ itemRenderer(index, item, theme) {
const onClick = (item) => {
this.props.dispatch({
type: 'NOTES_SELECT',
@@ -12,20 +30,27 @@ class NoteListComponent extends React.Component {
});
}
- let classes = ['item'];
- classes.push(index % 2 === 0 ? 'even' : 'odd');
- if (this.props.selectedNoteId === item.id) classes.push('selected');
- return { onClick(item) }} className={classes.join(' ')} key={index}>{item.title + ' ' + item.id.substr(0,4)}
+ const style = {
+ height: this.props.itemHeight,
+ display: 'block',
+ cursor: 'pointer',
+ backgroundColor: index % 2 === 0 ? theme.backgroundColor : theme.oddBackgroundColor,
+ fontWeight: this.props.selectedNoteId === item.id ? 'bold' : 'normal',
+ };
+
+ return this.itemContextMenu(event)} href="#" style={style} onClick={() => { onClick(item) }} key={index}>{item.title}
}
render() {
+ const theme = themeStyle(this.props.theme);
+
return (
{ return this.itemRenderer(index, item) } }
+ itemRenderer={ (index, item) => { return this.itemRenderer(index, item, theme) } }
>
);
}
@@ -36,6 +61,7 @@ const mapStateToProps = (state) => {
return {
notes: state.notes,
selectedNoteId: state.selectedNoteId,
+ theme: state.settings.theme,
};
};
diff --git a/ElectronClient/app/gui/NoteText.jsx b/ElectronClient/app/gui/NoteText.jsx
index e036d22a0..958e2234c 100644
--- a/ElectronClient/app/gui/NoteText.jsx
+++ b/ElectronClient/app/gui/NoteText.jsx
@@ -6,6 +6,7 @@ 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');
class NoteTextComponent extends React.Component {
@@ -55,6 +56,10 @@ class NoteTextComponent extends React.Component {
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(() => {
@@ -73,6 +78,7 @@ class NoteTextComponent extends React.Component {
this.setState({
note: note,
+ lastSavedNote: Object.assign({}, note),
mode: 'view',
});
}
@@ -164,7 +170,7 @@ class NoteTextComponent extends React.Component {
webviewReady: true,
});
- this.webview_.openDevTools();
+ //this.webview_.openDevTools();
}
webview_ref(element) {
@@ -218,6 +224,7 @@ class NoteTextComponent extends React.Component {
const style = this.props.style;
const note = this.state.note;
const body = note ? note.body : '';
+ const theme = themeStyle(this.props.theme);
const viewerStyle = {
width: Math.floor(style.width / 2),
@@ -227,12 +234,17 @@ class NoteTextComponent extends React.Component {
verticalAlign: 'top',
};
+ const paddingTop = 14;
+
const editorStyle = {
width: style.width - viewerStyle.width,
- height: style.height,
+ height: style.height - paddingTop,
overflowY: 'scroll',
float: 'left',
verticalAlign: 'top',
+ paddingTop: paddingTop + 'px',
+ lineHeight: theme.textAreaLineHeight + 'px',
+ fontSize: theme.fontSize + 'px',
};
if (this.state.webviewReady) {
@@ -242,7 +254,7 @@ class NoteTextComponent extends React.Component {
},
postMessageSyntax: 'ipcRenderer.sendToHost',
};
- const html = this.mdToHtml().render(body, {}, mdOptions);
+ const html = this.mdToHtml().render(body, theme, mdOptions);
this.webview_.send('setHtml', html);
}
diff --git a/ElectronClient/app/gui/PromptDialog.jsx b/ElectronClient/app/gui/PromptDialog.jsx
new file mode 100644
index 000000000..0612437bc
--- /dev/null
+++ b/ElectronClient/app/gui/PromptDialog.jsx
@@ -0,0 +1,107 @@
+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.focusInput_ = true;
+ }
+
+ componentWillReceiveProps(newProps) {
+ if ('visible' in newProps) {
+ this.setState({ visible: newProps.visible });
+ if (newProps.visible) this.focusInput_ = true;
+ }
+ }
+
+ 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: 10,
+ display: 'inline-block',
+ boxShadow: '6px 6px 20px rgba(0,0,0,0.5)',
+ };
+
+ const buttonStyle = {
+ minWidth: theme.buttonMinWidth,
+ minHeight: theme.buttonMinHeight,
+ marginLeft: 5,
+ };
+
+ const inputStyle = {
+ width: 0.5 * style.width,
+ maxWidth: 400,
+ };
+
+ const onAccept = () => {
+ if (this.props.onAccept) this.props.onAccept(this.state.answer);
+ this.setState({ visible: false, answer: '' });
+ }
+
+ const onReject = () => {
+ if (this.props.onReject) this.props.onReject();
+ this.setState({ visible: false, answer: '' });
+ }
+
+ const onChange = (event) => {
+ this.setState({ answer: event.target.value });
+ }
+
+ const onKeyDown = (event) => {
+ if (event.key === 'Enter') {
+ onAccept();
+ } else if (event.key === 'Escape') {
+ onReject();
+ }
+ }
+
+ return (
+
+
+
+
this.answerInput_ = input}
+ value={this.state.answer}
+ type="text"
+ onChange={(event) => onChange(event)}
+ onKeyDown={(event) => onKeyDown(event)} />
+
+
+
+
+
+
+ );
+ }
+
+}
+
+module.exports = { PromptDialog };
\ No newline at end of file
diff --git a/ElectronClient/app/gui/SideBar.jsx b/ElectronClient/app/gui/SideBar.jsx
index 9f465b0e1..408c981c6 100644
--- a/ElectronClient/app/gui/SideBar.jsx
+++ b/ElectronClient/app/gui/SideBar.jsx
@@ -2,9 +2,27 @@ 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 { themeStyle } = require('../theme.js');
class SideBarComponent extends React.Component {
+ style() {
+ const theme = themeStyle(this.props.theme);
+
+ const itemHeight = 20;
+
+ let style = {
+ root: {},
+ listItem: {
+ display: 'block',
+ cursor: 'pointer',
+ height: itemHeight,
+ },
+ };
+
+ return style;
+ }
+
folderItem_click(folder) {
this.props.dispatch({
type: 'FOLDERS_SELECT',
@@ -24,15 +42,17 @@ class SideBarComponent extends React.Component {
}
folderItem(folder, selected) {
- let classes = [];
- if (selected) classes.push('selected');
- return {this.folderItem_click(folder)}}>{folder.title}
+ const style = Object.assign({}, this.style().listItem, {
+ fontWeight: selected ? 'bold' : 'normal',
+ });
+ return {this.folderItem_click(folder)}}>{folder.title}
}
tagItem(tag, selected) {
- let classes = [];
- if (selected) classes.push('selected');
- return {this.tagItem_click(tag)}}>Tag: {tag.title}
+ const style = Object.assign({}, this.style().listItem, {
+ fontWeight: selected ? 'bold' : 'normal',
+ });
+ return {this.tagItem_click(tag)}}>Tag: {tag.title}
}
makeDivider(key) {
@@ -44,6 +64,9 @@ class SideBarComponent extends React.Component {
}
render() {
+ const theme = themeStyle(this.props.theme);
+ const style = Object.assign({}, this.style().root, this.props.style);
+
let items = [];
if (this.props.folders.length) {
@@ -69,7 +92,7 @@ class SideBarComponent extends React.Component {
items.push({syncReportText}
);
return (
-
+
{items}
);
diff --git a/ElectronClient/app/index.html b/ElectronClient/app/index.html
index 1db1dd121..2b3275949 100644
--- a/ElectronClient/app/index.html
+++ b/ElectronClient/app/index.html
@@ -2,12 +2,11 @@
-
Hello World!
+
Joplin
-
-
+