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,408 @@
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 Modifier = window.DraftJS.Modifier;
17
+ var EditorState = window.DraftJS.EditorState;
18
+ var ToolbarButton = window.Draftail.ToolbarButton;
19
+ var data = window.draftailTextUtils || {
20
+ customFontSizes: {
21
+ PRESETS: [
22
+ 8,
23
+ 9,
24
+ 10,
25
+ 11,
26
+ 12,
27
+ 14,
28
+ this.state.inputValue || '',
29
+ 18,
30
+ 24,
31
+ 30,
32
+ 36,
33
+ 48,
34
+ 60,
35
+ 72,
36
+ 96,
37
+ ],
38
+ },
39
+ };
40
+
41
+ // Build options from config
42
+ var options = data.customFontSizes.PRESETS.map(function (size) {
43
+ return {
44
+ label: String(size),
45
+ type: 'FONT_SIZE_' + size,
46
+ style: { fontSize: size + 'px' },
47
+ size: size,
48
+ };
49
+ });
50
+
51
+ var optMap = new Map(
52
+ options.map(function (opt) {
53
+ return [opt.type, opt];
54
+ }),
55
+ );
56
+
57
+ var FONT_SIZE_STEP = data.customFontSizes.STEP || 1;
58
+ var MIN_FONT_SIZE = data.customFontSizes.MIN || options[0].size;
59
+ var MAX_FONT_SIZE =
60
+ data.customFontSizes.MAX || options[options.length - 1].size;
61
+
62
+ var savedSelection = null;
63
+
64
+ const entityType = 'FONT_SIZE';
65
+ const controlType = 'font-size';
66
+ const control = JSON.parse(
67
+ document.getElementById(`draftail-plugin-control-${controlType}`)
68
+ .textContent,
69
+ );
70
+
71
+ function removeFontSizeEntity(editorState) {
72
+ var selection = editorState.getSelection();
73
+ if (!selection.getHasFocus()) return editorState;
74
+
75
+ var contentWithoutEntity = Modifier.applyEntity(
76
+ editorState.getCurrentContent(),
77
+ selection,
78
+ null, // null removes entity
79
+ );
80
+
81
+ return EditorState.push(editorState, contentWithoutEntity, 'apply-entity');
82
+ }
83
+
84
+ function saveSelection(getEditorState) {
85
+ const selection = getEditorState().getSelection();
86
+ if (selection && !selection.isCollapsed()) {
87
+ savedSelection = selection;
88
+ }
89
+ }
90
+
91
+ function restoreSelection(editorState) {
92
+ if (savedSelection && !savedSelection.isCollapsed()) {
93
+ return EditorState.forceSelection(editorState, savedSelection);
94
+ }
95
+ return editorState;
96
+ }
97
+
98
+ // Helper to find active size
99
+ function getActiveSize(editorState) {
100
+ var selection = editorState.getSelection();
101
+ if (!selection.getHasFocus()) return null;
102
+
103
+ var contentState = editorState.getCurrentContent();
104
+ var startKey = selection.getStartKey();
105
+ var startOffset = selection.getStartOffset();
106
+ var blockWithEntityAt = contentState.getBlockForKey(startKey);
107
+
108
+ // Check entity at cursor position
109
+ var entityKey = blockWithEntityAt.getEntityAt(startOffset);
110
+ if (entityKey) {
111
+ var entity = contentState.getEntity(entityKey);
112
+ if (entity.getType() === entityType) {
113
+ return entity.getData().size;
114
+ }
115
+ }
116
+
117
+ // If selection is collapsed, also check character before (so styling after typing works)
118
+ if (selection.isCollapsed() && startOffset > 0) {
119
+ entityKey = blockWithEntityAt.getEntityAt(startOffset - 1);
120
+ if (entityKey) {
121
+ var entity = contentState.getEntity(entityKey);
122
+ if (entity.getType() === entityType) {
123
+ return entity.getData().size;
124
+ }
125
+ }
126
+ }
127
+
128
+ return null;
129
+ }
130
+
131
+ // The control component
132
+ var FontSizeControl = class FontSizeControl extends React.Component {
133
+ constructor(props) {
134
+ super(props); // getEditorState, onChange
135
+ this.state = {
136
+ isDropdownOpen: false,
137
+ inputValue: '',
138
+ isInputFocused: false,
139
+ };
140
+ this.controlRef = React.createRef();
141
+ }
142
+
143
+ // lifecycle
144
+ componentDidMount() {
145
+ this.syncInputValue();
146
+ document.addEventListener('mousedown', this.handleClickOutside);
147
+ }
148
+
149
+ componentWillUnmount() {
150
+ document.removeEventListener('mousedown', this.handleClickOutside);
151
+ }
152
+
153
+ componentDidUpdate() {
154
+ this.syncInputValue(); // always runs, but guarded internally
155
+ }
156
+
157
+ // helpers
158
+ getActiveSize() {
159
+ return getActiveSize(this.props.getEditorState());
160
+ }
161
+
162
+ syncInputValue() {
163
+ // While the user is editing, never overwrite their input
164
+ if (this.state.isInputFocused || this.state.isDropdownOpen) {
165
+ return;
166
+ }
167
+
168
+ var activeSize = this.getActiveSize();
169
+ var newValue = activeSize || '';
170
+
171
+ if (this.state.inputValue !== newValue) {
172
+ this.setState({ inputValue: newValue });
173
+ }
174
+ }
175
+
176
+ toggleDropdown(force) {
177
+ const willBeOpen =
178
+ typeof force === 'boolean' ? force : !this.state.isDropdownOpen;
179
+ if (willBeOpen && !this.state.isDropdownOpen) {
180
+ saveSelection(this.props.getEditorState);
181
+ }
182
+ this.setState({ isDropdownOpen: willBeOpen });
183
+ }
184
+
185
+ applyFontSize(size) {
186
+ console.log(size);
187
+ var editorState = restoreSelection(this.props.getEditorState());
188
+ var currentSize = this.getActiveSize(editorState);
189
+ editorState = removeFontSizeEntity(editorState);
190
+
191
+ if (size === currentSize) {
192
+ // Re-sync the input value
193
+ this.props.onChange(editorState);
194
+ this.setState({ isDropdownOpen: false, inputValue: '' });
195
+ this.syncInputValue();
196
+ return;
197
+ }
198
+
199
+ var selection = editorState.getSelection();
200
+
201
+ // Apply the entity to the selection
202
+ var contentWithEntity = editorState
203
+ .getCurrentContent()
204
+ .createEntity(entityType, 'MUTABLE', {
205
+ size,
206
+ });
207
+ var contentStateWithEntity = Modifier.applyEntity(
208
+ contentWithEntity,
209
+ selection,
210
+ contentWithEntity.getLastCreatedEntityKey(),
211
+ );
212
+ var newContent = EditorState.push(
213
+ editorState,
214
+ contentStateWithEntity,
215
+ 'apply-entity',
216
+ );
217
+ this.props.onChange(newContent);
218
+
219
+ // Re-sync the input value
220
+ this.setState({ isDropdownOpen: false, inputValue: String(size) });
221
+ this.syncInputValue();
222
+ }
223
+
224
+ increment() {
225
+ var activeSize = this.getActiveSize();
226
+ var currentSize = isNaN(activeSize || this.state.inputValue || 'NaN')
227
+ ? 16
228
+ : parseInt(activeSize || this.state.inputValue);
229
+ this.applyFontSize(Math.min(MAX_FONT_SIZE, currentSize + 1));
230
+ }
231
+
232
+ decrement() {
233
+ var activeSize = this.getActiveSize();
234
+ var currentSize = isNaN(activeSize || this.state.inputValue || 'NaN')
235
+ ? 16
236
+ : parseInt(activeSize || this.state.inputValue);
237
+ this.applyFontSize(Math.max(MIN_FONT_SIZE, currentSize - 1));
238
+ }
239
+
240
+ // event handlers
241
+ handleInputMouseDown = (e) => {
242
+ this.toggleDropdown();
243
+ };
244
+
245
+ handleInputFocus = () => {
246
+ saveSelection(this.props.getEditorState);
247
+ this.setState({ isInputFocused: true });
248
+ if (!this.state.isDropdownOpen) {
249
+ this.setState({ isDropdownOpen: true });
250
+ }
251
+ };
252
+
253
+ handleInputBlur = () => {
254
+ // When leaving the field, clear the focus flag and re‑sync (will clear if no style)
255
+ this.setState({ isInputFocused: false }, () => {
256
+ this.syncInputValue();
257
+ });
258
+ };
259
+
260
+ handleInputChange = (e) => {
261
+ this.setState({ inputValue: e.target.value.replace(/[^0-9]/g, '') });
262
+ };
263
+
264
+ handleInputKeyDown = (e) => {
265
+ if (e.key === 'Escape') {
266
+ this.toggleDropdown(false);
267
+ this.syncInputValue();
268
+ return;
269
+ }
270
+
271
+ if (['ArrowUp', 'ArrowDown', 'Enter'].includes(e.key)) {
272
+ e.preventDefault();
273
+ if (e.key === 'Enter') {
274
+ var size = parseInt(this.state.inputValue, 10);
275
+ if (size > 0 && size <= 400) {
276
+ this.applyFontSize(size);
277
+ } else {
278
+ this.syncInputValue();
279
+ }
280
+ return;
281
+ }
282
+
283
+ if (e.key === 'ArrowUp') {
284
+ this.increment();
285
+ return;
286
+ }
287
+
288
+ this.decrement();
289
+ return;
290
+ }
291
+ };
292
+
293
+ handleClickOutside = (event) => {
294
+ if (
295
+ this.controlRef.current &&
296
+ !this.controlRef.current.contains(event.target)
297
+ ) {
298
+ this.setState({ isDropdownOpen: false });
299
+ }
300
+ };
301
+
302
+ // render
303
+ render() {
304
+ var activeSize = this.getActiveSize();
305
+ var inputDisplay = this.state.inputValue;
306
+ var isDropdownExpanded = this.state.isDropdownOpen;
307
+ var dropdown = React.createElement(
308
+ 'ul',
309
+ {
310
+ 'id': 'Draftail--font-size-dropdown',
311
+ 'className': 'Draftail--font-size-dropdown',
312
+ 'aria-expanded': isDropdownExpanded,
313
+ },
314
+ options.map(
315
+ function (opt) {
316
+ return React.createElement(
317
+ 'li',
318
+ {
319
+ 'key': `FONT_SIZE_OPT_${opt.size}`,
320
+ 'onMouseDown': (e) => {
321
+ e.preventDefault();
322
+ this.applyFontSize(opt.size);
323
+ },
324
+ 'className': 'Draftail--font-size-option',
325
+ 'aria-selected': inputDisplay === opt.size,
326
+ },
327
+ opt.label,
328
+ );
329
+ }.bind(this),
330
+ ),
331
+ );
332
+
333
+ var decrementBtn = React.createElement(ToolbarButton, {
334
+ key: `FONT_SIZE_CTRL_DECREMENT`,
335
+ name: 'DECREMENT',
336
+ title: 'Decrease font size',
337
+ label: '-',
338
+ tooltipDirection: 'up',
339
+ className: 'Draftail--font-size-decrement',
340
+ onClick: this.decrement.bind(this),
341
+ });
342
+
343
+ var incrementBtn = React.createElement(ToolbarButton, {
344
+ key: `FONT_SIZE_CTRL_INCREMENT`,
345
+ name: 'INCREMENT',
346
+ title: 'Increase font size',
347
+ label: '+',
348
+ tooltipDirection: 'up',
349
+ className: 'Draftail--font-size-increment',
350
+ onClick: this.increment.bind(this),
351
+ });
352
+
353
+ var input = React.createElement(
354
+ 'div',
355
+ {
356
+ 'key': 'FONT_SIZE_CTRL_INPUT_WRAPPER',
357
+ 'className': 'Draftail--font-size-input-wrapper',
358
+ 'data-draftail-balloon': 'up',
359
+ 'aria-label': control.label,
360
+ 'aria-controls': 'Draftail--font-size-dropdown',
361
+ },
362
+ React.createElement('input', {
363
+ key: 'FONT_SIZE_CTRL_INPUT',
364
+ id: 'Draftail--font-size-input',
365
+ className: 'Draftail--font-size-input',
366
+ type: 'text',
367
+ defaultValue: activeSize || '',
368
+ value: inputDisplay,
369
+ placeholder: 'Font Size',
370
+ onMouseDown: this.handleInputMouseDown,
371
+ onFocus: this.handleInputFocus,
372
+ onBlur: this.handleInputBlur,
373
+ onChange: this.handleInputChange,
374
+ onKeyDown: this.handleInputKeyDown,
375
+ }),
376
+ );
377
+
378
+ var controlRow = React.createElement(
379
+ 'div',
380
+ {
381
+ key: 'FONT_SIZE_CTRL_ROW',
382
+ className: 'Draftail--font-size-control-row',
383
+ },
384
+ decrementBtn,
385
+ input,
386
+ incrementBtn,
387
+ );
388
+
389
+ return React.createElement(
390
+ 'div',
391
+ {
392
+ key: 'FONT_SIZE_CTRL',
393
+ className: 'Draftail--font-size-control',
394
+ ref: this.controlRef,
395
+ },
396
+ controlRow,
397
+ dropdown,
398
+ );
399
+ }
400
+ };
401
+ window.draftail.registerPlugin(
402
+ {
403
+ type: control.type,
404
+ inline: FontSizeControl,
405
+ },
406
+ 'controls',
407
+ );
408
+ })();
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Font size entity for Draftail.
3
+ * Registers an entity type (FONT_SIZE) that stores an arbitrary font size.
4
+ * The entity is applied by `font-size` control when a custom size is entered.
5
+ * See:`src/draftail_text_utils/static/draftail_text_utils/js/font_family.js`
6
+ */
7
+ (function () {
8
+ const React = window.React;
9
+ const ENTITY_TYPE = 'FONT_SIZE';
10
+ const STYLE_KEY = 'style';
11
+ const DATASET_KEY = 'data-entity-type';
12
+
13
+ const FontSizeDecorator = ({ children, contentState, entityKey }) => {
14
+ const entity = contentState.getEntity(entityKey);
15
+ const { size: fontSize } = entity.getData();
16
+ return React.createElement(
17
+ 'span',
18
+ {
19
+ [STYLE_KEY]: { fontSize },
20
+ [DATASET_KEY]: ENTITY_TYPE,
21
+ },
22
+ children,
23
+ );
24
+ };
25
+
26
+ const FontSizeSource = ({ onClose }) => {
27
+ onClose();
28
+ return null;
29
+ };
30
+
31
+ window.draftail.registerPlugin(
32
+ {
33
+ type: ENTITY_TYPE,
34
+ source: FontSizeSource,
35
+ decorator: FontSizeDecorator,
36
+ },
37
+ 'entityTypes',
38
+ );
39
+ })();