diff --git a/md2cf/api.py b/md2cf/api.py
index 54ba44a..4c9dc6f 100644
--- a/md2cf/api.py
+++ b/md2cf/api.py
@@ -66,6 +66,7 @@ def __init__(
def _request(self, method, path, **kwargs):
r = self.api.request(method, urljoin(self.host, path), **kwargs)
r.raise_for_status()
+
return bunchify(r.json())
def _get(self, path, **kwargs):
@@ -186,13 +187,14 @@ def update_page(
update_message=None,
labels=None,
minor_edit=False,
+ title=None,
):
update_structure = {
"version": {
"number": page.version.number + 1,
"minorEdit": minor_edit,
},
- "title": page.title,
+ "title": title if title is not None else page.title,
"type": content_type,
"body": {"storage": {"value": body, "representation": "storage"}},
}
diff --git a/md2cf/confluence_renderer.py b/md2cf/confluence_renderer.py
index 74b3496..6a81c8d 100644
--- a/md2cf/confluence_renderer.py
+++ b/md2cf/confluence_renderer.py
@@ -1,6 +1,7 @@
import uuid
+import re
from pathlib import Path
-from typing import List, NamedTuple
+from typing import List, NamedTuple, Optional, Any
from urllib.parse import unquote, urlparse
import mistune
@@ -60,7 +61,7 @@ def append(self, child):
self.children.append(child)
-class ConfluenceRenderer(mistune.Renderer):
+class ConfluenceRenderer(mistune.HTMLRenderer):
def __init__(
self,
strip_header=False,
@@ -90,8 +91,8 @@ def header(self, text, level, raw=None):
return super(ConfluenceRenderer, self).header(text, level, raw=raw)
- def structured_macro(self, name):
- return ConfluenceTag("structured-macro", attrib={"name": name})
+ def structured_macro(self, name, text=""):
+ return ConfluenceTag("structured-macro", attrib={"name": name}, text=text)
def parameter(self, name, value):
parameter_tag = ConfluenceTag("parameter", attrib={"name": name})
@@ -103,8 +104,13 @@ def plain_text_body(self, text):
body_tag.text = text
return body_tag
- def link(self, link, title, text):
- parsed_link = urlparse(link)
+ def rich_text_body(self, text):
+ body_tag = ConfluenceTag("rich-text-body", cdata=False)
+ body_tag.text = text
+ return body_tag
+
+ def link(self, text, url, title=None):
+ parsed_link = urlparse(url)
if (
self.enable_relative_links
and (not parsed_link.scheme and not parsed_link.netloc)
@@ -119,12 +125,12 @@ def link(self, link, title, text):
path=unquote(parsed_link.path),
replacement=replacement_link,
fragment=parsed_link.fragment,
- original=link,
- escaped_original=mistune.escape_link(link),
+ original=url,
+ escaped_original=mistune.escape_link(url),
)
)
- link = replacement_link
- return super(ConfluenceRenderer, self).link(link, title, text)
+ url = replacement_link
+ return super(ConfluenceRenderer, self).link(text, url, title)
def text(self, text):
if self.remove_text_newlines:
@@ -132,31 +138,100 @@ def text(self, text):
return super().text(text)
- def block_code(self, code, lang=None):
+ def block_code(self, code, info=None):
root_element = self.structured_macro("code")
- if lang is not None:
- lang_parameter = self.parameter(name="language", value=lang)
+ if info is not None:
+ lang_parameter = self.parameter(name="language", value=info)
root_element.append(lang_parameter)
root_element.append(self.parameter(name="linenumbers", value="true"))
root_element.append(self.plain_text_body(code))
return root_element.render()
- def image(self, src, title, text):
- attributes = {"alt": text}
- if title:
- attributes["title"] = title
+ def image(self, alt, url, title=None, width=None, height=None):
+ attributes = {"alt": alt,
+ "title": title if title is not None else alt,
+ }
+ if width:
+ attributes["width"] = width
+ if height:
+ attributes["height"] = height
root_element = ConfluenceTag(name="image", attrib=attributes)
- parsed_source = urlparse(src)
+ parsed_source = urlparse(url)
if not parsed_source.netloc:
# Local file, requires upload
- basename = Path(src).name
+ basename = Path(url).name
url_tag = ConfluenceTag(
"attachment", attrib={"filename": basename}, namespace="ri"
)
- self.attachments.append(src)
+ self.attachments.append(url)
else:
- url_tag = ConfluenceTag("url", attrib={"value": src}, namespace="ri")
+ url_tag = ConfluenceTag("url", attrib={"value": url}, namespace="ri")
root_element.append(url_tag)
return root_element.render()
+
+ def strikethrough(self, text):
+ return f"""{text}"""
+
+ def task_list_item(self, text, checked=False, **attrs):
+ return f"""
+
+
+ {"in" if not checked else ""}complete
+ {text}
+
+
+ """
+
+ def block_spoiler(self, text):
+ lines = text.splitlines(keepends=True)
+ firstline = re.sub('<.*?>', '', lines[0])
+
+ root_element = self.structured_macro("expand")
+ title_param = self.parameter(name="title", value=firstline)
+ root_element.append(title_param)
+
+ root_element.append(self.rich_text_body(''.join(lines[1:])))
+ return root_element.render()
+
+
+ def mark(self, text):
+ return f"""{text}"""
+
+ def insert(self, text):
+ return f"""{text}"""
+
+ def admonition(self, text: str, name: str, **attrs) -> str:
+ confluence_mapping = {"tip" : "tip",
+ "attention": "warning",
+ "caution": "warning",
+ "danger": "warning",
+ "error": "warning",
+ "hint" : "tip",
+ "important": "note",
+ "note": "info",
+ "warning": "warning"}
+
+ adm_class = confluence_mapping.get(name, "info")
+ root_element = self.structured_macro(name=adm_class, text=text)
+ return root_element.render()
+
+ def admonition_title(self, text: str) -> str:
+ param = self.parameter(name="title", value=text)
+ return param.render()
+
+
+ def admonition_content(self, text: str) -> str:
+ body = self.rich_text_body(text)
+ return body.render()
+
+ def block_image(
+ self,
+ src: str,
+ alt: Optional[str] = None,
+ width: Optional[str] = None,
+ height: Optional[str] = None,
+ **attrs: Any,
+ ) -> str:
+ return self.image(alt, src, alt, width, height)
diff --git a/md2cf/upsert.py b/md2cf/upsert.py
index 7d757d6..9a3b119 100644
--- a/md2cf/upsert.py
+++ b/md2cf/upsert.py
@@ -104,6 +104,7 @@ def upsert_page(
update_message=page_message,
labels=page.labels if replace_all_labels else None,
minor_edit=minor_edit,
+ title=page.title
)
action = UpsertAction.UPDATED
else:
diff --git a/setup.py b/setup.py
index 4034063..4447a55 100644
--- a/setup.py
+++ b/setup.py
@@ -23,9 +23,9 @@
install_requires=[
"rich-argparse==1.0.0",
"rich==13.0.1",
- "mistune==0.8.4",
+ "mistune==3.1.2",
"chardet==5.1.0",
- "requests==2.31.0",
+ "requests==2.32.3",
"PyYAML==6.0.1",
"gitignorefile==1.1.2",
],