Open Undistraction opened 5 years ago
@Undistraction what do you think about this as a potential solution: https://github.com/netlify/netlify-cms/issues/1409#issuecomment-458781037
Any progress on this feature? It's really needed for relations. CMS use cases like predefined taxonomies, separate author bios to be linked from posts, series collections with previous/last all need a relationship identifier that can survive editing the relationship target.
Hey @dopry - totally agree on priority, but no one has dug into it yet.
Tom mentioned this issue in gitter chat & I'd like to share my custom id widget here for those who needs to solve this issue right now: https://github.com/d4rekanguok/netlify-cms-widgets
Please note that this is an implementation of an alternative that @Undistraction has already mentioned above:
There is the alternative described in #1407 which involves creating a widget to dynamically supply a UID. However I think a more low-level solution is preferable. Relations should not be being declared based on properties that can be edited by a user. This solution allows a stable field to be created at creation time and set as a hidden widget to prevent user-editing.
A bit lack of doc (sorry), but the usage for the id widget is fairly simple. Here's an example:
In cms.js
// Register the widget
import cms from 'netlify-cms-app'
import { Widget as IdWidget } from '@ncwidgets/id'
cms.registerWidget(IdWidget)
cms.init()
In config.yml
collections:
- label: "Posts"
name: "posts"
folder: "_posts"
create: true
fields:
- label: ID
name: id
widget: ncw-id
prefix: post # will generate post-124hfkjas
timestamp: true # will generate 1245119112-post-124hfkjas
hint: This widget generate an unique read-only id
You can view a demo at https://custom-widgets.netlify.com/
Feedback & contribution is much, much appreciated.
Thanks so much for sharing @d4rekanguok!
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Not stale
This should really be integrated in the core netlify cms. I had a lot of headaches with relations...
Needed this too, after playing around for a while with the suggestions from this issue and some others, I made this widget and just added some style:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import uuidv4 from 'uuid/v4';
CMS.registerWidget(
'uuid',
class extends React.Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
forID: PropTypes.string,
value: PropTypes.node,
classNameWrapper: PropTypes.string.isRequired
};
static defaultProps = {
value: ''
};
componentDidMount() {
const { value, onChange } = this.props;
if (!value) {
onChange(uuidv4());
}
}
render() {
const styles = {
element: {display:"none"},
};
const { value, classNameWrapper, forID } = this.props;
return (
<span id={forID} className={classNameWrapper} style={styles.element}>
{value}
</span>
);
}
}
);
To also hide the label in the UI, I added some css
<style>
label[for*="uuid-field-"] {
display:none;
}
</style>
Would be pretty nice though if we had this in the core.
nanoid, https://github.com/ai/nanoid, may be a good optimization on uuid.
I also wish this should be in the core as well.
Thanks to @dopry and @madsem, here is my implementation
admin/index.html
to use.
// https://github.com/ai/nanoid
function nanoid(t = 21) {
return crypto.getRandomValues(new Uint8Array(t)).reduce((t, e) => (t += (e &= 63) < 36 ? e.toString(36) : e < 62 ? (e - 26).toString(36).toUpperCase() : e > 62 ? '-' : '_'), '')
}
const UuidWidget = createClass({
componentDidMount() {
const { value, onChange } = this.props
if (!value) {
onChange(nanoid())
}
},
render() {
const { value, classNameWrapper, forID } = this.props
return h(
'span',
{
id: forID,
style: { fontFamily: 'monospace', marginLeft: '1rem' },
//className: classNameWrapper
},
value
)
},
})
CMS.registerWidget('uuid', UuidWidget)
fields
in config.yml
- { label: 'UUID', name: 'uuid', widget: uuid }
Thank you, @lockevn, I like this solution very much, as I'm using the CDN version of Netlify CMS and this helps keep my implementation super simple.
Just one question: when I add this, each item in a collection needs to be saved via the Netlify CMS GUI in order for the UUID to be saved in the markdown file. This is a problem as I have many hundreds of files that need UUIDs. Is there a way to bulk save after adding this?
@pensivedog It sounds like what you're generally asking about is some form of schema migration. NetlifyCMS doesn't have any schema migration that I am aware of. In the interim, you would need to make a a script yourself to write the id to each file.
Any update on this, would be nice if this could be in core? :) Seems should be easy to add a simple id-ish widget and perhaps support both UUID and nanoid via some configuration.
@liufuyang feel free to submit a PR.
Hey @dopry, so I tried to port the String widget and made a simple UUID widget plus a few options that we really need in our place. Do you think this could be something nice to have here as a default UUID component? Thank you https://github.com/netlify/netlify-cms/pull/6675
I'm not a mantainer of netlify-cms. I would recommend that you fix your PR so that all the checks are passing, then a maintainer will be more likely to review and merge your code.
Before you spend any more time making a PR into this repository, I'd evaluate the "What is the future of netlify-cms" issue #6503 Also https://answers.netlify.com/t/is-this-project-dead/70988/61
So as this project is not dead, but just rebranded now, can we get a standard UUID widget, please?
Thanks for all the useful comments on this topic! I took bits a pieces from the work noted in this issue to build my own custom uuid widget for my set up. I'm using React 17, Next.js 13, Typescript, DecapCMS over the CDN, and CMS_MANUAL_INIT. Below are some code samples if anyone is struggling to adapt things for Typescript in a similar set up.
// utils/helpers.ts
import padStart from 'lodash/padStart'
import { randomFillSync } from 'crypto'
// Generate a UUID
// @returns string
export function generateUUID(): string {
const buffer = new Uint8Array(16)
randomFillSync(buffer)
buffer[6] = (buffer[6] & 0x0f) | 0x40
buffer[8] = (buffer[8] & 0x3f) | 0x80
return (
padStart(buffer[0].toString(16), 2, '0') +
padStart(buffer[1].toString(16), 2, '0') +
padStart(buffer[2].toString(16), 2, '0') +
padStart(buffer[3].toString(16), 2, '0') +
'-' +
padStart(buffer[4].toString(16), 2, '0') +
padStart(buffer[5].toString(16), 2, '0') +
'-' +
padStart(buffer[6].toString(16), 2, '0') +
padStart(buffer[7].toString(16), 2, '0') +
'-' +
padStart(buffer[8].toString(16), 2, '0') +
padStart(buffer[9].toString(16), 2, '0') +
'-' +
padStart(buffer[10].toString(16), 2, '0') +
padStart(buffer[11].toString(16), 2, '0') +
padStart(buffer[12].toString(16), 2, '0') +
padStart(buffer[13].toString(16), 2, '0') +
padStart(buffer[14].toString(16), 2, '0') +
padStart(buffer[15].toString(16), 2, '0')
)
}
// cms/Editor/widgets.tsx
import { Component } from 'react'
import { generateUUID } from '@/utils/helpers'
interface uuidWidgetProps {
value: string
onChange: (value: string) => void
}
const uuid = {
name: 'uuid',
control: class widgetControl extends Component<uuidWidgetProps> {
componentDidMount() {
const { value, onChange } = this.props
if (!value) {
onChange(generateUUID())
}
}
render() {
const { value } = this.props
return <input type="text" value={value} readOnly={true} />
}
},
}
export default [uuid]
// pages/admin/index.tsx
// @ts-nocheck
import { useEffect } from 'react'
import Script from 'next/script'
import { config } from '@/cms/config'
import components from '@/cms/Editor/components'
import widgets from '@/cms/Editor/widgets'
const AdminPage = () => {
useEffect(() => {
if (typeof window !== 'undefined') {
window.CMS_MANUAL_INIT = true
setTimeout(() => {
const { initCMS: init } = window
if (typeof init !== 'undefined') {
CMS.init({ config })
// Import editor components
components.forEach((component) => {
CMS.registerEditorComponent(component)
})
// Import editor widgets
widgets.forEach((widget: any) => {
CMS.registerWidget(widget.name, widget.control)
})
}
}, 1000)
}
}, [])
return (
<>
<Script src="https://identity.netlify.com/v1/netlify-identity-widget.js" />
<Script src="https://unpkg.com/decap-cms@3.0.12/dist/decap-cms.js" />
</>
)
}
export default AdminPage
Is your feature request related to a problem? Please describe. This is an issue to flesh out the discussion in #1407. It aims to describe how the discussed solution would be implemented. The goals are:
slug
field.uuid
placeholder that is replaced by a UUID.default
field.Proposed Solution
uuid
field to the placeholder tokens supported by the slug formatter.uuidv1()
.default
value resolution.default
field.defaultValue
.There are some further questions, but I don't think they necessarily need to be resolved now and could be added later to keep this feature small and focussed:
uuid
prop as the value forvalueField
?Note: I have a little time over the next week and should be able to get this done if the above is acceptable.
Describe alternatives you've considered There is the alternative described in #1407 which involves creating a widget to dynamically supply a UID. However I think a more low-level solution is preferable. Relations should not be being declared based on properties that can be edited by a user. This solution allows a stable field to be created at creation time and set as a
hidden
widget to prevent user-editing.