draftail-text-utils 0.1.7__py3-none-any.whl

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.
Files changed (39) hide show
  1. draftail_text_utils/__init__.py +4 -0
  2. draftail_text_utils/apps.py +7 -0
  3. draftail_text_utils/conf.py +482 -0
  4. draftail_text_utils/migrations/__init__.py +0 -0
  5. draftail_text_utils/rich_text/__init__.py +0 -0
  6. draftail_text_utils/rich_text/base.py +267 -0
  7. draftail_text_utils/rich_text/font_family.py +61 -0
  8. draftail_text_utils/rich_text/font_size.py +94 -0
  9. draftail_text_utils/rich_text/highlight_color.py +99 -0
  10. draftail_text_utils/rich_text/text_alignment.py +70 -0
  11. draftail_text_utils/rich_text/text_color.py +95 -0
  12. draftail_text_utils/static/draftail_text_utils/css/draftail_text_utils.css +5 -0
  13. draftail_text_utils/static/draftail_text_utils/css/font_family.css +57 -0
  14. draftail_text_utils/static/draftail_text_utils/css/font_size.css +102 -0
  15. draftail_text_utils/static/draftail_text_utils/css/highlight_color.css +117 -0
  16. draftail_text_utils/static/draftail_text_utils/css/text_alignment.css +72 -0
  17. draftail_text_utils/static/draftail_text_utils/css/text_color.css +117 -0
  18. draftail_text_utils/static/draftail_text_utils/js/font_family.js +149 -0
  19. draftail_text_utils/static/draftail_text_utils/js/font_size.js +408 -0
  20. draftail_text_utils/static/draftail_text_utils/js/font_size_entity.js +39 -0
  21. draftail_text_utils/static/draftail_text_utils/js/highlight_color.js +350 -0
  22. draftail_text_utils/static/draftail_text_utils/js/highlight_color_entity.js +43 -0
  23. draftail_text_utils/static/draftail_text_utils/js/text_alignment.js +139 -0
  24. draftail_text_utils/static/draftail_text_utils/js/text_color.js +343 -0
  25. draftail_text_utils/static/draftail_text_utils/js/text_color_entity.js +42 -0
  26. draftail_text_utils/templates/draftail_text_utils/icons/align-center.svg +1 -0
  27. draftail_text_utils/templates/draftail_text_utils/icons/align-justify.svg +1 -0
  28. draftail_text_utils/templates/draftail_text_utils/icons/align-left.svg +1 -0
  29. draftail_text_utils/templates/draftail_text_utils/icons/align-right.svg +1 -0
  30. draftail_text_utils/templates/draftail_text_utils/icons/font.svg +1 -0
  31. draftail_text_utils/templates/draftail_text_utils/icons/highlighter.svg +1 -0
  32. draftail_text_utils/templates/draftail_text_utils/icons/palette.svg +1 -0
  33. draftail_text_utils/templates/draftail_text_utils/icons/text-height.svg +1 -0
  34. draftail_text_utils/templatetags/__init__.py +0 -0
  35. draftail_text_utils/templatetags/draftail_text_utils_tags.py +29 -0
  36. draftail_text_utils/wagtail_hooks.py +266 -0
  37. draftail_text_utils-0.1.7.dist-info/METADATA +63 -0
  38. draftail_text_utils-0.1.7.dist-info/RECORD +39 -0
  39. draftail_text_utils-0.1.7.dist-info/WHEEL +4 -0
@@ -0,0 +1,350 @@
1
+ // https://github.com/wagtail/draftail/blob/main/src/components/Toolbar/ToolbarButton.tsx
2
+ // export interface ToolbarButtonProps {
3
+ // name?: string;
4
+ // active?: boolean;
5
+ // label?: string | null;
6
+ // title?: string | null;
7
+ // icon?: IconProp | null;
8
+ // className?: string | null;
9
+ // tooltipDirection?: "up" | "down";
10
+ // onClick?: ((name: string) => void) | null;
11
+ // }
12
+
13
+ // CSS Color Component
14
+ // https://github.com/argyleink/css-color-component
15
+
16
+ (function () {
17
+ var React = window.React;
18
+ var RichUtils = window.DraftJS.RichUtils;
19
+ var Modifier = window.DraftJS.Modifier;
20
+ var EditorState = window.DraftJS.EditorState;
21
+ var ToolbarButton = window.Draftail.ToolbarButton;
22
+ var data = window.draftailTextUtils || {};
23
+
24
+ // Build options from config
25
+ var options = data.customHighlightColors || [];
26
+ var optMap = data.customHighlightColorStyleMap || {};
27
+
28
+ var savedSelection = null;
29
+
30
+ const entityType = 'HIGHLIGHT_COLOR';
31
+ const controlType = 'highlight-color';
32
+ const control = JSON.parse(
33
+ document.getElementById(`draftail-plugin-control-${controlType}`)
34
+ .textContent,
35
+ );
36
+
37
+ function removeHighlightColorEntity(editorState) {
38
+ var selection = editorState.getSelection();
39
+ if (!selection.getHasFocus()) return editorState;
40
+
41
+ var contentWithoutEntity = Modifier.applyEntity(
42
+ editorState.getCurrentContent(),
43
+ selection,
44
+ null, // null removes entity
45
+ );
46
+
47
+ return EditorState.push(editorState, contentWithoutEntity, 'apply-entity');
48
+ }
49
+
50
+ function saveSelection(getEditorState) {
51
+ const selection = getEditorState().getSelection();
52
+ if (selection && !selection.isCollapsed()) {
53
+ savedSelection = selection;
54
+ }
55
+ }
56
+
57
+ function restoreSelection(editorState) {
58
+ if (savedSelection && !savedSelection.isCollapsed()) {
59
+ return EditorState.forceSelection(editorState, savedSelection);
60
+ }
61
+ return editorState;
62
+ }
63
+
64
+ // Helper to find active color
65
+ function getActiveHighlightColor(editorState) {
66
+ var selection = editorState.getSelection();
67
+ if (!selection.getHasFocus()) return null;
68
+
69
+ var contentState = editorState.getCurrentContent();
70
+ var startKey = selection.getStartKey();
71
+ var startOffset = selection.getStartOffset();
72
+ var blockWithEntityAt = contentState.getBlockForKey(startKey);
73
+
74
+ // Check entity at cursor position
75
+ var entityKey = blockWithEntityAt.getEntityAt(startOffset);
76
+ if (entityKey) {
77
+ var entity = contentState.getEntity(entityKey);
78
+ if (entity.getType() === entityType) {
79
+ return entity.getData().backgroundColor;
80
+ }
81
+ }
82
+
83
+ // If selection is collapsed, also check character before (so styling after typing works)
84
+ if (selection.isCollapsed() && startOffset > 0) {
85
+ entityKey = blockWithEntityAt.getEntityAt(startOffset - 1);
86
+ if (entityKey) {
87
+ var entity = contentState.getEntity(entityKey);
88
+ if (entity.getType() === entityType) {
89
+ return entity.getData().backgroundColor;
90
+ }
91
+ }
92
+ }
93
+
94
+ return null;
95
+ }
96
+
97
+ // The control component
98
+ var HighlightColorControl = class HighlightColorControl
99
+ extends React.Component
100
+ {
101
+ constructor(props) {
102
+ super(props); // getEditorState, onChange
103
+ this.state = {
104
+ isDropdownOpen: false,
105
+ inputValue: '',
106
+ isInputFocused: false,
107
+ };
108
+ this.controlRef = React.createRef();
109
+ }
110
+
111
+ // lifecycle
112
+ componentDidMount() {
113
+ this.syncInputValue();
114
+ document.addEventListener('mousedown', this.handleClickOutside);
115
+
116
+ // Listen to CSS Color Component events
117
+ var inputEl = document.querySelector('#Draftail--highlight-color-input');
118
+ if (inputEl) {
119
+ inputEl.addEventListener('open', this.handleInputFocus);
120
+ inputEl.addEventListener('close', this.handleInputBlur);
121
+ inputEl.addEventListener('change', this.handleInputChange);
122
+ }
123
+ }
124
+
125
+ componentWillUnmount() {
126
+ document.removeEventListener('mousedown', this.handleClickOutside);
127
+
128
+ // Unsubscribe from CSS Color Component events
129
+ var inputEl = document.querySelector('#Draftail--highlight-color-input');
130
+ if (inputEl) {
131
+ inputEl.removeEventListener('open', this.handleInputFocus);
132
+ inputEl.removeEventListener('close', this.handleInputBlur);
133
+ inputEl.removeEventListener('change', this.handleInputChange);
134
+ }
135
+ }
136
+
137
+ componentDidUpdate() {
138
+ this.syncInputValue();
139
+ }
140
+
141
+ // helpers
142
+ getActiveHighlightColor() {
143
+ return getActiveHighlightColor(this.props.getEditorState());
144
+ }
145
+
146
+ syncInputValue() {
147
+ // While the user is editing, never overwrite their input
148
+ if (this.state.isInputFocused || this.state.isDropdownOpen) {
149
+ return;
150
+ }
151
+
152
+ var activeHighlightColor = this.getActiveHighlightColor();
153
+ var newValue = activeHighlightColor || '';
154
+
155
+ if (this.state.inputValue !== newValue) {
156
+ this.setState({ inputValue: newValue });
157
+ }
158
+ }
159
+
160
+ toggleDropdown(force) {
161
+ const willBeOpen =
162
+ typeof force === 'boolean' ? force : !this.state.isDropdownOpen;
163
+ if (willBeOpen && !this.state.isDropdownOpen) {
164
+ saveSelection(this.props.getEditorState);
165
+ }
166
+ this.setState({ isDropdownOpen: willBeOpen });
167
+ }
168
+
169
+ applyHighlightColor(newColor) {
170
+ var backgroundColor = newColor === 'transparent' ? '#00000000' : newColor;
171
+ var editorState = restoreSelection(this.props.getEditorState());
172
+ var currentColor = this.getActiveHighlightColor(editorState);
173
+ editorState = removeHighlightColorEntity(editorState);
174
+
175
+ if (backgroundColor === currentColor) {
176
+ // Re-sync the input value
177
+ this.props.onChange(editorState);
178
+ this.setState({ isDropdownOpen: false, inputValue: '' });
179
+ this.syncInputValue();
180
+ return;
181
+ }
182
+
183
+ var selection = editorState.getSelection();
184
+
185
+ // Apply the entity to the selection
186
+ var contentWithEntity = editorState
187
+ .getCurrentContent()
188
+ .createEntity(entityType, 'MUTABLE', {
189
+ backgroundColor,
190
+ });
191
+ var contentStateWithEntity = Modifier.applyEntity(
192
+ contentWithEntity,
193
+ selection,
194
+ contentWithEntity.getLastCreatedEntityKey(),
195
+ );
196
+ var newContent = EditorState.push(
197
+ editorState,
198
+ contentStateWithEntity,
199
+ 'apply-entity',
200
+ );
201
+ this.props.onChange(newContent);
202
+
203
+ // Re-sync the input value
204
+ this.setState({
205
+ isDropdownOpen: false,
206
+ inputValue: String(backgroundColor),
207
+ });
208
+ this.syncInputValue();
209
+ }
210
+
211
+ handleInputFocus = () => {
212
+ saveSelection(this.props.getEditorState);
213
+ this.setState({ isInputFocused: true });
214
+ if (!this.state.isDropdownOpen) {
215
+ this.setState({ isDropdownOpen: true });
216
+ }
217
+ };
218
+
219
+ handleInputBlur = () => {
220
+ // When leaving the field, clear the focus flag and re‑sync (will clear if no style)
221
+ this.setState({ isInputFocused: false }, () => {
222
+ this.syncInputValue();
223
+ });
224
+ };
225
+
226
+ handleInputChange = (e) => {
227
+ this.applyHighlightColor(e.detail.value);
228
+ };
229
+
230
+ handleInputKeyDown = (e) => {
231
+ if (e.key === 'Escape') {
232
+ this.toggleDropdown(false);
233
+ this.syncInputValue();
234
+ return;
235
+ }
236
+
237
+ if (e.key === 'Enter') {
238
+ e.preventDefault();
239
+ this.applyHighlightColor(this.state.inputValue);
240
+ return;
241
+ }
242
+ };
243
+
244
+ handleClickOutside = (event) => {
245
+ if (
246
+ this.controlRef.current &&
247
+ !this.controlRef.current.contains(event.target)
248
+ ) {
249
+ this.setState({ isDropdownOpen: false, isInputFocused: false });
250
+ var inputEl = document.querySelector(
251
+ '#Draftail--highlight-color-input',
252
+ );
253
+ if (inputEl) {
254
+ inputEl.close();
255
+ }
256
+ }
257
+ };
258
+
259
+ createOptElement = (opt) => {
260
+ return React.createElement(
261
+ 'li',
262
+ {
263
+ key: `HIGHLIGHT_COLOR_OPT_${opt.key.toUpperCase()}`,
264
+ className: 'Draftail--highlight-color-option-wrapper',
265
+ },
266
+ React.createElement(ToolbarButton, {
267
+ name: opt.value === 'transparent' ? '#00000000' : opt.value,
268
+ active: this.getActiveHighlightColor() === opt.value,
269
+ title: opt.label,
270
+ label: '\u2713',
271
+ tooltipDirection: 'up',
272
+ className: 'Draftail--highlight-color-option',
273
+ onClick: () => this.applyHighlightColor(opt.value),
274
+ }),
275
+ );
276
+ };
277
+
278
+ // render
279
+ render() {
280
+ const icon = `#icon-${control.icon}`;
281
+
282
+ var activeHighlightColor = this.getActiveHighlightColor();
283
+ var inputDisplay = this.state.inputValue;
284
+ var isDropdownExpanded = this.state.isDropdownOpen;
285
+
286
+ var input = React.createElement(
287
+ 'li',
288
+ {
289
+ 'key': 'HIGHLIGHT_COLOR_CTRL_INPUT_WRAPPER',
290
+ 'className': 'Draftail--highlight-color-input-wrapper',
291
+ 'data-draftail-balloon': 'down',
292
+ 'aria-label': 'Select custom highlight color',
293
+ },
294
+ React.createElement('color-input', {
295
+ key: 'HIGHLIGHT_COLOR_CTRL_INPUT',
296
+ id: 'Draftail--highlight-color-input',
297
+ className: 'Draftail--highlight-color-input',
298
+ defaultValue: activeHighlightColor || '#000000',
299
+ value: inputDisplay || '#000000',
300
+ onKeyDown: this.handleInputKeyDown,
301
+ }),
302
+ );
303
+
304
+ var hr = React.createElement('hr', {
305
+ key: 'HIGHLIGHT_COLOR_CTRL_HR',
306
+ className: 'Draftail--highlight-color-hr',
307
+ });
308
+
309
+ var dropdown = React.createElement(
310
+ 'ul',
311
+ {
312
+ 'id': 'Draftail--highlight-color-dropdown',
313
+ 'className': 'Draftail--highlight-color-dropdown',
314
+ 'aria-expanded': isDropdownExpanded,
315
+ },
316
+ options.map(this.createOptElement),
317
+ hr,
318
+ input,
319
+ );
320
+
321
+ // ── Toolbar button ──
322
+ var button = React.createElement(ToolbarButton, {
323
+ name: control.type.toUpperCase(),
324
+ active: !!activeHighlightColor,
325
+ title: control.label,
326
+ icon: icon,
327
+ tooltipDirection: 'up',
328
+ onClick: () => this.toggleDropdown(),
329
+ });
330
+
331
+ return React.createElement(
332
+ 'div',
333
+ {
334
+ key: 'HIGHLIGHT_COLOR_CTRL',
335
+ className: 'Draftail--highlight-color-control',
336
+ ref: this.controlRef,
337
+ },
338
+ dropdown,
339
+ button,
340
+ );
341
+ }
342
+ };
343
+ window.draftail.registerPlugin(
344
+ {
345
+ type: control.type,
346
+ inline: HighlightColorControl,
347
+ },
348
+ 'controls',
349
+ );
350
+ })();
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Highlight color entity for Draftail.
3
+ * Registers an entity type (HIGHLIGHT_COLOR) that stores an arbitrary highlight color.
4
+ * The entity is applied by `highlight-color` control when a custom highlight color is entered.
5
+ * See:`src/draftail_text_utils/static/draftail_text_utils/js/highlight_color.js`
6
+ */
7
+ (function () {
8
+ const React = window.React;
9
+ const ENTITY_TYPE = 'HIGHLIGHT_COLOR';
10
+ const STYLE_KEY = 'style';
11
+ const DATASET_KEY = 'data-entity-type';
12
+
13
+ const HighlightColorDecorator = ({ children, contentState, entityKey }) => {
14
+ const entity = contentState.getEntity(entityKey);
15
+ const { backgroundColor: entityColor } = entity.getData();
16
+ if (!entityColor) return children;
17
+
18
+ const backgroundColor =
19
+ entityColor == 'transparent' ? '#00000000' : entityColor;
20
+ return React.createElement(
21
+ 'span',
22
+ {
23
+ [STYLE_KEY]: { backgroundColor },
24
+ [DATASET_KEY]: ENTITY_TYPE,
25
+ },
26
+ children,
27
+ );
28
+ };
29
+
30
+ const HighlightColorSource = ({ onClose }) => {
31
+ onClose();
32
+ return null;
33
+ };
34
+
35
+ window.draftail.registerPlugin(
36
+ {
37
+ type: ENTITY_TYPE,
38
+ source: HighlightColorSource,
39
+ decorator: HighlightColorDecorator,
40
+ },
41
+ 'entityTypes',
42
+ );
43
+ })();
@@ -0,0 +1,139 @@
1
+ // https://github.com/wagtail/draftail/blob/main/src/components/Toolbar/ToolbarButton.tsx
2
+ // export interface ToolbarButtonProps {
3
+ // name?: string;
4
+ // active?: boolean;
5
+ // label?: string | null;
6
+ // title?: string | null;
7
+ // icon?: IconProp | null;
8
+ // className?: string | null;
9
+ // tooltipDirection?: "up" | "down";
10
+ // onClick?: ((name: string) => void) | null;
11
+ // }
12
+
13
+ (function () {
14
+ var React = window.React;
15
+ var RichUtils = window.DraftJS.RichUtils;
16
+ var ToolbarButton = window.Draftail.ToolbarButton;
17
+ var data = window.draftailTextUtils || {};
18
+ const type_ = 'text-alignment';
19
+ const control = JSON.parse(
20
+ document.getElementById(`draftail-plugin-control-${type_}`).textContent,
21
+ );
22
+ const options = control.data || [];
23
+
24
+ // ── Helper to find active text alignment ──
25
+ function getActiveOption(editorState) {
26
+ const selection = editorState.getSelection();
27
+ const block = editorState
28
+ .getCurrentContent()
29
+ .getBlockForKey(selection.getStartKey());
30
+ const blockType = block.getType();
31
+ return options.find((opt) => opt.type === blockType) || null;
32
+ }
33
+
34
+ class TextAlignmentControl extends React.Component {
35
+ constructor(props) {
36
+ super(props);
37
+ this.state = {
38
+ isOpen: false,
39
+ };
40
+ this.controlRef = React.createRef();
41
+ }
42
+
43
+ componentDidMount() {
44
+ document.addEventListener('mousedown', this.handleClickOutside);
45
+ }
46
+
47
+ componentWillUnmount() {
48
+ document.removeEventListener('mousedown', this.handleClickOutside);
49
+ }
50
+
51
+ // ── close dropdown when clicking outside ──
52
+ handleClickOutside = (event) => {
53
+ if (
54
+ this.controlRef.current &&
55
+ !this.controlRef.current.contains(event.target)
56
+ ) {
57
+ this.setState({ isOpen: false });
58
+ }
59
+ };
60
+
61
+ // ── toggle dropdown (used by button) ──
62
+ toggleDropdown = (force) => {
63
+ this.setState((prevState) => ({
64
+ isOpen: typeof force === 'boolean' ? force : !prevState.isOpen,
65
+ }));
66
+ };
67
+
68
+ // ── apply a text alignment ──
69
+ handleOptionClick = (opt) => {
70
+ const { getEditorState, onChange } = this.props;
71
+ const editorState = getEditorState();
72
+ const newState = RichUtils.toggleBlockType(editorState, opt.type);
73
+ onChange(newState);
74
+ this.setState({ isOpen: false });
75
+ };
76
+
77
+ render() {
78
+ const { getEditorState } = this.props;
79
+ const editorState = getEditorState();
80
+ const activeOption = getActiveOption(editorState);
81
+ const isOpen = this.state.isOpen;
82
+ const icon = `#icon-${activeOption ? activeOption.icon : control.icon}`;
83
+
84
+ // ── Dropdown list ──
85
+ const dropdown = React.createElement(
86
+ 'ul',
87
+ {
88
+ 'className': 'Draftail--text-alignment-dropdown',
89
+ 'aria-expanded': isOpen,
90
+ },
91
+ options.map((opt) =>
92
+ React.createElement(
93
+ 'li',
94
+ {
95
+ key: opt.type,
96
+ },
97
+ React.createElement(ToolbarButton, {
98
+ name: opt.type.toUpperCase(),
99
+ active: activeOption ? activeOption.type === opt.type : false,
100
+ title: opt.label,
101
+ className: 'Draftail--text-alignment-option',
102
+ icon: `#icon-${opt.icon}`,
103
+ tooltipDirection: 'up',
104
+ onClick: () => this.handleOptionClick(opt),
105
+ }),
106
+ ),
107
+ ),
108
+ );
109
+
110
+ // ── Toolbar button ──
111
+ const button = React.createElement(ToolbarButton, {
112
+ name: control.type.toUpperCase(),
113
+ active: !!activeOption,
114
+ title: control.label,
115
+ icon: icon,
116
+ tooltipDirection: 'up',
117
+ onClick: () => this.toggleDropdown(),
118
+ });
119
+
120
+ return React.createElement(
121
+ 'div',
122
+ {
123
+ className: 'Draftail--text-alignment-control',
124
+ ref: this.controlRef,
125
+ },
126
+ dropdown,
127
+ button,
128
+ );
129
+ }
130
+ }
131
+
132
+ window.draftail.registerPlugin(
133
+ {
134
+ type: control.type,
135
+ inline: TextAlignmentControl,
136
+ },
137
+ 'controls',
138
+ );
139
+ })();