-
-
Notifications
You must be signed in to change notification settings - Fork 494
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Slug filter doesn't create url safe slugs. #278
Comments
I just noticed a similar problem with commas in the title - it leaves the commas in for the automatically slugified permalink. |
Oh, hmm. Well that’s not ideal. I did try |
I think the point is more that I'd expect apostrophes to be be stripped by the filter. And that it would generally make 'nice' urls. In my case this was most obvious when using automatic anchor links from headings - or from tags from tag pages. |
Well as much as I agree with you, this would be a breaking change and would require a major version bump. So I'm trying to figure out the exact impact of this to determine priority. It doesn't seem to be breaking anything in the test case I tried. |
Oh totally! Quite a bad breaking change too. Even with a major version bump I suspect you'll want / need to offer a compatibility mode. Although I it only impacts a certain percentage of links, and users will likely have avoided using them, it has the potential to screw up lots of links. Some thoughts:
FWIW, I think I prefer the |
Out of curiosity:
I think it makes sense to expose config/options on the built in filter (like @edwardhorsford says above), opt-in only; keep the defaults as-is and avoid breaking changes. |
@jevets the latter - a new filter with a new name. FWIW I went with I'm not sure the long term aim should be to keep as-is - though changing it presents issues and challenges. Fundamentally (to me at least), this filter is for making slugs / urls. It takes arbitrary string input and outputs something that can be used safely as a slug / url. I think you could argue that it's a bug that it doesn't currently do that - some arbitrary strings create slugs and urls that cannot be used. Edit - here's the filter I'm now using: const string = require('string')
module.exports = function(input) {
if (!input) return false;
else return string(input).slugify().toString();
} |
@edwardhorsford Did you managed to also generate the folders with this new slug function? If so, care to explain how? I'm also having this issue where punctuation keeps adding noise to the URL, which looks odd and is awful for usability (imagine an user having to nitpick punctuations from the URL instead of just typing alphanumeric and dashes). This:
Versus this:
What is easier to identify/read/type? Yeah... Medium seems to have an answer. |
@kazzkiq I think you should be able to use custom filters in permalink generation - have you tried? |
Yep, it worked! So just in case anyone else ends up here, here is how to fix your slug function:
// .eleventy.js
eleventyConfig.addFilter('slugify', input => {
if (!input) { return false };
return string(input).slugify().toString();
});
|
Thanks for sharing @kazzkiq! I wonder if you could just use slug? Does it overwrite the built in filter? I feel like that should work if it doesn't |
yup @zachleat , it does replace the slug, so I just do remove some chars to make it little bit prettier tho const slugify = require("slugify");
eleventyConfig.addFilter("slug", (input) => {
const options = {
replacement: "-",
remove: /[&,+()$~%.'":*?<>{}]/g,
lower: true
};
return slugify(input, options);
}); |
Excellent, thank you @okitavera. |
I’m going to move this into the new feature queue and it is logged for the next major version milestone. |
This repository is now using lodash style issue management for enhancements. This means enhancement issues will now be closed instead of leaving them open. View the enhancement backlog here. Don’t forget to upvote the top comment with 👍! |
@okitavera Thanks for posting that solution. It works great for the post titles. @zachleat or anyone: Is there a suggestion for how to make @okitavera 's solution work with markdown-it-anchor, as well? I'm not sure where to begin because I am a Javascript noob. Thanks! |
@bridgestew are you using If so your let opts = {
permalink: true,
permalinkClass: "direct-link",
permalinkSymbol: "#"
}; would be something like: const slugify = require("slugify");
let opts = {
permalink: true,
permalinkClass: "direct-link",
permalinkSymbol: "#",
// this is the same function shared above
slugify: function(input) {
const options = {
replacement: "-",
remove: /[&,+()$~%.'":*?<>{}]/g,
lower: true
};
return slugify(input, options);
}
}; Does that make sense? |
@zachleat It does and it worked. Thank you so much for the help! |
If a 'tag' has an apostrophe in the name it breaks Netlify's deploy script, which doesn't like special characters. The built-in 'slug' function in Eleventy doesn't fix apostrophes either 11ty/eleventy#278 Using the soltion there, I've updated 'slug' to be more inclusive and the new tag names are better
This is only partly related, but I got an error if the frontmatter
The error:
|
Hm, experience told me, that slugging is an area where it is better to rely on a library instead of rolling an own solution.
Maybe it makes sense to talk about the constrains here? |
For anybody else curious, here's the difference between the default
const slugify = require("@sindresorhus/slugify");
module.exports = (eleventyConfig) => {
eleventyConfig.addFilter("slugify", slugify);
return {...};
}; Or, if you want to replace the built-in Eleventy "slug" filter with a different implementation, just name your custom slugify filter "slug": const slugify = require("@sindresorhus/slugify");
eleventyConfig.addFilter("slug", slugify); NOTE: Only
UPDATE: For my own future reference, I managed to get async filters working in Nunjucks using eleventyConfig.addNunjucksAsyncFilter("slugify", async function(str, callback) {
// Where @sindresorhus/slugify is v2.1.0, which is an ESM module.
const slugify = await import("@sindresorhus/slugify");
callback(null, slugify.default(str.toString()));
}); If you're using Eleventy v1/Canary (which currently has [email protected]), this code works for an async LiquidJS filter or an .11ty.js template, but will return a Promise object in Nunjucks: eleventyConfig.addFilter("slugify", async (str) => {
const slugify = await import("@sindresorhus/slugify");
return slugify.default(str.toString());
}); // test.11ty.js
module.exports = {
async render(data) {
return `slug=${ await this.slugify("Peter deHaan 'tis an idiot (11ty.js)") }`;
}
};
|
Thank you very much.
Is this a reason not to use |
Oh, I wasn't suggesting 11ty change the default slugify library (although personally I think |
Awesome commentary y’all—I think the safest option (even in 1.0) will be to keep the existing This would also include a docs change to recommend the new filter as the default moving forward and deprecate the old one. That way we won’t break any sites or have to offer a compatibility mode or cumbersome link checker thing. |
I don't know. It isn't prohibitively difficult to use a different const slugify = require("@sindresorhus/slugify"); // v1
eleventyConfig.addFilter("slug", slugify); Curious if it'd be better to wrap the old or new slugify implementation in a custom 11ty-plugin-slug Plugin and people can opt in to the old or new behavior. What if Eleventy v1 switches to @sindresorhus/slugify@1 (non-ESM-promise version), and then people that complain that their URLs break, can opt into the brand new "@11ty/eleventy-slug-legacy" plugin. Unless we ship w/ both slugify and @sindresorhus/slugify dependencies (which I think is what you were proposing above anyways), and let people opt into one or the other using some new Eleventy config option. Maybe by default the old slugify module is used, but if you opt-in to the @sindresorhus/slugify version, you get the better i18n support and everything else. This way both libraries can use the "slug" filter name and you just specify if you want the new or old library. |
I would go for the breaking change in 1.0 because 2.0 might take time… 😅 I've already been using And I agree with @pdehaan that there could be a plugin for legacy behavior. I don't know if it's possible to use the ESM version of |
This is as close as I've ever gotten to using the ESM version in commonjs/Node. It seems to work for Liquid (9.1.3+ via eleventy@canary build) and Nunjucks (via module.exports = function (eleventyConfig) {
const slugifyFn = async (str) => {
// Import the ESM module...
const fn = await import("@sindresorhus/slugify");
return fn.default(str);
};
eleventyConfig.addFilter("slugify", (str) => slugifyFn(str));
// Note that the async Nunjucks filter uses the same name as the global `slugify` filter.
eleventyConfig.addNunjucksAsyncFilter("slugify", (str, callback) => {
slugifyFn(str)
.then((value) => callback(null, value))
.catch((err) => callback(err));
}); Definitely easier to stick with @sindresorhus/slugify v1 (pre-ESM). I tried, but could never figure out how to get Node's |
I've long since stopped using the Eleventy solution and gone with my own - so this doesn't directly impact me any more. Personally I think this should be a breaking change with an option to revert to the old behaviour. If there is a new filter, though I personally like |
I appreciate the pushback! To be clear here the Exposing a new name and a soft-default-change (docs update) to point to this new name has a bunch of benefits:
|
That said, we could also do a tiny opt-in |
#1873 is open for feedback! I stuck with the 1.x |
Purely for comedic purposes, but I think this might possibly work if we have a future [Eleventy v1] eleventyConfig.addFilter("slug", eleventyConfig.getFilter("slugify")); Yeah, it has great potential to break your existing URLs because it's a completely different implementation, but #YOLO. 💥 |
Another silly experiment… I hijacked the const assert = require("assert");
const inspect = require("util").inspect;
const slugify = require("@sindresorhus/slugify");
module.exports= (eleventyConfig) => {
const slugFn = eleventyConfig.getFilter("slug");
const slugErrors = new Set();
eleventyConfig.addFilter("slugify", slugify);
eleventyConfig.addFilter("slug", function (str="") {
const slugifyFn = eleventyConfig.getFilter("slugify");
const slugValue = slugFn(str);
try {
assert.strictEqual(slugValue, slugifyFn(str));
} catch (err) {
// Only display a unique error once.
if (!slugErrors.has(err.message)) {
console.error(`\nslug-vs-slugify filter mismatch for ${inspect(str)}\n${err.message}`);
slugErrors.add(err.message);
process.exitCode = 2;
}
} finally {
return slugValue;
}
});
return {
dir: {
input: "src",
output: "www"
}
};
}; INPUT---
title: Peter deHaan
permalink: "{{ title | slug }}/"
---
title={{ title }}
permalink={{ page.url }}
---
slug={{ title | slug }}
slugify={{ title | slugify }} OUTPUT (HTML)title=Peter deHaan
permalink=/peter-dehaan/
---
slug=peter-dehaan
slugify=peter-de-haan OUTPUT (Console)slug-vs-slugify filter mismatch for 'Peter deHaan'
Expected values to be strictly equal:
+ actual - expected
+ 'peter-dehaan'
- 'peter-de-haan'
^ So, if your site still builds successfully and doesn't exit with a non-zero code, I think it's probably safe to switch from using TLDR: I was bored and wanted to try building a slugify linter. UPDATE: If you want to migrate from eleventyConfig.addFilter("slug", () => {
throw new Error("`slug` filter is no longer supported. Please use `slugify`.");
}); Probably limited usefulness, but if you've already migrated to Or, you could just use the fancy alias technique from above so that both |
This PR is merged and |
@pdehaan I slightly modified your linter in a gist to link up from the docs: https://gist.github.com/zachleat/a58bc9e7273fc182a3c9c1234fee82c8 |
Just a small update here, @pdehaan your little script is going to live on in the 1.0 upgrade helper plugin: https://github.com/11ty/eleventy-upgrade-help/ Feel free to contribute over there! |
Moving the case sensitivity (and/or normalization) of tags to #2462, if y’all want to follow along! |
I've been using the
slug
filter to convert regular strings to strings suitable for use as urls. However I've realised it's not set up to remove apostrophes (and possibly other characters?).So
it's a test
becomesit's-a-test
, and when rendered with eleventy the url is/tags/it's-a-test/
- the ampersand breaks the url. It also looks ugly!I see that the package supports supplying a list of characters to remove.
If the intention is that this filter is used to create 'safe' urls, could Eleventy remove these by default?
As an alternative I've added my own filter using the
slugify()
option of the string library. I might stick with this anyway as it seems to do a good job of creating 'pretty' slugs.The text was updated successfully, but these errors were encountered: