draftail-text-utils 3.0.2__tar.gz → 3.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/PKG-INFO +1 -1
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/pyproject.toml +1 -1
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/pyproject.toml.orig +1 -1
- draftail_text_utils-3.0.3/src/draftail_text_utils/__init__.py +1 -0
- draftail_text_utils-3.0.3/src/draftail_text_utils/rich_text/text_alignment.py +197 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/css/common.css +1 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/js/font_size.js +122 -65
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/js/text_alignment.js +96 -5
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/wagtail_hooks.py +3 -1
- draftail_text_utils-3.0.2/src/draftail_text_utils/__init__.py +0 -1
- draftail_text_utils-3.0.2/src/draftail_text_utils/rich_text/text_alignment.py +0 -70
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/LICENSE +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/README.md +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/apps.py +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/conf.py +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/migrations/__init__.py +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/rich_text/__init__.py +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/rich_text/base.py +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/rich_text/dynamic_link.py +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/rich_text/font_family.py +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/rich_text/font_size.py +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/rich_text/highlight_color.py +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/rich_text/text_color.py +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/rich_text/text_style.py +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/css/color.css +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/css/dynamic_link.css +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/css/font_family.css +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/css/font_size.css +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/css/highlight_color.css +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/css/page.css +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/css/text_alignment.css +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/css/text_color.css +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/js/common.js +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/js/dynamic_link.js +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/js/font_family.js +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/js/highlight_color.js +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/js/styled_link_source.js +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/js/text_color.js +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/static/draftail_text_utils/js/text_style_entity.js +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/templates/draftail_text_utils/icons/align-center.svg +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/templates/draftail_text_utils/icons/align-justify.svg +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/templates/draftail_text_utils/icons/align-left.svg +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/templates/draftail_text_utils/icons/align-right.svg +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/templates/draftail_text_utils/icons/anchor.svg +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/templates/draftail_text_utils/icons/font.svg +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/templates/draftail_text_utils/icons/highlighter.svg +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/templates/draftail_text_utils/icons/palette.svg +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/templates/draftail_text_utils/icons/text-height.svg +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/templatetags/__init__.py +0 -0
- {draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/templatetags/draftail_text_utils_tags.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.0.3"
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Draftail rich-text feature: Text alignment.
|
|
3
|
+
|
|
4
|
+
Registers:
|
|
5
|
+
- Legacy ``BlockFeature``s for old, block-type-based alignment content.
|
|
6
|
+
- A ``"text-alignment"`` ControlFeature for the toolbar button group.
|
|
7
|
+
- Converter rules that store alignment in block data, so alignment can
|
|
8
|
+
coexist with heading, list, and other default block types.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from django.utils.translation import gettext_lazy as _
|
|
12
|
+
from draftjs_exporter.defaults import render_children
|
|
13
|
+
from draftjs_exporter.dom import DOM
|
|
14
|
+
from wagtail.admin.rich_text.converters.html_to_contentstate import (
|
|
15
|
+
BLOCK_KEY_NAME,
|
|
16
|
+
Block,
|
|
17
|
+
BlockElementHandler,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from draftail_text_utils.conf import feature_enabled
|
|
21
|
+
|
|
22
|
+
from .base import (
|
|
23
|
+
control_json_script,
|
|
24
|
+
register_block_feature,
|
|
25
|
+
register_control_feature,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
ALIGNMENTS = [
|
|
30
|
+
("ALIGNMENT_LEFT", "left", "start", _("Left")),
|
|
31
|
+
("ALIGNMENT_CENTER", "center", "center", _("Center")),
|
|
32
|
+
("ALIGNMENT_RIGHT", "right", "end", _("Right")),
|
|
33
|
+
("ALIGNMENT_JUSTIFY", "justify", "justify", _("Justify")),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
ALIGNMENT_STYLES = {
|
|
37
|
+
value: style_value for _type, value, style_value, _label in ALIGNMENTS
|
|
38
|
+
}
|
|
39
|
+
STYLE_ALIGNMENTS = {
|
|
40
|
+
style_value: value for _type, value, style_value, _label in ALIGNMENTS
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
BLOCK_TAGS = [
|
|
44
|
+
("unstyled", "p"),
|
|
45
|
+
("header-one", "h1"),
|
|
46
|
+
("header-two", "h2"),
|
|
47
|
+
("header-three", "h3"),
|
|
48
|
+
("header-four", "h4"),
|
|
49
|
+
("header-five", "h5"),
|
|
50
|
+
("header-six", "h6"),
|
|
51
|
+
("blockquote", "blockquote"),
|
|
52
|
+
("code-block", "pre"),
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
LIST_TAGS = [
|
|
56
|
+
("ordered-list-item", "ol"),
|
|
57
|
+
("unordered-list-item", "ul"),
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class AlignmentBlock(Block):
|
|
62
|
+
"""A block that persists alignment in its Draft.js ``data`` field."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, typ, depth=0, key=None, alignment=None):
|
|
65
|
+
super().__init__(typ, depth, key)
|
|
66
|
+
self.data = {"alignment": alignment or "left"}
|
|
67
|
+
|
|
68
|
+
def as_dict(self):
|
|
69
|
+
return super().as_dict() | {"data": self.data}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class AlignmentHandler(BlockElementHandler):
|
|
73
|
+
"""Database HTML to ContentState, preserving the original block type."""
|
|
74
|
+
|
|
75
|
+
def __init__(self, block_type=None):
|
|
76
|
+
self.block_type = block_type
|
|
77
|
+
|
|
78
|
+
def create_block(self, name, attrs, state, contentstate):
|
|
79
|
+
block_type = self.block_type or state.list_item_type or "unstyled"
|
|
80
|
+
style_value = attrs.get("data-dtu-text-align", "start")
|
|
81
|
+
alignment = STYLE_ALIGNMENTS.get(style_value, "left")
|
|
82
|
+
|
|
83
|
+
return AlignmentBlock(
|
|
84
|
+
block_type,
|
|
85
|
+
depth=state.list_depth,
|
|
86
|
+
key=attrs.get(BLOCK_KEY_NAME),
|
|
87
|
+
alignment=alignment,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _alignment_element(tag_name):
|
|
92
|
+
"""
|
|
93
|
+
Build a Draft.js exporter element function for ``tag_name``.
|
|
94
|
+
|
|
95
|
+
Alignment is read from block data and only added to the output when
|
|
96
|
+
present, so unaligned blocks keep their normal database representation.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def element(props):
|
|
100
|
+
block = props.get("block") or {}
|
|
101
|
+
data = block.get("data") or {}
|
|
102
|
+
alignment = data.get("alignment")
|
|
103
|
+
|
|
104
|
+
attrs = {}
|
|
105
|
+
if alignment:
|
|
106
|
+
style_value = ALIGNMENT_STYLES.get(alignment)
|
|
107
|
+
if style_value:
|
|
108
|
+
attrs["class"] = "dtu-block_text-align"
|
|
109
|
+
attrs["data-dtu-text-align"] = style_value
|
|
110
|
+
|
|
111
|
+
return DOM.create_element(tag_name, attrs, render_children(props))
|
|
112
|
+
|
|
113
|
+
element.__name__ = f"text_alignment_{tag_name}"
|
|
114
|
+
element.__repr__ = lambda _self: f"text_alignment_{tag_name}"
|
|
115
|
+
return element
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _alignment_converter_rule():
|
|
119
|
+
block_map = {}
|
|
120
|
+
from_database_format = {}
|
|
121
|
+
|
|
122
|
+
for block_type, tag_name in BLOCK_TAGS:
|
|
123
|
+
block_map[block_type] = {"element": _alignment_element(tag_name)}
|
|
124
|
+
for _type, _value, style_value, _label in ALIGNMENTS:
|
|
125
|
+
from_database_format[f"{tag_name}[data-dtu-text-align='{style_value}']"] = (
|
|
126
|
+
AlignmentHandler(block_type)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
for block_type, wrapper in LIST_TAGS:
|
|
130
|
+
block_map[block_type] = {
|
|
131
|
+
"element": _alignment_element("li"),
|
|
132
|
+
"wrapper": wrapper,
|
|
133
|
+
}
|
|
134
|
+
for _type, _value, style_value, _label in ALIGNMENTS:
|
|
135
|
+
from_database_format[f"li[data-dtu-text-align='{style_value}']"] = (
|
|
136
|
+
AlignmentHandler()
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
"from_database_format": from_database_format,
|
|
141
|
+
"to_database_format": {"block_map": block_map},
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def register(features):
|
|
146
|
+
if not feature_enabled("TEXT_ALIGNMENT"):
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
# Keep parsing and re-saving old alignment content, which used block types
|
|
150
|
+
# and a div wrapper. Scope the selectors to ``div`` so they cannot match
|
|
151
|
+
# the new, alignment-preserving heading/list elements.
|
|
152
|
+
for type_, _value, style_value, _label in ALIGNMENTS:
|
|
153
|
+
register_block_feature(
|
|
154
|
+
features,
|
|
155
|
+
type_,
|
|
156
|
+
{},
|
|
157
|
+
css_selector=f"div.dtu-block_text-align[data-dtu-text-align='{style_value}']",
|
|
158
|
+
props={
|
|
159
|
+
"class": "dtu-block_text-align",
|
|
160
|
+
"data-dtu-text-align": style_value,
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
register_control_feature(
|
|
165
|
+
features,
|
|
166
|
+
"text-alignment",
|
|
167
|
+
js=["draftail_text_utils/js/text_alignment.js"],
|
|
168
|
+
css={"all": ("draftail_text_utils/css/text_alignment.css",)},
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
features.register_converter_rule(
|
|
172
|
+
"contentstate",
|
|
173
|
+
"text-alignment",
|
|
174
|
+
_alignment_converter_rule(),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def control():
|
|
179
|
+
if not feature_enabled("TEXT_ALIGNMENT"):
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
return control_json_script(
|
|
183
|
+
type_="text-alignment",
|
|
184
|
+
label="Align Text",
|
|
185
|
+
description="Change text alignment",
|
|
186
|
+
icon="align-left",
|
|
187
|
+
data=[
|
|
188
|
+
{
|
|
189
|
+
"type": type_,
|
|
190
|
+
"value": value,
|
|
191
|
+
"label": label,
|
|
192
|
+
"style": {"textAlign": style_value},
|
|
193
|
+
"icon": f"align-{value}",
|
|
194
|
+
}
|
|
195
|
+
for type_, value, style_value, label in ALIGNMENTS
|
|
196
|
+
],
|
|
197
|
+
)
|
|
@@ -38,6 +38,12 @@
|
|
|
38
38
|
const entityType = 'TEXT_STYLE';
|
|
39
39
|
const control = dt.parseControl('font-size');
|
|
40
40
|
|
|
41
|
+
function normaliseSize(value) {
|
|
42
|
+
if (value === null || value === undefined || value === '') return '';
|
|
43
|
+
var parsed = parseInt(value, 10);
|
|
44
|
+
return isNaN(parsed) ? String(value) : String(parsed);
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
// The control component
|
|
42
48
|
var FontSizeControl = class FontSizeControl extends React.Component {
|
|
43
49
|
constructor(props) {
|
|
@@ -48,16 +54,19 @@
|
|
|
48
54
|
isInputFocused: false,
|
|
49
55
|
};
|
|
50
56
|
this.controlRef = React.createRef();
|
|
57
|
+
this.replaceOnNextDigit = false;
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
// lifecycle
|
|
54
61
|
componentDidMount() {
|
|
55
62
|
this.syncInputValue();
|
|
56
63
|
document.addEventListener('mousedown', this.handleClickOutside);
|
|
64
|
+
document.addEventListener('keydown', this.handleDocumentKeyDown, true);
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
componentWillUnmount() {
|
|
60
68
|
document.removeEventListener('mousedown', this.handleClickOutside);
|
|
69
|
+
document.removeEventListener('keydown', this.handleDocumentKeyDown, true);
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
componentDidUpdate() {
|
|
@@ -65,9 +74,9 @@
|
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
// helpers
|
|
68
|
-
getActiveSize() {
|
|
77
|
+
getActiveSize(editorState) {
|
|
69
78
|
return dt.getActiveEntityData(
|
|
70
|
-
this.props.getEditorState(),
|
|
79
|
+
editorState || this.props.getEditorState(),
|
|
71
80
|
entityType,
|
|
72
81
|
'size',
|
|
73
82
|
);
|
|
@@ -79,31 +88,33 @@
|
|
|
79
88
|
return;
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
var
|
|
83
|
-
var newValue = activeSize || '';
|
|
91
|
+
var newValue = normaliseSize(this.getActiveSize());
|
|
84
92
|
|
|
85
93
|
if (this.state.inputValue !== newValue) {
|
|
86
94
|
this.setState({ inputValue: newValue });
|
|
87
95
|
}
|
|
88
96
|
}
|
|
89
97
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
this.setState({ isDropdownOpen: willBeOpen });
|
|
98
|
+
stopEditing() {
|
|
99
|
+
this.replaceOnNextDigit = false;
|
|
100
|
+
this.setState({ isDropdownOpen: false, isInputFocused: false }, () => {
|
|
101
|
+
this.syncInputValue();
|
|
102
|
+
});
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
applyFontSize(size) {
|
|
100
106
|
var editorState = dt.restoreSelection(this.props.getEditorState());
|
|
101
|
-
var currentSize = this.getActiveSize(editorState);
|
|
107
|
+
var currentSize = normaliseSize(this.getActiveSize(editorState));
|
|
102
108
|
|
|
103
|
-
if (size === currentSize) {
|
|
109
|
+
if (String(size) === currentSize) {
|
|
104
110
|
editorState = dt.removeStyleProperty(editorState, entityType, 'size');
|
|
105
111
|
this.props.onChange(editorState);
|
|
106
|
-
this.
|
|
112
|
+
this.replaceOnNextDigit = false;
|
|
113
|
+
this.setState({
|
|
114
|
+
isDropdownOpen: false,
|
|
115
|
+
isInputFocused: false,
|
|
116
|
+
inputValue: '',
|
|
117
|
+
});
|
|
107
118
|
this.syncInputValue();
|
|
108
119
|
return;
|
|
109
120
|
}
|
|
@@ -112,82 +123,132 @@
|
|
|
112
123
|
this.props.onChange(newContent);
|
|
113
124
|
|
|
114
125
|
// Re-sync the input value
|
|
115
|
-
this.
|
|
126
|
+
this.replaceOnNextDigit = false;
|
|
127
|
+
this.setState({
|
|
128
|
+
isDropdownOpen: false,
|
|
129
|
+
isInputFocused: false,
|
|
130
|
+
inputValue: String(size),
|
|
131
|
+
});
|
|
116
132
|
this.syncInputValue();
|
|
117
133
|
}
|
|
118
134
|
|
|
119
135
|
increment() {
|
|
120
|
-
var
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
136
|
+
var value =
|
|
137
|
+
this.state.isInputFocused && this.state.inputValue
|
|
138
|
+
? this.state.inputValue
|
|
139
|
+
: this.getActiveSize();
|
|
140
|
+
var currentSize = isNaN(value || 'NaN') ? 16 : parseInt(value);
|
|
124
141
|
this.applyFontSize(Math.min(MAX_FONT_SIZE, currentSize + FONT_SIZE_STEP));
|
|
125
142
|
}
|
|
126
143
|
|
|
127
144
|
decrement() {
|
|
128
|
-
var
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
145
|
+
var value =
|
|
146
|
+
this.state.isInputFocused && this.state.inputValue
|
|
147
|
+
? this.state.inputValue
|
|
148
|
+
: this.getActiveSize();
|
|
149
|
+
var currentSize = isNaN(value || 'NaN') ? 16 : parseInt(value);
|
|
132
150
|
this.applyFontSize(Math.max(MIN_FONT_SIZE, currentSize - FONT_SIZE_STEP));
|
|
133
151
|
}
|
|
134
152
|
|
|
135
153
|
// event handlers
|
|
136
154
|
handleInputMouseDown = (e) => {
|
|
137
|
-
|
|
155
|
+
// Do not focus the input: that would blur the editor, clear Draftail's
|
|
156
|
+
// selection focus, and unmount the floating toolbar. Keep the editor
|
|
157
|
+
// focused and route keyboard input to the control instead.
|
|
158
|
+
e.preventDefault();
|
|
159
|
+
dt.saveSelection(this.props.getEditorState);
|
|
160
|
+
this.replaceOnNextDigit = true;
|
|
161
|
+
this.setState({ isDropdownOpen: true, isInputFocused: true });
|
|
138
162
|
};
|
|
139
163
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
164
|
+
handleDocumentKeyDown = (e) => {
|
|
165
|
+
if (!this.state.isInputFocused) return;
|
|
166
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
167
|
+
|
|
168
|
+
if (/^[0-9]$/.test(e.key)) {
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
e.stopPropagation();
|
|
171
|
+
var nextValue = this.replaceOnNextDigit
|
|
172
|
+
? e.key
|
|
173
|
+
: (this.state.inputValue || '') + e.key;
|
|
174
|
+
this.replaceOnNextDigit = false;
|
|
175
|
+
this.setState({ inputValue: nextValue });
|
|
176
|
+
return;
|
|
145
177
|
}
|
|
146
|
-
};
|
|
147
178
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this.
|
|
152
|
-
|
|
153
|
-
|
|
179
|
+
if (e.key === 'Backspace') {
|
|
180
|
+
e.preventDefault();
|
|
181
|
+
e.stopPropagation();
|
|
182
|
+
this.replaceOnNextDigit = false;
|
|
183
|
+
this.setState({
|
|
184
|
+
inputValue: (this.state.inputValue || '').slice(0, -1),
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
154
188
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
189
|
+
if (e.key === 'ArrowUp') {
|
|
190
|
+
e.preventDefault();
|
|
191
|
+
e.stopPropagation();
|
|
192
|
+
this.increment();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
158
195
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
this.
|
|
196
|
+
if (e.key === 'ArrowDown') {
|
|
197
|
+
e.preventDefault();
|
|
198
|
+
e.stopPropagation();
|
|
199
|
+
this.decrement();
|
|
163
200
|
return;
|
|
164
201
|
}
|
|
165
202
|
|
|
166
|
-
if (
|
|
203
|
+
if (e.key === 'Enter') {
|
|
167
204
|
e.preventDefault();
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
return;
|
|
205
|
+
e.stopPropagation();
|
|
206
|
+
var size = parseInt(this.state.inputValue, 10);
|
|
207
|
+
if (size >= MIN_FONT_SIZE && size <= MAX_FONT_SIZE) {
|
|
208
|
+
this.applyFontSize(size);
|
|
209
|
+
} else {
|
|
210
|
+
this.stopEditing();
|
|
176
211
|
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
177
214
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
215
|
+
if (e.key === 'Escape') {
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
e.stopPropagation();
|
|
218
|
+
this.stopEditing();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
182
221
|
|
|
183
|
-
|
|
222
|
+
if (e.key === 'Tab') {
|
|
223
|
+
this.stopEditing();
|
|
184
224
|
return;
|
|
185
225
|
}
|
|
226
|
+
|
|
227
|
+
// Keep all other typing/navigation out of the editor while the control
|
|
228
|
+
// is capturing input.
|
|
229
|
+
if (
|
|
230
|
+
e.key.length === 1 ||
|
|
231
|
+
[
|
|
232
|
+
'Delete',
|
|
233
|
+
'Home',
|
|
234
|
+
'End',
|
|
235
|
+
'PageUp',
|
|
236
|
+
'PageDown',
|
|
237
|
+
'ArrowLeft',
|
|
238
|
+
'ArrowRight',
|
|
239
|
+
].includes(e.key)
|
|
240
|
+
) {
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
e.stopPropagation();
|
|
243
|
+
}
|
|
186
244
|
};
|
|
187
245
|
|
|
188
246
|
handleClickOutside = (event) => {
|
|
189
247
|
if (dt.clickOutsideGuard(this.controlRef, event)) {
|
|
190
|
-
this.
|
|
248
|
+
this.replaceOnNextDigit = false;
|
|
249
|
+
this.setState({ isDropdownOpen: false, isInputFocused: false }, () => {
|
|
250
|
+
this.syncInputValue();
|
|
251
|
+
});
|
|
191
252
|
}
|
|
192
253
|
};
|
|
193
254
|
|
|
@@ -201,7 +262,8 @@
|
|
|
201
262
|
this.applyFontSize(opt.size);
|
|
202
263
|
},
|
|
203
264
|
'className': 'Draftail--dtu-option Draftail--font-size-option',
|
|
204
|
-
'aria-selected':
|
|
265
|
+
'aria-selected':
|
|
266
|
+
normaliseSize(this.state.inputValue) === String(opt.size),
|
|
205
267
|
},
|
|
206
268
|
opt.label,
|
|
207
269
|
);
|
|
@@ -209,7 +271,6 @@
|
|
|
209
271
|
|
|
210
272
|
// render
|
|
211
273
|
render() {
|
|
212
|
-
var activeSize = this.getActiveSize();
|
|
213
274
|
var inputDisplay = this.state.inputValue;
|
|
214
275
|
var isDropdownExpanded = this.state.isDropdownOpen;
|
|
215
276
|
var dropdown = React.createElement(
|
|
@@ -256,14 +317,10 @@
|
|
|
256
317
|
id: 'Draftail--font-size-input',
|
|
257
318
|
className: 'Draftail--font-size-input',
|
|
258
319
|
type: 'text',
|
|
259
|
-
defaultValue: activeSize || '',
|
|
260
320
|
value: inputDisplay,
|
|
261
321
|
placeholder: 'Font Size',
|
|
322
|
+
tabIndex: -1,
|
|
262
323
|
onMouseDown: this.handleInputMouseDown,
|
|
263
|
-
onFocus: this.handleInputFocus,
|
|
264
|
-
onBlur: this.handleInputBlur,
|
|
265
|
-
onChange: this.handleInputChange,
|
|
266
|
-
onKeyDown: this.handleInputKeyDown,
|
|
267
324
|
}),
|
|
268
325
|
);
|
|
269
326
|
|
|
@@ -12,13 +12,21 @@
|
|
|
12
12
|
|
|
13
13
|
(function () {
|
|
14
14
|
var React = window.React;
|
|
15
|
-
var
|
|
15
|
+
var DraftJS = window.DraftJS;
|
|
16
16
|
var ToolbarButton = window.Draftail.ToolbarButton;
|
|
17
17
|
var dt = window.DraftailTextUtils;
|
|
18
18
|
var data = window.draftailTextUtils || {};
|
|
19
19
|
const control = dt.parseControl('text-alignment');
|
|
20
20
|
const options = control.data || [];
|
|
21
21
|
|
|
22
|
+
// Legacy alignment was stored as a block type instead of block data.
|
|
23
|
+
const LEGACY_ALIGNMENTS = {
|
|
24
|
+
ALIGNMENT_LEFT: 'left',
|
|
25
|
+
ALIGNMENT_CENTER: 'center',
|
|
26
|
+
ALIGNMENT_RIGHT: 'right',
|
|
27
|
+
ALIGNMENT_JUSTIFY: 'justify',
|
|
28
|
+
};
|
|
29
|
+
|
|
22
30
|
// ── Helper to find active text alignment ──
|
|
23
31
|
function getActiveOption(editorState) {
|
|
24
32
|
const selection = editorState.getSelection();
|
|
@@ -26,8 +34,60 @@
|
|
|
26
34
|
.getCurrentContent()
|
|
27
35
|
.getBlockForKey(selection.getStartKey());
|
|
28
36
|
if (!block) return null;
|
|
29
|
-
const
|
|
30
|
-
|
|
37
|
+
const alignment =
|
|
38
|
+
block.getData().get('alignment') || LEGACY_ALIGNMENTS[block.getType()];
|
|
39
|
+
return options.find((opt) => opt.value === alignment) || null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function setAlignment(editorState, alignment) {
|
|
43
|
+
const selection = editorState.getSelection();
|
|
44
|
+
const startKey = selection.getStartKey();
|
|
45
|
+
const endKey = selection.getEndKey();
|
|
46
|
+
let content = editorState.getCurrentContent();
|
|
47
|
+
let blockKey = startKey;
|
|
48
|
+
|
|
49
|
+
while (blockKey) {
|
|
50
|
+
let block = content.getBlockForKey(blockKey);
|
|
51
|
+
const blockSelection = selection.merge({
|
|
52
|
+
anchorKey: blockKey,
|
|
53
|
+
anchorOffset: 0,
|
|
54
|
+
focusKey: blockKey,
|
|
55
|
+
focusOffset: block.getLength(),
|
|
56
|
+
isBackward: false,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Migrate legacy ALIGNMENT_* blocks to a regular paragraph plus block
|
|
60
|
+
// data, so the new control can update them.
|
|
61
|
+
if (LEGACY_ALIGNMENTS[block.getType()]) {
|
|
62
|
+
content = DraftJS.Modifier.setBlockType(
|
|
63
|
+
content,
|
|
64
|
+
blockSelection,
|
|
65
|
+
'unstyled',
|
|
66
|
+
);
|
|
67
|
+
block = content.getBlockForKey(blockKey);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let blockData = block.getData();
|
|
71
|
+
if (alignment) {
|
|
72
|
+
blockData = blockData.set('alignment', alignment);
|
|
73
|
+
} else {
|
|
74
|
+
blockData = blockData.delete('alignment');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
content = DraftJS.Modifier.setBlockData(
|
|
78
|
+
content,
|
|
79
|
+
blockSelection,
|
|
80
|
+
blockData,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (blockKey === endKey) break;
|
|
84
|
+
blockKey = content.getKeyAfter(blockKey);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return DraftJS.EditorState.forceSelection(
|
|
88
|
+
DraftJS.EditorState.push(editorState, content, 'change-block-data'),
|
|
89
|
+
selection,
|
|
90
|
+
);
|
|
31
91
|
}
|
|
32
92
|
|
|
33
93
|
class TextAlignmentControl extends React.Component {
|
|
@@ -65,8 +125,10 @@
|
|
|
65
125
|
handleOptionClick = (opt) => {
|
|
66
126
|
const { getEditorState, onChange } = this.props;
|
|
67
127
|
const editorState = getEditorState();
|
|
68
|
-
const
|
|
69
|
-
|
|
128
|
+
const activeOption = getActiveOption(editorState);
|
|
129
|
+
const removing = activeOption && activeOption.type === opt.type;
|
|
130
|
+
|
|
131
|
+
onChange(setAlignment(editorState, removing ? null : opt.value));
|
|
70
132
|
this.setState({ isOpen: false });
|
|
71
133
|
};
|
|
72
134
|
|
|
@@ -127,6 +189,35 @@
|
|
|
127
189
|
}
|
|
128
190
|
}
|
|
129
191
|
|
|
192
|
+
function blockStyleFn(block) {
|
|
193
|
+
const alignment = block.getData().get('alignment');
|
|
194
|
+
if (!alignment) return null;
|
|
195
|
+
return `Draftail-block--ALIGNMENT_${alignment.toUpperCase()}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Add alignment classes to the editor blocks without changing their type.
|
|
199
|
+
if (window.draftail && window.draftail.initEditor) {
|
|
200
|
+
var originalInitEditor = window.draftail.initEditor;
|
|
201
|
+
if (!originalInitEditor.__dtuTextAlignment) {
|
|
202
|
+
var patchedInitEditor = function (selector, options, script) {
|
|
203
|
+
options = options || {};
|
|
204
|
+
options.plugins = (options.plugins || []).concat([
|
|
205
|
+
{
|
|
206
|
+
type: 'dtu-text-alignment',
|
|
207
|
+
blockStyleFn: blockStyleFn,
|
|
208
|
+
},
|
|
209
|
+
]);
|
|
210
|
+
return Reflect.apply(originalInitEditor, window.draftail, [
|
|
211
|
+
selector,
|
|
212
|
+
options,
|
|
213
|
+
script,
|
|
214
|
+
]);
|
|
215
|
+
};
|
|
216
|
+
patchedInitEditor.__dtuTextAlignment = true;
|
|
217
|
+
window.draftail.initEditor = patchedInitEditor;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
130
221
|
window.draftail.registerPlugin(
|
|
131
222
|
{
|
|
132
223
|
type: control.type,
|
{draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/wagtail_hooks.py
RENAMED
|
@@ -58,8 +58,10 @@ def register_text_style_feature(features):
|
|
|
58
58
|
text_style.register(features)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
@hooks.register("register_rich_text_features")
|
|
61
|
+
@hooks.register("register_rich_text_features", order=100)
|
|
62
62
|
def register_text_alignment_feature(features):
|
|
63
|
+
# Run after Wagtail's built-in block features so the alignment converter
|
|
64
|
+
# rule is applied after (and can therefore override) their block maps.
|
|
63
65
|
text_alignment.register(features)
|
|
64
66
|
|
|
65
67
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "3.0.2"
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Draftail rich-text feature: Text alignment.
|
|
3
|
-
|
|
4
|
-
Registers:
|
|
5
|
-
- Four ``BlockFeature``s per alignment value.
|
|
6
|
-
- A ``"text-alignment"`` ControlFeature for the toolbar button group.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from django.utils.translation import gettext_lazy as _
|
|
10
|
-
|
|
11
|
-
from draftail_text_utils.conf import feature_enabled
|
|
12
|
-
|
|
13
|
-
from .base import (
|
|
14
|
-
control_json_script,
|
|
15
|
-
register_block_feature,
|
|
16
|
-
register_control_feature,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
ALIGNMENTS = [
|
|
21
|
-
("ALIGNMENT_LEFT", "left", "start", _("Left")),
|
|
22
|
-
("ALIGNMENT_CENTER", "center", "center", _("Center")),
|
|
23
|
-
("ALIGNMENT_RIGHT", "right", "end", _("Right")),
|
|
24
|
-
("ALIGNMENT_JUSTIFY", "justify", "justify", _("Justify")),
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def register(features):
|
|
29
|
-
if not feature_enabled("TEXT_ALIGNMENT"):
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
for type_, _value, styleValue, _label in ALIGNMENTS:
|
|
33
|
-
register_block_feature(
|
|
34
|
-
features,
|
|
35
|
-
type_,
|
|
36
|
-
{},
|
|
37
|
-
css_selector=f".dtu-block_text-align[data-dtu-text-align='{styleValue}']",
|
|
38
|
-
props={
|
|
39
|
-
"class": "dtu-block_text-align",
|
|
40
|
-
"data-dtu-text-align": styleValue,
|
|
41
|
-
},
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
register_control_feature(
|
|
45
|
-
features,
|
|
46
|
-
"text-alignment",
|
|
47
|
-
js=["draftail_text_utils/js/text_alignment.js"],
|
|
48
|
-
css={"all": ("draftail_text_utils/css/text_alignment.css",)},
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def control():
|
|
53
|
-
if not feature_enabled("TEXT_ALIGNMENT"):
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
return control_json_script(
|
|
57
|
-
type_="text-alignment",
|
|
58
|
-
label="Align Text",
|
|
59
|
-
description="Change text alignment",
|
|
60
|
-
icon="align-left",
|
|
61
|
-
data=[
|
|
62
|
-
{
|
|
63
|
-
"type": type_,
|
|
64
|
-
"label": label,
|
|
65
|
-
"style": {"textAlign": styleValue},
|
|
66
|
-
"icon": f"align-{value}",
|
|
67
|
-
}
|
|
68
|
-
for type_, value, styleValue, label in ALIGNMENTS
|
|
69
|
-
],
|
|
70
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{draftail_text_utils-3.0.2 → draftail_text_utils-3.0.3}/src/draftail_text_utils/rich_text/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|