All notable changes to this project will be documented in this file. This project adheres to Semantic Versioning.
- Add tentative support for Python 3.11.
- Add new "string_compat" engine for maximum output stability, with identical output to its first release. To use it, set the
engine
property to'engine': DOM.STRING_COMPAT,
(#138).
- Remove support for Python 3.6.
Python 3.6 is no longer supported, as it has reached its end of life. For projects needing Python 3.6, please keep using v4.1.2 of the exporter.
- Add tentative support for Python 3.10.
- Stop using
extras_require
for development-only dependencies.
- Add support for Python 3.9 (#134).
- Update html5lib upper bound, now defined as
html5lib>=0.999,<2
, to ensure compatibility with Python 3.10 (#134).
This release contains breaking changes. Be sure to check out the "how to upgrade" section below.
- Remove support for Python 3.5 (#129)
- Remove HTML attributes alphabetical sorting of default string engine (#129)
- Disable single and double quotes escaping outside of attributes for string engine (#129)
- Stop sorting inline styles alphabetically (#129)
Do not upgrade to this version if you are using the exporter in Python 3.5. Please keep using v3.0.1 of the exporter.
The default string
engine no longer sorts attributes alphabetically by name in its output HTML. This makes it possible to control the order as needed, wherever attributes can be specified:
def image(props):
return DOM.create_element('img', {
'src': props.get('src'),
'width': props.get('width'),
'height': props.get('height'),
'alt': props.get('alt'),
})
If you relied on this behavior, you can either reorder your props
/ wrapper_props
/ create_element
calls as needed, or subclass the built-in string
engine and override its render_attrs
method to add back the attrs.sort
:
@staticmethod
def render_attrs(attr: Attr) -> str:
attrs = [f' {k}="{escape(v)}"' for k, v in attr.items()]
attrs.sort()
return "".join(attrs)
The default string
engine no longer escapes single and double quotes in HTML content (it still escapes quotes inside attributes). If you relied on this behavior, subclass the built-in string
engine and override its render_children
method to add back quote=True
:
@staticmethod
def render_children(children: Sequence[Union[HTML, Elt]]) -> HTML:
return "".join(
[
DOMString.render(c)
if isinstance(c, Elt)
else escape(c, quote=True)
for c in children
]
)
The exporter supports passing the style
attribute as a dictionary with JS attributes for style properties, and will automatically convert it to a string. The properties are no longer sorted alphabetically – it’s now possible to reorder the dictionary’s keys to change the order.
If you relied on this behavior, either reorder the keys as needed, or pass the style
as a string (with CSS properties syntax).
- Add
Typing :: Typed
trove classifier to the package.
- Small performance improvements (1.5x faster) for blocks that do not have inline styles, and configurations that only use
\n -> <br/>
composite decorators. (#127)
This release contains breaking changes. Be sure to check out the "how to upgrade" section below.
- Remove support for Python 2.7 and 3.4 (#111, #120).
- Add support for Python 3.8.
- Small performance improvements by using lists’ mutable
.sort()
instead ofsorted()
, which is a bit faster. (±2% faster) (#120).
- Add PEP-484 type annotations for the project’s public APIs (#101, #123).
- Add PEP-561 metadata so the exporter’s type annotations can be read by type checkers (#101, #123).
- Give entity rendering components access to the current
block
,blocks
list,mutability
, and key asentity_range.key
(#91, #124).
Do not upgrade to this version if you are using the exporter in Python 2.7 or 3.4. Please keep using v2.1.7 of the exporter.
If you are using the exporter in a codebase using type annotations and a type checker, there is a chance the annotations added in this release will create conflicts with your project’s annotations – if there are discrepancies between the expected input/output of the exporter, or in the configuration. In this case you may need to update your project’s type annotations or stubs to match the expected types of the exporter’s public API.
If you believe there is a problem with how the public API is typed, please open a new issue.
- Minor performance improvements (10% speed-up, 30% lower memory consumption) by adding Python
__slots__
and implementing other optimisations.
- Assume same block defaults as Draft.js would when attributes are missing: depth = 0, type = unstyled, no entities, no styles (#110, thanks to @tpict).
- Minor performance improvements for text-only blocks (#112).
- Minor performance improvements (8% speed-up, 20% lower memory consumption) (#108)
- Fix export bug with adjacent entities - the exporter moved their contents outside of the entities' markup (#106, #107). Thanks to @ericpai for reporting this.
- Increase lower bound of optional lxml dependency to v4.2.0 to guarantee Python 3.7 support (#88).
- Add upper bound to lxml dependency, now defined as
lxml>=3.6.0,<5
(#75). - Update html5lib upper bound, now defined as
html5lib>=0.999,<=1.0.1
.
- Give block rendering components access to the current
block
, when the component is rendered for a block, and theblocks
list (#90). - Give text decorators renderers access to the current
block
andblocks
list (#90). - Give style rendering components access to the current
block
,blocks
list, and current style type asinline_style_range.style
(#87, #90).
- Performance improvements for text-only (no inline styles, no entities) blocks (#89).
This release contains breaking changes that will require updating the exporter's configurations. Be sure to check out the "how to upgrade" section below.
- Change default DOM engine to
DOMString
(#79, #85). - Add extra install for html5lib (#79, #85).
- Remove support for class-based decorators (#73, #84).
- Switch composite decorators to dict format like that of Draft.js, with
strategy
andcomponent
attributes. - Use dotted-path loading for custom engines (#64, #81).
- Use dotted-path loading for built-in engines.
- Raise
ImportError
when loading an engine fails, notConfigException
.
- Calls to
DOM.use
must use a valid engine, there is no default value anymore. - Stop supporting passing an engine class directly in the
engine
option, or toDOM.use
. - Stop including tests in published package.
- Stop loading html5lib engine on every use, even if unused (#80).
The specificities of the new engine are described in the documentation. To start using the new default,
- Remove the
engine
property from the exporter configuration, or do'engine': DOM.STRING,
. - You can also remove the
html5lib
andbeautifulsoup4
dependencies from your project if they aren't used anywhere else.
To keep using the previous default, html5lib:
- Set the
engine
property to'engine': DOM.HTML5LIB,
. - Make sure you install the exporter with
pip install draftjs_exporter[html5lib]
.
Decorator components now require the function syntax (see the relevant documentation).
# Before:
class OrderedList:
def render(self, props):
depth = props['block']['depth']
return DOM.create_element('ol', {
'class': f'list--depth-{depth}'
}, props['children'])
# After:
def ordered_list(props):
depth = props['block']['depth']
return DOM.create_element('ol', {
'class': f'list--depth-{depth}'
}, props['children'])
If you were relying on the configuration capabilities of the class API, switch to composing components instead:
# Before:
class Link:
def __init__(self, use_new_window=False):
self.use_new_window = use_new_window
def render(self, props):
link_props = {
'href': props['url'],
}
if self.use_new_window:
link_props['target'] = '_blank'
link_props['rel'] = 'noreferrer noopener'
return DOM.create_element('a', link_props, props['children'])
# In the config:
ENTITY_TYPES.LINK: Link(use_new_window=True)
# After:
def link(props):
return DOM.create_element('a', props, props['children'])
def same_window_link(props):
return DOM.create_element(link, {
'href': props['url'],
}, props['children'])
})
def new_window_link(props):
return DOM.create_element(link, {
'href': props['url'],
'target': '_blank',
'rel': 'noreferrer noopener',
}, props['children'])
})
The composite decorators API now looks closer to that of other decorators, and to Draft.js:
# Before:
class BR:
SEARCH_RE = re.compile(r'\n')
def render(self, props):
if props['block']['type'] == BLOCK_TYPES.CODE:
return props['children']
return DOM.create_element('br')
'composite_decorators': [
BR,
]
# After:
def br(props):
if props['block']['type'] == BLOCK_TYPES.CODE:
return props['children']
return DOM.create_element('br')
# In the config:
'composite_decorators': [
{
'strategy': re.compile(r'\n'),
'component': br,
},
],
# The `engine` field in the exporter config now has to be a dotted path string pointing to a valid engine.
- 'engine': 'html5lib',
+ 'engine': 'draftjs_exporter.engines.html5lib.DOM_HTML5LIB',
# Or, using the shorthand.
+ 'engine': DOM.HTML5LIB,
# It's not possible either to directly provide an engine implementation - use a dotted path instead.
- DOM.use(DOMTestImpl)
+ DOM.use('tests.test_dom.DOMTestImpl')
- Fix string engine incorrectly skipping identical elements at the same depth level (#83).
- Add new string-based dependency-free DOM backing engine, with much better performance, thanks to the expertise of @BertrandBordage (#77).
- Pre-compile regexes in html5lib engine for performance improvements (#76).
There is no need to make any changes to keep using the previous engines (html5lib, lxml). To switch to the new string engine, opt-in via the config:
exporter = HTML({
+ # Specify which DOM backing engine to use.
+ 'engine': 'string',
})
The new engine is faster than both html5lib
and lxml
, and outputs a functionally identical HTML (see a list of all known engine differences at test_engine_differences.py
). Its only drawback is that when using the DOM.parse_html()
no safeguards are provided against malformed or unescaped HTML, whereas lxml or html5lib sanitise the input.
This release is functionally identical to the previous one,
v0.9.0
.
The project has reached a high-enough level of stability to be used in production, and breaking changes will now be reflected via major version changes.
- Add configuration options to determine handling of missing blocks #52.
- Add configuration options to determine handling of missing styles.
- Add configuration options to determine handling of missing entities.
- Block components now have access to the block type via
props['block']['type']
. - Entity components now have access to the entity type via
props['entity']['type']
. - Composite decorators now have access to the current block depth and data via
props['block']['depth']
,props['block']['data']
. - Allow discarding component children by returning
None
inrender
. - Add support for
lxml
as a DOM backing engine, withpip install draftjs_exporter[lxml]
pip extra. - Add support for custom DOM backing engines.
- Add support for None content state in HTML.render #67.
- For composite decorators, the block type has moved from
props['block_type']
toprops['block']['type']
. - Move
ConfigException
todraftjs_exporter.error
.
- Remove
DOM.get_children
method. - Remove
DOM.pretty_print
method. - Remove automatic conversion from
className
prop toclass
.
- Stop rendering decorators when there is no text to decorate.
- Remove extra HTML serialisation steps.
# Change composite decorators block type access
- props['block_type']
+ props['block']['type']
# Stop using DOM.get_children directly.
- DOM.get_children()
# Stop using DOM.pretty_print directly.
- DOM.pretty_print()
# Move `ConfigException` to `draftjs_exporter.error`.
- from draftjs_exporter.options import ConfigException
+ from draftjs_exporter.error import ConfigException
# Remove automatic conversion from `className` prop to `class` attribute.
- BLOCK_TYPES.BLOCKQUOTE: ['blockquote', {'className': 'c-pullquote'}]
+ BLOCK_TYPES.BLOCKQUOTE: ['blockquote', {'class': 'c-pullquote'}]
- Fix KeyError when the content state is empty.
- Add simplified block mapping format:
BLOCK_TYPES.HEADER_TWO: 'h2'
. - Raise exception when
style_map
does not define anelement
for the style. - Add support for any props on
style_map
. - Automatically convert
style
prop from a dict of camelCase properties to a string, on all elements (ifstyle
is already a string, it will be output as is). - Support components (
render
function returningcreate_element
nodes) instyle_map
. - Add more defaults in the style map:
BOLD = 'strong'
CODE = 'code'
ITALIC = 'em'
UNDERLINE = 'u'
STRIKETHROUGH = 's'
SUPERSCRIPT = 'sup'
SUBSCRIPT = 'sub'
MARK = 'mark'
QUOTATION = 'q'
SMALL = 'small'
SAMPLE = 'samp'
INSERT = 'ins'
DELETE = 'del'
KEYBOARD = 'kbd'
- Add new
pre
block type. - Support components (
render
function returningcreate_element
nodes) inblock_map
, for bothelement
andwrapper
.
- Remove array-style block element and wrapper declarations (
['ul']
,['ul', {'class': 'bullet-list'}]
). - Remove
DOM.create_text_node
method.
- Replace array-style mapping declarations of block element and wrapper props with
props
andwrapper_props
attributes (dictionaries of props). - Moved and renamed
BlockException
toConfigException
. - Replace
style_map
config format to the one of theblock_map
. - Move internal
camel_to_dash
method toDOM
for official use. - Change ordering of inline styles - now using alphabetical ordering of style key instead of tag name.
STRIKETHROUGH
styles in default style map now map tos
tag.UNDERLINE
styles in default style map now map tou
tag.- By default,
code-block
blocks are now rendered inside a combination ofpre
andcode
tags. - For entities, directly pass
data
dict as props instead of whole entity map declaration.
- Fix block ordering with block components and wrapper. Fix #55.
# Change element-only block declarations:
- BLOCK_TYPES.HEADER_TWO: {'element': 'h2'},
+ BLOCK_TYPES.HEADER_TWO: 'h2',
# Change array-style block declarations:
- BLOCK_TYPES.BLOCKQUOTE: ['blockquote', {'class': 'c-pullquote'}]
+ BLOCK_TYPES.BLOCKQUOTE: {'element': 'blockquote', 'props': {'class': 'c-pullquote'}}
# Change block wrapper declarations:
- 'wrapper': ['ul', {'class': 'bullet-list'}],
+ 'wrapper': 'ul',
+ 'wrapper_props': {'class': 'bullet-list'},
# Change location and name of exceptions:
- from draftjs_exporter.wrapper_state import BlockException
+ from draftjs_exporter.options import ConfigException
# Change element-only style declarations:
- 'KBD': {'element': 'kbd'},
+ 'KBD': 'kbd',
# Change object-style style declarations:
- 'HIGHLIGHT': {'element': 'strong', 'textDecoration': 'underline'},
+ 'HIGHLIGHT': {'element': 'strong', 'props': {'style': {'textDecoration': 'underline'}}},
# Create custom STRIKETHROUGH styles:
+ 'STRIKETHROUGH': {'element': 'span', 'props': {'style': {'textDecoration': 'line-through'}}},
# Create custom UNDERLINE styles:
+ 'UNDERLINE': {'element': 'span', 'props': {'style': {'textDecoration': 'underline'}}},
# New camel_to_dash location:
- from draftjs_exporter.style_state import camel_to_dash
- camel_to_dash()
+ from draftjs_exporter.dom import DOM
+ DOM.camel_to_dash()
# New default rendering for code-block:
- BLOCK_TYPES.CODE: 'pre',
+ BLOCK_TYPES.CODE: lambda props: DOM.create_element('pre', {}, DOM.create_element('code', {}, props['children'])),
# Use the new pre block to produce the previous result, or override the default for code-block.
+ BLOCK_TYPES.PRE: 'pre',
# Entities now receive the content of `data` directly, instead of the whole entity:
def render(self, props):
- data = props.get('data', {})
link_props = {
- 'href': data['url'],
+ 'href': props['url'],
}
# Remove wrapping around text items.
- DOM.create_text_node(text)
+ text
# Remove fragment calls.
- DOM.create_document_fragment()
+ DOM.create_element()
# Remove text getters and setters. This is not supported anymore.
- DOM.get_text_content(elt)
- DOM.set_text_content(elt, text)
- Add support for decorators thanks to @su27 (#16, #17).
- Add support for configurable decorators and entities.
- Add support for decorators and entities in function form.
- Stop lowercasing HTML attributes.
*ngFor
will now be exported as*ngFor
.
- Drop Python 3.3 support (likely still runs fine, but tests are not ran on it).
- Add profiling tooling thanks to @su27 (#31).
- Add more common entity types in constants (#34).
- Stop mutating entity data when rendering entities (#36).
- Automatically convert line breaks to
br
elements.
This release is likely to be a breaking change. It is not released as such because the exporter has not reached 1.0 yet.
- Change
hr
rendering to be done with entities instead of block types. Instead of having aTOKEN
entity rendering asNull
inside ahorizontal-rule
block rendering ashr
, we now have aHORIZONTAL_RULE
entitiy rendering asHR
inside anatomic
block rendering asfragment
.
- Remove custom block type
pullquote
- Fix state being kept between exports, causing blocks to be duplicated in re-runs.
- Fix broken link in README
This release is likely to be a breaking change. It is not released as such because the exporter has not reached 1.0 yet.
- Add support for more scenarios with nested blocks. Jumping depths eg. 0, 2, 3. Starting directly above 0 eg. 2, 2, 0. Not using 0 at all eg. 3, 3, 3.
- Entity decorators now have complete control on where their content (markup, not just text) is inserted into the DOM. This is done via the
children
prop in a similar fashion to React's.
- Built-in entities are no longer available as part of the library. They should be defined in userland.
This release is likely to be a breaking change. It is not released as such because the exporter has not reached 1.0 yet.
- Now using Beautiful Soup 4 and the html5lib parser instead of lxml.
- Entities are now available from
draftjs_exporter.entities
instead ofdraftjs_exporter.entities.<entity>
- Support for simpler
wrapper
options definition:{'unordered-list-item' : { 'element': 'li', 'wrapper': 'ul'}}
- Support for options definition for every element, not just wrappers:
{'header-two' : { 'element': ['h2', {'class': 'c-amazing-heading'}]}}
- Support for None in the children of an element in
DOM.create_element
, for conditional rendering like what React does. - Support for entity class in
DOM.create_element
.
- Fix behavior of wrapper stack in nested wrappers (#15)
Last release before switching to BeautifulSoup4 / html5lib. If we ever need to switch back to lxml, it should be as simple as looking at the code at v0.3.3.
- Add wrapper method to create new elements.
- Add wrapper method to retrieve an element's list of classes.
- Fix exporter crashing on empty blocks (renders empty string instead)
- Use HTML parser instead of XML for DOM API
- Automatic conversion of entity data to HTML attributes (int & boolean to string,
class
toclass
). - Default, extensible block & inline style maps for common HTML elements.
- React-like API to create custom entity decorators.
- DOM API to abstract HTML building code.
- Dynamically generate test cases from JSON fixture
- Raise exception for undefined entity decorators
- (Breaking change) Exporter API changed to be closer to React's
- (Breaking change) Entity decorator API changed to be closer to React's
- Nested blocks backtracking creating multiple wrappers at the same depths instead of reusing existing ones (#9)
- Removed Token entity (identical as Null)
- Support for
<hr/>
tag /TOKEN
entities - Support for wrapped item nesting (arbitrary depth)
First usable release!
vx.y.z (Template: http://keepachangelog.com/)
- Something was added to the API / a new feature was introduced.