+ );
} else {
console.warn(`Type not implemented: ${key}`);
}
diff --git a/README.md b/README.md
index 9dd2b17bb5..33643fec43 100644
--- a/README.md
+++ b/README.md
@@ -24,9 +24,9 @@ Three types of applications are available: for the **desktop** (Windows, macOS a
Operating System | Download | Alternative
-----------------|--------|-------------------
-Windows (32 and 64-bit) | | Or get the Portable version
The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
-macOS | | You can also use Homebrew: `brew cask install joplin`
-Linux | | An Arch Linux package [is also available](#terminal-application).
If it works with your distribution (it has been tested on Ubuntu, Fedora, Gnome and Mint), the recommended way is to use this script as it will handle the desktop icon too:
`wget -O - https://raw.githubusercontent.com/laurent22/joplin/master/Joplin_install_and_update.sh \| bash`
+Windows (32 and 64-bit) | | Or get the Portable version
The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
+macOS | | You can also use Homebrew: `brew cask install joplin`
+Linux | | An Arch Linux package [is also available](#terminal-application).
If it works with your distribution (it has been tested on Ubuntu, Fedora, Gnome and Mint), the recommended way is to use this script as it will handle the desktop icon too:
`wget -O - https://raw.githubusercontent.com/laurent22/joplin/master/Joplin_install_and_update.sh \| bash`
## Mobile applications
@@ -259,6 +259,8 @@ Joplin uses and renders a Github-flavoured Markdown with a few variations and ad
Rendered markdown can be customized by placing a userstyle file in the profile directory `~/.config/joplin-desktop/userstyle.css` (This path might be different on your device - check at the top of the Config screen for the exact path). This file supports standard CSS syntax. Joplin ***must*** be restarted for the new css to be applied, please ensure that Joplin is not closing to the tray, but is actually exiting. Note that this file is used for both displaying the notes and printing the notes. Be aware how the CSS may look printed (for example, printing white text over a black background is usually not wanted).
+Editor styles can be customized by placing a custom editor style file in the profile directory `~/.config/joplin-desktop/userchrome.css`.
+
# Note templates
In the **desktop app**, templates can be used to create new notes or to insert into existing ones by creating a `templates` folder in Joplin's config folder and placing Markdown template files into it. For example creating the file `hours.md` in the `templates` directory with the contents:
diff --git a/ReactNativeClient/lib/BaseApplication.js b/ReactNativeClient/lib/BaseApplication.js
index e59682fc6b..2ac0ebcce4 100644
--- a/ReactNativeClient/lib/BaseApplication.js
+++ b/ReactNativeClient/lib/BaseApplication.js
@@ -39,6 +39,7 @@ const BaseService = require('lib/services/BaseService');
const SearchEngine = require('lib/services/SearchEngine');
const KvStore = require('lib/services/KvStore');
const MigrationService = require('lib/services/MigrationService');
+const CssUtils = require('lib/CssUtils');
SyncTargetRegistry.addClass(SyncTargetFilesystem);
SyncTargetRegistry.addClass(SyncTargetOneDrive);
@@ -609,6 +610,11 @@ class BaseApplication {
await Setting.load();
+ // Loads app-wide styles. (Markdown preview-specific styles loaded in app.js)
+ const dir = Setting.value('profileDir');
+ const filename = Setting.custom_css_files.JOPLIN_APP;
+ await CssUtils.injectCustomStyles(`${dir}/${filename}`);
+
if (!Setting.value('clientId')) Setting.setValue('clientId', uuid.create());
if (Setting.value('firstStart')) {
diff --git a/ReactNativeClient/lib/CssUtils.js b/ReactNativeClient/lib/CssUtils.js
new file mode 100644
index 0000000000..bbe2e3698a
--- /dev/null
+++ b/ReactNativeClient/lib/CssUtils.js
@@ -0,0 +1,27 @@
+const fs = require('fs-extra');
+
+const loadCustomCss = async filePath => {
+ let cssString = '';
+ if (await fs.pathExists(filePath)) {
+ try {
+ cssString = await fs.readFile(filePath, 'utf-8');
+ } catch (error) {
+ let msg = error.message ? error.message : '';
+ msg = `Could not load custom css from ${filePath}\n${msg}`;
+ error.message = msg;
+ throw error;
+ }
+ }
+
+ return cssString;
+};
+
+const injectCustomStyles = async cssFilePath => {
+ const css = await loadCustomCss(cssFilePath);
+ const styleTag = document.createElement('style');
+ styleTag.type = 'text/css';
+ styleTag.appendChild(document.createTextNode(css));
+ document.head.appendChild(styleTag);
+};
+
+module.exports = {loadCustomCss, injectCustomStyles};
diff --git a/ReactNativeClient/lib/layout-utils.js b/ReactNativeClient/lib/layout-utils.js
deleted file mode 100644
index 1a39f9a7b9..0000000000
--- a/ReactNativeClient/lib/layout-utils.js
+++ /dev/null
@@ -1,9 +0,0 @@
-const layoutUtils = {};
-
-layoutUtils.size = function(preferred, min, max) {
- if (preferred < min) return min;
- if (typeof max !== 'undefined' && preferred > max) return max;
- return preferred;
-};
-
-module.exports = layoutUtils;
diff --git a/ReactNativeClient/lib/models/Setting.js b/ReactNativeClient/lib/models/Setting.js
index 9f0a7823ea..e10151cf4c 100644
--- a/ReactNativeClient/lib/models/Setting.js
+++ b/ReactNativeClient/lib/models/Setting.js
@@ -410,6 +410,43 @@ class Setting extends BaseModel {
},
'style.sidebar.width': { value: 150, minimum: 80, maximum: 400, type: Setting.TYPE_INT, public: false, appTypes: ['desktop'] },
'style.noteList.width': { value: 150, minimum: 80, maximum: 400, type: Setting.TYPE_INT, public: false, appTypes: ['desktop'] },
+
+ // TODO: Is there a better way to do this? The goal here is to simply have
+ // a way to display a link to the customizable stylesheets, not for it to
+ // serve as a customizable Setting. But because the Setting page is auto-
+ // generated from this list of settings, there wasn't a really elegant way
+ // to do that directly in the React markup.
+ 'style.customCss.renderedMarkdown': {
+ onClick: () => {
+ const dir = Setting.value('profileDir');
+ const filename = Setting.custom_css_files.RENDERED_MARKDOWN;
+ const filepath = `${dir}/${filename}`;
+ const defaultContents = '/* For styling the rendered Markdown */';
+
+ shim.openOrCreateFile(filepath, defaultContents);
+ },
+ type: Setting.TYPE_BUTTON,
+ public: true,
+ appTypes: ['desktop'],
+ label: () => _('Custom stylesheet for rendered Markdown'),
+ section: 'appearance',
+ },
+ 'style.customCss.joplinApp': {
+ onClick: () => {
+ const dir = Setting.value('profileDir');
+ const filename = Setting.custom_css_files.JOPLIN_APP;
+ const filepath = `${dir}/${filename}`;
+ const defaultContents = `/* For styling the entire Joplin app (except the rendered Markdown, which is defined in \`${Setting.custom_css_files.RENDERED_MARKDOWN}\`) */`;
+
+ shim.openOrCreateFile(filepath, defaultContents);
+ },
+ type: Setting.TYPE_BUTTON,
+ public: true,
+ appTypes: ['desktop'],
+ label: () => _('Custom stylesheet for Joplin-wide app styles'),
+ section: 'appearance',
+ },
+
autoUpdateEnabled: { value: true, type: Setting.TYPE_BOOL, section: 'application', public: true, appTypes: ['desktop'], label: () => _('Automatically update the application') },
'autoUpdate.includePreReleases': { value: false, type: Setting.TYPE_BOOL, section: 'application', public: true, appTypes: ['desktop'], label: () => _('Get pre-releases when checking for updates'), description: () => _('See the pre-release page for more details: %s', 'https://joplinapp.org/prereleases') },
'clipperServer.autoStart': { value: false, type: Setting.TYPE_BOOL, public: false },
@@ -937,6 +974,7 @@ Setting.TYPE_STRING = 2;
Setting.TYPE_BOOL = 3;
Setting.TYPE_ARRAY = 4;
Setting.TYPE_OBJECT = 5;
+Setting.TYPE_BUTTON = 6;
Setting.THEME_LIGHT = 1;
Setting.THEME_DARK = 2;
@@ -966,6 +1004,12 @@ Setting.DATE_FORMAT_6 = 'DD.MM.YYYY';
Setting.TIME_FORMAT_1 = 'HH:mm';
Setting.TIME_FORMAT_2 = 'h:mm A';
+Setting.custom_css_files = {
+ JOPLIN_APP: 'userchrome.css',
+ RENDERED_MARKDOWN: 'userstyle.css',
+};
+
+
// Contains constants that are set by the application and
// cannot be modified by the user:
Setting.constants_ = {
diff --git a/ReactNativeClient/lib/shim-init-node.js b/ReactNativeClient/lib/shim-init-node.js
index 1e9fe29d1b..a754ed5b3f 100644
--- a/ReactNativeClient/lib/shim-init-node.js
+++ b/ReactNativeClient/lib/shim-init-node.js
@@ -358,7 +358,23 @@ function shimInit() {
shim.openUrl = url => {
const { bridge } = require('electron').remote.require('./bridge');
- bridge().openExternal(url);
+ // Returns true if it opens the file successfully; returns false if it could
+ // not find the file.
+ return bridge().openExternal(url);
+ };
+
+ shim.openOrCreateFile = (filepath, defaultContents) => {
+ // If the file doesn't exist, create it
+ if (!fs.existsSync(filepath)) {
+ fs.writeFile(filepath, defaultContents, 'utf-8', (error) => {
+ if (error) {
+ console.error(`error: ${error}`);
+ }
+ });
+ }
+
+ // Open the file
+ return shim.openUrl(`file://${filepath}`);
};
shim.waitForFrame = () => {};
diff --git a/ReactNativeClient/lib/shim.js b/ReactNativeClient/lib/shim.js
index 5d5d94918c..e26aae053d 100644
--- a/ReactNativeClient/lib/shim.js
+++ b/ReactNativeClient/lib/shim.js
@@ -190,6 +190,9 @@ shim.Buffer = null;
shim.openUrl = () => {
throw new Error('Not implemented');
};
+shim.openOrCreateFile = () => {
+ throw new Error('Not implemented');
+};
shim.waitForFrame = () => {
throw new Error('Not implemented');
};
diff --git a/docs/index.html b/docs/index.html
index edc4f2fb2e..40b7bc084f 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -336,7 +336,7 @@ https://github.com/laurent22/joplin/blob/master/
Joplin is a free, open source note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in Markdown format.
+
Joplin is a free, open source note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in Markdown format.
Notes exported from Evernote via .enex files can be imported into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.). Plain Markdown files can also be imported.
The notes can be synchronised with various cloud services including Nextcloud, Dropbox, OneDrive, WebDAV or the file system (for example with a network directory). When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.
The application is available for Windows, Linux, macOS, Android and iOS (the terminal app also works on FreeBSD). A Web Clipper, to save web pages and screenshots from your browser, is also available for Firefox and Chrome.
The portable application allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
The portable application allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
macOS
-
+
You can also use Homebrew: brew cask install joplin
If it works with your distribution (it has been tested on Ubuntu, Fedora, Gnome and Mint), the recommended way is to use this script as it will handle the desktop icon too: