Skip to content
This repository was archived by the owner on Jan 31, 2019. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .babelrc

This file was deleted.

15 changes: 5 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,31 +47,27 @@
}
},
"devDependencies": {
"@types/chai": "^3.5.2",
"@types/chai": "^4.0.0",
"@types/mocha": "^2.2.41",
"@types/node": "^7.0.18",
"@types/sinon": "^2.2.1",
"@types/sinon-chai": "^2.7.28",
"babel-preset-env": "^1.4.0",
"babel-preset-stage-3": "^6.24.1",
"chai": "^4.0.0",
"condition-circle": "^1.5.0",
"cz-conventional-changelog": "^2.0.0",
"husky": "^0.13.3",
"mocha-suite": "^1.0.9",
"re-start": "^1.14.6",
"sinon": "^2.3.2",
"re-start": "^1.14.8",
"sinon": "^2.3.4",
"sinon-chai": "^2.10.0",
"start-babel-cli": "^4.0.1",
"start-simple-cli": "^4.1.1",
"tslint": "^5.2.0",
"tslint-config-groupby": "^1.0.0",
"typescript": "^2.3.2",
"typings": "^2.1.1",
"webpack": "^2.5.1"
},
"dependencies": {
"@storefront/core": "dev",
"@storefront/flux-capacitor": "dev",
"@storefront/flux-capacitor": "^1.0.0",
"@types/deep-assign": "^0.1.0",
"@types/js-cookie": "^2.0.28",
"@types/loglevel": "^1.4.29",
Expand All @@ -85,7 +81,6 @@
"lodash.kebabcase": "^4.1.1",
"loglevel": "^1.4.1",
"query-string": "^4.3.4",
"redux": "^3.6.0",
"riot": "^3.4.4",
"urijs": "^1.18.10"
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/decorators.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CORE } from '../globals';
import StoreFront from '../storefront';
import Tag from '../tag';
import { CORE } from './system';

export const core = (target) => { target[CORE] = true; };

Expand Down
19 changes: 19 additions & 0 deletions src/core/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,25 @@ const DEFAULTS: Partial<Configuration> = {
services: {
logging: {
level: 'debug'
},
url: {
beautifier: {
refinementMapping: [],
params: {
refinements: 'refinements',
page: 'page',
pageSize: 'page_size'
},
queryToken: 'q',
suffix: '',
useReferenceKeys: true,
navigations: {}
},
routes: {
search: '/search',
details: '/details',
navigation: '/navigation'
}
}
},

Expand Down
5 changes: 3 additions & 2 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Service } from '../services';
import Tag from '../tag';
import Configuration from './configuration';
import ProductTransformer from './product-transformer';
import Service from './service';
import System from './system';
import UrlBeautifier from './url-beautifier';
import * as utils from './utils';

export * from './decorators';
export * from './types';
export { Configuration, ProductTransformer, Service, System, Tag, utils };
export { Configuration, ProductTransformer, Service, System, Tag, UrlBeautifier, utils };
30 changes: 30 additions & 0 deletions src/core/service/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { SystemServices } from '../../services';
import StoreFront from '../../storefront';

export abstract class BaseService<T = any> implements Service {

constructor(protected app: StoreFront, protected opts: T) { }

abstract init(services: SystemServices): void;
}

export interface Service {

init(services: SystemServices): void;
}
export namespace Service {

export namespace Constructor {
export type Map = { [key: string]: Constructor };
}

export interface Constructor {
new (app: StoreFront, config: any): Service;
}

export type Map = { [key: string]: Service };

export type Options<T> = Service.Constructor | T | false;
}

export default Service;
25 changes: 25 additions & 0 deletions src/core/service/lazy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { BaseService } from '.';
import { SystemServices } from '../../services';
import StoreFront from '../../storefront';

abstract class LazyService<T = any> extends BaseService<T> {

initialized: boolean = false;
registered: any[] = [];

init() {
// no-op
}

abstract lazyInit(services: SystemServices);

register(tag: any) {
this.registered.push(tag);
if (!this.initialized) {
this.initialized = true;
this.lazyInit(this.app.services);
}
}
}

export default LazyService;
10 changes: 4 additions & 6 deletions src/core/system.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import FluxCapacitor from '@storefront/flux-capacitor';
import { utils, Configuration } from '.';
import Globals from '../globals';
import { CoreServices, Service } from '../services';
import Globals, { CORE } from '../globals';
import { SystemServices } from '../services';
import StoreFront from '../storefront';
import Tag from '../tag';

export const CORE = Symbol.for('storfront_core_service');
export const TAGS = Symbol.for('storefront_tags');
import Service from './service';

export default class System {

Expand Down Expand Up @@ -62,7 +60,7 @@ export default class System {
}

static buildServices(app: StoreFront, services: Service.Constructor.Map, config: any) {
return <Service.Map & CoreServices>Object.keys(services)
return <SystemServices>Object.keys(services)
.filter((key) => services[key][CORE] || config[key] !== false)
.reduce((svcs, key) => {
const serviceConfig = typeof config[key] === 'object' ? config[key] : {};
Expand Down
51 changes: 51 additions & 0 deletions src/core/url-beautifier/details/generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { SelectedValueRefinement } from 'groupby-api';
import UrlBeautifier from '..';
import { UrlGenerator } from '../handler';

export class DetailsUrlGenerator extends UrlGenerator<UrlBeautifier.DetailsRequest> {

build = (request: UrlBeautifier.DetailsRequest) => {
let path = [];

if (request.refinements.length !== 0) {
if (this.config.useReferenceKeys) {
path = DetailsUrlGenerator.convertRefinements(request.refinements, this.refinementsToKeys);
} else {
request.refinements.forEach(({ value, navigationName }) => path.push(value, navigationName));
}
}

path.unshift(request.title);
path.push(request.id);

return `/${path.map((part) => encodeURIComponent(part.replace(/\s/g, '-'))).join('/')}`;
}

static convertRefinements(refinements: SelectedValueRefinement[], refinementsToKeys: object) {
let referenceKeys = '';

return refinements.sort(DetailsUrlGenerator.refinementsComparator)
.reduce((path, { navigationName, value }) => {

if (!(navigationName in refinementsToKeys)) {
throw new Error(`no mapping found for navigation '${navigationName}'`);
}

path.push(value);
referenceKeys += refinementsToKeys[navigationName];

return path;
}, [])
.concat(referenceKeys);
}

static refinementsComparator(lhs: SelectedValueRefinement, rhs: SelectedValueRefinement): number {
let comparison = lhs.navigationName.localeCompare(rhs.navigationName);
if (comparison === 0) {
comparison = lhs.value.localeCompare(rhs.value);
}
return comparison;
}
}

export default DetailsUrlGenerator;
66 changes: 66 additions & 0 deletions src/core/url-beautifier/details/parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as URI from 'urijs';
import UrlBeautifier from '..';
import { UrlParser } from '../handler';

export class DetailsUrlParser extends UrlParser<UrlBeautifier.DetailsRequest> {

parse = (url: string) => {
const uri = URI.parse(url);
const path = uri.path.split('/')
.filter((val) => val)
.map((val) => decodeURIComponent(val).replace(/-/g, ' '));

if (path.length < 2) {
throw new Error('path has fewer than two parts');
}

const title = path.shift();
const id = path.pop();
let refinements = [];

if (path.length !== 0) {
if (!this.config.useReferenceKeys) {
refinements = DetailsUrlParser.extractPathRefinements(path);
} else {
refinements = DetailsUrlParser.extractReferencesRefinements(path, this.keysToRefinements);
}
}

return { id, refinements, title };
}

static extractReferencesRefinements(path: string[], keysToRefinements: { [key: string]: string }) {
if (path.length < 2) {
throw new Error('path has wrong number of parts');
}

const referenceKeys = path.pop().split('');

if (path.length !== referenceKeys.length) {
throw new Error('token reference is invalid');
}

return path.map((value) => ({
value,
navigationName: keysToRefinements[referenceKeys.shift()],
type: 'Value'
}));
}

static extractPathRefinements(path: string[]) {
if (path.length % 2 !== 0) {
throw new Error('path has an odd number of parts');
}

const refinements = [];
while (path.length) {
const value = path.shift();
const navigationName = path.shift();
refinements.push({ navigationName, value, type: 'Value' });
}

return refinements;
}
}

export default DetailsUrlParser;
35 changes: 35 additions & 0 deletions src/core/url-beautifier/factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import UrlBeautifier from '.';
import DetailsUrlGenerator from './details/generator';
import DetailsUrlParser from './details/parser';
import NavigationUrlGenerator from './navigation/generator';
import NavigationUrlParser from './navigation/parser';
import SearchUrlGenerator from './search/generator';
import SearchUrlParser from './search/parser';

namespace BeautifierFactory {
export function create(beautifier: UrlBeautifier): UrlBeautifier.Beautifiers {
const detailsParser = new DetailsUrlParser(beautifier);
const detailsGenerator = new DetailsUrlGenerator(beautifier);
const navigationParser = new NavigationUrlParser(beautifier);
const navigationGenerator = new NavigationUrlGenerator(beautifier);
const searchParser = new SearchUrlParser(beautifier);
const searchGenerator = new SearchUrlGenerator(beautifier);

return {
search: {
parse: searchParser.parse,
build: searchGenerator.build
},
navigation: {
parse: navigationParser.parse,
build: navigationGenerator.build
},
details: {
parse: detailsParser.parse,
build: detailsGenerator.build
}
};
}
}

export default BeautifierFactory;
32 changes: 32 additions & 0 deletions src/core/url-beautifier/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import UrlBeautifier from '.';

export abstract class UrlHandler {

config: UrlBeautifier.Configuration = this.beautifier.config;

constructor(protected beautifier: UrlBeautifier) { }

get keysToRefinements() {
return this.config.refinementMapping.reduce((map, mapping) => {
const key = Object.keys(mapping)[0];
return Object.assign(map, { [key]: mapping[key] });
}, {});
}

get refinementsToKeys() {
return this.config.refinementMapping.reduce((map, mapping) => {
const key = Object.keys(mapping)[0];
return Object.assign(map, { [mapping[key]]: key });
}, {});
}
}

export abstract class UrlParser<T> extends UrlHandler implements UrlBeautifier.Parser<T> {

abstract parse: (url: string) => T;
}

export abstract class UrlGenerator<T> extends UrlHandler implements UrlBeautifier.Generator<T> {

abstract build: (request: T) => string;
}
Loading