Giter Site home page Giter Site logo

Comments (3)

thibaudcolas avatar thibaudcolas commented on May 27, 2024

Hey @su27, glad to know this is useful to you! This is getting fairly close to a stable 1.0, although there are parts of the API that still need work.

For decorators, I think this would be what you are looking for: https://github.com/springload/draftjs_exporter/blob/master/draftjs_exporter/entities.py. I'm not 100% familiar with Draft.js terminology, I think this should be called decorators.py.

This part of the API is trying to look like React's. Here is a Button decorator:

class Button():
    def render(self, props):
        data = props.get('data', {})
        href = data.get('href', '#')
        icon = data.get('icon', None)
        text = data.get('text', '')

        return DOM.create_element('a', {'class': 'icon-text' if icon else None, 'href': href},
            DOM.create_element(Icon, {'name': icon}) if icon else None,
            DOM.create_element('span', {'class': 'icon-text__text'}, text) if icon else text,
        )

Note that it uses another Icon component/decorator, which is defined in that same file. I wouldn't rely on those built-in decorators, they are mostly here to show how this API is supposed to work. Compared to React's API / JSX, this:

  • Does not convert camelCase propNames to kebab-case HTML attributes. You're supposed to provide the HTML attributes. Not sure what I think of this.
  • Supports conditional rendering (DOM.create_element(...) if thing else None).
  • Does not support children as an array (I think this will come, just haven't had the use for it yet).

To build your own decorators, there are two options:

  • DOM.create_element is meant to be the go-to API to create HTML elements for decorators.
  • DOM.parse_html is meant to facilitate the integration with other tools like existing templates or some other HTML generation. You should be able to give it an HTML string and it should "just work".

Then, when configuring the exporter this would look like:

config = {
    'entity_decorators': {
        'LINK': WagtailLink(),
        'MODEL': WagtailModel(),
        'TOKEN': Null(),
        'DOCUMENT': Document(),
        'WAGTAIL_IMAGE': WagtailImage(),
        'WAGTAIL_EMBED': WagtailEmbed(),
        'ANCHOR': Anchor(),
    },
    'block_map': BLOCK_MAP,
}

And here are the definitions for those various decorators (part of a codebase using this library, that I plan to make public, but haven't had the chance to yet):

class WagtailLink():
    def render(self, props):
        data = props.get('data', {})

        if 'id' in data:
            try:
                page = Page.objects.get(id=data['id'])
                href = page.url
            except Page.DoesNotExist:
                # TODO If the page does not exist anymore, we want a 404.
                href = data.get('url', '#')
        else:
            href = data.get('url', '#')

        return DOM.create_element('a', {
            'href': href,
            'title': data.get('title'),
        })


class WagtailModel():
    def render(self, props):
        data = props.get('data', {})
        href = '#'
        className = ''

        # TODO I'm sure there is a better way to do this.
        # See http://stackoverflow.com/questions/4881607/django-get-model-from-string
        # See http://stackoverflow.com/questions/1221240/django-content-type-how-do-i-get-an-object
        if data['contentType'] == 'location.Location':
            try:
                location = Location.objects.get(pk=data['id'])
                href = reverse('locator_detail', args=[location.type_slug, location.id, location.slug])
                className = 'link--location'
            except Location.DoesNotExist:
                raise()
                # TODO If the page does not exist anymore, we want a 404.

        return DOM.create_element('a', {
            'className': className,
            'href': href,
        })


class WagtailImage():
    """
    Inspired by:
    - https://github.com/torchbox/wagtail/blob/master/wagtail/wagtailimages/rich_text.py
    - https://github.com/torchbox/wagtail/blob/master/wagtail/wagtailimages/shortcuts.py
    - https://github.com/torchbox/wagtail/blob/master/wagtail/wagtailimages/formats.py
    """
    def render(self, props):
        image_model = get_image_model()
        alignment = props['data'].get('alignment', 'left')
        alt_text = props['data'].get('altText', '')

        try:
            image = image_model.objects.get(id=props['data']['id'])
        except image_model.DoesNotExist:
            return DOM.create_element('img', {'alt': alt_text})

        image_format = get_image_format(alignment)
        rendition = get_rendition_or_not_found(image, image_format.filter_spec)

        return DOM.create_element('img', dict(rendition.attrs_dict, **{
            'class': image_format.classnames,
            'src': rendition.url,
            'alt': alt_text,
        }))


class WagtailEmbed():
    """
    Inspired by: https://github.com/torchbox/wagtail/blob/master/wagtail/wagtailembeds/rich_text.py
    """
    def render(self, props):
        return DOM.parse_html(embed_to_frontend_html(props['data']['url']))


class Anchor():
    def render(self, props):
        return DOM.create_element('a', {
            'href': '#%s' % props['data'].get('slug', '')
        })


class Icon():
    def render(self, props):
        href = 'icon-%s' % props['name']
        return DOM.create_element('svg', {'class': 'icon'},
            DOM.create_element('use', {'xlink:href': href}),
        )


class Document():
    """
    Based on deluxetext/filters.py DocumentLinkParser.
    """
    def render(self, props):
        document_model = get_document_model()
        doc = document_model.objects.get(id=props['data']['id'])
        extended_file = get_extended_file_information(file=doc.file)
        extension = extended_file.get('extension')

        # TODO Can we reuse more stuff from https://github.com/torchbox/wagtail/tree/master/wagtail/wagtaildocs?
        metadata_text = '({size} {extension})'.format(
            size=extended_file.get('size'),
            extension=extension,
        )

        return DOM.create_element('a', {'href': doc.url},
            DOM.create_element('span', {'class': 'file-info icon-text'},
                DOM.create_element(Icon, {'name': extension.lower()}),
                DOM.create_element('span', {'class': 'icon-text__text'}, metadata_text),
            ),
        )

One more note: I'm not sure why we went with decorators as classes with a render method instead of just the render function being the decorator. Perhaps there is a use case that I forgot about. Perhaps we would support both, like React's stateless function components, and class components. Expect API churn.

What do you think? Is this what you're after?

from draftjs_exporter.

su27 avatar su27 commented on May 27, 2024

Hi @thibaudcolas,

Thanks very much for your detailed response!

In fact, I've read your code before, and understand the way you mentioned above, actually I've defined a lot of custom entities for my own need, for example, Image with caption on bottom of the photo, Video, Separator as a dividing line, etc. I noticed you called this mechanism decorator, which is totally fine but different from facebook's terminology. I think they just call it entity.

The "decorator" I mentioned in the title means another mechanism which is defined here, it's just simple regular expression search and replace in the blocks, according to some user defined strategies, for example: replace @someone to blue spans, or replace urls in the text to actual links. As to implement, I suppose you could simply treat them as another kind of commands.

And yes! I did wonder why not just use render function instead of the instance method, but I think maybe it has own benefit, we can define the class with not only render strategy, but much more stuff, like what attributes are required or optional in the data dict, etc. So I think it's just fine.

Another wondering: when I write those custom entities, I find it quite trivial to define complex html structure with the create_element method. Maybe we can think about adopting template rendering? Even JSX for isomorphically rendering? OK that's a little too far.

from draftjs_exporter.

thibaudcolas avatar thibaudcolas commented on May 27, 2024

Doh, I actually never used those things but they do look like a very important part of the Draft.js API to cover. Thank you for the details!

from draftjs_exporter.

Related Issues (20)

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.