|
| 1 | +import React from 'react'; |
| 2 | +import PropTypes from 'prop-types'; |
| 3 | +import base32Encode from 'base32-encode'; |
| 4 | +import hexToArrayBuffer from 'hex-to-array-buffer'; |
| 5 | + |
| 6 | +export default class UuidControl extends React.Component { |
| 7 | + static propTypes = { |
| 8 | + onChange: PropTypes.func.isRequired, |
| 9 | + forID: PropTypes.string, |
| 10 | + value: PropTypes.node, |
| 11 | + classNameWrapper: PropTypes.string.isRequired, |
| 12 | + setActiveStyle: PropTypes.func.isRequired, |
| 13 | + setInactiveStyle: PropTypes.func.isRequired, |
| 14 | + }; |
| 15 | + |
| 16 | + // The selection to maintain for the input element |
| 17 | + _sel = 0; |
| 18 | + |
| 19 | + // The input element ref |
| 20 | + _el = null; |
| 21 | + |
| 22 | + // NOTE: This prevents the cursor from jumping to the end of the text for |
| 23 | + // nested inputs. In other words, this is not an issue on top-level text |
| 24 | + // fields such as the `title` of a collection post. However, it becomes an |
| 25 | + // issue on fields nested within other components, namely widgets nested |
| 26 | + // within a `markdown` widget. For example, the alt text on a block image |
| 27 | + // within markdown. |
| 28 | + // SEE: https://github.com/netlify/netlify-cms/issues/4539 |
| 29 | + // SEE: https://github.com/netlify/netlify-cms/issues/3578 |
| 30 | + componentDidUpdate() { |
| 31 | + if (this._el && this._el.selectionStart !== this._sel) { |
| 32 | + this._el.setSelectionRange(this._sel, this._sel); |
| 33 | + } |
| 34 | + } |
| 35 | + |
| 36 | + // componentDidMount is used for generate a UUID when the page loads for the first time |
| 37 | + componentDidMount() { |
| 38 | + const { value, field, onChange } = this.props; |
| 39 | + if (!value) { |
| 40 | + const prefix = field.get('prefix', ''); |
| 41 | + const useB32Encode = field.get('use_b32_encode', false); |
| 42 | + const uuid = crypto.randomUUID(); |
| 43 | + const uuidFormatted = useB32Encode ? this.uuidToB32(uuid) : uuid; |
| 44 | + onChange(prefix + uuidFormatted); |
| 45 | + } |
| 46 | + } |
| 47 | + |
| 48 | + uuidToB32 = uuid => { |
| 49 | + const bytes = hexToArrayBuffer(uuid.replace(/-/g, '') || ''); |
| 50 | + const encodedUUID = base32Encode(bytes, 'RFC4648', { padding: false }); |
| 51 | + return encodedUUID.toLowerCase(); |
| 52 | + }; |
| 53 | + |
| 54 | + handleChange = e => { |
| 55 | + this._sel = e.target.selectionStart; |
| 56 | + this.props.onChange(e.target.value); |
| 57 | + }; |
| 58 | + |
| 59 | + render() { |
| 60 | + const { field, forID, value, classNameWrapper, setActiveStyle, setInactiveStyle } = this.props; |
| 61 | + const readOnly = field.get('read_only', true); |
| 62 | + |
| 63 | + return ( |
| 64 | + <input |
| 65 | + ref={el => { |
| 66 | + this._el = el; |
| 67 | + }} |
| 68 | + type="text" |
| 69 | + id={forID} |
| 70 | + readOnly={readOnly} |
| 71 | + style={{ fontFamily: 'monospace', opacity: readOnly ? '0.5' : '1.0' }} |
| 72 | + className={classNameWrapper} |
| 73 | + value={value || ''} |
| 74 | + onChange={this.handleChange} |
| 75 | + onFocus={setActiveStyle} |
| 76 | + onBlur={setInactiveStyle} |
| 77 | + /> |
| 78 | + ); |
| 79 | + } |
| 80 | +} |
0 commit comments