1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-24 08:12:24 +02:00
joplin/ElectronClient/app/app.js

528 lines
14 KiB
JavaScript
Raw Normal View History

require('app-module-path').addPath(__dirname);
const { BaseApplication } = require('lib/BaseApplication');
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
2017-12-14 20:12:14 +02:00
const Setting = require('lib/models/Setting.js');
2017-11-14 20:02:58 +02:00
const { shim } = require('lib/shim.js');
2017-12-14 20:12:14 +02:00
const BaseModel = require('lib/BaseModel.js');
const MasterKey = require('lib/models/MasterKey');
2017-11-12 02:44:26 +02:00
const { _, setLocale } = require('lib/locale.js');
const os = require('os');
const fs = require('fs-extra');
2017-12-14 20:12:14 +02:00
const Tag = require('lib/models/Tag.js');
const { reg } = require('lib/registry.js');
const { sprintf } = require('sprintf-js');
const { JoplinDatabase } = require('lib/joplin-database.js');
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
const { ElectronAppWrapper } = require('./ElectronAppWrapper');
const { defaultState } = require('lib/reducer.js');
2017-11-28 21:53:29 +02:00
const packageInfo = require('./packageInfo.js');
const AlarmService = require('lib/services/AlarmService.js');
const AlarmServiceDriverNode = require('lib/services/AlarmServiceDriverNode');
2017-01-29 20:29:34 +02:00
const DecryptionWorker = require('lib/services/DecryptionWorker');
2017-11-11 14:00:37 +02:00
const { bridge } = require('electron').remote.require('./bridge');
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
const appDefaultState = Object.assign({}, defaultState, {
route: {
type: 'NAV_GO',
routeName: 'Main',
2017-11-11 19:36:47 +02:00
props: {},
},
navHistory: [],
2017-11-11 19:36:47 +02:00
fileToImport: null,
windowCommand: null,
noteVisiblePanes: ['editor', 'viewer'],
2017-11-14 20:02:58 +02:00
windowContentSize: bridge().windowContentSize(),
2017-11-14 01:04:27 +02:00
});
class Application extends BaseApplication {
2017-11-11 19:36:47 +02:00
constructor() {
super();
this.lastMenuScreen_ = null;
}
hasGui() {
return true;
}
2018-01-31 21:34:38 +02:00
checkForUpdateLoggerPath() {
return Setting.value('profileDir') + '/log-autoupdater.txt';
}
reducer(state = appDefaultState, action) {
let newState = state;
try {
switch (action.type) {
case 'NAV_BACK':
case 'NAV_GO':
const goingBack = action.type === 'NAV_BACK';
if (goingBack && !state.navHistory.length) break;
const currentRoute = state.route;
newState = Object.assign({}, state);
let newNavHistory = state.navHistory.slice();
if (goingBack) {
let newAction = null;
while (newNavHistory.length) {
newAction = newNavHistory.pop();
if (newAction.routeName !== state.route.routeName) break;
}
if (!newAction) break;
action = newAction;
}
if (!goingBack) newNavHistory.push(currentRoute);
newState.navHistory = newNavHistory
newState.route = action;
break;
case 'WINDOW_CONTENT_SIZE_SET':
newState = Object.assign({}, state);
newState.windowContentSize = action.size;
break;
2017-11-11 19:36:47 +02:00
case 'WINDOW_COMMAND':
newState = Object.assign({}, state);
2017-11-12 01:13:14 +02:00
let command = Object.assign({}, action);
delete command.type;
newState.windowCommand = command;
2017-11-11 19:36:47 +02:00
break;
case 'NOTE_VISIBLE_PANES_TOGGLE':
let panes = state.noteVisiblePanes.slice();
if (panes.length === 2) {
panes = ['editor'];
} else if (panes.indexOf('editor') >= 0) {
panes = ['viewer'];
} else if (panes.indexOf('viewer') >= 0) {
panes = ['editor', 'viewer'];
} else {
panes = ['editor', 'viewer'];
}
newState = Object.assign({}, state);
newState.noteVisiblePanes = panes;
break;
case 'NOTE_VISIBLE_PANES_SET':
newState = Object.assign({}, state);
newState.noteVisiblePanes = action.panes;
break;
}
} catch (error) {
error.message = 'In reducer: ' + error.message + ' Action: ' + JSON.stringify(action);
throw error;
}
return super.reducer(newState, action);
}
async generalMiddleware(store, next, action) {
2017-11-12 02:44:26 +02:00
if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'locale' || action.type == 'SETTING_UPDATE_ALL') {
setLocale(Setting.value('locale'));
this.refreshMenu();
}
2018-01-31 22:10:32 +02:00
if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'showTrayIcon' || action.type == 'SETTING_UPDATE_ALL') {
this.updateTray();
}
if (['NOTE_UPDATE_ONE', 'NOTE_DELETE', 'FOLDER_UPDATE_ONE', 'FOLDER_DELETE'].indexOf(action.type) >= 0) {
if (!await reg.syncTarget().syncStarted()) reg.scheduleSync();
}
if (['EVENT_NOTE_ALARM_FIELD_CHANGE', 'NOTE_DELETE'].indexOf(action.type) >= 0) {
await AlarmService.updateNoteNotification(action.id, action.type === 'NOTE_DELETE');
}
2017-11-11 19:36:47 +02:00
const result = await super.generalMiddleware(store, next, action);
const newState = store.getState();
if (action.type === 'NAV_GO' || action.type === 'NAV_BACK') {
app().updateMenu(newState.route.routeName);
}
if (['NOTE_VISIBLE_PANES_TOGGLE', 'NOTE_VISIBLE_PANES_SET'].indexOf(action.type) >= 0) {
Setting.setValue('noteVisiblePanes', newState.noteVisiblePanes);
}
2017-11-11 19:36:47 +02:00
return result;
2017-11-12 02:44:26 +02:00
}
2017-11-11 19:36:47 +02:00
2017-11-12 02:44:26 +02:00
refreshMenu() {
const screen = this.lastMenuScreen_;
this.lastMenuScreen_ = null;
this.updateMenu(screen);
}
2017-11-11 19:36:47 +02:00
updateMenu(screen) {
if (this.lastMenuScreen_ === screen) return;
const sortNoteItems = [];
const sortNoteOptions = Setting.enumOptions('notes.sortOrder.field');
for (let field in sortNoteOptions) {
if (!sortNoteOptions.hasOwnProperty(field)) continue;
sortNoteItems.push({
label: sortNoteOptions[field],
screens: ['Main'],
type: 'checkbox',
checked: Setting.value('notes.sortOrder.field') === field,
click: () => {
Setting.setValue('notes.sortOrder.field', field);
this.refreshMenu();
}
});
}
2017-11-11 14:00:37 +02:00
const template = [
{
2017-11-12 02:44:26 +02:00
label: _('File'),
2017-11-11 14:00:37 +02:00
submenu: [{
2017-11-11 19:36:47 +02:00
label: _('New note'),
accelerator: 'CommandOrControl+N',
screens: ['Main'],
click: () => {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'newNote',
});
}
}, {
label: _('New to-do'),
accelerator: 'CommandOrControl+T',
screens: ['Main'],
click: () => {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'newTodo',
});
}
}, {
label: _('New notebook'),
accelerator: 'CommandOrControl+B',
2017-11-11 19:36:47 +02:00
screens: ['Main'],
click: () => {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'newNotebook',
});
}
}, {
type: 'separator',
}, {
2017-11-11 14:00:37 +02:00
label: _('Import Evernote notes'),
2017-11-11 19:36:47 +02:00
click: () => {
const filePaths = bridge().showOpenDialog({
properties: ['openFile', 'createDirectory'],
filters: [
{ name: _('Evernote Export Files'), extensions: ['enex'] },
]
});
if (!filePaths || !filePaths.length) return;
this.dispatch({
type: 'NAV_GO',
routeName: 'Import',
props: {
filePath: filePaths[0],
},
});
}
}, {
type: 'separator',
platforms: ['darwin'],
}, {
label: _('Hide %s', 'Joplin'),
platforms: ['darwin'],
accelerator: 'CommandOrControl+H',
click: () => { bridge().window().hide() }
2017-11-11 19:36:47 +02:00
}, {
type: 'separator',
2017-11-11 14:00:37 +02:00
}, {
label: _('Quit'),
accelerator: 'CommandOrControl+Q',
click: () => { bridge().electronApp().exit() }
}]
2017-11-17 20:57:27 +02:00
}, {
label: _('Edit'),
submenu: [{
label: _('Copy'),
screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
2017-11-17 20:57:27 +02:00
role: 'copy',
accelerator: 'CommandOrControl+C',
}, {
label: _('Cut'),
screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
role: 'cut',
2017-11-17 20:57:27 +02:00
accelerator: 'CommandOrControl+X',
}, {
label: _('Paste'),
screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
role: 'paste',
2017-11-17 20:57:27 +02:00
accelerator: 'CommandOrControl+V',
}, {
type: 'separator',
screens: ['Main'],
2017-11-17 20:57:27 +02:00
}, {
label: _('Search in all the notes'),
screens: ['Main'],
accelerator: 'F6',
click: () => {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'search',
});
},
}],
}, {
label: _('View'),
submenu: [{
label: _('Toggle editor layout'),
screens: ['Main'],
accelerator: 'CommandOrControl+L',
click: () => {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'toggleVisiblePanes',
});
}
}, {
type: 'separator',
screens: ['Main'],
}, {
label: Setting.settingMetadata('notes.sortOrder.field').label(),
screens: ['Main'],
submenu: sortNoteItems,
}, {
label: Setting.settingMetadata('notes.sortOrder.reverse').label(),
type: 'checkbox',
checked: Setting.setValue('notes.sortOrder.reverse'),
screens: ['Main'],
click: () => {
Setting.setValue('notes.sortOrder.reverse', !Setting.value('notes.sortOrder.reverse'));
},
}, {
label: Setting.settingMetadata('uncompletedTodosOnTop').label(),
type: 'checkbox',
checked: Setting.setValue('uncompletedTodosOnTop'),
screens: ['Main'],
click: () => {
Setting.setValue('uncompletedTodosOnTop', !Setting.value('uncompletedTodosOnTop'));
},
}],
2017-11-11 14:00:37 +02:00
}, {
2017-11-12 02:44:26 +02:00
label: _('Tools'),
submenu: [{
2017-12-05 20:56:39 +02:00
label: _('Synchronisation status'),
click: () => {
this.dispatch({
type: 'NAV_GO',
routeName: 'Status',
});
}
}, {
type: 'separator',
screens: ['Main'],
2017-12-05 20:56:39 +02:00
},{
label: _('Encryption options'),
2017-11-12 02:44:26 +02:00
click: () => {
this.dispatch({
type: 'NAV_GO',
routeName: 'EncryptionConfig',
2017-11-12 02:44:26 +02:00
});
}
},{
label: _('General Options'),
accelerator: 'CommandOrControl+,',
click: () => {
this.dispatch({
type: 'NAV_GO',
routeName: 'Config',
});
}
}],
2017-11-12 02:44:26 +02:00
}, {
label: _('Help'),
2017-11-11 14:00:37 +02:00
submenu: [{
2017-11-14 20:02:58 +02:00
label: _('Website and documentation'),
2017-11-11 14:00:37 +02:00
accelerator: 'F1',
click () { bridge().openExternal('http://joplin.cozic.net') }
}, {
label: _('Check for updates...'),
click: () => {
bridge().checkForUpdates(false, bridge().window(), this.checkForUpdateLoggerPath());
}
2017-11-11 14:00:37 +02:00
}, {
label: _('About Joplin'),
2017-11-12 02:44:26 +02:00
click: () => {
2017-11-28 21:53:29 +02:00
const p = packageInfo;
2017-11-12 02:44:26 +02:00
let message = [
p.description,
'',
2018-01-03 21:20:16 +02:00
'Copyright © 2016-2018 Laurent Cozic',
2017-11-14 20:02:58 +02:00
_('%s %s (%s, %s)', p.name, p.version, Setting.value('env'), process.platform),
2017-11-12 02:44:26 +02:00
];
bridge().showMessageBox({
message: message.join('\n'),
});
}
2017-11-11 14:00:37 +02:00
}]
},
2017-11-11 19:36:47 +02:00
];
function isEmptyMenu(template) {
for (let i = 0; i < template.length; i++) {
const t = template[i];
if (t.type !== 'separator') return false;
}
return true;
}
2017-11-11 19:36:47 +02:00
function removeUnwantedItems(template, screen) {
const platform = shim.platformName();
2017-11-11 19:36:47 +02:00
let output = [];
for (let i = 0; i < template.length; i++) {
const t = Object.assign({}, template[i]);
if (t.screens && t.screens.indexOf(screen) < 0) continue;
if (t.platforms && t.platforms.indexOf(platform) < 0) continue;
2017-11-11 19:36:47 +02:00
if (t.submenu) t.submenu = removeUnwantedItems(t.submenu, screen);
if (('submenu' in t) && isEmptyMenu(t.submenu)) continue;
2017-11-11 19:36:47 +02:00
output.push(t);
}
return output;
}
let screenTemplate = removeUnwantedItems(template, screen);
const menu = Menu.buildFromTemplate(screenTemplate);
Menu.setApplicationMenu(menu);
2017-11-11 14:00:37 +02:00
2017-11-11 19:36:47 +02:00
this.lastMenuScreen_ = screen;
2017-11-11 14:00:37 +02:00
}
2018-01-31 22:10:32 +02:00
updateTray() {
// Tray icon (called AppIndicator) doesn't work in Ubuntu
// http://www.webupd8.org/2017/04/fix-appindicator-not-working-for.html
// Might be fixed in Electron 18.x but no non-beta release yet.
if (!shim.isWindows() && !shim.isMac()) return;
2018-01-31 22:10:32 +02:00
const app = bridge().electronApp();
if (app.trayShown() === Setting.value('showTrayIcon')) return;
if (!Setting.value('showTrayIcon')) {
app.destroyTray();
} else {
const contextMenu = Menu.buildFromTemplate([
{ label: _('Open %s', app.electronApp().getName()), click: () => { app.window().show(); } },
{ type: 'separator' },
{ label: _('Exit'), click: () => { app.exit() } },
])
app.createTray(contextMenu);
}
}
async start(argv) {
argv = await super.start(argv);
2017-11-28 21:53:29 +02:00
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
reg.setShowErrorMessageBoxHandler((message) => { bridge().showErrorMessageBox(message) });
if (Setting.value('openDevTools')) {
bridge().window().webContents.openDevTools();
}
2017-11-11 19:36:47 +02:00
this.updateMenu('Main');
2017-11-11 14:00:37 +02:00
this.initRedux();
// Since the settings need to be loaded before the store is created, it will never
2017-11-08 23:22:24 +02:00
// receive the SETTING_UPDATE_ALL even, which mean state.settings will not be
// initialised. So we manually call dispatchUpdateAll() to force an update.
Setting.dispatchUpdateAll();
await FoldersScreenUtils.refreshFolders();
const tags = await Tag.allWithNotes();
this.dispatch({
2017-11-08 23:22:24 +02:00
type: 'TAG_UPDATE_ALL',
items: tags,
});
const masterKeys = await MasterKey.all();
this.dispatch({
type: 'MASTERKEY_UPDATE_ALL',
items: masterKeys,
});
this.store().dispatch({
2017-11-08 23:22:24 +02:00
type: 'FOLDER_SELECT',
id: Setting.value('activeFolderId'),
});
2017-11-14 12:18:18 +02:00
2017-11-14 20:02:58 +02:00
// Note: Auto-update currently doesn't work in Linux: it downloads the update
// but then doesn't install it on exit.
if (shim.isWindows() || shim.isMac()) {
2018-01-31 21:34:38 +02:00
const runAutoUpdateCheck = () => {
2017-11-21 21:37:34 +02:00
if (Setting.value('autoUpdateEnabled')) {
bridge().checkForUpdates(true, bridge().window(), this.checkForUpdateLoggerPath());
2017-11-21 21:37:34 +02:00
}
2017-11-14 20:02:58 +02:00
}
// Initial check on startup
2017-11-14 20:02:58 +02:00
setTimeout(() => { runAutoUpdateCheck() }, 5000);
// Then every x hours
setInterval(() => { runAutoUpdateCheck() }, 12 * 60 * 60 * 1000);
2017-11-14 12:53:18 +02:00
}
2017-11-21 21:47:29 +02:00
2018-01-31 22:10:32 +02:00
this.updateTray();
setTimeout(() => {
AlarmService.garbageCollect();
}, 1000 * 60 * 60);
2017-11-30 20:36:26 +02:00
if (Setting.value('env') === 'dev') {
AlarmService.updateAllNotifications();
2017-11-30 20:36:26 +02:00
} else {
reg.scheduleSync().then(() => {
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
AlarmService.updateAllNotifications();
DecryptionWorker.instance().scheduleStart();
2017-11-30 20:36:26 +02:00
});
}
}
}
let application_ = null;
function app() {
if (!application_) application_ = new Application();
return application_;
}
2017-11-11 14:00:37 +02:00
module.exports = { app };