mkdocs2confluence 0.7.38__tar.gz → 0.8.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/PKG-INFO +73 -1
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/README.md +72 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/pyproject.toml +1 -1
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs2confluence.egg-info/PKG-INFO +73 -1
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs2confluence.egg-info/SOURCES.txt +12 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/cli.py +123 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/loader/config.py +7 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/publisher/client.py +82 -0
- mkdocs2confluence-0.8.1/src/mkdocs_to_confluence/sync/__init__.py +1 -0
- mkdocs2confluence-0.8.1/src/mkdocs_to_confluence/sync/anchoring.py +25 -0
- mkdocs2confluence-0.8.1/src/mkdocs_to_confluence/sync/command.py +236 -0
- mkdocs2confluence-0.8.1/src/mkdocs_to_confluence/sync/comments.py +89 -0
- mkdocs2confluence-0.8.1/src/mkdocs_to_confluence/sync/github.py +143 -0
- mkdocs2confluence-0.8.1/src/mkdocs_to_confluence/sync/platform.py +50 -0
- mkdocs2confluence-0.8.1/src/mkdocs_to_confluence/sync/state.py +45 -0
- mkdocs2confluence-0.8.1/tests/test_sync_anchoring.py +56 -0
- mkdocs2confluence-0.8.1/tests/test_sync_command.py +637 -0
- mkdocs2confluence-0.8.1/tests/test_sync_comments.py +137 -0
- mkdocs2confluence-0.8.1/tests/test_sync_github.py +212 -0
- mkdocs2confluence-0.8.1/tests/test_sync_state.py +75 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/LICENSE +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/setup.cfg +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs2confluence.egg-info/dependency_links.txt +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs2confluence.egg-info/entry_points.txt +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs2confluence.egg-info/requires.txt +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs2confluence.egg-info/top_level.txt +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/__init__.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/emitter/__init__.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/emitter/xhtml.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/ir/__init__.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/ir/document.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/ir/nodes.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/ir/treeutil.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/loader/__init__.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/loader/extra_css.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/loader/nav.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/loader/page.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/parser/__init__.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/parser/markdown.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/pdf/__init__.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/pdf/generator.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/pdf/render.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/preprocess/__init__.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/preprocess/abbrevs.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/preprocess/fence.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/preprocess/frontmatter.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/preprocess/icons.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/preprocess/includes.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/preprocess/linkdefs.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/preview/__init__.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/preview/render.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/preview/server.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/publisher/__init__.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/publisher/pipeline.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/transforms/__init__.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/transforms/abbrevs.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/transforms/assets.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/transforms/editlink.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/transforms/images.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/transforms/internallinks.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/transforms/mermaid.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_abbrevs.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_cli.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_editlink.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_emitter.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_extra_css.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_frontmatter.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_icons.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_images.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_internallinks.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_ir.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_linkdefs.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_loader.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_mermaid.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_page_loader.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_parser.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_pdf.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_preprocess.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_preview.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_publish_client.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_publish_config.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_publish_pipeline.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_server.py +0 -0
- {mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/tests/test_treeutil.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs2confluence
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more
|
|
5
5
|
Author: Anders Hybertz
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -54,6 +54,8 @@ Dynamic: license-file
|
|
|
54
54
|
|
|
55
55
|
A Python CLI tool that compiles MkDocs-flavoured Markdown into native Confluence storage XHTML and publishes it directly to Confluence Cloud. It is a **compiler/transpiler**, not an HTML converter — every construct maps to its native Confluence equivalent, so pages look and behave like hand-authored Confluence content.
|
|
56
56
|
|
|
57
|
+
It also bridges the gap between Confluence reviewers and developers: the `sync-comments` command turns open Confluence page comments into GitHub pull request review threads, and auto-resolves them in Confluence when the PR is merged.
|
|
58
|
+
|
|
57
59
|
> **Zensical compatible** — [Zensical](https://zensical.org/) is the modern successor to MkDocs + Material for MkDocs. Since it uses the same `mkdocs.yml` format and Python Markdown extensions, your Zensical project works with mk2conf today with no changes required.
|
|
58
60
|
|
|
59
61
|
---
|
|
@@ -109,6 +111,12 @@ mk2conf publish
|
|
|
109
111
|
|
|
110
112
|
# Export a nav section to a stand-alone PDF document
|
|
111
113
|
mk2conf pdf --section Guide --out guide.pdf
|
|
114
|
+
|
|
115
|
+
# Sync open Confluence comments to GitHub review PRs
|
|
116
|
+
mk2conf sync-comments
|
|
117
|
+
|
|
118
|
+
# Resolve Confluence comments when their review PRs are merged
|
|
119
|
+
mk2conf sync-comments --check-merges
|
|
112
120
|
```
|
|
113
121
|
|
|
114
122
|
---
|
|
@@ -128,12 +136,32 @@ confluence:
|
|
|
128
136
|
full_width: true # default: true
|
|
129
137
|
```
|
|
130
138
|
|
|
139
|
+
The `confluence:` block is also accepted under `extra:` (MkDocs strict-mode compatible):
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
extra:
|
|
143
|
+
confluence:
|
|
144
|
+
base_url: https://yourorg.atlassian.net
|
|
145
|
+
space_key: TECH
|
|
146
|
+
...
|
|
147
|
+
```
|
|
148
|
+
|
|
131
149
|
The API token is read from (in priority order):
|
|
132
150
|
|
|
133
151
|
1. `token:` in `mkdocs.yml` — typically via `!ENV CONFLUENCE_API_TOKEN`
|
|
134
152
|
2. `CONFLUENCE_API_TOKEN` environment variable
|
|
135
153
|
3. `MK2CONF_TOKEN` environment variable
|
|
136
154
|
|
|
155
|
+
### Additional fields for `sync-comments`
|
|
156
|
+
|
|
157
|
+
```yaml
|
|
158
|
+
confluence:
|
|
159
|
+
# ... base fields above ...
|
|
160
|
+
github_repo: owner/repo # required for sync-comments
|
|
161
|
+
github_token: !ENV GITHUB_TOKEN # falls back to GITHUB_TOKEN env var
|
|
162
|
+
github_base_branch: main # default: main
|
|
163
|
+
```
|
|
164
|
+
|
|
137
165
|
---
|
|
138
166
|
|
|
139
167
|
## Your first publish
|
|
@@ -270,6 +298,48 @@ The PDF includes a **cover page**, **table of contents** with page numbers, and
|
|
|
270
298
|
|
|
271
299
|
---
|
|
272
300
|
|
|
301
|
+
### `mk2conf sync-comments`
|
|
302
|
+
|
|
303
|
+
Bridge Confluence page/inline comments to GitHub pull request review threads. Non-technical reviewers comment in Confluence; developers address feedback on a GitHub feature branch; Confluence comments are auto-resolved when the PR is merged.
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
mk2conf sync-comments [--config PATH] [--check-merges] [--force] [--dry-run] [--quiet]
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
| Flag | Default | Description |
|
|
310
|
+
|---|---|---|
|
|
311
|
+
| `--config PATH` | `./mkdocs.yml` | Path to `mkdocs.yml` |
|
|
312
|
+
| `--check-merges` | off | Check tracked PRs for merges and auto-resolve Confluence comments |
|
|
313
|
+
| `--force` | off | Re-sync pages that already have an open review PR |
|
|
314
|
+
| `--dry-run` | off | Print what would be synced without making any API calls |
|
|
315
|
+
| `--quiet` | off | Suppress progress output |
|
|
316
|
+
|
|
317
|
+
**Required config** (add to the `confluence:` block in `mkdocs.yml`):
|
|
318
|
+
|
|
319
|
+
```yaml
|
|
320
|
+
confluence:
|
|
321
|
+
# ... base fields ...
|
|
322
|
+
github_repo: owner/repo # required for sync-comments
|
|
323
|
+
github_token: !ENV GITHUB_TOKEN # falls back to GITHUB_TOKEN env var
|
|
324
|
+
github_base_branch: main # default: main
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Workflow:**
|
|
328
|
+
|
|
329
|
+
1. Run `mk2conf publish` first — generates `.mk2conf-pages.json` mapping source files to Confluence page IDs.
|
|
330
|
+
2. Run `mk2conf sync-comments` — for each page with open Confluence comments, creates a `mk2conf/review/{slug}` branch and PR, then posts each comment as a GitHub review thread. Inline comments with a text selection are anchored to the matching source line; page-level comments fall back to file-level review threads. Every thread body includes a **View in Confluence** deep-link that opens Confluence focused on the exact comment.
|
|
331
|
+
3. Developer addresses feedback on the branch, pushes changes, and merges the PR.
|
|
332
|
+
4. Run `mk2conf sync-comments --check-merges` — detects merged PRs, adds a resolution reply to each Confluence comment with the commit info, and marks the comments as resolved.
|
|
333
|
+
|
|
334
|
+
**State files** (add to `.gitignore`):
|
|
335
|
+
|
|
336
|
+
| File | Purpose |
|
|
337
|
+
|---|---|
|
|
338
|
+
| `.mk2conf-pages.json` | Source path → Confluence page ID map, written after each `publish` |
|
|
339
|
+
| `.mk2conf-sync-state.json` | Tracks open/merged review PRs and their associated comment IDs |
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
273
343
|
## Supported Markdown features
|
|
274
344
|
|
|
275
345
|
### Block elements
|
|
@@ -379,6 +449,8 @@ MkDocs abbreviation definitions (`*[ABBR]: Full term`) are rendered as inline su
|
|
|
379
449
|
|
|
380
450
|
Each stage is a separate Python module under `src/mkdocs_to_confluence/`. The **plan** phase makes all API read calls (find existing pages); the **execute** phase makes all write calls, ensuring parent pages always exist before their children.
|
|
381
451
|
|
|
452
|
+
The `sync/` package is a self-contained pipeline for the `sync-comments` command: it fetches Confluence comments, anchors them to source lines, posts them as GitHub review threads via GraphQL, and resolves them on PR merge. The `ReviewPlatformClient` Protocol keeps GitHub-specific code isolated so future GitLab or Azure DevOps adapters slot in without touching the core.
|
|
453
|
+
|
|
382
454
|
---
|
|
383
455
|
|
|
384
456
|
## Development
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
A Python CLI tool that compiles MkDocs-flavoured Markdown into native Confluence storage XHTML and publishes it directly to Confluence Cloud. It is a **compiler/transpiler**, not an HTML converter — every construct maps to its native Confluence equivalent, so pages look and behave like hand-authored Confluence content.
|
|
16
16
|
|
|
17
|
+
It also bridges the gap between Confluence reviewers and developers: the `sync-comments` command turns open Confluence page comments into GitHub pull request review threads, and auto-resolves them in Confluence when the PR is merged.
|
|
18
|
+
|
|
17
19
|
> **Zensical compatible** — [Zensical](https://zensical.org/) is the modern successor to MkDocs + Material for MkDocs. Since it uses the same `mkdocs.yml` format and Python Markdown extensions, your Zensical project works with mk2conf today with no changes required.
|
|
18
20
|
|
|
19
21
|
---
|
|
@@ -69,6 +71,12 @@ mk2conf publish
|
|
|
69
71
|
|
|
70
72
|
# Export a nav section to a stand-alone PDF document
|
|
71
73
|
mk2conf pdf --section Guide --out guide.pdf
|
|
74
|
+
|
|
75
|
+
# Sync open Confluence comments to GitHub review PRs
|
|
76
|
+
mk2conf sync-comments
|
|
77
|
+
|
|
78
|
+
# Resolve Confluence comments when their review PRs are merged
|
|
79
|
+
mk2conf sync-comments --check-merges
|
|
72
80
|
```
|
|
73
81
|
|
|
74
82
|
---
|
|
@@ -88,12 +96,32 @@ confluence:
|
|
|
88
96
|
full_width: true # default: true
|
|
89
97
|
```
|
|
90
98
|
|
|
99
|
+
The `confluence:` block is also accepted under `extra:` (MkDocs strict-mode compatible):
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
extra:
|
|
103
|
+
confluence:
|
|
104
|
+
base_url: https://yourorg.atlassian.net
|
|
105
|
+
space_key: TECH
|
|
106
|
+
...
|
|
107
|
+
```
|
|
108
|
+
|
|
91
109
|
The API token is read from (in priority order):
|
|
92
110
|
|
|
93
111
|
1. `token:` in `mkdocs.yml` — typically via `!ENV CONFLUENCE_API_TOKEN`
|
|
94
112
|
2. `CONFLUENCE_API_TOKEN` environment variable
|
|
95
113
|
3. `MK2CONF_TOKEN` environment variable
|
|
96
114
|
|
|
115
|
+
### Additional fields for `sync-comments`
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
confluence:
|
|
119
|
+
# ... base fields above ...
|
|
120
|
+
github_repo: owner/repo # required for sync-comments
|
|
121
|
+
github_token: !ENV GITHUB_TOKEN # falls back to GITHUB_TOKEN env var
|
|
122
|
+
github_base_branch: main # default: main
|
|
123
|
+
```
|
|
124
|
+
|
|
97
125
|
---
|
|
98
126
|
|
|
99
127
|
## Your first publish
|
|
@@ -230,6 +258,48 @@ The PDF includes a **cover page**, **table of contents** with page numbers, and
|
|
|
230
258
|
|
|
231
259
|
---
|
|
232
260
|
|
|
261
|
+
### `mk2conf sync-comments`
|
|
262
|
+
|
|
263
|
+
Bridge Confluence page/inline comments to GitHub pull request review threads. Non-technical reviewers comment in Confluence; developers address feedback on a GitHub feature branch; Confluence comments are auto-resolved when the PR is merged.
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
mk2conf sync-comments [--config PATH] [--check-merges] [--force] [--dry-run] [--quiet]
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
| Flag | Default | Description |
|
|
270
|
+
|---|---|---|
|
|
271
|
+
| `--config PATH` | `./mkdocs.yml` | Path to `mkdocs.yml` |
|
|
272
|
+
| `--check-merges` | off | Check tracked PRs for merges and auto-resolve Confluence comments |
|
|
273
|
+
| `--force` | off | Re-sync pages that already have an open review PR |
|
|
274
|
+
| `--dry-run` | off | Print what would be synced without making any API calls |
|
|
275
|
+
| `--quiet` | off | Suppress progress output |
|
|
276
|
+
|
|
277
|
+
**Required config** (add to the `confluence:` block in `mkdocs.yml`):
|
|
278
|
+
|
|
279
|
+
```yaml
|
|
280
|
+
confluence:
|
|
281
|
+
# ... base fields ...
|
|
282
|
+
github_repo: owner/repo # required for sync-comments
|
|
283
|
+
github_token: !ENV GITHUB_TOKEN # falls back to GITHUB_TOKEN env var
|
|
284
|
+
github_base_branch: main # default: main
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Workflow:**
|
|
288
|
+
|
|
289
|
+
1. Run `mk2conf publish` first — generates `.mk2conf-pages.json` mapping source files to Confluence page IDs.
|
|
290
|
+
2. Run `mk2conf sync-comments` — for each page with open Confluence comments, creates a `mk2conf/review/{slug}` branch and PR, then posts each comment as a GitHub review thread. Inline comments with a text selection are anchored to the matching source line; page-level comments fall back to file-level review threads. Every thread body includes a **View in Confluence** deep-link that opens Confluence focused on the exact comment.
|
|
291
|
+
3. Developer addresses feedback on the branch, pushes changes, and merges the PR.
|
|
292
|
+
4. Run `mk2conf sync-comments --check-merges` — detects merged PRs, adds a resolution reply to each Confluence comment with the commit info, and marks the comments as resolved.
|
|
293
|
+
|
|
294
|
+
**State files** (add to `.gitignore`):
|
|
295
|
+
|
|
296
|
+
| File | Purpose |
|
|
297
|
+
|---|---|
|
|
298
|
+
| `.mk2conf-pages.json` | Source path → Confluence page ID map, written after each `publish` |
|
|
299
|
+
| `.mk2conf-sync-state.json` | Tracks open/merged review PRs and their associated comment IDs |
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
233
303
|
## Supported Markdown features
|
|
234
304
|
|
|
235
305
|
### Block elements
|
|
@@ -339,6 +409,8 @@ MkDocs abbreviation definitions (`*[ABBR]: Full term`) are rendered as inline su
|
|
|
339
409
|
|
|
340
410
|
Each stage is a separate Python module under `src/mkdocs_to_confluence/`. The **plan** phase makes all API read calls (find existing pages); the **execute** phase makes all write calls, ensuring parent pages always exist before their children.
|
|
341
411
|
|
|
412
|
+
The `sync/` package is a self-contained pipeline for the `sync-comments` command: it fetches Confluence comments, anchors them to source lines, posts them as GitHub review threads via GraphQL, and resolves them on PR merge. The `ReviewPlatformClient` Protocol keeps GitHub-specific code isolated so future GitLab or Azure DevOps adapters slot in without touching the core.
|
|
413
|
+
|
|
342
414
|
---
|
|
343
415
|
|
|
344
416
|
## Development
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mkdocs2confluence"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.1"
|
|
4
4
|
description = "Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "GPL-3.0-or-later" }
|
{mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs2confluence.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs2confluence
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more
|
|
5
5
|
Author: Anders Hybertz
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -54,6 +54,8 @@ Dynamic: license-file
|
|
|
54
54
|
|
|
55
55
|
A Python CLI tool that compiles MkDocs-flavoured Markdown into native Confluence storage XHTML and publishes it directly to Confluence Cloud. It is a **compiler/transpiler**, not an HTML converter — every construct maps to its native Confluence equivalent, so pages look and behave like hand-authored Confluence content.
|
|
56
56
|
|
|
57
|
+
It also bridges the gap between Confluence reviewers and developers: the `sync-comments` command turns open Confluence page comments into GitHub pull request review threads, and auto-resolves them in Confluence when the PR is merged.
|
|
58
|
+
|
|
57
59
|
> **Zensical compatible** — [Zensical](https://zensical.org/) is the modern successor to MkDocs + Material for MkDocs. Since it uses the same `mkdocs.yml` format and Python Markdown extensions, your Zensical project works with mk2conf today with no changes required.
|
|
58
60
|
|
|
59
61
|
---
|
|
@@ -109,6 +111,12 @@ mk2conf publish
|
|
|
109
111
|
|
|
110
112
|
# Export a nav section to a stand-alone PDF document
|
|
111
113
|
mk2conf pdf --section Guide --out guide.pdf
|
|
114
|
+
|
|
115
|
+
# Sync open Confluence comments to GitHub review PRs
|
|
116
|
+
mk2conf sync-comments
|
|
117
|
+
|
|
118
|
+
# Resolve Confluence comments when their review PRs are merged
|
|
119
|
+
mk2conf sync-comments --check-merges
|
|
112
120
|
```
|
|
113
121
|
|
|
114
122
|
---
|
|
@@ -128,12 +136,32 @@ confluence:
|
|
|
128
136
|
full_width: true # default: true
|
|
129
137
|
```
|
|
130
138
|
|
|
139
|
+
The `confluence:` block is also accepted under `extra:` (MkDocs strict-mode compatible):
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
extra:
|
|
143
|
+
confluence:
|
|
144
|
+
base_url: https://yourorg.atlassian.net
|
|
145
|
+
space_key: TECH
|
|
146
|
+
...
|
|
147
|
+
```
|
|
148
|
+
|
|
131
149
|
The API token is read from (in priority order):
|
|
132
150
|
|
|
133
151
|
1. `token:` in `mkdocs.yml` — typically via `!ENV CONFLUENCE_API_TOKEN`
|
|
134
152
|
2. `CONFLUENCE_API_TOKEN` environment variable
|
|
135
153
|
3. `MK2CONF_TOKEN` environment variable
|
|
136
154
|
|
|
155
|
+
### Additional fields for `sync-comments`
|
|
156
|
+
|
|
157
|
+
```yaml
|
|
158
|
+
confluence:
|
|
159
|
+
# ... base fields above ...
|
|
160
|
+
github_repo: owner/repo # required for sync-comments
|
|
161
|
+
github_token: !ENV GITHUB_TOKEN # falls back to GITHUB_TOKEN env var
|
|
162
|
+
github_base_branch: main # default: main
|
|
163
|
+
```
|
|
164
|
+
|
|
137
165
|
---
|
|
138
166
|
|
|
139
167
|
## Your first publish
|
|
@@ -270,6 +298,48 @@ The PDF includes a **cover page**, **table of contents** with page numbers, and
|
|
|
270
298
|
|
|
271
299
|
---
|
|
272
300
|
|
|
301
|
+
### `mk2conf sync-comments`
|
|
302
|
+
|
|
303
|
+
Bridge Confluence page/inline comments to GitHub pull request review threads. Non-technical reviewers comment in Confluence; developers address feedback on a GitHub feature branch; Confluence comments are auto-resolved when the PR is merged.
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
mk2conf sync-comments [--config PATH] [--check-merges] [--force] [--dry-run] [--quiet]
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
| Flag | Default | Description |
|
|
310
|
+
|---|---|---|
|
|
311
|
+
| `--config PATH` | `./mkdocs.yml` | Path to `mkdocs.yml` |
|
|
312
|
+
| `--check-merges` | off | Check tracked PRs for merges and auto-resolve Confluence comments |
|
|
313
|
+
| `--force` | off | Re-sync pages that already have an open review PR |
|
|
314
|
+
| `--dry-run` | off | Print what would be synced without making any API calls |
|
|
315
|
+
| `--quiet` | off | Suppress progress output |
|
|
316
|
+
|
|
317
|
+
**Required config** (add to the `confluence:` block in `mkdocs.yml`):
|
|
318
|
+
|
|
319
|
+
```yaml
|
|
320
|
+
confluence:
|
|
321
|
+
# ... base fields ...
|
|
322
|
+
github_repo: owner/repo # required for sync-comments
|
|
323
|
+
github_token: !ENV GITHUB_TOKEN # falls back to GITHUB_TOKEN env var
|
|
324
|
+
github_base_branch: main # default: main
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Workflow:**
|
|
328
|
+
|
|
329
|
+
1. Run `mk2conf publish` first — generates `.mk2conf-pages.json` mapping source files to Confluence page IDs.
|
|
330
|
+
2. Run `mk2conf sync-comments` — for each page with open Confluence comments, creates a `mk2conf/review/{slug}` branch and PR, then posts each comment as a GitHub review thread. Inline comments with a text selection are anchored to the matching source line; page-level comments fall back to file-level review threads. Every thread body includes a **View in Confluence** deep-link that opens Confluence focused on the exact comment.
|
|
331
|
+
3. Developer addresses feedback on the branch, pushes changes, and merges the PR.
|
|
332
|
+
4. Run `mk2conf sync-comments --check-merges` — detects merged PRs, adds a resolution reply to each Confluence comment with the commit info, and marks the comments as resolved.
|
|
333
|
+
|
|
334
|
+
**State files** (add to `.gitignore`):
|
|
335
|
+
|
|
336
|
+
| File | Purpose |
|
|
337
|
+
|---|---|
|
|
338
|
+
| `.mk2conf-pages.json` | Source path → Confluence page ID map, written after each `publish` |
|
|
339
|
+
| `.mk2conf-sync-state.json` | Tracks open/merged review PRs and their associated comment IDs |
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
273
343
|
## Supported Markdown features
|
|
274
344
|
|
|
275
345
|
### Block elements
|
|
@@ -379,6 +449,8 @@ MkDocs abbreviation definitions (`*[ABBR]: Full term`) are rendered as inline su
|
|
|
379
449
|
|
|
380
450
|
Each stage is a separate Python module under `src/mkdocs_to_confluence/`. The **plan** phase makes all API read calls (find existing pages); the **execute** phase makes all write calls, ensuring parent pages always exist before their children.
|
|
381
451
|
|
|
452
|
+
The `sync/` package is a self-contained pipeline for the `sync-comments` command: it fetches Confluence comments, anchors them to source lines, posts them as GitHub review threads via GraphQL, and resolves them on PR merge. The `ReviewPlatformClient` Protocol keeps GitHub-specific code isolated so future GitLab or Azure DevOps adapters slot in without touching the core.
|
|
453
|
+
|
|
382
454
|
---
|
|
383
455
|
|
|
384
456
|
## Development
|
{mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs2confluence.egg-info/SOURCES.txt
RENAMED
|
@@ -38,6 +38,13 @@ src/mkdocs_to_confluence/preview/server.py
|
|
|
38
38
|
src/mkdocs_to_confluence/publisher/__init__.py
|
|
39
39
|
src/mkdocs_to_confluence/publisher/client.py
|
|
40
40
|
src/mkdocs_to_confluence/publisher/pipeline.py
|
|
41
|
+
src/mkdocs_to_confluence/sync/__init__.py
|
|
42
|
+
src/mkdocs_to_confluence/sync/anchoring.py
|
|
43
|
+
src/mkdocs_to_confluence/sync/command.py
|
|
44
|
+
src/mkdocs_to_confluence/sync/comments.py
|
|
45
|
+
src/mkdocs_to_confluence/sync/github.py
|
|
46
|
+
src/mkdocs_to_confluence/sync/platform.py
|
|
47
|
+
src/mkdocs_to_confluence/sync/state.py
|
|
41
48
|
src/mkdocs_to_confluence/transforms/__init__.py
|
|
42
49
|
src/mkdocs_to_confluence/transforms/abbrevs.py
|
|
43
50
|
src/mkdocs_to_confluence/transforms/assets.py
|
|
@@ -67,4 +74,9 @@ tests/test_publish_client.py
|
|
|
67
74
|
tests/test_publish_config.py
|
|
68
75
|
tests/test_publish_pipeline.py
|
|
69
76
|
tests/test_server.py
|
|
77
|
+
tests/test_sync_anchoring.py
|
|
78
|
+
tests/test_sync_command.py
|
|
79
|
+
tests/test_sync_comments.py
|
|
80
|
+
tests/test_sync_github.py
|
|
81
|
+
tests/test_sync_state.py
|
|
70
82
|
tests/test_treeutil.py
|
|
@@ -223,6 +223,48 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
223
223
|
help="Suppress per-item progress output.",
|
|
224
224
|
)
|
|
225
225
|
|
|
226
|
+
# --- sync-comments ---
|
|
227
|
+
sc = sub.add_parser(
|
|
228
|
+
"sync-comments",
|
|
229
|
+
help="Sync open Confluence comments to GitHub review PRs.",
|
|
230
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
231
|
+
epilog=(
|
|
232
|
+
"Requires github_repo and github_token (or GITHUB_TOKEN env var) in mkdocs.yml.\n"
|
|
233
|
+
"\n"
|
|
234
|
+
"Examples:\n"
|
|
235
|
+
" mk2conf sync-comments # sync new comments → PRs\n"
|
|
236
|
+
" mk2conf sync-comments --check-merges # resolve merged PRs in Confluence\n"
|
|
237
|
+
" mk2conf sync-comments --dry-run # preview without making changes\n"
|
|
238
|
+
),
|
|
239
|
+
)
|
|
240
|
+
sc.add_argument(
|
|
241
|
+
"--config",
|
|
242
|
+
metavar="PATH",
|
|
243
|
+
default="mkdocs.yml",
|
|
244
|
+
help="Path to mkdocs.yml (default: ./mkdocs.yml).",
|
|
245
|
+
)
|
|
246
|
+
sc.add_argument(
|
|
247
|
+
"--check-merges",
|
|
248
|
+
action="store_true",
|
|
249
|
+
help="Check tracked PRs for merges and resolve their Confluence comments.",
|
|
250
|
+
)
|
|
251
|
+
sc.add_argument(
|
|
252
|
+
"--force",
|
|
253
|
+
action="store_true",
|
|
254
|
+
help="Re-sync pages that already have an open review PR.",
|
|
255
|
+
)
|
|
256
|
+
sc.add_argument(
|
|
257
|
+
"--dry-run",
|
|
258
|
+
action="store_true",
|
|
259
|
+
help="Print what would be synced without creating branches, PRs, or review threads.",
|
|
260
|
+
)
|
|
261
|
+
sc.add_argument(
|
|
262
|
+
"--quiet",
|
|
263
|
+
"-q",
|
|
264
|
+
action="store_true",
|
|
265
|
+
help="Suppress progress output.",
|
|
266
|
+
)
|
|
267
|
+
|
|
226
268
|
return parser
|
|
227
269
|
|
|
228
270
|
|
|
@@ -244,6 +286,8 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
244
286
|
_cmd_publish(args)
|
|
245
287
|
elif args.command == "pdf":
|
|
246
288
|
_cmd_pdf(args)
|
|
289
|
+
elif args.command == "sync-comments":
|
|
290
|
+
_cmd_sync_comments(args)
|
|
247
291
|
except (ValueError, FileNotFoundError) as exc:
|
|
248
292
|
print(f"error: {exc}", file=sys.stderr)
|
|
249
293
|
sys.exit(1)
|
|
@@ -503,6 +547,27 @@ def _cmd_publish(args: argparse.Namespace) -> None:
|
|
|
503
547
|
|
|
504
548
|
print(str(report))
|
|
505
549
|
|
|
550
|
+
# Write page map so sync-comments can match source files to Confluence pages.
|
|
551
|
+
if not (getattr(args, "page", None) or getattr(args, "section", None)):
|
|
552
|
+
import json as _json_pm
|
|
553
|
+
try:
|
|
554
|
+
repo_root = config_path.parent
|
|
555
|
+
try:
|
|
556
|
+
docs_rel = config.docs_dir.relative_to(repo_root)
|
|
557
|
+
except ValueError:
|
|
558
|
+
docs_rel = Path("docs")
|
|
559
|
+
page_map = {
|
|
560
|
+
str(docs_rel / action.node.docs_path): action.page_id
|
|
561
|
+
for action in plan
|
|
562
|
+
if action.node.docs_path and action.page_id and not action.is_folder
|
|
563
|
+
}
|
|
564
|
+
pm_path = repo_root / ".mk2conf-pages.json"
|
|
565
|
+
pm_path.write_text(_json_pm.dumps(page_map, indent=2), encoding="utf-8")
|
|
566
|
+
if not getattr(args, "quiet", False):
|
|
567
|
+
print(f"Page map: {len(page_map)} page(s) → {pm_path.name}")
|
|
568
|
+
except Exception as exc:
|
|
569
|
+
print(f" [warn] could not write page map: {exc}", file=sys.stderr)
|
|
570
|
+
|
|
506
571
|
if getattr(args, "report", None):
|
|
507
572
|
import json as _json
|
|
508
573
|
|
|
@@ -612,3 +677,61 @@ def _cmd_pdf(args: argparse.Namespace) -> None:
|
|
|
612
677
|
sys.exit(1)
|
|
613
678
|
|
|
614
679
|
print(f"PDF written to {out_path}")
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def _cmd_sync_comments(args: argparse.Namespace) -> None:
|
|
683
|
+
from mkdocs_to_confluence.publisher.client import ConfluenceClient, ConfluenceError
|
|
684
|
+
from mkdocs_to_confluence.sync.command import check_and_resolve_merges, run_sync_comments
|
|
685
|
+
from mkdocs_to_confluence.sync.github import GitHubReviewClient
|
|
686
|
+
|
|
687
|
+
config_path = Path(args.config).resolve()
|
|
688
|
+
config = load_config(config_path)
|
|
689
|
+
|
|
690
|
+
conf = config.confluence
|
|
691
|
+
if conf is None:
|
|
692
|
+
print("error: no 'confluence:' section in mkdocs.yml", file=sys.stderr)
|
|
693
|
+
sys.exit(1)
|
|
694
|
+
|
|
695
|
+
if not conf.token:
|
|
696
|
+
print("error: Confluence API token not set. Set CONFLUENCE_API_TOKEN env var.", file=sys.stderr)
|
|
697
|
+
sys.exit(1)
|
|
698
|
+
|
|
699
|
+
if not conf.github_repo:
|
|
700
|
+
print(
|
|
701
|
+
"error: 'confluence.github_repo' is required for sync-comments (e.g. 'owner/repo').",
|
|
702
|
+
file=sys.stderr,
|
|
703
|
+
)
|
|
704
|
+
sys.exit(1)
|
|
705
|
+
|
|
706
|
+
if not conf.github_token:
|
|
707
|
+
print(
|
|
708
|
+
"error: GitHub token not set. Set GITHUB_TOKEN env var or 'confluence.github_token' in mkdocs.yml.",
|
|
709
|
+
file=sys.stderr,
|
|
710
|
+
)
|
|
711
|
+
sys.exit(1)
|
|
712
|
+
|
|
713
|
+
review_client = GitHubReviewClient(conf.github_repo, conf.github_token)
|
|
714
|
+
config_dir = config_path.parent
|
|
715
|
+
|
|
716
|
+
try:
|
|
717
|
+
with ConfluenceClient(conf) as confluence_client:
|
|
718
|
+
if args.check_merges:
|
|
719
|
+
check_and_resolve_merges(
|
|
720
|
+
config_dir=config_dir,
|
|
721
|
+
confluence_client=confluence_client,
|
|
722
|
+
review_client=review_client,
|
|
723
|
+
quiet=args.quiet,
|
|
724
|
+
)
|
|
725
|
+
else:
|
|
726
|
+
run_sync_comments(
|
|
727
|
+
config=config,
|
|
728
|
+
config_dir=config_dir,
|
|
729
|
+
confluence_client=confluence_client,
|
|
730
|
+
review_client=review_client,
|
|
731
|
+
force=args.force,
|
|
732
|
+
dry_run=args.dry_run,
|
|
733
|
+
quiet=args.quiet,
|
|
734
|
+
)
|
|
735
|
+
except ConfluenceError as exc:
|
|
736
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
737
|
+
sys.exit(1)
|
{mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/loader/config.py
RENAMED
|
@@ -30,6 +30,9 @@ class ConfluenceConfig:
|
|
|
30
30
|
full_width: bool = True # set full-width layout on every published page
|
|
31
31
|
nav_file: str = ".pages" # awesome-pages filename (default: .pages, can be .nav etc.)
|
|
32
32
|
mermaid_render: str = "kroki" # "kroki", "kroki:<url>", or "none"
|
|
33
|
+
github_repo: str | None = None # "owner/repo" — required for sync-comments
|
|
34
|
+
github_token: str | None = None # GitHub PAT (falls back to GITHUB_TOKEN env var)
|
|
35
|
+
github_base_branch: str = "main" # base branch for review PRs
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
@dataclass(frozen=True)
|
|
@@ -241,6 +244,10 @@ def load_config(mkdocs_yml: Path) -> MkDocsConfig:
|
|
|
241
244
|
full_width=bool(raw_conf.get("full_width", True)),
|
|
242
245
|
nav_file=str(raw_conf.get("nav_file", ".pages")),
|
|
243
246
|
mermaid_render=str(raw_conf.get("mermaid_render", "kroki")),
|
|
247
|
+
github_repo=str(raw_conf["github_repo"]) if raw_conf.get("github_repo") else None,
|
|
248
|
+
github_token=(str(raw_conf["github_token"]) if raw_conf.get("github_token")
|
|
249
|
+
else os.environ.get("GITHUB_TOKEN") or None),
|
|
250
|
+
github_base_branch=str(raw_conf.get("github_base_branch", "main")),
|
|
244
251
|
)
|
|
245
252
|
|
|
246
253
|
# --- extra_css (optional) ---
|
{mkdocs2confluence-0.7.38 → mkdocs2confluence-0.8.1}/src/mkdocs_to_confluence/publisher/client.py
RENAMED
|
@@ -538,3 +538,85 @@ class ConfluenceClient:
|
|
|
538
538
|
"""Permanently delete *page_id* from Confluence."""
|
|
539
539
|
resp = self._http.delete(self._v2(f"/pages/{page_id}"))
|
|
540
540
|
self._raise_for_status(resp, f"delete_page({page_id!r})")
|
|
541
|
+
|
|
542
|
+
# ── Comments ───────────────────────────────────────────────────────────────
|
|
543
|
+
|
|
544
|
+
def get_page_inline_comments(self, page_id: str) -> list[dict[str, Any]]:
|
|
545
|
+
"""Return all open inline comments for *page_id* (paginated)."""
|
|
546
|
+
results: list[dict[str, Any]] = []
|
|
547
|
+
url = self._v2(f"/pages/{page_id}/inline-comments")
|
|
548
|
+
params: dict[str, Any] = {"resolution-status": "open", "body-format": "storage", "limit": 250}
|
|
549
|
+
while True:
|
|
550
|
+
resp = self._http.get(url, params=params)
|
|
551
|
+
self._raise_for_status(resp, f"get_page_inline_comments({page_id!r})")
|
|
552
|
+
data = resp.json()
|
|
553
|
+
results.extend(data.get("results", []))
|
|
554
|
+
next_url = data.get("_links", {}).get("next")
|
|
555
|
+
if not next_url:
|
|
556
|
+
break
|
|
557
|
+
url = self._v2(f"/pages/{page_id}/inline-comments")
|
|
558
|
+
params = {"resolution-status": "open", "body-format": "storage",
|
|
559
|
+
"limit": 250, "cursor": _extract_cursor(next_url)}
|
|
560
|
+
return results
|
|
561
|
+
|
|
562
|
+
def get_page_footer_comments(self, page_id: str) -> list[dict[str, Any]]:
|
|
563
|
+
"""Return all open footer comments for *page_id* (paginated)."""
|
|
564
|
+
results: list[dict[str, Any]] = []
|
|
565
|
+
url = self._v2(f"/pages/{page_id}/footer-comments")
|
|
566
|
+
params: dict[str, Any] = {"resolution-status": "open", "body-format": "storage", "limit": 250}
|
|
567
|
+
while True:
|
|
568
|
+
resp = self._http.get(url, params=params)
|
|
569
|
+
self._raise_for_status(resp, f"get_page_footer_comments({page_id!r})")
|
|
570
|
+
data = resp.json()
|
|
571
|
+
results.extend(data.get("results", []))
|
|
572
|
+
next_url = data.get("_links", {}).get("next")
|
|
573
|
+
if not next_url:
|
|
574
|
+
break
|
|
575
|
+
url = self._v2(f"/pages/{page_id}/footer-comments")
|
|
576
|
+
params = {"resolution-status": "open", "body-format": "storage",
|
|
577
|
+
"limit": 250, "cursor": _extract_cursor(next_url)}
|
|
578
|
+
return results
|
|
579
|
+
|
|
580
|
+
def add_comment_reply(self, comment_id: str, reply_text: str) -> None:
|
|
581
|
+
"""Post a reply to *comment_id* using the v1 content API."""
|
|
582
|
+
url = self._v1(f"/content/{comment_id}/child/comment")
|
|
583
|
+
resp = self._http.post(url, json={
|
|
584
|
+
"type": "comment",
|
|
585
|
+
"body": {
|
|
586
|
+
"storage": {
|
|
587
|
+
"value": f"<p>{reply_text}</p>",
|
|
588
|
+
"representation": "storage",
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
})
|
|
592
|
+
self._raise_for_status(resp, f"add_comment_reply({comment_id!r})")
|
|
593
|
+
|
|
594
|
+
def resolve_inline_comment(self, comment_id: str) -> None:
|
|
595
|
+
"""Resolve an inline comment by setting *resolved=true*."""
|
|
596
|
+
url = self._v2(f"/inline-comments/{comment_id}")
|
|
597
|
+
get_resp = self._http.get(url, params={"body-format": "storage"})
|
|
598
|
+
self._raise_for_status(get_resp, f"get_inline_comment({comment_id!r})")
|
|
599
|
+
data = get_resp.json()
|
|
600
|
+
version = data.get("version", {}).get("number", 1)
|
|
601
|
+
body_value = data.get("body", {}).get("storage", {}).get("value", "")
|
|
602
|
+
resp = self._http.put(url, json={
|
|
603
|
+
"version": {"number": version + 1},
|
|
604
|
+
"resolved": True,
|
|
605
|
+
"body": {"representation": "storage", "value": body_value},
|
|
606
|
+
})
|
|
607
|
+
self._raise_for_status(resp, f"resolve_inline_comment({comment_id!r})")
|
|
608
|
+
|
|
609
|
+
def resolve_footer_comment(self, comment_id: str) -> None:
|
|
610
|
+
"""Resolve a footer comment by setting *resolved=true*."""
|
|
611
|
+
url = self._v2(f"/footer-comments/{comment_id}")
|
|
612
|
+
get_resp = self._http.get(url, params={"body-format": "storage"})
|
|
613
|
+
self._raise_for_status(get_resp, f"get_footer_comment({comment_id!r})")
|
|
614
|
+
data = get_resp.json()
|
|
615
|
+
version = data.get("version", {}).get("number", 1)
|
|
616
|
+
body_value = data.get("body", {}).get("storage", {}).get("value", "")
|
|
617
|
+
resp = self._http.put(url, json={
|
|
618
|
+
"version": {"number": version + 1},
|
|
619
|
+
"resolved": True,
|
|
620
|
+
"body": {"representation": "storage", "value": body_value},
|
|
621
|
+
})
|
|
622
|
+
self._raise_for_status(resp, f"resolve_footer_comment({comment_id!r})")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Confluence ↔ GitHub comment synchronisation."""
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Map Confluence inlineOriginalSelection text to a source file line number."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def find_anchor_line(source_path: Path, selection_text: str) -> int | None:
|
|
9
|
+
"""Return the 1-based line number of the first line containing *selection_text*.
|
|
10
|
+
|
|
11
|
+
Returns ``None`` when:
|
|
12
|
+
- *selection_text* is empty
|
|
13
|
+
- the file cannot be read
|
|
14
|
+
- no line contains the text
|
|
15
|
+
"""
|
|
16
|
+
if not selection_text:
|
|
17
|
+
return None
|
|
18
|
+
try:
|
|
19
|
+
lines = source_path.read_text(encoding="utf-8").splitlines()
|
|
20
|
+
except OSError:
|
|
21
|
+
return None
|
|
22
|
+
for i, line in enumerate(lines, start=1):
|
|
23
|
+
if selection_text in line:
|
|
24
|
+
return i
|
|
25
|
+
return None
|