diff --git a/CHANGELOG.md b/CHANGELOG.md index c7c540e1..949940c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ way to update this template, but currently, we follow a pattern: --- +## Upcoming version +* [fix] Improve slug creation (slashes were breaking rendering in some environments) + [#850](https://github.com/sharetribe/flex-template-web/pull/850) + ## v1.0.0 * [change] Migrate remaining Redux Forms to Final Form. Also now all the form components can be diff --git a/package.json b/package.json index 874012c9..07a5f271 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "0.3.1", + "version": "1.0.0", "private": true, "license": "Apache-2.0", "dependencies": { diff --git a/src/util/urlHelpers.js b/src/util/urlHelpers.js index c21875eb..a873ba96 100644 --- a/src/util/urlHelpers.js +++ b/src/util/urlHelpers.js @@ -5,13 +5,55 @@ const { LatLng, LatLngBounds } = sdkTypes; export const LISTING_PAGE_PENDING_APPROVAL_VARIANT = 'pending-approval'; -export const createSlug = str => - encodeURIComponent( - str - .toLowerCase() - .split(' ') - .join('-') +// Create slug from random texts +// From Gist thread: https://gist.github.com/mathewbyrne/1280286 +export const createSlug = str => { + let text = str + .toString() + .toLowerCase() + .trim(); + + const sets = [ + { to: 'a', from: 'ÀÁÂÃÄÅÆĀĂĄẠẢẤẦẨẪẬẮẰẲẴẶ' }, + { to: 'c', from: 'ÇĆĈČ' }, + { to: 'd', from: 'ÐĎĐÞ' }, + { to: 'e', from: 'ÈÉÊËĒĔĖĘĚẸẺẼẾỀỂỄỆ' }, + { to: 'g', from: 'ĜĞĢǴ' }, + { to: 'h', from: 'ĤḦ' }, + { to: 'i', from: 'ÌÍÎÏĨĪĮİỈỊ' }, + { to: 'j', from: 'Ĵ' }, + { to: 'ij', from: 'IJ' }, + { to: 'k', from: 'Ķ' }, + { to: 'l', from: 'ĹĻĽŁ' }, + { to: 'm', from: 'Ḿ' }, + { to: 'n', from: 'ÑŃŅŇ' }, + { to: 'o', from: 'ÒÓÔÕÖØŌŎŐỌỎỐỒỔỖỘỚỜỞỠỢǪǬƠ' }, + { to: 'oe', from: 'Œ' }, + { to: 'p', from: 'ṕ' }, + { to: 'r', from: 'ŔŖŘ' }, + { to: 's', from: 'ߌŜŞŠ' }, + { to: 't', from: 'ŢŤ' }, + { to: 'u', from: 'ÙÚÛÜŨŪŬŮŰŲỤỦỨỪỬỮỰƯ' }, + { to: 'w', from: 'ẂŴẀẄ' }, + { to: 'x', from: 'ẍ' }, + { to: 'y', from: 'ÝŶŸỲỴỶỸ' }, + { to: 'z', from: 'ŹŻŽ' }, + { to: '-', from: "·/_,:;'" }, + ]; + + sets.forEach(set => { + text = text.replace(new RegExp(`[${set.from}]`, 'gi'), set.to); + }); + + return encodeURIComponent( + text + .replace(/\s+/g, '-') // Replace spaces with - + .replace(/[^\w-]+/g, '') // Remove all non-word chars + .replace(/--+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, '') // Trim - from end of text ); +}; /** * Parse float from a string diff --git a/src/util/urlHelpers.test.js b/src/util/urlHelpers.test.js index e6b2b94f..fa1ad4ad 100644 --- a/src/util/urlHelpers.test.js +++ b/src/util/urlHelpers.test.js @@ -1,5 +1,6 @@ import { types as sdkTypes } from './sdkLoader'; import { + createSlug, parseFloatNum, encodeLatLng, decodeLatLng, @@ -15,6 +16,32 @@ const SPACE = encodeURIComponent(' '); const COMMA = encodeURIComponent(','); describe('urlHelpers', () => { + describe('parseFloatNum()', () => { + it('handles empty string (returns "")', () => { + expect(createSlug('')).toEqual(''); + }); + + it('handles space characters', () => { + expect(createSlug('ice hockey tournament')).toEqual('ice-hockey-tournament'); + }); + + it('handles special characters', () => { + expect(createSlug('ice hockey!%/@$€ for the win')).toEqual('ice-hockey-for-the-win'); + }); + + it('handles multiple "-"', () => { + expect(createSlug('testing ---- dashes')).toEqual('testing-dashes'); + }); + + it('handles umlauts', () => { + expect(createSlug('jääkiekko / pesäpallo')).toEqual('jaakiekko-pesapallo'); + }); + + it('handles Emojis', () => { + expect(createSlug('smiling 💩 emoji')).toEqual('smiling-emoji'); + }); + }); + describe('parseFloatNum()', () => { it('handles empty value', () => { expect(parseFloatNum('')).toBeNull();