Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Conversation

@marcpfuller
Copy link
Contributor

@marcpfuller marcpfuller commented Dec 5, 2025

Issue

#782
#611

Description of the Change

This PR adds comprehensive footnote support to Ghostwriter's rich text editor and report generation system. The implementation spans three main areas:

1. TipTap Editor Extension (JavaScript/TypeScript)

  • Created new Footnote extension (javascript/src/tiptap_gw/footnote.tsx) as an inline atom node
  • Footnotes display as superscript numbers in the editor with dynamic auto-numbering
  • Serializes to HTML as <footnote>content here</footnote> tags for clean semantic markup
  • Added FootnoteButton component (javascript/src/frontend/collab_forms/rich_text_editor/footnote.tsx) with modal dialog for entering footnote text
  • Integrated into the rich text editor's "Misc" menu toolbar

2. Report Generation (Python)

  • Updated HtmlToDocx class in ghostwriter/modules/reportwriter/richtext/docx.py to handle <footnote> HTML tags and convert them to proper Word footnotes using the forked python-docx library
  • Implemented sequential footnote ID tracking for reliable numbering across paragraphs and table cells
  • Added <w:footnoteRef/> element to display footnote numbers within footnote content at bottom of page
  • Implemented post-processing cleanup in DocxReportBuilder to remove extra empty paragraphs from separator footnotes that cause unwanted spacing
  • Updated HtmlToPptx to silently ignore footnotes since PowerPoint doesn't support them natively

3. Python-docx Fork Integration

  • Integrated GhostManager/python-docx fork (branch: feat/footnote2) which adds native footnote API support to python-docx
  • Modified fork to use direct superscript formatting (<w:vertAlign w:val="superscript"/>) instead of referencing non-existent FootnoteReference character style for broader template compatibility
  • Updated both local and production Dockerfiles to install git package for GitHub dependency resolution
  • Updated requirements/base.txt to install from GitHub repository instead of PyPI

4. Testing

  • Added comprehensive test suite (ghostwriter/reporting/tests/test_footnotes.py) with 8 tests covering:
    • Basic footnote creation and verification
    • Multiple footnotes with sequential IDs
    • Multi-paragraph footnotes
    • Footnote access from paragraphs and document
    • HTML-to-DOCX conversion with footnotes in various contexts
    • Footnotes within table cells
  • Extended existing rich text test suites for both DOCX and PPTX converters
  • All tests use temporary directories with automatic cleanup

Alternate Designs

Footnote Numbering Algorithm: Initially considered using python-docx's built-in _calculate_next_footnote_reference_id() algorithm, but it relies on paragraph ordering in document.paragraphs which doesn't work reliably for dynamically-built documents or table cell paragraphs. The selected approach uses simple sequential ID tracking based on the maximum existing footnote ID, which is more predictable and reliable for HTML-to-DOCX conversion workflows.

Separator Footnote Cleanup Timing: Initially attempted to clean up separator footnotes during document building by modifying the document object directly, but this interfered with docxtpl's internal template rendering state and caused the template variables to not be replaced. The selected approach uses post-processing that reopens the saved BytesIO document, cleans the separators, and re-saves it - avoiding interference with template rendering.

PowerPoint Support: PowerPoint doesn't natively support footnotes. Considered three options:

  1. Silently ignore/strip footnotes (selected)
  2. Convert to inline parenthetical text like "(Note: ...)"
  3. Collect footnotes and add as text at end of slide

Selected option 1 (silent ignore) to keep presentations clean and avoid cluttering slides with footnote text that doesn't fit the presentation medium.

Python-docx Style Reference: The fork initially used <w:rStyle w:val="FootnoteReference"/> which references a character style that may not exist in all Word templates, causing "unreadable content" errors. Modified to use direct formatting via <w:vertAlign w:val="superscript"/> XML element for universal compatibility.

Possible Drawbacks

  • External GitHub Dependency: Now depends on a GitHub-hosted fork of python-docx instead of the stable PyPI package. Requires git installation in Docker images and active maintenance of the fork to stay current with upstream changes.

  • Docker Image Size: Adding git to Alpine-based Docker images slightly increases build time (~10-20 seconds) and image size (~5-10 MB for git and dependencies).

  • Template Post-Processing: The separator cleanup step reopens and re-saves the generated document, which adds minimal overhead but could theoretically cause issues with heavily customized templates containing complex custom XML parts (though this is unlikely in practice).

  • PowerPoint Limitation: Footnotes are silently dropped in PPTX exports without user notification, which could confuse users expecting footnotes to appear. This should be documented in user-facing documentation.

  • Fork Maintenance: The python-docx fork needs to be kept in sync with upstream releases to receive bug fixes and new features, requiring ongoing maintenance effort.

Verification Process

Unit and Integration Testing:

# Ran full Django test suite with coverage
docker compose -f local.yml run django coverage run manage.py test

# Ran footnote-specific tests
docker compose -f local.yml run django python manage.py test ghostwriter.reporting.tests.test_footnotes

# Verified all 8 new footnote tests pass
# Verified all 31 existing rich text tests still pass (no regressions)

Code Quality Checks:

# Ran pylint on modified Python files
docker compose -f local.yml run django pylint ghostwriter/modules/reportwriter/richtext/docx.py
docker compose -f local.yml run django pylint ghostwriter/modules/reportwriter/base/docx.py
docker compose -f local.yml run django pylint ghostwriter/reporting/tests/test_footnotes.py

# Ran TypeScript type checking
cd javascript && npm run check

# Verified no TypeScript errors in footnote extension or UI components

Manual UI Testing:

  1. Started development environment: ./ghostwriter-cli-linux up --dev
  2. Created new client "SpecterOps" via web interface
  3. Created new project "SpecterOps Pentest" under client
  4. Created finding with rich text description containing 3 footnotes:
    • Clicked "Misc" menu (☰ icon) in rich text toolbar
    • Selected "Insert Footnote"
    • Entered footnote text in modal dialog
    • Clicked "Insert" button
    • Verified superscript number appeared inline (¹, ², ³)
    • Repeated for second and third footnotes
  5. Generated DOCX report via "Generate Report" button
  6. Downloaded and opened report in Microsoft Word 2021
  7. Verified results:
    • No "unreadable content" errors when opening file
    • Footnote references appear as superscript numbers in text (¹, ², ³)
    • Footnotes appear at bottom of page with correct numbering
    • Footnote text is properly formatted
    • Minimal spacing between separator line and footnote content

Cross-Context Testing:
Verified footnotes work correctly in:

  • Regular paragraphs
  • Headings (H1-H6)
  • List items (ordered and unordered)
  • Table cells
  • Mixed formatting (bold, italic within footnotes)

Editor Testing:

  • Inserted multiple footnotes and verified dynamic renumbering
  • Deleted middle footnote and verified remaining footnotes renumber correctly
  • Added footnote, typed more text, added another footnote
  • Verified tooltip shows footnote content on hover over superscript number
  • Tested undo/redo functionality with footnotes

Template Compatibility:

  • Tested with default Ghostwriter template
  • Tested with custom uploaded template containing existing separator footnotes
  • Verified separator cleanup removes extra paragraphs without breaking template

PowerPoint Export:

  • Generated PPTX report with footnotes
  • Verified footnotes are silently ignored (no errors, no remnant text)
  • Confirmed slide content renders normally without footnote references

Release Notes

Added footnote support in rich text editor with automatic superscript numbering and proper Word footnote export in DOCX reports

@marcpfuller marcpfuller self-assigned this Dec 5, 2025
@marcpfuller marcpfuller marked this pull request as draft December 5, 2025 03:07
ghostwriter/modules/reportwriter/richtext/pptx.py Outdated Show resolved Hide resolved
ghostwriter/modules/reportwriter/richtext/docx.py Outdated Show resolved Hide resolved
@marcpfuller marcpfuller changed the base branch from release/v6.1.0 to master December 8, 2025 21:22
@marcpfuller marcpfuller force-pushed the feat/footnotes branch 2 times, most recently from d6163a1 to b2932f8 Compare December 8, 2025 21:46
@codecov
Copy link

codecov bot commented Dec 8, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.42%. Comparing base (9d4587d) to head (295b112).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #783      +/-   ##
==========================================
+ Coverage   91.34%   91.42%   +0.07%     
==========================================
  Files         367      368       +1     
  Lines       20728    20924     +196     
==========================================
+ Hits        18935    19130     +195     
- Misses       1793     1794       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@marcpfuller marcpfuller marked this pull request as ready for review December 9, 2025 01:20
@marcpfuller marcpfuller force-pushed the feat/footnotes branch 2 times, most recently from 7775ee4 to 410cee6 Compare December 9, 2025 23:42
Copy link
Contributor

@ColonelThirtyTwo ColonelThirtyTwo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also add some styling for span.footnotes in ghostwriter/static/css/styles.css so that they will appear differently from normal text in the rich text previews.

ghostwriter/modules/reportwriter/richtext/docx.py Outdated Show resolved Hide resolved
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds comprehensive footnote support to Ghostwriter's rich text editor and report generation system, addressing issues #782 and #611. The implementation enables users to insert footnotes in the TipTap editor with automatic superscript numbering, which are then properly converted to native Word footnotes in DOCX exports and gracefully ignored in PowerPoint exports.

Key Changes:

  • Created TipTap Footnote extension as an inline atom node with dynamic auto-numbering and modal-based input UI
  • Implemented HTML-to-DOCX conversion for footnotes with sequential ID tracking and proper Word footnote references
  • Integrated forked python-docx library with native footnote API support via GitHub dependency
  • Added post-processing cleanup to remove extra paragraphs from separator footnotes
  • Implemented silent footnote ignoring for PPTX exports (PowerPoint doesn't support footnotes natively)

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
requirements/base.txt Changed python-docx from PyPI package to GhostManager fork on GitHub with footnote support
javascript/src/tiptap_gw/footnote.tsx New TipTap extension defining footnote node with auto-numbering view component and insert/update commands
javascript/src/tiptap_gw/index.ts Registered Footnote extension in the TipTap extensions array
javascript/src/frontend/collab_forms/rich_text_editor/footnote.tsx New React component providing modal dialog UI for inserting footnotes with text input
javascript/src/frontend/collab_forms/rich_text_editor/index.tsx Added FootnoteButton to the editor's "Misc" menu toolbar
ghostwriter/static/css/styles.css Added CSS styling for footnote markers in rich text previews (superscript, blue color, hover effects)
ghostwriter/modules/reportwriter/richtext/docx.py Implemented make_footnote() method to convert HTML footnote spans to Word footnotes with proper ID tracking and reference elements
ghostwriter/modules/reportwriter/richtext/pptx.py Added tag_footnote() stub method to silently ignore footnotes in PowerPoint exports
ghostwriter/modules/reportwriter/base/docx.py Implemented post-processing cleanup method to remove extra empty paragraphs from separator footnotes
ghostwriter/reporting/tests/test_footnotes.py New comprehensive test suite with 8 tests covering footnote creation, IDs, HTML conversion, and table footnotes
ghostwriter/reporting/tests/test_rich_text_docx.py Extended with FootnoteToDocxTests class containing 4 tests for HTML-to-DOCX footnote conversion
ghostwriter/reporting/tests/test_rich_text_pptx.py Added 2 tests verifying footnotes are silently ignored in PPTX conversion
compose/production/django/Dockerfile Added git package installation to support GitHub-based python-docx dependency
compose/local/django/Dockerfile Added git package installation to support GitHub-based python-docx dependency

requirements/base.txt Outdated Show resolved Hide resolved
ghostwriter/modules/reportwriter/base/docx.py Show resolved Hide resolved
ghostwriter/modules/reportwriter/base/docx.py Show resolved Hide resolved
…port

Signed-off-by: marc fuller <gogita99@gmail.com>
@chrismaddalena
Copy link
Collaborator

chrismaddalena commented Dec 22, 2025

This is looking very promising, @marcpfuller! My testing was smooth. I added footnotes and they appeared in the Word doc. I noted a few things that need some refinement in the editor and in the final output.

I liked how the footnote objects appear as footnotes with the superscript numbers. I also appreciated how they incremented. I tested adding two footnotes and then adding a third before the other two. This resulted in the third footnote becoming 1 while the first two remained 1 and 2.

CleanShot 2025-12-22 at 15 20 19

I revisited the editor later and noticed the numbering was correct. This is a minor thing, but worth seeing if we can polish it to keep numbering consistent.

The other thing was the footnote objects. They appear as clickable (the mouse changes to cursor), but clicking didn't do anything. Is there any way to see or edit the footnote content?

The last thing I noticed was in the Word output. Functionally, everything looked good. The footnotes were present and moved appropriately if I bumped the footnote to the next page with a page break. Styling was a little off, though. Here is a screenshot of the footnotes from this PR and a footnote I added in Word.

Word uses the Footnote Reference style for the number and the Footnote Test style for the text. If we swap from Normal to those two styles, we should get matching footnotes to native Word footnotes.

CleanShot 2025-12-22 at 15 28 55

- Use useEditorState hook to reactively update footnote numbers
- Make footnote markers non-clickable with tooltip
- Apply Word's Footnote Text and Footnote Reference styles to DOCX output

Signed-off-by: marc fuller <gogita99@gmail.com>
Signed-off-by: marc fuller <gogita99@gmail.com>
Signed-off-by: marc fuller <gogita99@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.