Regression: Local @imported CSS no longer accessible

Operating system

Linux

Joplin version

3.1.22

Desktop version info

Joplin 3.1.22 (prod, linux)

Client ID: 9959465eea794e60b1b95760c308ca91
Sync Version: 3
Profile Version: 47
Keychain Supported: No

Revision: da10297

Automatic Backlinks to note: 3.0.3
Backup: 1.4.2
Combine notes: 1.2.2
Conflict Resolution: 1.2.3
Diff view: 0.1.1
Favorites: 1.3.2
Hotfolder: 1.2.0
Inline tags: 1.3.0
Inline TODO: 1.7.1
Kminder Mindmap: 0.8.6
Markmap: 1.7.0
Note list (Preview): 1.1.0
Note overview: 1.7.1
Note Rename: 1.0.0
Note Tabs: 1.4.0
Persistent Editor Layout: 2.2.0
Quick Links: 1.3.2
Resume Note: 0.1.1
Space Indenter: 0.2.5
Tagging: 1.0.3
Templates: 2.4.0

Sync target

Joplin Cloud

Editor

Markdown Editor

What issue do you have?

I highly customize many of my notes with various CSS stacks. I used to be able to import local CSS from anywhere on my filesystem and also from a URL on the internet. The remote (URL on the internet) still functions just fine, but the local imports seem to be failing now. Here's what I see in the debug console:

Result of @import "/home/todd/pathto/manuscript.css";
GET joplin-content://note-viewer//tmp/.mount_joplin9pVwSp/resources/app.asar/gui/note-viewer/file/home/todd/pathto/manuscript.css net::ERR_UNEXPECTED

Result of @import "file:///home/todd/pathto/manuscript.css";
Not allowed to load local resource: file:///home/todd/pathto/manuscript.css

Thank you for reporting this!

Issue cause & explanation: The local CSS import failures are related to moving the note viewer from a file:// URL to a custom joplin-content:// protocol. This change moves the note viewer to a separate process, which should improve the note viewer's performance on large notes and security if a bug exists in Joplin's HTML sanitizer. This does, however, prevent the note viewer from directly loading file:// resources.

Workaround: I've just created an experimental plugin that may help work around this issue. At present, the plugin isn't in the Joplin plugin repository.

Edit: Updated plugin link.

Firstly, thank you for tackling this regression.

installed and now testing …

My my CSS file and CSS note

(1) /home/todd/joplin-css-test.css:

p { color: green !important; }

(2) resource cd684768653248e09c681b2d85b247cb:

<style>
    p { color: green !important; }
</style>

CORRECTION: that resource did not have the <style></style> elements. Since, I treated it like a CSS file. And so, I was just the same as the on-disk joplin-css-test.css file:

p { color: green !important; }


Test Notes

My test note v1:

<style>
    p { color: green; }
</style>

This should be green.

Result: the text color is indeed green. No errors in the console. But all notes turn green (even if they don't call the CSS). :wink: The CSS should be flushed every time you move off of the note (or change the note at all). And it should never apply to any other note. Heh.

image


My test note v2:

<style>
    @import ":/cd684768653248e09c681b2d85b247cb";
</style>

This should be green.

Result: the text color is indeed green. No errors in the console. All notes turn green (even if they don't call the CSS). :wink:


My test note v3:

<style>
    @import "/home/todd/joplin-css-test.css";
</style>

This should be green.

Result: the text color is indeed green. No errors in the console. All notes turn green (even if they don't call the CSS). :wink:


My test note v4:

<style>
    @import "file:///home/todd/joplin-css-test.css";
</style>

This should be green.

Result: the text color is indeed green. There IS an error in the console. And again, all notes turn green (even if they don't call the CSS). :wink:

Not allowed to load local resource: file:///home/todd/joplin-css-test.css
1 Like

Currently, CSS imports from other notes only supports CSS included in a Markdown css codeblock (no <style> tag support). For example,

```css
  p { color: green !important; }
```

I do think supporting content wrapped in <style> would make sense.

This should be fixed in v0.0.3. Previously, when switching notes, plugin-applied CSS was only cleared if the new note also included a <style> block with a file/note @import.

This is still an issue in v0.0.3 (see known issues). Currently, CSS processing happens after the rendered note is loaded by the note viewer. As a result, Electron briefly applies the CSS before @imports are resolved by the plugin.

It should be possible to fix this by extracting file/note @imports while rendering the note (rather than after).

CORRECTION: that joplin resource did not have the <style></style> elements. Since, I treated it like a CSS file. And so, I was just the same as the on-disk joplin-css-test.css file:

p { color: green !important; }

Comment above updated.

Thank you for 0.0.3 … The CSS is not applied to all notes now and I appreciate that. :slight_smile:


When you state (in the github repo) that "Using CSS supports/layer import options is unsupported." Do you mean the cascading style sheet does not cascade? Because, currently, it does not.

I updated that Joplin resource cd684768653248e09c681b2d85b247cb

@import ":/7405d65b05bd4d948139f3c74cfa093c";
p { color: green !important; }

… where 7405d65b05bd4d948139f3c74cfa093c is …

H3 { color: blue !important; }

 

My new note is this:

<style>
    @import ":/cd684768653248e09c681b2d85b247cb";
    body { background: white; }
</style>

### This should be blue

This should be green.

image

No — that's a different issue! The CSS spec allows for conditional import rules and specifying layer()s. These currently aren't supported by the plugin.

CSS import replacement in @imported files currently isn't implemented (though it's planned!). Update: Version 0.0.5 supports recursive CSS imports.

You are a busy person. Thanks!

Ok. I hate to keep slamming you with the bug reports. But ... CSS cascades with relative pathing.

I.e., I just made a joplin-css-test-layer2.css:

H3 { color: blue !important; }

And the joplin-css-test.css is:

@import "joplin-css-test-layer2.css";
p { color: green !important; }

Both of those live in my $HOME directory.

And the note is:

<style>
    @import "/home/todd/joplin-css-test.css";
    body { background: white; }
</style>

### This should be blue

This should be green.

That H3 does not render blue, and the error message is …

GET joplin-content://note-viewer//tmp/.mount_joplinfPtiJG/resources/app.asar/gui/note-viewer/joplin-css-test-layer2.css net::ERR_UNEXPECTED

As I am sure you are aware, CSS that calls CSS uses relative pathing unless, of course, an absolute path is given.

Great progress, though. Glad to see this activity and energy for a project.

Thank you for testing!

For now, relative CSS paths need to start with ./ or ../ to be recognised as such. For example,

@import "./joplin-css-test-layer2.css";
1 Like

Interesting. That works … for the simple case. My more complex CSS fails for some reason. These are the things I attempted in an attempt to reduce the errors.

  • added ./ to the @import calls
  • moved all imports above the comments in the cascading sheets. I don't think your plugin likes multi-line comments
  • removed things like ./css-file.css?rev=20241107 from import calls. I don't think the plugin likes the ?X=Y URL semantic. I use those in an attempt to force cache updates.

Here's the current error I see in the console that may be relevant:

viewer.js:97 CSS import error TypeError: Failed to fetch
    at /home/todd/.config/j…local-css.js:550:24
    at Generator.next (<anonymous>)
    at /home/todd/.config/j…local-css.js:532:71
    at new Promise (<anonymous>)
    at __webpack_modules__.492.__awaiter (/home/todd/.config/j…local-css.js:528:12)
    at fetchUrlText (/home/todd/.config/j…local-css.js:546:31)
    at /home/todd/.config/j…local-css.js:563:16
    at Generator.next (<anonymous>)
    at /home/todd/.config/j…local-css.js:532:71
    at new Promise (<anonymous>)

Is that just telling me I have some error in my CSS? Regardless, it does not load and I am not sure how to troubleshoot this.

Thank you for helping test the plugin!

I've released a new version with better error handling — previously, if one imported CSS file failed to load, all would fail.

There are still a few problems that may make it difficult to work with more complex CSS with many @imports:

  • No caching — large amounts of CSS may be slow.
  • Although @imports directly in <style> blocks are now identified and replaced with browser APIs, @imports in imported CSS are still found and replaced using regular expressions. As commented above, this can cause issues if @imports are within /* comments */ or don't match the syntax expected by the regular expressions.
  • As commented above, @import '/some/file/here.css' logs errors to the console.
  • There may be other nested-import-related bugs.

Hey hey! It … almost works. :slight_smile:

  • I had to mangle <style> and @import statements from the comment sections of the CSS (there are how-to-use examples in there). This didn't change the rendering, but it removed errors from the console.
  • The way things are rendered implies @media logic is being ignored (screen versus print). I am not sure. Pointing at the same CSS, but via an https URL, does the right thing. (UPDATE: @media is not being ignored! But the CSS is being handled differently, that is for sure.)

This is what I am going to do. Now that it is sorta working, and since the CSS I am referencing is maintained in it's own github project, I will submit my changes, get some decent test example "notes" for you to look at and have you play. That's if you are game for that. It will take me a bit to get that together.



P.S. @import "/path/to/somestyling.css?v=2024110800"; (and it's file:/// equivalent) still won't load.

Got things working …

By brute force. I.e., I had to force a thing that shouldn't have had to be forced.

Question: How is the ordering of CSS managed in Joplin?

Suspicion: I think the erroneous output I witnessed is due to the normalization CSS that is shipped with Joplin being processed AFTER my locally imported CSS, but before any CSS pulled from the internet.

I.e. the ordering of CSS processing is not the same. Joplin should do it's thing, then the note's CSS should be applied after the fact. I can't do !important after every setting in conflict. :slight_smile:

It's just kick-ass that we got this far with this. We all need a personalized refriger, apparently.

That's probably a bug in the plugin!

Currently, @imported CSS is added to the end of the <head> element, which might be before the location of Joplin's note CSS, making the @imported CSS lower precedence. Previously, @imported CSS was added to the main <div id="rendered-md"> (where the original <style> tag is). Joplin, however, replaces the content of <div id="rendered-md"> when the note is updated. This caused custom CSS to briefly be unapplied in the note viewer after making small changes to the note.

Adding @imported CSS to the end of the <body> or just before the start of <div id="rendered-md"> might fix the issue. (Update: Resolved in v0.0.7).

Currently, different types of CSS are processed by the plugin in different ways:

  • <style>-block CSS: Uses the Electron's built-in CSS parser browser API to replace imports. This parser should correctly handle comments. As such, @imports that are commented out should not be replaced.
  • @imported CSS: Uses regular expressions to find @imports. This does not handle comments correctly.
    • In the future, I hope to update the @imported CSS processor to use the APIs that are currently used for <style>-block CSS.
1 Like

Thank you for fixing this with v0.0.7. So far; so good.

New problem

I imported the plugin from file. Then clicked on a note and attempted to export it to PDF, and I got this error message when I click on Save. (Sorry, that it is an image, but Joplin popup text is not selectable, oddly.)

Captured the error from the console:

MainScreen.tsx:467 Error: Error: EINVAL: invalid argument, rmdir '/home/todd/.config/joplin-desktop/tmp/pluginAssets/personalizedrefrigerator-local-css-imports-content-script/.'. Path: /home/todd/.config/joplin-desktop/tmp/pluginAssets/personalizedrefrigerator-local-css-imports-content-script/.
    at FsDriverNode.fsErrorToJsError_ (/tmp/.mount_joplinX7vsG7/resources/app.asar/node_modules/@joplin/lib/fs-driver-node.js:14:24)
    at FsDriverNode.remove (/tmp/.mount_joplinX7vsG7/resources/app.asar/node_modules/@joplin/lib/fs-driver-node.js:50:24)
    at async InteropService_Exporter_Html.close (/tmp/.mount_joplinX7vsG7/resources/app.asar/node_modules/@joplin/lib/services/interop/InteropService_Exporter_Html.js:158:17)
    at async InteropService.export (/tmp/.mount_joplinX7vsG7/resources/app.asar/node_modules/@joplin/lib/services/interop/InteropService.js:397:9)
    at async InteropServiceHelper.exportNoteToHtmlFile (InteropServiceHelper.ts:42:18)
    at async InteropServiceHelper.exportNoteTo_ (InteropServiceHelper.ts:63:15)
    at async MainScreenComponent.printTo_ (MainScreen.tsx:458:21)
    at async Object.execute (exportPdf.ts:56:6)
printTo_ @ MainScreen.tsx:467
await in printTo_ (async)
processTicksAndRejections @ node:internal/process/task_queues:95
await in processTicksAndRejections (async)
execute @ exportPdf.ts:56
processTicksAndRejections @ node:internal/process/task_queues:95
await in processTicksAndRejections (async)
processTicksAndRejections @ node:internal/process/task_queues:95
await in processTicksAndRejections (async)
execute @ /tmp/.mount_joplinX7vsG7/resources/app.asar/node_modules/@joplin/lib/services/CommandService.js:152
(anonymous) @ MenuBar.tsx:292
(anonymous) @ MenuBar.tsx:385
click @ /tmp/.mount_joplinX7vsG7/resources/app.asar/node_modules/@joplin/lib/services/commands/MenuUtils.js:50
apply @ /tmp/.mount_joplinX7vsG7/resources/app.asar/node_modules/@electron/remote/dist/src/renderer/callbacks-registry.js:54
(anonymous) @ /tmp/.mount_joplinX7vsG7/resources/app.asar/node_modules/@electron/remote/dist/src/renderer/remote.js:353
(anonymous) @ /tmp/.mount_joplinX7vsG7/resources/app.asar/node_modules/@electron/remote/dist/src/renderer/remote.js:335
emit @ node:events:514
onMessage @ node:electron/js2c/renderer_init:2

This has been fixed in v0.0.8.

1 Like