def render_entities(self, style_node):
        # We have a complete (start, stop) entity to render.
        if self.completed_entity is not None:
            entity_details = self.get_entity_details(self.completed_entity)
            opts = Options.for_entity(self.entity_decorators, entity_details['type'])
            props = entity_details['data'].copy()
            props['entity'] = {
                'type': entity_details['type'],
            }

            if len(self.element_stack) == 1:
                children = self.element_stack[0]
            else:
                children = DOM.create_element()

                for n in self.element_stack:
                    DOM.append_child(children, n)

            self.completed_entity = None
            self.element_stack = []

            # Is there still another entity? (adjacent) if so add the current style_node for it.
            if self.has_entity():
                self.element_stack.append(style_node)

            return DOM.create_element(opts.element, props, children)

        if self.has_entity():
            self.element_stack.append(style_node)
            return None

        return style_node
    def update_stack(self, options, depth):
        if depth >= self.stack.length():
            # If the depth is gte the stack length, we need more wrappers.
            depth_levels = range(self.stack.length(), depth + 1)

            for level in depth_levels:
                new_wrapper = Wrapper(level, options)

                # Determine where to append the new wrapper.
                if self.stack.head().last_child is None:
                    # If there is no content in the current wrapper, we need
                    # to add an intermediary node.
                    props = dict(options.props)
                    props['block'] = {
                        'type': options.type,
                        'depth': depth,
                        'data': {},
                    }
                    props['blocks'] = self.blocks

                    wrapper_parent = DOM.create_element(options.element, props)
                    DOM.append_child(self.stack.head().elt, wrapper_parent)
                else:
                    # Otherwise we can append at the end of the last child.
                    wrapper_parent = self.stack.head().last_child

                DOM.append_child(wrapper_parent, new_wrapper.elt)

                self.stack.append(new_wrapper)
        else:
            # Cut the stack to where it now stops, and add new wrapper.
            self.stack.slice(depth)
            self.stack.append(Wrapper(depth, options))
示例#3
0
    def render_block(self, block, entity_map, wrapper_state):
        if block['inlineStyleRanges'] or block['entityRanges']:
            content = DOM.create_element()
            entity_state = EntityState(self.entity_decorators, entity_map)
            style_state = StyleState(self.style_map)

            for (text, commands) in self.build_command_groups(block):
                for command in commands:
                    entity_state.apply(command)
                    style_state.apply(command)

                # Decorators are not rendered inside entities.
                if entity_state.has_no_entity() and self.has_decorators:
                    decorated_node = render_decorators(self.composite_decorators, text, block, wrapper_state.blocks)
                else:
                    decorated_node = text

                styled_node = style_state.render_styles(decorated_node, block, wrapper_state.blocks)
                entity_node = entity_state.render_entities(styled_node)

                if entity_node is not None:
                    DOM.append_child(content, entity_node)

                    # Check whether there actually are two different nodes, confirming we are not inserting an upcoming entity.
                    if styled_node != entity_node and entity_state.has_no_entity():
                        DOM.append_child(content, styled_node)
        # Fast track for blocks which do not contain styles nor entities, which is very common.
        elif self.has_decorators:
            content = render_decorators(self.composite_decorators, block['text'], block, wrapper_state.blocks)
        else:
            content = block['text']

        return wrapper_state.element_for(block, content)
    def test_render_www(self):
        match = next(LINKIFY_DECORATOR['strategy'].finditer('test www.example.com'))

        self.assertEqual(DOM.render(DOM.create_element(LINKIFY_DECORATOR['component'], {
            'block': {'type': BLOCK_TYPES.UNSTYLED},
            'match': match,
        }, match.group(0))), '<a href="http://www.example.com">www.example.com</a>')
def render_decorators(decorators, text, block, blocks):
    decorated_children = list(apply_decorators(decorators, text, block, blocks))

    if len(decorated_children) == 1:
        decorated_node = decorated_children[0]
    else:
        decorated_node = DOM.create_element()
        for decorated_child in decorated_children:
            DOM.append_child(decorated_node, decorated_child)

    return decorated_node
示例#6
0
    def __init__(self, config=None):
        if config is None:
            config = {}

        self.entity_decorators = config.get('entity_decorators', {})
        self.composite_decorators = config.get('composite_decorators', [])
        self.has_decorators = len(self.composite_decorators) > 0
        self.block_map = config.get('block_map', BLOCK_MAP)
        self.style_map = config.get('style_map', STYLE_MAP)

        DOM.use(config.get('engine', DOM.STRING))
def button(props):
    href = props.get('href', '#')
    icon_name = props.get('icon', None)
    text = props.get('text', '')

    return DOM.create_element(
        'a',
        {'class': 'icon-text' if icon_name else None, 'href': href},
        DOM.create_element(icon, {'name': icon_name}) if icon_name else None,
        DOM.create_element('span', {'class': 'icon-text__text'}, text) if icon_name else text
    )
    def parent_for(self, options, depth, elt):
        if options.wrapper:
            parent = self.get_wrapper_elt(options, depth)
            DOM.append_child(parent, elt)
            self.stack.stack[-1].last_child = elt
        else:
            # Reset the stack if there is no wrapper.
            self.stack = WrapperStack()
            parent = elt

        return parent
def link(props):
    attributes = {}
    for key in props:
        attr = key if key != 'url' else 'href'
        attributes[attr] = props[key]

    return DOM.create_element('a', attributes, props['children'])
    def test_render_code_block(self):
        match = next(LINKIFY_DECORATOR['strategy'].finditer('test https://www.example.com'))

        self.assertEqual(DOM.create_element(LINKIFY_DECORATOR['component'], {
            'block': {'type': BLOCK_TYPES.CODE},
            'match': match,
        }, match.group(0)), match.group(0))
示例#11
0
def image(props):
    return DOM.create_element('img', {
        'src': props.get('src'),
        'width': props.get('width'),
        'height': props.get('height'),
        'alt': props.get('alt'),
    })
示例#12
0
def br(props):
    """
    Replace line breaks (\n) with br tags.
    """
    # Do not process matches inside code blocks.
    if props['block']['type'] == BLOCK_TYPES.CODE:
        return props['children']

    return DOM.create_element('br')
示例#13
0
def hashtag(props):
    """
    Wrap hashtags in spans with a specific class.
    """
    # Do not process matches inside code blocks.
    if props['block']['type'] == BLOCK_TYPES.CODE:
        return props['children']

    return DOM.create_element('span', {'class': 'hashtag'}, props['children'])
示例#14
0
def media_embed_entity(props):
    """
    Helper to construct elements of the form
    <embed embedtype="media" url="https://www.youtube.com/watch?v=y8Kyi0WNg40"/>
    when converting from contentstate data
    """
    return DOM.create_element('embed', {
        'embedtype': 'media',
        'url': props.get('url'),
    })
    def __init__(self, depth, options=None):
        self.depth = depth
        self.last_child = None

        if options:
            self.type = options.wrapper
            self.props = options.wrapper_props

            wrapper_props = dict(self.props) if self.props else {}
            wrapper_props['block'] = {
                'type': options.type,
                'depth': depth,
            }

            self.elt = DOM.create_element(self.type, wrapper_props)
        else:
            self.type = None
            self.props = None
            self.elt = DOM.create_element()
示例#16
0
def document_link_entity(props):
    """
    Helper to construct elements of the form
    <a id="1" linktype="document">document link</a>
    when converting from contentstate data
    """

    return DOM.create_element('a', {
        'linktype': 'document',
        'id': props.get('id'),
    }, props['children'])
示例#17
0
def image_entity(props):
    """
    Helper to construct elements of the form
    <embed alt="Right-aligned image" embedtype="image" format="right" id="1"/>
    when converting from contentstate data
    """
    return DOM.create_element('embed', {
        'embedtype': 'image',
        'format': props.get('format'),
        'id': props.get('id'),
        'alt': props.get('alt'),
    })
示例#18
0
    def render(self, content_state=None):
        """
        Starts the export process on a given piece of content state.
        """
        if content_state is None:
            content_state = {}

        blocks = content_state.get('blocks', [])
        wrapper_state = WrapperState(self.block_map, blocks)
        document = DOM.create_element()
        entity_map = content_state.get('entityMap', {})
        min_depth = 0

        for block in blocks:
            depth = block['depth']
            elt = self.render_block(block, entity_map, wrapper_state)

            if depth > min_depth:
                min_depth = depth

            # At level 0, append the element to the document.
            if depth == 0:
                DOM.append_child(document, elt)

        # If there is no block at depth 0, we need to add the wrapper that contains the whole tree to the document.
        if min_depth > 0 and wrapper_state.stack.length() != 0:
            DOM.append_child(document, wrapper_state.stack.tail().elt)

        return DOM.render(document)
示例#19
0
 def test_camel_to_dash(self):
     self.assertEqual(DOM.camel_to_dash('testCamelToDash'), 'test-camel-to-dash')
     self.assertEqual(DOM.camel_to_dash('TestCamelToDash'), 'test-camel-to-dash')
     self.assertEqual(DOM.camel_to_dash('TestCamelTODash'), 'test-camel-to-dash')
     self.assertEqual(DOM.camel_to_dash('TestCamelTODasH'), 'test-camel-to-das-h')
     self.assertEqual(DOM.camel_to_dash('testcameltodash'), 'testcameltodash')
     self.assertEqual(DOM.camel_to_dash('test-Camel-ToDash'), 'test-camel-to-dash')
    def element_for(self, block, block_content):
        type_ = block['type']
        depth = block['depth']
        options = Options.for_block(self.block_map, type_)
        props = dict(options.props)
        props['block'] = block
        props['blocks'] = self.blocks

        # Make an element from the options specified in the block map.
        elt = DOM.create_element(options.element, props, block_content)

        parent = self.parent_for(options, depth, elt)

        return parent
示例#21
0
def link_entity(props):
    """
    <a linktype="page" id="1">internal page link</a>
    """
    id_ = props.get('id')
    link_props = {}

    if id_ is not None:
        link_props['linktype'] = 'page'
        link_props['id'] = id_
    else:
        link_props['href'] = props.get('url')

    return DOM.create_element('a', link_props, props['children'])
示例#22
0
    def render_styles(self, decorated_node, block, blocks):
        node = decorated_node
        if not self.is_empty():
            # Nest the tags.
            for style in sorted(self.styles, reverse=True):
                opt = Options.for_style(self.style_map, style)
                props = dict(opt.props)
                props['block'] = block
                props['blocks'] = blocks
                props['inline_style_range'] = {
                    'style': style,
                }
                node = DOM.create_element(opt.element, props, node)

        return node
示例#23
0
def block_fallback(props):
    type_ = props['block']['type']

    if type_ == 'example-discard':
        logging.warn('Missing config for "%s". Discarding block, keeping content.' % type_)
        # Directly return the block's children to keep its content.
        return props['children']
    elif type_ == 'example-delete':
        logging.error('Missing config for "%s". Deleting block.' % type_)
        # Return None to not render anything, removing the whole block.
        return None
    else:
        logging.warn('Missing config for "%s". Using div instead.' % type_)
        # Provide a fallback.
        return DOM.create_element('div', {}, props['children'])
def apply_decorators(decorators, text, block, blocks):
    decorations = get_decorations(decorators, text)

    pointer = 0
    for begin, end, match, decorator in decorations:
        if pointer < begin:
            yield text[pointer:begin]

        yield DOM.create_element(decorator['component'], {
            'match': match,
            'block': block,
            'blocks': blocks,
        }, match.group(0))
        pointer = end

    if pointer < len(text):
        yield text[pointer:]
示例#25
0
def linkify(props):
    """
    Wrap plain URLs with link tags.
    """
    match = props['match']
    protocol = match.group(1)
    url = match.group(2)
    href = protocol + url

    if props['block']['type'] == BLOCK_TYPES.CODE:
        return href

    link_props = {
        'href': href,
    }

    if href.startswith('www'):
        link_props['href'] = 'http://' + href

    return DOM.create_element('a', link_props, href)
示例#26
0
def hr(props):
    return DOM.create_element('hr')
示例#27
0
 def tearDown(self):
     DOM.use(DOM.HTML5LIB)
示例#28
0
def register_core_features(features):
    # Hallo.js
    features.register_editor_plugin(
        'hallo', 'hr',
        HalloPlugin(
            name='hallohr',
            js=['wagtailadmin/js/hallo-plugins/hallo-hr.js'],
            order=45,
        ))
    features.register_converter_rule(
        'editorhtml', 'hr', [WhitelistRule('hr', allow_without_attributes)])

    features.register_editor_plugin(
        'hallo', 'link',
        HalloPlugin(
            name='hallowagtaillink',
            js=[
                'wagtailadmin/js/page-chooser-modal.js',
                'wagtailadmin/js/hallo-plugins/hallo-wagtaillink.js',
            ],
        ))
    features.register_converter_rule('editorhtml', 'link', [
        WhitelistRule('a', attribute_rule({'href': check_url})),
        LinkTypeRule('page', PageLinkHandler),
    ])

    features.register_editor_plugin('hallo', 'bold',
                                    HalloFormatPlugin(format_name='bold'))
    features.register_converter_rule('editorhtml', 'bold', [
        WhitelistRule('b', allow_without_attributes),
        WhitelistRule('strong', allow_without_attributes),
    ])

    features.register_editor_plugin('hallo', 'italic',
                                    HalloFormatPlugin(format_name='italic'))
    features.register_converter_rule('editorhtml', 'italic', [
        WhitelistRule('i', allow_without_attributes),
        WhitelistRule('em', allow_without_attributes),
    ])

    headings_elements = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
    headings_order_start = HalloHeadingPlugin.default_order + 1
    for order, element in enumerate(headings_elements,
                                    start=headings_order_start):
        features.register_editor_plugin(
            'hallo', element, HalloHeadingPlugin(element=element, order=order))
        features.register_converter_rule(
            'editorhtml', element,
            [WhitelistRule(element, allow_without_attributes)])

    features.register_editor_plugin('hallo', 'ol',
                                    HalloListPlugin(list_type='ordered'))
    features.register_converter_rule('editorhtml', 'ol', [
        WhitelistRule('ol', allow_without_attributes),
        WhitelistRule('li', allow_without_attributes),
    ])

    features.register_editor_plugin('hallo', 'ul',
                                    HalloListPlugin(list_type='unordered'))
    features.register_converter_rule('editorhtml', 'ul', [
        WhitelistRule('ul', allow_without_attributes),
        WhitelistRule('li', allow_without_attributes),
    ])

    # Draftail
    features.register_editor_plugin(
        'draftail', 'hr',
        draftail_features.BooleanFeature('enableHorizontalRule'))
    features.register_converter_rule(
        'contentstate', 'hr', {
            'from_database_format': {
                'hr': HorizontalRuleHandler(),
            },
            'to_database_format': {
                'entity_decorators': {
                    'HORIZONTAL_RULE': lambda props: DOM.create_element('hr')
                }
            }
        })

    features.register_editor_plugin(
        'draftail', 'h1',
        draftail_features.BlockFeature({
            'label': 'H1',
            'type': 'header-one',
            'description': gettext('Heading %(level)d') % {
                'level': 1
            },
        }))
    features.register_converter_rule(
        'contentstate', 'h1', {
            'from_database_format': {
                'h1': BlockElementHandler('header-one'),
            },
            'to_database_format': {
                'block_map': {
                    'header-one': 'h1'
                }
            }
        })
    features.register_editor_plugin(
        'draftail', 'h2',
        draftail_features.BlockFeature({
            'label': 'H2',
            'type': 'header-two',
            'description': gettext('Heading %(level)d') % {
                'level': 2
            },
        }))
    features.register_converter_rule(
        'contentstate', 'h2', {
            'from_database_format': {
                'h2': BlockElementHandler('header-two'),
            },
            'to_database_format': {
                'block_map': {
                    'header-two': 'h2'
                }
            }
        })
    features.register_editor_plugin(
        'draftail', 'h3',
        draftail_features.BlockFeature({
            'label': 'H3',
            'type': 'header-three',
            'description': gettext('Heading %(level)d') % {
                'level': 3
            },
        }))
    features.register_converter_rule(
        'contentstate', 'h3', {
            'from_database_format': {
                'h3': BlockElementHandler('header-three'),
            },
            'to_database_format': {
                'block_map': {
                    'header-three': 'h3'
                }
            }
        })
    features.register_editor_plugin(
        'draftail', 'h4',
        draftail_features.BlockFeature({
            'label': 'H4',
            'type': 'header-four',
            'description': gettext('Heading %(level)d') % {
                'level': 4
            },
        }))
    features.register_converter_rule(
        'contentstate', 'h4', {
            'from_database_format': {
                'h4': BlockElementHandler('header-four'),
            },
            'to_database_format': {
                'block_map': {
                    'header-four': 'h4'
                }
            }
        })
    features.register_editor_plugin(
        'draftail', 'h5',
        draftail_features.BlockFeature({
            'label': 'H5',
            'type': 'header-five',
            'description': gettext('Heading %(level)d') % {
                'level': 5
            },
        }))
    features.register_converter_rule(
        'contentstate', 'h5', {
            'from_database_format': {
                'h5': BlockElementHandler('header-five'),
            },
            'to_database_format': {
                'block_map': {
                    'header-five': 'h5'
                }
            }
        })
    features.register_editor_plugin(
        'draftail', 'h6',
        draftail_features.BlockFeature({
            'label': 'H6',
            'type': 'header-six',
            'description': gettext('Heading %(level)d') % {
                'level': 6
            },
        }))
    features.register_converter_rule(
        'contentstate', 'h6', {
            'from_database_format': {
                'h6': BlockElementHandler('header-six'),
            },
            'to_database_format': {
                'block_map': {
                    'header-six': 'h6'
                }
            }
        })
    features.register_editor_plugin(
        'draftail', 'ul',
        draftail_features.BlockFeature({
            'type': 'unordered-list-item',
            'icon': 'list-ul',
            'description': gettext('Bulleted list'),
        }))
    features.register_converter_rule(
        'contentstate', 'ul', {
            'from_database_format': {
                'ul': ListElementHandler('unordered-list-item'),
                'li': ListItemElementHandler(),
            },
            'to_database_format': {
                'block_map': {
                    'unordered-list-item': {
                        'element': 'li',
                        'wrapper': 'ul'
                    }
                }
            }
        })
    features.register_editor_plugin(
        'draftail', 'ol',
        draftail_features.BlockFeature({
            'type': 'ordered-list-item',
            'icon': 'list-ol',
            'description': gettext('Numbered list'),
        }))
    features.register_converter_rule(
        'contentstate', 'ol', {
            'from_database_format': {
                'ol': ListElementHandler('ordered-list-item'),
                'li': ListItemElementHandler(),
            },
            'to_database_format': {
                'block_map': {
                    'ordered-list-item': {
                        'element': 'li',
                        'wrapper': 'ol'
                    }
                }
            }
        })
    features.register_editor_plugin(
        'draftail', 'blockquote',
        draftail_features.BlockFeature({
            'type': 'blockquote',
            'icon': 'openquote',
            'description': gettext('Blockquote'),
        }))
    features.register_converter_rule(
        'contentstate', 'blockquote', {
            'from_database_format': {
                'blockquote': BlockElementHandler('blockquote'),
            },
            'to_database_format': {
                'block_map': {
                    'blockquote': 'blockquote'
                }
            }
        })

    features.register_editor_plugin(
        'draftail', 'bold',
        draftail_features.InlineStyleFeature({
            'type': 'BOLD',
            'icon': 'bold',
            'description': gettext('Bold'),
        }))
    features.register_converter_rule(
        'contentstate', 'bold', {
            'from_database_format': {
                'b': InlineStyleElementHandler('BOLD'),
                'strong': InlineStyleElementHandler('BOLD'),
            },
            'to_database_format': {
                'style_map': {
                    'BOLD': 'b'
                }
            }
        })
    features.register_editor_plugin(
        'draftail', 'italic',
        draftail_features.InlineStyleFeature({
            'type': 'ITALIC',
            'icon': 'italic',
            'description': gettext('Italic'),
        }))
    features.register_converter_rule(
        'contentstate', 'italic', {
            'from_database_format': {
                'i': InlineStyleElementHandler('ITALIC'),
                'em': InlineStyleElementHandler('ITALIC'),
            },
            'to_database_format': {
                'style_map': {
                    'ITALIC': 'i'
                }
            }
        })

    features.register_editor_plugin(
        'draftail',
        'link',
        draftail_features.EntityFeature(
            {
                'type': 'LINK',
                'icon': 'link',
                'description': gettext('Link'),
                # We want to enforce constraints on which links can be pasted into rich text.
                # Keep only the attributes Wagtail needs.
                'attributes': ['url', 'id', 'parentId'],
                'whitelist': {
                    # Keep pasted links with http/https protocol, and not-pasted links (href = undefined).
                    'href': "^(http:|https:|undefined$)",
                }
            },
            js=[
                'wagtailadmin/js/page-chooser-modal.js',
            ]))
    features.register_converter_rule(
        'contentstate', 'link', {
            'from_database_format': {
                'a[href]': ExternalLinkElementHandler('LINK'),
                'a[linktype="page"]': PageLinkElementHandler('LINK'),
            },
            'to_database_format': {
                'entity_decorators': {
                    'LINK': link_entity
                }
            }
        })
    features.register_editor_plugin(
        'draftail', 'superscript',
        draftail_features.InlineStyleFeature({
            'type':
            'SUPERSCRIPT',
            'icon':
            'superscript',
            'description':
            gettext('Superscript'),
        }))
    features.register_converter_rule(
        'contentstate', 'superscript', {
            'from_database_format': {
                'sup': InlineStyleElementHandler('SUPERSCRIPT'),
            },
            'to_database_format': {
                'style_map': {
                    'SUPERSCRIPT': 'sup'
                }
            }
        })
    features.register_editor_plugin(
        'draftail', 'subscript',
        draftail_features.InlineStyleFeature({
            'type':
            'SUBSCRIPT',
            'icon':
            'subscript',
            'description':
            gettext('Subscript'),
        }))
    features.register_converter_rule(
        'contentstate', 'subscript', {
            'from_database_format': {
                'sub': InlineStyleElementHandler('SUBSCRIPT'),
            },
            'to_database_format': {
                'style_map': {
                    'SUBSCRIPT': 'sub'
                }
            }
        })
    features.register_editor_plugin(
        'draftail', 'strikethrough',
        draftail_features.InlineStyleFeature({
            'type':
            'STRIKETHROUGH',
            'icon':
            'strikethrough',
            'description':
            gettext('Strikethrough'),
        }))
    features.register_converter_rule(
        'contentstate', 'strikethrough', {
            'from_database_format': {
                's': InlineStyleElementHandler('STRIKETHROUGH'),
            },
            'to_database_format': {
                'style_map': {
                    'STRIKETHROUGH': 's'
                }
            }
        })
    features.register_editor_plugin(
        'draftail', 'code',
        draftail_features.InlineStyleFeature({
            'type': 'CODE',
            'icon': 'code',
            'description': gettext('Code'),
        }))
    features.register_converter_rule(
        'contentstate', 'code', {
            'from_database_format': {
                'code': InlineStyleElementHandler('CODE'),
            },
            'to_database_format': {
                'style_map': {
                    'CODE': 'code'
                }
            }
        })
示例#29
0
def missing_block(props):
    return DOM.create_element('div', {'class': 'missing-block'},
                              props['children'])
示例#30
0
def entity_fallback(props):
    type_ = props['entity']['type']
    logging.warn('Missing config for "%s".' % type_)
    return DOM.create_element('span', {'class': 'missing-entity'},
                              props['children'])
示例#31
0
def list_item(props):
    depth = props['block']['depth']

    return DOM.create_element('li',
                              {'class': 'list-item--depth-{0}'.format(depth)},
                              props['children'])
示例#32
0
 def test_image_alt(self):
     self.assertEqual(DOM.render(image({
         'src': 'test.png',
         'alt': 'test',
     })), '![test](test.png)\n\n')
示例#33
0
 def test_horizontal_rule(self):
     self.assertEqual(DOM.render(horizontal_rule({})), '---\n\n')
示例#34
0
 def test_use_custom(self):
     DOM.use("tests.test_dom.DOMTestImpl")
     self.assertEqual(DOM.dom, DOMTestImpl)
示例#35
0
def code_block(props):
    return DOM.create_element('pre', {}, DOM.create_element('code', {}, props['children']))
示例#36
0
 def test_works(self):
     self.assertEqual(
         DOM.render(code_element({
             'block': {},
             'children': 'test',
         })), 'test\n')
示例#37
0
 def test_works(self):
     self.assertEqual(
         DOM.render(code_wrapper({
             'block': {},
             'children': 'test',
         })), '```\n')
示例#38
0
 def test_create_element_empty(self):
     self.assertEqual(
         DOM.render_debug(DOM.create_element()), "<fragment></fragment>"
     )
示例#39
0
 def test_use_invalid(self):
     with self.assertRaises(ImportError):
         DOM.use("test")
示例#40
0
 def test_use_string(self):
     DOM.use(DOM.STRING)
     self.assertEqual(DOM.dom, DOMString)
示例#41
0
 def test_use_html5lib(self):
     DOM.use(DOM.HTML5LIB)
     self.assertEqual(DOM.dom, DOM_HTML5LIB)
示例#42
0
 def test_append_child(self):
     parent = DOM.create_element("p")
     DOM.append_child(parent, DOM.create_element("span", {}, "Test text"))
     self.assertEqual(
         DOM.render_debug(parent), "<p><span>Test text</span></p>"
     )
示例#43
0
 def test_link(self):
     self.assertEqual(DOM.render(link({
         'url': 'http://www.example.com/',
         'children': 'test',
     })), '[test](http://www.example.com/)')
示例#44
0
 def test_parse_html(self):
     self.assertEqual(
         DOM.render_debug(DOM.parse_html("<p><span>Test text</span></p>")),
         "<p><span>Test text</span></p>",
     )
示例#45
0
                 }
             }
         },
         # Provide a fallback component (advanced).
         INLINE_STYLES.FALLBACK:
         style_fallback,
     }),
 'entity_decorators': {
     # Map entities to components so they can be rendered with their data.
     ENTITY_TYPES.IMAGE:
     image,
     ENTITY_TYPES.LINK:
     link,
     # Lambdas work too.
     ENTITY_TYPES.HORIZONTAL_RULE:
     lambda props: DOM.create_element('hr'),
     # Discard those entities.
     ENTITY_TYPES.EMBED:
     None,
     # Provide a fallback component (advanced).
     ENTITY_TYPES.FALLBACK:
     entity_fallback,
 },
 'composite_decorators': [
     # Use composite decorators to replace text based on a regular expression.
     {
         'strategy': re.compile(r'\n'),
         'component': br,
     },
     {
         'strategy': re.compile(r'#\w+'),
示例#46
0
 def test_create_element_entity(self):
     self.assertEqual(
         DOM.render_debug(DOM.create_element(icon, {"name": "rocket"})),
         '<svg class="icon"><use xlink:href="#icon-rocket"></use></svg>',
     )
示例#47
0
def blockquote(props):
    block_data = props['block']['data']

    return DOM.create_element('blockquote', {'cite': block_data.get('cite')},
                              props['children'])
示例#48
0
def ordered_list(props):
    depth = props['block']['depth']

    return DOM.create_element('ol', {
        'class': 'list--depth-{0}'.format(depth)
    }, props['children'])
示例#49
0
def ordered_list(props):
    depth = props['block']['depth']

    return DOM.create_element('ol', {'class': 'list--depth-{0}'.format(depth)},
                              props['children'])
示例#50
0
 def test_render_without_icon(self):
     self.assertEqual(DOM.render(DOM.create_element(button, {
         'href': 'http://example.com',
         'text': 'Launch',
     })), '<a href="http://example.com">Launch</a>')
示例#51
0
def icon(props):
    href = 'icon-%s' % props.get('name', '')
    return DOM.create_element('svg', {'class': 'icon'},
                              DOM.create_element('use', {'xlink:href': href}))
示例#52
0
def link(props):
    return DOM.create_element('a', {
        'href': props['url']
    }, props['children'])
示例#53
0
def missing_inline(props):
    return DOM.create_element('span', {'class': 'missing-inline'},
                              props['children'])
示例#54
0
 def test_render_with_icon(self):
     self.assertEqual(DOM.render(DOM.create_element(button, {
         'href': 'http://example.com',
         'icon': 'rocket',
         'text': 'Launch',
     })), '<a class="icon-text" href="http://example.com"><svg class="icon"><use xlink:href="#icon-rocket"></use></svg><span class="icon-text__text">Launch</span></a>')
示例#55
0
def link(props):
    return DOM.create_element('a', {'href': props['url']}, props['children'])
示例#56
0
 def test_render(self):
     self.assertEqual(DOM.render(DOM.create_element(link, {
         'url': 'http://example.com',
     }, 'wow')), '<a href="http://example.com">wow</a>')
示例#57
0
def br(props):
    if props['block']['type'] == 'code-block':
        return props['children']

    return DOM.create_element('br')
示例#58
0
 def test_image(self):
     self.assertEqual(DOM.render(image({
         'src': 'test.png',
     })), '![](test.png)\n\n')
示例#59
0
def register_core_features(features):
    # Hallo.js
    features.register_editor_plugin(
        'hallo', 'hr',
        HalloPlugin(
            name='hallohr',
            js=['wagtailadmin/js/hallo-plugins/hallo-hr.js'],
            order=45,
        )
    )
    features.register_converter_rule('editorhtml', 'hr', [
        WhitelistRule('hr', allow_without_attributes)
    ])

    features.register_editor_plugin(
        'hallo', 'link',
        HalloPlugin(
            name='hallowagtaillink',
            js=['wagtailadmin/js/hallo-plugins/hallo-wagtaillink.js'],
        )
    )
    features.register_converter_rule('editorhtml', 'link', [
        WhitelistRule('a', attribute_rule({'href': check_url})),
        LinkTypeRule('page', PageLinkHandler),
    ])

    features.register_editor_plugin(
        'hallo', 'bold', HalloFormatPlugin(format_name='bold')
    )
    features.register_converter_rule('editorhtml', 'bold', [
        WhitelistRule('b', allow_without_attributes),
        WhitelistRule('strong', allow_without_attributes),
    ])

    features.register_editor_plugin(
        'hallo', 'italic', HalloFormatPlugin(format_name='italic')
    )
    features.register_converter_rule('editorhtml', 'italic', [
        WhitelistRule('i', allow_without_attributes),
        WhitelistRule('em', allow_without_attributes),
    ])

    headings_elements = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
    headings_order_start = HalloHeadingPlugin.default_order + 1
    for order, element in enumerate(headings_elements, start=headings_order_start):
        features.register_editor_plugin(
            'hallo', element, HalloHeadingPlugin(element=element, order=order)
        )
        features.register_converter_rule('editorhtml', element, [
            WhitelistRule(element, allow_without_attributes)
        ])

    features.register_editor_plugin(
        'hallo', 'ol', HalloListPlugin(list_type='ordered')
    )
    features.register_converter_rule('editorhtml', 'ol', [
        WhitelistRule('ol', allow_without_attributes),
        WhitelistRule('li', allow_without_attributes),
    ])

    features.register_editor_plugin(
        'hallo', 'ul', HalloListPlugin(list_type='unordered')
    )
    features.register_converter_rule('editorhtml', 'ul', [
        WhitelistRule('ul', allow_without_attributes),
        WhitelistRule('li', allow_without_attributes),
    ])

    # Draftail
    features.register_editor_plugin(
        'draftail', 'hr', draftail_features.BooleanFeature('enableHorizontalRule')
    )
    features.register_converter_rule('contentstate', 'hr', {
        'from_database_format': {
            'hr': HorizontalRuleHandler(),
        },
        'to_database_format': {
            'entity_decorators': {'HORIZONTAL_RULE': lambda props: DOM.create_element('hr')}
        }
    })

    features.register_editor_plugin(
        'draftail', 'h1', draftail_features.BlockFeature({
            'label': 'H1',
            'type': 'header-one',
            'description': ugettext('Heading {level}').format(level=1),
        })
    )
    features.register_converter_rule('contentstate', 'h1', {
        'from_database_format': {
            'h1': BlockElementHandler('header-one'),
        },
        'to_database_format': {
            'block_map': {'header-one': 'h1'}
        }
    })
    features.register_editor_plugin(
        'draftail', 'h2', draftail_features.BlockFeature({
            'label': 'H2',
            'type': 'header-two',
            'description': ugettext('Heading {level}').format(level=2),
        })
    )
    features.register_converter_rule('contentstate', 'h2', {
        'from_database_format': {
            'h2': BlockElementHandler('header-two'),
        },
        'to_database_format': {
            'block_map': {'header-two': 'h2'}
        }
    })
    features.register_editor_plugin(
        'draftail', 'h3', draftail_features.BlockFeature({
            'label': 'H3',
            'type': 'header-three',
            'description': ugettext('Heading {level}').format(level=3),
        })
    )
    features.register_converter_rule('contentstate', 'h3', {
        'from_database_format': {
            'h3': BlockElementHandler('header-three'),
        },
        'to_database_format': {
            'block_map': {'header-three': 'h3'}
        }
    })
    features.register_editor_plugin(
        'draftail', 'h4', draftail_features.BlockFeature({
            'label': 'H4',
            'type': 'header-four',
            'description': ugettext('Heading {level}').format(level=4),
        })
    )
    features.register_converter_rule('contentstate', 'h4', {
        'from_database_format': {
            'h4': BlockElementHandler('header-four'),
        },
        'to_database_format': {
            'block_map': {'header-four': 'h4'}
        }
    })
    features.register_editor_plugin(
        'draftail', 'h5', draftail_features.BlockFeature({
            'label': 'H5',
            'type': 'header-five',
            'description': ugettext('Heading {level}').format(level=5),
        })
    )
    features.register_converter_rule('contentstate', 'h5', {
        'from_database_format': {
            'h5': BlockElementHandler('header-five'),
        },
        'to_database_format': {
            'block_map': {'header-five': 'h5'}
        }
    })
    features.register_editor_plugin(
        'draftail', 'h6', draftail_features.BlockFeature({
            'label': 'H6',
            'type': 'header-six',
            'description': ugettext('Heading {level}').format(level=6),
        })
    )
    features.register_converter_rule('contentstate', 'h6', {
        'from_database_format': {
            'h6': BlockElementHandler('header-six'),
        },
        'to_database_format': {
            'block_map': {'header-six': 'h6'}
        }
    })
    features.register_editor_plugin(
        'draftail', 'ul', draftail_features.BlockFeature({
            'type': 'unordered-list-item',
            'icon': 'list-ul',
            'description': ugettext('Bulleted list'),
        })
    )
    features.register_converter_rule('contentstate', 'ul', {
        'from_database_format': {
            'ul': ListElementHandler('unordered-list-item'),
            'li': ListItemElementHandler(),
        },
        'to_database_format': {
            'block_map': {'unordered-list-item': {'element': 'li', 'wrapper': 'ul'}}
        }
    })
    features.register_editor_plugin(
        'draftail', 'ol', draftail_features.BlockFeature({
            'type': 'ordered-list-item',
            'icon': 'list-ol',
            'description': ugettext('Numbered list'),
        })
    )
    features.register_converter_rule('contentstate', 'ol', {
        'from_database_format': {
            'ol': ListElementHandler('ordered-list-item'),
            'li': ListItemElementHandler(),
        },
        'to_database_format': {
            'block_map': {'ordered-list-item': {'element': 'li', 'wrapper': 'ol'}}
        }
    })

    features.register_editor_plugin(
        'draftail', 'bold', draftail_features.InlineStyleFeature({
            'type': 'BOLD',
            'icon': 'bold',
            'description': ugettext('Bold'),
        })
    )
    features.register_converter_rule('contentstate', 'bold', {
        'from_database_format': {
            'b': InlineStyleElementHandler('BOLD'),
            'strong': InlineStyleElementHandler('BOLD'),
        },
        'to_database_format': {
            'style_map': {'BOLD': 'b'}
        }
    })
    features.register_editor_plugin(
        'draftail', 'italic', draftail_features.InlineStyleFeature({
            'type': 'ITALIC',
            'icon': 'italic',
            'description': ugettext('Italic'),
        })
    )
    features.register_converter_rule('contentstate', 'italic', {
        'from_database_format': {
            'i': InlineStyleElementHandler('ITALIC'),
            'em': InlineStyleElementHandler('ITALIC'),
        },
        'to_database_format': {
            'style_map': {'ITALIC': 'i'}
        }
    })

    features.register_editor_plugin(
        'draftail', 'link', draftail_features.EntityFeature({
            'type': 'LINK',
            'icon': 'link',
            'description': ugettext('Link'),
            # We want to enforce constraints on which links can be pasted into rich text.
            # Keep only the attributes Wagtail needs.
            'attributes': ['url', 'id', 'parentId'],
            'whitelist': {
                # Keep pasted links with http/https protocol, and not-pasted links (href = undefined).
                'href': "^(http:|https:|undefined$)",
            }
        })
    )
    features.register_converter_rule('contentstate', 'link', {
        'from_database_format': {
            'a[href]': ExternalLinkElementHandler('LINK'),
            'a[linktype="page"]': PageLinkElementHandler('LINK'),
        },
        'to_database_format': {
            'entity_decorators': {'LINK': link_entity}
        }
    })
示例#60
0
 def test_use_lxml(self):
     DOM.use(DOM.LXML)
     self.assertEqual(DOM.dom, DOM_LXML)