22
33import json
44from pathlib import Path
5+ import re
56
67from jimmy import common , converter , intermediate_format as imf
8+ import jimmy .md_lib .common
9+
10+
11+ DRAFTS_LINK_RE = re .compile (
12+ r"(drafts:\/\/open\?uuid="
13+ r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}))"
14+ )
715
816
917class Converter (converter .BaseConverter ):
1018 accepted_extensions = [".draftsexport" ]
1119
12- @common .catch_all_exceptions
13- def convert_note (self , draft ):
20+ def __init__ (self , * args , ** kwargs ):
21+ super ().__init__ (* args , ** kwargs )
22+ self .note_id_title_map = {}
23+
24+ def handle_drafts_links (self , note_body : str ) -> imf .NoteLinks :
25+ """
26+ Drafts links are raw text. Not wrapped in Markdown syntax.
27+ https://docs.getdrafts.com/docs/drafts/cross-linking#draft-links-urls
28+ """
29+ note_links = []
30+ for original_text , id_ in DRAFTS_LINK_RE .findall (note_body ):
31+ note_links .append (imf .NoteLink (original_text , id_ , "" ))
32+ return note_links
33+
34+ def handle_wikilink_links (self , body : str ) -> imf .NoteLinks :
35+ # https://docs.getdrafts.com/docs/drafts/cross-linking#wiki-style-cross-linking-drafts
36+ note_links = []
37+ for _ , url , _ in jimmy .md_lib .common .get_wikilink_links (body ):
38+ original_text = f"[[{ url } ]]"
39+ if url .startswith ("d:" ):
40+ best_match_id = common .get_best_match (url [2 :], self .note_id_title_map )
41+ if best_match_id is not None :
42+ note_links .append (imf .NoteLink (original_text , best_match_id , "" ))
43+ elif url .startswith ("u:" ): # id
44+ note_links .append (imf .NoteLink (original_text , url [2 :], "" ))
45+ elif url .startswith ("w:" ) or url .startswith ("s:" ):
46+ pass # TODO: How to handle workspaces and search?
47+ else : # no prefix
48+ best_match_id = common .get_best_match (url , self .note_id_title_map )
49+ if best_match_id is not None :
50+ note_links .append (imf .NoteLink (original_text , best_match_id , "" ))
51+ return note_links
52+
53+ @staticmethod
54+ def get_title (content : str ) -> str :
1455 # There is no title in drafts.
1556 # Simply take the first 80 characters of the first line.
16- title = draft ["content" ].partition ("\n " )[0 ][:80 ].strip ()
57+ if content .strip ():
58+ for line in content .split ("\n " ):
59+ if line .strip ():
60+ return line [:80 ].strip ()
61+ return common .unique_title ()
62+
63+ @common .catch_all_exceptions
64+ def convert_note (self , draft ):
65+ title = self .get_title (draft ["content" ])
1766 self .logger .debug (f'Converting draft "{ title } "' )
1867
1968 if draft ["languageGrammar" ] not in ("Markdown" , "Plain Text" ):
@@ -32,6 +81,10 @@ def convert_note(self, draft):
3281 original_id = draft ["uuid" ],
3382 )
3483
84+ wikilink_note_links = self .handle_wikilink_links (note_imf .body )
85+ draft_note_links = self .handle_drafts_links (note_imf .body )
86+ note_imf .note_links = wikilink_note_links + draft_note_links
87+
3588 if draft .get ("flagged" , False ):
3689 note_imf .tags .append (imf .Tag ("drafts-flagged" ))
3790
@@ -45,5 +98,10 @@ def convert_note(self, draft):
4598 def convert (self , file_or_folder : Path ):
4699 input_json = json .loads (file_or_folder .read_text (encoding = "utf-8" ))
47100
101+ # first pass: for internal links, we need to store the note titles and IDs
102+ for draft in input_json :
103+ self .note_id_title_map [draft ["uuid" ]] = self .get_title (draft ["content" ])
104+
105+ # second pass: convert the notes
48106 for draft in input_json :
49107 self .convert_note (draft )
0 commit comments