Use feature registry to manage contentstate conversion rules

pull/4136/head
Matt Westcott 2017-12-11 18:14:38 +00:00 zatwierdzone przez Thibaud Colas
rodzic 6ceee1a6ce
commit 1ceff85f12
6 zmienionych plików z 192 dodań i 141 usunięć

Wyświetl plik

@ -2,12 +2,13 @@ import json
import logging
import re
from draftjs_exporter.constants import BLOCK_TYPES, ENTITY_TYPES, INLINE_STYLES
from draftjs_exporter.constants import BLOCK_TYPES, ENTITY_TYPES
from draftjs_exporter.defaults import render_children
from draftjs_exporter.dom import DOM
from draftjs_exporter.html import HTML as HTMLExporter
from wagtail.admin.rich_text.converters.html_to_contentstate import HtmlToContentStateHandler
from wagtail.core.rich_text import features as feature_registry
def Image(props):
@ -85,55 +86,6 @@ def EntityFallback(props):
return None
EXPORTER_CONFIG_BY_FEATURE = {
'h1': {
'block_map': {BLOCK_TYPES.HEADER_ONE: 'h1'}
},
'h2': {
'block_map': {BLOCK_TYPES.HEADER_TWO: 'h2'}
},
'h3': {
'block_map': {BLOCK_TYPES.HEADER_THREE: 'h3'}
},
'h4': {
'block_map': {BLOCK_TYPES.HEADER_FOUR: 'h4'}
},
'h5': {
'block_map': {BLOCK_TYPES.HEADER_FIVE: 'h5'}
},
'h6': {
'block_map': {BLOCK_TYPES.HEADER_SIX: 'h6'}
},
'bold': {
'style_map': {INLINE_STYLES.BOLD: 'b'}
},
'italic': {
'style_map': {INLINE_STYLES.ITALIC: 'i'}
},
'ol': {
'block_map': {BLOCK_TYPES.ORDERED_LIST_ITEM: {'element': 'li', 'wrapper': 'ol'}}
},
'ul': {
'block_map': {BLOCK_TYPES.UNORDERED_LIST_ITEM: {'element': 'li', 'wrapper': 'ul'}}
},
'hr': {
'entity_decorators': {ENTITY_TYPES.HORIZONTAL_RULE: lambda props: DOM.create_element('hr')}
},
'link': {
'entity_decorators': {ENTITY_TYPES.LINK: Link}
},
'document-link': {
'entity_decorators': {ENTITY_TYPES.DOCUMENT: Document}
},
'image': {
'entity_decorators': {ENTITY_TYPES.IMAGE: Image}
},
'embed': {
'entity_decorators': {ENTITY_TYPES.EMBED: Embed}
},
}
class ContentstateConverter():
def __init__(self, features=None):
self.features = features
@ -156,10 +108,12 @@ class ContentstateConverter():
}
for feature in self.features:
feature_config = EXPORTER_CONFIG_BY_FEATURE.get(feature, {})
exporter_config['block_map'].update(feature_config.get('block_map', {}))
exporter_config['style_map'].update(feature_config.get('style_map', {}))
exporter_config['entity_decorators'].update(feature_config.get('entity_decorators', {}))
rule = feature_registry.get_converter_rule('contentstate', feature)
if rule is not None:
feature_config = rule['to_database_format']
exporter_config['block_map'].update(feature_config.get('block_map', {}))
exporter_config['style_map'].update(feature_config.get('style_map', {}))
exporter_config['entity_decorators'].update(feature_config.get('entity_decorators', {}))
self.exporter = HTMLExporter(exporter_config)

Wyświetl plik

@ -5,6 +5,7 @@ from wagtail.admin.rich_text.converters.contentstate_models import (
Block, ContentState, Entity, EntityRange, InlineStyleRange
)
from wagtail.admin.rich_text.converters.html_ruleset import HTMLRuleset
from wagtail.core.rich_text import features as feature_registry
from wagtail.core.models import Page
from wagtail.documents.models import get_document_model
@ -226,60 +227,6 @@ class HorizontalRuleHandler(AtomicBlockEntityElementHandler):
return Entity('HORIZONTAL_RULE', 'IMMUTABLE', {})
ELEMENT_HANDLERS_BY_FEATURE = {
'ol': {
'ol': ListElementHandler('ordered-list-item'),
'li': ListItemElementHandler(),
},
'ul': {
'ul': ListElementHandler('unordered-list-item'),
'li': ListItemElementHandler(),
},
'h1': {
'h1': BlockElementHandler('header-one'),
},
'h2': {
'h2': BlockElementHandler('header-two'),
},
'h3': {
'h3': BlockElementHandler('header-three'),
},
'h4': {
'h4': BlockElementHandler('header-four'),
},
'h5': {
'h5': BlockElementHandler('header-five'),
},
'h6': {
'h6': BlockElementHandler('header-six'),
},
'italic': {
'i': InlineStyleElementHandler('ITALIC'),
'em': InlineStyleElementHandler('ITALIC'),
},
'bold': {
'b': InlineStyleElementHandler('BOLD'),
'strong': InlineStyleElementHandler('BOLD'),
},
'link': {
'a[href]': ExternalLinkElementHandler('LINK'),
'a[linktype="page"]': PageLinkElementHandler('LINK'),
},
'document-link': {
'a[linktype="document"]': DocumentLinkElementHandler('DOCUMENT'),
},
'image': {
'embed[embedtype="image"]': ImageElementHandler(),
},
'embed': {
'embed[embedtype="media"]': MediaEmbedElementHandler(),
},
'hr': {
'hr': HorizontalRuleHandler(),
},
}
class HtmlToContentStateHandler(HTMLParser):
def __init__(self, features=None):
self.paragraph_handler = BlockElementHandler('unstyled')
@ -288,11 +235,9 @@ class HtmlToContentStateHandler(HTMLParser):
})
if features is not None:
for feature in features:
try:
feature_element_handlers = ELEMENT_HANDLERS_BY_FEATURE[feature]
except KeyError:
continue
self.element_handlers.add_rules(feature_element_handlers)
rule = feature_registry.get_converter_rule('contentstate', feature)
if rule is not None:
self.element_handlers.add_rules(rule['from_database_format'])
super().__init__()

Wyświetl plik

@ -3,12 +3,18 @@ from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from draftjs_exporter.constants import BLOCK_TYPES, ENTITY_TYPES, INLINE_STYLES
from draftjs_exporter.dom import DOM
from wagtail.admin.menu import MenuItem, SubmenuMenuItem, settings_menu
from wagtail.admin.navigation import get_explorable_root_page
from wagtail.admin.rich_text import (
HalloFormatPlugin, HalloHeadingPlugin, HalloListPlugin, HalloPlugin)
from wagtail.admin.rich_text.converters.contentstate import Link
from wagtail.admin.rich_text.converters.editor_html import LinkTypeRule, WhitelistRule
from wagtail.admin.rich_text.converters.html_to_contentstate import (
BlockElementHandler, ExternalLinkElementHandler, HorizontalRuleHandler, InlineStyleElementHandler,
ListElementHandler, ListItemElementHandler, PageLinkElementHandler
)
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.search import SearchArea
from wagtail.admin.utils import user_has_any_page_permission
@ -256,50 +262,138 @@ def register_core_features(features):
features.register_editor_plugin(
'draftail', 'hr', draftail_features.BooleanFeature('enableHorizontalRule')
)
features.register_editor_plugin(
'draftail', 'br', draftail_features.BooleanFeature('enableLineBreak')
)
features.register_converter_rule('contentstate', 'hr', {
'from_database_format': {
'hr': HorizontalRuleHandler(),
},
'to_database_format': {
'entity_decorators': {ENTITY_TYPES.HORIZONTAL_RULE: lambda props: DOM.create_element('hr')}
}
})
features.register_editor_plugin(
'draftail', 'h1', draftail_features.BlockFeature({'label': 'H1', 'type': BLOCK_TYPES.HEADER_ONE})
)
features.register_converter_rule('contentstate', 'h1', {
'from_database_format': {
'h1': BlockElementHandler('header-one'),
},
'to_database_format': {
'block_map': {BLOCK_TYPES.HEADER_ONE: 'h1'}
}
})
features.register_editor_plugin(
'draftail', 'h2', draftail_features.BlockFeature({'label': 'H2', 'type': BLOCK_TYPES.HEADER_TWO})
)
features.register_converter_rule('contentstate', 'h2', {
'from_database_format': {
'h2': BlockElementHandler('header-two'),
},
'to_database_format': {
'block_map': {BLOCK_TYPES.HEADER_TWO: 'h2'}
}
})
features.register_editor_plugin(
'draftail', 'h3', draftail_features.BlockFeature({'label': 'H3', 'type': BLOCK_TYPES.HEADER_THREE})
)
features.register_converter_rule('contentstate', 'h3', {
'from_database_format': {
'h3': BlockElementHandler('header-three'),
},
'to_database_format': {
'block_map': {BLOCK_TYPES.HEADER_THREE: 'h3'}
}
})
features.register_editor_plugin(
'draftail', 'h4', draftail_features.BlockFeature({'label': 'H4', 'type': BLOCK_TYPES.HEADER_FOUR})
)
features.register_converter_rule('contentstate', 'h4', {
'from_database_format': {
'h4': BlockElementHandler('header-four'),
},
'to_database_format': {
'block_map': {BLOCK_TYPES.HEADER_FOUR: 'h4'}
}
})
features.register_editor_plugin(
'draftail', 'h5', draftail_features.BlockFeature({'label': 'H5', 'type': BLOCK_TYPES.HEADER_FIVE})
)
features.register_converter_rule('contentstate', 'h5', {
'from_database_format': {
'h5': BlockElementHandler('header-five'),
},
'to_database_format': {
'block_map': {BLOCK_TYPES.HEADER_FIVE: 'h5'}
}
})
features.register_editor_plugin(
'draftail', 'h6', draftail_features.BlockFeature({'label': 'H6', 'type': BLOCK_TYPES.HEADER_SIX})
)
features.register_converter_rule('contentstate', 'h6', {
'from_database_format': {
'h6': BlockElementHandler('header-six'),
},
'to_database_format': {
'block_map': {BLOCK_TYPES.HEADER_SIX: 'h6'}
}
})
features.register_editor_plugin(
'draftail', 'ul', draftail_features.BlockFeature({
'label': 'UL', 'type': BLOCK_TYPES.UNORDERED_LIST_ITEM, 'icon': 'icon-list-ul'
})
)
features.register_converter_rule('contentstate', 'ul', {
'from_database_format': {
'ul': ListElementHandler('unordered-list-item'),
'li': ListItemElementHandler(),
},
'to_database_format': {
'block_map': {BLOCK_TYPES.UNORDERED_LIST_ITEM: {'element': 'li', 'wrapper': 'ul'}}
}
})
features.register_editor_plugin(
'draftail', 'ol', draftail_features.BlockFeature({
'label': 'OL', 'type': BLOCK_TYPES.ORDERED_LIST_ITEM, 'icon': 'icon-list-ol'
})
)
features.register_converter_rule('contentstate', 'ol', {
'from_database_format': {
'ol': ListElementHandler('ordered-list-item'),
'li': ListItemElementHandler(),
},
'to_database_format': {
'block_map': {BLOCK_TYPES.ORDERED_LIST_ITEM: {'element': 'li', 'wrapper': 'ol'}}
}
})
features.register_editor_plugin(
'draftail', 'bold', draftail_features.InlineStyleFeature({
'label': 'Bold', 'type': INLINE_STYLES.BOLD, 'icon': 'icon-bold'
})
)
features.register_converter_rule('contentstate', 'bold', {
'from_database_format': {
'b': InlineStyleElementHandler('BOLD'),
'strong': InlineStyleElementHandler('BOLD'),
},
'to_database_format': {
'style_map': {INLINE_STYLES.BOLD: 'b'}
}
})
features.register_editor_plugin(
'draftail', 'italic', draftail_features.InlineStyleFeature({
'label': 'Italic', 'type': INLINE_STYLES.ITALIC, 'icon': 'icon-italic'
})
)
features.register_converter_rule('contentstate', 'italic', {
'from_database_format': {
'i': InlineStyleElementHandler('ITALIC'),
'em': InlineStyleElementHandler('ITALIC'),
},
'to_database_format': {
'style_map': {INLINE_STYLES.ITALIC: 'i'}
}
})
features.register_editor_plugin(
'draftail', 'link', draftail_features.EntityFeature({
@ -310,27 +404,12 @@ def register_core_features(features):
'decorator': 'Link',
})
)
features.register_editor_plugin(
'draftail', 'document-link', draftail_features.EntityFeature({
'label': 'Document',
'type': ENTITY_TYPES.DOCUMENT,
'icon': 'icon-doc-full',
'source': 'DocumentSource',
'decorator': 'Document',
})
)
features.register_editor_plugin(
'draftail', 'image', draftail_features.ImageFeature()
)
features.register_editor_plugin(
'draftail', 'embed', draftail_features.EntityFeature({
'label': 'Embed',
'type': ENTITY_TYPES.EMBED,
'icon': 'icon-media',
'source': 'EmbedSource',
'decorator': 'Embed',
})
)
features.register_converter_rule('contentstate', 'link', {
'from_database_format': {
'a[href]': ExternalLinkElementHandler('LINK'),
'a[linktype="page"]': PageLinkElementHandler('LINK'),
},
'to_database_format': {
'entity_decorators': {ENTITY_TYPES.LINK: Link}
}
})

Wyświetl plik

@ -7,9 +7,14 @@ from django.utils.html import format_html, format_html_join
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext
from draftjs_exporter.constants import ENTITY_TYPES
from wagtail.admin.menu import MenuItem
from wagtail.admin.rich_text import HalloPlugin
from wagtail.admin.rich_text.converters.contentstate import Document as DocumentEntity
from wagtail.admin.rich_text.converters.editor_html import LinkTypeRule
from wagtail.admin.rich_text.converters.html_to_contentstate import DocumentLinkElementHandler
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.search import SearchArea
from wagtail.admin.site_summary import SummaryItem
from wagtail.core import hooks
@ -75,6 +80,7 @@ def editor_js():
@hooks.register('register_rich_text_features')
def register_document_feature(features):
features.register_link_type('document', document_linktype_handler)
features.register_editor_plugin(
'hallo', 'document-link',
HalloPlugin(
@ -82,9 +88,28 @@ def register_document_feature(features):
js=['wagtaildocs/js/hallo-plugins/hallo-wagtaildoclink.js'],
)
)
features.register_editor_plugin(
'draftail', 'document-link', draftail_features.EntityFeature({
'label': 'Document',
'type': ENTITY_TYPES.DOCUMENT,
'icon': 'icon-doc-full',
'source': 'DocumentSource',
'decorator': 'Document',
})
)
features.register_converter_rule('editorhtml', 'document-link', [
LinkTypeRule('document', DocumentLinkHandler),
])
features.register_converter_rule('contentstate', 'document-link', {
'from_database_format': {
'a[linktype="document"]': DocumentLinkElementHandler('DOCUMENT'),
},
'to_database_format': {
'entity_decorators': {ENTITY_TYPES.DOCUMENT: DocumentEntity}
}
})
features.default_features.append('document-link')

Wyświetl plik

@ -2,8 +2,13 @@ from django.conf.urls import include, url
from django.urls import reverse
from django.utils.html import format_html
from draftjs_exporter.constants import ENTITY_TYPES
from wagtail.admin.rich_text import HalloPlugin
from wagtail.admin.rich_text.converters.contentstate import Embed as EmbedEntity
from wagtail.admin.rich_text.converters.editor_html import EmbedTypeRule
from wagtail.admin.rich_text.converters.html_to_contentstate import MediaEmbedElementHandler
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.core import hooks
from wagtail.embeds import urls
from wagtail.embeds.rich_text import MediaEmbedHandler, media_embedtype_handler
@ -48,5 +53,27 @@ def register_embed_feature(features):
EmbedTypeRule('media', MediaEmbedHandler)
])
# define a draftail plugin to use when the 'embed' feature is active
features.register_editor_plugin(
'draftail', 'embed', draftail_features.EntityFeature({
'label': 'Embed',
'type': ENTITY_TYPES.EMBED,
'icon': 'icon-media',
'source': 'EmbedSource',
'decorator': 'Embed',
})
)
# define how to convert between contentstate's representation of embeds and
# the database representation
features.register_converter_rule('contentstate', 'embed', {
'from_database_format': {
'embed[embedtype="media"]': MediaEmbedElementHandler(),
},
'to_database_format': {
'entity_decorators': {ENTITY_TYPES.EMBED: EmbedEntity}
}
})
# add 'embed' to the set of on-by-default rich text features
features.default_features.append('embed')

Wyświetl plik

@ -5,9 +5,14 @@ from django.utils.html import format_html, format_html_join
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext
from draftjs_exporter.constants import ENTITY_TYPES
from wagtail.admin.menu import MenuItem
from wagtail.admin.rich_text import HalloPlugin
from wagtail.admin.rich_text.converters.contentstate import Image as ImageEntity
from wagtail.admin.rich_text.converters.editor_html import EmbedTypeRule
from wagtail.admin.rich_text.converters.html_to_contentstate import ImageElementHandler
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.search import SearchArea
from wagtail.admin.site_summary import SummaryItem
from wagtail.core import hooks
@ -84,6 +89,22 @@ def register_image_feature(features):
EmbedTypeRule('image', ImageEmbedHandler)
])
# define a draftail plugin to use when the 'image' feature is active
features.register_editor_plugin(
'draftail', 'image', draftail_features.ImageFeature()
)
# define how to convert between contentstate's representation of images and
# the database representation
features.register_converter_rule('contentstate', 'image', {
'from_database_format': {
'embed[embedtype="image"]': ImageElementHandler(),
},
'to_database_format': {
'entity_decorators': {ENTITY_TYPES.IMAGE: ImageEntity}
}
})
# add 'image' to the set of on-by-default rich text features
features.default_features.append('image')