obsidian-html / obsidian-html

Python code to convert Obsidian notes to proper markdown and optionally to create an html site too.
https://obsidian-html.github.io
GNU General Public License v3.0
337 stars 50 forks source link

Different style for transcluded blocks #766

Open katylava opened 8 months ago

katylava commented 8 months ago

In Obsidian, transcluded blocks are styled differently:

Transclusion_Examples_-_tmp-obsidian-devdocs_-_Obsidian_v1_5_3

I would like it if they were styled differently by obsidian-html as well, or at least had a class name on the element so I could style them differently myself.

dwrolvink commented 8 months ago

I just released a new patch version that implements this. To upgrade use pip install --upgrade obsidianhtml this should get you to v4.0.1 You can use obsidianhtml version to check your version.

I stopped development on this package for the time being, but I had some spare time and this was a very simple one. I didn't have time to test this well for edge cases though, so for now this is an opt-in setting, read more here for how to enable it:

katylava commented 8 months ago

i appreciate your time, thank you!

Gewerd-Strauss commented 8 months ago

I am not sure if this implementation is the soundest design to go for. Or maybe the docs will need to get a warning.

I am currently playing around with the changes in 4.0.x, cuz I am curious and want to see if there is anything I am interested in. My understanding of this version is that it is intended to do two things:

When opening up the logic to multiple authors we obviously increase the likelihood of overlapping extensions being developed.

Such is the case for me while dabbling with the current state cafcb031c2a75c1487e2b53bb132c18ff0e86fdf:

This is the change I will be talking about, which modifies the included_page.page by applying styling in MarkdownPage.py:

if self.pb.gc("toggles/wrap_inclusions", cached=True):
                included_page.page = f'\n<div class="inclusion" markdown="1">\n{included_page.page}\n</div>\n'

            self.page = self.page.replace(matched_link, included_page.page)

Rivalling this, I have a function in my private clone (the public version is not a function though), which allows flush integration of a note-section if the inclusion does not sit flush at the start of a line:


# Header

1: This is a main note, and I want to embed text on a new line:  
![[019-ObsHTML_EmbeddedTitleStripping_Child#A Section]]|||

2: This is a main note, and I want to embed text inline which is ![[019-ObsHTML_EmbeddedTitleStripping_Child#A Section]]|||

3: Indented:  
        ![[019-ObsHTML_EmbeddedTitleStripping_Child#A Section]]|||

# Full-Note

4: This is a main note, and I want to embed text on a new line:  
![[019-ObsHTML_EmbeddedTitleStripping_Child]]|||

5: This is a main note, and I want to embed text inline which is ![[019-ObsHTML_EmbeddedTitleStripping_Child]]|||

6: Indented  
        ![[019-ObsHTML_EmbeddedTitleStripping_Child]]|||

becomes


# Header

1: This is a main note, and I want to embed text on a new line:  

# A Section
containing words.  
And a paragraph starting on the next line. yay.

And another one.

|||

2: This is a main note, and I want to embed text inline which is containing words.  
And a paragraph starting on the next line. yay.

And another one.
|||

3: Indented:  
containing words.  
And a paragraph starting on the next line. yay.

And another one.
|||

# Full-Note

4: This is a main note, and I want to embed text on a new line:  

This is a child note

# A Section

containing words.  
And a paragraph starting onthe next line. yay.

And another one.

# A second section

**containing different words**

And another paragraph, but with a real double-newline-break inbetween.  
Whee.  
Yea, this is the end, I guess.

|||

5: This is a main note, and I want to embed text inline which is This is a child note

A Section

containing words.  
And a paragraph starting on the next line. yay.

And another one.

# A second section

**containing different words**

And another paragraph, but with a real double-newline-break inbetween.  
Whee.  
Yea, this is the end, I guess.
|||

6: Indented  
This is a child note

A Section

containing words.  
And a paragraph starting on the next line. yay.

And another one.

# A second section

**containing different words**

And another paragraph, but with a real double-newline-break inbetween.  
Whee.  
Yea, this is the end, I guess.
|||

The point I am trying to make is that such features are both useful, but they would block each other. Of course this is a very specific scenario which just happens to operate on the same string as the extension for toggles/wrap_inclusions - but the point still stands, that extensions must be documented as to what they modify, and that in the scenarion of multiple extensions getting added over time there could be wildly different results:

  1. if the author hard-defines a hacking order in which extensions are called. Earliest extensions have the "luck" of having least-deviating datainput directly from preceeding steps of ObsidianHTML. With each run extension, the next will get decreasingly reliable input.
  2. If a method of defining calling-order were to be implemented, this would ensure the most flexible solution. However, it would also be extremely ludicrously easy to break because extensions could interfere (as is the case "here").

This is how I understood the direction to be: user-written modules are forced to assume set input data and set output data - e.g. all extensions to "MarkdownPage" get called after point X (whatever stept that would be is likely up for debate), are given access to variables X Y Z, and can do whatever they want with it - except they must return valid output for A B C. A B C is considered input for extension2() and so on.

In this scenario, valid output could be included_page.page, which would then be fed to the next extension which wants to modify included_page.page. But these two extensions both replace the includes, so whoever gets to work first wins.

I personally don't have an idea about a proper solution here. However, I want to point out this potential issue as soon as possible, in case authors & other contributors are not aware of this.

Thank you. Sincerely, ~Gw


P.S.: I will be handing in my own extension as a suggested solution to the problem I was working on in a couple of days (if I get the time, that is - life is somewhat hectic currently.)

dwrolvink commented 8 months ago

First of, indeed this should be ideally put into a module, but I have little time and this was a quick fix to implement. If you have a module that can do this then by all means share it, we can add it to the default modules, and add it at the right position in the module list so that it will get input that it can work with. That would allow me to remove this patch again, as that was indeed the direction I was moving in - removing all note manipulations into modules.

Regarding controlling this issue of what input is expected and what is given; this will be very difficult to do in a reliable way. The way I thought about it is that every module has a description/readme/wiki page that explains what it does to the output, and then users should be able to piece together what adding/moving/removing a module will do in the greater scheme of things.

Users that don't want to get into the whole module system should get a module list that "just works", containing only built-in modules so that a random module builder can't just sneak in some malware, or more likely, change what the module does in a subtle way that breaks the assumption of the other modules.

Gewerd-Strauss commented 8 months ago

Just to be sure, I want to emphasize that I don't want this to read as a criticism.

Also, clarification: I'm not working on a stand-in module for this issue, but for a different issue which I have talked about once a long time ago, but so far kept private. I am still not sure if it is sensible because - in the same manner as this - it would have to be opt-in because it inherently results in self.page being populated with contents of all included_page.page for which matched_link resolves to a section à la ![[note#section]].

This would be opt-in anyways because its useful, but not casually expected output.

I will try and look into how the modules are organised in the coming days, but I am fairly busy with my thesis-preparation and have my mind on other code mostly. So... Yea. Same boat I'd say.

The issue on my end is that I have no experience writing python modules, and have no proper overview on the organisation of this project. I don't even know if there is developer docs on the module system with a good overview or not. Again, I just converted my shit to an actual function, instead of hard-coding it into Markdownpage.py the same way the solution to this issue here is. But this function still has to be called by ConvertObsidianPageToMarkdownPage when handling self.page = self.page.replace(matched_link, included_page.page). Which makes stuff a bit hard to write in, until we figure out how to properly schedule module-calls in the correct order.

I have thoughts on how to design stuff like this in principal, but... yea. Stuff's complicated.

dwrolvink commented 8 months ago

So yeah we're both busy, let's put a pin in it for now 😄

Indeed, we need modules that can be called from inside that function, or have each module result in partial output. Haven't made modules yet that touch the actual conversion process, just the setup.

I'm starting to have some more time, in the coming months I might work on modules being called in that step of the process.

Tutorial on creating a basic module can be found here: https://obsidian-html.github.io/configurations/modules/developer-documentation/creating-a-custom-module.html

Good luck on your thesis!

Gewerd-Strauss commented 8 months ago

Tutorial on creating ...

I have taken a somewhat closer read on this over the past couple days. For now I have decided to make a private fork containing all the changes I require for my thesis (the feature I outlined above, utf16-handling, some other warnings, changes to default-config,...) because I need them now and public for reproducibility when I submit at some point in the semi-distant future, but can't wait for the development to reach the point where I can introduce them proper via FRs/PRs. However, as long as we have not decided on a clear path forward on the future of MarkdownPage.py, I will run this local branch. It doesn't seem sensible for myself to design a module right now, so long as we don't know how this is initially implemented.

(I want to point out that I don't want you to read this as a critique on development speed or anything!)


To wrap this up here for now;

Good luck on your thesis!

Thank you.