Custom stylesheets

Thanks. My css is very personal to my needs, i have to complete it before to share the code.

Just came across this. I’m applying for GSoC under Joplin, and my idea is to implement a framework to sync settings in Joplin. If it works out well, I think it would be helpful to customise styling as a setting and be able to sync it. This would solve the issue of

  1. Losing the styling if it’s stored as a setting
  2. Having the styling notes mess with the UX if it’s stored as a note in a notebook

As I’ve planned it, it would handle the discrepancies in valid settings across platforms, so that aspect of styling should be handled. I’m happy to get feedback on this!

P.S. I’d be willing to work on this even if I’m not selected, if the community thinks its a useful feature!

Hi @anihm136,

I appreciate your enthusiasm. Just some remarks here as an appetizer. :slight_smile:

It's more than a technical challenge, I think. I would like to see some settings synchronized, but not all. For instance, mobile and desktop often demands for different styling. And within the desktop range: the resolution of the screen can certainly make a difference. At this moment I'm forcing myself to keep the settings the same on all my desktops, but once fallen prey to aestheticism, I might decide otherwise. And I don't mind to synchronize the CSS files manually, as I can still oversee my machinery up to now.

So the desirable outcome could be some mixture of global and local components that refer to each other. I haven't done yet any thinking about a configuration for that.

Neither the technical aspect is trivial. How to harmonize desktop and mobile? Especially iOS could be a hard nut, because of Apple's policy.

1 Like

I understand that it will be a hard task (otherwise it would probably be implemented by now!). The way I’m thinking of doing it, we should be able to separate the platform-independent and platform-dependent settings, and when retrieving from sync target on a different platform, only retrieve the platform-independent parts. That way -

  1. If you use 2 phones, all settings should be replicated accurately
  2. If you use one client on laptop and one on mobile, you should get the basic settings synced (date and time format, language, sync settings etc), and the platform-specific settings (like style settings, font size, theme etc) will be left for the user to customise (and be available to sync for other devices of the same type)
  3. If you want to have 2 devices of the same type with different configurations, add option to disable setting sync for that device

I think between these options, it should handle most common customization cases. Of course, it still needs a lot of community feedback to handle specific cases, and inevitably some unexpected issues will crop up while implementing, but it should be a good feature to implement overall.

1 Like

Hi all,
I come back to this subject to tell of a misadventure.
Note: I use Joplin to prepare a newsletter via email, then I copy/paste the formatted page in my message.

Here is the problem
1- if the external style sheet is imported, I lose the formatting
2- if the styles are included in the Joplin note, the formatting is kept

To troubleshoot, I exported the note in html, then I copied the content of the web page.
Is there a better way to get around this or should I continue to include my styles in the note?

Edit: I’ve found a possible roundabout, importing my stylesheet by this syntax
@import url("absolute path to stylesheet.css") than by the simple string syntax.
Thanks to w3school!

Could you confirm if I’m right or not?

Hi @betternote,

This is more complicated than I had expected. In the first place: copy/paste from the Joplin viewer works for me, at least in Outlook on Windows. Exporting to HTML and copy/paste from Firefox drops the styling, but the same trick with Chrome as intermediary is okay again.

Even when it is a success, it’s not a complete success. Ik lose some of the stylings by false tags, but not in all cases. I don’t see the pattern yet.

As for keeping the styles available to my exported HTML, I use this model:

  • Let’s presume I want to export my note “The Nature of Reality” to ~/thoughts/content/The Nature of Reality.html (I put all my deep thoughts in that folder).
  • I’ve made this copy of my personal stylesheet: ~/thoughts/style/userstyle.css.
  • I put a relative reference to that stylesheet in my note (this has no effect in Joplin, since it cannot interpret such references well). It can be done in several ways:
<style>
  @import "../style/userstyle.css";
</style>

Or:

<link rel="stylesheet" href="../style/userstyle.css"/> 

Both Firefox and Chrome render the exported note as wished for. But as said, some stylings may get lost in the copying from Joplin or Chrome to a message.

1 Like

Thank you for so detailed explanations.

Do you know what the folder structure is on iOS? I got it working on my pc, but can’t figure out how to get it to work on my iPhone.

I don’t think custom stylesheets are supported on mobile.

You can take a .css file and add it as an attachment to a note. Then you can get the filename from the link it creates and use it in the import statement. For example, if you attach the file and it gives you this link:
[special.css](:/e64fdb1989644d399eedd7f30bc3b9a2)
Then on pc you can reference it in a note like this:

<style>
@import "/Users/<username>/.config/joplin-desktop/resources/e64fdb1989644d399eedd7f30bc3b9a2.css"
</style>

or this:

<style>
@import "../../../../../../../../.config/joplin-desktop/resources/e64fdb1989644d399eedd7f30bc3b9a2.css"
</style>

So if we knew the folder structure on iOS we could do the same thing, right?

in fact if you put your css on the web and then you can use it on mobile adding this in each note

<link rel="stylesheet" href="http://.........../userstyle.css">

why not for some notes :wink:

I thought of userstyle.css & userchrome.css that are used to change the appearance of Joplin. I see what you mean now. Unfortunately I’m not familiar with iOS internals to comment on this.

I found the path using FilzaEscaped. The problem is that the uuid changes on each install of the app.

<style>
@import "/var/mobile/Containers/Data/Application/7B861EE7-382C-484B-8B4B-3156A6544A23/Documents/e64fdb1989644d399eedd7f30bc3b9a2.css"
</style>

Edit: Never mind. This works:

<style>
@import "./e64fdb1989644d399eedd7f30bc3b9a2.css"
</style>

Edit again: Using the <link> tag is better because you can use both. With @import you can use only one; anything after the first is ignored.

<link rel="stylesheet" href="../../../../../../../../.config/joplin-desktop/resources/e64fdb1989644d399eedd7f30bc3b9a2.css">
<link rel="stylesheet" href="./e64fdb1989644d399eedd7f30bc3b9a2.css">
1 Like

Nice research, @bizzaro. For some moments I thought you had found the secret door, the universal gateway to CSS files. I guess the experiments below took place on Linux:

In the first line you navigate 8 levels up, bringing you to /Users/<user>/, and then drill 3 levels down to the location of the CSS file. In Windows I have to go just 6 levels up (from somewhere below C:\Users\<user>\+apps\Joplin\?) to arrive in C:\Users\<user>\ before drilling down 3 levels. It's worse in macOS: 7 levels up (from somewhere below /Applications/Joplin.app/Contents/?) to the root directory, and from there 5 levels down to /Users/<user>/.config/joplin-desktop/resources/. I have once read a post that illustrates how to find out which directory Joplin is running from, but I couldn't find that back, regrettably.

Your second line does not work on any of my platforms, but I guess you didn't pretend it would work from Joplin anyway. I suppose this line becomes operational when the note is exported (provided that the CSS file is copied to the same directory). Correct me if I have misinterpreted you.

Anyway, thanks for your contribution.

For me the first one works in Windows 10 and the second in iOS. If you bring up the development tools you can find the path. For Windows 10 it looks like C:/Users//AppData/Local/Programs/Joplin/resources/app.asar/gui/note-viewer.


Here’s a css file I attached:

And here’s how it looks on Windows 10:

And iOS:

1 Like

Great! (My apologies for editing your path in the quote above a bit.) My paths are:

  • Win 10: C:/Users/<user>/+apps/Joplin/resources/app.asar/gui/note-viewer/
    ⇒ 6 levels upwards to the <user> folder
  • macOS Mojave: /Applications/Joplin-app/Contents/Resources/app.asar/gui/note-viewer/
    ⇒ 7 levels upwards to the root folder
  • Linux: No idea, but I expect Linux users can easily find out now via the development tools.
  • iOS: The CSS file is in the parent folder indeed, wherever that may be
  • Android: Something to be tried out by an Android user.

It works, but I'm pessimistic about the usability, though. I haven't found a way to update such a CSS file and get it synchronized. All my (rather dangerous) experiments failed.

So this is what I did…

Open the userstyle.css
At the top of the file, insert this:

:root {
    font-size: 16px;
    --template-background-color: #000000;
    --accent-background-color: #555555;
    --font-color: #dddddd;
    --highlight-font-color: #ffffff;
    --accent-color: #e9ffd9;
    --font-family: 'open_sanslight';
    --font-bold-family: 'open_sansbold';
    --font-italic-family: 'open_sansitalic';
}
@import url(../styles/fonts.css);

I created a styles folder in the root path of the Joplin folder called “styles”
Then I created a fonts.css file

@import url(../fonts/lato/stylesheet.css);
@import url(../fonts/montserrat/stylesheet.css);
@import url(../fonts/open_sans/stylesheet.css);
font {
    font-family: var(--font-family);
    color: var(--font-color);
}

Now back into the root path of the Joplin Folder, I created a fonts folder and then setup individual folders for each font.

Inside the font folder, I have their own stylesheet.css

@font-face {
    font-family: 'montserratblack';
    src: url('montserrat-black-webfont.woff2') format('woff2'), url('montserrat-black-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratblack_italic';
    src: url('montserrat-blackitalic-webfont.woff2') format('woff2'), url('montserrat-blackitalic-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratbold';
    src: url('montserrat-bold-webfont.woff2') format('woff2'), url('montserrat-bold-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratbold_italic';
    src: url('montserrat-bolditalic-webfont.woff2') format('woff2'), url('montserrat-bolditalic-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratextrabold';
    src: url('montserrat-extrabold-webfont.woff2') format('woff2'), url('montserrat-extrabold-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratextrabold_italic';
    src: url('montserrat-extrabolditalic-webfont.woff2') format('woff2'), url('montserrat-extrabolditalic-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratextralight';
    src: url('montserrat-extralight-webfont.woff2') format('woff2'), url('montserrat-extralight-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratextralight_italic';
    src: url('montserrat-extralightitalic-webfont.woff2') format('woff2'), url('montserrat-extralightitalic-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratitalic';
    src: url('montserrat-italic-webfont.woff2') format('woff2'), url('montserrat-italic-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratlight_italic';
    src: url('montserrat-lightitalic-webfont.woff2') format('woff2'), url('montserrat-lightitalic-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratmedium';
    src: url('montserrat-medium-webfont.woff2') format('woff2'), url('montserrat-medium-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratmedium_italic';
    src: url('montserrat-mediumitalic-webfont.woff2') format('woff2'), url('montserrat-mediumitalic-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratregular';
    src: url('montserrat-regular-webfont.woff2') format('woff2'), url('montserrat-regular-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratsemibold';
    src: url('montserrat-semibold-webfont.woff2') format('woff2'), url('montserrat-semibold-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratsemibold_italic';
    src: url('montserrat-semibolditalic-webfont.woff2') format('woff2'), url('montserrat-semibolditalic-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratthin';
    src: url('montserrat-thin-webfont.woff2') format('woff2'), url('montserrat-thin-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'montserratthin_italic';
    src: url('montserrat-thinitalic-webfont.woff2') format('woff2'), url('montserrat-thinitalic-webfont.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

font.montserrat_black {
    font-family: 'montserratblack';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_black_italic {
    font-family: 'montserratblack_italic';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_bold {
    font-family: 'montserratbold';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_bold_italic {
    font-family: 'montserratbold_italic';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_extra_bold {
    font-family: 'montserratextrabold';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_extra_bold_italic {
    font-family: 'montserratextrabold_italic';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_extra_light {
    font-family: 'montserratextralight';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_extra_light_italic {
    font-family: 'montserratextralight_italic';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_italic {
    font-family: 'montserratitalic';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_light_italic {
    font-family: 'montserratlight_italic';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_medium {
    font-family: 'montserratmedium';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_medium_italic {
    font-family: 'montserratmedium_italic';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_regular {
    font-family: 'montserratregular';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_semibold {
    font-family: 'montserratsemibold';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_semibold_italic {
    font-family: 'montserratsemibold_italic';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_thin {
    font-family: 'montserratthin';
    font-weight: normal;
    font-style: normal;
}

font.montserrat_thin_italic {
    font-family: 'montserratthin_italic';
    font-weight: normal;
    font-style: normal;
}

To use the fonts, I simple have to do this in the Joplin app

<font class="montserrat_bold">Montserrat</font><br />
<font class="montserrat_bold_italic">Montserrat</font><br />
<font class="montserrat_semibold">Montserrat</font><br />
<font class="montserrat_semibold_italic">Montserrat</font><br />
<font class="montserrat_extra_bold">Montserrat</font><br />
<font class="montserrat_extra_bold_italic">Montserrat</font><br />
<font class="montserrat_medium">Montserrat</font><br />
<font class="montserrat_medium_italic">Montserrat</font><br />
<font class="montserrat_black">Montserrat</font><br />
<font class="montserrat_black_italic">Montserrat</font><br />
<font class="montserrat_extra_light">Montserrat</font><br />
<font class="montserrat_extra_light_italic">Montserrat</font>
<font class="open_sans">Open Sans</font><br />
<font class="open_sans_italic">Open Sans</font><br />
<font class="open_sans_bold">Open Sans</font><br />
<font class="open_sans_bold_italic">Open Sans</font><br />
<font class="open_sans_semibold">Open Sans</font><br />
<font class="open_sans_semibold_italic">Open Sans</font><br />
<font class="open_sans_extrabold">Open Sans</font><br />
<font class="open_sans_extrabold_italic">Open Sans</font><br />
1 Like

I am not sure whether this place is appropriate for posting this quasi-PR, but I feel hesitant to write directly in Github because I want to listen to the community’s opinions, so…

I see that many people consider that the cross-platform syncing of the custom CSS file across desktop and mobile devices is important and that the inline tag <link rel="stylesheet" href="..."> is sufficient for their use cases. However, to me, having to append inline CSS links to every single note does not feel great.

Mimicking the current implementation in the desktop app (e.g., reading %HOMEPATH%/.config/joplin/userstyle.css in Windows), I have implemented the following in my github fork supporting custom CSS in mobile devices:

  1. On the startup of the application, it reads ${RNFS.ExternalDirectoryPath}/userstyle.css and passes the CSS string to the Markdown-to-HTML renderer.
    In Android, ${RNFS.ExternalDirectoryPath} is /Android/data/net.cozic.joplin/files/ if the app is installed in the internal storage.

  2. I have added to the <body> tag class="mobile android" attribute in Android, and "mobile iOS" in iOS.
    This attributes can help to create a CSS file applicable to multiple platforms.

Do you think this commit can make it to upstream, or is it too tailored to my own use case?


In the long run, I agree that a proper implementation should support the features discussed earlier, for example cross-platform syncing and selecting a file from filesystem mentioned by @tessus and @CalebJohn. In the mean time, however, it would be great if we can use custom CSS files in mobile devices using the above way, similar to the current implementation in the desktop app.

3 Likes

Is there a hope that we see this improvements in master version?
As I get it fork will have no further updates.

I think it's time to close this topic in favor of the main CSS topic:

Share your CSS