<commit_before>var mongoose = require('mongoose');
var feedparser = require('feedparser')
var request = require('request');

var feedSchema = mongoose.Schema({
    _id:              { type: String, required: true, unique: true },
    title:            String,
    description:      String,
    link:             String,
    xmlUrl:           String,
    date:             { type: Date },
    pubDate:          { type: Date },
    author:           String,
    language:         String,
    image:            { title: String, url: String },
    favicon:          String,
    copyright:        String,
    generator:        String,
    categories:       [String],
    lastModified:     String
});

feedSchema.virtual('url').get(function () {
  return this._id;
});

feedSchema.virtual('url').set(function (url) {
  this._id = url;
});

var Feed = mongoose.model('Feed', feedSchema);

var articleSchema = mongoose.Schema({
    title:            String,
    description:      String,
    link:             String,
    origLink:         String,
    date:             { type: Date },
    pubDate:          { type: Date },
    author:           String,
    guid:             String,
    comments:         String,
    image:            { title: String, url: String },
    categories:       [String],
    source:           { title: String, url: String },
    enclosures:       [{ url: String, type: String, length: String }],
    feed:             { type: String, ref: 'Feed' },
});

var Article = mongoose.model('Article', articleSchema);

var timeoutInMs = 10000;
var defaultMongoDbPort = 27017;
var updateIntervalHandle = null;

function connect(uri, database, port) {
    port = typeof port !== 'undefined' ? port : defaultMongoDbPort;
    mongoose.connect(uri, database, port);

    mongoose.connection.on('error', function(e){
        logError("Could not connect to MongoDB: "+e.message);
        process.exit(1);
    });

    mongoose.connection.once('open', function callback () {
        logDebug("Connection to MongoDB was successful.");
    });
}

function addFeed(url) {
    Feed.count({ _id: url }, function(error, count) {
        if(error != null) {
            logError('Failed to check if feed exists in MongoDB: '+error);
        }
        else {
            if(count == 0) {
               requestAndParseFeed(url);
            }
            else if(count == 1) {
                logWarning('Feed '+url+' already exists in the MongoDB. '
                + 'Skipping adding new feed to the storage.');
            }
            else {
                logError('Multiple documents with same url exist in '
                + 'MongoDB. This should not happen!');
            }
        }
    });
}

function removeFeed(url) {
    Feed.findByIdAndRemove(url, function(error, feedDocument) {
        if(error != null) {
            logError('Failed to find feed to be removed: '+error);
        }
        else if(feedDocument != null) {
            setTimeout(function() { removeArticlesByFeedUrl(url) }, 5000);
        }
        else {
            logWarn('Feed not found with url '+url+'. Skipping removing.');
        }
    });
}

function removeArticlesByFeedUrl(url) {
    Article.remove({ feed: url }, function (error) {
        if(error != null) {
            logError('Failed to find articles to be removed: '+error);
        }
    });
}

function requestAndParseFeed(url) {
    var requestObject = createDefaultRequest(url);

    Feed.findOne({ _id: url }, 'lastModified', function(error,
        partialFeedDocument) {
        if(error != null) {
            logError('Failed to get feeds Last-Modified from MongoDB: '+
                error);
        }
        else if(partialFeedDocument != null &&
            partialFeedDocument.lastModified != null) {
            requestObject.headers = { 'If-Modified-Since':
                partialFeedDocument.lastModified }
        }

        request(requestObject, function(error, response, body) {
            handleResponse(url, error, response, body)
        });
    });
}

function createDefaultRequest(url) {
    return { url: url, timeout: timeoutInMs };
}

function handleResponse(url, error, response, body) {
    if(error != null) {
        logError('Failed to handle HTTP response: '+error);
    }
    else if (response.statusCode == 200) {
        var lastModified = getLastModifiedFromResponseHeaders(response.headers);
        feedparser.parseString(body)
        .on('meta', function(meta) { saveOrUpdateFeedMeta(meta, url,
            lastModified) })
        .on('article', function(article) { saveOrUpdateArticle(article, url) })
        .on('error', function(error) { handleParseError(error, url) });
    }
    else if (response.statusCode == 304) {
        logDebug('Feed '+url+' not modified (304). Skipping parsing.' );
    }
    else {
        logWarning('HTTP status code received that can not be handled by '
            + 'this module: '+response.statusCode);
    }
}

function handleParseError(error, url) {
    logError('Could not parse feed at '+url+'. '+error);
}

function getLastModifiedFromResponseHeaders(headers) {
    var lastModified = null;

    for(var headerName in headers){
        if(headerName.toLowerCase() == 'last-modified') {
            lastModified = headers[headerName];
        }
    }

    return lastModified;
}

function saveOrUpdateFeedMeta(meta, url, lastModified) {
    Feed.findOne({ _id: url }, function(error, feedDocument) {
        if(error != null) {
            logError('Failed to get feed from MongoDB: '+error);
        }
        else {
            if(feedDocument == null) {
                saveFeedMeta(meta, url, lastModified);
            }
            else {
                updateFeedMeta(feedDocument, meta, url, lastModified);
            }
        }
    });
}

function saveFeedMeta(meta, url, lastModified) {
    var feedDocument = createFeedDocument(meta, url, lastModified);

    feedDocument.save(function (error, feedDocument) {
        if(error != null) {
            logError('Failed to save feed meta to MongoDB: '+error);
        }
        else {
            logDebug('Added new feed "'+feedDocument.url+'" to MongoDB.');
        }
    });
}

function updateFeedMeta(feedDocument, meta, lastModified) {
    if(feedRequiresUpdate(feedDocument, meta, lastModified)) {
        feedDocument.title = meta.title;
        feedDocument.description = meta.description;
        feedDocument.link = meta.link;
        feedDocument.xmlUrl = meta.xmlUrl;
        feedDocument.date = meta.date;
        feedDocument.pubDate = meta.pubDate;
        feedDocument.author = meta.author;
        feedDocument.language = meta.language;
        feedDocument.image = meta.image;
        feedDocument.favicon = meta.favicon;
        feedDocument.copyright = meta.copyright;
        feedDocument.generator = meta.generator;
        feedDocument.categories = meta.categories;
        feedDocument.lastModified = lastModified;

        feedDocument.save(function (error, feedDocument) {
            if(error != null) {
                logError('Failed to update feed meta to MongoDB: '+error);
            }
            else {
                logDebug('Updated feed "'+feedDocument.url+'" in MongoDB.');
            }
        });
    }
}

function feedRequiresUpdate(feedDocument, meta, lastModified) {
    var requiresUpdate = false;

    if(feedDocument.title != meta.title &&
        feedDocument.description != meta.description &&
        feedDocument.link != meta.link &&
        feedDocument.xmlUrl != meta.xmlUrl &&
        feedDocument.author != meta.author &&
        feedDocument.language != meta.language &&
        feedDocument.image.title != meta.image.title &&
        feedDocument.image.url != meta.image.url &&
        feedDocument.favicon != meta.favicon &&
        feedDocument.copyright != meta.copyright &&
        feedDocument.generator != meta.generator &&
        JSON.stringify(feedDocument.categories) !=
            JSON.stringify(meta.categories) &&
        feedDocument.lastModified != lastModified) {
        requiresUpdate = true;
    }

    return requiresUpdate;
}

function createFeedDocument(meta, url, lastModified) {
    return new Feed({ url: url, title: meta.title,
        description: meta.description, link: meta.link, xmlUrl: meta.xmlUrl,
        date: meta.date, pubDate: meta.pubDate, author: meta.author,
        language: meta.language, image: meta.image, favicon: meta.favicon,
        copyright: meta.copyright, generator: meta.generator,
        categories: meta.categories, lastModified: lastModified });
}

function saveOrUpdateArticle(article, url) {
    Article.findOne({ guid: article.guid, feed: url }, function(error,
        articleDocument) {
        if(error != null) {
            logError('Failed to get article from MongoDB: '+error);
        }
        else {
            if(articleDocument == null) {
                saveArticle(article, url);
            }
            else {
                updateArticle(articleDocument, article);
            }
        }
    });
}

function saveArticle(article, url) {
    var articleDocument = createArticleDocument(article, url);

    articleDocument.save(function (error, articleDocument) {
        if(error != null) {
            logError('Failed to save article to MongoDB: '+error);
        }
        else {
            logDebug('Added new article "'+articleDocument.guid
            + '" to MongoDB.');
        }
    });
}

function updateArticle(articleDocument, article) {
    if(articleRequiresUpdate(articleDocument, article)) {
        articleDocument.title = article.title;
        articleDocument.description = article.description;
        articleDocument.link = article.link;
        articleDocument.xmlUrl = article.xmlUrl;
        articleDocument.date = article.date;
        articleDocument.pubDate = article.pubDate;
        articleDocument.author = article.author;
        articleDocument.language = article.language;
        articleDocument.image = article.image;
        articleDocument.favicon = article.favicon;
        articleDocument.copyright = article.copyright;
        articleDocument.generator = article.generator;
        articleDocument.categories = article.categories;

        articleDocument.save(function (error, feedDocument) {
            if(error != null) {
                logError('Failed to update article to MongoDB: '+error);
            }
            else {
                logDebug('Updated article "'+articleDocument.guid
                +'" in MongoDB.');
            }
        });
    }
}

function articleRequiresUpdate(articleDocument, article) {
    var requiresUpdate = false;

    if(articleDocument.title != article.title &&
        articleDocument.description != article.description &&
        articleDocument.link != article.link &&
        articleDocument.origLink != article.origLink &&
        articleDocument.author != article.author &&
        articleDocument.guid != article.guid &&
        articleDocument.comments != article.comments &&
        articleDocument.image.title != article.image.title &&
        articleDocument.image.url != article.image.url &&
        JSON.stringify(articleDocument.categories) !=
            JSON.stringify(article.categories) &&
        articleDocument.source.title != article.source.title &&
        articleDocument.source.url != article.source.url &&
        articleDocument.enclosures.url != article.enclosures.url &&
        articleDocument.enclosures.type != article.enclosures.type &&
        articleDocument.enclosures.length != article.enclosures.length) {
        requiresUpdate = true;
    }

    return requiresUpdate;

}

function createArticleDocument(article, url) {
    return new Article({ title: article.title,
        description: article.description, link: article.link,
        origLink: article.origLink, author: article.author, guid: article.guid,
        comments: article.comments, image: article.image,
        categories: article.categories, source: article.source,
        enclosures: article.enclosures, feed: url });
}

function updateDatabase() {
    Feed.find(function(error, feedDocuments) {
        if(error != null) {
            logError('Failed to get feeds from MongoDB: '+error);
        }
        else {
            feedDocuments.forEach(function(feedDocument) {
                requestAndParseFeed(feedDocument.url);
            });
        }
    });
}

function updateDatabaseAtInterval(seconds) {
    if(updateIntervalHandle == null) {
        updateIntervalHandle = setInterval(updateDatabase, seconds);
    }
    else {
        logWarning('Database update interval already set. Skipping.');
    }
}

function stopUpdateDataBaseAtInterval() {
    if(updateIntervalHandle != null) {
        clearInterval(updateIntervalHandle);
        updateIntervalHandle = null;
    }
}

function getArticlesByKeyword(keyword, limit, callback) {
    var searchTerm = new RegExp('.*(\\s|-)+'+keyword+'(\\s|-)+.*', 'i');

    var query = Article.find({ $or: [ { title: searchTerm }, { description:
    searchTerm }, { author: searchTerm } ] }).limit(limit);

    query.execFind(function (error, articleDocuments) {
        if(error != null) {
            logError('Failed to get articles from MongoDB: '+error);
        }
        else {
            callback(articleDocuments);
        }
    });
}

function getArticlesByKeywordArray(keywords, limit, callback) {
    var keyword = '('+keywords.join('|')+')';
    getArticlesByKeyword(keyword, limit, callback);
}

function logDebug(message) {
    console.log(getTimeStampForLog()+' [DEBUG] '+message);
}

function logWarning(message) {
    console.warn(getTimeStampForLog()+' [WARN] '+message);
}

function logError(message) {
    console.error(getTimeStampForLog()+' [ERROR] '+message);
}

function getTimeStampForLog() {
    return new Date().toUTCString();;
}

exports.connect = connect;
exports.addFeed = addFeed;
exports.updateDatabase = updateDatabase;
exports.updateDatabaseAtInterval = updateDatabaseAtInterval;
exports.stopUpdateDataBaseAtInterval = stopUpdateDataBaseAtInterval;
exports.getArticlesByKeyword = getArticlesByKeyword;
exports.getArticlesByKeywordArray = getArticlesByKeywordArray;
exports.removeFeed = removeFeed;
<commit_msg>Implemented feature to delete articles from database according to to their date.<commit_after>var mongoose = require('mongoose');
var feedparser = require('feedparser')
var request = require('request');

var feedSchema = mongoose.Schema({
    _id:              { type: String, required: true, unique: true },
    title:            String,
    description:      String,
    link:             String,
    xmlUrl:           String,
    date:             { type: Date },
    pubDate:          { type: Date },
    author:           String,
    language:         String,
    image:            { title: String, url: String },
    favicon:          String,
    copyright:        String,
    generator:        String,
    categories:       [String],
    lastModified:     String
});

feedSchema.virtual('url').get(function () {
  return this._id;
});

feedSchema.virtual('url').set(function (url) {
  this._id = url;
});

var Feed = mongoose.model('Feed', feedSchema);

var articleSchema = mongoose.Schema({
    title:            String,
    description:      String,
    link:             String,
    origLink:         String,
    date:             { type: Date },
    pubDate:          { type: Date },
    author:           String,
    guid:             String,
    comments:         String,
    image:            { title: String, url: String },
    categories:       [String],
    source:           { title: String, url: String },
    enclosures:       [{ url: String, type: String, length: String }],
    feed:             { type: String, ref: 'Feed' },
});

var Article = mongoose.model('Article', articleSchema);

var timeoutInMs = 10000;
var defaultMongoDbPort = 27017;
var updateIntervalHandle = null;

function connect(uri, database, port) {
    port = typeof port !== 'undefined' ? port : defaultMongoDbPort;
    mongoose.connect(uri, database, port);

    mongoose.connection.on('error', function(e){
        logError("Could not connect to MongoDB: "+e.message);
        process.exit(1);
    });

    mongoose.connection.once('open', function callback () {
        logDebug("Connection to MongoDB was successful.");
    });
}

function addFeed(url) {
    Feed.count({ _id: url }, function(error, count) {
        if(error != null) {
            logError('Failed to check if feed exists in MongoDB: '+error);
        }
        else {
            if(count == 0) {
               requestAndParseFeed(url);
            }
            else if(count == 1) {
                logWarning('Feed '+url+' already exists in the MongoDB. '
                + 'Skipping adding new feed to the storage.');
            }
            else {
                logError('Multiple documents with same url exist in '
                + 'MongoDB. This should not happen!');
            }
        }
    });
}

function removeFeed(url) {
    Feed.findByIdAndRemove(url, function(error, feedDocument) {
        if(error != null) {
            logError('Failed to find feed to be removed: '+error);
        }
        else if(feedDocument != null) {
            setTimeout(function() { removeArticlesByFeedUrl(url) }, 5000);
        }
        else {
            logWarn('Feed not found with url '+url+'. Skipping removing.');
        }
    });
}

function removeArticlesByFeedUrl(url) {
    Article.remove({ feed: url }, function (error) {
        if(error != null) {
            logError('Failed to find articles to be removed: '+error);
        }
    });
}

function removeArticlesOlderThan(days) {
    var oldArticleDate = new Date();
    oldArticleDate.setDate(oldArticleDate.getDate()-days);

    Article.remove({ date: { $lt: oldArticleDate } }, function (error) {
        if(error != null) {
            logError('Failed to remove articles older than '+days+': '+error);
        }
    });
}

function requestAndParseFeed(url) {
    var requestObject = createDefaultRequest(url);

    Feed.findOne({ _id: url }, 'lastModified', function(error,
        partialFeedDocument) {
        if(error != null) {
            logError('Failed to get feeds Last-Modified from MongoDB: '+
                error);
        }
        else if(partialFeedDocument != null &&
            partialFeedDocument.lastModified != null) {
            requestObject.headers = { 'If-Modified-Since':
                partialFeedDocument.lastModified }
        }

        request(requestObject, function(error, response, body) {
            handleResponse(url, error, response, body)
        });
    });
}

function createDefaultRequest(url) {
    return { url: url, timeout: timeoutInMs };
}

function handleResponse(url, error, response, body) {
    if(error != null) {
        logError('Failed to handle HTTP response: '+error);
    }
    else if (response.statusCode == 200) {
        var lastModified = getLastModifiedFromResponseHeaders(response.headers);
        feedparser.parseString(body)
        .on('meta', function(meta) { saveOrUpdateFeedMeta(meta, url,
            lastModified) })
        .on('article', function(article) { saveOrUpdateArticle(article, url) })
        .on('error', function(error) { handleParseError(error, url) });
    }
    else if (response.statusCode == 304) {
        logDebug('Feed '+url+' not modified (304). Skipping parsing.' );
    }
    else {
        logWarning('HTTP status code received that can not be handled by '
            + 'this module: '+response.statusCode);
    }
}

function handleParseError(error, url) {
    logError('Could not parse feed at '+url+'. '+error);
}

function getLastModifiedFromResponseHeaders(headers) {
    var lastModified = null;

    for(var headerName in headers){
        if(headerName.toLowerCase() == 'last-modified') {
            lastModified = headers[headerName];
        }
    }

    return lastModified;
}

function saveOrUpdateFeedMeta(meta, url, lastModified) {
    Feed.findOne({ _id: url }, function(error, feedDocument) {
        if(error != null) {
            logError('Failed to get feed from MongoDB: '+error);
        }
        else {
            if(feedDocument == null) {
                saveFeedMeta(meta, url, lastModified);
            }
            else {
                updateFeedMeta(feedDocument, meta, url, lastModified);
            }
        }
    });
}

function saveFeedMeta(meta, url, lastModified) {
    var feedDocument = createFeedDocument(meta, url, lastModified);

    feedDocument.save(function (error, feedDocument) {
        if(error != null) {
            logError('Failed to save feed meta to MongoDB: '+error);
        }
        else {
            logDebug('Added new feed "'+feedDocument.url+'" to MongoDB.');
        }
    });
}

function updateFeedMeta(feedDocument, meta, lastModified) {
    if(feedRequiresUpdate(feedDocument, meta, lastModified)) {
        feedDocument.title = meta.title;
        feedDocument.description = meta.description;
        feedDocument.link = meta.link;
        feedDocument.xmlUrl = meta.xmlUrl;
        feedDocument.date = meta.date;
        feedDocument.pubDate = meta.pubDate;
        feedDocument.author = meta.author;
        feedDocument.language = meta.language;
        feedDocument.image = meta.image;
        feedDocument.favicon = meta.favicon;
        feedDocument.copyright = meta.copyright;
        feedDocument.generator = meta.generator;
        feedDocument.categories = meta.categories;
        feedDocument.lastModified = lastModified;

        feedDocument.save(function (error, feedDocument) {
            if(error != null) {
                logError('Failed to update feed meta to MongoDB: '+error);
            }
            else {
                logDebug('Updated feed "'+feedDocument.url+'" in MongoDB.');
            }
        });
    }
}

function feedRequiresUpdate(feedDocument, meta, lastModified) {
    var requiresUpdate = false;

    if(feedDocument.title != meta.title &&
        feedDocument.description != meta.description &&
        feedDocument.link != meta.link &&
        feedDocument.xmlUrl != meta.xmlUrl &&
        feedDocument.author != meta.author &&
        feedDocument.language != meta.language &&
        feedDocument.image.title != meta.image.title &&
        feedDocument.image.url != meta.image.url &&
        feedDocument.favicon != meta.favicon &&
        feedDocument.copyright != meta.copyright &&
        feedDocument.generator != meta.generator &&
        JSON.stringify(feedDocument.categories) !=
            JSON.stringify(meta.categories) &&
        feedDocument.lastModified != lastModified) {
        requiresUpdate = true;
    }

    return requiresUpdate;
}

function createFeedDocument(meta, url, lastModified) {
    return new Feed({ url: url, title: meta.title,
        description: meta.description, link: meta.link, xmlUrl: meta.xmlUrl,
        date: meta.date, pubDate: meta.pubDate, author: meta.author,
        language: meta.language, image: meta.image, favicon: meta.favicon,
        copyright: meta.copyright, generator: meta.generator,
        categories: meta.categories, lastModified: lastModified });
}

function saveOrUpdateArticle(article, url) {
    Article.findOne({ guid: article.guid, feed: url }, function(error,
        articleDocument) {
        if(error != null) {
            logError('Failed to get article from MongoDB: '+error);
        }
        else {
            if(articleDocument == null) {
                saveArticle(article, url);
            }
            else {
                updateArticle(articleDocument, article);
            }
        }
    });
}

function saveArticle(article, url) {
    var articleDocument = createArticleDocument(article, url);

    articleDocument.save(function (error, articleDocument) {
        if(error != null) {
            logError('Failed to save article to MongoDB: '+error);
        }
        else {
            logDebug('Added new article "'+articleDocument.guid
            + '" to MongoDB.');
        }
    });
}

function updateArticle(articleDocument, article) {
    if(articleRequiresUpdate(articleDocument, article)) {
        articleDocument.title = article.title;
        articleDocument.description = article.description;
        articleDocument.link = article.link;
        articleDocument.xmlUrl = article.xmlUrl;
        articleDocument.date = article.date;
        articleDocument.pubDate = article.pubDate;
        articleDocument.author = article.author;
        articleDocument.language = article.language;
        articleDocument.image = article.image;
        articleDocument.favicon = article.favicon;
        articleDocument.copyright = article.copyright;
        articleDocument.generator = article.generator;
        articleDocument.categories = article.categories;

        articleDocument.save(function (error, feedDocument) {
            if(error != null) {
                logError('Failed to update article to MongoDB: '+error);
            }
            else {
                logDebug('Updated article "'+articleDocument.guid
                +'" in MongoDB.');
            }
        });
    }
}

function articleRequiresUpdate(articleDocument, article) {
    var requiresUpdate = false;

    if(articleDocument.title != article.title &&
        articleDocument.description != article.description &&
        articleDocument.link != article.link &&
        articleDocument.origLink != article.origLink &&
        articleDocument.author != article.author &&
        articleDocument.guid != article.guid &&
        articleDocument.comments != article.comments &&
        articleDocument.image.title != article.image.title &&
        articleDocument.image.url != article.image.url &&
        JSON.stringify(articleDocument.categories) !=
            JSON.stringify(article.categories) &&
        articleDocument.source.title != article.source.title &&
        articleDocument.source.url != article.source.url &&
        articleDocument.enclosures.url != article.enclosures.url &&
        articleDocument.enclosures.type != article.enclosures.type &&
        articleDocument.enclosures.length != article.enclosures.length) {
        requiresUpdate = true;
    }

    return requiresUpdate;

}

function createArticleDocument(article, url) {
    return new Article({ title: article.title,
        description: article.description, link: article.link,
        origLink: article.origLink, date: article.date,
        pubDate: article.pubDate, author: article.author, guid: article.guid,
        comments: article.comments, image: article.image,
        categories: article.categories, source: article.source,
        enclosures: article.enclosures, feed: url });
}

function updateDatabase() {
    Feed.find(function(error, feedDocuments) {
        if(error != null) {
            logError('Failed to get feeds from MongoDB: '+error);
        }
        else {
            feedDocuments.forEach(function(feedDocument) {
                requestAndParseFeed(feedDocument.url);
            });
        }
    });
}

function updateDatabaseAtInterval(seconds) {
    if(updateIntervalHandle == null) {
        updateIntervalHandle = setInterval(updateDatabase, seconds);
    }
    else {
        logWarning('Database update interval already set. Skipping.');
    }
}

function stopUpdateDataBaseAtInterval() {
    if(updateIntervalHandle != null) {
        clearInterval(updateIntervalHandle);
        updateIntervalHandle = null;
    }
}

function getArticlesByKeyword(keyword, limit, callback) {
    var searchTerm = new RegExp('.*(\\s|-)+'+keyword+'(\\s|-)+.*', 'i');

    var query = Article.find({ $or: [ { title: searchTerm }, { description:
    searchTerm }, { author: searchTerm } ] }).limit(limit);

    query.execFind(function (error, articleDocuments) {
        if(error != null) {
            logError('Failed to get articles from MongoDB: '+error);
        }
        else {
            callback(articleDocuments);
        }
    });
}

function getArticlesByKeywordArray(keywords, limit, callback) {
    var keyword = '('+keywords.join('|')+')';
    getArticlesByKeyword(keyword, limit, callback);
}

function logDebug(message) {
    console.log(getTimeStampForLog()+' [DEBUG] '+message);
}

function logWarning(message) {
    console.warn(getTimeStampForLog()+' [WARN] '+message);
}

function logError(message) {
    console.error(getTimeStampForLog()+' [ERROR] '+message);
}

function getTimeStampForLog() {
    return new Date().toUTCString();;
}

exports.connect = connect;
exports.addFeed = addFeed;
exports.updateDatabase = updateDatabase;
exports.updateDatabaseAtInterval = updateDatabaseAtInterval;
exports.stopUpdateDataBaseAtInterval = stopUpdateDataBaseAtInterval;
exports.getArticlesByKeyword = getArticlesByKeyword;
exports.getArticlesByKeywordArray = getArticlesByKeywordArray;
exports.removeFeed = removeFeed;
exports.removeArticlesOlderThan = removeArticlesOlderThan;
<|endoftext|>


<commit_before>'use strict';

angular.module('ddsCommon').factory('CustomizationService', function(lyonMetropoleInseeCodes) {

    function determineCustomizationId(testCase, currentPeriod) {
        if (testCase.menages &&
            testCase.menages._) {
            if (testCase.menages._.depcom[currentPeriod].match(/^93/))
                return 'D93-SSD';
            if (testCase.menages._.depcom[currentPeriod].match(/^75/))
                return 'D75-PARIS';
            if (testCase.menages._.depcom[currentPeriod].match(/^14/))
                return 'D14-CALVADOS';
            if (_.includes(lyonMetropoleInseeCodes, testCase.menages._.depcom[currentPeriod]))
                return 'M69-LYON';
        }

        return undefined;
    }

    return {
        determineCustomizationId: determineCustomizationId,
    };
});
<commit_msg>Add customization for conseil departemental nord<commit_after>'use strict';

angular.module('ddsCommon').factory('CustomizationService', function(lyonMetropoleInseeCodes) {

    function determineCustomizationId(testCase, currentPeriod) {
        if (testCase.menages &&
            testCase.menages._) {
            if (testCase.menages._.depcom[currentPeriod].match(/^93/))
                return 'D93-SSD';
            if (testCase.menages._.depcom[currentPeriod].match(/^75/))
                return 'D75-PARIS';
            if (testCase.menages._.depcom[currentPeriod].match(/^14/))
                return 'D14-CALVADOS';
            if (testCase.menages._.depcom[currentPeriod].match(/^59/))
                return 'D59-NORD';
            if (_.includes(lyonMetropoleInseeCodes, testCase.menages._.depcom[currentPeriod]))
                return 'M69-LYON';
        }

        return undefined;
    }

    return {
        determineCustomizationId: determineCustomizationId,
    };
});
<|endoftext|>


<commit_before>(function ( $ ) {

$.widget( "mobile.page", $.mobile.widget, {
	options: {},

	_create: function() {
		if ( this._trigger( "beforeCreate" ) === false ) {
			return;
		}

		//some of the form elements currently rely on the presence of ui-page and ui-content
		// classes so we'll handle page and content roles outside of the main role processing
		// loop below.
		this.element.find( "[data-role=page],[data-role=content]" ).andSelf().each(function() {
			var $this = $( this );
			$this.addClass( "ui-" + $this.data( "role" ) );
		});

		this.element.find( "[data-role='nojs']" ).addClass( "ui-nojs" );
		this._enchanceControls();

		//pre-find data els
		var $dataEls = this.element.find( "[data-role]" ).andSelf().each(function() {
			var $this = $( this ),
				role = $this.data( "role" ),
				theme = $this.data( "theme" );

			//apply theming and markup modifications to page,header,content,footer
			if ( role === "header" || role === "footer" ) {
				$this.addClass( "ui-bar-" + (theme || "a") );

				//add ARIA role
				if( role == "header" ){
					$this.attr("role","banner");
				}
				else{
					$this.attr("role","contentinfo");
				}

				//right,left buttons
				var $headeranchors = $this.children( "a" ),
					leftbtn = $headeranchors.hasClass( "ui-btn-left" ),
					rightbtn = $headeranchors.hasClass( "ui-btn-right" );

				if ( !leftbtn ) {
					leftbtn = $headeranchors.eq( 0 ).not('.ui-btn-right').addClass( "ui-btn-left" ).length;
				}
				if ( !rightbtn ) {
					rightbtn = $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
				}

				//auto-add back btn on pages beyond first view
				if ( $.mobile.addBackBtn && role === "header" && ($.mobile.urlStack.length > 1 || $('.ui-page').length > 1) && !leftbtn && !$this.data( "noBackBtn" ) ) {
					$( "Back" )
						.click(function() {
							history.back();
							return false;
						})
						.prependTo( $this );
				}

				//page title
				$this.children( "h1, h2, h3, h4, h5, h6" )
					.addClass( "ui-title" )
					.attr( "tabindex" , "0")
					.attr( "role" ,"heading")
					.attr( "aria-level", "1" ); //regardless of h element number in src, it becomes h1 for the enhanced page
			} else if ( role === "content" ) {
				if( theme ){
					$this.addClass( "ui-body-" + theme);
				}
				//add ARIA role
				$this.attr("role","main");
			}
			else if( role == "page" ){
				$this.addClass( "ui-body-" + (theme || "c") );
			}

			switch(role) {
			case "header":
			case "footer":
			case "page":
			case "content":
				$this.addClass( "ui-" + role );
				break;
			case "collapsible":
			case "fieldcontain":
			case "navbar":
			case "listview":
			case "dialog":
			case "ajaxform":
				$this[ role ]();
				break;
			}
		});

		//links in bars, or those with data-role become buttons
		this.element.find( "[data-role='button'], .ui-bar a, .ui-header a, .ui-footer a" )
			.not( ".ui-btn" )
			.buttonMarkup();

		this.element
			.find("[data-role='controlgroup']")
			.controlgroup();

		//links within content areas
		this.element.find( "a:not(.ui-btn):not(.ui-link-inherit)" )
			.addClass( "ui-link" );

		//fix toolbars
		this.element.fixHeaderFooter();
	},

	_enchanceControls: function() {
		// degrade inputs to avoid poorly implemented native functionality
		this.element.find( "input" ).each(function() {
			var type = this.getAttribute( "type" );
			if ( $.mobile.degradeInputs[ type ] ) {
				$( this ).replaceWith(
					$( "
" ).html( $(this).clone() ).html()
						.replace( /type="([a-zA-Z]+)"/, "data-type='$1'" ) );
			}
		});

		// enchance form controls
		this.element
			.find( "[type='radio'], [type='checkbox']" )
			.customCheckboxRadio();

		this.element
			.find( "button, [type='button'], [type='submit'], [type='reset'], [type='image']" )
			.not( ".ui-nojs" )
			.customButton();

		this.element
			.find( "input, textarea" )
			.not( "[type='radio'], [type='checkbox'], button, [type='button'], [type='submit'], [type='reset'], [type='image']" )
			.customTextInput();

		this.element
			.find( "input, select" )
			.filter( "[data-role='slider'], [data-type='range']" )
			.slider();

		this.element
			.find( "select:not([data-role='slider'])" )
			.customSelect();
	}
});

})( jQuery );
<commit_msg>Minor tweaks to page logic.<commit_after>(function ( jQuery ) {

jQuery.widget( "mobile.page", jQuery.mobile.widget, {
	options: {},

	_create: function() {
		var $elem = this.element;

		if ( this._trigger( "beforeCreate" ) === false ) {
			return;
		}

		//some of the form elements currently rely on the presence of ui-page and ui-content
		// classes so we'll handle page and content roles outside of the main role processing
		// loop below.
		$elem.find( "[data-role='page'], [data-role='content']" ).andSelf().each(function() {
			$(this).addClass( "ui-" + $(this).data( "role" ) );
		});

		$elem.find( "[data-role='nojs']" ).addClass( "ui-nojs" );

		this._enchanceControls();

		// pre-find data els
		var $dataEls = $elem.find( "[data-role]" ).andSelf().each(function() {
			var $this = $( this ),
				role = $this.data( "role" ),
				theme = $this.data( "theme" );

			//apply theming and markup modifications to page,header,content,footer
			if ( role === "header" || role === "footer" ) {
				$this.addClass( "ui-bar-" + (theme || "a") );

				//add ARIA role
				if( role == "header" ){
					$this.attr("role","banner");
				}
				else{
					$this.attr("role","contentinfo");
				}

				//right,left buttons
				var $headeranchors = $this.children( "a" ),
					leftbtn = $headeranchors.hasClass( "ui-btn-left" ),
					rightbtn = $headeranchors.hasClass( "ui-btn-right" );

				if ( !leftbtn ) {
					leftbtn = $headeranchors.eq( 0 ).not('.ui-btn-right').addClass( "ui-btn-left" ).length;
				}
				if ( !rightbtn ) {
					rightbtn = $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
				}

				//auto-add back btn on pages beyond first view
				if ( $.mobile.addBackBtn && role === "header" && ($.mobile.urlStack.length > 1 || $('.ui-page').length > 1) && !leftbtn && !$this.data( "noBackBtn" ) ) {
					$( "Back" )
						.click(function() {
							history.back();
							return false;
						})
						.prependTo( $this );
				}

				//page title
				$this.children( "h1, h2, h3, h4, h5, h6" )
					.addClass( "ui-title" )
					.attr( "tabindex" , "0")
					.attr( "role" ,"heading")
					.attr( "aria-level", "1" ); //regardless of h element number in src, it becomes h1 for the enhanced page
			} else if ( role === "content" ) {
				if( theme ){
					$this.addClass( "ui-body-" + theme);
				}
				//add ARIA role
				$this.attr("role","main");
			}
			else if( role == "page" ){
				$this.addClass( "ui-body-" + (theme || "c") );
			}

			switch(role) {
			case "header":
			case "footer":
			case "page":
			case "content":
				$this.addClass( "ui-" + role );
				break;
			case "collapsible":
			case "fieldcontain":
			case "navbar":
			case "listview":
			case "dialog":
			case "ajaxform":
				$this[ role ]();
				break;
			}
		});

		//links in bars, or those with data-role become buttons
		$elem.find( "[data-role='button'], .ui-bar a, .ui-header a, .ui-footer a" )
			.not( ".ui-btn" )
			.buttonMarkup();

		$elem
			.find("[data-role='controlgroup']")
			.controlgroup();

		//links within content areas
		$elem.find( "a:not(.ui-btn):not(.ui-link-inherit)" )
			.addClass( "ui-link" );

		//fix toolbars
		$elem.fixHeaderFooter();
	},

	_enchanceControls: function() {
		// degrade inputs to avoid poorly implemented native functionality
		this.element.find( "input" ).each(function() {
			var type = this.getAttribute( "type" );
			if ( $.mobile.degradeInputs[ type ] ) {
				$( this ).replaceWith(
					$( "
" ).html( $(this).clone() ).html()
						.replace( /type="([a-zA-Z]+)"/, "data-type='$1'" ) );
			}
		});

		// enchance form controls
		this.element
			.find( "[type='radio'], [type='checkbox']" )
			.customCheckboxRadio();

		this.element
			.find( "button, [type='button'], [type='submit'], [type='reset'], [type='image']" )
			.not( ".ui-nojs" )
			.customButton();

		this.element
			.find( "input, textarea" )
			.not( "[type='radio'], [type='checkbox'], button, [type='button'], [type='submit'], [type='reset'], [type='image']" )
			.customTextInput();

		this.element
			.find( "input, select" )
			.filter( "[data-role='slider'], [data-type='range']" )
			.slider();

		this.element
			.find( "select:not([data-role='slider'])" )
			.customSelect();
	}
});

})( jQuery );
<|endoftext|>


<commit_before>import _ from 'lodash';
import Field from '../_classes/field/Field';
import { boolValue, getLocaleDateFormatInfo } from '../../utils/utils';

export default class DayComponent extends Field {
  static schema(...extend) {
    return Field.schema({
      type: 'day',
      label: 'Day',
      key: 'day',
      fields: {
        day: {
          type: 'number',
          placeholder: '',
          required: false
        },
        month: {
          type: 'select',
          placeholder: '',
          required: false
        },
        year: {
          type: 'number',
          placeholder: '',
          required: false
        }
      },
      dayFirst: false
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Day',
      group: 'common',
      icon: 'calendar',
      documentation: 'http://help.form.io/userguide/#day',
      weight: 50,
      schema: DayComponent.schema()
    };
  }

  get dayRequired() {
    return this.showDay && _.get(this.component, 'fields.day.required', false);
  }

  get showDay() {
    return !_.get(this.component, 'fields.day.hide', false);
  }

  get monthRequired() {
    return this.showMonth && _.get(this.component, 'fields.month.required', false);
  }

  get showMonth() {
    return !_.get(this.component, 'fields.month.hide', false);
  }

  get yearRequired() {
    return this.showYear && _.get(this.component, 'fields.year.required', false);
  }

  get showYear() {
    return !_.get(this.component, 'fields.year.hide', false);
  }

  get defaultSchema() {
    return DayComponent.schema();
  }

  get inputInfo() {
    const info = super.elementInfo();
    info.type = 'input';
    info.attr.type = 'hidden';
    info.changeEvent = 'change';
    return info;
  }

  inputDefinition(name) {
    let min, max;
    if (name === 'day') {
      min = 1;
      max = 31;
    }
    if (name === 'month') {
      min = 1;
      max = 12;
    }
    if (name === 'year') {
      min = _.get(this.component, 'fields.year.minYear', 1900) || 1900;
      max = _.get(this.component, 'fields.year.maxYear', 2030) || 1900;
    }
    return {
      type: 'input',
      ref: name,
      attr: {
        id: `${this.component.key}-${name}`,
        class: 'form-control',
        type: this.component.fields[name].type === 'select' ? 'select' : 'number',
        placeholder: this.component.fields[name].placeholder,
        step: 1,
        min,
        max,
      }
    };
  }

  selectDefinition(name) {
    return {
      multiple: false,
      ref: name,
      widget: 'html5',
      attr: {
        class: 'form-control',
        name,
        lang: this.options.language
      }
    };
  }

  get days() {
    if (this._days) {
      return this._days;
    }
    this._days = [
      { value: 0, label: _.get(this.component, 'fields.day.placeholder', '') }
    ];
    for (let x = 1; x <= 31; x++) {
      this._days.push({
        value: x,
        label: x.toString()
      });
    }
    return this._days;
  }

  get months() {
    if (this._months) {
      return this._months;
    }
    this._months = [
      { value: undefined, label: _.get(this.component, 'fields.month.placeholder') || (this.hideInputLabels ? this.t('Month') : '') },
      { value: 1, label: 'January' },
      { value: 2, label: 'February' },
      { value: 3, label: 'March' },
      { value: 4, label: 'April' },
      { value: 5, label: 'May' },
      { value: 6, label: 'June' },
      { value: 7, label: 'July' },
      { value: 8, label: 'August' },
      { value: 9, label: 'September' },
      { value: 10, label: 'October' },
      { value: 11, label: 'November' },
      { value: 12, label: 'December' }
    ];
    return this._months;
  }

  get years() {
    if (this._years) {
      return this._years;
    }
    this._years = [
      { value: 0, label: _.get(this.component, 'fields.year.placeholder', '') }
    ];
    const minYears = _.get(this.component, 'fields.year.minYear', 1900) || 1900;
    const maxYears = _.get(this.component, 'fields.year.maxYear', 2030) || 2030;
    for (let x = minYears; x <= maxYears; x++) {
      this._years.push({
        value: x,
        label: x.toString()
      });
    }
    return this._years;
  }

  init() {
    super.init();
    this.validators = this.validators.concat(['day', 'maxDate', 'minDate']);
    const dateFormatInfo = getLocaleDateFormatInfo(this.options.language);
    this.dayFirst = this.component.useLocaleSettings
      ? dateFormatInfo.dayFirst
      : this.component.dayFirst;
  }

  render() {
    return super.render(this.renderTemplate('day', {
      dayFirst: this.dayFirst,
      showDay: this.showDay,
      showMonth: this.showMonth,
      showYear: this.showYear,
      day: this.renderField('day'),
      month: this.renderField('month'),
      year: this.renderField('year'),
    }));
  }

  renderField(name) {
    if (this.component.fields[name].type === 'select') {
      return this.renderTemplate('select', {
        input: this.selectDefinition(name),
        selectOptions: this[`${name}s`].reduce((html, option) =>
          html + this.renderTemplate('selectOption', {
            option,
            selected: false, attrs: {}
          }), ''
        ),
      });
    }
    else {
      return this.renderTemplate('input', {
        input: this.inputDefinition(name)
      });
    }
  }

  attach(element) {
    this.loadRefs(element, { day: 'single', month: 'single', year: 'single', input: 'multiple' });
    super.attach(element);
    this.addEventListener(this.refs.day, 'change', () => this.updateValue());
    this.addEventListener(this.refs.month, 'change', () => this.updateValue());
    // TODO: Need to rework this to work with day select as well.
    // Change day max input when month changes.
    this.addEventListener(this.refs.month, 'change', () => {
      const maxDay = parseInt(new Date(this.refs.year.value, this.refs.month.value, 0).getDate(), 10);
      const day = this.getFieldValue('day');
      this.refs.day.max = maxDay;
      if (day > maxDay) {
        this.refs.day.value = this.refs.day.max;
      }
    });
    this.addEventListener(this.refs.year, 'change', () => this.updateValue());
    this.addEventListener(this.refs.input, this.info.changeEvent, () => this.updateValue());
    this.setValue(this.dataValue);
  }

  validateRequired(setting, value) {
    const { day, month, year } = this.parts;
    if (this.dayRequired && !day) {
      return false;
    }

    if (this.monthRequired && month < 0) {
      return false;
    }

    if (this.yearRequired && !year) {
      return false;
    }

    if (!boolValue(setting)) {
      return true;
    }
    return !this.isEmpty(value);
  }

  set disabled(disabled) {
    super.disabled = disabled;
    if (!this.refs.year || !this.refs.month || !this.refs.day) {
      return;
    }
    if (disabled) {
      this.refs.year.setAttribute('disabled', 'disabled');
      this.refs.month.setAttribute('disabled', 'disabled');
      this.refs.day.setAttribute('disabled', 'disabled');
    }
    else {
      this.refs.year.removeAttribute('disabled');
      this.refs.month.removeAttribute('disabled');
      this.refs.day.removeAttribute('disabled');
    }
  }

  /**
   * Set the value at a specific index.
   *
   * @param index
   * @param value
   */
  setValueAt(index, value) {
    // temporary solution to avoid input reset
    // on invalid date.
    if (!value || value === 'Invalid date') {
      return null;
    }
    const parts = value.split('/');
    let day, month, year;
    if (this.refs.day && this.component.dayFirst && this.showDay) {
      day = parts.shift();
      this.refs.day.value = day === '00' ? undefined : parseInt(day, 10);
    }
    if (this.refs.month && this.showMonth) {
      month = parts.shift();
      this.refs.month.value = month === '00' ? undefined : parseInt(month, 10);
    }
    if (this.refs.day && !this.component.dayFirst && this.showDay) {
      day = parts.shift();
      this.refs.day.value = day === '00' ? undefined : parseInt(day, 10);
    }
    if (this.refs.year && this.showYear) {
      year = parts.shift();
      this.refs.year.value = year === '0000' ? undefined : parseInt(year, 10);
    }
  }

  getFieldValue(name) {
    let val = 0;
    if (!this.refs[name]) {
      return val;
    }
    if (this.component.fields[name].type === 'number') {
      val = this.refs[name].value;
    }
    else if (this.component.fields[name].type === 'select') {
      val = this.refs[name].options[this.refs[name].selectedIndex].value;
    }

    val = parseInt(val, 10);
    return (!_.isNaN(val) && _.isNumber(val)) ? val : 0;
  }

  get parts() {
    return {
      day: this.getFieldValue('day'),
      month: this.getFieldValue('month'),
      year: this.getFieldValue('year'),
    };
  }

  /**
   * Get the format for the value string.
   * @returns {string}
   */
  get format() {
    let format = '';
    if (this.component.dayFirst && this.showDay) {
      format += 'D/';
    }
    if (this.showMonth) {
      format += 'M/';
    }
    if (!this.component.dayFirst && this.showDay) {
      format += 'D/';
    }
    if (this.showYear) {
      format += 'YYYY';
      return format;
    }
    else {
      // Trim off the "/" from the end of the format string.
      return format.length ? format.substring(0, format.length - 1) : format;
    }
  }

  /**
   * Return the date for this component.
   *
   * @param value
   * @return {*}
   */
  getDate(value) {
    let defaults = [], day, month, year;
    // Map positions to identifiers to get default values for each part of day
    const [DAY, MONTH, YEAR] = this.component.dayFirst ? [0, 1, 2] : [1, 0, 2];
    const defaultValue = value || this.component.defaultValue;
    if (defaultValue) {
      defaults = defaultValue.split('/').map(x => parseInt(x, 10));
    }

    if (this.showDay && this.refs.day) {
      day = parseInt(this.refs.day.value, 10);
    }
    if (day === undefined || _.isNaN(day)) {
      day = defaults[DAY] && !_.isNaN(defaults[DAY]) ? defaults[DAY] : 0;
    }

    if (this.showMonth && this.refs.month) {
      // Months are 0 indexed.
      month = parseInt(this.refs.month.value, 10);
    }
    if (month === undefined || _.isNaN(month)) {
      month = defaults[MONTH] && !_.isNaN(defaults[MONTH]) ? defaults[MONTH] : 0;
    }

    if (this.showYear && this.refs.year) {
      year = parseInt(this.refs.year.value);
    }
    if (year === undefined || _.isNaN(year)) {
      year = defaults[YEAR] && !_.isNaN(defaults[YEAR]) ? defaults[YEAR] : 0;
    }

    let result;
    if (!day && !month && !year) {
      return null;
    }

    //add trailing zeros
    day = day.toString().padStart(2, 0);
    month = month.toString().padStart(2, 0);
    year = year.toString().padStart(4, 0);
    if (this.component.dayFirst) {
      result = `${day}/${month}/${year}`;
    }
    else {
      result = `${month}/${day}/${year}`;
    }
    return result;
  }

  /**
   * Return the date object for this component.
   * @returns {Date}
   */
  get date() {
    return this.getDate();
  }

  /**
   * Return the raw value.
   *
   * @returns {Date}
   */
  get validationValue() {
    return this.date;
  }

  /**
   * Get the value at a specific index.
   *
   * @param index
   * @returns {*}
   */
  getValueAt(index) {
    const date = this.date;
    if (date) {
      this.refs.input[index].value = date;
      return this.refs.input[index].value;
    }
    else {
      this.refs.input[index].value = '';
      return null;
    }
  }

  /**
   * Get the view of the date.
   *
   * @param value
   * @return {null}
   */
  getView(value) {
    return this.getDate(value);
  }

  focus() {
    if (this.dayFirst && this.showDay || !this.dayFirst && !this.showMonth && this.showDay) {
      this.refs.day.focus();
    }
    else if (this.dayFirst && !this.showDay && this.showMonth || !this.dayFirst && this.showMonth) {
      this.refs.month.focus();
    }
    else if (!this.showDay && !this.showDay && this.showYear) {
      this.refs.year.focus();
    }
  }

  isPartialDay(value) {
    if (!value) {
      return false;
    }
    const [DAY, MONTH, YEAR] = this.component.dayFirst ? [0, 1, 2] : [1, 0, 2];
    const values = value.split('/');
    return (values[DAY] === '00' || values[MONTH] === '00' || values[YEAR] === '0000');
  }
}
<commit_msg>FOR-2019: Added ability to override Day component CSS classes<commit_after>import _ from 'lodash';
import Field from '../_classes/field/Field';
import { boolValue, getLocaleDateFormatInfo } from '../../utils/utils';

export default class DayComponent extends Field {
  static schema(...extend) {
    return Field.schema({
      type: 'day',
      label: 'Day',
      key: 'day',
      fields: {
        day: {
          type: 'number',
          placeholder: '',
          required: false
        },
        month: {
          type: 'select',
          placeholder: '',
          required: false
        },
        year: {
          type: 'number',
          placeholder: '',
          required: false
        }
      },
      dayFirst: false
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Day',
      group: 'common',
      icon: 'calendar',
      documentation: 'http://help.form.io/userguide/#day',
      weight: 50,
      schema: DayComponent.schema()
    };
  }

  get dayRequired() {
    return this.showDay && _.get(this.component, 'fields.day.required', false);
  }

  get showDay() {
    return !_.get(this.component, 'fields.day.hide', false);
  }

  get monthRequired() {
    return this.showMonth && _.get(this.component, 'fields.month.required', false);
  }

  get showMonth() {
    return !_.get(this.component, 'fields.month.hide', false);
  }

  get yearRequired() {
    return this.showYear && _.get(this.component, 'fields.year.required', false);
  }

  get showYear() {
    return !_.get(this.component, 'fields.year.hide', false);
  }

  get defaultSchema() {
    return DayComponent.schema();
  }

  get inputInfo() {
    const info = super.elementInfo();
    info.type = 'input';
    info.attr.type = 'hidden';
    info.changeEvent = 'change';
    return info;
  }

  inputDefinition(name) {
    let min, max;
    if (name === 'day') {
      min = 1;
      max = 31;
    }
    if (name === 'month') {
      min = 1;
      max = 12;
    }
    if (name === 'year') {
      min = _.get(this.component, 'fields.year.minYear', 1900) || 1900;
      max = _.get(this.component, 'fields.year.maxYear', 2030) || 1900;
    }
    return {
      type: 'input',
      ref: name,
      attr: {
        id: `${this.component.key}-${name}`,
        class: `form-control ${this.transform('class', `formio-day-component-${name}`)}`,
        type: this.component.fields[name].type === 'select' ? 'select' : 'number',
        placeholder: this.component.fields[name].placeholder,
        step: 1,
        min,
        max,
      }
    };
  }

  selectDefinition(name) {
    return {
      multiple: false,
      ref: name,
      widget: 'html5',
      attr: {
        class: 'form-control',
        name,
        lang: this.options.language
      }
    };
  }

  get days() {
    if (this._days) {
      return this._days;
    }
    this._days = [
      { value: 0, label: _.get(this.component, 'fields.day.placeholder', '') }
    ];
    for (let x = 1; x <= 31; x++) {
      this._days.push({
        value: x,
        label: x.toString()
      });
    }
    return this._days;
  }

  get months() {
    if (this._months) {
      return this._months;
    }
    this._months = [
      { value: undefined, label: _.get(this.component, 'fields.month.placeholder') || (this.hideInputLabels ? this.t('Month') : '') },
      { value: 1, label: 'January' },
      { value: 2, label: 'February' },
      { value: 3, label: 'March' },
      { value: 4, label: 'April' },
      { value: 5, label: 'May' },
      { value: 6, label: 'June' },
      { value: 7, label: 'July' },
      { value: 8, label: 'August' },
      { value: 9, label: 'September' },
      { value: 10, label: 'October' },
      { value: 11, label: 'November' },
      { value: 12, label: 'December' }
    ];
    return this._months;
  }

  get years() {
    if (this._years) {
      return this._years;
    }
    this._years = [
      { value: 0, label: _.get(this.component, 'fields.year.placeholder', '') }
    ];
    const minYears = _.get(this.component, 'fields.year.minYear', 1900) || 1900;
    const maxYears = _.get(this.component, 'fields.year.maxYear', 2030) || 2030;
    for (let x = minYears; x <= maxYears; x++) {
      this._years.push({
        value: x,
        label: x.toString()
      });
    }
    return this._years;
  }

  init() {
    super.init();
    this.validators = this.validators.concat(['day', 'maxDate', 'minDate']);
    const dateFormatInfo = getLocaleDateFormatInfo(this.options.language);
    this.dayFirst = this.component.useLocaleSettings
      ? dateFormatInfo.dayFirst
      : this.component.dayFirst;
  }

  render() {
    return super.render(this.renderTemplate('day', {
      dayFirst: this.dayFirst,
      showDay: this.showDay,
      showMonth: this.showMonth,
      showYear: this.showYear,
      day: this.renderField('day'),
      month: this.renderField('month'),
      year: this.renderField('year'),
    }));
  }

  renderField(name) {
    if (this.component.fields[name].type === 'select') {
      return this.renderTemplate('select', {
        input: this.selectDefinition(name),
        selectOptions: this[`${name}s`].reduce((html, option) =>
          html + this.renderTemplate('selectOption', {
            option,
            selected: false, attrs: {}
          }), ''
        ),
      });
    }
    else {
      return this.renderTemplate('input', {
        input: this.inputDefinition(name)
      });
    }
  }

  attach(element) {
    this.loadRefs(element, { day: 'single', month: 'single', year: 'single', input: 'multiple' });
    super.attach(element);
    this.addEventListener(this.refs.day, 'change', () => this.updateValue());
    this.addEventListener(this.refs.month, 'change', () => this.updateValue());
    // TODO: Need to rework this to work with day select as well.
    // Change day max input when month changes.
    this.addEventListener(this.refs.month, 'change', () => {
      const maxDay = parseInt(new Date(this.refs.year.value, this.refs.month.value, 0).getDate(), 10);
      const day = this.getFieldValue('day');
      this.refs.day.max = maxDay;
      if (day > maxDay) {
        this.refs.day.value = this.refs.day.max;
      }
    });
    this.addEventListener(this.refs.year, 'change', () => this.updateValue());
    this.addEventListener(this.refs.input, this.info.changeEvent, () => this.updateValue());
    this.setValue(this.dataValue);
  }

  validateRequired(setting, value) {
    const { day, month, year } = this.parts;
    if (this.dayRequired && !day) {
      return false;
    }

    if (this.monthRequired && month < 0) {
      return false;
    }

    if (this.yearRequired && !year) {
      return false;
    }

    if (!boolValue(setting)) {
      return true;
    }
    return !this.isEmpty(value);
  }

  set disabled(disabled) {
    super.disabled = disabled;
    if (!this.refs.year || !this.refs.month || !this.refs.day) {
      return;
    }
    if (disabled) {
      this.refs.year.setAttribute('disabled', 'disabled');
      this.refs.month.setAttribute('disabled', 'disabled');
      this.refs.day.setAttribute('disabled', 'disabled');
    }
    else {
      this.refs.year.removeAttribute('disabled');
      this.refs.month.removeAttribute('disabled');
      this.refs.day.removeAttribute('disabled');
    }
  }

  /**
   * Set the value at a specific index.
   *
   * @param index
   * @param value
   */
  setValueAt(index, value) {
    // temporary solution to avoid input reset
    // on invalid date.
    if (!value || value === 'Invalid date') {
      return null;
    }
    const parts = value.split('/');
    let day, month, year;
    if (this.refs.day && this.component.dayFirst && this.showDay) {
      day = parts.shift();
      this.refs.day.value = day === '00' ? undefined : parseInt(day, 10);
    }
    if (this.refs.month && this.showMonth) {
      month = parts.shift();
      this.refs.month.value = month === '00' ? undefined : parseInt(month, 10);
    }
    if (this.refs.day && !this.component.dayFirst && this.showDay) {
      day = parts.shift();
      this.refs.day.value = day === '00' ? undefined : parseInt(day, 10);
    }
    if (this.refs.year && this.showYear) {
      year = parts.shift();
      this.refs.year.value = year === '0000' ? undefined : parseInt(year, 10);
    }
  }

  getFieldValue(name) {
    let val = 0;
    if (!this.refs[name]) {
      return val;
    }
    if (this.component.fields[name].type === 'number') {
      val = this.refs[name].value;
    }
    else if (this.component.fields[name].type === 'select') {
      val = this.refs[name].options[this.refs[name].selectedIndex].value;
    }

    val = parseInt(val, 10);
    return (!_.isNaN(val) && _.isNumber(val)) ? val : 0;
  }

  get parts() {
    return {
      day: this.getFieldValue('day'),
      month: this.getFieldValue('month'),
      year: this.getFieldValue('year'),
    };
  }

  /**
   * Get the format for the value string.
   * @returns {string}
   */
  get format() {
    let format = '';
    if (this.component.dayFirst && this.showDay) {
      format += 'D/';
    }
    if (this.showMonth) {
      format += 'M/';
    }
    if (!this.component.dayFirst && this.showDay) {
      format += 'D/';
    }
    if (this.showYear) {
      format += 'YYYY';
      return format;
    }
    else {
      // Trim off the "/" from the end of the format string.
      return format.length ? format.substring(0, format.length - 1) : format;
    }
  }

  /**
   * Return the date for this component.
   *
   * @param value
   * @return {*}
   */
  getDate(value) {
    let defaults = [], day, month, year;
    // Map positions to identifiers to get default values for each part of day
    const [DAY, MONTH, YEAR] = this.component.dayFirst ? [0, 1, 2] : [1, 0, 2];
    const defaultValue = value || this.component.defaultValue;
    if (defaultValue) {
      defaults = defaultValue.split('/').map(x => parseInt(x, 10));
    }

    if (this.showDay && this.refs.day) {
      day = parseInt(this.refs.day.value, 10);
    }
    if (day === undefined || _.isNaN(day)) {
      day = defaults[DAY] && !_.isNaN(defaults[DAY]) ? defaults[DAY] : 0;
    }

    if (this.showMonth && this.refs.month) {
      // Months are 0 indexed.
      month = parseInt(this.refs.month.value, 10);
    }
    if (month === undefined || _.isNaN(month)) {
      month = defaults[MONTH] && !_.isNaN(defaults[MONTH]) ? defaults[MONTH] : 0;
    }

    if (this.showYear && this.refs.year) {
      year = parseInt(this.refs.year.value);
    }
    if (year === undefined || _.isNaN(year)) {
      year = defaults[YEAR] && !_.isNaN(defaults[YEAR]) ? defaults[YEAR] : 0;
    }

    let result;
    if (!day && !month && !year) {
      return null;
    }

    //add trailing zeros
    day = day.toString().padStart(2, 0);
    month = month.toString().padStart(2, 0);
    year = year.toString().padStart(4, 0);
    if (this.component.dayFirst) {
      result = `${day}/${month}/${year}`;
    }
    else {
      result = `${month}/${day}/${year}`;
    }
    return result;
  }

  /**
   * Return the date object for this component.
   * @returns {Date}
   */
  get date() {
    return this.getDate();
  }

  /**
   * Return the raw value.
   *
   * @returns {Date}
   */
  get validationValue() {
    return this.date;
  }

  /**
   * Get the value at a specific index.
   *
   * @param index
   * @returns {*}
   */
  getValueAt(index) {
    const date = this.date;
    if (date) {
      this.refs.input[index].value = date;
      return this.refs.input[index].value;
    }
    else {
      this.refs.input[index].value = '';
      return null;
    }
  }

  /**
   * Get the view of the date.
   *
   * @param value
   * @return {null}
   */
  getView(value) {
    return this.getDate(value);
  }

  focus() {
    if (this.dayFirst && this.showDay || !this.dayFirst && !this.showMonth && this.showDay) {
      this.refs.day.focus();
    }
    else if (this.dayFirst && !this.showDay && this.showMonth || !this.dayFirst && this.showMonth) {
      this.refs.month.focus();
    }
    else if (!this.showDay && !this.showDay && this.showYear) {
      this.refs.year.focus();
    }
  }

  isPartialDay(value) {
    if (!value) {
      return false;
    }
    const [DAY, MONTH, YEAR] = this.component.dayFirst ? [0, 1, 2] : [1, 0, 2];
    const values = value.split('/');
    return (values[DAY] === '00' || values[MONTH] === '00' || values[YEAR] === '0000');
  }
}
<|endoftext|>


<commit_before>$('.dropdown-toggle').dropdown();

if ($('#messages').attr('data-show')) {
	$('#messages').modal();
}

function countProperties(obj) {
	var count = 0;
	for(var prop in obj) {
		if(obj.hasOwnProperty(prop))
			++count;
	}
	return count;
}

function GoreadCtrl($scope, $http, $timeout) {
	$scope.loading = 0;
	$scope.contents = {};

	$scope.importOpml = function() {
		$scope.shown = 'feeds';
		$scope.loading++;
		$('#import-opml-form').ajaxForm(function() {
			$('#import-opml-form')[0].reset();
			$scope.loaded();
			$scope.showMessage('OPML is importing. May take a bit. Refresh at will.');
		});
	};

	$scope.loaded = function() {
		$scope.loading--;
	};

	$scope.http = function(method, url, data) {
		return $http({
			method: method,
			url: url,
			data: $.param(data),
			headers: {'Content-Type': 'application/x-www-form-urlencoded'}
		});
	};

	$scope.addSubscription = function() {
		if (!$scope.addFeedUrl) {
			return false;
		}
		$scope.loading++;
		var f = $('#add-subscription-form');
		$scope.http('POST', f.attr('data-url'), {
			url: $scope.addFeedUrl
		}).then(function() {
			$scope.addFeedUrl = '';
			// I think this is needed due to the datastore's eventual consistency.
			// Without the delay we only get the feed data with no story data.
			$timeout(function() {
				$scope.refresh($scope.loaded);
			}, 250);
		}, function(data) {
			if (data.data) {
				alert(data.data);
			}
			$scope.loading--;
		});
	};

	$scope.refresh = function(cb) {
		$scope.loading++;
		$scope.shown = 'feeds';
		delete $scope.currentStory;
		$http.get($('#refresh').attr('data-url-feeds'))
			.success(function(data) {
				$scope.feeds = data;
				$scope.numfeeds = 0;
				$scope.stories = [];
				$scope.unreadStories = {};
				var today = new Date().toDateString();
				for(var p in $scope.feeds) {
					$scope.numfeeds++;
					var f = $scope.feeds[p];
					if (!f.Stories)
						continue;
					for(var i = 0; i < f.Stories.length; i++) {
						f.Stories[i].feed = f.Feed;
						var d = new Date(f.Stories[i].Date * 1000);
						if (d.toDateString() == today) {
							f.Stories[i].dispdate = d.format("shortTime");
						} else {
							f.Stories[i].dispdate = d.format("mediumDate");
						}
						f.Stories[i].read = false;
						f.Stories[i].guid = f.Feed.Url + '|' + f.Stories[i].Id;
						if (!f.Stories[i].Title) {
							f.Stories[i].Title = '(title unknown)';
						}
						$scope.stories.push(f.Stories[i]);
						$scope.unreadStories[f.Stories[i].Id] = true;
					}
					$scope.stories.sort(function(a, b) {
						return b.Date - a.Date;
					});
				}
				if (typeof cb === 'function') cb();
				$scope.loaded();
				var ur = $scope.unread();
				$scope.updateTitle();
			})
			.error(function() {
				if (typeof cb === 'function') cb();
				$scope.loaded();
			});
	};

	$scope.updateTitle = function() {
		document.title = 'Go Read' + (ur != 0 ? ' (' + ur + ')' : '');
	};

	$scope.setCurrent = function(i) {
		if (i == $scope.currentStory) {
			delete $scope.currentStory;
			return;
		}
		var story = $scope.stories[i];
		$scope.getContents(story);
		if (i > 0) {
			$scope.getContents($scope.stories[i - 1]);
		}
		if (i < $scope.stories.length - 2) {
			$scope.getContents($scope.stories[i + 1]);
		}
		$('#story' + $scope.currentStory).empty();
		$scope.currentStory = i;
		$scope.markRead(story);
		$('#story' + i).html($scope.contents[story.guid] || '');
		setTimeout(function() {
			se = $('#storydiv' + i);
			var docTop = $(window).scrollTop() + 40;
			var docBottom = docTop + $(window).height() + 40;
			var eTop = se.offset().top;
			if (docTop > eTop || docBottom < eTop) {
				window.scrollTo(0, eTop - 40);
			}
		});
	};
	$scope.prev = function() {
		if ($scope.currentStory > 0) {
			$scope.setCurrent($scope.currentStory - 1);
		}
	};
	$scope.next = function() {
		if ($scope.stories && typeof $scope.currentStory === 'undefined') {
			$scope.setCurrent(0);
		} else if ($scope.stories && $scope.currentStory < $scope.stories.length - 1) {
			$scope.setCurrent($scope.currentStory + 1);
		}
	};

	$scope.unread = function() {
		return countProperties($scope.unreadStories);
	};

	$scope.markRead = function(s) {
		if ($scope.unreadStories[s.Id]) {
			delete $scope.unreadStories[s.Id];
			s.read = true;
			$scope.http('POST', $('#mark-all-read').attr('data-url-read'), {
				feed: s.feed.Url,
				story: s.Id
			});
			$scope.updateTitle();
		}
	};

	$scope.markAllRead = function(s) {
		$scope.unreadStories = {};
		$scope.stories = [];
		$http.post($('#mark-all-read').attr('data-url'));
		$scope.updateTitle();
	};

	$scope.nothing = function() {
		return $scope.loading == 0 && $scope.stories && !$scope.numfeeds && $scope.shown != 'about';
	};

	$scope.toggleNav = function() {
		$scope.nav = !$scope.nav;
	}
	$scope.navspan = function() {
		return 'span' + ($scope.nav ? '10' : '12');
	};
	$scope.navmargin = function() {
		return $scope.nav ? {} : {'margin-left': '0'};
	};

	$scope.overContents = function(s) {
		if (typeof $scope.contents[s.guid] !== 'undefined') {
			return;
		}
		s.getTimeout = $timeout(function() {
			$scope.getContents(s);
		}, 250);
	};
	$scope.leaveContents = function(s) {
		if (s.getTimeout) {
			$timeout.cancel(s.getTimeout);
		}
	};

	$scope.toFetch = [];
	$scope.getContents = function(s) {
		if (typeof $scope.contents[s.guid] !== 'undefined') {
			return;
		}
		$scope.toFetch.push(s);
		if (!$scope.fetchPromise) {
			$scope.fetchPromise = $timeout($scope.fetchContents);
		}
	};

	$scope.fetchContents = function() {
		delete $scope.fetchPromise;
		if ($scope.toFetch.length == 0) {
			return;
		}
		var tofetch = $scope.toFetch;
		$scope.toFetch = [];
		var data = [];
		for (var i = 0; i < tofetch.length; i++) {
			$scope.contents[tofetch[i].guid] = '';
			data.push({
				Feed: tofetch[i].feed.Url,
				Story: tofetch[i].Id
			});
		}
		$http.post($('#mark-all-read').attr('data-url-contents'), data)
			.success(function(data) {
				var current = '';
				if ($scope.stories[$scope.currentStory]) {
					current = $scope.stories[$scope.currentStory].guid;
				}
				for (var i = 0; i < data.length; i++) {
					$scope.contents[tofetch[i].guid] = data[i];
					if (current == tofetch[i].guid) {
						$('#story' + $scope.currentStory).html(data[i]);
					}
				}
			});
	};

	var shortcuts = $('#shortcuts');
	Mousetrap.bind('?', function() {
		shortcuts.modal('toggle');
	});
	Mousetrap.bind('esc', function() {
		shortcuts.modal('hide');
	});
	Mousetrap.bind('r', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply($scope.refresh());
	});
	Mousetrap.bind(['j', 'n'], function() {
		$scope.$apply('next()');
	});
	Mousetrap.bind(['k', 'p'], function() {
		$scope.$apply('prev()');
	});
	Mousetrap.bind('v', function() {
		if ($scope.stories[$scope.currentStory]) {
			window.open($scope.stories[$scope.currentStory].Link);
		}
	});
	Mousetrap.bind('shift+a', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply($scope.markAllRead());
	});
	Mousetrap.bind('a', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply("shown = 'add-subscription'");

		// need to wait for the keypress to finish before focusing
		setTimeout(function() {
			$('#add-subscription-form input').focus();
		}, 0);
	});
	Mousetrap.bind('g a', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply("shown = 'feeds'");
	});
	Mousetrap.bind('u', function() {
		$scope.$apply("toggleNav()");
	});

	$scope.showMessage = function(m) {
		$('#message-list').text(m);
		$('#messages').modal('show');
	};
}
<commit_msg>Missed unread count variable<commit_after>$('.dropdown-toggle').dropdown();

if ($('#messages').attr('data-show')) {
	$('#messages').modal();
}

function countProperties(obj) {
	var count = 0;
	for(var prop in obj) {
		if(obj.hasOwnProperty(prop))
			++count;
	}
	return count;
}

function GoreadCtrl($scope, $http, $timeout) {
	$scope.loading = 0;
	$scope.contents = {};

	$scope.importOpml = function() {
		$scope.shown = 'feeds';
		$scope.loading++;
		$('#import-opml-form').ajaxForm(function() {
			$('#import-opml-form')[0].reset();
			$scope.loaded();
			$scope.showMessage('OPML is importing. May take a bit. Refresh at will.');
		});
	};

	$scope.loaded = function() {
		$scope.loading--;
	};

	$scope.http = function(method, url, data) {
		return $http({
			method: method,
			url: url,
			data: $.param(data),
			headers: {'Content-Type': 'application/x-www-form-urlencoded'}
		});
	};

	$scope.addSubscription = function() {
		if (!$scope.addFeedUrl) {
			return false;
		}
		$scope.loading++;
		var f = $('#add-subscription-form');
		$scope.http('POST', f.attr('data-url'), {
			url: $scope.addFeedUrl
		}).then(function() {
			$scope.addFeedUrl = '';
			// I think this is needed due to the datastore's eventual consistency.
			// Without the delay we only get the feed data with no story data.
			$timeout(function() {
				$scope.refresh($scope.loaded);
			}, 250);
		}, function(data) {
			if (data.data) {
				alert(data.data);
			}
			$scope.loading--;
		});
	};

	$scope.refresh = function(cb) {
		$scope.loading++;
		$scope.shown = 'feeds';
		delete $scope.currentStory;
		$http.get($('#refresh').attr('data-url-feeds'))
			.success(function(data) {
				$scope.feeds = data;
				$scope.numfeeds = 0;
				$scope.stories = [];
				$scope.unreadStories = {};
				var today = new Date().toDateString();
				for(var p in $scope.feeds) {
					$scope.numfeeds++;
					var f = $scope.feeds[p];
					if (!f.Stories)
						continue;
					for(var i = 0; i < f.Stories.length; i++) {
						f.Stories[i].feed = f.Feed;
						var d = new Date(f.Stories[i].Date * 1000);
						if (d.toDateString() == today) {
							f.Stories[i].dispdate = d.format("shortTime");
						} else {
							f.Stories[i].dispdate = d.format("mediumDate");
						}
						f.Stories[i].read = false;
						f.Stories[i].guid = f.Feed.Url + '|' + f.Stories[i].Id;
						if (!f.Stories[i].Title) {
							f.Stories[i].Title = '(title unknown)';
						}
						$scope.stories.push(f.Stories[i]);
						$scope.unreadStories[f.Stories[i].Id] = true;
					}
					$scope.stories.sort(function(a, b) {
						return b.Date - a.Date;
					});
				}
				if (typeof cb === 'function') cb();
				$scope.loaded();
				var ur = $scope.unread();
				$scope.updateTitle();
			})
			.error(function() {
				if (typeof cb === 'function') cb();
				$scope.loaded();
			});
	};

	$scope.updateTitle = function() {
		var ur = $scope.unread();
		document.title = 'Go Read' + (ur != 0 ? ' (' + ur + ')' : '');
	};

	$scope.setCurrent = function(i) {
		if (i == $scope.currentStory) {
			delete $scope.currentStory;
			return;
		}
		var story = $scope.stories[i];
		$scope.getContents(story);
		if (i > 0) {
			$scope.getContents($scope.stories[i - 1]);
		}
		if (i < $scope.stories.length - 2) {
			$scope.getContents($scope.stories[i + 1]);
		}
		$('#story' + $scope.currentStory).empty();
		$scope.currentStory = i;
		$scope.markRead(story);
		$('#story' + i).html($scope.contents[story.guid] || '');
		setTimeout(function() {
			se = $('#storydiv' + i);
			var docTop = $(window).scrollTop() + 40;
			var docBottom = docTop + $(window).height() + 40;
			var eTop = se.offset().top;
			if (docTop > eTop || docBottom < eTop) {
				window.scrollTo(0, eTop - 40);
			}
		});
	};
	$scope.prev = function() {
		if ($scope.currentStory > 0) {
			$scope.setCurrent($scope.currentStory - 1);
		}
	};
	$scope.next = function() {
		if ($scope.stories && typeof $scope.currentStory === 'undefined') {
			$scope.setCurrent(0);
		} else if ($scope.stories && $scope.currentStory < $scope.stories.length - 1) {
			$scope.setCurrent($scope.currentStory + 1);
		}
	};

	$scope.unread = function() {
		return countProperties($scope.unreadStories);
	};

	$scope.markRead = function(s) {
		if ($scope.unreadStories[s.Id]) {
			delete $scope.unreadStories[s.Id];
			s.read = true;
			$scope.http('POST', $('#mark-all-read').attr('data-url-read'), {
				feed: s.feed.Url,
				story: s.Id
			});
			$scope.updateTitle();
		}
	};

	$scope.markAllRead = function(s) {
		$scope.unreadStories = {};
		$scope.stories = [];
		$http.post($('#mark-all-read').attr('data-url'));
		$scope.updateTitle();
	};

	$scope.nothing = function() {
		return $scope.loading == 0 && $scope.stories && !$scope.numfeeds && $scope.shown != 'about';
	};

	$scope.toggleNav = function() {
		$scope.nav = !$scope.nav;
	}
	$scope.navspan = function() {
		return 'span' + ($scope.nav ? '10' : '12');
	};
	$scope.navmargin = function() {
		return $scope.nav ? {} : {'margin-left': '0'};
	};

	$scope.overContents = function(s) {
		if (typeof $scope.contents[s.guid] !== 'undefined') {
			return;
		}
		s.getTimeout = $timeout(function() {
			$scope.getContents(s);
		}, 250);
	};
	$scope.leaveContents = function(s) {
		if (s.getTimeout) {
			$timeout.cancel(s.getTimeout);
		}
	};

	$scope.toFetch = [];
	$scope.getContents = function(s) {
		if (typeof $scope.contents[s.guid] !== 'undefined') {
			return;
		}
		$scope.toFetch.push(s);
		if (!$scope.fetchPromise) {
			$scope.fetchPromise = $timeout($scope.fetchContents);
		}
	};

	$scope.fetchContents = function() {
		delete $scope.fetchPromise;
		if ($scope.toFetch.length == 0) {
			return;
		}
		var tofetch = $scope.toFetch;
		$scope.toFetch = [];
		var data = [];
		for (var i = 0; i < tofetch.length; i++) {
			$scope.contents[tofetch[i].guid] = '';
			data.push({
				Feed: tofetch[i].feed.Url,
				Story: tofetch[i].Id
			});
		}
		$http.post($('#mark-all-read').attr('data-url-contents'), data)
			.success(function(data) {
				var current = '';
				if ($scope.stories[$scope.currentStory]) {
					current = $scope.stories[$scope.currentStory].guid;
				}
				for (var i = 0; i < data.length; i++) {
					$scope.contents[tofetch[i].guid] = data[i];
					if (current == tofetch[i].guid) {
						$('#story' + $scope.currentStory).html(data[i]);
					}
				}
			});
	};

	var shortcuts = $('#shortcuts');
	Mousetrap.bind('?', function() {
		shortcuts.modal('toggle');
	});
	Mousetrap.bind('esc', function() {
		shortcuts.modal('hide');
	});
	Mousetrap.bind('r', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply($scope.refresh());
	});
	Mousetrap.bind(['j', 'n'], function() {
		$scope.$apply('next()');
	});
	Mousetrap.bind(['k', 'p'], function() {
		$scope.$apply('prev()');
	});
	Mousetrap.bind('v', function() {
		if ($scope.stories[$scope.currentStory]) {
			window.open($scope.stories[$scope.currentStory].Link);
		}
	});
	Mousetrap.bind('shift+a', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply($scope.markAllRead());
	});
	Mousetrap.bind('a', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply("shown = 'add-subscription'");

		// need to wait for the keypress to finish before focusing
		setTimeout(function() {
			$('#add-subscription-form input').focus();
		}, 0);
	});
	Mousetrap.bind('g a', function() {
		if ($scope.nouser) {
			return;
		}
		$scope.$apply("shown = 'feeds'");
	});
	Mousetrap.bind('u', function() {
		$scope.$apply("toggleNav()");
	});

	$scope.showMessage = function(m) {
		$('#message-list').text(m);
		$('#messages').modal('show');
	};
}
<|endoftext|>


<commit_before>/** @jsx React.DOM */

var PlayerCell = React.createClass({
	render: function() {
		var img = (

);
		var playerLink = (
--
);
		if (this.props.player !== null) {
			img = ()
			var hrefTarget = window.location.pathname;
			if (hrefTarget.indexOf("/", hrefTarget.length - 1) === -1){
				hrefTarget += "/";
			}
			hrefTarget += "players/" + this.props.player.Name;
			playerLink = ({this.props.player? this.props.player.Name : "--"});
		}
		return (

				{playerLink}
				{img}

		);
	}
});

var GameRow = React.createClass({
	handleChange: function(event) {
		$.ajax({url:"/admin/api/weeks/",
			type: 'POST',
			data: {
				SeasonId: decodeURIComponent(window.seasonStore.seasonId),
				Data: event.target.value
			},
			dataType: 'json',
			success: function(data) {
				this.loadSeason(data);
			}.bind(this),
			error: function(xhr, status, err) {
    			console.error(this.props.url, status, err.toString());
  			}.bind(this)
		});

	},
	render: function() {
		var winnerRow = (<PlayerCell player={this.props.winner} admin={this.props.admin} />);
		var player1Selected = this.props.winner? this.props.player1.Name === this.props.winner.Name : false;
		var player2Selected = this.props.winner? this.props.player2.Name === this.props.winner.Name : false;
		if (this.props.admin && this.props.admin !== "false") {
			var baseString = this.props.week + ":" + this.props.player1.Name + ":" + this.props.player2.Name + ":";
			winnerRow = (

					<select name="" onChange={this.handleChange}>
						<option value={baseString} selected={!player1Selected && player2Selected}>-</option>
						<option value={baseString + this.props.player1.Name} selected={player1Selected}>{this.props.player1.Name}</option>
						<option value={baseString + this.props.player2.Name} selected={player2Selected}>{this.props.player2.Name}</option>
					</select>

			);
		}
		return (

				<PlayerCell player={this.props.player1} admin={this.props.admin} />
				<PlayerCell player={this.props.player2} admin={this.props.admin} />
				{winnerRow}

		);
	}
});

var WeekGroup = React.createClass({
	render: function() {
		var rows = [];
		var admin = this.props.admin;
		var number = this.props.week.Number;
		this.props.week.Games.forEach(function(game) {
			rows.push(
					<GameRow player1={game.Players[0]}
							   player2={game.Players[1]}
							   hasWinner={game.Winner !== null}
							   winner={game.Winner}
							   week={number}
							   admin={admin} />
			);
		});
		return (

{"Week " + number}

{"Scenario Numbers: (" + this.props.week.Scenarios[0] + ", " + this.props.week.Scenarios[1] + ")"}

Player 1

Player 2

Winner

					{rows}

		);
	}
});

var SeasonScheduleTable = React.createClass({
	mixins: [Reflux.ListenerMixin],
	onStatusChange: function(data) {
		this.setState({
			season: data,
		});
	},
	getInitialState: function() {
		return {
			season: window.seasonStore.season,
		};
	},
	componentDidMount: function() {
		this.listenTo(window.seasonStore, this.onStatusChange);
  	},
	render: function() {
		if (this.state.season === null) {
			return (

);
		}
		var rows = [];
		var admin = this.props.admin;
		this.state.season.Weeks.forEach(function(week) {
			console.log("Adding a week")
			rows.push(<WeekGroup week={week} admin={admin} key={week.Number}/>)
		});
		return (

{this.state.season.Name + " (" + this.state.season.Year + ")"}

				{rows}

		);
	}
});
<commit_msg>Added in a new-window popout for the player information<commit_after>/** @jsx React.DOM */

var PlayerCell = React.createClass({
	render: function() {
		var img = (

);
		var playerLink = (
--
);
		if (this.props.player !== null) {
			img = ()
			var hrefTarget = window.location.pathname;
			if (hrefTarget.indexOf("/", hrefTarget.length - 1) === -1){
				hrefTarget += "/";
			}
			hrefTarget += "players/" + this.props.player.Name;
			playerLink = ({this.props.player? this.props.player.Name : "--"});
		}
		return (

				{playerLink}
				{img}

		);
	}
});

var GameRow = React.createClass({
	handleChange: function(event) {
		$.ajax({url:"/admin/api/weeks/",
			type: 'POST',
			data: {
				SeasonId: decodeURIComponent(window.seasonStore.seasonId),
				Data: event.target.value
			},
			dataType: 'json',
			success: function(data) {
				this.loadSeason(data);
			}.bind(this),
			error: function(xhr, status, err) {
    			console.error(this.props.url, status, err.toString());
  			}.bind(this)
		});

	},
	render: function() {
		var winnerRow = (<PlayerCell player={this.props.winner} admin={this.props.admin} />);
		var player1Selected = this.props.winner? this.props.player1.Name === this.props.winner.Name : false;
		var player2Selected = this.props.winner? this.props.player2.Name === this.props.winner.Name : false;
		if (this.props.admin && this.props.admin !== "false") {
			var baseString = this.props.week + ":" + this.props.player1.Name + ":" + this.props.player2.Name + ":";
			winnerRow = (

					<select name="" onChange={this.handleChange}>
						<option value={baseString} selected={!player1Selected && player2Selected}>-</option>
						<option value={baseString + this.props.player1.Name} selected={player1Selected}>{this.props.player1.Name}</option>
						<option value={baseString + this.props.player2.Name} selected={player2Selected}>{this.props.player2.Name}</option>
					</select>

			);
		}
		return (

				<PlayerCell player={this.props.player1} admin={this.props.admin} />
				<PlayerCell player={this.props.player2} admin={this.props.admin} />
				{winnerRow}

		);
	}
});

var WeekGroup = React.createClass({
	render: function() {
		var rows = [];
		var admin = this.props.admin;
		var number = this.props.week.Number;
		this.props.week.Games.forEach(function(game) {
			rows.push(
					<GameRow player1={game.Players[0]}
							   player2={game.Players[1]}
							   hasWinner={game.Winner !== null}
							   winner={game.Winner}
							   week={number}
							   admin={admin} />
			);
		});
		return (

{"Week " + number}

{"Scenario Numbers: (" + this.props.week.Scenarios[0] + ", " + this.props.week.Scenarios[1] + ")"}

Player 1

Player 2

Winner

					{rows}

		);
	}
});

var SeasonScheduleTable = React.createClass({
	mixins: [Reflux.ListenerMixin],
	onStatusChange: function(data) {
		this.setState({
			season: data,
		});
	},
	getInitialState: function() {
		return {
			season: window.seasonStore.season,
		};
	},
	componentDidMount: function() {
		this.listenTo(window.seasonStore, this.onStatusChange);
  	},
	render: function() {
		if (this.state.season === null) {
			return (

);
		}
		var rows = [];
		var admin = this.props.admin;
		this.state.season.Weeks.forEach(function(week) {
			console.log("Adding a week")
			rows.push(<WeekGroup week={week} admin={admin} key={week.Number}/>)
		});
		return (

{this.state.season.Name + " (" + this.state.season.Year + ")"}

				{rows}

		);
	}
});
<|endoftext|>


<commit_before>describe('ddTimepicker', function () {
    var $scope,
        $sniffer,
        $document,
        $timeout,
        $compile,
        element;

    beforeEach(function () {
        module('dd.ui.dd-timepicker');

        inject(function ($rootScope, _$compile_, _$timeout_, _$sniffer_, _$document_) {
            $scope = $rootScope.$new();
            $sniffer = _$sniffer_;
            $timeout = _$timeout_;
            $document = _$document_;
            $compile = _$compile_;
        });
    });

    describe('Change model value', function () {

        it('apply parsed user input for model', function () {
            element = buildDirective($scope);

            changeInputValue(element, '8a');

            expect($scope.time).toEqual('08:00');
        });

        it('clear model if invalid time', function () {
            element = buildDirective($scope);

            changeInputValue(element, 'kepalas');

            expect($scope.time).toBeNull();
        });

        it('do not change model if not blured', function () {

            $scope.time = '10:15';
            element = buildDirective($scope);

            changeInputValue(element, '8p');

            expect($scope.time).toBe($scope.time);
        });

        it('return model as Date if is-date-type="true"', function () {

            $scope.time = '10:15';
            element = $compile(angular.element('<input id="time1" is-date-type="true" class="form-control" dd-timepicker type="text" ng-model="time" />'))($scope);
            $scope.$digest();

            changeInputValue(element, '10:20');

            expect($scope.time instanceof Date).toBe(true);
            expect($scope.time.getHours()).toBe(10);
            expect($scope.time.getMinutes()).toBe(20);
        });
    });

    describe('Keyboard arrow up', function () {
        it('increase time by one minute', function () {
            $scope.time = '10:15';
            element = buildDirective($scope);

            triggerKeypress(element, 38);

            expect($scope.time).toBe('10:16');
        });
    });

    describe('Keyboard arrow down', function () {
        it('decrease time by one minute', function () {
            $scope.time = '10:15';
            element = buildDirective($scope);

            triggerKeypress(element, 40);

            expect($scope.time).toBe('10:14');
        });
    });

    describe('Keyboard enter', function () {
        it('create time from current datetime if input is empty', function () {

            $scope.time = '';
            element = buildDirective($scope);

            triggerKeypress(element, 13);

            expect($scope.time).toBeDefined();
        });

        it('dont create time if input not empty', function () {

            $scope.time = '10:10';
            element = buildDirective($scope);

            triggerKeypress(element, 13);

            expect($scope.time).toBe('10:10');
        });
    });

    describe('on-change callback', function () {
        it('is called when model is changed', function () {

            $scope.onChange = jasmine.createSpy('onChange');
            element = buildDirective($scope);

            changeInputValue(element, '8p');
            $timeout.flush();
            changeInputValue(element, '8p');
            $timeout.flush();

            expect($scope.onChange).toHaveBeenCalledTimes(2);
        });

        it('is called onces if model is changed to same value', function () {

            $scope.onChange = jasmine.createSpy('onChange');
            element = buildDirective($scope);

            changeInputValue(element, '10:00');
            $timeout.flush();
            changeInputValue(element, '10:00');

            expect($scope.onChange).toHaveBeenCalledTimes(1);
        });
    });

    function buildDirective(scope) {
        var element = $compile(angular.element('<input id="time1" on-change="onChange()" class="form-control" dd-timepicker type="text" ng-model="time" />'))($scope);
        $scope.$digest();
        return element;
    }

    function changeInputValue(el, value) {
        el.val(value);
        el.trigger($sniffer.hasEvent('input') ? 'input' : 'change');
        $scope.$digest();
        element.trigger('blur');
    }

    function triggerKeypress(el, keycode) {
        var e = angular.element.Event("keypress");
        e.which = keycode;
        el.trigger(e);
    }
});<commit_msg>feat(timepicker): added more tests<commit_after>describe('ddTimepicker', function () {
    var $scope,
        $sniffer,
        $document,
        $timeout,
        $compile,
        element;

    beforeEach(function () {
        module('dd.ui.dd-timepicker');

        inject(function ($rootScope, _$compile_, _$timeout_, _$sniffer_, _$document_) {
            $scope = $rootScope.$new();
            $sniffer = _$sniffer_;
            $timeout = _$timeout_;
            $document = _$document_;
            $compile = _$compile_;
        });
    });

    describe('Change model value', function () {

        it('apply parsed user input for model', function () {
            element = buildDirective($scope);

            changeInputValue(element, '8a');

            expect($scope.time).toEqual('08:00');
        });

        it('clear model if invalid time', function () {
            element = buildDirective($scope);

            changeInputValue(element, 'kepalas');

            expect($scope.time).toBeNull();
        });

        it('do not change model if not blured', function () {

            $scope.time = '10:15';
            element = buildDirective($scope);

            changeInputValue(element, '8p');

            expect($scope.time).toBe($scope.time);
        });

        it('return model as Date if is-date-type="true"', function () {

            $scope.time = '10:15';
            element = $compile(angular.element('<input id="time1" is-date-type="true" class="form-control" dd-timepicker type="text" ng-model="time" />'))($scope);
            $scope.$digest();

            changeInputValue(element, '10:20');

            expect($scope.time instanceof Date).toBe(true);
            expect($scope.time.getHours()).toBe(10);
            expect($scope.time.getMinutes()).toBe(20);
        });
    });

    describe('Keyboard arrow up', function () {
        it('increase time by one minute', function () {
            $scope.time = '10:15';
            element = buildDirective($scope);

            triggerKeypress(element, 38);

            expect($scope.time).toBe('10:16');
        });
    });

    describe('Keyboard arrow down', function () {
        it('decrease time by one minute', function () {
            $scope.time = '10:15';
            element = buildDirective($scope);

            triggerKeypress(element, 40);

            expect($scope.time).toBe('10:14');
        });
    });

    describe('Keyboard enter', function () {
        it('create time from current datetime if input is empty', function () {

            $scope.time = '';
            element = buildDirective($scope);

            triggerKeypress(element, 13);

            expect($scope.time).toBeDefined();
        });

        it('dont create time if input not empty', function () {

            $scope.time = '10:10';
            element = buildDirective($scope);

            triggerKeypress(element, 13);

            expect($scope.time).toBe('10:10');
        });
    });

    describe('on-change callback', function () {
        it('is called when model is changed', function () {

            $scope.onChange = jasmine.createSpy('onChange');
            element = buildDirective($scope);

            changeInputValue(element, '8p');
            $timeout.flush();
            changeInputValue(element, '8p');
            $timeout.flush();

            expect($scope.onChange).toHaveBeenCalledTimes(2);
        });

        it('is called onces if model is changed to same value', function () {

            $scope.onChange = jasmine.createSpy('onChange');
            element = buildDirective($scope);

            changeInputValue(element, '10:00');
            $timeout.flush();
            changeInputValue(element, '10:00');

            expect($scope.onChange).toHaveBeenCalledTimes(1);
        });

        it('is not called if model changed from code', function () {

            $scope.onChange = jasmine.createSpy('onChange');
            $scope.time = '10:15';
            element = buildDirective($scope);

            $scope.time = '10:10';
            $scope.$digest();

            expect($scope.onChange).not.toHaveBeenCalled();
        });
    });

    function buildDirective(scope) {
        var element = $compile(angular.element('<input id="time1" on-change="onChange()" class="form-control" dd-timepicker type="text" ng-model="time" />'))($scope);
        $scope.$digest();
        return element;
    }

    function changeInputValue(el, value) {
        el.val(value);
        el.trigger($sniffer.hasEvent('input') ? 'input' : 'change');
        $scope.$digest();
        element.trigger('blur');
    }

    function triggerKeypress(el, keycode) {
        var e = angular.element.Event("keypress");
        e.which = keycode;
        el.trigger(e);
    }
});<|endoftext|>


<commit_before>'use strict'

/*eslint-env node*/

const Hapi = require('hapi')
const fetch = require('isomorphic-fetch')
const logger = require('bucker').createLogger()
const Inert = require('inert')

const apiKey = process.env.SL_REAL

const server = new Hapi.Server()
server.connection({ port: 3000, host: 'localhost' })
server.register(Inert, () => {})

server.route({
  method: 'GET',
  path: '/{param*}',
  handler: {
    directory: {
      path: 'dist',
      listing: true
    }
  }
})

server.route({
  method: 'GET',
  path: '/stop/{stopId}',
  handler: function (request, reply) {
    const stopId = parseInt(request.params.stopId)
    if (isNaN(stopId)) {
      logger.error('Malformed stopId: ' + request.params.stopId)
      return reply('bad request').code(401)
    }

    logger.info('Requesting stop: ' + stopId)
    fetch('http://api.sl.se/api2/realtimedeparturesV4.json?key='+apiKey+'&timewindow=60&siteid=' + stopId)
        .then(response => response.json())
        .then(json => {
          if (json.StatusCode !== 0) {
            logger.error(json.Message)
          } else {
            logger.debug(json.ResponseData)
            const data = {departures: json.ResponseData.Metros.map((stop) => {
              return {line: stop.LineNumber, destination: stop.Destination, time: stop.DisplayTime, direction: stop.JourneyDirection}
            })}

            return reply(data).header('Access-Control-Allow-Origin', 'http://localhost:9000')
          }
        })
        .catch((error) => logger.error(error))
  }
})

server.start((err) => {

  if (err) {
    throw err
  }
  logger.info(`Server running at: ${server.info.uri}`)
})

<commit_msg>Remove server.js<commit_after><|endoftext|>


<commit_before>import childProcess from 'child_process'
import path from 'path'

export {default as arg} from 'arg'

export * from 'kleur/colors'

export const spawn = (strs, ...quoted) =>
  new Promise((resolve) => {
    const args = []

    for (let i = 0; i < strs.length; i++) {
      args.push(...strs[i].split(' '))

      if (quoted[i] != null) {
        args.push(quoted[i])
      }
    }

    const env = Object.assign({}, process.env, {
      PATH: `${process.env.PATH}${path.delimiter}${path.join(
        process.cwd(),
        'node_modules/.bin'
      )}`
    })

    const spawned = childProcess.spawn(args[0], args.slice(1), {
      stdio: 'inherit',
      cwd: process.cwd(),
      env
    })

    spawned.on('exit', () => {
      resolve()
    })
  })
<commit_msg>Change syntax<commit_after>import childProcess from 'child_process'
import path from 'path'

export {default as arg} from 'arg'

export * from 'kleur/colors'

export const spawn = (strs, ...quoted) =>
  new Promise((resolve) => {
    const args = []

    for (let i = 0; i < strs.length; i++) {
      args.push(...strs[i].split(' '))

      if (quoted[i] != null) {
        args.push(quoted[i])
      }
    }

    const env = {
      ...process.env,
      PATH: `${process.env.PATH}${path.delimiter}${path.join(
        process.cwd(),
        'node_modules/.bin'
      )}`
    }

    const spawned = childProcess.spawn(args[0], args.slice(1), {
      stdio: 'inherit',
      cwd: process.cwd(),
      env
    })

    spawned.on('exit', () => {
      resolve()
    })
  })
<|endoftext|>