decaporg / decap-cms

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

Make sluggification visible, customizable, and fail-safe #445

Open erquhart opened 7 years ago

erquhart commented 7 years ago

Default sluggification is simplistic, relying on a title field and performing no inference whatsoever.

With no field named "title", entries currently fail to save.

The ideal fix is to simply make slugs editable in the editor UI.

Considerations:

Benaiah commented 7 years ago

@erquhart how does the slug entry in collections affect sluggification - is it functional anymore? (I've been working under the assumption that the current behavior is that the title field is sluggified and turned into the {{slug}} part of the slug template).

erquhart commented 7 years ago

Yep, that's the default behavior, but date segments can be used for some light slug templating: https://github.com/netlify/netlify-cms/blob/e388ed1721576221523776bfcf25f7124b5f116c/src/backends/backend.js#L27-L44

erquhart commented 7 years ago

May happen in tandem with #180.

tech4him1 commented 7 years ago

We currently overwrite any existing entry when a new entry is created that has the same slug. This is probably best fixed along with this issue.

tech4him1 commented 7 years ago

I also think we should throw an error if the slug is going to be blank.

aperep commented 7 years ago

It is a bad idea to silently overwrite entries. If a user creates multiple entries on same day and does not bother about titles, then all entries except last will be lost without any notification. This is easily the worst imaginable behaviour.

t1merickson commented 6 years ago

I wonder if we should change or prioritize how we visualize the 'title' field to imply it's importance

erquhart commented 6 years ago

We're actually looking to eliminate it's importance. Right now it's critical because it's used for automated slug creation, which the user can't see or influence. This issue calls for making slug creation interactive. We could pre-fill with whatever's in a title field, but if there's no field (or the slug is blank for any other reason), the entry would fail validation and could not be saved.

bjrn commented 6 years ago

Hi! What's the status on slug editing, anyone working on a suggestion?

erquhart commented 6 years ago

@bjrn no one is working this yet, definitely open to someone taking a stab at it.

robsonsobral commented 6 years ago

May I point something? Some SSGs allow us to overwrite the filename based slug. This is speacially useful for Hugo multilingual files. We can have a travel.en.md file with slug: "travel" on front matter and a travel.pt.md file with slug: "viagem".

Can we avoid conflicts between the two things?

erquhart commented 6 years ago

@robsonsobral I'm not sure what you're saying Netlify CMS should do differently to accommodate for this. Can you explain further?

Update: Your comment on #1063 made it click for me, you're saying filenames and slugs shouldn't be assumed to map 1:1. This is a great point, I'll add it to the OP.

Would you have time to write up some thoughts on what setting both custom slugs and filenames could look like from your perspective?

erquhart commented 6 years ago

Actually, @robsonsobral, that already works. If you need a "slug" frontmatter property for Hugo, just add it to your config and the CMS won't pick it up or use it at all. Slugs, for our purposes, are related to the source file name only, and your SSG is concerned with the slug for the built file name.

robsonsobral commented 6 years ago

Thank you for your attention, @erquhart . Please, forgive my late answer.

What will happen if there's a slug field on front matter, after you allow the edition of slug? How to deal with two fields with the same name?

erquhart commented 6 years ago

The editable slug will be an internal value, we won't output it to frontmatter. It should be made available for reuse at some point, though, as requested in #450 and #1063.

tech4him1 commented 6 years ago

As I see it, you can use it in frontmatter if you want, but you could also set your own string. I'm not saying this is exactly how it will work, but a possible config format would be:

# No output to frontmatter.
- {label: "Slug", widget: "slug"}

# Save generated slug in frontmatter.
- {label: "Slug", widget: "slug", name: "slug"}

# Manual slug in frontmatter.
- {label: "Slug", widget: "string", name: "slug"}
robsonsobral commented 6 years ago

Can we customize the field label? If we have a slug field on frontmatter, we could and with confusing labels.

erquhart commented 6 years ago

I'm not expecting there to be a typical field configuration for the slug editor, but I may be underthinking things. Either way, I'd expect output of the slug and other internal/meta values to be made available to fields as placeholder values via https://github.com/netlify/netlify-cms/issues/450. So you could do:

- {name: slug, widget: hidden, default: "{{slug}}"}
erquhart commented 6 years ago

@tech4him1 I wouldn't expect a slug widget - here's your example as I'd expect it:

# No output to frontmatter.
# (automatic)

# Save generated slug in frontmatter.
- {label: "Slug", name: "slug", default: "{{slug}}"}

# Manual slug in frontmatter.
# (same as above since the slug is editable as a meta field)

I can't think of a use case for allowing the slug to be configured as a field. You?

mistermantas commented 6 years ago

This should be tagged high priority or something.

I’ve just had to redo my entire frontmatter because of this single issue. Posts couldn’t be created and it wasn’t even obvious why.

I don’t think there needs to be a super special UI for the slug — perhaps the default behavior, as it is now for title, would be showing the slug in small lettering underneath that with an edit button? I think something like that is done with WordPress. Otherwise just expose it like any other field.

tech4him1 commented 6 years ago

Starting in v2.0 there is an error message for this -- at least an improvement on the silent breakage we had before.

mistermantas commented 6 years ago

That’s… not really the case in my experience. If there was no field like title (even if there was Title) it would try to publish and throw an error in console and that’s it.

Maybe that should be taken into account too.

tech4him1 commented 6 years ago

@mistermantas That should already be throwing an error. Would you mind opening a new issue with an example config so that I can get that to at least throw an error?

therealshark commented 6 years ago

It would also be nice if there was another placeholder like {{random}} generating a random string to use in the slug. I'd like to create a collections with items having no unique field, and just using a slug like slug: "{{year}}-{{month}}-{{day}}-{{hour}}_{{minute}}_{{second}}" feels a bit risky.

Another possibility to minimize the chance of colliding would be to add milliseconds as another field.

webmasterkai commented 5 years ago

Are we abstracting too much by calling it a slug instead of a filename or id? I think it is important the file name be thought of more as the resource identifier (id) for the item, not necessarily the slug value that will be used on a possible website. The primary focus of a CMS is about editing and managing content more than it is about creating a specific website.

In the old days of using prose to make editing GitHub hosted markdown files with front matter easier, the file name was simply a field you filled out when making a new item. Link to prose config docs. img

The simplest, most straightforward option for defining a file name is to have a form input field presented to the user. I'd like to see this be the default behavior when no slug property is provided to the collections config.

The next step in convenience is automatically creating the initial file name based on a template. I suggest passing the values of the fields to the template. So instead of {{ slug }} it would be {{ title }}. If you want a date add a date field. If you want the date in a special format use the the field date option for format in the config. Perhaps we add something like a slugFormat to the date field config options if the slug needs a different format from how it gets saved to the file. But that gets messy.

before

create: true
slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
fields:
  - {label: "Title", name: "title", widget: "string"}

after

create: true
slug: "{{date}}-{{title}}"
fields:
  - {label: "Title", name: "title", widget: "string"}
  - {label: "Created Date", name: "date", widget: "datetime", format: "YYYY-MM-DD"}

Fields used in the template should be required to prevent unexpected results. If you want a "random" element for the slug add a hidden field with a widget that creates a random string portion. Access its value in the template like any other field.

erquhart commented 5 years ago

Are we abstracting too much by calling it a slug instead of a filename or id? I think it is important the file name be thought of more as the resource identifier (id) for the item, not necessarily the slug value that will be used on a possible website.

This 👆👆

@webmasterkai I was glad to see your comment as I recently came to the same conclusion: we should not be calling the filename "slug". It's confusing, and I'm betting most folks thumbing this issue up are thinking about the path to their live content and not the filename in the repo. @robsonsobral made a similar point a while back as well.

The more I consider this, the more I realize this whole slug editing thing is almost a non-issue. Static site generators don't care about filenames much (except for extensions), and they typically derive the actual slug and URL from your frontmatter and configuration.

Example I just gave in https://github.com/netlify/netlify-cms/issues/1576#issuecomment-459885997:

For example, let's say you have a Hugo site, a blog post frontmatter might look like this:

---
title: A Clear Performance Comparison between Gatsby and WordPress
slug: gatsby-vs-wordpress-performance
---

And your Hugo config would include something like:

permalinks:
  posts: /:year/:month/:slug/

Anyone think I'm oversimplifying here? Example use cases that require more?

@barthc I'd especially like to hear from you considering your recent work in this area.

marcojakob commented 5 years ago

@erquhart I think static site generators do care a lot about filenames (and folder names).

The first choice is to use the filename for the output path. See for example in Hugo's Content Organization:

Hugo assumes that the same structure that works to organize your source content is used to organize the rendered site.

The slug in frontmatter is only meant to override this behavior if needed. Otherwise it's much better to use the folder and filename as a path. Unrelated file names would make it unnecessary hard to find the content file for a specific blog post, for example.

Not only file names matter but also folder names. As is discussed in #1472 with so called Page Bundles, assets and markdown files are stored in a folder. The entire folder will be copied and rendered to the final site.

content/
├── posts
│   ├── my-post-1
│   │   ├── index.md
│   │   └── post-image.jpg
│   ├── my-post-2
│   │   ├── index.md
│   │   └── post-image.jpg
│   ├── _index.md
│   └── overview-image.jpg

will render to:

public/
├── posts
│   ├── my-post-1
│   │   ├── index.html
│   │   └── post-image.jpg
│   ├── my-post-2
│   │   ├── index.html
│   │   └── post-image.jpg
│   ├── index.html
│   └── overview-image.jpg

For sluggification to work with this, Netlify CMS would need to allow changing the folder name.

erquhart commented 5 years ago

That's a very good point, thanks for laying it out. I guess I mostly see sites using custom permalinks via frontmatter rather than going with filename driven defaults, so it didn't seem that relevant, but it's a smart default, agreed.

erquhart commented 5 years ago

Update:

Some SSG's, like Jekyll, respect a slug value in the frontmatter. Until recently the {{slug}} built in placeholder made it impossible to reference a field named slug in your slug configuration, but as of Netlify CMS 2.4.2 you can now do {{fields.slug}} to reference a field by that name. That means you can control your filename in a predictable way:

collections:
  - name: posts
    folder: posts
    slug: {{fields.slug}}
    fields:
      - {name: title, label: Title}
      - {name: slug, label: Slug}
      - {name: body, label: Body, widget: markdown}

Note: this does not provide any way to change the filename after the initial save.

It's manual, but it's technically a minimum viable approach that satisfies the goals of this issue:

We should keep the issue open for a more automated approach that's workable for non-technical editors, but this is at least a good stop gap for folks that need it.

erodrig commented 5 years ago

@erquhart that approach do not allow the field to be sluggified automatically from the title, it will need to be manually set each time. Can you please confirm. Ideally the field should be visible and updated once the title is set, but in this case if is empty it will fail, and will force manual entry. In a different note, without talking about slug update, it will be nice to be able to create a field with the contents of the slug field just like path : "/example/{{slug}}"

erquhart commented 5 years ago

@erodrig yep, just providing a newly available stopgap. Issue remains open for an automated solution.

Sent with GitHawk

srsgores commented 5 years ago

What's the status on this? Can anyone provide an update? Is it at all possible to save the relation value as the slug?

erquhart commented 5 years ago

That's always been possible, just use the name of your relation field in the slug template. Let's say you have a relation field named "relation", your slug template would be {{relation}}.

verythorough commented 5 years ago

Tied this to https://github.com/netlify/netlify-cms/issues/1767, because files without frontmatter have their own particular sluggification issues.

stale[bot] commented 4 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.

adrian5 commented 4 years ago

I want to point out that the introduction of the path parameter introduces the problem of how to handle slug/frontmatter changes after the first save. E.g. /pages/{:year}/entry.md or /roster/{:team}/player.md remain at their current location, even if the dynamic portion of their path changes.

erezrokah commented 4 years ago

Hi @adrian5, thank you for pointing this out. I think the issue you mentioned is already present with the slug configuration as you could do slug: {{year}}-{{title}} but probably more prominent when using path.

hanneskuettner commented 4 years ago

I stumbled across this issue myself. Now that the config, entry data and collection is available to all control widgets (#3672) I was able to implement a custom slug control widget that allows the following:

... # whatever collection
- slug: '{{field.slug}}'
- fields:
  - name: title
     label: Title
     widget: string
  - name: slug
     label: Slug
     widget: slug
     slug: 'custom-slug-{{title}}'

This works very well for me (I think at least). It still does not provide a way for a user to forcefully update the filename through the slug though.

Once it is a bit more polished I will open a PR (in the next few days) and then I would love to continue this discussion.

keegan-lillo commented 4 years ago

@hanneskuettner I look forward to your PR. In the mean time, I came up with a fairly decent solution that might help others.

I used Nested Collections to basically give me the same effect since the slug (file name) is set via the meta.path field.

config.yaml

collections:
  - name: 'pages'
    create: true
    label: 'Page'
    folder: 'src/content/pages'
    nested: { depth: 100, summary: '{{meta.path}}' }
    meta: { path: { widget: string, label: 'Path', index_file: 'index' } }
    fields:
      - label: 'Title'
        name: 'title'
        widget: 'string'

      - label: 'Body'
        name: 'body'
        widget: 'markdown'

And then if I add two items in the collection with paths set to: dir-a/dir-b/item-a and dir-a/dir-c/item-b This results in a src/content/pages directory looking like:

src/content/pages:
  dir-a:
    dir-b:
      item-a:
        - index.md
    dir-c:
      item-b:
        - index.md

image

I'm using Gatsby so it's reasonably straightforward to generate slugs at build time using the technique from this guide.

mikestopcontinues commented 4 years ago

Here's the thing: Without keeping the filename in sync with the slug, it's impossible to look up a single post without parsing the frontmatter of all files in the same collection. E.g., if my app wants the post with slug what-a-nice-post, the app needs to load all of the posts into memory. This kinda sucks when used alongside NextJS, which supports incremental static generation as well as incremental cache invalidation.

For this reason—and since URL slugs must be unique anyway—I think it makes more sense to update the filename whenever the slug changes.

Regarding how and when the slug changes, I like @hanneskuettner approach. The slug should stop auto-updating once the document is published (which happens at varying times depending on if you use the editorial workflow). After that, a notice is fair, as well as preventing a user from overwriting another file or making a change that would cause merge errors (if there are multiple editorial edits open on the file that would be renamed).

Honestly, I think Wordpress really nails their slug behavior, especially in cases where extra bits are added to the complete path. #377 has a screenshot that at least partly shows this behavior.

@hanneskuettner If you would, I'd very much appreciate seeing your work on that slug field. :)

michalhudecek commented 3 years ago

What's the progress on this?

erezrokah commented 3 years ago

Hi @michalhudecek this issue is not an easy lift. Do you have a specific use case you're trying to solve?

michalhudecek commented 3 years ago

I would like to make the slug editable with a warning for editors that this might break already shared links.

erezrokah commented 3 years ago

I would like to make the slug editable with a warning for editors that this might break already shared links.

Will the approach in https://github.com/netlify/netlify-cms/issues/445#issuecomment-621700949 satisfy your use case?

michalhudecek commented 3 years ago

Yes, that seems like a perfect solution.

andreasbalevik commented 3 years ago

Just want to share. Problem in my case is that slug does not sync with the title.

I use jekyll with netlify cms. When creating a page in Netlify "Hello World Page" this will be saved as "hello-world-page-md". In jekyll this will be generated to "/hello-world-page.html" when the site is built. Problem is when changing the title of the page to "Hello Again World", the markdown is still "hello-world-page.md and therfore the generated page will be "/hello-world-page.html".

This causing problems with url not reflecting the page title. I think it is problematic to share a url not reflecting the content of the page.

Jekyll can overwrite the generated url with slug variable provided in font matter. Problem is i cant use jekylls liquid variables in font matter. So it wont be dynamic.

I could create a field "slug" in the netlify collection. But the only way that would work for me was if it mirrored title, and the field was hidden. So the solution described here might work for me aswell.

tylerbrostrom commented 3 years ago

Problem in my case is that slug does not sync with the title.

Updating slugs has to be an explicit action. Otherwise, content authors would unknowingly break backlinks all the time. You’d have to account for the update by generating redirect rules for the web server. Traditional CMSs—which may have access to the web server and thus could generate redirects—don’t even do this.

If you have access to the code (i.e. you’re not a non-technical content author), the most straightforward way is to just rename the file.

mikestopcontinues commented 3 years ago

@tylerbrostrom I think it should be explicit as well, but I'd much rather see this change happen in pieces than it be delayed another 4 years. The vast majority of users only have one content editor.

andreasbalevik commented 3 years ago

@tylerbrostrom i agree.

But this may result in a dillemma.

Not having relevant keywords in domain / url could hurt your relevant search scoore, preventing you from getting the 1st spot in search lists.

But as you mentions, broken backlinks may be a bigger problem. Backlinks also impact the search scoore.

I cant prove that this is the case. If anyone can confirm that the url does not affect the search result, then I have no idea what is best.

Something crossed my mind. With netlify you can create a _redirects file in root. May be complicated. But if Netlify CMS could manage a file like that. Then changing the slug wouldnt be a problem with already shared links. Just a crazy thought.

mikestopcontinues commented 3 years ago

@andreasbalevik I'd prefer a formerSlugs array on the documents themselves for the user to handle based on platform. Some will need redirects, some won't because they're the location of new documents.