mkdocs2confluence 0.7.14__tar.gz → 0.7.16__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.
Files changed (72) hide show
  1. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/PKG-INFO +5 -3
  2. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/README.md +4 -2
  3. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/pyproject.toml +1 -1
  4. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs2confluence.egg-info/PKG-INFO +5 -3
  5. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/cli.py +4 -4
  6. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/ir/nodes.py +1 -0
  7. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/preprocess/frontmatter.py +8 -1
  8. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/publisher/client.py +10 -0
  9. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/publisher/pipeline.py +22 -8
  10. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_cli.py +2 -2
  11. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_frontmatter.py +18 -0
  12. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_publish_client.py +17 -1
  13. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_publish_pipeline.py +4 -4
  14. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/LICENSE +0 -0
  15. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/setup.cfg +0 -0
  16. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs2confluence.egg-info/SOURCES.txt +0 -0
  17. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs2confluence.egg-info/dependency_links.txt +0 -0
  18. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs2confluence.egg-info/entry_points.txt +0 -0
  19. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs2confluence.egg-info/requires.txt +0 -0
  20. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs2confluence.egg-info/top_level.txt +0 -0
  21. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/__init__.py +0 -0
  22. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/emitter/__init__.py +0 -0
  23. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/emitter/xhtml.py +0 -0
  24. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/ir/__init__.py +0 -0
  25. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/ir/document.py +0 -0
  26. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/ir/treeutil.py +0 -0
  27. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/loader/__init__.py +0 -0
  28. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/loader/config.py +0 -0
  29. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/loader/extra_css.py +0 -0
  30. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/loader/nav.py +0 -0
  31. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/loader/page.py +0 -0
  32. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/parser/__init__.py +0 -0
  33. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/parser/markdown.py +0 -0
  34. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/pdf/__init__.py +0 -0
  35. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/pdf/generator.py +0 -0
  36. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/pdf/render.py +0 -0
  37. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/preprocess/__init__.py +0 -0
  38. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/preprocess/abbrevs.py +0 -0
  39. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/preprocess/fence.py +0 -0
  40. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/preprocess/icons.py +0 -0
  41. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/preprocess/includes.py +0 -0
  42. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/preprocess/linkdefs.py +0 -0
  43. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/preview/__init__.py +0 -0
  44. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/preview/render.py +0 -0
  45. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/preview/server.py +0 -0
  46. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/publisher/__init__.py +0 -0
  47. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/transforms/__init__.py +0 -0
  48. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/transforms/abbrevs.py +0 -0
  49. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/transforms/assets.py +0 -0
  50. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/transforms/editlink.py +0 -0
  51. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/transforms/images.py +0 -0
  52. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/transforms/internallinks.py +0 -0
  53. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/src/mkdocs_to_confluence/transforms/mermaid.py +0 -0
  54. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_abbrevs.py +0 -0
  55. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_editlink.py +0 -0
  56. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_emitter.py +0 -0
  57. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_extra_css.py +0 -0
  58. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_icons.py +0 -0
  59. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_images.py +0 -0
  60. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_internallinks.py +0 -0
  61. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_ir.py +0 -0
  62. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_linkdefs.py +0 -0
  63. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_loader.py +0 -0
  64. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_mermaid.py +0 -0
  65. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_page_loader.py +0 -0
  66. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_parser.py +0 -0
  67. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_pdf.py +0 -0
  68. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_preprocess.py +0 -0
  69. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_preview.py +0 -0
  70. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_publish_config.py +0 -0
  71. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_server.py +0 -0
  72. {mkdocs2confluence-0.7.14 → mkdocs2confluence-0.7.16}/tests/test_treeutil.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs2confluence
3
- Version: 0.7.14
3
+ Version: 0.7.16
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
@@ -340,6 +340,7 @@ lastUpdated: 2026-01-12
340
340
  author: "Anders Hybertz"
341
341
  tags: [architecture, iam]
342
342
  ready: true
343
+ status: in-progress
343
344
  ---
344
345
  ```
345
346
 
@@ -349,13 +350,14 @@ ready: true
349
350
  | `subtitle` | Rendered as italic lead paragraph above the properties table |
350
351
  | `tags` | Also applied as Confluence page labels |
351
352
  | `ready` | `true` → ✅ Ready · `false` → 📝 Draft (skips publish) |
353
+ | `status` | Sets the Confluence page status badge — common values: `rough-draft`, `in-progress`, `ready-for-review` (space-specific values are also supported). Not shown in the properties table. |
352
354
  | *other fields* | Title-cased key, value stringified |
353
355
 
354
356
  If `repo_url` + `edit_uri` are set in `mkdocs.yml`, an **Edit Source** row links to the source file. If `site_url` is set, a **Published Page** row links to the rendered MkDocs site.
355
357
 
356
358
  ### Abbreviation expansion
357
359
 
358
- MkDocs abbreviation definitions (`*[ABBR]: Full term`) are rendered as inline Confluence **footnotes**. The **first occurrence** of each abbreviation in body text is annotated with a footnote macro Confluence renders it as a superscript number and collects all definitions at the bottom of the page. Subsequent occurrences are left as plain text. Abbreviations that only appear in headings or other non-expandable contexts are collected into an auto-appended **Glossary** section as a fallback.
360
+ MkDocs abbreviation definitions (`*[ABBR]: Full term`) are rendered as inline superscript anchor links. The **first occurrence** of each abbreviation in body text gets a superscript number (`API¹`) that links to a numbered glossary appended at the bottom of the page. Subsequent occurrences are left as plain text. Abbreviations that only appear in headings or other non-expandable contexts are included in the glossary as plain numbered entries (no inline back-link). Uses only native Confluence storage format — no plugins required.
359
361
 
360
362
  ---
361
363
 
@@ -364,7 +366,7 @@ MkDocs abbreviation definitions (`*[ABBR]: Full term`) are rendered as inline Co
364
366
  | Feature | Behaviour |
365
367
  |---|---|
366
368
  | **Admonition styling** | `tip`, `info`, `warning`, `note` use Confluence's fixed native macro colours. `danger`, `error`, `bug` use a custom red `panel` macro with 🚨 prefix. All other types are mapped to the nearest native macro. |
367
- | **Abbreviation tooltips** | No native tooltip support. First occurrence annotated with an inline Confluence footnote; definition collected at the bottom of the page. |
369
+ | **Abbreviation tooltips** | No native tooltip support. First occurrence gets a superscript anchor link (`API¹`); all definitions collected in a numbered glossary at the bottom of the page. No plugins required. |
368
370
  | **Page ordering** | Confluence sorts child pages alphabetically; the v2 REST API has no write endpoint for ordering. |
369
371
  | **Code language aliases** | Short aliases (`py`, `js`, `yml`, `ts`, `sh`) are passed through as-is; Confluence requires full language names for syntax highlighting. |
370
372
  | **Unrecognised blocks** | Preserved as a visible `warning` macro — no content is silently lost. |
@@ -300,6 +300,7 @@ lastUpdated: 2026-01-12
300
300
  author: "Anders Hybertz"
301
301
  tags: [architecture, iam]
302
302
  ready: true
303
+ status: in-progress
303
304
  ---
304
305
  ```
305
306
 
@@ -309,13 +310,14 @@ ready: true
309
310
  | `subtitle` | Rendered as italic lead paragraph above the properties table |
310
311
  | `tags` | Also applied as Confluence page labels |
311
312
  | `ready` | `true` → ✅ Ready · `false` → 📝 Draft (skips publish) |
313
+ | `status` | Sets the Confluence page status badge — common values: `rough-draft`, `in-progress`, `ready-for-review` (space-specific values are also supported). Not shown in the properties table. |
312
314
  | *other fields* | Title-cased key, value stringified |
313
315
 
314
316
  If `repo_url` + `edit_uri` are set in `mkdocs.yml`, an **Edit Source** row links to the source file. If `site_url` is set, a **Published Page** row links to the rendered MkDocs site.
315
317
 
316
318
  ### Abbreviation expansion
317
319
 
318
- MkDocs abbreviation definitions (`*[ABBR]: Full term`) are rendered as inline Confluence **footnotes**. The **first occurrence** of each abbreviation in body text is annotated with a footnote macro Confluence renders it as a superscript number and collects all definitions at the bottom of the page. Subsequent occurrences are left as plain text. Abbreviations that only appear in headings or other non-expandable contexts are collected into an auto-appended **Glossary** section as a fallback.
320
+ MkDocs abbreviation definitions (`*[ABBR]: Full term`) are rendered as inline superscript anchor links. The **first occurrence** of each abbreviation in body text gets a superscript number (`API¹`) that links to a numbered glossary appended at the bottom of the page. Subsequent occurrences are left as plain text. Abbreviations that only appear in headings or other non-expandable contexts are included in the glossary as plain numbered entries (no inline back-link). Uses only native Confluence storage format — no plugins required.
319
321
 
320
322
  ---
321
323
 
@@ -324,7 +326,7 @@ MkDocs abbreviation definitions (`*[ABBR]: Full term`) are rendered as inline Co
324
326
  | Feature | Behaviour |
325
327
  |---|---|
326
328
  | **Admonition styling** | `tip`, `info`, `warning`, `note` use Confluence's fixed native macro colours. `danger`, `error`, `bug` use a custom red `panel` macro with 🚨 prefix. All other types are mapped to the nearest native macro. |
327
- | **Abbreviation tooltips** | No native tooltip support. First occurrence annotated with an inline Confluence footnote; definition collected at the bottom of the page. |
329
+ | **Abbreviation tooltips** | No native tooltip support. First occurrence gets a superscript anchor link (`API¹`); all definitions collected in a numbered glossary at the bottom of the page. No plugins required. |
328
330
  | **Page ordering** | Confluence sorts child pages alphabetically; the v2 REST API has no write endpoint for ordering. |
329
331
  | **Code language aliases** | Short aliases (`py`, `js`, `yml`, `ts`, `sh`) are passed through as-is; Confluence requires full language names for syntax highlighting. |
330
332
  | **Unrecognised blocks** | Preserved as a visible `warning` macro — no content is silently lost. |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mkdocs2confluence"
3
- version = "0.7.14"
3
+ version = "0.7.16"
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" }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs2confluence
3
- Version: 0.7.14
3
+ Version: 0.7.16
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
@@ -340,6 +340,7 @@ lastUpdated: 2026-01-12
340
340
  author: "Anders Hybertz"
341
341
  tags: [architecture, iam]
342
342
  ready: true
343
+ status: in-progress
343
344
  ---
344
345
  ```
345
346
 
@@ -349,13 +350,14 @@ ready: true
349
350
  | `subtitle` | Rendered as italic lead paragraph above the properties table |
350
351
  | `tags` | Also applied as Confluence page labels |
351
352
  | `ready` | `true` → ✅ Ready · `false` → 📝 Draft (skips publish) |
353
+ | `status` | Sets the Confluence page status badge — common values: `rough-draft`, `in-progress`, `ready-for-review` (space-specific values are also supported). Not shown in the properties table. |
352
354
  | *other fields* | Title-cased key, value stringified |
353
355
 
354
356
  If `repo_url` + `edit_uri` are set in `mkdocs.yml`, an **Edit Source** row links to the source file. If `site_url` is set, a **Published Page** row links to the rendered MkDocs site.
355
357
 
356
358
  ### Abbreviation expansion
357
359
 
358
- MkDocs abbreviation definitions (`*[ABBR]: Full term`) are rendered as inline Confluence **footnotes**. The **first occurrence** of each abbreviation in body text is annotated with a footnote macro Confluence renders it as a superscript number and collects all definitions at the bottom of the page. Subsequent occurrences are left as plain text. Abbreviations that only appear in headings or other non-expandable contexts are collected into an auto-appended **Glossary** section as a fallback.
360
+ MkDocs abbreviation definitions (`*[ABBR]: Full term`) are rendered as inline superscript anchor links. The **first occurrence** of each abbreviation in body text gets a superscript number (`API¹`) that links to a numbered glossary appended at the bottom of the page. Subsequent occurrences are left as plain text. Abbreviations that only appear in headings or other non-expandable contexts are included in the glossary as plain numbered entries (no inline back-link). Uses only native Confluence storage format — no plugins required.
359
361
 
360
362
  ---
361
363
 
@@ -364,7 +366,7 @@ MkDocs abbreviation definitions (`*[ABBR]: Full term`) are rendered as inline Co
364
366
  | Feature | Behaviour |
365
367
  |---|---|
366
368
  | **Admonition styling** | `tip`, `info`, `warning`, `note` use Confluence's fixed native macro colours. `danger`, `error`, `bug` use a custom red `panel` macro with 🚨 prefix. All other types are mapped to the nearest native macro. |
367
- | **Abbreviation tooltips** | No native tooltip support. First occurrence annotated with an inline Confluence footnote; definition collected at the bottom of the page. |
369
+ | **Abbreviation tooltips** | No native tooltip support. First occurrence gets a superscript anchor link (`API¹`); all definitions collected in a numbered glossary at the bottom of the page. No plugins required. |
368
370
  | **Page ordering** | Confluence sorts child pages alphabetically; the v2 REST API has no write endpoint for ordering. |
369
371
  | **Code language aliases** | Short aliases (`py`, `js`, `yml`, `ts`, `sh`) are passed through as-is; Confluence requires full language names for syntax highlighting. |
370
372
  | **Unrecognised blocks** | Preserved as a visible `warning` macro — no content is silently lost. |
@@ -315,7 +315,7 @@ def _cmd_preview(args: argparse.Namespace) -> None:
315
315
  for node in pages:
316
316
  html_name = page_link_map.get(node.title, f"{Path(node.docs_path or node.title).stem}.html")
317
317
  try:
318
- xhtml, _a, _l = compile_page(node, config, link_map, quiet=args.quiet)
318
+ xhtml, _a, _l, _s = compile_page(node, config, link_map, quiet=args.quiet)
319
319
  except PageLoadError as exc:
320
320
  print(f" warning: skipping '{node.title}': {exc}", file=sys.stderr)
321
321
  continue
@@ -373,7 +373,7 @@ def _cmd_preview(args: argparse.Namespace) -> None:
373
373
 
374
374
  def _build_page() -> None:
375
375
  try:
376
- xhtml, _a, _l = compile_page(page_node, config, link_map, quiet=True)
376
+ xhtml, _a, _l, _s = compile_page(page_node, config, link_map, quiet=True)
377
377
  except PageLoadError as exc:
378
378
  print(f" warning: {exc}", file=sys.stderr)
379
379
  return
@@ -397,7 +397,7 @@ def _cmd_preview(args: argparse.Namespace) -> None:
397
397
  return
398
398
 
399
399
  try:
400
- xhtml, _attachments, _labels = compile_page(page_node, config, link_map, quiet=args.quiet)
400
+ xhtml, _attachments, _labels, _status = compile_page(page_node, config, link_map, quiet=args.quiet)
401
401
  except PageLoadError as exc:
402
402
  print(f"error: {exc}", file=sys.stderr)
403
403
  sys.exit(1)
@@ -583,7 +583,7 @@ def _cmd_pdf(args: argparse.Namespace) -> None:
583
583
  chapters: list[tuple[str, str]] = []
584
584
  for node in pages:
585
585
  try:
586
- xhtml, _a, _l = compile_page(node, config, link_map, quiet=args.quiet)
586
+ xhtml, _a, _l, _s = compile_page(node, config, link_map, quiet=args.quiet)
587
587
  except PageLoadError as exc:
588
588
  print(f" warning: skipping '{node.title}': {exc}", file=sys.stderr)
589
589
  continue
@@ -426,6 +426,7 @@ class FrontMatter(IRNode):
426
426
  labels: tuple[str, ...]
427
427
  source_url: str | None = None
428
428
  site_url: str | None = None
429
+ confluence_status: str | None = None
429
430
 
430
431
 
431
432
  # ── Abbreviation footnotes ────────────────────────────────────────────────────
@@ -47,7 +47,9 @@ from mkdocs_to_confluence.ir.nodes import FrontMatter
47
47
  # ── Constants ─────────────────────────────────────────────────────────────────
48
48
 
49
49
  # Fields that carry no meaning in Confluence and should be discarded silently.
50
- _STRIP_FIELDS: frozenset[str] = frozenset({"source"})
50
+ # ``status`` is consumed as a publishing directive (sets the Confluence page
51
+ # status via the API) and must not appear in the Page Properties table.
52
+ _STRIP_FIELDS: frozenset[str] = frozenset({"source", "status"})
51
53
 
52
54
  # Fields whose value has special formatting logic (see _format_value).
53
55
  _DISPLAY_NAMES: dict[str, str] = {
@@ -114,6 +116,10 @@ def _build_node(raw: dict[str, Any]) -> FrontMatter:
114
116
  title: str | None = _stringify(raw.get("title")) if "title" in raw else None
115
117
  subtitle: str | None = _stringify(raw.get("subtitle")) if "subtitle" in raw else None
116
118
 
119
+ # ``status:`` sets the Confluence page lifecycle status via the API.
120
+ status_raw = raw.get("status")
121
+ confluence_status: str | None = str(status_raw) if status_raw is not None else None
122
+
117
123
  # Labels come from the ``tags`` field.
118
124
  tags_raw = raw.get("tags", [])
119
125
  labels: tuple[str, ...] = tuple(str(t) for t in (tags_raw if isinstance(tags_raw, list) else [tags_raw]))
@@ -140,6 +146,7 @@ def _build_node(raw: dict[str, Any]) -> FrontMatter:
140
146
  subtitle=subtitle,
141
147
  properties=tuple(properties),
142
148
  labels=labels,
149
+ confluence_status=confluence_status,
143
150
  )
144
151
 
145
152
 
@@ -361,6 +361,16 @@ class ConfluenceClient:
361
361
  resp = self._http.post(label_url, json=payload)
362
362
  self._raise_for_status(resp, f"set_page_labels({page_id!r})")
363
363
 
364
+ def set_page_status(self, page_id: str, status_key: str) -> None:
365
+ """Set the Confluence page status (e.g. ``rough-draft``, ``in-progress``).
366
+
367
+ Uses the v1 ``PUT /content/{id}/state`` endpoint. The *status_key*
368
+ must match a state key configured in the Confluence space.
369
+ """
370
+ url = self._v1(f"/content/{page_id}/state")
371
+ resp = self._http.put(url, json={"state": {"key": status_key}})
372
+ self._raise_for_status(resp, f"set_page_status({page_id!r}, {status_key!r})")
373
+
364
374
  def list_attachments(self, page_id: str) -> dict[str, dict[str, Any]]:
365
375
  """Return a ``{filename: metadata}`` mapping of all page attachments.
366
376
 
@@ -73,6 +73,7 @@ class PageAction:
73
73
  xhtml: str | None = None
74
74
  attachments: list[Path] = field(default_factory=list)
75
75
  labels: tuple[str, ...] = field(default_factory=tuple)
76
+ confluence_status: str | None = None
76
77
  is_folder: bool = False # True when this action creates a Confluence folder
77
78
  parent_is_folder: bool = False # True when the parent content is a folder
78
79
  # Set after execution:
@@ -137,16 +138,16 @@ def compile_page(
137
138
  link_map: dict[str, str] | None = None,
138
139
  *,
139
140
  quiet: bool = False,
140
- ) -> tuple[str, list[Path], tuple[str, ...]]:
141
+ ) -> tuple[str, list[Path], tuple[str, ...], str | None]:
141
142
  """Run the full compile pipeline for one page.
142
143
 
143
144
  Returns
144
145
  -------
145
- tuple[str, list[Path], tuple[str, ...]]
146
- ``(xhtml_string, attachment_paths, labels)``
146
+ tuple[str, list[Path], tuple[str, ...], str | None]
147
+ ``(xhtml_string, attachment_paths, labels, confluence_status)``
147
148
  """
148
149
  if node.source_path is None:
149
- return "", [], ()
150
+ return "", [], (), None
150
151
 
151
152
  raw = load_page(node)
152
153
 
@@ -189,15 +190,17 @@ def compile_page(
189
190
  if edit_url or site_url:
190
191
  ir_nodes = attach_source_url(ir_nodes, edit_url or "", site_url)
191
192
 
192
- # Extract labels from FrontMatter node (tags: field)
193
+ # Extract labels and confluence_status from FrontMatter node.
193
194
  labels: tuple[str, ...] = ()
195
+ confluence_status: str | None = None
194
196
  for node_item in ir_nodes:
195
197
  if isinstance(node_item, FrontMatter):
196
198
  labels = node_item.labels
199
+ confluence_status = node_item.confluence_status
197
200
  break
198
201
 
199
202
  xhtml = emit(ir_nodes)
200
- return xhtml, attachments, labels
203
+ return xhtml, attachments, labels, confluence_status
201
204
 
202
205
 
203
206
  def _xhtml_hash(xhtml: str) -> str:
@@ -271,7 +274,9 @@ def _plan_nodes(
271
274
  if not quiet:
272
275
  print(f" compiling '{clean_title}' (section index)")
273
276
  try:
274
- xhtml, attachments, labels = compile_page(index_child, config, link_map, quiet=quiet)
277
+ xhtml, attachments, labels, confluence_status = compile_page(
278
+ index_child, config, link_map, quiet=quiet
279
+ )
275
280
  existing = client.find_page(space_id, clean_title)
276
281
  xhtml_h = _xhtml_hash(xhtml)
277
282
  if existing is not None and client.get_content_hash(str(existing["id"])) == xhtml_h:
@@ -300,6 +305,7 @@ def _plan_nodes(
300
305
  xhtml=xhtml,
301
306
  attachments=attachments,
302
307
  labels=labels,
308
+ confluence_status=confluence_status,
303
309
  page_id=str(existing["id"]) if existing is not None else None,
304
310
  version=(
305
311
  existing["version"]["number"] if existing is not None else None
@@ -367,7 +373,7 @@ def _plan_nodes(
367
373
  if not quiet:
368
374
  print(f" compiling '{clean_title}'")
369
375
  try:
370
- xhtml, attachments, labels = compile_page(node, config, link_map, quiet=quiet)
376
+ xhtml, attachments, labels, confluence_status = compile_page(node, config, link_map, quiet=quiet)
371
377
  except (PageLoadError, OSError) as exc:
372
378
  if not quiet:
373
379
  print(f" skipping '{clean_title}' (error: {exc})")
@@ -405,6 +411,7 @@ def _plan_nodes(
405
411
  xhtml=xhtml,
406
412
  attachments=attachments,
407
413
  labels=labels,
414
+ confluence_status=confluence_status,
408
415
  page_id=str(existing["id"]) if existing is not None else None,
409
416
  version=(
410
417
  existing["version"]["number"] if existing is not None else None
@@ -651,6 +658,13 @@ def _post_process_action(
651
658
  except Exception:
652
659
  pass
653
660
 
661
+ # Set Confluence page status (rough-draft / in-progress / etc.) — non-fatal.
662
+ if action.page_id and action.confluence_status and not action.is_folder:
663
+ try:
664
+ client.set_page_status(action.page_id, action.confluence_status)
665
+ except Exception:
666
+ pass
667
+
654
668
  # Upload assets — skip files whose mtime is not newer than Confluence.
655
669
  if action.page_id and action.attachments:
656
670
  uploaded, asset_skipped, asset_errors = _upload_assets(
@@ -175,7 +175,7 @@ class TestQuietOutputBehavior:
175
175
  yml = _minimal_config(tmp_path)
176
176
  (tmp_path / "docs" / "index.md").write_text("# Home\n\nHello.", encoding="utf-8")
177
177
 
178
- mock_compile = MagicMock(return_value=("<p>Hello</p>", [], ()))
178
+ mock_compile = MagicMock(return_value=("<p>Hello</p>", [], (), None))
179
179
  with patch("mkdocs_to_confluence.cli.compile_page", mock_compile), \
180
180
  patch("sys.stdout.isatty", return_value=False):
181
181
  flags = ["--quiet"] if quiet else []
@@ -223,7 +223,7 @@ class TestWatchFlag:
223
223
  yml = _minimal_config(tmp_path)
224
224
  (tmp_path / "docs" / "index.md").write_text("# Home\n", encoding="utf-8")
225
225
 
226
- mock_compile = MagicMock(return_value=("<p>Hello</p>", [], ()))
226
+ mock_compile = MagicMock(return_value=("<p>Hello</p>", [], (), None))
227
227
  mock_render = MagicMock(return_value="<html>preview</html>")
228
228
 
229
229
  with patch("mkdocs_to_confluence.cli.compile_page", mock_compile), \
@@ -129,6 +129,24 @@ def test_invalid_yaml_returns_none():
129
129
  assert fm is None
130
130
 
131
131
 
132
+ def test_status_extracted_as_confluence_status():
133
+ """status: in front matter is stored in confluence_status, not the table."""
134
+ text = "---\nstatus: in-progress\n---\n\nContent.\n"
135
+ fm, _ = extract_front_matter(text)
136
+ assert fm is not None
137
+ assert fm.confluence_status == "in-progress"
138
+ prop_keys = [k for k, _ in fm.properties]
139
+ assert "Status" not in prop_keys
140
+
141
+
142
+ def test_status_absent_is_none():
143
+ """When status: is absent, confluence_status is None."""
144
+ text = "---\ntitle: My Page\n---\n\nContent.\n"
145
+ fm, _ = extract_front_matter(text)
146
+ assert fm is not None
147
+ assert fm.confluence_status is None
148
+
149
+
132
150
  # ── emitter ───────────────────────────────────────────────────────────────────
133
151
 
134
152
 
@@ -164,7 +164,23 @@ def test_set_page_labels_skips_post_when_empty() -> None:
164
164
  assert len(transport.requests) == 1 # only GET, no POST
165
165
 
166
166
 
167
- # ── set_page_full_width ───────────────────────────────────────────────────────
167
+ # ── set_page_status ───────────────────────────────────────────────────────────
168
+
169
+
170
+ def test_set_page_status_sends_put() -> None:
171
+ """set_page_status PUTs the state key to the v1 /content/{id}/state endpoint."""
172
+ transport = _MockTransport(httpx.Response(200, json={}))
173
+ config = _make_config()
174
+ with ConfluenceClient(config) as client:
175
+ client._client = httpx.Client(transport=transport) # type: ignore[assignment]
176
+ client.set_page_status("42", "in-progress")
177
+ assert len(transport.requests) == 1
178
+ req = transport.requests[0]
179
+ assert req.method == "PUT"
180
+ assert "/content/42/state" in str(req.url)
181
+ import json
182
+ body = json.loads(req.content)
183
+ assert body == {"state": {"key": "in-progress"}}
168
184
 
169
185
 
170
186
  def test_set_page_full_width_creates_property_when_absent() -> None:
@@ -53,7 +53,7 @@ def test_compile_page_returns_xhtml(tmp_path: Path) -> None:
53
53
 
54
54
  node = _page_node("Index", md)
55
55
  config = _make_config(docs)
56
- xhtml, attachments, labels = compile_page(node, config)
56
+ xhtml, attachments, labels, _ = compile_page(node, config)
57
57
 
58
58
  assert "<h1>" in xhtml or "Hello" in xhtml
59
59
  assert attachments == []
@@ -68,7 +68,7 @@ def test_compile_page_with_ready_false_still_compiles(tmp_path: Path) -> None:
68
68
 
69
69
  node = _page_node("Draft", md)
70
70
  config = _make_config(docs)
71
- xhtml, attachments, labels = compile_page(node, config)
71
+ xhtml, attachments, labels, _ = compile_page(node, config)
72
72
  # Still compiles fine; plan_publish is the gatekeeper
73
73
  assert isinstance(xhtml, str)
74
74
 
@@ -78,7 +78,7 @@ def test_compile_page_with_source_path_none_returns_empty(tmp_path: Path) -> Non
78
78
  docs.mkdir()
79
79
  node = NavNode(title="Missing", docs_path="missing.md", source_path=None, level=0)
80
80
  config = _make_config(docs)
81
- xhtml, attachments, labels = compile_page(node, config)
81
+ xhtml, attachments, labels, _ = compile_page(node, config)
82
82
  assert xhtml == ""
83
83
  assert attachments == []
84
84
  assert labels == ()
@@ -179,7 +179,7 @@ def test_plan_publish_skips_when_content_unchanged(tmp_path: Path) -> None:
179
179
 
180
180
  # Compile once to get the real hash
181
181
  from mkdocs_to_confluence.publisher.pipeline import compile_page
182
- xhtml, _, _ = compile_page(node, config)
182
+ xhtml, _, _, _ = compile_page(node, config)
183
183
  stored_hash = _xhtml_hash(xhtml)
184
184
 
185
185
  existing_page = {"id": "77", "version": {"number": 2}}