1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Removing need for including build files

This commit is contained in:
Laurent Cozic 2017-11-14 23:01:19 +00:00
parent c838548831
commit 11ad6c6bef
17 changed files with 43 additions and 2310 deletions

View File

@ -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);
}
}
});

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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'));

View File

@ -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 };

View File

@ -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",