2015-11-19 04:42:09 +00:00
|
|
|
/**
|
2015-11-19 15:39:14 +00:00
|
|
|
Definition of the FRESHResume class.
|
2015-12-17 15:15:59 +00:00
|
|
|
@license MIT. Copyright (c) 2015 James Devlin / FluentDesk.
|
|
|
|
@module fresh-resume.js
|
2015-11-19 04:42:09 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
|
|
|
var FS = require('fs')
|
|
|
|
, extend = require('../utils/extend')
|
|
|
|
, validator = require('is-my-json-valid')
|
|
|
|
, _ = require('underscore')
|
2015-12-24 09:05:56 +00:00
|
|
|
, __ = require('lodash')
|
2015-11-19 04:42:09 +00:00
|
|
|
, PATH = require('path')
|
2015-11-19 14:46:02 +00:00
|
|
|
, moment = require('moment')
|
2015-12-31 11:38:30 +00:00
|
|
|
, XML = require('xml-escape')
|
2015-12-09 03:22:33 +00:00
|
|
|
, MD = require('marked')
|
2015-12-12 15:48:26 +00:00
|
|
|
, CONVERTER = require('./convert')
|
|
|
|
, JRSResume = require('./jrs-resume');
|
2015-11-19 04:42:09 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
A FRESH-style resume in JSON or YAML.
|
2015-11-19 15:39:14 +00:00
|
|
|
@class FreshResume
|
2015-11-19 04:42:09 +00:00
|
|
|
*/
|
2015-11-19 15:39:14 +00:00
|
|
|
function FreshResume() {
|
2015-11-19 04:42:09 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-11-19 15:39:14 +00:00
|
|
|
Open and parse the specified FRESH resume sheet. Merge the JSON object model
|
2015-11-19 04:42:09 +00:00
|
|
|
onto this Sheet instance with extend() and convert sheet dates to a safe &
|
|
|
|
consistent format. Then sort each section by startDate descending.
|
|
|
|
*/
|
2015-11-19 15:39:14 +00:00
|
|
|
FreshResume.prototype.open = function( file, title ) {
|
2015-11-20 14:28:55 +00:00
|
|
|
this.imp = { fileName: file };
|
|
|
|
this.imp.raw = FS.readFileSync( file, 'utf8' );
|
|
|
|
return this.parse( this.imp.raw, title );
|
2015-11-19 04:42:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
Save the sheet to disk (for environments that have disk access).
|
|
|
|
*/
|
2015-11-19 15:39:14 +00:00
|
|
|
FreshResume.prototype.save = function( filename ) {
|
2015-11-20 14:28:55 +00:00
|
|
|
this.imp.fileName = filename || this.imp.fileName;
|
|
|
|
FS.writeFileSync( this.imp.fileName, this.stringify(), 'utf8' );
|
2015-11-19 04:42:09 +00:00
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2015-11-20 13:29:19 +00:00
|
|
|
Save the sheet to disk in a specific format, either FRESH or JSON Resume.
|
2015-11-19 04:42:09 +00:00
|
|
|
*/
|
2015-11-20 13:29:19 +00:00
|
|
|
FreshResume.prototype.saveAs = function( filename, format ) {
|
2015-12-12 15:48:26 +00:00
|
|
|
|
2015-11-20 13:29:19 +00:00
|
|
|
if( format !== 'JRS' ) {
|
2015-12-12 15:48:26 +00:00
|
|
|
this.imp.fileName = filename || this.imp.fileName;
|
2015-11-20 14:28:55 +00:00
|
|
|
FS.writeFileSync( this.imp.fileName, this.stringify(), 'utf8' );
|
2015-11-20 13:29:19 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
var newRep = CONVERTER.toJRS( this );
|
2015-12-12 15:48:26 +00:00
|
|
|
FS.writeFileSync( filename, JRSResume.stringify( newRep ), 'utf8' );
|
2015-11-20 13:29:19 +00:00
|
|
|
}
|
|
|
|
return this;
|
2015-12-10 02:44:35 +00:00
|
|
|
};
|
2015-11-20 13:29:19 +00:00
|
|
|
|
2015-12-09 05:13:58 +00:00
|
|
|
FreshResume.prototype.dupe = function() {
|
|
|
|
var rnew = new FreshResume();
|
|
|
|
rnew.parse( this.stringify(), { } );
|
|
|
|
return rnew;
|
|
|
|
};
|
|
|
|
|
2015-11-20 13:29:19 +00:00
|
|
|
/**
|
|
|
|
Convert the supplied object to a JSON string, sanitizing meta-properties along
|
|
|
|
the way.
|
|
|
|
*/
|
|
|
|
FreshResume.stringify = function( obj ) {
|
2015-11-19 04:42:09 +00:00
|
|
|
function replacer( key,value ) { // Exclude these keys from stringification
|
2015-11-20 14:28:55 +00:00
|
|
|
return _.some(['imp', 'warnings', 'computed', 'filt', 'ctrl', 'index',
|
2015-11-19 15:39:14 +00:00
|
|
|
'safe', 'result', 'isModified', 'htmlPreview', 'display_progress_bar'],
|
2015-11-19 04:42:09 +00:00
|
|
|
function( val ) { return key.trim() === val; }
|
|
|
|
) ? undefined : value;
|
|
|
|
}
|
2015-11-20 13:29:19 +00:00
|
|
|
return JSON.stringify( obj, replacer, 2 );
|
2015-12-10 02:44:35 +00:00
|
|
|
};
|
2015-11-20 13:29:19 +00:00
|
|
|
|
2015-12-31 11:38:30 +00:00
|
|
|
/**
|
|
|
|
Create a copy of this resume in which all string fields have been run through
|
|
|
|
a transformation function (such as a Markdown filter or XML encoder).
|
|
|
|
*/
|
2016-01-03 12:35:47 +00:00
|
|
|
FreshResume.prototype.transformStrings = function( filt, transformer ) {
|
2015-12-31 11:38:30 +00:00
|
|
|
|
|
|
|
var that = this;
|
|
|
|
var ret = this.dupe();
|
|
|
|
|
|
|
|
// TODO: refactor recursion
|
2016-01-03 12:35:47 +00:00
|
|
|
function transformStringsInObject( obj, filters ) {
|
2015-12-31 11:38:30 +00:00
|
|
|
|
|
|
|
if( !obj ) return;
|
|
|
|
if( moment.isMoment( obj ) ) return;
|
|
|
|
|
|
|
|
if( _.isArray( obj ) ) {
|
|
|
|
obj.forEach( function(elem, idx, ar) {
|
|
|
|
if( typeof elem === 'string' || elem instanceof String )
|
|
|
|
ar[idx] = transformer( null, elem );
|
|
|
|
else if (_.isObject(elem))
|
2016-01-03 12:35:47 +00:00
|
|
|
transformStringsInObject( elem, filters );
|
2015-12-31 11:38:30 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
else if (_.isObject( obj )) {
|
|
|
|
Object.keys( obj ).forEach(function(k) {
|
2016-01-03 12:35:47 +00:00
|
|
|
if( filters.length && _.contains(filters, k) )
|
|
|
|
return;
|
2015-12-31 11:38:30 +00:00
|
|
|
var sub = obj[k];
|
|
|
|
if( typeof sub === 'string' || sub instanceof String ) {
|
|
|
|
obj[k] = transformer( k, sub );
|
|
|
|
}
|
|
|
|
else if (_.isObject( sub ))
|
2016-01-03 12:35:47 +00:00
|
|
|
transformStringsInObject( sub, filters );
|
2015-12-31 11:38:30 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.keys( ret ).forEach(function(member){
|
2016-01-03 12:35:47 +00:00
|
|
|
if( !filt || !filt.length || !_.contains(filt, member) )
|
|
|
|
transformStringsInObject( ret[ member ], filt || [] );
|
2015-12-31 11:38:30 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
};
|
|
|
|
|
2015-12-09 03:22:33 +00:00
|
|
|
/**
|
2015-12-09 09:32:39 +00:00
|
|
|
Create a copy of this resume in which all fields have been interpreted as
|
|
|
|
Markdown.
|
2015-12-09 03:22:33 +00:00
|
|
|
*/
|
|
|
|
FreshResume.prototype.markdownify = function() {
|
|
|
|
|
2015-12-31 11:38:30 +00:00
|
|
|
function MDIN( txt ){
|
|
|
|
return MD(txt || '' ).replace(/^\s*<p>|<\/p>\s*$/gi, '');
|
|
|
|
}
|
|
|
|
|
|
|
|
function trx(key, val) {
|
|
|
|
if( key === 'summary' ) {
|
|
|
|
return MD(val);
|
|
|
|
}
|
|
|
|
return MDIN(val);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.transformStrings( ['skills','url','start','end','date'], trx );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
Create a copy of this resume in which all fields have been interpreted as
|
|
|
|
Markdown.
|
|
|
|
*/
|
|
|
|
FreshResume.prototype.xmlify = function() {
|
|
|
|
|
|
|
|
function trx(key, val) {
|
|
|
|
return XML(val);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.transformStrings( [], trx );
|
|
|
|
};
|
|
|
|
|
2015-11-20 13:29:19 +00:00
|
|
|
/**
|
|
|
|
Convert this object to a JSON string, sanitizing meta-properties along the
|
|
|
|
way. Don't override .toString().
|
|
|
|
*/
|
|
|
|
FreshResume.prototype.stringify = function() {
|
|
|
|
return FreshResume.stringify( this );
|
2015-11-19 04:42:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2015-12-28 09:37:42 +00:00
|
|
|
Initialize the FreshResume from JSON data.
|
|
|
|
Open and parse the specified FRESH resume. Merge the JSON object model onto
|
|
|
|
this Sheet instance with extend() and convert sheet dates to a safe &
|
2015-11-19 04:42:09 +00:00
|
|
|
consistent format. Then sort each section by startDate descending.
|
|
|
|
*/
|
2015-12-28 09:37:42 +00:00
|
|
|
FreshResume.prototype.parseJSON = function( rep, opts ) {
|
2015-11-19 04:42:09 +00:00
|
|
|
// Convert JSON Resume to FRESH if necessary
|
2015-11-20 14:28:55 +00:00
|
|
|
if( rep.basics ) {
|
|
|
|
rep = CONVERTER.toFRESH( rep );
|
|
|
|
rep.imp = rep.imp || { };
|
|
|
|
rep.imp.orgFormat = 'JRS';
|
|
|
|
}
|
2015-11-19 04:42:09 +00:00
|
|
|
|
|
|
|
// Now apply the resume representation onto this object
|
|
|
|
extend( true, this, rep );
|
|
|
|
|
|
|
|
// Set up metadata
|
|
|
|
opts = opts || { };
|
2015-11-20 14:28:55 +00:00
|
|
|
if( opts.imp === undefined || opts.imp ) {
|
|
|
|
this.imp = this.imp || { };
|
|
|
|
this.imp.title = (opts.title || this.imp.title) || this.name;
|
2015-11-19 04:42:09 +00:00
|
|
|
}
|
|
|
|
// Parse dates, sort dates, and calculate computed values
|
|
|
|
(opts.date === undefined || opts.date) && _parseDates.call( this );
|
|
|
|
(opts.sort === undefined || opts.sort) && this.sort();
|
|
|
|
(opts.compute === undefined || opts.compute) && (this.computed = {
|
|
|
|
numYears: this.duration(),
|
|
|
|
keywords: this.keywords()
|
|
|
|
});
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
2016-01-06 03:26:16 +00:00
|
|
|
/**
|
|
|
|
Return the resume format.
|
|
|
|
*/
|
|
|
|
FreshResume.prototype.format = function() {
|
|
|
|
return 'FRESH';
|
|
|
|
};
|
|
|
|
|
2015-12-28 09:37:42 +00:00
|
|
|
/**
|
|
|
|
Initialize the the FreshResume from string data.
|
|
|
|
*/
|
|
|
|
FreshResume.prototype.parse = function( stringData, opts ) {
|
|
|
|
return this.parseJSON( JSON.parse( stringData ), opts );
|
|
|
|
};
|
|
|
|
|
2016-01-03 08:18:56 +00:00
|
|
|
/**
|
|
|
|
Return internal metadata. Create if it doesn't exist.
|
|
|
|
*/
|
2016-01-06 03:02:11 +00:00
|
|
|
FreshResume.prototype.i = function() {
|
2016-01-03 08:18:56 +00:00
|
|
|
this.imp = (this.imp || { });
|
|
|
|
return this.imp;
|
|
|
|
};
|
|
|
|
|
2015-11-19 04:42:09 +00:00
|
|
|
/**
|
|
|
|
Return a unique list of all keywords across all skills.
|
|
|
|
*/
|
2015-11-19 15:39:14 +00:00
|
|
|
FreshResume.prototype.keywords = function() {
|
2015-11-19 04:42:09 +00:00
|
|
|
var flatSkills = [];
|
2016-01-03 11:39:46 +00:00
|
|
|
if( this.skills ) {
|
|
|
|
if( this.skills.sets ) {
|
2016-01-03 14:48:02 +00:00
|
|
|
flatSkills = this.skills.sets.map(function(sk) { return sk.skills; })
|
|
|
|
.reduce(function(a,b) { return a.concat(b); });
|
2016-01-03 11:39:46 +00:00
|
|
|
}
|
|
|
|
else if( this.skills.list ) {
|
|
|
|
flatSkills = flatSkills.concat( this.skills.list.map(function(sk) { return sk.name; }) );
|
|
|
|
}
|
2016-01-03 14:48:02 +00:00
|
|
|
flatSkills = _.uniq( flatSkills );
|
2016-01-03 11:39:46 +00:00
|
|
|
}
|
2015-11-19 04:42:09 +00:00
|
|
|
return flatSkills;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
Reset the sheet to an empty state.
|
|
|
|
*/
|
2015-11-19 15:39:14 +00:00
|
|
|
FreshResume.prototype.clear = function( clearMeta ) {
|
2015-11-19 04:42:09 +00:00
|
|
|
clearMeta = ((clearMeta === undefined) && true) || clearMeta;
|
2015-11-20 14:28:55 +00:00
|
|
|
clearMeta && (delete this.imp);
|
2015-11-19 04:42:09 +00:00
|
|
|
delete this.computed; // Don't use Object.keys() here
|
|
|
|
delete this.employment;
|
|
|
|
delete this.service;
|
|
|
|
delete this.education;
|
2015-12-02 19:56:36 +00:00
|
|
|
delete this.recognition;
|
|
|
|
delete this.reading;
|
|
|
|
delete this.writing;
|
|
|
|
delete this.interests;
|
2015-11-19 04:42:09 +00:00
|
|
|
delete this.skills;
|
|
|
|
delete this.social;
|
|
|
|
};
|
|
|
|
|
2015-12-15 11:20:06 +00:00
|
|
|
/**
|
|
|
|
Get a safe count of the number of things in a section.
|
|
|
|
*/
|
|
|
|
FreshResume.prototype.count = function( obj ) {
|
|
|
|
if( !obj ) return 0;
|
|
|
|
if( obj.history ) return obj.history.length;
|
2015-12-16 16:25:50 +00:00
|
|
|
if( obj.sets ) return obj.sets.length;
|
2015-12-15 11:20:06 +00:00
|
|
|
return obj.length || 0;
|
2015-12-16 17:13:50 +00:00
|
|
|
};
|
2015-12-15 11:20:06 +00:00
|
|
|
|
2015-11-19 04:42:09 +00:00
|
|
|
/**
|
|
|
|
Get the default (empty) sheet.
|
|
|
|
*/
|
2015-11-19 15:39:14 +00:00
|
|
|
FreshResume.default = function() {
|
2016-01-06 04:28:49 +00:00
|
|
|
return new FreshResume().parseJSON( require('fresh-resume-empty') );
|
2015-12-10 02:44:35 +00:00
|
|
|
};
|
2015-11-19 04:42:09 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
Add work experience to the sheet.
|
|
|
|
*/
|
2015-11-19 15:39:14 +00:00
|
|
|
FreshResume.prototype.add = function( moniker ) {
|
|
|
|
var defSheet = FreshResume.default();
|
2015-12-15 11:20:06 +00:00
|
|
|
var newObject = defSheet[moniker].history ?
|
|
|
|
$.extend( true, {}, defSheet[ moniker ].history[0] ) :
|
2015-12-16 16:25:50 +00:00
|
|
|
(moniker === 'skills' ?
|
|
|
|
$.extend( true, {}, defSheet.skills.sets[0] ) :
|
|
|
|
$.extend( true, {}, defSheet[ moniker ][0] ));
|
2015-11-19 04:42:09 +00:00
|
|
|
this[ moniker ] = this[ moniker ] || [];
|
2015-12-15 11:20:06 +00:00
|
|
|
if( this[ moniker ].history )
|
|
|
|
this[ moniker ].history.push( newObject );
|
2015-12-16 16:25:50 +00:00
|
|
|
else if( moniker === 'skills' )
|
|
|
|
this.skills.sets.push( newObject );
|
2015-12-15 11:20:06 +00:00
|
|
|
else
|
|
|
|
this[ moniker ].push( newObject );
|
2015-11-19 04:42:09 +00:00
|
|
|
return newObject;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
Determine if the sheet includes a specific social profile (eg, GitHub).
|
|
|
|
*/
|
2015-11-19 15:39:14 +00:00
|
|
|
FreshResume.prototype.hasProfile = function( socialNetwork ) {
|
2015-11-19 04:42:09 +00:00
|
|
|
socialNetwork = socialNetwork.trim().toLowerCase();
|
2015-11-19 06:47:23 +00:00
|
|
|
return this.social && _.some( this.social, function(p) {
|
2015-11-19 04:42:09 +00:00
|
|
|
return p.network.trim().toLowerCase() === socialNetwork;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-12-08 02:24:14 +00:00
|
|
|
/**
|
|
|
|
Return the specified network profile.
|
|
|
|
*/
|
|
|
|
FreshResume.prototype.getProfile = function( socialNetwork ) {
|
|
|
|
socialNetwork = socialNetwork.trim().toLowerCase();
|
|
|
|
return this.social && _.find( this.social, function(sn) {
|
2015-12-10 02:44:35 +00:00
|
|
|
return sn.network.trim().toLowerCase() === socialNetwork;
|
2015-12-08 02:24:14 +00:00
|
|
|
});
|
2015-12-10 02:44:35 +00:00
|
|
|
};
|
2015-12-08 02:24:14 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
Return an array of profiles for the specified network, for when the user
|
|
|
|
has multiple eg. GitHub accounts.
|
|
|
|
*/
|
|
|
|
FreshResume.prototype.getProfiles = function( socialNetwork ) {
|
|
|
|
socialNetwork = socialNetwork.trim().toLowerCase();
|
|
|
|
return this.social && _.filter( this.social, function(sn){
|
2015-12-10 02:44:35 +00:00
|
|
|
return sn.network.trim().toLowerCase() === socialNetwork;
|
2015-12-08 02:24:14 +00:00
|
|
|
});
|
2015-12-10 02:44:35 +00:00
|
|
|
};
|
2015-12-08 02:24:14 +00:00
|
|
|
|
2015-11-19 04:42:09 +00:00
|
|
|
/**
|
|
|
|
Determine if the sheet includes a specific skill.
|
|
|
|
*/
|
2015-11-19 15:39:14 +00:00
|
|
|
FreshResume.prototype.hasSkill = function( skill ) {
|
2015-11-19 04:42:09 +00:00
|
|
|
skill = skill.trim().toLowerCase();
|
|
|
|
return this.skills && _.some( this.skills, function(sk) {
|
|
|
|
return sk.keywords && _.some( sk.keywords, function(kw) {
|
|
|
|
return kw.trim().toLowerCase() === skill;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
Validate the sheet against the FRESH Resume schema.
|
|
|
|
*/
|
2015-11-19 15:39:14 +00:00
|
|
|
FreshResume.prototype.isValid = function( info ) {
|
2015-12-24 08:23:56 +00:00
|
|
|
var schemaObj = require('fresca');
|
2015-12-10 02:44:35 +00:00
|
|
|
var validator = require('is-my-json-valid');
|
2015-11-19 17:36:58 +00:00
|
|
|
var validate = validator( schemaObj, { // Note [1]
|
|
|
|
formats: { date: /^\d{4}(?:-(?:0[0-9]{1}|1[0-2]{1})(?:-[0-9]{2})?)?$/ }
|
|
|
|
});
|
2015-11-19 14:46:02 +00:00
|
|
|
var ret = validate( this );
|
|
|
|
if( !ret ) {
|
2015-11-20 14:28:55 +00:00
|
|
|
this.imp = this.imp || { };
|
|
|
|
this.imp.validationErrors = validate.errors;
|
2015-11-19 14:46:02 +00:00
|
|
|
}
|
|
|
|
return ret;
|
2015-11-19 04:42:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
Calculate the total duration of the sheet. Assumes this.work has been sorted
|
|
|
|
by start date descending, perhaps via a call to Sheet.sort().
|
|
|
|
@returns The total duration of the sheet's work history, that is, the number
|
|
|
|
of years between the start date of the earliest job on the resume and the
|
|
|
|
*latest end date of all jobs in the work history*. This last condition is for
|
|
|
|
sheets that have overlapping jobs.
|
|
|
|
*/
|
2016-01-03 14:48:22 +00:00
|
|
|
FreshResume.prototype.duration = function(unit) {
|
|
|
|
unit = unit || 'years';
|
2015-12-24 09:05:56 +00:00
|
|
|
var empHist = __.get(this, 'employment.history');
|
|
|
|
if( empHist && empHist.length ) {
|
2016-01-04 04:17:18 +00:00
|
|
|
var firstJob = _.last( empHist );
|
2015-12-14 12:32:41 +00:00
|
|
|
var careerStart = firstJob.start ? firstJob.safe.start : '';
|
2015-11-19 04:42:09 +00:00
|
|
|
if ((typeof careerStart === 'string' || careerStart instanceof String) &&
|
|
|
|
!careerStart.trim())
|
|
|
|
return 0;
|
2016-01-04 04:17:18 +00:00
|
|
|
var careerLast = _.max( empHist, function( w ) {
|
2015-12-14 12:32:41 +00:00
|
|
|
return( w.safe && w.safe.end ) ? w.safe.end.unix() : moment().unix();
|
|
|
|
});
|
2016-01-03 14:48:22 +00:00
|
|
|
return careerLast.safe.end.diff( careerStart, unit );
|
2015-11-19 04:42:09 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
Sort dated things on the sheet by start date descending. Assumes that dates
|
|
|
|
on the sheet have been processed with _parseDates().
|
|
|
|
*/
|
2015-11-19 15:39:14 +00:00
|
|
|
FreshResume.prototype.sort = function( ) {
|
2015-11-19 04:42:09 +00:00
|
|
|
|
2016-01-04 04:17:18 +00:00
|
|
|
function byDateDesc(a,b) {
|
|
|
|
return( a.safe.start.isBefore(b.safe.start) ) ? 1
|
|
|
|
: ( a.safe.start.isAfter(b.safe.start) && -1 ) || 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
function sortSection( key ) {
|
|
|
|
var ar = __.get(this, key);
|
|
|
|
if( ar && ar.length ) {
|
|
|
|
var datedThings = obj.filter( function(o) { return o.start; } );
|
|
|
|
datedThings.sort( byDateDesc );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sortSection( 'employment.history' );
|
|
|
|
sortSection( 'education.history' );
|
|
|
|
sortSection( 'service.history' );
|
2016-01-05 10:00:04 +00:00
|
|
|
sortSection( 'projects.history' );
|
2015-11-19 04:42:09 +00:00
|
|
|
|
|
|
|
// this.awards && this.awards.sort( function(a, b) {
|
|
|
|
// return( a.safeDate.isBefore(b.safeDate) ) ? 1
|
|
|
|
// : ( a.safeDate.isAfter(b.safeDate) && -1 ) || 0;
|
|
|
|
// });
|
2015-12-02 19:56:36 +00:00
|
|
|
this.writing && this.writing.sort( function(a, b) {
|
2015-11-19 04:42:09 +00:00
|
|
|
return( a.safe.date.isBefore(b.safe.date) ) ? 1
|
|
|
|
: ( a.safe.date.isAfter(b.safe.date) && -1 ) || 0;
|
|
|
|
});
|
|
|
|
|
2016-01-04 04:17:18 +00:00
|
|
|
|
2015-11-19 04:42:09 +00:00
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
Convert human-friendly dates into formal Moment.js dates for all collections.
|
|
|
|
We don't want to lose the raw textual date as entered by the user, so we store
|
|
|
|
the Moment-ified date as a separate property with a prefix of .safe. For ex:
|
|
|
|
job.startDate is the date as entered by the user. job.safeStartDate is the
|
|
|
|
parsed Moment.js date that we actually use in processing.
|
|
|
|
*/
|
|
|
|
function _parseDates() {
|
|
|
|
|
|
|
|
var _fmt = require('./fluent-date').fmt;
|
2015-12-09 03:21:42 +00:00
|
|
|
var that = this;
|
|
|
|
|
|
|
|
// TODO: refactor recursion
|
|
|
|
function replaceDatesInObject( obj ) {
|
|
|
|
|
|
|
|
if( !obj ) return;
|
|
|
|
if( Object.prototype.toString.call( obj ) === '[object Array]' ) {
|
|
|
|
obj.forEach(function(elem){
|
|
|
|
replaceDatesInObject( elem );
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else if (typeof obj === 'object') {
|
|
|
|
if( obj._isAMomentObject || obj.safe )
|
|
|
|
return;
|
|
|
|
Object.keys( obj ).forEach(function(key) {
|
|
|
|
replaceDatesInObject( obj[key] );
|
|
|
|
});
|
|
|
|
['start','end','date'].forEach( function(val) {
|
2015-12-14 12:32:41 +00:00
|
|
|
if( (obj[val] !== undefined) && (!obj.safe || !obj.safe[val] )) {
|
2015-12-09 03:21:42 +00:00
|
|
|
obj.safe = obj.safe || { };
|
|
|
|
obj.safe[ val ] = _fmt( obj[val] );
|
|
|
|
if( obj[val] && (val === 'start') && !obj.end ) {
|
|
|
|
obj.safe.end = _fmt('current');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2015-11-19 04:42:09 +00:00
|
|
|
|
2015-12-09 03:21:42 +00:00
|
|
|
Object.keys( this ).forEach(function(member){
|
|
|
|
replaceDatesInObject( that[ member ] );
|
2015-11-19 04:42:09 +00:00
|
|
|
});
|
2015-12-09 03:21:42 +00:00
|
|
|
|
2015-11-19 04:42:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Export the Sheet function/ctor.
|
|
|
|
*/
|
2015-11-19 15:39:14 +00:00
|
|
|
module.exports = FreshResume;
|
2015-11-19 04:42:09 +00:00
|
|
|
|
|
|
|
}());
|
2015-11-19 17:36:58 +00:00
|
|
|
|
|
|
|
// Note 1: Adjust default date validation to allow YYYY and YYYY-MM formats
|
|
|
|
// in addition to YYYY-MM-DD. The original regex:
|
|
|
|
//
|
|
|
|
// /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/
|
|
|
|
//
|