Git Product home page Git Product logo

wagtail-footnotes's Introduction

Wagtail Footnotes

PyPI PyPI downloads Build Status

Add footnotes functionality to your Wagtail project.

⚡ Quick start

Add wagtail_footnotes to INSTALLED_APPS:

# settings.py

INSTALLED_APPS = [
    # ...
    "wagtail_footnotes",
    # ...
]

Add the footnotes urls.py to your project's urls.py:

# urls.py
# ...
from wagtail_footnotes import urls as footnotes_urls


urlpatterns = [
    # ...
    path("footnotes/", include(footnotes_urls)),
    # ...
]

Note: The URL has to be defined as above as it is currently hardcoded in the JavaScript.

Update your page models to show the footnotes panel:

from wagtail.models import Page
from wagtail.admin.panels import InlinePanel


class InformationPage(Page):
    # ...
    content_panels = [
        # ...
        InlinePanel("footnotes", label="Footnotes"),
    ]

Make and run migrations:

python manage.py makemigrations
python manage.py migrate

Showing footnotes in page templates

Update your page templates to include {% include "wagtail_footnotes/includes/footnotes.html" %}. You can copy from this template and instead include your own customized version.

Using footnotes in RichTextField

Update any RichTextFields that you want to add footnotes feature. Add "footnotes" to the features argument for each RichTextField that you want to have this functionality. For instance:

class InformationPage(Page):
    body = RichTextField(
        features=[
            "h1",
            "h2",
            "h3",
            "h4",
            "footnotes",  # Make sure this line is part of the features
        ],
    )

See Wagtail's documentation for a list of features that you may want to configure since we are overwriting the defaults.

Using footnotes in StreamFields

In order to have footnotes available in a RichTextBlock, you will need to change RichTextBlocks to wagtail_footnotes.blocks.RichTextBlockWithFootnotes. For instance:

from wagtail_footnotes.blocks import RichTextBlockWithFootnotes
# ...

class MyPage(Page):
    body = StreamField(
        [
            # ...
            ("paragraph", RichTextBlockWithFootnotes()),  # Using RichTextBlockWithFootnotes
            # ...
        ],
    )

Adding footnotes as a global default

You might want to simply have all RichText editors display footnotes. But remember that you will need the footnotes InlinePanel added on all your Page models for the footnotes functionality to be enabled.

# settings.py
# ...
WAGTAILADMIN_RICH_TEXT_EDITORS = {
    "default": {
        "WIDGET": "wagtail.admin.rich_text.DraftailRichTextArea",
        "OPTIONS": {"features": ["bold", "italic", "h3", "h4", "ol", "ul", "link", "footnotes"]},
    }
}

⚙️ Settings

  • WAGTAIL_FOOTNOTES_TEXT_FEATURES
    • Default: ["bold", "italic", "link"]
    • Use this to update a list of Rich Text features allowed in the footnote text.

🌍 Internationalisation

Wagtail Footnotes can be translated. Note that in a multi-lingual setup, the URL setup for footnotes needs to be in a i18n_patterns() call with prefix_default_language=False:

# urls.py

urlpatterns += i18n_patterns(
    path("footnotes/", include(footnotes_urls)),
    # ...
    path("", include(wagtail_urls)),
    prefix_default_language=False,
)

or outside i18n_patterns():

# urls.py

urlpattherns += [
    path("footnotes/", include(footnotes_urls)),
]
urlpatterns += i18n_patterns(
    # ...
    path("", include(wagtail_urls)),
)

💡 Common issues

  • I click on the Fn button in the editor and it stops working
    • This is likely because the URL in the JS does not match the URL of the footnotes view. Check the URL in wagtail_footnotes/static/footnotes/js/footnotes.js matches the URL you set.
  • NoneType error when rendering page.
    • Make sure you are rendering the field in the template using {% include_block page.field_name %}

Contributing

All contributions are welcome!

Install

To make changes to this project, first clone this repository:

git clone [email protected]:torchbox/wagtail-footnotes.git
cd wagtail-footnotes

With your preferred virtualenv activated, install testing dependencies:

python -m pip install -e '.[testing]' -U

pre-commit

Note that this project uses pre-commit. To set up locally:

# set up your virtual environment of choice
$ python -m pip install pre-commit
# initialize pre-commit
$ pre-commit install
# Optional, run all checks once for this, then the checks will run only on the changed files
$ pre-commit run --all-files

How to run tests

To run all tests in all environments:

tox

To run tests for a specific environment:

tox -e python3.12-django5.0-wagtail6.0

To run a single test method in a specific environment:

tox -e python3.12-django5.0-wagtail6.0 -- tests.test.test_blocks.TestBlocks.test_block_with_features

wagtail-footnotes's People

Contributors

alxbridge avatar amulree avatar benjaoming avatar brianxu20 avatar jsma avatar katdom13 avatar kevinhowbrook avatar nickmoreton avatar sunnyr avatar tomdyson avatar zerolab avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wagtail-footnotes's Issues

Adding footnotes to existing RichTextField features

The README implies to add features=["footnotes"] but doesn't give guidance on how to simply extend the list of already existing features.

Do I need to register a hook? Or do I write out the entire features list like:

RichTextField(features=["h1", "h2", "h3", "h4", "h5", "h6", "bold", "italic", "ol", "ul", "hr", "link", "document-link", "image", "embed", "footnotes"])

Unpublishing page causes validation error with footnotes

If I publish a page with footnotes on it then unpublished it I get validation errors on the footnote when trying to save any changes to that draft page
Screenshot 2023-10-31 at 10 54 59 AM
The occurs even if that is the only footnote on the page, or if new foot notes are added

RichTextBlockWithFootnotes no longer respects `WAGTAILADMIN_RICH_TEXT_EDITORS`

Simply swapping all RichTextBlocks with RichTextBlockWithFootnotes will remove the existing functionality where RichTextBlocks get their features from WAGTAILADMIN_RICH_TEXT_EDITORS["default"]["OPTIONS"]["features"].

Task:
Update the RichTextBlockWithFootnotes.__init__() to respect the features set by RichTextBlock then append footnotes to that list.

502 error if a footnote call points to a missing footnote

Hello,

I get 502 error if a footnote call points to a missing footnote. It's was kind of hard to debug. Would there be a way to raise a validation issue in the admin in case a footnote call refers to a missing footnote?

Thanks

AttributeError: module 'wagtail_footnotes' has no attribute 'blocks'

Hi! I'm not sure if I'm importing it correctly.

from streams import blocks
from wagtail.snippets.blocks import SnippetChooserBlock
import wagtail_footnotes
from wagtail import blocks as wagtail_blocks

class BlogPage(Page):
    body = RichTextField(blank=True, features=["bold", "italic", "ol", "ul", "hr", "link", "footnotes", "document-link"])
    extras = StreamField([
         ("richtext", wagtail_footnotes.blocks.RichTextBlockWithFootnotes(
                template="streams/simple_richtext_block.html",
                features=["bold", "italic", "ol", "ul", "hr", "link", "footnotes", "document-link"]
          )),
    ])

If I use RichTextBlock instead of RichTextBlockWithFootnotes I don't get this attribute error but then the footnotes don't rendering correctly.

Footnotes are not replaced when RichTextWithFootnotesBlock is a StructBlock sub-block

When you include a RichTextWithFootnotesBlock in a StreamBlock definition, things work great! This is because StreamBlock.to_python returns a StreamValue, which has a render_as_block() method that feeds back into StreamBlock.render_basic() to do the actual rendering... which calls the render() method of each sub-block, ultimately leading to RichTextWithFootnotesBlock.replace_footnote_tags() doing its thing.

When you include a RichTextWithFootnotesBlock as a sub-block of a StructBlock, things work out a little differently. The tendency is to do the following in StructBlock templates:

{% load wagtailcore_tags %}
{{ value.caption|richtext }}

This definitely won't trigger RichTextWithFootnotesBlock.replace_footnote_tags(), because we're basically just taking the value from the block and rendering it directly.

I thought that changing all instances of the above would resolve the problem:

{% load wagtailcore_tags %}
{% include_block value.caption %}

However, that didn't do the job because {% include_block %} only does anything special with the value if it has a render_as_block() method (like StreamValue does). If not, the value itself is used as the output value, which isn't really what we want.

What we want is for RichTextWithFootnotesBlock.render() to always be used in rendering, and in order to do that, RichTextWithFootnotesBlock needs to return something other than an instance of RichText... we need something with a render_as_block() method (like StreamValue does), so that we can always bring things back to RichTextWithFootnotesBlock.render(). Something like this worked in a project I am currently working on:

class RichTextBlockValue:
    """
    Value type returned by CustomRichTextBlockWithFootnotes.to_python().

    When rendered in a StructBlock, FieldBlock values are usually rendered directly, bypassing the
    footnote replacement logic that is usually triggered by RichTextBlockWithFootnotes.render(). 
    If the block instead returns a value with a 'render_as_block()' method, we can always reliably
    hand things back to RichTextBlockWithFootnotes.render() to take care of rendering, where the
    footnote replacement logic will kick-in.
    """

    def __init__(self, block, value):
        self.block = block
        self.source = value
        self.value = RichText(value)

    def render_as_block(self, context=None):
        return self.block.render(self, context=context)

    def __html__(self):
        return self.block.render(self)

    def __str__(self):
        return self.__html__()


class CustomRichTextWithFootnotesBlock(RichTextWithFootnotesBlock):
    def to_python(self, value):
        if isinstance(value, RichTextBlockValue):
            return value
        return RichTextBlockValue(self, value)

    def render(self, value, context=None):
        if isinstance(value, RichTextBlockValue):
            value = value.value
        return super().render(value, context=context)
   

With these changes, as long as I use {% include_block %} in all StructBlock templates, the footnotes are replaced successfully, wherever they are used:

{% load wagtailcore_tags %}
{% include_block value.caption %}

Page/RichText Footnote Validation

Draftail allows copying and pasting of text from one instance to another.
If I copy text containing a footnote reference from one page to another, there won't be a footnote related to that page to display.

Current Behaviour:
The page saves fine and renders with no errors. However, there is an unlinked footnote reference in the page as there isn't a related footnote to link to.

Expected Behaviour:
On save, a validation error is raised that informs the user that there is an invalid footnote in the RichText.

Current thoughts on how to fix:
I think this is going to need a Mixin that people can add to their Page models. The mixin will define the validation logic for the footnotes.

No footnotes in "Choose a footnote" modal

Hello,

I am running wagtail version 4.0.2 and wagtail-footnotes version 0.8.1.

I am able to save footnotes:
Screen Shot 2022-09-28 at 3 01 04 PM

But when it comes to choosing a footnote from the "Choose a footnote" modal that appears after clicking the "footnotes" icon in the richtext bar, there are no footnotes to choose from:
Screen Shot 2022-09-28 at 3 05 58 PM

How might I go about solving this problem?

Frontend template not working inside StructBlock

Hello,

I cannot get the frontend template working for a RichTextBlockWithFootnotes as part of a Structblock. It is working like a charm on its own or as part of a ListBlock, though. Imagine the following StreamField:

body = StreamField([
    ('quote', StructBlock([
        ('author', CharBlock()),
        ('text', RichTextBlockWithFootnotes(features=['footnotes', ],)),
    ])),
])

The following template would not work for the StructBlock. It does work for ListBlock or stand-alone though.

{% for block in page.body %}
    {% include_block block %}
{% endfor %}
{% include "wagtail_footnotes/includes/footnotes.html" %}

Also this more specific try to call include_block on the RichText sub-block does not work. It returns the unfiltered RichText, e.g. "Lorem ipsum dolor sit amet[d93fa8]" and would not contribute to page.footnotes_list.

{% for block in page.body %}
    {% if block.block_type == 'quote' %}
        {% include_block block.value.text %}
    {% endif %}        
{% endfor %}
{% include "wagtail_footnotes/includes/footnotes.html" %}

Best case scenario: documentation needs a little update to cover this use case. A RichTextBlock as part of StructBlock is pretty common, I guess. Not sure how to debug this. The render function(s) of the RichTextBlockWithFootnotes do not get called, do they?

Warmest,
André

"The editor just crashed" on fresh install

wagtail==2.11.3
wagtail-footnotes==0.4.1
Django==3.1.4

When I click on the "Fn" button, I get:

Error: Minified React error #130; visit https://reactjs.org/docs/error-decoder.html?invariant=130&args[]=undefined&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

Error: Minified React error #130; visit https://reactjs.org/docs/error-decoder.html?invariant=130&args[]=undefined&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
    at Pl (http://localhost:8000/static/wagtailadmin/js/vendor.js?v=c42ede84:22:109822)
    at c (http://localhost:8000/static/wagtailadmin/js/vendor.js?v=c42ede84:22:52404)
    at h (http://localhost:8000/static/wagtailadmin/js/vendor.js?v=c42ede84:22:53763)
    at v (http://localhost:8000/static/wagtailadmin/js/vendor.js?v=c42ede84:22:54338)
    at http://localhost:8000/static/wagtailadmin/js/vendor.js?v=c42ede84:22:56318
    at Mu (http://localhost:8000/static/wagtailadmin/js/vendor.js?v=c42ede84:22:65804)
    at ya (http://localhost:8000/static/wagtailadmin/js/vendor.js?v=c42ede84:22:106175)
    at cl (http://localhost:8000/static/wagtailadmin/js/vendor.js?v=c42ede84:22:96716)
    at al (http://localhost:8000/static/wagtailadmin/js/vendor.js?v=c42ede84:22:96641)
    at Za (http://localhost:8000/static/wagtailadmin/js/vendor.js?v=c42ede84:22:93671)

    in div
    in e
    in e

Improve docs

Trying to install in a fresh project in Wagtail 5.1

from wagtail_footnotes.blocks import RichTextBlockWithFootnotes
class InformationPage(BasePage):
    body = RichTextBlockWithFootnotes(blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('body'),
    ]

but get
class InformationPage(BasePage):
^^^^^^^^
NameError: name 'BasePage' is not defined

Rendrering the footnotes references in the richtextblock

I have a problem of passing the footnotes to the templates of my richtextblock. r The footnotes are rendered correctly in the page's 'footer'/footnotes section, while the richtextblock displays the numbers that are assigned on the Admin. I practice on a simple page model which has StreamField or CustomStreamFieldBlock in it.

Since I don't pass the footnotes reference the richtextblock I guess this is the problem. What I did tried ended up in recursive

As I could not find much info about it, I wanted to ask if you could advise me on the issue or refer me to a sample template.

In case it helps, the below is my page's footnotes render.
Footnotes

[1]

first note
[↩](https://localhost-yrfriedman-5.paiza-user-lite.cloud:8000/en/fn2/#footnote-source-1)
[2]

second note
[↩](https://localhost-yrfriedman-5.paiza-user-lite.cloud:8000/en/fn2/#footnote-source-2)

Issues with multiple references to the same footnote

The current implementation assigns an ID to <sup> elements based on the footnote's index.

return f'<a href="#footnote-{index}" id="footnote-source-{index}"><sup>[{index}]</sup></a>'

This leads to duplicate id values so that browsers (tested in Chrome and Firefox) will only return to the first occurrence in the copy. This can cause issues if the first instance of "[1]" is not currently visible (e.g. in a collapsed accordion), the browser won't do anything when the "Back to content" link is clicked.

I've started working on an implementation to fix this situation which borrows ideas from Wikipedia. They handle this by always assigning unique IDs and then have individual links back to each reference in the copy, using letters of the alphabet. If there is a single reference, then the caret is clickable, otherwise the caret is not clickable, only the letters are:

Screen Shot 2022-02-24 at 1 10 20 PM

I have a basic WIP in my fork so that the block rendering will handle assigning unique IDs to the links and tracks these on the page object so when footnotes are rendered, they can be accessed.

This WIP does not currently change anything on the front-end. I'm still experimenting but so far can solve our issue by using a custom template and template tag to process multiple back links. For simplicity, I'm just using integers as the link text but it shouldn't be too hard to convert these to letters of the alphabet a la Wikipedia:

{% load core_tags %}
{% load wagtailcore_tags %}

{% if page.footnotes_list %}
  <ol>
    {% for footnote in page.footnotes_list %}
      <li id="footnote-{{ forloop.counter }}">
        {{ footnote.text|richtext }}
        {% with reference_ids=page|get_references:footnote.uuid %}
          {% with num=reference_ids|length %}
            {% if num == 1 %}
              <a href="#{{ reference_ids.0 }}" aria-label="Back to content">↩</a>
            {% else %}
              <span aria-label="Back to content">↩</span>
              {% for reference_id in reference_ids %}
                <a href="#{{ reference_id }}">
                  <sup>
                    <i><b>{{ forloop.counter }}</b></i>
                  </sup>
                </a>
              {% endfor %}
            {% endif %}
          {% endwith %}
        {% endwith %}
      </li>
    {% endfor %}
  </ol>
{% endif %}

And a simple template tag since we can't do variable lookups in dictionaries:

@register.filter
def get_references(value, footnote_uuid):
    return value.footnotes_references[footnote_uuid]

Let me know if something like this could be considered for inclusion. Happy to make a formal and comprehensive PR, but looking for feedback first. Thanks!

render footnotes generated in richtextblock

How to name/ refer to footnotes in a block
In models.py I can generate footnotes as expected. Yet, if I use the same block it doesn't render the note. That is, I create and populates the notes in Admin, in the template they appear with the Admin with system's ref, and the footnotes section is empty - {% include "wagtail_footnotes/includes/footnotes.html" %}.

class Notes1Page(Page):
    body = StreamField([
        ('fnotes', RichTextBlockWithFootnotes(label=("Feature type"), blank=True, default='abc',)),
    ])    ...

    content_panels = Page.content_panels + [
        FieldPanel('body', classname='full'),        ...
    ]

this above works, but the below doesn't:

class ColumnBlock(blocks.StreamBlock):

    paragraph = blocks.wagtail_footnotes.blocks.RichTextBlockWithFootnotes(editor='full', label='Paragraphe') 
    # error: 'wagtail.core.blocks' has no attribute 'wagtail_footnotes'
    paragraph1 = blocks.RichTextBlock(editor='full', label='Paragraphe')    #full' is defined in WAGTAILADMIN_RICH_TEXT_EDITORS/base.py, 
    paragraph2 = blocks.RichTextBlock(features=["bold", "italic", "link", "footnotes"], required=False, label='Paragraphe2') 
    
    paragraph3 = RichTextBlockWithFootnotes(features=["bold", "italic", "footnotes"], required=False, label='Paragraphe Notes1')
#Paragraph1-3 generate footnotes in the Admin, but don't render them in the template.
    ...

Add Contributing docs to README

Something similar to https://github.com/torchbox/wagtailmedia#contributing would be very helpful.

I'm not very familiar with tox (beyond blindly following Contributing docs), but I was unable to run specific tests e.g.:

 tox -e python3.11-django4.2-wagtail5.0 tests.test.test_blocks.TestBlocks
usage: tox [-h] [--colored {yes,no}] [-v | -q] [--exit-and-dump-after seconds] [-c file] [--workdir dir] [--root dir] [--runner {virtualenv}] [--version] [--no-provision [REQ_JSON]] [--no-recreate-provision] [-r] [-x OVERRIDE]
           {run,r,run-parallel,p,depends,de,list,l,devenv,d,config,c,quickstart,q,exec,e,legacy,le} ...
tox: error: unrecognized arguments: tests.test.test_blocks.TestBlocks
hint: if you tried to pass arguments to a command use -- to separate them from tox ones

Thanks to @nickmoreton for getting the existing tests in place!

Template Filter for regular RichTextField?

I love this package!
Is there a way to add these footnotes to a regular RichTextField (not RichTextBlock) as well?
Adding the "footnotes" feature to a RichTextField works on the backend side.
In frontend template I cannot get it to work – neither by the regular richttext filter, nor by passing the value on with include_block. I reckon that a custom template filter could work. This could just append the list of footnotes to the richtext.

Footnotes not numbering correctly & footnotesection not showing

Hi there,

The most likely cause is that I haven't configured wagtail_footnotes properly, but I can't seem to find what I did wrong, so that's why I'm writing this post. If there is another place or person I can go to for support, please let know.

I have followed the instruction for implementing wagtail_footnotes as well as possible. I added wagtail_footnotes to the installed apps section in my base.py, added the footnotes to urls.py, included a footnote section to the content_panels of the page I wanted to use footnotes on and changed RichTextBlock to RichTextBlockWithFootnotes. Lastly I added the footnotesection to the template.

And in the CMS section it actually works. Sort of. I have the option to add a footnote, both as a section and in the Richtext editor. I can add a footnote in the "Add footnote" section, but from there it goes off the rails for me. I know it shows a unique number on the backend side of things, but when I view the page it shows the unique id-number too (without a link) and not a regular footnote number.

Furthermore, the html section is not showing either. I'm sure it's me, but I just can't see what I'm doing wrong. Help is much appreciated.

Many thanks in advance,

All the best,

MPE

but I am running into two issues (probably related)

Footnotes are not rendered in preview mode

In preview mode, before a page or its footnotes have been saved to the database, no footnotes are rendered on the page. The issue appears to be that the Footnote.uuid returns a UUID type before it is saved to the db but returns a str when retrieved from the db:

Screen Shot 2022-02-22 at 11 46 59 AM

The footnote_id that is captured from the rich text HTML is of course a string:

Screen Shot 2022-02-22 at 11 47 17 AM

CustomUUIDField will always return a string from the db:

def from_db_value(self, value, expression, connection, context=None):
return str(value)

The fix then requires that the keys in self.footnotes also be guaranteed to be strings.

I'll submit a PR shortly. Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.