Skip to content

Commit 94497cc

Browse files
committed
Merge branch 'main' into pr/7645
2 parents 33cb623 + f57084c commit 94497cc

16 files changed

Lines changed: 295 additions & 4 deletions

File tree

dev-test/backends/test/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ collections: # A list of collections the CMS should be able to edit
117117
search_fields: ['title', 'body']
118118
value_field: 'title'
119119
- { label: 'Title', name: 'title', widget: 'string' }
120+
- { label: 'ID', name: 'uuid', widget: 'uuid', read_only: true, prefix: '', use_b32_encoding: false }
120121
- { label: 'Boolean', name: 'boolean', widget: 'boolean', default: true }
121122
- { label: 'Map', name: 'map', widget: 'map' }
122123
- { label: 'Text', name: 'text', widget: 'text', hint: 'Plain text, not markdown' }

dev-test/backends/test/index.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

dev-test/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ collections: # A list of collections the CMS should be able to edit
150150
search_fields: ['title', 'body']
151151
value_field: 'title'
152152
- { label: 'Title', name: 'title', widget: 'string' }
153+
- { label: 'ID', name: 'uuid', widget: 'uuid', read_only: true, prefix: '', use_b32_encoding: false }
153154
- { label: 'Boolean', name: 'boolean', widget: 'boolean', default: true }
154155
- { label: 'Map', name: 'map', widget: 'map' }
155156
- { label: 'Text', name: 'text', widget: 'text', hint: 'Plain text, not markdown' }

dev-test/index.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

packages/decap-cms-app/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,16 @@
6363
"decap-cms-widget-number": "workspace:*",
6464
"decap-cms-widget-object": "workspace:*",
6565
"decap-cms-widget-relation": "workspace:*",
66+
"decap-cms-widget-richtext": "workspace:*",
6667
"decap-cms-widget-select": "workspace:*",
6768
"decap-cms-widget-string": "workspace:*",
6869
"decap-cms-widget-text": "workspace:*",
70+
"decap-cms-widget-uuid": "workspace:*",
6971
"immutable": "catalog:",
7072
"lodash": "catalog:",
7173
"prop-types": "catalog:",
7274
"react-immutable-proptypes": "catalog:",
73-
"uuid": "catalog:",
74-
"decap-cms-widget-richtext": "workspace:*"
75+
"uuid": "catalog:"
7576
},
7677
"peerDependencies": {
7778
"react": "catalog:",

packages/decap-cms-app/src/extensions.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import DecapCmsWidgetMap from 'decap-cms-widget-map';
2727
import DecapCmsWidgetDatetime from 'decap-cms-widget-datetime';
2828
import DecapCmsWidgetCode from 'decap-cms-widget-code';
2929
import DecapCmsWidgetColorString from 'decap-cms-widget-colorstring';
30+
import DecapCmsWidgetUuid from 'decap-cms-widget-uuid';
3031
// Editor Components
3132
import image from 'decap-cms-editor-component-image';
3233
// Locales
@@ -59,6 +60,7 @@ CMS.registerWidget([
5960
DecapCmsWidgetDatetime.Widget(),
6061
DecapCmsWidgetCode.Widget(),
6162
DecapCmsWidgetColorString.Widget(),
63+
DecapCmsWidgetUuid.Widget(),
6264
]);
6365
CMS.registerEditorComponent(image);
6466
CMS.registerEditorComponent({
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Change Log
2+
3+
All notable changes to this project will be documented in this file.
4+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5+
6+
7+
**Note:** Version bump only for package decap-cms-widget-uuid
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# decap-cms-widget-uuid
2+
3+
UUID widget for Decap CMS.
4+
5+
## Options
6+
7+
- `read_only`: Prevent editing the generated UUID. Defaults to `true`.
8+
- `prefix`: String prepended to the generated UUID. Defaults to an empty string.
9+
- `use_b32_encoding`: Encode the UUID using unpadded, lowercase RFC 4648 Base32. Defaults to `false`.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "decap-cms-widget-uuid",
3+
"description": "Widget for UUID values in Decap CMS.",
4+
"version": "3.0.0",
5+
"homepage": "https://www.decapcms.org/docs/widgets/#uuid",
6+
"repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-uuid",
7+
"bugs": "https://github.com/decaporg/decap-cms/issues",
8+
"module": "dist/esm/index.js",
9+
"main": "dist/decap-cms-widget-uuid.js",
10+
"license": "MIT",
11+
"keywords": [
12+
"decap-cms",
13+
"widget",
14+
"uuid"
15+
],
16+
"sideEffects": false,
17+
"scripts": {
18+
"develop": "pnpm run build:esm --watch",
19+
"build": "cross-env NODE_ENV=production webpack",
20+
"build:esm": "cross-env NODE_ENV=esm babel src --out-dir dist/esm --ignore \"**/__tests__\" --root-mode upward"
21+
},
22+
"dependencies": {
23+
"base32-encode": "catalog:",
24+
"hex-to-array-buffer": "catalog:"
25+
},
26+
"peerDependencies": {
27+
"decap-cms-ui-default": "workspace:*",
28+
"prop-types": "catalog:",
29+
"react": "catalog:"
30+
}
31+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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/decaporg/decap-cms/issues/4539
29+
// SEE: https://github.com/decaporg/decap-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, collection, locale } = this.props;
39+
const defaultLocale = collection?.getIn(['i18n', 'default_locale']);
40+
const isTranslatable = field.get('i18n') === 'translate';
41+
const shouldGenerate = !locale || !defaultLocale || locale === defaultLocale || isTranslatable;
42+
43+
if (!value && shouldGenerate) {
44+
const prefix = field.get('prefix', '');
45+
const useB32Encoding = field.get('use_b32_encoding', false);
46+
const uuid = crypto.randomUUID();
47+
const uuidFormatted = useB32Encoding ? this.uuidToB32(uuid) : uuid;
48+
onChange(prefix + uuidFormatted);
49+
}
50+
}
51+
52+
uuidToB32 = uuid => {
53+
const bytes = hexToArrayBuffer(uuid.replace(/-/g, '') || '');
54+
const encodedUUID = base32Encode(bytes, 'RFC4648', { padding: false });
55+
return encodedUUID.toLowerCase();
56+
};
57+
58+
handleChange = e => {
59+
this._sel = e.target.selectionStart;
60+
this.props.onChange(e.target.value);
61+
};
62+
63+
render() {
64+
const { field, forID, value, classNameWrapper, setActiveStyle, setInactiveStyle } = this.props;
65+
const readOnly = field.get('read_only', true);
66+
67+
return (
68+
<input
69+
ref={el => {
70+
this._el = el;
71+
}}
72+
type="text"
73+
id={forID}
74+
readOnly={readOnly}
75+
style={{ fontFamily: 'monospace', opacity: readOnly ? '0.5' : '1.0' }}
76+
className={classNameWrapper}
77+
value={value || ''}
78+
onChange={this.handleChange}
79+
onFocus={setActiveStyle}
80+
onBlur={setInactiveStyle}
81+
/>
82+
);
83+
}
84+
}

0 commit comments

Comments
 (0)