decaporg / decap-cms

A Git-based CMS for Static Site Generators
https://decapcms.org
MIT License
17.92k stars 3.04k forks source link

Feature: edit data files with a list/array at root level #531

Open rriemann opened 7 years ago

rriemann commented 7 years ago

- Do you want to request a feature or report a bug?

Feature

- What is the current behavior?

it seems to be impossible to create data files with a list at root level:

- nav_object_1
- nav_object_2

The current work-around is to use

navigation:
  - nav_object_1
  - nav_object_2

However, this may require a change of the template that may be part of libraries (jekyll theme gems).

- What is the expected behavior?

I can just create lists also on root level.

erquhart commented 7 years ago

Thanks for opening this - it's definitely something folks have needed.

I think the most straightforward approach would be to accept a boolean root config value on the list widget (this doesn't make much sense for any other widget type). If root is true, the resulting data is a list rather than an object. We would need validation to ensure that no other fields are defined if a "root" field is provided, and we would need to start handling frontmatter and data that are lists and not just objects as we do currently.

This probably conflicts with https://github.com/netlify/netlify-cms/pull/468 as well, where I believe we're outmoding the list widget in favor of a "repeatable" flag for all fields, in which case any "repeatable" field could have a "root" field (but still only one field defined if there's a root).

Would you be up for taking a swing at this?

rriemann commented 7 years ago

Would you be up for taking a swing at this?

Unfortunately I'm super busy with university. :(

erquhart commented 7 years ago

No problem! I'm sure someone will take it on.

r1b commented 7 years ago

I would be happy to implement this - currently it is not possible to use gatsby-transformer-yaml with netlify since the transformer only supports lists.

Any pointers on where to look in the code to get started? I'm pretty new to netlify.

r1b commented 7 years ago

I have solved this on the gatsby side with a custom yaml transformer. I would still like to support it here for maximum flexibility :)

erquhart commented 5 years ago

Forget the boolean thing I said before, we should apply the field/fields approach from the list widget to the collection config.

fields vs. field

Here's the list widget using fields:

# list field config using `fields`
- name: my-list
  label: My List
  widget: list
  fields: [{ name: title, label: title, widget: string }]

# output:
# list: [{ title: 'a title' }, { title: 'another title' }]

Here's the same widget config using field:

# list field config using `field`
- name: my-list
  label: My List
  widget: list
  field: [{ name: title, label: title, widget: string }]

# output
# list: ['a title', 'another title']

field on collections

Note: this is a proposal, doesn't actually work!

- name: posts
  label: Posts
  file: data/list.json # this only makes sense for file collections
  field: [{ name: name, label: Name, widget: string }]

# output
['item', 'another item']

The UI and certain config options (like allowAdd) from the list widget would be reused for at the collection level for this.

I can't think of any downsides or issues with this approach, personally. Thoughts?

kripod commented 5 years ago

Are there any updates regarding the proposals mentioned above? This is a deal-breaker for Gatsby users, please see #1282.

talves commented 5 years ago

@kripod I believe this is a feature the CMS should support, Until then, it is NOT a deal-breaker for Gatsby users when it is still able to map to a structure the CMS does support at the moment.

stale[bot] commented 5 years ago

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.

erquhart commented 4 years ago

This is still happening.

bharrisau commented 4 years ago

I've just patched this locally in netlify-cms-core/src/backend.ts functions entryToRaw and entryWithFormat. Around the calls to either format.toFile or format.fromFile I'm just doing a wrap/unwrap with a special key rootArray.

  entryWithFormat(collectionOrEntity: unknown) {
    return (entry: EntryValue): EntryValue => {
      const format = resolveFormat(collectionOrEntity, entry);
      if (entry && entry.raw !== undefined) {
        let data = (format && attempt(format.fromFile.bind(format, entry.raw))) || {};
        let data = Array.isArray(data) ? { rootArray: data } : data; //Added wrapper for root arrays
        if (isError(data)) console.error(data);
        return Object.assign(entry, { data: isError(data) ? {} : data });
      }
      let data = format.fromFile(entry);
      return Array.isArray(data) ? { rootArray: data } : data; //Added wrapper for root arrays
    };
  }

  entryToRaw(collection: Collection, entry: EntryMap): string {
    const format = resolveFormat(collection, entry.toJS());
    const fieldsOrder = this.fieldsOrder(collection, entry);
    let data = entry.get('data').toJS();
    let data = data.rootArray || data; // Unwrap root array (this should probably check the fields.length == 1 too)
    return format && format.toFile(data, fieldsOrder);
  }

Not as clever as the approach mentioned above - but if you think of it as normalising files to always be a map instead of sometimes passing arrays around then it feels a bit less hacky.

cyonder commented 4 years ago

No fix on this since 2017? )=

erezrokah commented 4 years ago

Hi @cyonder, we try to get to the most up voted features first. You can see the sorted list here https://github.com/netlify/netlify-cms/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc. Regardless, we would love a contribution for this.

reimertz commented 4 years ago

I just tried to see if I could get the ball rolling on this issue.

  1. Parse the single entry file to get array of content
  2. Based on that content, generate new "real" entries
  3. Populate the collection with previously generated entries

Doing edits / adding new content would be similar to above but in reversed order.

But the assumptions of content being in separate files made it very complicated straight away but maybe my idea was overly complicated?

Would love to get some help / discussion with main contributors to the project. <3

erezrokah commented 4 years ago

Hi @reimertz, thanks for pushing this forward. What do you think about putting a draft PR for it? We can use it for the discussion.

Another option is our #contributing channel on Slack https://www.netlifycms.org/chat.

vnourdin commented 3 years ago

Hi everyone, any update on this ? This seems blocking for Eleventy global data files too, if anyone has a workaround for this I'm interested :sweat_smile:

erezrokah commented 3 years ago

Hi @vnourdin this would make a great contribution to the CMS if anyone would like to pick it up

ManUtopiK commented 3 years ago

Just to said this is also an issue for @nuxt/content with array at the root level of a json. Like here.

hayden2114 commented 2 years ago

+1 would love to see support for .json files with array at root level to mimic a MongoDB collection

astrawnuts commented 2 years ago

would love to see support for this

izanagi1995 commented 2 years ago

Still needed :)

TheReyzer commented 2 years ago

NEEEEED

CKLFish commented 1 year ago

Need it....

reteps commented 1 week ago

Here is a workaround:

            CMS.registerCustomFormat('flat-json', 'json', {
                fromFile: text => {
                    const body = JSON.parse(text) as Record<string, Record<string, string>>;
                    // Now we want to transform this object into a flat object with a 'key' field
                    const value = { items: Object.entries(body).reduce((acc, [key, value] : [
                        string,
                        Record<string, string>
                    ]) => {
                        acc.push({ key, ...value });
                        return acc;
                    }, [] as Record<string, string>[]) };
                    return value;
                },
                toFile: (value : { items: Record<string, string>[] }) => {
                    const body = value.items.reduce((acc, { key, ...rest }) => {
                        acc[key] = rest;
                        return acc;
                    }, {} as Record<string, Record<string, string>>);
                    return JSON.stringify(body, null, 2);
                }
            });
   - name: 'redirects-collection'
    label: 'Redirects'
    format: 'flat-json'
    files:
    - label: 'All Redirects'
      file: 'sigpwny.com/src/redirects.json'
      name: 'redirects'
      fields:
        - {label: 'Links', name: 'items', widget: 'list', fields: [
            # must be named 'key' for our custom formatter to work
            {label: 'From', name: 'key', widget: 'string', hint: 'Example: "/qrcode"'},
            {label: 'To', name: 'destination', widget: 'string', hint: 'Example: "myurl.com/foo"'},
            {label: 'Status Code', name: 'status', widget: 'hidden', default: 302}
          ]}

If you have a folder collection, you need a title field, and then your fromFile should add one.