StaticJsCMS / static-cms

A Git-based CMS for Static Site Generators
https://staticjscms.netlify.app/
MIT License
596 stars 52 forks source link

Support string / number only lists #97

Closed KaneFreeman closed 1 year ago

KaneFreeman commented 1 year ago

Can we have a List widget that's just an array of strings, because right now for the List component, it has to be an array of objects like this

image

but for the tags on my blog I just want an array of strings

image

Originally posted by @robigan in https://github.com/StaticJsCMS/static-cms/discussions/4#discussioncomment-4255287

robigan commented 1 year ago

I tried to make my own widget based off of the List widget but my dev environment kept crashing and I can hardly understand the code as it's just so vertical and not commented

robigan commented 1 year ago

Most likely my node version but I'll figure that out later

robigan commented 1 year ago

Actually @KaneFreeman what node version do you use?

KaneFreeman commented 1 year ago

Actually @KaneFreeman what node version do you use?

16

robigan commented 1 year ago

Hmm

robigan commented 1 year ago

Turns out it has got to do with some strange error in port number https://stackoverflow.com/questions/70854917/webpackdevserver-crashing-with-error-rsv1-must-be-clear

robigan commented 1 year ago
image

Getting somewhere

robigan commented 1 year ago
image

Getting somewhere

@KaneFreeman How would you propose implementing the removal of an item from an array

CamilleScholtz commented 1 year ago

I did this on netlifyCMS using the following extension:

// See: https://github.com/netlify/netlify-cms/issues/4646 -->
var ArrayControl = createClass({
    handleChange: function (e) {
        const separator = this.props.field.get("separator", ", ");
        this.props.onChange(e.target.value.split(separator));
    },

    render: function () {
        const separator = this.props.field.get("separator", ", ");
        var value = this.props.value;

        return h("input", {
            id: this.props.forID,
            className: this.props.classNameWrapper,
            type: "text",
            value: value ? value.join(separator) : "",
            onChange: this.handleChange,
        });
    },
});

var ArrayPreview = createClass({
    render: function () {
        return h(
            "ul",
            {},
            this.props.value.map(function (val, index) {
                return h("li", { key: index }, val);
            })
    );
    },
});

var schema = {
    properties: {
        separator: { type: "string" },
    },
};

CMS.registerWidget("array", ArrayControl, ArrayPreview, schema);
KaneFreeman commented 1 year ago

@KaneFreeman How would you propose implementing the removal of an item from an array

Make a copy of the array, then use splice.

const newValue = [...oldValue];
newValue.splice(index, 1);
robigan commented 1 year ago

I did this on netlifyCMS using the following extension:

// See: https://github.com/netlify/netlify-cms/issues/4646 -->
var ArrayControl = createClass({
  handleChange: function (e) {
      const separator = this.props.field.get("separator", ", ");
      this.props.onChange(e.target.value.split(separator));
  },

  render: function () {
      const separator = this.props.field.get("separator", ", ");
      var value = this.props.value;

      return h("input", {
          id: this.props.forID,
          className: this.props.classNameWrapper,
          type: "text",
          value: value ? value.join(separator) : "",
          onChange: this.handleChange,
      });
  },
});

var ArrayPreview = createClass({
  render: function () {
      return h(
          "ul",
          {},
          this.props.value.map(function (val, index) {
              return h("li", { key: index }, val);
          })
  );
  },
});

var schema = {
  properties: {
      separator: { type: "string" },
  },
};

CMS.registerWidget("array", ArrayControl, ArrayPreview, schema);

This is just a string separated by commas, neat implementation but am trying to integrate into the existing code base and use a UI similar to the one I have presented above, ideally without creating new components

robigan commented 1 year ago

@KaneFreeman How would you propose implementing the removal of an item from an array

Make a copy of the array, then use splice.

const newValue = [...oldValue];
newValue.splice(index, 1);

I mean UI wise

KaneFreeman commented 1 year ago

Ideally this would just be an extension of the existing List widget, which already handles addition, deletion and reordering. Best approach here would be to change the List widget to accept a string or number array in addition to an object array and simply render them as if they were objects with the respective widgets under them.

So an array of strings and an array of objects with one string widget in it would render identically, but save differently.

An array of numbers and an array of objects with one number widget in it would render identically, but save differently.

robigan commented 1 year ago

Very well then, cus I had 3 different ideas for implementation, but I'll work on that.

Btw Kane, for specifying this in config.yml, should I use a field: widget_config or just the standard fields: widget_config[]? Note that NetlifyCMS supports both field and fields on the list component.

Supporting field makes more sense as it explicitly states that you have to save as an array of numbers and strings, but then in that case should I throw an error if specifying a widget_config on the field property that isn't a string/number widget? Or just handle it like it was a fields property?

robigan commented 1 year ago

@KaneFreeman I have tried the best to my knowledge of the code base to implement this feature for the List widget, I would appreciate if you could look at my branch and figure out what has to be done for the value field to be correctly passed because I am unsure about how to do it at this point.

KaneFreeman commented 1 year ago

I've been thinking on this some more, and re-adding field creates some issues elsewhere in the app too, not just in the widget itself. field was used by NetlifyCMS's select, object and list widgets, and I removed it during the rewrite to attempt and simplify the setup. I don't quite like the idea of reverting that simplification.

So after thinking it over, I believe the best and simplest approach would be to add some simple logic to the input/output of the list widget that when a single field exists in the list's fields it stores it DOES NOT wrap the value in an object. This will keep the logic for this change isolated to the list widget.

Examples

Single Field

Field Setup

name: testimonials
label: Testimonials
widget: list
summary: '{{fields.quote}} - {{fields.author.name}}'
fields:
  - name: quote
    label: Quote
    widget: string
    default: Everything is awesome!

Output

testimonials:
  - A testimonial
  - Another testimonial
  - Yet one more testimonial

Multiple Fields

Field Setup

name: testimonials
label: Testimonials
widget: list
summary: '{{fields.quote}} - {{fields.author.name}}'
fields:
  - name: quote
    label: Quote
    widget: string
    default: Everything is awesome!
  - name: author
    label: Author
    widget: object
    fields:
      - name: name
        label: Name
        widget: string
        default: Emmet
      - name: avatar
        label: Avatar
        widget: image
        default: /img/emmet.jpg

Output

testimonials:
  - quote: A testimonial
    author:
      name: Bob
      avatar: /img/bob.jpg
  - quote: Another testimonial
    author:
      name: Sam
      avatar: /img/sam.jpg
  - quote: Yet one more testimonial
    author:
      name: Billy
      avatar: /img/billy.jpg
robigan commented 1 year ago

Hmmm, now that you say that it does make more sense, and I hadn't considered that it might cause issues elsewhere, but then in that case how would one do that? How would one implement the checks, and for that matter of fact, how does it even compute how to convert the data to YAML? I presume it's just one large tree of data that gets converted no?

KaneFreeman commented 1 year ago

So the format conversion is a separate process from the widgets, and not a concern for this issue. All the widget needs to concern itself with is its input (value) and its output (what is passed to onChange).

SeraphRoy commented 1 year ago

Hi, would like to know if there's any updates of this feature or is this on the roadmap? This would be super useful for people like me who use taxonomy like categories and tags in blog posts.

Appreciate your efforts here. This is a really awesome project!

denyskon commented 1 year ago

Hi, would like to know if there's any updates of this feature or is this on the roadmap? This would be super useful for people like me who use taxonomy like categories and tags in blog posts.

Appreciate your efforts here. This is a really awesome project!

Seems like there is something happening: https://github.com/StaticJsCMS/static-cms/tree/feature/string-array-list-widget

robigan commented 1 year ago

Family has gotten in the way, and I've abandoned work on this for now because I do not possess the necessary knowledge of the codebase to make such a change, given that everything I tried tweaking was always breaking something else. But as @denyskon mentioned, there seems to be an official feature branch. I switched to TinaCMS, but it doesn't have native support for a JAMstack with Hugo, so I might switch back.

KaneFreeman commented 1 year ago

Yes I am working on this feature. I'm testing out a couple of ideas for how it should work, and am getting close to a workable solution. Hopefully in the next couple of weeks this will be done.

KaneFreeman commented 1 year ago

This has been added in 1.0.0-beta.12. I ended up going with a more generalized approach. Any list that only has one field will be treated as a singleton array. So a list widget with one string field will result in an array of strings. If the child widget is a number widget then the result will be an array of numbers.

KaneFreeman commented 1 year ago

Example: https://staticjscms.netlify.app/docs/widget-list#singleton-list-list-of-strings