1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-01-29 07:46:13 +02:00

Compare commits

...

24 Commits

Author SHA1 Message Date
Laurent Cozic
87045a7b60 notarization script 2020-11-29 00:53:17 +00:00
Laurent Cozic
35242b9735 Merge branch 'dev' into mac_notarization 2020-11-29 00:23:13 +00:00
Laurent Cozic
1851b0e7d1 Merge branch 'release-1.4' into dev 2020-11-29 00:22:17 +00:00
Laurent Cozic
0fcb6441de notarize 2020-11-29 00:18:39 +00:00
Laurent Cozic
76c4d99b87 Desktop release v1.4.18 2020-11-28 12:03:29 +00:00
Laurent Cozic
849ef418a6 Desktop: Fixed notifications on macOS 2020-11-28 12:03:06 +00:00
Laurent Cozic
8fbd1ae21a notarization 2020-11-27 20:42:23 +00:00
Laurent Cozic
d733c0e010 Desktop release v1.4.16 2020-11-27 18:19:31 +00:00
Laurent Cozic
a48e5cd4e8 Desktop: Fixed spell checker crash when no language is selected 2020-11-27 18:15:22 +00:00
Laurent Cozic
03942a0073 All: Fix sorting by title in a case insensitive way 2020-11-27 15:16:50 +00:00
Laurent Cozic
0bc53dc063 Merge branch 'release-1.4' into dev 2020-11-27 12:43:40 +00:00
Laurent Cozic
56605beea2 Desktop release v1.4.15 2020-11-27 12:41:24 +00:00
Laurent Cozic
8059d3fbd1 Desktop release v1.4.14 2020-11-27 12:41:12 +00:00
Laurent Cozic
46c38ce0e0 Desktop: Remove support for buggy asar packing as it breaks notifications on macOS. Also make sure only DMG is built for macOS. 2020-11-27 12:40:43 +00:00
Laurent Cozic
dfa928c1f7 Log info 2020-11-27 12:21:59 +00:00
Laurent Cozic
cb696276da Desktop: Fixes #4146: Prevents crash when invalid spell checker language is selected, and provide fallback for invalid language codes 2020-11-27 12:21:34 +00:00
Laurent Cozic
5f80628a4d Desktop: Fixed potential crash when watching note files or resources 2020-11-27 12:19:57 +00:00
Laurent Cozic
b77f868fc8 Log info 2020-11-27 12:03:32 +00:00
Laurent Cozic
6ad9931e43 Desktop: Fixes #4146: Prevents crash when invalid spell checker language is selected, and provide fallback for invalid language codes 2020-11-27 11:12:28 +00:00
Laurent Cozic
011a65f73b Desktop: Fixed potential crash when watching note files or resources 2020-11-27 11:08:42 +00:00
Laurent Cozic
72ccc90ea0 Merge branch 'release-1.4' into dev 2020-11-27 01:16:52 +00:00
Laurent Cozic
f3e6c0da32 Desktop release v1.4.13 2020-11-26 23:32:30 +00:00
Laurent Cozic
9308c3f38c Plugins: Fixed webview postMessage call 2020-11-26 23:31:31 +00:00
Laurent Cozic
c8a7c70838 ios-v10.4.1 2020-11-26 22:17:55 +00:00
22 changed files with 359 additions and 71 deletions

View File

@@ -1,6 +1,7 @@
document.addEventListener('click', event => {
const element = event.target;
if (element.className === 'toc-item-link') {
console.debug('TOC Plugin Webview: Sending scrollToHash message', element.dataset.slug);
webviewApi.postMessage({
name: 'scrollToHash',
hash: element.dataset.slug,

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>

View File

@@ -30,6 +30,7 @@ import { themeStyle } from '@joplin/lib/theme';
import validateLayout from '../ResizableLayout/utils/validateLayout';
import iterateItems from '../ResizableLayout/utils/iterateItems';
import removeItem from '../ResizableLayout/utils/removeItem';
import Logger from '@joplin/lib/Logger';
const { connect } = require('react-redux');
const { PromptDialog } = require('../PromptDialog.min.js');
@@ -38,6 +39,8 @@ const PluginManager = require('@joplin/lib/services/PluginManager');
const EncryptionService = require('@joplin/lib/services/EncryptionService');
const ipcRenderer = require('electron').ipcRenderer;
const logger = Logger.create('MainScreen');
interface LayerModalState {
visible: boolean;
message: string;
@@ -564,6 +567,7 @@ class MainScreenComponent extends React.Component<Props, State> {
}
userWebview_message(event: any) {
logger.debug('Got message (WebView => Plugin) (2)', event);
PluginService.instance().pluginById(event.pluginId).viewController(event.viewId).emitMessage(event);
}

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "1.4.12",
"version": "1.4.18",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -5956,6 +5956,75 @@
"resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-0.3.0.tgz",
"integrity": "sha1-FOb9pcaOnk7L7/nM8DfL18BcWv4="
},
"electron-notarize": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.0.0.tgz",
"integrity": "sha512-dsib1IAquMn0onCrNMJ6gtEIZn/azG8hZMCYOuZIMVMUeRMgBYHK1s5TK9P8xAcrAjh/2aN5WYHzgVSWX314og==",
"dev": true,
"requires": {
"debug": "^4.1.1",
"fs-extra": "^9.0.1"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"fs-extra": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
"dev": true,
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^1.0.0"
}
},
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"dependencies": {
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"dev": true
}
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"universalify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
"dev": true
}
}
},
"electron-publish": {
"version": "22.9.1",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.9.1.tgz",
@@ -8828,9 +8897,7 @@
"is-docker": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz",
"integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==",
"dev": true,
"optional": true
"integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw=="
},
"is-dotfile": {
"version": "1.0.3",
@@ -9021,9 +9088,12 @@
"dev": true
},
"is-wsl": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz",
"integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog=="
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"requires": {
"is-docker": "^2.0.0"
}
},
"is-yarn-global": {
"version": "0.3.0",
@@ -11707,21 +11777,30 @@
"dev": true
},
"node-notifier": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-6.0.0.tgz",
"integrity": "sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.0.tgz",
"integrity": "sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA==",
"requires": {
"growly": "^1.3.0",
"is-wsl": "^2.1.1",
"semver": "^6.3.0",
"is-wsl": "^2.2.0",
"semver": "^7.3.2",
"shellwords": "^0.1.1",
"which": "^1.3.1"
"uuid": "^8.3.0",
"which": "^2.0.2"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"requires": {
"isexe": "^2.0.0"
}
}
}
},
@@ -14984,9 +15063,7 @@
"uuid": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==",
"dev": true,
"optional": true
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
},
"v8-to-istanbul": {
"version": "7.0.0",
@@ -15202,6 +15279,7 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "1.4.12",
"version": "1.4.19",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
@@ -27,6 +27,7 @@
"appId": "net.cozic.joplin-desktop",
"productName": "Joplin",
"npmRebuild": false,
"afterSign": "./tools/notarizeMacApp.js",
"extraResources": [
"build/icons/*",
"build/images/*"
@@ -72,7 +73,10 @@
"artifactName": "${productName}Portable.${ext}"
},
"mac": {
"icon": "../../Assets/macOs.icns"
"icon": "../../Assets/macOs.icns",
"target": "dmg",
"hardenedRuntime": true,
"entitlements": "./build-mac/entitlements.mac.inherit.plist"
},
"linux": {
"icon": "../../Assets/LinuxIcons",
@@ -100,6 +104,7 @@
"babel-preset-react": "^6.24.1",
"electron": "^10.1.6",
"electron-builder": "22.9.1",
"electron-notarize": "^1.0.0",
"electron-rebuild": "^2.3.2",
"glob": "^7.1.6",
"gulp": "^4.0.2",
@@ -136,7 +141,7 @@
"md5": "^2.2.1",
"moment": "^2.22.2",
"node-fetch": "^1.7.3",
"node-notifier": "^6.0.0",
"node-notifier": "^8.0.0",
"pretty-bytes": "^5.3.0",
"re-resizable": "^6.5.4",
"react": "16.13.1",

View File

@@ -6,8 +6,11 @@ import bridge from '../bridge';
import Setting from '@joplin/lib/models/Setting';
import { EventHandlers } from '@joplin/lib/services/plugins/utils/mapEventHandlersToIds';
import shim from '@joplin/lib/shim';
import Logger from '@joplin/lib/Logger';
const ipcRenderer = require('electron').ipcRenderer;
const logger = Logger.create('PluginRunner');
enum PluginMessageTarget {
MainWindow = 'mainWindow',
Plugin = 'plugin',
@@ -127,7 +130,9 @@ export default class PluginRunner extends BasePluginRunner {
const mappedArgs = mapEventIdsToHandlers(plugin.id, message.args);
const fullPath = `joplin.${message.path}`;
this.logger().debug(`PluginRunner: execute call: ${fullPath}: ${mappedArgs}`);
// Don't log complete HTML code, which can be long, for setHtml calls
const debugMappedArgs = fullPath.includes('setHtml') ? '<hidden>' : mappedArgs;
logger.debug(`Got message (3): ${fullPath}: ${debugMappedArgs}`);
let result: any = null;
let error: any = null;

View File

@@ -7,7 +7,10 @@ import useSubmitHandler from './hooks/useSubmitHandler';
import useHtmlLoader from './hooks/useHtmlLoader';
import useWebviewToPluginMessages from './hooks/useWebviewToPluginMessages';
import useScriptLoader from './hooks/useScriptLoader';
const styled = require('styled-components').default;
import Logger from '@joplin/lib/Logger';
import styled from 'styled-components';
const logger = Logger.create('UserWebview');
export interface Props {
html: string;
@@ -72,6 +75,9 @@ function UserWebview(props: Props, ref: any) {
function postMessage(name: string, args: any = null) {
const win = frameWindow();
if (!win) return;
logger.debug('Got message', name, args);
win.postMessage({ target: 'webview', name, args }, '*');
}
@@ -112,6 +118,7 @@ function UserWebview(props: Props, ref: any) {
useWebviewToPluginMessages(
frameWindow(),
isReady,
props.onMessage,
props.pluginId,
props.viewId

View File

@@ -58,10 +58,10 @@ const webviewApi = {
setHtml: (args) => {
contentElement.innerHTML = args.html;
console.debug('UserWebView frame: setting html to', args.html);
// console.debug('UserWebviewIndex: setting html to', args.html);
window.requestAnimationFrame(() => {
console.debug('UserWebView frame: setting html callback', args.hash);
console.debug('UserWebviewIndex: setting html callback', args.hash);
window.postMessage({ target: 'UserWebview', message: 'htmlIsSet', hash: args.hash }, '*');
});
},
@@ -105,6 +105,7 @@ const webviewApi = {
if (!ipc[callName]) {
console.warn('Missing IPC function:', event.data);
} else {
console.debug('UserWebviewIndex: Got message', callName, args);
ipc[callName](args);
}
}));
@@ -115,7 +116,7 @@ const webviewApi = {
// Need to send it with a delay to make sure all listeners are
// ready when the message is sent.
window.requestAnimationFrame(() => {
console.debug('UserWebView frame: calling isReady');
console.debug('UserWebViewIndex: calling isReady');
window.postMessage({ target: 'UserWebview', message: 'ready' }, '*');
});
});

View File

@@ -39,7 +39,7 @@ export default function(frameWindow: any, isReady: boolean, postMessage: Functio
if (!isReady) return;
console.info('useHtmlLoader: setHtml', htmlHash, html);
console.info('useHtmlLoader: setHtml', htmlHash);
postMessage('setHtml', {
hash: htmlHash,

View File

@@ -1,11 +1,20 @@
import Logger from '@joplin/lib/Logger';
import { useEffect } from 'react';
export default function(frameWindow: any, onMessage: Function, pluginId: string, viewId: string) {
const logger = Logger.create('useWebviewToPluginMessages');
export default function(frameWindow: any, isReady: boolean, onMessage: Function, pluginId: string, viewId: string) {
useEffect(() => {
if (!frameWindow) return () => {};
function onMessage(event: any) {
function onMessage_(event: any) {
if (!event.data || event.data.target !== 'plugin') return;
// The message is passed from one component or service to the next
// till it reaches its destination, so if something doesn't work
// follow the chain of messages searching for the string "Got message"
logger.debug('Got message (WebView => Plugin) (1)', pluginId, viewId, event.data.message);
onMessage({
pluginId: pluginId,
viewId: viewId,
@@ -13,10 +22,10 @@ export default function(frameWindow: any, onMessage: Function, pluginId: string,
});
}
frameWindow.addEventListener('message', onMessage);
frameWindow.addEventListener('message', onMessage_);
return () => {
frameWindow.removeEventListener('message', onMessage);
frameWindow.removeEventListener('message', onMessage_);
};
}, [onMessage, pluginId, viewId]);
}, [frameWindow, onMessage, isReady, pluginId, viewId]);
}

View File

@@ -2,6 +2,10 @@
import SpellCheckerServiceDriverBase from '@joplin/lib/services/spellChecker/SpellCheckerServiceDriverBase';
import bridge from '../bridge';
import { languageCodeOnly, localesFromLanguageCode } from '@joplin/lib/locale';
import Logger from '@joplin/lib/Logger';
const logger = Logger.create('SpellCheckerServiceDriverNative');
export default class SpellCheckerServiceDriverNative extends SpellCheckerServiceDriverBase {
@@ -17,7 +21,31 @@ export default class SpellCheckerServiceDriverNative extends SpellCheckerService
public setLanguage(v: string) {
// If we pass an empty array, it disables spell checking
// https://github.com/electron/electron/issues/25228
this.session().setSpellCheckerLanguages(v ? [v] : []);
if (!v) {
this.session().setSpellCheckerLanguages([]);
return;
}
// The below function will throw an error if the provided language is
// not supported, so we provide fallbacks.
// https://github.com/laurent22/joplin/issues/4146
const languagesToTry = [
v,
languageCodeOnly(v),
].concat(localesFromLanguageCode(languageCodeOnly(v), this.availableLanguages));
for (const toTry of languagesToTry) {
try {
this.session().setSpellCheckerLanguages([toTry]);
logger.info(`Set effective language from "${v}" to "${toTry}"`);
return;
} catch (error) {
logger.warn(`Failed to set language to "${toTry}". Will try the next one in this list: ${JSON.stringify(languagesToTry)}`);
logger.warn('Error was:', error);
}
}
logger.error(`Could not set language to: ${v}`);
}
public get language(): string {

View File

@@ -0,0 +1,45 @@
const fs = require('fs');
const path = require('path');
const electron_notarize = require('electron-notarize');
module.exports = async function(params) {
if (process.platform !== 'darwin') return;
if (!process.env.APPLE_ID || !process.env.APPLE_ID_PASSWORD) {
console.warn('Environment variables APPLE_ID and APPLE_ID_PASSWORD not found - notarization will NOT be done.');
return;
}
// Same appId in electron-builder.
const appId = 'net.cozic.joplin-desktop';
const appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`);
if (!fs.existsSync(appPath)) {
throw new Error(`Cannot find application at: ${appPath}`);
}
console.log(`Notarizing ${appId} found at ${appPath}`);
await electron_notarize.notarize({
appBundleId: appId,
appPath: appPath,
// Apple Developer email address
appleId: process.env.APPLE_ID,
// App-specific password: https://support.apple.com/en-us/HT204397
appleIdPassword: process.env.APPLE_ID_PASSWORD,
// When Apple ID is attached to multiple providers (eg if the
// account has been used to build multiple apps for different
// companies), in that case the provider "Team Short Name" (also
// known as "ProviderShortname") must be provided.
//
// Use this to get it:
//
// xcrun altool --list-providers -u APPLE_ID -p APPLE_ID_PASSWORD
ascProvider: process.env.APPLE_ASC_PROVIDER,
});
console.log(`Done notarizing ${appId}`);
};

View File

@@ -6,5 +6,6 @@
],
"exclude": [
"**/node_modules",
"**/dist",
],
}

View File

@@ -338,13 +338,13 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 56;
CURRENT_PROJECT_VERSION = 57;
DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.4.0;
MARKETING_VERSION = 10.4.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -365,12 +365,12 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 56;
CURRENT_PROJECT_VERSION = 57;
DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.4.0;
MARKETING_VERSION = 10.4.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",

View File

@@ -255,14 +255,6 @@ class BaseModel {
if (!options) options = {};
if (options.order && options.order.length) {
// const items = [];
// for (let i = 0; i < options.order.length; i++) {
// const o = options.order[i];
// let item = `\`${o.by}\``;
// if (options.caseInsensitive === true) item += ' COLLATE NOCASE';
// if (o.dir) item += ` ${o.dir}`;
// items.push(item);
// }
sql += ` ORDER BY ${paginationToSql(options)}`;
}

View File

@@ -572,6 +572,12 @@ function languageCode() {
return languageCodeOnly(currentLocale_);
}
function localesFromLanguageCode(languageCode: string, locales: string[]): string[] {
return locales.filter((l: string) => {
return languageCodeOnly(l) === languageCode;
});
}
function _(s: string, ...args: any[]) {
const strings = localeStrings(currentLocale_);
let result = strings[s];
@@ -588,4 +594,4 @@ function _n(singular: string, plural: string, n: number, ...args: any[]) {
return _(singular, ...args);
}
export { _, _n, supportedLocales, countryDisplayName, localeStrings, setLocale, supportedLocalesToLanguages, defaultLocale, closestSupportedLocale, languageCode, countryCodeOnly };
export { _, _n, supportedLocales, localesFromLanguageCode, languageCodeOnly, countryDisplayName, localeStrings, setLocale, supportedLocalesToLanguages, defaultLocale, closestSupportedLocale, languageCode, countryCodeOnly };

View File

@@ -446,6 +446,12 @@ class Setting extends BaseModel {
options: () => themeOptions(),
},
notificationPermission: {
value: '',
type: SettingItemType.String,
public: false,
},
showNoteCounts: { value: true, type: SettingItemType.Bool, public: false, advanced: true, appTypes: ['desktop'], label: () => _('Show note counts') },
layoutButtonSequence: {

View File

@@ -6,7 +6,7 @@ export default function(pagination: Pagination): string {
for (let i = 0; i < pagination.order.length; i++) {
const o = pagination.order[i];
let item = `\`${o.by}\``;
if (o.caseInsensitive === true) item += ' COLLATE NOCASE';
if (!!o.caseInsensitive || !!pagination.caseInsensitive) item += ' COLLATE NOCASE';
item += ` ${o.dir}`;
sql.push(item);
}

View File

@@ -13,4 +13,5 @@ export interface Pagination {
limit: number;
order: PaginationOrder[];
page: number;
caseInsensitive?: boolean;
}

View File

@@ -1,6 +1,7 @@
import eventManager from '../eventManager';
import { Notification } from '../models/Alarm';
import shim from '../shim';
import Setting from '../models/Setting';
const notifier = require('node-notifier');
const bridge = require('electron').remote.require('./bridge').default;
@@ -43,7 +44,91 @@ export default class AlarmServiceDriverNode {
delete this.notifications_[id];
}
scheduleNotification(notification: Notification) {
private displayDefaultNotification(notification: Notification) {
const o: any = {
appID: this.appName_,
title: notification.title,
icon: `${bridge().electronApp().buildDir()}/icons/512x512.png`,
};
if ('body' in notification) o.message = notification.body;
// Message is required on Windows 7 however we don't want to repeat the title so
// make it an empty string.
// https://github.com/laurent22/joplin/issues/2144
if (!o.message) o.message = '-';
this.logger().info('AlarmServiceDriverNode::scheduleNotification: Triggering notification (default):', o);
notifier.notify(o, (error: any, response: any) => {
this.logger().info('AlarmServiceDriverNode::scheduleNotification: node-notifier response:', error, response);
});
}
private displayMacNotification(notification: Notification) {
// On macOS, node-notifier is broken:
//
// https://github.com/mikaelbr/node-notifier/issues/352
//
// However we can use the native browser notification as described
// there:
//
// https://www.electronjs.org/docs/tutorial/notifications
//
// In fact it's likely that we could use this on other platforms too
try {
const options: any = {
body: notification.body ? notification.body : '-',
onerror: (error: any) => {
this.logger().error('AlarmServiceDriverNode::displayMacNotification', error);
},
};
this.logger().info('AlarmServiceDriverNode::displayMacNotification: Triggering notification (macOS):', notification.title, options);
new Notification(notification.title, options);
} catch (error) {
this.logger().error('AlarmServiceDriverNode::displayMacNotification', error);
}
}
private async checkPermission() {
if (shim.isMac() && shim.isElectron()) {
this.logger().info(`AlarmServiceDriverNode::checkPermission: Permission in settings is "${Setting.value('notificationPermission')}"`);
if (Setting.value('notificationPermission') !== '') return Setting.value('notificationPermission');
// In theory `Notification.requestPermission()` should be used to
// ask for permission but in practice this API is unreliable. In
// particular, it returns "granted" immediately even when
// notifications definitely aren't allowed (and creating a new
// notification would fail).
//
// Because of that, our approach is to trigger a notification, which
// should prompt macOS to ask for permission. Once this is done we
// manually save the result in the settings. Of course it means that
// if permission is changed afterwards, for example from the
// notification center, we won't know it and notifications will
// fail.
//
// All this means that for now this checkPermission function always
// returns "granted" and the setting has only two values: "granted"
// or "" (which means we need to do the check permission trick).
//
// The lack of "denied" value is acceptable in our context because
// if a user doesn't want notifications, they can simply not set
// alarms.
new Notification('Checking permissions...', {
body: 'Permission has been granted',
});
Setting.setValue('notificationPermission', 'granted');
}
return 'granted';
}
async scheduleNotification(notification: Notification) {
const now = Date.now();
const interval = notification.date.getTime() - now;
if (interval < 0) return;
@@ -52,6 +137,12 @@ export default class AlarmServiceDriverNode {
throw new Error(`Trying to create a notification from an invalid object: ${JSON.stringify(notification)}`);
}
const permission = await this.checkPermission();
if (permission !== 'granted') {
this.logger().info(`AlarmServiceDriverNode::scheduleNotification: Notification ${notification.id}: Cancelled because permission was not granted.`);
return;
}
this.logger().info(`AlarmServiceDriverNode::scheduleNotification: Notification ${notification.id} with interval: ${interval}ms`);
if (this.notifications_[notification.id]) shim.clearTimeout(this.notifications_[notification.id].timeoutId);
@@ -72,27 +163,15 @@ export default class AlarmServiceDriverNode {
this.logger().info(`AlarmServiceDriverNode::scheduleNotification: Notification ${notification.id} has been deleted - not rescheduling it`);
return;
}
this.scheduleNotification(this.notifications_[notification.id]);
void this.scheduleNotification(this.notifications_[notification.id]);
}, maxInterval);
} else {
timeoutId = shim.setTimeout(() => {
const o: any = {
appID: this.appName_,
title: notification.title,
icon: `${bridge().electronApp().buildDir()}/icons/512x512.png`,
};
if ('body' in notification) o.message = notification.body;
// Message is required on Windows 7 however we don't want to repeat the title so
// make it an empty string.
// https://github.com/laurent22/joplin/issues/2144
if (!o.message) o.message = '-';
this.logger().info('AlarmServiceDriverNode::scheduleNotification: Triggering notification:', o);
notifier.notify(o, (error: any, response: any) => {
this.logger().info('AlarmServiceDriverNode::scheduleNotification: node-notifier response:', error, response);
});
if (shim.isMac() && shim.isElectron()) {
this.displayMacNotification(notification);
} else {
this.displayDefaultNotification(notification);
}
this.clearNotification(notification.id);

View File

@@ -81,7 +81,10 @@ export default class ExternalEditWatcher {
if (!this.chokidar_) return;
if (!this.watcher_) {
this.watcher_ = this.chokidar_.watch(fileToWatch);
this.watcher_ = this.chokidar_.watch(fileToWatch, {
useFsEvents: false,
});
this.watcher_.on('all', async (event: string, path: string) => {
this.logger().debug(`ExternalEditWatcher: Event: ${event}: ${path}`);

View File

@@ -156,9 +156,14 @@ export default class ResourceEditWatcher {
};
if (!this.watcher_) {
this.watcher_ = this.chokidar_.watch(fileToWatch);
this.watcher_ = this.chokidar_.watch(fileToWatch, {
// Need to turn off fs-events because when it's on Chokidar
// keeps emitting "modified" events (on "raw" handler), several
// times per seconds, even when nothing is changed.
useFsEvents: false,
});
this.watcher_.on('all', (event: any, path: string) => {
path = toSystemSlashes(path, 'linux');
path = path ? toSystemSlashes(path, 'linux') : '';
this.logger().info(`ResourceEditWatcher: Event: ${event}: ${path}`);
@@ -186,7 +191,7 @@ export default class ResourceEditWatcher {
//
// @ts-ignore Leave unused path variable
this.watcher_.on('raw', (event: string, path: string, options: any) => {
const watchedPath = toSystemSlashes(options.watchedPath, 'linux');
const watchedPath = options.watchedPath ? toSystemSlashes(options.watchedPath, 'linux') : '';
this.logger().debug(`ResourceEditWatcher: Raw event: ${event}: ${watchedPath}`);
if (event === 'rename') {