panda-editor 0.3.0 → 0.4.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +24 -0
- data/docs/FOOTNOTES.md +51 -2
- data/lib/panda/editor/footnote_registry.rb +44 -2
- data/lib/panda/editor/renderer.rb +2 -1
- data/lib/panda/editor/version.rb +1 -1
- data/panda-editor.gemspec +1 -0
- metadata +15 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 148f84007d3e1e31886f6be8297edfd19f5ddc13e29d1ff565d66a7c27261f85
         | 
| 4 | 
            +
              data.tar.gz: 0e2794c6183a16940bdd62c62ff0f63e6b55c0e529adb025e596f93041de565f
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 06cef5d5517ef1211bcfc1b0b5c0c63dba5c8bfd9d189b1074e1174d261d14de5d15c522734cdc69fca71c3ef3cfbbed88fe741de6e739a4c9b49a4a7960219d
         | 
| 7 | 
            +
              data.tar.gz: c169a830348145a9b9f24a62d15333a02fb9b27cc0bdc2356a383ade82010af16369e2279034bae78607ff751844c52a417d872a0146023445bcf7c4d7dc29b8
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. | |
| 5 5 | 
             
            The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
         | 
| 6 6 | 
             
            and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
         | 
| 7 7 |  | 
| 8 | 
            +
            ## [0.4.0] - 2025-10-30
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            ### Added
         | 
| 11 | 
            +
            - Markdown support for rich text formatting in footnotes
         | 
| 12 | 
            +
              - **Bold**, *italic*, `code`, ~~strikethrough~~, and [link](url) support
         | 
| 13 | 
            +
              - Automatic URL linking in markdown content
         | 
| 14 | 
            +
              - Security-hardened Redcarpet configuration (no images, safe links only)
         | 
| 15 | 
            +
              - Works alongside existing autolink_urls option
         | 
| 16 | 
            +
              - Comprehensive test coverage for markdown features
         | 
| 17 | 
            +
              - Updated documentation with markdown examples
         | 
| 18 | 
            +
             | 
| 8 19 | 
             
            ## [0.3.0] - 2025-10-30
         | 
| 9 20 |  | 
| 10 21 | 
             
            ### Added
         | 
    
        data/README.md
    CHANGED
    
    | @@ -168,6 +168,27 @@ The sources section includes data attributes for integration with JavaScript fra | |
| 168 168 |  | 
| 169 169 | 
             
            See [docs/FOOTNOTES.md](docs/FOOTNOTES.md) for detailed implementation examples.
         | 
| 170 170 |  | 
| 171 | 
            +
            ### Markdown Support
         | 
| 172 | 
            +
             | 
| 173 | 
            +
            Enable markdown formatting in footnote content for rich text citations:
         | 
| 174 | 
            +
             | 
| 175 | 
            +
            ```ruby
         | 
| 176 | 
            +
            renderer = Panda::Editor::Renderer.new(@content, markdown: true)
         | 
| 177 | 
            +
            html = renderer.render
         | 
| 178 | 
            +
            ```
         | 
| 179 | 
            +
             | 
| 180 | 
            +
            Supports **bold**, *italic*, `code`, ~~strikethrough~~, and [links](url):
         | 
| 181 | 
            +
             | 
| 182 | 
            +
            **Input:**
         | 
| 183 | 
            +
            ```
         | 
| 184 | 
            +
            Smith, J. (2023). **Important study** on *ADHD treatment*. See https://example.com for details.
         | 
| 185 | 
            +
            ```
         | 
| 186 | 
            +
             | 
| 187 | 
            +
            **Output:**
         | 
| 188 | 
            +
            ```html
         | 
| 189 | 
            +
            Smith, J. (2023). <strong>Important study</strong> on <em>ADHD treatment</em>. See <a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a> for details.
         | 
| 190 | 
            +
            ```
         | 
| 191 | 
            +
             | 
| 171 192 | 
             
            ### Auto-linking URLs
         | 
| 172 193 |  | 
| 173 194 | 
             
            Enable automatic URL linking in footnote content:
         | 
| @@ -195,6 +216,9 @@ Features: | |
| 195 216 | 
             
            - Won't double-link URLs already in `<a>` tags
         | 
| 196 217 | 
             
            - Supports `http://`, `https://`, `ftp://`, and `www.` URLs
         | 
| 197 218 | 
             
            - Handles multiple URLs in the same footnote
         | 
| 219 | 
            +
            - Can be combined with `markdown: true` (markdown's autolink runs first, then custom autolink for any remaining URLs)
         | 
| 220 | 
            +
             | 
| 221 | 
            +
            **Note:** When using `markdown: true`, you typically don't need `autolink_urls: true` as markdown includes built-in autolinking. However, both options can work together safely.
         | 
| 198 222 |  | 
| 199 223 | 
             
            To enable globally for all content using the `Panda::Editor::Content` concern, pass the option in `generate_cached_content`.
         | 
| 200 224 |  | 
    
        data/docs/FOOTNOTES.md
    CHANGED
    
    | @@ -308,9 +308,58 @@ insert at position 11: "Hello world<sup>2</sup>" | |
| 308 308 | 
             
            insert at position 5: "Hello<sup>1</sup> world<sup>2</sup>"
         | 
| 309 309 | 
             
            ```
         | 
| 310 310 |  | 
| 311 | 
            +
            ### Markdown Support
         | 
| 312 | 
            +
             | 
| 313 | 
            +
            The footnote system supports markdown formatting, allowing you to use rich text formatting in your citations.
         | 
| 314 | 
            +
             | 
| 315 | 
            +
            **Enable markdown:**
         | 
| 316 | 
            +
             | 
| 317 | 
            +
            ```ruby
         | 
| 318 | 
            +
            renderer = Panda::Editor::Renderer.new(content, markdown: true)
         | 
| 319 | 
            +
            output = renderer.render
         | 
| 320 | 
            +
            ```
         | 
| 321 | 
            +
             | 
| 322 | 
            +
            **Supported markdown features:**
         | 
| 323 | 
            +
             | 
| 324 | 
            +
            - **Bold text** (`**bold**` or `__bold__`)
         | 
| 325 | 
            +
            - *Italic text* (`*italic*` or `_italic_`)
         | 
| 326 | 
            +
            - `Inline code` (`` `code` ``)
         | 
| 327 | 
            +
            - ~~Strikethrough~~ (`~~text~~`)
         | 
| 328 | 
            +
            - [Links](url) (`[text](url)`)
         | 
| 329 | 
            +
            - Automatic URL linking
         | 
| 330 | 
            +
             | 
| 331 | 
            +
            **Example:**
         | 
| 332 | 
            +
             | 
| 333 | 
            +
            ```ruby
         | 
| 334 | 
            +
            content = {
         | 
| 335 | 
            +
              "blocks" => [{
         | 
| 336 | 
            +
                "type" => "paragraph",
         | 
| 337 | 
            +
                "data" => {
         | 
| 338 | 
            +
                  "text" => "Research findings",
         | 
| 339 | 
            +
                  "footnotes" => [{
         | 
| 340 | 
            +
                    "id" => "fn-1",
         | 
| 341 | 
            +
                    "content" => "Smith, J. (2023). **Important study** on *ADHD treatment*. See https://example.com for details.",
         | 
| 342 | 
            +
                    "position" => 17
         | 
| 343 | 
            +
                  }]
         | 
| 344 | 
            +
                }
         | 
| 345 | 
            +
              }]
         | 
| 346 | 
            +
            }
         | 
| 347 | 
            +
             | 
| 348 | 
            +
            renderer = Panda::Editor::Renderer.new(content, markdown: true)
         | 
| 349 | 
            +
            # Output will include: Smith, J. (2023). <strong>Important study</strong> on <em>ADHD treatment</em>. See <a href="https://example.com">https://example.com</a> for details.
         | 
| 350 | 
            +
            ```
         | 
| 351 | 
            +
             | 
| 352 | 
            +
            **Important notes:**
         | 
| 353 | 
            +
             | 
| 354 | 
            +
            - Markdown includes built-in URL autolinking, so you typically don't need `autolink_urls: true` when using markdown
         | 
| 355 | 
            +
            - However, both options can be used together if needed - the custom autolink_urls will skip URLs that markdown already linked
         | 
| 356 | 
            +
            - Markdown links are rendered with `target="_blank"` and `rel="noopener noreferrer"` for security
         | 
| 357 | 
            +
            - Images are disabled in markdown footnotes for security
         | 
| 358 | 
            +
            - HTML styles are stripped from markdown output
         | 
| 359 | 
            +
             | 
| 311 360 | 
             
            ### Auto-linking URLs
         | 
| 312 361 |  | 
| 313 | 
            -
            The footnote system can automatically convert plain URLs in footnote content into clickable links.
         | 
| 362 | 
            +
            The footnote system can automatically convert plain URLs in footnote content into clickable links when markdown is not enabled.
         | 
| 314 363 |  | 
| 315 364 | 
             
            **Enable auto-linking:**
         | 
| 316 365 |  | 
| @@ -575,7 +624,7 @@ bundle exec rspec spec/lib/panda/editor/renderer_spec.rb | |
| 575 624 | 
             
            Potential improvements for future versions:
         | 
| 576 625 |  | 
| 577 626 | 
             
            - [ ] Support for footnotes in other block types (headers, quotes, etc.)
         | 
| 578 | 
            -
            - [ | 
| 627 | 
            +
            - [x] Rich text formatting within footnote content (implemented via markdown support)
         | 
| 579 628 | 
             
            - [ ] Footnote tooltips on hover
         | 
| 580 629 | 
             
            - [ ] Customizable footnote markers (*, †, ‡, etc.)
         | 
| 581 630 | 
             
            - [ ] Export footnotes to bibliography formats (BibTeX, etc.)
         | 
| @@ -1,14 +1,17 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            require "redcarpet"
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            module Panda
         | 
| 4 6 | 
             
              module Editor
         | 
| 5 7 | 
             
                class FootnoteRegistry
         | 
| 6 8 | 
             
                  attr_reader :footnotes
         | 
| 7 9 |  | 
| 8 | 
            -
                  def initialize(autolink_urls: false)
         | 
| 10 | 
            +
                  def initialize(autolink_urls: false, markdown: false)
         | 
| 9 11 | 
             
                    @footnotes = []
         | 
| 10 12 | 
             
                    @footnote_ids = {}
         | 
| 11 13 | 
             
                    @autolink_urls = autolink_urls
         | 
| 14 | 
            +
                    @markdown = markdown
         | 
| 12 15 | 
             
                  end
         | 
| 13 16 |  | 
| 14 17 | 
             
                  def add(id:, content:)
         | 
| @@ -30,7 +33,7 @@ module Panda | |
| 30 33 |  | 
| 31 34 | 
             
                    footnote_items = @footnotes.map.with_index do |footnote, index|
         | 
| 32 35 | 
             
                      number = index + 1
         | 
| 33 | 
            -
                      content =  | 
| 36 | 
            +
                      content = process_content(footnote[:content])
         | 
| 34 37 | 
             
                      <<~HTML.strip
         | 
| 35 38 | 
             
                        <li id="fn:#{number}">
         | 
| 36 39 | 
             
                          <p>
         | 
| @@ -66,6 +69,45 @@ module Panda | |
| 66 69 |  | 
| 67 70 | 
             
                  private
         | 
| 68 71 |  | 
| 72 | 
            +
                  def process_content(content)
         | 
| 73 | 
            +
                    # Apply markdown processing if enabled
         | 
| 74 | 
            +
                    content = render_markdown(content) if @markdown
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    # Apply URL autolinking if enabled
         | 
| 77 | 
            +
                    # Note: Markdown already includes autolink, but custom autolink_urls can still be used
         | 
| 78 | 
            +
                    # if needed for additional URL patterns. The autolink_urls method skips already-linked URLs.
         | 
| 79 | 
            +
                    content = autolink_urls(content) if @autolink_urls
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    content
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  def render_markdown(text)
         | 
| 85 | 
            +
                    # Configure Redcarpet with safe options for footnotes
         | 
| 86 | 
            +
                    renderer = Redcarpet::Render::HTML.new(
         | 
| 87 | 
            +
                      filter_html: false,
         | 
| 88 | 
            +
                      no_images: true,
         | 
| 89 | 
            +
                      no_styles: true,
         | 
| 90 | 
            +
                      safe_links_only: true,
         | 
| 91 | 
            +
                      link_attributes: {target: "_blank", rel: "noopener noreferrer"}
         | 
| 92 | 
            +
                    )
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                    markdown = Redcarpet::Markdown.new(
         | 
| 95 | 
            +
                      renderer,
         | 
| 96 | 
            +
                      autolink: true,
         | 
| 97 | 
            +
                      space_after_headers: true,
         | 
| 98 | 
            +
                      fenced_code_blocks: false,
         | 
| 99 | 
            +
                      no_intra_emphasis: true,
         | 
| 100 | 
            +
                      strikethrough: true,
         | 
| 101 | 
            +
                      superscript: false,
         | 
| 102 | 
            +
                      underline: false
         | 
| 103 | 
            +
                    )
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    # Render markdown and strip the wrapping <p> tags if present
         | 
| 106 | 
            +
                    # since we're already wrapping in <p> tags in the template
         | 
| 107 | 
            +
                    html = markdown.render(text).strip
         | 
| 108 | 
            +
                    html.gsub(%r{^<p>(.*)</p>$}m, '\1')
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
             | 
| 69 111 | 
             
                  def autolink_urls(text)
         | 
| 70 112 | 
             
                    # Regex to match URLs that aren't already in <a> tags
         | 
| 71 113 | 
             
                    # Matches http://, https://, and other common protocols
         | 
| @@ -14,7 +14,8 @@ module Panda | |
| 14 14 | 
             
                    @cache_store = options.delete(:cache_store) || Rails.cache
         | 
| 15 15 | 
             
                    @validate_html = options.delete(:validate_html) || false
         | 
| 16 16 | 
             
                    autolink_urls = options.delete(:autolink_urls) || false
         | 
| 17 | 
            -
                     | 
| 17 | 
            +
                    markdown = options.delete(:markdown) || false
         | 
| 18 | 
            +
                    @footnote_registry = FootnoteRegistry.new(autolink_urls: autolink_urls, markdown: markdown)
         | 
| 18 19 | 
             
                    @options[:footnote_registry] = @footnote_registry
         | 
| 19 20 | 
             
                  end
         | 
| 20 21 |  | 
    
        data/lib/panda/editor/version.rb
    CHANGED
    
    
    
        data/panda-editor.gemspec
    CHANGED
    
    | @@ -34,6 +34,7 @@ Gem::Specification.new do |spec| | |
| 34 34 | 
             
              spec.add_dependency "rails", ">= 7.1"
         | 
| 35 35 | 
             
              spec.add_dependency "sanitize", "~> 6.0"
         | 
| 36 36 | 
             
              spec.add_dependency "dry-configurable", "~> 1.0"
         | 
| 37 | 
            +
              spec.add_dependency "redcarpet", "~> 3.6"
         | 
| 37 38 |  | 
| 38 39 | 
             
              # Development dependencies
         | 
| 39 40 | 
             
              spec.add_development_dependency "rspec-rails", "~> 6.0"
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: panda-editor
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.4.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Otaina Limited
         | 
| @@ -53,6 +53,20 @@ dependencies: | |
| 53 53 | 
             
                - - "~>"
         | 
| 54 54 | 
             
                  - !ruby/object:Gem::Version
         | 
| 55 55 | 
             
                    version: '1.0'
         | 
| 56 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 57 | 
            +
              name: redcarpet
         | 
| 58 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 59 | 
            +
                requirements:
         | 
| 60 | 
            +
                - - "~>"
         | 
| 61 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 62 | 
            +
                    version: '3.6'
         | 
| 63 | 
            +
              type: :runtime
         | 
| 64 | 
            +
              prerelease: false
         | 
| 65 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 66 | 
            +
                requirements:
         | 
| 67 | 
            +
                - - "~>"
         | 
| 68 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 69 | 
            +
                    version: '3.6'
         | 
| 56 70 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 57 71 | 
             
              name: rspec-rails
         | 
| 58 72 | 
             
              requirement: !ruby/object:Gem::Requirement
         |