diff --git a/.gitignore b/.gitignore index fe498e7..0c731df 100644 --- a/.gitignore +++ b/.gitignore @@ -128,5 +128,9 @@ dmypy.json # Pyre type checker .pyre/ +# branch feature_ha_append_page_metadata +tmp +appTest.py + # End of https://www.gitignore.io/api/code,python diff --git a/confluence_markdown_exporter/confluence.py b/confluence_markdown_exporter/confluence.py index 0993815..9df9a97 100644 --- a/confluence_markdown_exporter/confluence.py +++ b/confluence_markdown_exporter/confluence.py @@ -16,6 +16,9 @@ from typing import TypeAlias from typing import cast +import json +from datetime import datetime, timedelta + import jmespath import yaml from atlassian.errors import ApiError @@ -324,6 +327,30 @@ class Page(Document): editor2: str labels: list["Label"] attachments: list["Attachment"] + page_metadata: str + + @staticmethod + def get_custom_metadata(response: dict | None) -> dict: + if response is None: + return {} + custom_metadata = { + 'id' : response.get('id',''), + 'title' : response.get('title',''), + 'type' : response.get('type',''), + 'created' : response.get('history', {}).get('createdDate',''), + 'author' : response.get('history', {}).get('createdBy', {}).get('displayName',''), + 'version' : response.get('version', {}).get('number','1'), + 'lastModified' : response.get('version', {}).get('when',''), + 'lastModifiedBy' : response.get('version', {}).get('by', {}).get('displayName',''), + 'lastModifiedWithin365Days': response.get('version', {}).get('when','')[10] >= (datetime.now()-timedelta(days=365)).strftime('%Y-%m-%d') if response.get('version', {}).get('when','') else False, + 'pageURL' : f"""{response.get('_links', {}).get('base','')}/{response.get('_links', {}).get('webui','')}""", + # 'pageURL' : f"""{response.get('_links', {}).get('base','')}/pages/viewpage.action?pageId={response.get('id','')}""", + } + custom_metadata_html = "\n" + custom_metadata_html += "".join(f"\n" for key, value in custom_metadata.items()) + custom_metadata_html += "
{key}{value}
\n" + response['page_metadata'] = custom_metadata_html + return response @property def descendants(self) -> list[int]: @@ -382,9 +409,12 @@ def export_path(self) -> Path: @property def html(self) -> str: + body = self.body if settings.export.include_document_title: - return f"

{self.title}

{self.body}" - return self.body + body = f"

{self.title}

{self.body}" + if settings.export.include_document_metadata: + body = f"{body}


Metadata:


{self.page_metadata}" + return body @property def markdown(self) -> str: @@ -486,20 +516,23 @@ def from_json(cls, data: JsonResponse) -> "Page": ], attachments=Attachment.from_page_id(data.get("id", 0)), ancestors=[ancestor.get("id") for ancestor in data.get("ancestors", [])][1:], + page_metadata=data.get("page_metadata", ""), ) @classmethod @functools.lru_cache(maxsize=1000) def from_id(cls, page_id: int) -> "Page": try: + response = confluence.get_page_by_id( + page_id, + expand="body.view,body.export_view,body.editor2,metadata.labels," + "metadata.properties,ancestors,history,version", + ) + response = cls.get_custom_metadata(response) return cls.from_json( cast( JsonResponse, - confluence.get_page_by_id( - page_id, - expand="body.view,body.export_view,body.editor2,metadata.labels," - "metadata.properties,ancestors", - ), + response, ) ) except (ApiError, HTTPError) as e: @@ -515,6 +548,7 @@ def from_id(cls, page_id: int) -> "Page": labels=[], attachments=[], ancestors=[], + page_metadata="", ) @classmethod diff --git a/confluence_markdown_exporter/utils/app_data_store.py b/confluence_markdown_exporter/utils/app_data_store.py index bd2ec4f..ebc6e2b 100644 --- a/confluence_markdown_exporter/utils/app_data_store.py +++ b/confluence_markdown_exporter/utils/app_data_store.py @@ -221,6 +221,14 @@ class ExportConfig(BaseModel): "If enabled, the title will be added as a top-level heading." ), ) + include_document_metadata: bool = Field( + default=False, + title="Include Document Metadata", + description=( + "Whether to include document metadata (like author, last modified date) " + "at the bottom of the exported markdown file." + ), + ) class ConfigModel(BaseModel):