diff --git a/ElectronClient/app/app.js b/ElectronClient/app/app.js index 59a64c09db..69e7822d1c 100644 --- a/ElectronClient/app/app.js +++ b/ElectronClient/app/app.js @@ -28,6 +28,7 @@ const PluginManager = require('lib/services/PluginManager'); const RevisionService = require('lib/services/RevisionService'); const MigrationService = require('lib/services/MigrationService'); const TemplateUtils = require('lib/TemplateUtils'); +const CssUtils = require('lib/CssUtils'); const pluginClasses = [ require('./plugins/GotoAnything.min'), @@ -1224,8 +1225,8 @@ class Application extends BaseApplication { ids: Setting.value('collapsedFolderIds'), }); - const cssString = await this.loadCustomCss(`${Setting.value('profileDir')}/userstyle.css`); - + // Loads custom Markdown preview styles + const cssString = await CssUtils.loadCustomCss(`${Setting.value('profileDir')}/userstyle.css`); this.store().dispatch({ type: 'LOAD_CUSTOM_CSS', css: cssString, diff --git a/ElectronClient/app/gui/ConfigScreen.jsx b/ElectronClient/app/gui/ConfigScreen.jsx index ca635a83e0..a7db35001b 100644 --- a/ElectronClient/app/gui/ConfigScreen.jsx +++ b/ElectronClient/app/gui/ConfigScreen.jsx @@ -413,6 +413,24 @@ class ConfigScreenComponent extends React.Component { {descriptionComp} ); + } else if (md.type === Setting.TYPE_BUTTON) { + const theme = themeStyle(this.props.theme); + const buttonStyle = Object.assign({}, theme.buttonStyle, { + display: 'inline-block', + marginRight: 10, + }); + + return ( +
+
+ +
+ + {descriptionComp} +
+ ); } 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) | Get it on Windows | 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 | Get it on macOS | You can also use Homebrew: `brew cask install joplin` -Linux | Get it on 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) | Get it on Windows | 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 | Get it on macOS | You can also use Homebrew: `brew cask install joplin` +Linux | Get it on 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 applying for Google Summer of Code 2020 🌞


-

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.

@@ -355,17 +355,17 @@ https://github.com/laurent22/joplin/blob/master/ Windows (32 and 64-bit) -Get it on Windows -Or get the Portable version

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. +Get it on Windows +Or get the Portable version

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 -Get it on macOS +Get it on macOS You can also use Homebrew: brew cask install joplin Linux -Get it on Linux +Get it on Linux An Arch Linux package is also available.

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 @@ -383,7 +383,7 @@ https://github.com/laurent22/joplin/blob/master/ Android Get it on Google Play -or download the APK file: 64-bit 32-bit +or download the APK file: 64-bit 32-bit iOS @@ -492,7 +492,6 @@ https://github.com/laurent22/joplin/blob/master/

WebDAV-compatible services that are known to work with Joplin: