1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Desktop: Allow for custom Joplin theme and Ace editor styles (#2099)

* Delete unused file

* Implement CssUtils

* Inject custom CSS styles

* Add info about custom CSS styles to README

* Add note that ElectronClient/app/app.js is generated

* Add support for Setting.TYPE_BUTTON

* Add buttons in Preferences to open custom CSS files

* Swap custom CSS filenames

* Swap custom CSS filenames

* Wrap "Edit" with translation fn

* Incorporate PR feedback from @laurent22

* Add openOrCreateFile to Settings

* Move openOrCreateFile to shim

* Removing header for now - see https://github.com/laurent22/joplin/pull/2099#discussion_r353120915
This commit is contained in:
Devon Zuegel 2019-12-12 16:40:58 -08:00 committed by Laurent Cozic
parent 4f3e031f4f
commit 611be7c0fa
9 changed files with 120 additions and 12 deletions

View File

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

View File

@ -413,6 +413,24 @@ class ConfigScreenComponent extends React.Component {
{descriptionComp}
</div>
);
} 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 (
<div key={key} style={rowStyle}>
<div style={labelStyle}>
<label>{md.label()}</label>
</div>
<button style={buttonStyle} onClick={md.onClick}>
{_('Edit')}
</button>
{descriptionComp}
</div>
);
} else {
console.warn(`Type not implemented: ${key}`);
}

View File

@ -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:

View File

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

View File

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

View File

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

View File

@ -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_ = {

View File

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

View File

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