Markdown and store image

Well, if the assets are built in or not depends very much on the document, not at all on the database. As has already been discussed many times.

I added my way of seeing it. I’m not arguing that the assets must be in a fixed location. Just noted that this option is kind of non-obvious in the prefs (vs. say a choice between “with doc, with group, with DB”).

And then, one day, they need to split one of their groups into two fire-walled ones? And have to pick the assets subgroup apart?

Anyway, this is all good. The asset mechanism is super-useful and has made md with images viable to me in DT. I tried to point out that with group-based assets, inconspicuous DT operations like “move item” can break the integrity of a document (md file plus assets).

Not sure what this means. I am not discussing the pros and cons of asset integration. I pointed out that chaining assets to groups while still allowing the user to move files into another group (and it is irrelevant in this context that the “segregated cluster” crowd chooses not to do that) will break the integrity of the document. That’s a fact. I’m not aware of another situation in DT where using a common command breaks a document.

I provided a suggestion for a mechanism that avoids that, should your DB organization allow that. Incidentally, my scheme will suffer from exactly the same problem when a document is moved into another DB. In that case, I believe, the only safe method is “1 document = 1 file”.

Thank you very much for sharing this! I think I also tried to set a location for the Import images to group… preference when the feature became available but it wasn’t possible back then (or maybe I did it wrong). It’s great that it’s possible now!

However, there’s a problem:

  • If the Markdown record and the Assets group are both located in the database’s root, then DEVONthink does not use the image’s item link (i.e. its “x-devonthink-item” link).

  • Instead it inserts a relative link.
    (I guess that’s because using a relative link is the normal behaviour for records that have the same location as the Assets group.)

That’s unfortunate as it makes the usage of /Assets (i.e. usage of a location instead of a name) somehow inconsistent. @cgrunenberg could this be changed, i.e. if the preference starts with a slash then always use the item link?

@gg378 it’s probably possible to create a Smart Rule script that moves linked images into the database into which a Markdown record was moved. Almost there but still not sure.

1 Like

Currently the used link depends on the location of the image and the document - if possible a relative link is preferred as that’s more compatible to other apps.

1 Like

This Smart Rule script moves linked images to the record’s new database.

It’s important that you set e.g. /Assets in preferences, see below. It won’t work if there’s no slash.

What

The script checks

  • whether an image is also linked in other records
  • whether there’s already an identical image in the new database’s Assets group
    (“identical image” means an image that would be a duplicate if both images shared a database)

Depending on these checks different things happen:

  • If only 1 record links to the image

    • If there’s no identical image in the new database’s Assets group:
      Move image to new database’s Assets group

    • If there’s an identical image in the new database’s Assets group:
      Replace link URL with identical image’s reference URL and move unnecessary image to trash

  • If more than 1 record links to the image

    • If there’s no identical image in the new database’s Assets group:
      Duplicate image and replace link URL with duplicated record’s reference URL

    • If there’s an identical image in the new database’s Assets group:
      Replace link URL with identical image’s reference URL

Setup

Preferences

  • Go to preferences Files > Markdown
  • Set a location for Import images to group… , e.g. /Assets
    Note the slash at the start.

Script properties

Click

Set property theRecordHistory_Name_End to your Record History records’ name end. If the name is 2022-03-10 My daily record history then set the property to My daily record history.

The script uses the linked image’s incoming references. If you use the “Record History” script and open an image then it’s added to the Record History, which means the Record History record is also added to the image’s incoming references. Because the Record History and the records this script was written for both are Markdown there’s no way to distinguish between them other than filtering by name.

Smart Rule

Create a Smart Rule:

  • Search in: Databases
  • Matching:
    • All
      • Content: ![
      • Content: x-devonthink-item://
      • Kind: Markdown
  • Event: On Moving
  • Action: Execute Script (AppleScript)

2022-03-10_16-29-37

-- Smart Rule script - Move Markdown records' linked images

property theImageDestinationLocation : "/Assets" -- Note the "/" at the start. This script is build for Assets groups that live in databases' root.
property theRecordHistory_Name_End : "Record History" -- If you use the "History Record" script then it's necessary to set this property to the History Record's name end (e.g. "Record History")

on performSmartRule(theRecords)
	tell application id "DNtp"
		try
			repeat with thisRecord in theRecords
				set thisRecord_Text to plain text of thisRecord
				
				--------------------------------------------------- Create Assets group in new database ----------------------------------------------------
				
				set theImageDestinationGroup_NewDatabase to create location theImageDestinationLocation in database of thisRecord
				
				--------------------------------------------------- Get Assets group in old database ----------------------------------------------------
				
				set thisRecord_Text_TextItems to my tid(thisRecord_Text, "![")
				set theImageDestinationGroup_OldDatabase to missing value
				
				repeat with thisTextItem in thisRecord_Text_TextItems
					set thisTextItem to thisTextItem as string
					if thisTextItem contains "](" then
						set thisTextItem_Part to ((characters ((offset of "](" in thisTextItem) + 2) thru -1) in thisTextItem) as string
						set thisLinkURL to ((characters 1 thru ((offset of ")" in thisTextItem_Part) - 1)) in thisTextItem_Part) as string
						if thisLinkURL starts with "x-devonthink-item://" then
							set thisImage to (get record with uuid thisLinkURL)
							set thisImage_LocationGroup to location group of thisImage
							if ("/" & (name of thisImage_LocationGroup)) = theImageDestinationLocation then
								set theImageDestinationGroup_OldDatabase to thisImage_LocationGroup
								exit repeat
							end if
						end if
					end if
				end repeat
				
				if theImageDestinationGroup_OldDatabase ≠ missing value then
					if theImageDestinationGroup_NewDatabase ≠ theImageDestinationGroup_OldDatabase then
						
						set thisRecord_Text_new to thisRecord_Text
						
						----------------------------------- Get images in old database's Assets group that link to record ------------------------------------
						
						set theImages to (children of theImageDestinationGroup_OldDatabase whose incoming references contains thisRecord)
						
						repeat with thisImage in theImages
							
							------------------------------ Check whether there's an identical image in new database's Assets group -------------------------------
							
							set theImageDestinationGroup_NewDatabase_Children to (children of theImageDestinationGroup_NewDatabase whose content hash = content hash of thisImage)
							if theImageDestinationGroup_NewDatabase_Children = {} then
								set theExistingDuplicateImage_ReferenceURL to missing value
							else
								set theExistingDuplicateImage_ReferenceURL to (reference URL of item 1 of theImageDestinationGroup_NewDatabase_Children) as string
							end if
							
							------------------------------------------------ Check how many records link to the image -------------------------------------------------
							
							set thisImage_IncomingReferences to (incoming references of thisImage whose name without extension does not end with theRecordHistory_Name_End)
							set thisImage_IncomingReferences_Count to (count of thisImage_IncomingReferences)
							
							if thisImage_IncomingReferences_Count = 1 then
								
								----------------------------------------------- Move image to new database's Assets group ------------------------------------------------
								
								if theExistingDuplicateImage_ReferenceURL = missing value then
									set thisImage to move record thisImage to theImageDestinationGroup_NewDatabase
									
									-------------------- Replace link URL with identical image's Reference URL and move unnecessary image to trash ---------------------
									
								else if theExistingDuplicateImage_ReferenceURL ≠ missing value then
									set thisRecord_Text_new to my tid(my tid(thisRecord_Text_new, (reference URL of thisImage) as string), theExistingDuplicateImage_ReferenceURL)
									move record thisImage to trash group of database of thisImage
								end if
								
							else if thisImage_IncomingReferences_Count > 1 then
								
								----------------------------- Duplicate image and replace link URL with duplicated record's Reference URL ------------------------------
								
								if theExistingDuplicateImage_ReferenceURL = missing value then
									set thisDuplicatedImage to duplicate record thisImage to theImageDestinationGroup_NewDatabase
									set thisRecord_Text_new to my tid(my tid(thisRecord_Text_new, (reference URL of thisImage) as string), (reference URL of thisDuplicatedImage) as string)
									
									----------------------------------------- Replace link URL with identical image's Reference URL ------------------------------------------
									
								else if theExistingDuplicateImage_ReferenceURL ≠ missing value then
									set thisRecord_Text_new to my tid(my tid(thisRecord_Text_new, (reference URL of thisImage) as string), theExistingDuplicateImage_ReferenceURL)
								end if
								
							end if
						end repeat
						
						if thisRecord_Text_new ≠ thisRecord_Text then set plain text of thisRecord to thisRecord_Text_new
						
					end if
				else
					log message info "Smart Rule: Couldn't find the moved record's old \"Assets\" group" record thisRecord
				end if
			end repeat
			
		on error error_message number error_number
			if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
			return
		end try
	end tell
end performSmartRule

on tid(theInput, theDelimiter)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	if class of theInput = text then
		set theOutput to text items of theInput
	else if class of theInput = list then
		set theOutput to theInput as text
	end if
	set AppleScript's text item delimiters to d
	return theOutput
end tid
1 Like

Wow, @pete31 , is there anything you can’t write a script for :smile: ? This is good to know. I also realized that if the assets are stored as x-items, even if the md file is moved to another DB, as long as all DBs are open, the asset can still be called up.

One downside of the DB-root x-item asset storage: A web page export of a group will not bring along the assets. I haven’t tried, but presumably with per-group storage that would work.

In the end, none of this is unsolvable and could be easily integrated into DT. This shows that the “asset” mechanism was a quick bolt-on that has not the same level of integration and consistency as all the other mechanisms in DT. I guess there was big pressure from parts of the md crowd to make decent image management possible. And the solution is definitely decent.

I should not end this with a Markdown rant, but here I go (disclaimer: I really like md): It was conceived as an extremely simple, portable tool to do a few basic formattings. I get that. But then, presumably because some needs arose, all sorts of things got bolted on, leading to mmd. The fine line of what features should be included and what not is sometimes hard to rationalize: Either stick to the simple guns and say “no images, period”, or allow simple image scaling commands (it’s almost as if they were inspired by NextStep’s RTFD). Drawing the KISS line there is strange. Same with the fact that bold face and italics are apparently important, but not font colour or size. I use md extensively in Jupyter notebooks, where rich documentation of computations is key for me. So I ended up writing all sorts of TextExpander shortcuts to quickly toss html-constructs into the md sections to get done what I need. I also use PmWiki extensively for my work, and it shows how very simple basic commands (essentially identical to Markdown) can be combined with ever increasing complexity for those who can/want wield it. No downside to that approach as far as I see.

2 Likes

I think it was conceived as an extremely simple tool to produce HTML without having to write it. That’s the reason why images are handled exactly as they’re in HTML: as external entities that the documents link to.

Whatever came later arose from the fact that people wanted to use md for purposes it was not meant for. Like „formatting“.

That’s just markdownish for the HTML elements strong and emphasize. Color and font size are handled by CSS, as well they should be.

Markdown was not about presentation but about semantics. Just like HTML. It seems that this origin is forgotten more and more. Which leads to all sorts of „improvements“ and „solutions“ to perceived problems. But unfortunately not to the decision to move to another format that does what one wants. Like include images and allow for easy font-color/size setting directly in the document.

2 Likes

@chrillek: I’m sure you’re right. I’m only vaguely familiar with the beginnings of Markdown. Wiki languages like PmWiki came along for a similar reason. The verboseness of html is in that sense very unfortunate. Same with TeX/LaTeX and Pascal. You can only deal with that many “begin{something}” (I write a lot of LaTeX). Nevermind, there are good reasons for doing it that way, so I won’t knock that (who am I to knock anything that Don Knuth has ever done).

Just as an example, PmWiki, which is also just a front end to rendering html, shows that it’s rather easy to go all the way. At the user level, it makes it entirely transparent whether something is a html element (strong/emphasize) or something coming through CSS. It just works. Now, while this has not been done to my knowledge, someone could write a PmWiki-markup standalone renderer for DT, Typora, etc., and it would function the same way as md, and it would be rather complete in its ability without having to resort to unbearably verbose html/css constructs inside your documents. I was under the impression that one basic tenet of these markup languages is that the un-rendered ascii text source is still human readable, which html and TeX are decidedly not. Markdown and PmWiki markup are, but Markdown with too many html constructs thrown in (like in my Jupyter notebooks) isn’t.

Inside DT and similar settings, md seems to have taken on a slightly different role, namely that of a portable, nimble format for (semi)rich documents, filling the remarkable void between plain text notes and full-fledged Word level stuff. For that purpose, it seems a bit underpowered, in my opinion, which no one has to take seriously :slight_smile: .

That’s one of the reasons I never got around to using (La)TeX – too much to type (that, and the penchant for american typographic conventions). Also, (La)TeX is kind of a hybrid language, since it deals with presentation and semantics at the same time.

Correct. HTML is readable (kind of), but it’s certainly not fun to do so. And, interestingly, Word, Pages etc. all use XML now under the hood – i.e. a human readable format (after you’ve unpacked the ZIP archive they use to hold everything together). Not that any human in their right mind would want to read that :crazy_face:

That is apparently the perception of many people. At least here – I haven’t seen the same kind of discussion in the context of Draft. In some enviroments, there seems to be a tendency to bend this format to perform something it was not meant to. Like the endless discussions about images: There are tons of formats that can handle embedded images just fine. MD is not one of them. It was not designed to be. In my mind, note taking is something quick and dirty (I’m not talking about the content here) – it’s a note, not a full fledged article. But it seems the borders between different forms of text are getting blurred.

1 Like

Good points. That made me realize that I’m probably not really expressing well what I’m after. Indeed I should not tell the md crowd or devs what to do for the sake of just md. As a standalone tool, say the quick and dirty notes you mention, I can take it or leave it. And hence, for outside DT, my go-to quick and dirty note tool is Apple Notes. It could be Draft. You name it.

But once a format is adopted within a larger system, then format choices will go away. Let me try to illustrate that:

Jupyter devs chose md as their rich-text format. Great, especially with embedded LaTeX math, it’s very handy. But my use case to make powerful Jupyter documents for my research makes good (i.e. painlessly easy to use) image usage (and also tables) a must. Other than Mathematica there is no other tool out there that would do the job. So all those “tons of formats that can handle images just fine” become irrelevant. I’m chained to md because of Jupyter.

Now, in DT, the use of md docs is not mandatory, but it is compelling. An editable document format is particularly useful in DT when it is

  1. indexable
  2. viewable within DT (ideally natively, but at least with a preview mechanism)
  3. editable within DT (for fast workflow)

(text, rtf, rtfd, formatted notes, md) fullfil all 3, whereas Pages and Word fulfill (1), (2) partly, but not (3). Md is in my view the best of the first group, and so, for a DT user, it assumes the de facto role of the most capable, most future-proof (cf. rtf) internal document creator/editor/viewer. And that’s why many people clamour for certain features in it. At that point, the history and philosophy behind it take a back seat. People notice that it’s a cool format, and so close to doing what they want or need. And then they notice that in mmd, a lot of features do eventually show up, so it’s not that devs are very principled either. That at least partially excuses why people suggest features.

The introduction of image assets in DT md, either by dragging a file in or by pasting an image, was a game changer to me. I still would not mind at all the ability to scale the image, but I understand that that’s beyond DT, without breaking md compatibility (unless they spearhead another mmd flavour).

And I was then particular excited to find that I can collect all my image assets in one, x-item-referenced, location, which makes for my use case (and only mine!) the use entirely foolproof. I hindsight, I should have just pointed out the ability to put a slash in front of the asset folder name to make the location absolute. I should have abstained from remarking on the (factual) non-universality of the group-based location, which has its own advantages, as was pointed out.

2 Likes

Thank you very much for this thorough assessment. Although I’m not happy with it, it describes the situation quite accurately.

Also, I learned a lot about Jupyter notebooks –thanks for that, too!

As to „scalable“ images: why not use CSS for that? But you’ve opened a new thread for that,
so I’ll head over there.

When I move the markdown document, I got this error in the log:

“Smart Rule: Couldn’t find the moved record’s old "Assets" group”

The image in the markdown document is not moved. The markdown document now has a broken link, image not displayed.

Under Preferences Files > Markdown, I have Set the location to /MyMDImages

In the script, I have also change the property theImageDestinationLocation : “/MyMDImages”

Is there anything else I need to change?

Just an update of my observation. It seems the problem is that the image is not an x-devonthink-item link due to my md file is at the root of the DB.

More details here:

Indeed. @pete31 pointed that out. I would not have run into this problem as I never put documents at the root level. From @cgrunenberg 's explanation, this effect is hardwired into DT and we cannot influence it.

I am curious why md file at root level cannot create x-devonthink-item linked image? I have many md files at root level with x-devonthink-item link. I manually import the image. Copy the item link and Paste the item link.

It is not a question of whether it can be done. It’s a judgement call by the devs when to choose one or the other. As @cgrunenberg explained above:

Currently the used link depends on the location of the image and the document - if possible a relative link is preferred as that’s more compatible to other apps.

There are advantages to regular paths/links: They can be interpreted straightforwardly by other apps. So for example, if I want to use Typora as an external md-editor, it will not be able to make sense of the x-items. Any technique that relies on x-item links must be understood to only work within DT.

1 Like

Ok. It makes sense to make it more portable, more usage for other external app to use relative path.

I thought that has already been covered if you use a name, such as Asset, without the \. You will get a relative path.

For those who wants x-item, you have to use \. That will generate x-item link, except at the root level. So, question is why exclude md file at root level?

For one, this is a DT design decision, so I can’t really answer authoritatively. Having said that, I think the decision made by DT is not “absolute vs. relative path”, but rather “is the resource in a subfolder or not”. Documents put at the root level are special in the sense that any other document (other than those that are also at the root level) will by definition be in a subfolder (even if by more than one level).

We might add a preference to choose the desired behaviour if there should be a common need for it.

1 Like