You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-11-29 22:48: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:
committed by
Laurent Cozic
parent
4f3e031f4f
commit
611be7c0fa
@@ -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')) {
|
||||
|
||||
27
ReactNativeClient/lib/CssUtils.js
Normal file
27
ReactNativeClient/lib/CssUtils.js
Normal 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};
|
||||
@@ -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;
|
||||
@@ -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_ = {
|
||||
|
||||
@@ -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 = () => {};
|
||||
|
||||
@@ -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');
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user