Skip to content

Commit 61d6e22

Browse files
committed
drafts: improve test files and support note links
1 parent e18b4f9 commit 61d6e22

5 files changed

Lines changed: 75 additions & 10 deletions

File tree

docs/formats/drafts.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ source_app: Drafts
1111

1212
## Instructions
1313

14-
1. Creata a backup as described [at the website](https://docs.getdrafts.com/docs/settings/backups#partial-backups)
14+
1. Create a backup as described [at the website](https://docs.getdrafts.com/docs/settings/backups#partial-backups)
1515
2. [Install Jimmy](../index.md#installation)
1616
3. Convert to Markdown. Example: `jimmy-linux cli DraftsExport.draftsExport --format drafts`
1717
4. [Import to your app](../import_instructions.md)
@@ -20,8 +20,8 @@ source_app: Drafts
2020

2121
| Feature | Supported? | Remark |
2222
| --- | :---: | --- |
23-
| Attachments / Images / Resources | | Please share an example file. |
23+
| Attachments / Images / Resources | | |
2424
| Labels / Tags || |
25-
| Note Links | | Please share an example file. |
25+
| Note Links | | |
2626
| Notebook / Folder Hierarchy || |
2727
| Rich Text || |

jimmy/formats/drafts.py

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,67 @@
22

33
import json
44
from pathlib import Path
5+
import re
56

67
from 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

917
class 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)

jimmy/writer.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,16 @@ def update_note_links(self, note: imf.Note, note_link: imf.NoteLink):
210210
f'Note "{note.title}": could not find original link: "{note_link.original_text}"'
211211
)
212212

213-
link_title = note_link.title if note_link.title not in [None, ""] else note_link.original_id
214-
215213
new_path = self.note_id_map.get(note_link.original_id)
214+
215+
# find the best note title
216+
if note_link.title not in [None, ""]:
217+
link_title = note_link.title
218+
elif new_path is not None:
219+
link_title = new_path.stem
220+
else:
221+
link_title = note_link.original_id
222+
216223
if new_path is None:
217224
LOGGER.debug(
218225
f'Note "{note.title}": could not find linked note: "{note_link.original_id}"',

test/test_convert.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def format_test(self, test_input: Path):
159159
[["day_one/test_2/dayone-to-obsidian.zip"]],
160160
[["day_one/test_3/Export-Tagebuch.zip"]],
161161
[["diaro/test_1_frontmatter/Diaro_20250821.zip"]],
162-
[["drafts/test_1_frontmatter/DraftsExport.draftsExport"]],
162+
[["drafts/test_1_frontmatter/Drafts-2025-09-21-21-50.draftsExport"]],
163163
[["dynalist/test_1_frontmatter/dynalist-backup-2024-04-12.zip"]],
164164
[["evernote/test_1_frontmatter/obsidian-importer"]],
165165
[["evernote/test_2/joplin"]],

0 commit comments

Comments
 (0)