<commit_before>(function () {
    "use strict";

    angular.module("services")
        .factory("Prototyper", Prototyper);

    Prototyper.$inject = [
        'userConstants',
        'DocumentLabels'
    ];

    function Prototyper(userConstants, DocumentLabels) {
        const service = {
            toUserModel: toUserModel,
            toUsersCollection: applyToAll(toUserModel),
            toGroupModel: toGroupModel,
            toGroupsCollection: applyToAll(toGroupModel),
            toDocumentModel: toDocumentModel,
            toDocumentsCollection: applyToAll(toDocumentModel),
            toCollaborationModel: toCollaborationModel,
            toCollaborationsCollection: applyToAll(toCollaborationModel),
            toMembershipModel: toMembershipModel,
            toMembershipsCollection: applyToAll(toMembershipModel),
            toAuthorshipModel: toAuthorshipModel,
            toAuthorshipsCollection: applyToAll(toAuthorshipModel),
            toInstituteModel: toInstituteModel,
            toInstitutesCollection: applyToAll(toInstituteModel),
            toTagLabelModel: toTagLabelModel,
            toTagLabelsCollection: applyToAll(toTagLabelModel),
        };
        const userPrototype = {
            getAliases: function () {
                var firstLetter = function (string) {
                    if (!string)
                        return "";
                    return string.charAt(0).toUpperCase();
                };
                var capitalize = function (string) {
                    if (!string)
                        return "";
                    return _.capitalize(string.toLowerCase());
//                return str.replace(/\w\S*/g, function (txt) {
//                    return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
//                });
                };
                var aliases = [];
                if (_.isEmpty(this.name) || _.isEmpty(this.surname))
                    return aliases;
                var first_name = capitalize(this.name);
                var last_name = capitalize(this.surname);
                var initial_first_name = firstLetter(first_name);
                aliases.push(first_name + " " + last_name);
                aliases.push(last_name + " " + first_name);
                aliases.push(last_name + " " + initial_first_name + ".");
                aliases.push(initial_first_name + ". " + last_name + "");
                aliases = _.uniq(aliases);
                return aliases;
            },
            getUcAliases: function () {
                var aliases = this.getAliases();
                var ucAliases = _.map(aliases, function (a) {
                    return a.toUpperCase();
                });
                return ucAliases;
            },
            getDisplayName: function () {
                var name = this.name ? this.name : "";
                var surname = this.surname ? this.surname : "";
                var fullName = _.trim(name + " " + surname);
                return fullName;
            },
            getType: function () {
                return 'user';
            },
            getCollaborationGroups: function () {
                return _.map(this.collaborations, 'group');
            },
            isAdmin: function () {
                return this.role === userConstants.role.ADMINISTRATOR;
            },
            admins: function (group) {
                var administeredGroupsId = _.map(this.administratedGroups, 'id');
                return _.includes(administeredGroupsId, group.id);
            },
            getExternalConnectors: function () {
                var connectors = [];
                var publicationsConnector = {name: 'Publications', enabled: true};
                var orcidConnector = {name: 'ORCID', enabled: !!this.orcidId};
                var scopusConnector = {name: 'Scopus', enabled: !!this.scopusId};
                connectors.push(publicationsConnector);
                connectors.push(orcidConnector);
                connectors.push(scopusConnector);
                return connectors;
            },
            getNewDocument: function (documentTypeObj) {
                var documentData = {
                    draftCreator: this.id,
                    draft: true,
                    type: documentTypeObj.key,
                    sourceType: documentTypeObj.defaultSource
                };
                return documentPrototype.create(documentData);
            },
            getProfileUrl: function () {
                return '/users/' + this.id;
            },
            getTagsByDocument: function (document) {
                if (!document.tagLabels || !document.userTags)
                    return [];

                var filteredUserTags = document.userTags.filter(function (ut) {
                    return ut.researchEntity === this.id;
                }.bind(this));

                return document.tagLabels.filter(function (tagLabel) {
                    return !!filteredUserTags.find(function (ut) {
                        return tagLabel.id === ut.tagLabel;
                    });
                });
            }

        };
        const groupPrototype = {
            getDisplayName: function () {
                return this.name;
            },
            getType: function () {
                return 'group';
            },
            getExternalConnectors: function () {
                var connectors = [];
                var publicationsConnector = {name: 'Publications', enabled: !!this.publicationsAcronym};
                var scopusConnector = {name: 'Scopus', enabled: !!this.scopusId};
                connectors.push(publicationsConnector);
                connectors.push(scopusConnector);
                return connectors;
            },
            getNewDocument: function (documentTypeObj) {
                var documentData = {
                    draftCreator: this.id,
                    draft: true,
                    type: documentTypeObj.key,
                    sourceType: documentTypeObj.defaultSource
                };
                return documentPrototype.create(documentData);
            },
            getProfileUrl: function () {
                return '/groups/' + this.id;
            },
            getTagsByDocument: function (document) {
                if (!document.groupTagLabels || !document.groupTags)
                    return [];

                var filteredGroupTags = document.groupTags.filter(function (gt) {
                    return gt.researchEntity === this.id;
                }.bind(this));

                return document.groupTagLabels.filter(function (tagLabel) {
                    return !!filteredGroupTags.find(function (gt) {
                        return tagLabel.id === gt.tagLabel;
                    });
                });
            }
        };

        const documentPrototype = {
            UNKNOWN_DOCUMENT: 0,
            USER_DOCUMENT: 1,
            GROUP_DOCUMENT: 2,
            labels: [],
            fields: [
                'authors',
                'title',
                'year',
                'itSource',
                'source',
                'issue',
                'volume',
                'pages',
                'articleNumber',
                'doi',
                'abstract',
                'type',
                'sourceType',
                'scopusId',
                'wosId',
                'authorships',
                'affiliations',
                'institutes'
            ],
            create: function (documentData) {
                var fields = _.union(['draft', 'draftCreator', 'draftGroupCreator'], documentPrototype.fields);
                var document = _.pick(documentData, fields);
                _.extend(document, documentPrototype);
                return document;
            },
            getAllCoauthors: function () {
                return this.authors;
            },
            getType: function () {
                if (!!this.owner)
                    return this.USER_DOCUMENT;
                else if (!!this.groupOwner)
                    return this.GROUP_DOCUMENT;
                else
                    return this.UNKNOWN_DOCUMENT;
            },
            getAuthors: function () {
                if (!this.authorsStr)
                    return [];

                return this.authorsStr.replace(/\s+et all\s*$/i, '').split(/,|\sand\s/).map(_.trim);
            },
            getUcAuthors: function () {
                var authors = this.getAuthors();
                var ucAuthors = _.map(authors, function (a) {
                    return a.toUpperCase();
                });
                return ucAuthors;
            },
            getDisplayName: function () {
                return this.getDisplayName();
            },
            getUserIndex: function (user) {
                var index = _.findIndex(this.getAuthors(), a => user.getAliases().includes(a));
                return index;
            },
            getNewGroupDocument: function (groupId) {
                return {
                    title: "",
                    authorsStr: "",
                    draftGroupCreator: groupId,
                    draft: true
                };
            },
            getNewDraftDocument: function (userId) {
                return {
                    title: "",
                    authorsStr: "",
                    draftCreator: userId,
                    draft: true
                };
            },
            getDraftCreator: function () {
                if (this.draftCreator)
                    return this.draftCreator;
                return this.draftGroupCreator;
            },
            copyDocument: function (document, creator) {
                var excludedFields = ['draft', 'draftCreator', 'draftGroupCreator'];

                var documentTypeObj = {
                    key: document.type,
                    defaultSource: document.sourceType
                };

                var newDoc = creator.getNewDocument(documentTypeObj);

                _.forEach(documentPrototype.fields, function (key) {
                    if (_.includes(excludedFields, key))
                        return;

                    newDoc[key] = document[key];
                });

                return newDoc;
            },
            addLabel: function (label) {
                if (!this.hasLabel(label))
                    this.labels.push(label);
            },
            removeLabel: function (label) {
                _.remove(this.labels, function (t) {
                    return t === label;
                });
            },
            hasLabel: function(label){
                return this.labels.includes(label);
            },
            isDiscarded: function () {
                return this.hasLabel(DocumentLabels.DISCARDED);
            },
            isUnverifying: function(){
                return this.hasLabel(DocumentLabels.UVERIFYING);
            },
            getInstituteIdentifier: function(instituteIndex) {
                const alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
                if (instituteIndex < alphabet.length)
                    return alphabet[instituteIndex];
                const getBaseLog = (y, x) => Math.log(y) / Math.log(x);
                const firstLetter = alphabet [Math.floor(getBaseLog(instituteIndex, alphabet.length))-1];
                const secondLetter = alphabet [instituteIndex % alphabet.length];
                return firstLetter + secondLetter;
            }

        };

        const membershipPrototype = {
            getDisplayName: function () {
                if (_.isFunction(this.user.getDisplayName))
                    return this.user.getDisplayName();
                else
                    return '';
            }
        };

        const collaborationPrototype = {
            getDisplayName: function () {
                if (_.isFunction(this.group.getDisplayName))
                    return this.group.getDisplayName();
                else
                    return '';
            }
        };

        const authorshipPrototype = {};

        const institutePrototype = {
            getDisplayName: function () {
                return this.name;
            }
        };

        const tagLabelPrototype = {
            getDisplayName: function () {
                return this.value;
            }
        };

        function initializeAffiliations(document) {
            _.forEach(document.authorships, a => {
                if (a.affiliations)
                    return;
                const authorships = _.filter(document.affiliations, {authorship: a.id});
                const institutes = _.filter(document.institutes, i => authorships.some(a => a.institute === i.id));
                a.affiliations = institutes;
            });
        }

        function applyToAll(fun) {
            return function (elems) {
                _.forEach(elems, fun);
                return elems;
            };
        }

        function toUserModel(user) {
            _.defaultsDeep(user, userPrototype);
            service.toDocumentsCollection(user.documents);
            service.toGroupsCollection(user.memberships);
            service.toGroupsCollection(user.administratedGroups);
            service.toCollaborationsCollection(user.collaborations);
            return user;
        }

        function toGroupModel(group) {
            _.defaultsDeep(group, groupPrototype);
            service.toUsersCollection(group.members);
            service.toUsersCollection(group.administrators);
            service.toDocumentsCollection(group.documents);
            return group;
        }

        function toDocumentModel(document) {
            initializeAffiliations(document);
            _.defaultsDeep(document, documentPrototype);
            service.toUsersCollection(document.authors);
            service.toTagLabelsCollection(document.tagLabels);
            service.toTagLabelsCollection(document.groupTagLabels);
            service.toAuthorshipsCollection(document.authorships);
            return document;
        }

        function toMembershipModel(membership) {
            _.defaultsDeep(membership, membershipPrototype);
            service.toUserModel(membership.user);
            return membership;
        }

        function toCollaborationModel(collaboration) {
            _.defaultsDeep(collaboration, collaborationPrototype);
            service.toGroupModel(collaboration.group);
            return collaboration;
        }

        function toAuthorshipModel(authorship) {
            _.defaultsDeep(authorship, authorshipPrototype);
            service.toInstitutesCollection(authorship.affiliations);
            return authorship;
        }

        function toInstituteModel(institute) {
            _.defaultsDeep(institute, institutePrototype);
            return institute;
        }

        function toTagLabelModel(tagLabel) {
            _.defaultsDeep(tagLabel, tagLabelPrototype);
            return tagLabel;
        }

        return service;
    }
})();
<commit_msg>Fixed bug. Document autosaving was not working the the authors string was modified.<commit_after>(function () {
    "use strict";

    angular.module("services")
        .factory("Prototyper", Prototyper);

    Prototyper.$inject = [
        'userConstants',
        'DocumentLabels'
    ];

    function Prototyper(userConstants, DocumentLabels) {
        const service = {
            toUserModel: toUserModel,
            toUsersCollection: applyToAll(toUserModel),
            toGroupModel: toGroupModel,
            toGroupsCollection: applyToAll(toGroupModel),
            toDocumentModel: toDocumentModel,
            toDocumentsCollection: applyToAll(toDocumentModel),
            toCollaborationModel: toCollaborationModel,
            toCollaborationsCollection: applyToAll(toCollaborationModel),
            toMembershipModel: toMembershipModel,
            toMembershipsCollection: applyToAll(toMembershipModel),
            toAuthorshipModel: toAuthorshipModel,
            toAuthorshipsCollection: applyToAll(toAuthorshipModel),
            toInstituteModel: toInstituteModel,
            toInstitutesCollection: applyToAll(toInstituteModel),
            toTagLabelModel: toTagLabelModel,
            toTagLabelsCollection: applyToAll(toTagLabelModel),
        };
        const userPrototype = {
            getAliases: function () {
                var firstLetter = function (string) {
                    if (!string)
                        return "";
                    return string.charAt(0).toUpperCase();
                };
                var capitalize = function (string) {
                    if (!string)
                        return "";
                    return _.capitalize(string.toLowerCase());
//                return str.replace(/\w\S*/g, function (txt) {
//                    return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
//                });
                };
                var aliases = [];
                if (_.isEmpty(this.name) || _.isEmpty(this.surname))
                    return aliases;
                var first_name = capitalize(this.name);
                var last_name = capitalize(this.surname);
                var initial_first_name = firstLetter(first_name);
                aliases.push(first_name + " " + last_name);
                aliases.push(last_name + " " + first_name);
                aliases.push(last_name + " " + initial_first_name + ".");
                aliases.push(initial_first_name + ". " + last_name + "");
                aliases = _.uniq(aliases);
                return aliases;
            },
            getUcAliases: function () {
                var aliases = this.getAliases();
                var ucAliases = _.map(aliases, function (a) {
                    return a.toUpperCase();
                });
                return ucAliases;
            },
            getDisplayName: function () {
                var name = this.name ? this.name : "";
                var surname = this.surname ? this.surname : "";
                var fullName = _.trim(name + " " + surname);
                return fullName;
            },
            getType: function () {
                return 'user';
            },
            getCollaborationGroups: function () {
                return _.map(this.collaborations, 'group');
            },
            isAdmin: function () {
                return this.role === userConstants.role.ADMINISTRATOR;
            },
            admins: function (group) {
                var administeredGroupsId = _.map(this.administratedGroups, 'id');
                return _.includes(administeredGroupsId, group.id);
            },
            getExternalConnectors: function () {
                var connectors = [];
                var publicationsConnector = {name: 'Publications', enabled: true};
                var orcidConnector = {name: 'ORCID', enabled: !!this.orcidId};
                var scopusConnector = {name: 'Scopus', enabled: !!this.scopusId};
                connectors.push(publicationsConnector);
                connectors.push(orcidConnector);
                connectors.push(scopusConnector);
                return connectors;
            },
            getNewDocument: function (documentTypeObj) {
                var documentData = {
                    draftCreator: this.id,
                    draft: true,
                    type: documentTypeObj.key,
                    sourceType: documentTypeObj.defaultSource
                };
                return documentPrototype.create(documentData);
            },
            getProfileUrl: function () {
                return '/users/' + this.id;
            },
            getTagsByDocument: function (document) {
                if (!document.tagLabels || !document.userTags)
                    return [];

                var filteredUserTags = document.userTags.filter(function (ut) {
                    return ut.researchEntity === this.id;
                }.bind(this));

                return document.tagLabels.filter(function (tagLabel) {
                    return !!filteredUserTags.find(function (ut) {
                        return tagLabel.id === ut.tagLabel;
                    });
                });
            }

        };
        const groupPrototype = {
            getDisplayName: function () {
                return this.name;
            },
            getType: function () {
                return 'group';
            },
            getExternalConnectors: function () {
                var connectors = [];
                var publicationsConnector = {name: 'Publications', enabled: !!this.publicationsAcronym};
                var scopusConnector = {name: 'Scopus', enabled: !!this.scopusId};
                connectors.push(publicationsConnector);
                connectors.push(scopusConnector);
                return connectors;
            },
            getNewDocument: function (documentTypeObj) {
                var documentData = {
                    draftCreator: this.id,
                    draft: true,
                    type: documentTypeObj.key,
                    sourceType: documentTypeObj.defaultSource
                };
                return documentPrototype.create(documentData);
            },
            getProfileUrl: function () {
                return '/groups/' + this.id;
            },
            getTagsByDocument: function (document) {
                if (!document.groupTagLabels || !document.groupTags)
                    return [];

                var filteredGroupTags = document.groupTags.filter(function (gt) {
                    return gt.researchEntity === this.id;
                }.bind(this));

                return document.groupTagLabels.filter(function (tagLabel) {
                    return !!filteredGroupTags.find(function (gt) {
                        return tagLabel.id === gt.tagLabel;
                    });
                });
            }
        };

        const documentPrototype = {
            UNKNOWN_DOCUMENT: 0,
            USER_DOCUMENT: 1,
            GROUP_DOCUMENT: 2,
            labels: [],
            fields: [
                'authorsStr',
                'title',
                'year',
                'itSource',
                'source',
                'issue',
                'volume',
                'pages',
                'articleNumber',
                'doi',
                'abstract',
                'type',
                'sourceType',
                'scopusId',
                'wosId',
                'authorships',
                'affiliations',
                'institutes'
            ],
            create: function (documentData) {
                var fields = _.union(['draft', 'draftCreator', 'draftGroupCreator'], documentPrototype.fields);
                var document = _.pick(documentData, fields);
                _.extend(document, documentPrototype);
                return document;
            },
            getAllCoauthors: function () {
                return this.authors;
            },
            getType: function () {
                if (!!this.owner)
                    return this.USER_DOCUMENT;
                else if (!!this.groupOwner)
                    return this.GROUP_DOCUMENT;
                else
                    return this.UNKNOWN_DOCUMENT;
            },
            getAuthors: function () {
                if (!this.authorsStr)
                    return [];

                return this.authorsStr.replace(/\s+et all\s*$/i, '').split(/,|\sand\s/).map(_.trim);
            },
            getUcAuthors: function () {
                var authors = this.getAuthors();
                var ucAuthors = _.map(authors, function (a) {
                    return a.toUpperCase();
                });
                return ucAuthors;
            },
            getDisplayName: function () {
                return this.getDisplayName();
            },
            getUserIndex: function (user) {
                var index = _.findIndex(this.getAuthors(), a => user.getAliases().includes(a));
                return index;
            },
            getNewGroupDocument: function (groupId) {
                return {
                    title: "",
                    authorsStr: "",
                    draftGroupCreator: groupId,
                    draft: true
                };
            },
            getNewDraftDocument: function (userId) {
                return {
                    title: "",
                    authorsStr: "",
                    draftCreator: userId,
                    draft: true
                };
            },
            getDraftCreator: function () {
                if (this.draftCreator)
                    return this.draftCreator;
                return this.draftGroupCreator;
            },
            copyDocument: function (document, creator) {
                var excludedFields = ['draft', 'draftCreator', 'draftGroupCreator'];

                var documentTypeObj = {
                    key: document.type,
                    defaultSource: document.sourceType
                };

                var newDoc = creator.getNewDocument(documentTypeObj);

                _.forEach(documentPrototype.fields, function (key) {
                    if (_.includes(excludedFields, key))
                        return;

                    newDoc[key] = document[key];
                });

                return newDoc;
            },
            addLabel: function (label) {
                if (!this.hasLabel(label))
                    this.labels.push(label);
            },
            removeLabel: function (label) {
                _.remove(this.labels, function (t) {
                    return t === label;
                });
            },
            hasLabel: function(label){
                return this.labels.includes(label);
            },
            isDiscarded: function () {
                return this.hasLabel(DocumentLabels.DISCARDED);
            },
            isUnverifying: function(){
                return this.hasLabel(DocumentLabels.UVERIFYING);
            },
            getInstituteIdentifier: function(instituteIndex) {
                const alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
                if (instituteIndex < alphabet.length)
                    return alphabet[instituteIndex];
                const getBaseLog = (y, x) => Math.log(y) / Math.log(x);
                const firstLetter = alphabet [Math.floor(getBaseLog(instituteIndex, alphabet.length))-1];
                const secondLetter = alphabet [instituteIndex % alphabet.length];
                return firstLetter + secondLetter;
            }

        };

        const membershipPrototype = {
            getDisplayName: function () {
                if (_.isFunction(this.user.getDisplayName))
                    return this.user.getDisplayName();
                else
                    return '';
            }
        };

        const collaborationPrototype = {
            getDisplayName: function () {
                if (_.isFunction(this.group.getDisplayName))
                    return this.group.getDisplayName();
                else
                    return '';
            }
        };

        const authorshipPrototype = {};

        const institutePrototype = {
            getDisplayName: function () {
                return this.name;
            }
        };

        const tagLabelPrototype = {
            getDisplayName: function () {
                return this.value;
            }
        };

        function initializeAffiliations(document) {
            _.forEach(document.authorships, a => {
                if (a.affiliations)
                    return;
                const authorships = _.filter(document.affiliations, {authorship: a.id});
                const institutes = _.filter(document.institutes, i => authorships.some(a => a.institute === i.id));
                a.affiliations = institutes;
            });
        }

        function applyToAll(fun) {
            return function (elems) {
                _.forEach(elems, fun);
                return elems;
            };
        }

        function toUserModel(user) {
            _.defaultsDeep(user, userPrototype);
            service.toDocumentsCollection(user.documents);
            service.toGroupsCollection(user.memberships);
            service.toGroupsCollection(user.administratedGroups);
            service.toCollaborationsCollection(user.collaborations);
            return user;
        }

        function toGroupModel(group) {
            _.defaultsDeep(group, groupPrototype);
            service.toUsersCollection(group.members);
            service.toUsersCollection(group.administrators);
            service.toDocumentsCollection(group.documents);
            return group;
        }

        function toDocumentModel(document) {
            initializeAffiliations(document);
            _.defaultsDeep(document, documentPrototype);
            service.toUsersCollection(document.authors);
            service.toTagLabelsCollection(document.tagLabels);
            service.toTagLabelsCollection(document.groupTagLabels);
            service.toAuthorshipsCollection(document.authorships);
            return document;
        }

        function toMembershipModel(membership) {
            _.defaultsDeep(membership, membershipPrototype);
            service.toUserModel(membership.user);
            return membership;
        }

        function toCollaborationModel(collaboration) {
            _.defaultsDeep(collaboration, collaborationPrototype);
            service.toGroupModel(collaboration.group);
            return collaboration;
        }

        function toAuthorshipModel(authorship) {
            _.defaultsDeep(authorship, authorshipPrototype);
            service.toInstitutesCollection(authorship.affiliations);
            return authorship;
        }

        function toInstituteModel(institute) {
            _.defaultsDeep(institute, institutePrototype);
            return institute;
        }

        function toTagLabelModel(tagLabel) {
            _.defaultsDeep(tagLabel, tagLabelPrototype);
            return tagLabel;
        }

        return service;
    }
})();
<|endoftext|>


<commit_before>var bootStrapDrawables = (function (widthHalf, heightHalf, calcScreenConst, getTopRaster) {
    "use strict";

    function initBackGround(stage) {
        return stage.drawFresh(widthHalf, heightHalf, 'scene', 0);
    }

    var FONT_FACE = 'Arial';
    var FONT_COLOR = '#c4c4c4';

    function fontSize(width, height) {
        return calcScreenConst(height, 15);
    }

    function initTimeLeft(stage, backGroundDrawable) {
        function getTimeX() {
            return backGroundDrawable.getCornerX() + calcScreenConst(backGroundDrawable.getWidth(), 3);
        }
        return stage.drawText(getTimeX, getTopRaster, "2:00:00", fontSize, FONT_FACE, FONT_COLOR, 3,
            [backGroundDrawable]);
    }

    function initPeopleLeft(stage, backGroundDrawable) {
        function getPeopleLeftX() {
            return backGroundDrawable.getCornerX() + calcScreenConst(backGroundDrawable.getWidth(), 3, 2);
        }
        return stage.drawText(getPeopleLeftX, getTopRaster, "10 left", fontSize, FONT_FACE, FONT_COLOR, 3,
            [backGroundDrawable]);
    }

    function initFireFighter(stage, backGroundDrawable) {
        function fireFighterA_X(width) {
            return backGroundDrawable.getCornerX() < calcScreenConst(width, 20) ?
                calcScreenConst(width, 20) : backGroundDrawable.getCornerX();
        }
        function fireFighter_Y(height) {
            return calcScreenConst(height, 20, 19);
        }
        function fireFighterB_X(width) {
            return backGroundDrawable.getEndX() < calcScreenConst(width, 20, 19) ?
                backGroundDrawable.getEndX() : calcScreenConst(width, 20, 19);
        }
        function fireFighterSpeed(width) {
            var distance = fireFighterB_X(width) - fireFighterA_X(width);
            return calcScreenConst(distance, 3);
        }
//        function fromAtoB() {
//            stage.remove(fireFighterWrapper.drawable);
//            initFireFighter();
//        }
//        function fromBtoA() {
//            stage.remove(fireFighterWrapper.drawable);
//            var newWrapper = stage.moveFresh(fireFighterB_X, fireFighter_Y, 'firefighter', fireFighterA_X,
//                fireFighter_Y, fireFighterSpeed, Transition.EASE_IN_OUT_SIN, false, fromAtoB, [backGroundDrawable]);
//
//            fireFighterWrapper.drawable = newWrapper.drawable;
//            fireFighterWrapper.path = newWrapper.path;
//        }
//        function initFireFighter() {
//            var newWrapper = stage.moveFresh(fireFighterA_X, fireFighter_Y, 'firefighter', fireFighterB_X,
//                fireFighter_Y, fireFighterSpeed, Transition.EASE_IN_OUT_SIN, false, fromBtoA, [backGroundDrawable]);
//
//            fireFighterWrapper.drawable = newWrapper.drawable;
//            fireFighterWrapper.path = newWrapper.path;
//        }

//        var fireFighterWrapper = {};
//        initFireFighter();
//        return fireFighterWrapper;
        var a = stage.moveFreshRoundTrip(fireFighterA_X, fireFighter_Y, 'firefighter', fireFighterB_X,
            fireFighter_Y, fireFighterSpeed, Transition.EASE_IN_OUT_SIN, true, undefined, undefined, [backGroundDrawable]);
        return a;
    }

    function bootStrapDrawables(stage) {
        var backGroundDrawable = initBackGround(stage);

        return {
            backGround: backGroundDrawable,
            timeLeft: initTimeLeft(stage, backGroundDrawable),
            peopleLeft: initPeopleLeft(stage, backGroundDrawable),
            fireFighter: initFireFighter(stage, backGroundDrawable)
        };
    }

    return bootStrapDrawables;
})(widthHalf, heightHalf, calcScreenConst, getTopRaster);<commit_msg>removed legacy code<commit_after>var bootStrapDrawables = (function (widthHalf, heightHalf, calcScreenConst, getTopRaster) {
    "use strict";

    function initBackGround(stage) {
        return stage.drawFresh(widthHalf, heightHalf, 'scene', 0);
    }

    var FONT_FACE = 'Arial';
    var FONT_COLOR = '#c4c4c4';

    function fontSize(width, height) {
        return calcScreenConst(height, 15);
    }

    function initTimeLeft(stage, backGroundDrawable) {
        function getTimeX() {
            return backGroundDrawable.getCornerX() + calcScreenConst(backGroundDrawable.getWidth(), 3);
        }
        return stage.drawText(getTimeX, getTopRaster, "2:00:00", fontSize, FONT_FACE, FONT_COLOR, 3,
            [backGroundDrawable]);
    }

    function initPeopleLeft(stage, backGroundDrawable) {
        function getPeopleLeftX() {
            return backGroundDrawable.getCornerX() + calcScreenConst(backGroundDrawable.getWidth(), 3, 2);
        }
        return stage.drawText(getPeopleLeftX, getTopRaster, "10 left", fontSize, FONT_FACE, FONT_COLOR, 3,
            [backGroundDrawable]);
    }

    function initFireFighter(stage, backGroundDrawable) {
        function fireFighterA_X(width) {
            return backGroundDrawable.getCornerX() < calcScreenConst(width, 20) ?
                calcScreenConst(width, 20) : backGroundDrawable.getCornerX();
        }
        function fireFighter_Y(height) {
            return calcScreenConst(height, 20, 19);
        }
        function fireFighterB_X(width) {
            return backGroundDrawable.getEndX() < calcScreenConst(width, 20, 19) ?
                backGroundDrawable.getEndX() : calcScreenConst(width, 20, 19);
        }
        function fireFighterSpeed(width) {
            var distance = fireFighterB_X(width) - fireFighterA_X(width);
            return calcScreenConst(distance, 3);
        }

        return stage.moveFreshRoundTrip(fireFighterA_X, fireFighter_Y, 'firefighter', fireFighterB_X, fireFighter_Y,
            fireFighterSpeed, Transition.EASE_IN_OUT_SIN, true, undefined, undefined, [backGroundDrawable]);
    }

    function bootStrapDrawables(stage) {
        var backGroundDrawable = initBackGround(stage);

        return {
            backGround: backGroundDrawable,
            timeLeft: initTimeLeft(stage, backGroundDrawable),
            peopleLeft: initPeopleLeft(stage, backGroundDrawable),
            fireFighter: initFireFighter(stage, backGroundDrawable)
        };
    }

    return bootStrapDrawables;
})(widthHalf, heightHalf, calcScreenConst, getTopRaster);<|endoftext|>


<commit_before>/**
 * Thank with Google module initialization.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import { SetupMain } from './components/setup';
import { SettingsEdit, SettingsView } from './components/settings';
import ThankWithGoogleIcon from '../../../svg/graphics/thank-with-google.svg';
import { MODULES_THANK_WITH_GOOGLE } from './datastore/constants';
import { isFeatureEnabled } from '../../features';

export { registerStore } from './datastore';

const ifSwgIsEnabled = ( func ) => ( ...args ) => {
	if ( isFeatureEnabled( 'swgModule' ) ) {
		func( ...args );
	}
};

export const registerModule = ifSwgIsEnabled( ( modules ) => {
	modules.registerModule( 'thank-with-google', {
		storeName: MODULES_THANK_WITH_GOOGLE,
		SettingsEditComponent: SettingsEdit,
		SettingsViewComponent: SettingsView,
		SetupComponent: SetupMain,
		// TODO: Replace with another icon later.
		Icon: ThankWithGoogleIcon,
	} );
} );
<commit_msg>Rename feature flag to twgModule.<commit_after>/**
 * Thank with Google module initialization.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import { SetupMain } from './components/setup';
import { SettingsEdit, SettingsView } from './components/settings';
import ThankWithGoogleIcon from '../../../svg/graphics/thank-with-google.svg';
import { MODULES_THANK_WITH_GOOGLE } from './datastore/constants';
import { isFeatureEnabled } from '../../features';

export { registerStore } from './datastore';

const ifTwgIsEnabled = ( func ) => ( ...args ) => {
	if ( isFeatureEnabled( 'twgModule' ) ) {
		func( ...args );
	}
};

export const registerModule = ifTwgIsEnabled( ( modules ) => {
	modules.registerModule( 'thank-with-google', {
		storeName: MODULES_THANK_WITH_GOOGLE,
		SettingsEditComponent: SettingsEdit,
		SettingsViewComponent: SettingsView,
		SetupComponent: SetupMain,
		// TODO: Replace with another icon later.
		Icon: ThankWithGoogleIcon,
	} );
} );
<|endoftext|>


<commit_before>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import { forEach } from 'lodash';
/**
 * WordPress dependencies
 */
import { useCallback } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import { addQueryArgs } from '@wordpress/url';

/**
 * Internal dependencies
 */
import { DATA_VERSION } from '../../migration';
import { useConfig } from '../';
import Context from './context';

function APIProvider( { children } ) {
	const { api: { stories, media, fonts, users, statuses } } = useConfig();

	const getStoryById = useCallback(
		( storyId ) => {
			const path = addQueryArgs( `${ stories }/${ storyId }`, { context: `edit` } );
			return apiFetch( { path } );
		},
		[ stories ],
	);

	const saveStoryById = useCallback(
		/**
		 * Fire REST API call to save story.
		 *
		 * @param {Object} 	 story - A story object.
		 * @param {number}   story.storyId Story post id.
		 * @param {string}   story.title Story title.
		 * @param {string}   story.status Post status, draft or published.
		 * @param {Array}    story.pages Array of all pages.
		 * @param {number}   story.author User ID of story author.
		 * @param {string}   story.slug   The slug of the story.
		 * @param {string}   story.date   The publish date of the story.
		 * @param {string}   story.modified   The modified date of the story.
		 * @param {string}   story.content AMP HTML content.
		 * @param {string}   story.excerpt Short description.
		 * @param {number}   story.featuredMedia Featured image id.
		 * @param {string}   story.password Password
		 * @return {Promise} Return apiFetch promise.
		 */
		( { storyId, title, status, pages, author, slug, date, modified, content, excerpt, featuredMedia, password } ) => {
			return apiFetch( {
				path: `${ stories }/${ storyId }`,
				data: {
					title,
					status,
					author,
					password,
					slug,
					date,
					modified,
					content,
					excerpt,
					story_data: { version: DATA_VERSION, pages },
					featured_media: featuredMedia,
				},
				method: 'POST',
			} );
		},
		[ stories ],
	);

	const deleteStoryById = useCallback(
		/**
		 * Fire REST API call to delete story.
		 *
		 * @param {number}   storyId Story post id.
		 * @return {Promise} Return apiFetch promise.
		 */
		( storyId ) => {
			return apiFetch( {
				path: `${ stories }/${ storyId }`,
				method: 'DELETE',
			} );
		},
		[ stories ],
	);

	const getMedia = useCallback(
		( { mediaType, searchTerm } ) => {
			let apiPath = media;
			const perPage = 100;
			apiPath = addQueryArgs( apiPath, { per_page: perPage } );

			if ( mediaType ) {
				apiPath = addQueryArgs( apiPath, { media_type: mediaType } );
			}

			if ( searchTerm ) {
				apiPath = addQueryArgs( apiPath, { search: searchTerm } );
			}

			return apiFetch( { path: apiPath } )
				.then( ( data ) => data.map(
					( {
						id,
						guid: { rendered: src },
						media_details: { width: oWidth, height: oHeight },
						mime_type: mimeType,
						featured_media: posterId,
						featured_media_src: poster,
					} ) => ( {
						id,
						posterId,
						poster,
						src,
						oWidth,
						oHeight,
						mimeType,
					} ),
				) );
		},	[ media ],
	);

	/**
	 * Upload file to via REST API.
	 *
	 * @param {File}    file           Media File to Save.
	 * @param {?Object} additionalData Additional data to include in the request.
	 *
	 * @return {Promise} Media Object Promise.
	 */
	const uploadMedia = useCallback(
		( file, additionalData ) => {
			// Create upload payload
			const data = new window.FormData();
			data.append( 'file', file, file.name || file.type.replace( '/', '.' ) );
			forEach( additionalData, ( value, key ) => data.append( key, value ) );
			return apiFetch( {
				path: media,
				body: data,
				method: 'POST',
			} );
		}, [ media ],
	);

	/**
	 * Update Existing media.
	 *
	 * @param  {number} mediaId
	 * @param  {Object} data Object of properties to update on attachment.
	 * @return {Promise} Media Object Promise.
	 */
	const saveMedia = useCallback(
		( mediaId, data ) => {
			return apiFetch( {
				path: `${ media }/${ mediaId }`,
				data,
				method: 'POST',
			} );
		},
		[ media ],
	);

	const getAllFonts = useCallback(
		( {} ) => {
			return apiFetch( { path: fonts } )
				.then( ( data ) => data.map(
					( font ) => ( {
						value: font.name,
						...font,
					} ),
				) );
		}, [ fonts ],
	);

	const getAllStatuses = useCallback(
		() => {
			const path = addQueryArgs( statuses, { context: `edit` } );
			return apiFetch( { path } );
		}, [ statuses ],
	);

	const getAllUsers = useCallback(
		() => {
			return apiFetch( { path: users } );
		}, [ users ],
	);

	const state = {
		actions: {
			getStoryById,
			getMedia,
			saveStoryById,
			deleteStoryById,
			getAllFonts,
			getAllStatuses,
			getAllUsers,
			uploadMedia,
			saveMedia,
		},
	};

	return (
		<Context.Provider value={ state }>
			{ children }
		</Context.Provider>
	);
}

APIProvider.propTypes = {
	children: PropTypes.oneOfType( [
		PropTypes.arrayOf( PropTypes.node ),
		PropTypes.node,
	] ).isRequired,
};

export default APIProvider;
<commit_msg>Remove use of lodash.<commit_after>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
/**
 * WordPress dependencies
 */
import { useCallback } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import { addQueryArgs } from '@wordpress/url';

/**
 * Internal dependencies
 */
import { DATA_VERSION } from '../../migration';
import { useConfig } from '../';
import Context from './context';

function APIProvider( { children } ) {
	const { api: { stories, media, fonts, users, statuses } } = useConfig();

	const getStoryById = useCallback(
		( storyId ) => {
			const path = addQueryArgs( `${ stories }/${ storyId }`, { context: `edit` } );
			return apiFetch( { path } );
		},
		[ stories ],
	);

	const saveStoryById = useCallback(
		/**
		 * Fire REST API call to save story.
		 *
		 * @param {Object} 	 story - A story object.
		 * @param {number}   story.storyId Story post id.
		 * @param {string}   story.title Story title.
		 * @param {string}   story.status Post status, draft or published.
		 * @param {Array}    story.pages Array of all pages.
		 * @param {number}   story.author User ID of story author.
		 * @param {string}   story.slug   The slug of the story.
		 * @param {string}   story.date   The publish date of the story.
		 * @param {string}   story.modified   The modified date of the story.
		 * @param {string}   story.content AMP HTML content.
		 * @param {string}   story.excerpt Short description.
		 * @param {number}   story.featuredMedia Featured image id.
		 * @param {string}   story.password Password
		 * @return {Promise} Return apiFetch promise.
		 */
		( { storyId, title, status, pages, author, slug, date, modified, content, excerpt, featuredMedia, password } ) => {
			return apiFetch( {
				path: `${ stories }/${ storyId }`,
				data: {
					title,
					status,
					author,
					password,
					slug,
					date,
					modified,
					content,
					excerpt,
					story_data: { version: DATA_VERSION, pages },
					featured_media: featuredMedia,
				},
				method: 'POST',
			} );
		},
		[ stories ],
	);

	const deleteStoryById = useCallback(
		/**
		 * Fire REST API call to delete story.
		 *
		 * @param {number}   storyId Story post id.
		 * @return {Promise} Return apiFetch promise.
		 */
		( storyId ) => {
			return apiFetch( {
				path: `${ stories }/${ storyId }`,
				method: 'DELETE',
			} );
		},
		[ stories ],
	);

	const getMedia = useCallback(
		( { mediaType, searchTerm } ) => {
			let apiPath = media;
			const perPage = 100;
			apiPath = addQueryArgs( apiPath, { per_page: perPage } );

			if ( mediaType ) {
				apiPath = addQueryArgs( apiPath, { media_type: mediaType } );
			}

			if ( searchTerm ) {
				apiPath = addQueryArgs( apiPath, { search: searchTerm } );
			}

			return apiFetch( { path: apiPath } )
				.then( ( data ) => data.map(
					( {
						id,
						guid: { rendered: src },
						media_details: { width: oWidth, height: oHeight },
						mime_type: mimeType,
						featured_media: posterId,
						featured_media_src: poster,
					} ) => ( {
						id,
						posterId,
						poster,
						src,
						oWidth,
						oHeight,
						mimeType,
					} ),
				) );
		},	[ media ],
	);

	/**
	 * Upload file to via REST API.
	 *
	 * @param {File}    file           Media File to Save.
	 * @param {?Object} additionalData Additional data to include in the request.
	 *
	 * @return {Promise} Media Object Promise.
	 */
	const uploadMedia = useCallback(
		( file, additionalData ) => {
			// Create upload payload
			const data = new window.FormData();
			data.append( 'file', file, file.name || file.type.replace( '/', '.' ) );
			Object.entries( additionalData ).forEach( ( [ key, value ] ) => data.append( key, value ) );
			return apiFetch( {
				path: media,
				body: data,
				method: 'POST',
			} );
		}, [ media ],
	);

	/**
	 * Update Existing media.
	 *
	 * @param  {number} mediaId
	 * @param  {Object} data Object of properties to update on attachment.
	 * @return {Promise} Media Object Promise.
	 */
	const saveMedia = useCallback(
		( mediaId, data ) => {
			return apiFetch( {
				path: `${ media }/${ mediaId }`,
				data,
				method: 'POST',
			} );
		},
		[ media ],
	);

	const getAllFonts = useCallback(
		( {} ) => {
			return apiFetch( { path: fonts } )
				.then( ( data ) => data.map(
					( font ) => ( {
						value: font.name,
						...font,
					} ),
				) );
		}, [ fonts ],
	);

	const getAllStatuses = useCallback(
		() => {
			const path = addQueryArgs( statuses, { context: `edit` } );
			return apiFetch( { path } );
		}, [ statuses ],
	);

	const getAllUsers = useCallback(
		() => {
			return apiFetch( { path: users } );
		}, [ users ],
	);

	const state = {
		actions: {
			getStoryById,
			getMedia,
			saveStoryById,
			deleteStoryById,
			getAllFonts,
			getAllStatuses,
			getAllUsers,
			uploadMedia,
			saveMedia,
		},
	};

	return (
		<Context.Provider value={ state }>
			{ children }
		</Context.Provider>
	);
}

APIProvider.propTypes = {
	children: PropTypes.oneOfType( [
		PropTypes.arrayOf( PropTypes.node ),
		PropTypes.node,
	] ).isRequired,
};

export default APIProvider;
<|endoftext|>


<commit_before>/**
 * The campus popup view for the map module.
 *
 * @class A Backbone view to handle the campuses popup.
 * @author Lucien Bokouka
 * @type {Backbone.View}
 */
var CampusPopupView = Backbone.View.extend(
	/** @lends CampusPopupView */
	{

		/**
		 * @constructs
		 * @param campuses A list of campuses to display.
		 */
		initialize: function (options) {
			_.bindAll(this, "render");

			this.campuses = options.campuses;
			this.campusesMap = options.campusesMap;
		},

		/** Registers events */
		events: {
		  "click #campusesPopupList": "selectCampus",
		},

		selectCampus: function(evt) {
			// get the campus id from the parent
 (format "campus-X", where X is a number)
			var campusId = $(evt.target).parents("li").get(0).id.split("campus-")[1];
			$("#campus").val(campusId);
			$("#campus").selectmenu("refresh");
			$("#campus").trigger("change");
		},

		/**
		 * Render the campus popup view.
		 */
		render: function () {
			var self = this;
			// remove everything from the list except the first element (the header)
			$("#campusesPopupList").find("li:not(:first-child)").remove();
			$(this.campuses).each(function(i, campusName) { // append all campuses
				$("#campusesPopupList").append("
" + campusName + "
");
			});
			$("#campusesPopupList").listview("refresh"); // jQuery mobile-ify the added elements

			// close any other open popup (only one popup can be open at the same time.)
			$(document).find("[data-role='popup']:not([id='campusesPopup'])").popup("close").bind({
				// show the campuses popup after the other popups has closed
				popupafterclose: function(event, uit) {
          // --- HACK: for correct x and y position of popup ---
          var x = 150;
          // device is only defined when running on a physical device (not a web browser)
          if (typeof device != 'undefined') x = 50;

          var cellHeight = $("#campusesPopup li:nth-child(2)").height();
          var noOfCells = $("#campusesPopup li:not(:first-child)").length;
          var y = noOfCells * (cellHeight / 3) + 150;
          // --- END  HACK: for correct x and y position of popup ---

          $( "#campusesPopup" ).popup( "open", { x:x, y:y } );

					// unbind popupafterclose event after campuses popup open, to ensure it's
					// not accidentally opened after any other search.
					$(document).find("[data-role='popup']:not([id='campusesPopup'])").unbind("popupafterclose");
				}
			});
		}

	});
<commit_msg>Fixed violations.<commit_after>/**
 * The campus popup view for the map module.
 *
 * @class A Backbone view to handle the campuses popup.
 * @author Lucien Bokouka
 * @type {Backbone.View}
 */
var CampusPopupView = Backbone.View.extend(
    /** @lends CampusPopupView */
    {

      /**
       * @constructs
       * @param campuses A list of campuses to display.
       */
      initialize:function (options) {
        _.bindAll(this, "render");

        this.campuses = options.campuses;
        this.campusesMap = options.campusesMap;
      },

      /** Registers events */
      events:{
        "click #campusesPopupList":"selectCampus"
      },

      selectCampus:function (evt) {
        // get the campus id from the parent
 (format "campus-X", where X is a number)
        var campusId = $(evt.target).parents("li").get(0).id.split("campus-")[1];
        $("#campus").val(campusId);
        $("#campus").selectmenu("refresh");
        $("#campus").trigger("change");
      },

      /**
       * Render the campus popup view.
       */
      render:function () {
        var self = this;
        // remove everything from the list except the first element (the header)
        $("#campusesPopupList").find("li:not(:first-child)").remove();
        $(this.campuses).each(function (i, campusName) { // append all campuses
          $("#campusesPopupList").append("
" + campusName + "
");
        });
        $("#campusesPopupList").listview("refresh"); // jQuery mobile-ify the added elements

        // close any other open popup (only one popup can be open at the same time.)
        $(document).find("[data-role='popup']:not([id='campusesPopup'])").popup("close").bind({
          // show the campuses popup after the other popups has closed
          popupafterclose:function (event, uit) {
            // --- HACK: for correct x and y position of popup ---
            var x = 150;
            // device is only defined when running on a physical device (not a web browser)
            if (typeof device != 'undefined') {
              x = 50;
            }

            var cellHeight = $("#campusesPopup li:nth-child(2)").height();
            var noOfCells = $("#campusesPopup li:not(:first-child)").length;
            var y = noOfCells * (cellHeight / 3) + 150;
            // --- END  HACK: for correct x and y position of popup ---

            $("#campusesPopup").popup("open", { x:x, y:y });

            // unbind popupafterclose event after campuses popup open, to ensure it's
            // not accidentally opened after any other search.
            $(document).find("[data-role='popup']:not([id='campusesPopup'])").unbind("popupafterclose");
          }
        });
      }

    });
<|endoftext|>


<commit_before>(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(['underscore', 'jquery'], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory(require('underscore'), require('jquery'));
  } else {
    root.Torso = root.Torso || {};
    root.Torso.Utils = root.Torso.Utils || {};
    root.Torso.Utils.templateRenderer = factory(root._, (root.jQuery || root.Zepto || root.ender || root.$));
  }
}(this, function(_, $) {
  'use strict';

  /**
   * Changes DOM Nodes that are different, and leaves others untouched.
   *
   * Algorithm:
   * Delegates to a particular swapMethod, depending on the Node type.
   * Recurses for nested Element Nodes only.
   * There is always room for optimizing this method.
   *
   * @method hotswap
   * @param currentNode {Node} The DOM Node corresponding to the existing page content to update
   * @param newNode {Node} The detached DOM Node representing the desired DOM subtree
   * @param ignoreElements {Array} Array of jQuery selectors for DOM Elements to ignore during render. Can be an expensive check.
   */
  function hotswap(currentNode, newNode, ignoreElements) {
    var newNodeType = newNode.nodeType,
      currentNodeType = currentNode.nodeType,
      swapMethod;

    if(newNodeType !== currentNodeType) {
      $(currentNode).replaceWith(newNode);
    } else {
      swapMethod = swapMethods[newNodeType] || swapMethods['default'];
      swapMethod(currentNode, newNode, ignoreElements);
    }
  }

  /*
   * Swap method for Element Nodes
   * @param currentNode {Element} The pre-existing DOM Element to update
   * @param newNode {Element} The detached DOM Element representing the desired DOM Element subtree
   * @param ignoreElements {Array} Array of jQuery selectors for DOM Elements to ignore during render. Can be an expensive check.
   */
  function swapElementNodes(currentNode, newNode, ignoreElements) {
    var $currentNode = $(currentNode),
      $newNode = $(newNode),
      shouldIgnore,
      $currChildNodes,
      $newChildNodes,
      currentAttributes;

    shouldIgnore = _.some(ignoreElements, function(selector) {
      return $currentNode.is(selector);
    });

    if (shouldIgnore) {
      return;
    }

    // Handle tagname changes with full replacement
    if (newNode.tagName !== currentNode.tagName) {
      $currentNode.replaceWith(newNode);
      return;
    }

    // Remove current attributes
    currentAttributes = currentNode.attributes;
    while (currentAttributes.length > 0) {
      currentNode.removeAttribute(currentAttributes[0].name);
    }

    // Set new attributes
    _.each(newNode.attributes, function(attrib) {
      currentNode.setAttribute(attrib.name, attrib.value);
    });

    // Quick check to see if we need to bother comparing sub-levels
    if ($currentNode.html() === $newNode.html()) {
      return;
    }

    // Include all child nodes, including text and comment nodes
    $newChildNodes = $newNode.contents();
    $currChildNodes = $currentNode.contents();

    // If the DOM lists are different sizes, perform a hard refresh
    if ($newChildNodes.length !== $currChildNodes.length) {
      $currentNode.html($newNode.html());
      return;
    }

    // Perform a recursive hotswap for all children nodes
    $currChildNodes.each(function(index, currChildNode) {
      hotswap(currChildNode, $newChildNodes.get(index), ignoreElements);
    });
  }

  /*
   * Swap method for Text, Comment, and CDATA Section Nodes
   * @param currentNode {Node} The pre-existing DOM Node to update
   * @param newNode {Node} The detached DOM Node representing the desired DOM Node subtree
   */
  function updateIfNodeValueChanged(currentNode, newNode){
    var nodeValueChanged = newNode.nodeValue !== currentNode.nodeValue;
    if (nodeValueChanged) {
      $(currentNode).replaceWith(newNode);
    }
  }

  /*
   * Map of nodeType to hot swap implementations.
   * NodeTypes are hard-coded integers per the DOM Level 2 specification instead of
   * references to constants defined on the window.Node object for IE8 compatibility
   */
  var swapMethods = {
    1: swapElementNodes, // ELEMENT_NODE
    3: updateIfNodeValueChanged, // TEXT_NODE
    4: updateIfNodeValueChanged, // CDATA_SECTION_NODE
    8: updateIfNodeValueChanged, // COMMENT_NODE
    default: function(currentNode, newNode) {
      $(currentNode).replaceWith(newNode);
    }
  };

  /**
   * Static Template Engine.
   * All template renders should be piped through this method.
   *
   * @module    Torso
   * @namespace Torso.Utils
   * @class     templateRenderer
   * @static
   * @author    ariel.wexler@vecna.com
   */
  var templateRenderer = {
    /**
     * Performs efficient re-rendering of a template.
     * @method render
     * @param  $el {jQueryObject} The Element to render into
     * @param  template {Handlebars Template} The HBS template to apply
     * @param  context {Object} The context object to pass to the template
     * @param  [opts] {Object} Other options
     * @param  [opts.force=false] {Boolean} Will forcefully do a fresh render and not a diff-render
     * @param  [opts.ignoreElements] {Array} jQuery selectors of DOM elements to ignore during render. Can be an expensive check
     */
    render: function($el, template, context, opts) {
      var newDOM,
          newHTML = template(context),
          el = $el.get(0);
      opts = opts || {};

      if (opts.force) {
        $el.html(newHTML);
      } else {
        newDOM = this.copyTopElement(el);
        $(newDOM).html(newHTML);
        this.hotswapKeepCaret(el, newDOM, opts.ignoreElements);
      }
    },

    /**
     * Call this.hotswap but also keeps the caret position the same
     * @param currentNode {Node} The DOM Node corresponding to the existing page content to update
     * @param newNode {Node} The detached DOM Node representing the desired DOM subtree
     * @param ignoreElements {Array} Array of jQuery selectors for DOM Elements to ignore during render. Can be an expensive check.
     * @method hotswapKeepCaret
     */

    hotswapKeepCaret: function(currentNode, newNode, ignoreElements) {
      var currentCaret,
          activeElement = document.activeElement;
      if (activeElement && activeElement.hasAttribute('value')) {
        currentCaret = this.getCaretPosition(activeElement);
      }
      this.hotswap(currentNode, newNode, ignoreElements);
      if (activeElement) {
        this.setCaretPosition(activeElement, currentCaret);
      }
    },

    // See above function declaration for method-level documentation
    hotswap: hotswap,

    /**
     * Produces a copy of the element tag with attributes but with no contents
     * @param el {Element} the DOM element to be copied
     * @return {Element} a shallow copy of the element with no children but with attributes
     * @method copyTopElement
     */
    copyTopElement: function(el) {
      var newDOM = document.createElement(el.tagName);
      _.each(el.attributes, function(attrib) {
        newDOM.setAttribute(attrib.name, attrib.value);
      });
      return newDOM;
    },

    /**
     * Method that returns the current caret (cursor) position of a given element.
     * Source: http://stackoverflow.com/questions/2897155/get-cursor-position-in-characters-within-a-text-input-field
     * @method getCaretPosition
     * @param elem {element} the DOM element to check caret position
     * @return {Integer} the cursor index of the given element.
     */
    getCaretPosition: function(elem) {
      // range {IE selection object}
      // iCaretPos {Integer} will store the final caret position
      var range,
          iCaretPos = 0;
      // IE Support
      if (document.selection) {
        // Set focus on the element
        elem.focus();
        // To get cursor position, get empty selection range
        range = document.selection.createRange();
        // Move selection start to 0 position
        range.moveStart('character', -elem.value.length);
        // The caret position is selection length
        iCaretPos = range.text.length;
      } else if (elem.selectionStart || elem.selectionStart === 0) {
        // Firefox support
        iCaretPos = elem.selectionStart;
      }
      // Return results
      return iCaretPos;
    },

    /**
     * Method that returns sets the current caret (cursor) position of a given element and puts it in focus.
     * Source: http://stackoverflow.com/questions/512528/set-cursor-position-in-html-textbox
     * @method setCaretPosition
     * @param elem {element}
     * @param caretPos {Integer} The caret index to set
     * @return {Integer} the cursor index of the given element.
     */
    setCaretPosition: function(elem, caretPos) {
      var range;
      if (elem) {
        if (elem.createTextRange) {
          // IE support
          range = elem.createTextRange();
          range.move('character', caretPos);
          range.select();
        } else if (elem.selectionStart || elem.selectionStart === 0) {
          // Firefox support
          elem.focus();
          elem.setSelectionRange(caretPos, caretPos);
        } else {
          // At least focus the element if nothing else
          elem.focus();
        }
      }
    }
  };

  return templateRenderer;
}));
<commit_msg> #48 : Check if the input element supports selection<commit_after>(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(['underscore', 'jquery'], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory(require('underscore'), require('jquery'));
  } else {
    root.Torso = root.Torso || {};
    root.Torso.Utils = root.Torso.Utils || {};
    root.Torso.Utils.templateRenderer = factory(root._, (root.jQuery || root.Zepto || root.ender || root.$));
  }
}(this, function(_, $) {
  'use strict';

  /**
   * Changes DOM Nodes that are different, and leaves others untouched.
   *
   * Algorithm:
   * Delegates to a particular swapMethod, depending on the Node type.
   * Recurses for nested Element Nodes only.
   * There is always room for optimizing this method.
   *
   * @method hotswap
   * @param currentNode {Node} The DOM Node corresponding to the existing page content to update
   * @param newNode {Node} The detached DOM Node representing the desired DOM subtree
   * @param ignoreElements {Array} Array of jQuery selectors for DOM Elements to ignore during render. Can be an expensive check.
   */
  function hotswap(currentNode, newNode, ignoreElements) {
    var newNodeType = newNode.nodeType,
      currentNodeType = currentNode.nodeType,
      swapMethod;

    if(newNodeType !== currentNodeType) {
      $(currentNode).replaceWith(newNode);
    } else {
      swapMethod = swapMethods[newNodeType] || swapMethods['default'];
      swapMethod(currentNode, newNode, ignoreElements);
    }
  }

  /*
   * Swap method for Element Nodes
   * @param currentNode {Element} The pre-existing DOM Element to update
   * @param newNode {Element} The detached DOM Element representing the desired DOM Element subtree
   * @param ignoreElements {Array} Array of jQuery selectors for DOM Elements to ignore during render. Can be an expensive check.
   */
  function swapElementNodes(currentNode, newNode, ignoreElements) {
    var $currentNode = $(currentNode),
      $newNode = $(newNode),
      shouldIgnore,
      $currChildNodes,
      $newChildNodes,
      currentAttributes;

    shouldIgnore = _.some(ignoreElements, function(selector) {
      return $currentNode.is(selector);
    });

    if (shouldIgnore) {
      return;
    }

    // Handle tagname changes with full replacement
    if (newNode.tagName !== currentNode.tagName) {
      $currentNode.replaceWith(newNode);
      return;
    }

    // Remove current attributes
    currentAttributes = currentNode.attributes;
    while (currentAttributes.length > 0) {
      currentNode.removeAttribute(currentAttributes[0].name);
    }

    // Set new attributes
    _.each(newNode.attributes, function(attrib) {
      currentNode.setAttribute(attrib.name, attrib.value);
    });

    // Quick check to see if we need to bother comparing sub-levels
    if ($currentNode.html() === $newNode.html()) {
      return;
    }

    // Include all child nodes, including text and comment nodes
    $newChildNodes = $newNode.contents();
    $currChildNodes = $currentNode.contents();

    // If the DOM lists are different sizes, perform a hard refresh
    if ($newChildNodes.length !== $currChildNodes.length) {
      $currentNode.html($newNode.html());
      return;
    }

    // Perform a recursive hotswap for all children nodes
    $currChildNodes.each(function(index, currChildNode) {
      hotswap(currChildNode, $newChildNodes.get(index), ignoreElements);
    });
  }

  /*
   * Swap method for Text, Comment, and CDATA Section Nodes
   * @param currentNode {Node} The pre-existing DOM Node to update
   * @param newNode {Node} The detached DOM Node representing the desired DOM Node subtree
   */
  function updateIfNodeValueChanged(currentNode, newNode){
    var nodeValueChanged = newNode.nodeValue !== currentNode.nodeValue;
    if (nodeValueChanged) {
      $(currentNode).replaceWith(newNode);
    }
  }

  /*
   * Map of nodeType to hot swap implementations.
   * NodeTypes are hard-coded integers per the DOM Level 2 specification instead of
   * references to constants defined on the window.Node object for IE8 compatibility
   */
  var swapMethods = {
    1: swapElementNodes, // ELEMENT_NODE
    3: updateIfNodeValueChanged, // TEXT_NODE
    4: updateIfNodeValueChanged, // CDATA_SECTION_NODE
    8: updateIfNodeValueChanged, // COMMENT_NODE
    default: function(currentNode, newNode) {
      $(currentNode).replaceWith(newNode);
    }
  };

  /**
   * Static Template Engine.
   * All template renders should be piped through this method.
   *
   * @module    Torso
   * @namespace Torso.Utils
   * @class     templateRenderer
   * @static
   * @author    ariel.wexler@vecna.com
   */
  var templateRenderer = {
    /**
     * Performs efficient re-rendering of a template.
     * @method render
     * @param  $el {jQueryObject} The Element to render into
     * @param  template {Handlebars Template} The HBS template to apply
     * @param  context {Object} The context object to pass to the template
     * @param  [opts] {Object} Other options
     * @param  [opts.force=false] {Boolean} Will forcefully do a fresh render and not a diff-render
     * @param  [opts.ignoreElements] {Array} jQuery selectors of DOM elements to ignore during render. Can be an expensive check
     */
    render: function($el, template, context, opts) {
      var newDOM,
          newHTML = template(context),
          el = $el.get(0);
      opts = opts || {};

      if (opts.force) {
        $el.html(newHTML);
      } else {
        newDOM = this.copyTopElement(el);
        $(newDOM).html(newHTML);
        this.hotswapKeepCaret(el, newDOM, opts.ignoreElements);
      }
    },

    /**
     * Call this.hotswap but also keeps the caret position the same
     * @param currentNode {Node} The DOM Node corresponding to the existing page content to update
     * @param newNode {Node} The detached DOM Node representing the desired DOM subtree
     * @param ignoreElements {Array} Array of jQuery selectors for DOM Elements to ignore during render. Can be an expensive check.
     * @method hotswapKeepCaret
     */

    hotswapKeepCaret: function(currentNode, newNode, ignoreElements) {
      var currentCaret,
          activeElement = document.activeElement;
      if (activeElement && activeElement.hasAttribute('value') && this.supportsSelection(activeElement)) {
        currentCaret = this.getCaretPosition(activeElement);
      }
      this.hotswap(currentNode, newNode, ignoreElements);
      if (activeElement) {
        this.setCaretPosition(activeElement, currentCaret);
      }
    },

    // See above function declaration for method-level documentation
    hotswap: hotswap,

    /**
     * Produces a copy of the element tag with attributes but with no contents
     * @param el {Element} the DOM element to be copied
     * @return {Element} a shallow copy of the element with no children but with attributes
     * @method copyTopElement
     */
    copyTopElement: function(el) {
      var newDOM = document.createElement(el.tagName);
      _.each(el.attributes, function(attrib) {
        newDOM.setAttribute(attrib.name, attrib.value);
      });
      return newDOM;
    },

    /**
     * Determines if the element supports selection. As per spec, https://html.spec.whatwg.org/multipage/forms.html#do-not-apply
     * selection is only allowed for text, search, tel, url, password. Other input types will throw an exception in chrome
     * @param el {Element} the DOM element to check
     * @return {Boolean} boolean indicating whether or not the selection is allowed for {Element} el
     * @method supportsSelection
     */
    supportsSelection : function (el) {
      return (/text|password|search|tel|url/).test(el.type);
    },

    /**
     * Method that returns the current caret (cursor) position of a given element.
     * Source: http://stackoverflow.com/questions/2897155/get-cursor-position-in-characters-within-a-text-input-field
     * @method getCaretPosition
     * @param elem {element} the DOM element to check caret position
     * @return {Integer} the cursor index of the given element.
     */
    getCaretPosition: function(elem) {
      // range {IE selection object}
      // iCaretPos {Integer} will store the final caret position
      var range,
          iCaretPos = 0;
      // IE Support
      if (document.selection) {
        // Set focus on the element
        elem.focus();
        // To get cursor position, get empty selection range
        range = document.selection.createRange();
        // Move selection start to 0 position
        range.moveStart('character', -elem.value.length);
        // The caret position is selection length
        iCaretPos = range.text.length;
      } else if (elem.selectionStart || elem.selectionStart === 0) {
        // Firefox support
        iCaretPos = elem.selectionStart;
      }
      // Return results
      return iCaretPos;
    },

    /**
     * Method that returns sets the current caret (cursor) position of a given element and puts it in focus.
     * Source: http://stackoverflow.com/questions/512528/set-cursor-position-in-html-textbox
     * @method setCaretPosition
     * @param elem {element}
     * @param caretPos {Integer} The caret index to set
     * @return {Integer} the cursor index of the given element.
     */
    setCaretPosition: function(elem, caretPos) {
      var range;
      if (elem) {
        if (elem.createTextRange) {
          // IE support
          range = elem.createTextRange();
          range.move('character', caretPos);
          range.select();
        } else if (elem.selectionStart || elem.selectionStart === 0) {
          // Firefox support
          elem.focus();
          elem.setSelectionRange(caretPos, caretPos);
        } else {
          // At least focus the element if nothing else
          elem.focus();
        }
      }
    }
  };

  return templateRenderer;
}));
<|endoftext|>


<commit_before>import {
    event as d3_event,
    select as d3_select
} from 'd3-selection';

import { t } from '../util/locale';
import { modeBrowse } from '../modes';

export function uiSourceSwitch(context) {
    var keys;

    function click() {
        d3_event.preventDefault();

        if (context.history().hasChanges() &&
            !window.confirm(t('source_switch.lose_changes'))) return;

        var live = d3_select(this)
            .classed('live');

        context.history().clearSaved();
        context.connection().switch(live ? keys[1] : keys[0]);
        context.enter(modeBrowse(context));
        context.flush();

        d3_select(this)
            .text(live ? t('source_switch.dev') : t('source_switch.live'))
            .classed('live', !live);
    }

    var sourceSwitch = function(selection) {
        selection
            .append('a')
            .attr('href', '#')
            .text(t('source_switch.live'))
            .classed('live', true)
            .attr('tabindex', -1)
            .on('click', click);
    };

    sourceSwitch.keys = function(_) {
        if (!arguments.length) return keys;
        keys = _;
        return sourceSwitch;
    };

    return sourceSwitch;
}
<commit_msg>Disable source switcher during walkthrough<commit_after>import {
    event as d3_event,
    select as d3_select
} from 'd3-selection';

import { t } from '../util/locale';
import { modeBrowse } from '../modes';

export function uiSourceSwitch(context) {
    var keys;

    function click() {
        d3_event.preventDefault();
        if (context.inIntro()) return;

        if (context.history().hasChanges() &&
            !window.confirm(t('source_switch.lose_changes'))) return;

        var live = d3_select(this)
            .classed('live');

        context.history().clearSaved();
        context.connection().switch(live ? keys[1] : keys[0]);
        context.enter(modeBrowse(context));
        context.flush();

        d3_select(this)
            .text(live ? t('source_switch.dev') : t('source_switch.live'))
            .classed('live', !live);
    }

    var sourceSwitch = function(selection) {
        selection
            .append('a')
            .attr('href', '#')
            .text(t('source_switch.live'))
            .classed('live', true)
            .attr('tabindex', -1)
            .on('click', click);
    };

    sourceSwitch.keys = function(_) {
        if (!arguments.length) return keys;
        keys = _;
        return sourceSwitch;
    };

    return sourceSwitch;
}
<|endoftext|>


<commit_before>/* eslint-disable no-param-reassign */

const cheerio = require('cheerio');
const iconv = require('iconv-lite');
const chardet = require('chardet');
const request = require('got');
const url = require('url');
const media = require('./media');
const fields = require('./fields');
const charset = require('./charset');
const utils = require('./utils');

/*
 * extract meta tags from html string
 * @param string body - html string
 * @param string options - options the user has set
 */
const extractMetaTags = (body, options) => {
  let ogObject = {};
  const $ = cheerio.load(body);
  const meta = $('meta');
  const keys = Object.keys(meta);

  keys.forEach((key) => {
    if (!(meta[key].attribs && (meta[key].attribs.property || meta[key].attribs.name))) {
      return;
    }
    const property = meta[key].attribs.property || meta[key].attribs.name;
    const content = meta[key].attribs.content || meta[key].attribs.value;
    fields.forEach((item) => {
      if (property === item.property) {
        if (!item.multiple) {
          ogObject[item.fieldName] = content;
        } else if (!ogObject[item.fieldName]) {
          ogObject[item.fieldName] = [content];
        } else if (Array.isArray(ogObject[item.fieldName])) {
          ogObject[item.fieldName].push(content);
        }
      }
    });
  });

  // set the ogImage or fallback to ogImageURL or ogImageSecureURL
  // TODO: need to clean this up
  // eslint-disable-next-line no-nested-ternary
  ogObject.ogImage = ogObject.ogImage
    ? ogObject.ogImage
    // eslint-disable-next-line no-nested-ternary
    : (ogObject.ogImageURL ? ogObject.ogImageURL : (ogObject.ogImageSecureURL ? ogObject.ogImageSecureURL : []));
  if (!ogObject.ogImage || !ogObject.ogImage.length) {
    delete ogObject.ogImage;
  }

  // sets up all the media stuff
  ogObject = media.mediaSetup(ogObject, options);

  // Check for 'only get open graph info'
  if (!options.onlyGetOpenGraphInfo) {
    // Get title tag if og title was not provided
    if (!ogObject.ogTitle && $('head > title').text() && $('head > title').text().length > 0) {
      ogObject.ogTitle = $('head > title').text();
    }
    // Get meta description tag if og description was not provided
    if (!ogObject.ogDescription && $('head > meta[name="description"]').attr('content') && $('head > meta[name="description"]').attr('content').length > 0) {
      ogObject.ogDescription = $('head > meta[name="description"]').attr('content');
    }
    // Get first image as og:image if there is no og:image tag.
    const ogImageFallback = options.ogImageFallback === undefined ? true : options.ogImageFallback;
    if (!ogObject.ogImage && ogImageFallback) {
      ogObject.ogImage = [];
      const supportedImageExts = ['jpg', 'jpeg', 'png'];
      // TODO: need to clean this up
      $('img').map((i, elem) => {
        if ($(elem).attr('src') && $(elem).attr('src').length > 0 && supportedImageExts.indexOf($(elem).attr('src').split('.').pop()) !== -1) {
          ogObject.ogImage.push({
            url: $(elem).attr('src'),
          });
        }
        return false;
      });
    }
  }

  // remove ogObject.ogImage is there is nothing found
  if (ogObject.ogImage.length === 0) {
    delete ogObject.ogImage;
  }

  return ogObject;
};

/*
 * request and results formatter
 * @param string options - options the user has set
 * @param function callback
 */
const requestAndResultsFormatter = async (options) => {
  const peekSize = options.peekSize || 1024;
  const requestUrl = options.url;
  delete options.url;

  return request.get(requestUrl, options)
    .then((response) => {
      options.url = requestUrl;
      let formatBody = response.body;
      if (response && response.statusCode && (response.statusCode.toString().substring(0, 1) === '4' || response.statusCode.toString().substring(0, 1) === '5')) {
        throw new Error('Server has returned a 400/500 error code');
      }
      if (options.runChar) {
        const char = charset(response.headers, formatBody, peekSize) || chardet.detect(formatBody);
        if (char) {
          try {
            formatBody = iconv.decode(formatBody, char);
          } catch (exception) {
            throw new Error(exception);
          }
        } else {
          formatBody = formatBody.toString();
        }
      }

      const ogObject = extractMetaTags(formatBody, options);
      if (options.withCharset) {
        ogObject.charset = charset(response.headers, formatBody, peekSize);
      }
      ogObject.requestUrl = options.url;
      ogObject.success = true;
      return { ogObject, response };
    })
    .catch((error) => {
      options.url = requestUrl;
      if (error instanceof Error) throw error;
      throw new Error(error);
    });
};

/*
 * set options and return open graph results
 * @param string options - options the user has set
 * @param function callback
 */
const setOptionsAndReturnOpenGraphResults = async (options) => {
  if (options.html) {
    if (options.url) {
      throw new Error('Must specify either `url` or `html`, not both');
    }
    const ogObject = extractMetaTags(options.html, options);
    ogObject.requestUrl = null;
    ogObject.success = true;
    return { ogObject, response: { body: options.html } };
  }

  const validate = utils.validate(options.url, options.timeout);

  if (!validate.returnInputUrl) throw new Error('Invalid URL');

  options.url = validate.returnInputUrl;
  options.timeout = validate.returnInputTimeout;
  options.decompress = options.decompress || true;
  options.followAllRedirects = options.followAllRedirects || true;
  options.maxRedirects = options.maxRedirects || 20;

  if (options.encoding === null) {
    options.runChar = true;
    options.encoding = 'utf8';
    options.responseType = 'buffer';
  } else {
    options.encoding = options.encoding || 'utf8';
  }

  if (process.browser) {
    options.decompress = false;
    options.protocol = url.parse(options.url).protocol;
  }

  // trying to limit non html pages
  if (utils.endsWith(validate.returnInputUrl, '.jpg')
    || utils.endsWith(validate.returnInputUrl, '.jpeg')
    || utils.endsWith(validate.returnInputUrl, '.png')
    || utils.endsWith(validate.returnInputUrl, '.zip')
    || utils.endsWith(validate.returnInputUrl, '.pdf')) {
    throw new Error('Must scrape an HTML page');
  }

  // see if site is black listed
  if (options.blacklist && options.blacklist.length > 0) {
    for (let i = 0; i < options.blacklist.length; i += 1) {
      if (options.url.indexOf(options.blacklist[i]) !== -1) {
        throw new Error('Host Name Has Been Black Listed');
      }
    }
  }

  try {
    const results = await requestAndResultsFormatter(options);
    return results;
  } catch (exception) {
    if (exception && (exception.code === 'ENOTFOUND' || exception.code === 'EHOSTUNREACH')) {
      throw new Error('Page Not Found');
    } else if (exception && exception.code === 'ETIMEDOUT') {
      throw new Error('Time Out');
    } else if (exception && (exception.code === 'ENETUNREACH' || exception.code === 'ERR_INVALID_URL')) {
      throw new Error('Page Not Found');
    } else if (exception && exception.message === 'Response code 404 (Not Found)') {
      throw new Error('Page Not Found');
    }
    if (exception instanceof Error) throw exception;
    throw new Error('Page Not Found');
  }
};

/*
 * run
 * @param string options - options the user has set
 * @param function callback and promise
 */
exports.run = (options, callback) => {
  const hasCallback = typeof callback === 'function';
  // eslint-disable-next-line no-async-promise-executor, promise/avoid-new
  return new Promise(async (resolve, reject) => {
    let results;
    try {
      results = await setOptionsAndReturnOpenGraphResults(options);
    } catch (exception) {
      const returnError = {
        success: false,
        requestUrl: options.url,
        error: exception.message,
        errorDetails: exception,
      };
      if (hasCallback) return callback(true, returnError);
      const returnValues = {
        error: true,
        result: returnError,
      };
      return reject(returnValues);
    }
    if (hasCallback) return callback(false, results.ogObject, results.response);
    const returnValues = {
      error: false,
      result: results.ogObject,
      response: results.response,
    };
    return resolve(returnValues);
  });
};

module.exports = (options, callback) => exports.run(options, callback);
<commit_msg>adding error case for EINVAL<commit_after>/* eslint-disable no-param-reassign */

const cheerio = require('cheerio');
const iconv = require('iconv-lite');
const chardet = require('chardet');
const request = require('got');
const url = require('url');
const media = require('./media');
const fields = require('./fields');
const charset = require('./charset');
const utils = require('./utils');

/*
 * extract meta tags from html string
 * @param string body - html string
 * @param string options - options the user has set
 */
const extractMetaTags = (body, options) => {
  let ogObject = {};
  const $ = cheerio.load(body);
  const meta = $('meta');
  const keys = Object.keys(meta);

  keys.forEach((key) => {
    if (!(meta[key].attribs && (meta[key].attribs.property || meta[key].attribs.name))) {
      return;
    }
    const property = meta[key].attribs.property || meta[key].attribs.name;
    const content = meta[key].attribs.content || meta[key].attribs.value;
    fields.forEach((item) => {
      if (property === item.property) {
        if (!item.multiple) {
          ogObject[item.fieldName] = content;
        } else if (!ogObject[item.fieldName]) {
          ogObject[item.fieldName] = [content];
        } else if (Array.isArray(ogObject[item.fieldName])) {
          ogObject[item.fieldName].push(content);
        }
      }
    });
  });

  // set the ogImage or fallback to ogImageURL or ogImageSecureURL
  // TODO: need to clean this up
  // eslint-disable-next-line no-nested-ternary
  ogObject.ogImage = ogObject.ogImage
    ? ogObject.ogImage
    // eslint-disable-next-line no-nested-ternary
    : (ogObject.ogImageURL ? ogObject.ogImageURL : (ogObject.ogImageSecureURL ? ogObject.ogImageSecureURL : []));
  if (!ogObject.ogImage || !ogObject.ogImage.length) {
    delete ogObject.ogImage;
  }

  // sets up all the media stuff
  ogObject = media.mediaSetup(ogObject, options);

  // Check for 'only get open graph info'
  if (!options.onlyGetOpenGraphInfo) {
    // Get title tag if og title was not provided
    if (!ogObject.ogTitle && $('head > title').text() && $('head > title').text().length > 0) {
      ogObject.ogTitle = $('head > title').text();
    }
    // Get meta description tag if og description was not provided
    if (!ogObject.ogDescription && $('head > meta[name="description"]').attr('content') && $('head > meta[name="description"]').attr('content').length > 0) {
      ogObject.ogDescription = $('head > meta[name="description"]').attr('content');
    }
    // Get first image as og:image if there is no og:image tag.
    const ogImageFallback = options.ogImageFallback === undefined ? true : options.ogImageFallback;
    if (!ogObject.ogImage && ogImageFallback) {
      ogObject.ogImage = [];
      const supportedImageExts = ['jpg', 'jpeg', 'png'];
      // TODO: need to clean this up
      $('img').map((i, elem) => {
        if ($(elem).attr('src') && $(elem).attr('src').length > 0 && supportedImageExts.indexOf($(elem).attr('src').split('.').pop()) !== -1) {
          ogObject.ogImage.push({
            url: $(elem).attr('src'),
          });
        }
        return false;
      });
    }
  }

  // remove ogObject.ogImage is there is nothing found
  if (ogObject.ogImage.length === 0) {
    delete ogObject.ogImage;
  }

  return ogObject;
};

/*
 * request and results formatter
 * @param string options - options the user has set
 * @param function callback
 */
const requestAndResultsFormatter = async (options) => {
  const peekSize = options.peekSize || 1024;
  const requestUrl = options.url;
  delete options.url;

  return request.get(requestUrl, options)
    .then((response) => {
      options.url = requestUrl;
      let formatBody = response.body;
      if (response && response.statusCode && (response.statusCode.toString().substring(0, 1) === '4' || response.statusCode.toString().substring(0, 1) === '5')) {
        throw new Error('Server has returned a 400/500 error code');
      }
      if (options.runChar) {
        const char = charset(response.headers, formatBody, peekSize) || chardet.detect(formatBody);
        if (char) {
          try {
            formatBody = iconv.decode(formatBody, char);
          } catch (exception) {
            throw new Error(exception);
          }
        } else {
          formatBody = formatBody.toString();
        }
      }

      const ogObject = extractMetaTags(formatBody, options);
      if (options.withCharset) {
        ogObject.charset = charset(response.headers, formatBody, peekSize);
      }
      ogObject.requestUrl = options.url;
      ogObject.success = true;
      return { ogObject, response };
    })
    .catch((error) => {
      options.url = requestUrl;
      if (error instanceof Error) throw error;
      throw new Error(error);
    });
};

/*
 * set options and return open graph results
 * @param string options - options the user has set
 * @param function callback
 */
const setOptionsAndReturnOpenGraphResults = async (options) => {
  if (options.html) {
    if (options.url) {
      throw new Error('Must specify either `url` or `html`, not both');
    }
    const ogObject = extractMetaTags(options.html, options);
    ogObject.requestUrl = null;
    ogObject.success = true;
    return { ogObject, response: { body: options.html } };
  }

  const validate = utils.validate(options.url, options.timeout);

  if (!validate.returnInputUrl) throw new Error('Invalid URL');

  options.url = validate.returnInputUrl;
  options.timeout = validate.returnInputTimeout;
  options.decompress = options.decompress || true;
  options.followAllRedirects = options.followAllRedirects || true;
  options.maxRedirects = options.maxRedirects || 20;

  if (options.encoding === null) {
    options.runChar = true;
    options.encoding = 'utf8';
    options.responseType = 'buffer';
  } else {
    options.encoding = options.encoding || 'utf8';
  }

  if (process.browser) {
    options.decompress = false;
    options.protocol = url.parse(options.url).protocol;
  }

  // trying to limit non html pages
  if (utils.endsWith(validate.returnInputUrl, '.jpg')
    || utils.endsWith(validate.returnInputUrl, '.jpeg')
    || utils.endsWith(validate.returnInputUrl, '.png')
    || utils.endsWith(validate.returnInputUrl, '.zip')
    || utils.endsWith(validate.returnInputUrl, '.pdf')) {
    throw new Error('Must scrape an HTML page');
  }

  // see if site is black listed
  if (options.blacklist && options.blacklist.length > 0) {
    for (let i = 0; i < options.blacklist.length; i += 1) {
      if (options.url.indexOf(options.blacklist[i]) !== -1) {
        throw new Error('Host Name Has Been Black Listed');
      }
    }
  }

  try {
    const results = await requestAndResultsFormatter(options);
    return results;
  } catch (exception) {
    if (exception && (exception.code === 'ENOTFOUND' || exception.code === 'EHOSTUNREACH')) {
      throw new Error('Page Not Found');
    } else if (exception && exception.code === 'ETIMEDOUT') {
      throw new Error('Time Out');
    } else if (exception && (exception.code === 'ENETUNREACH' || exception.code === 'ERR_INVALID_URL' || exception.code === 'EINVAL')) {
      throw new Error('Page Not Found');
    } else if (exception && exception.message === 'Response code 404 (Not Found)') {
      throw new Error('Page Not Found');
    }
    if (exception instanceof Error) throw exception;
    throw new Error('Page Not Found');
  }
};

/*
 * run
 * @param string options - options the user has set
 * @param function callback and promise
 */
exports.run = (options, callback) => {
  const hasCallback = typeof callback === 'function';
  // eslint-disable-next-line no-async-promise-executor, promise/avoid-new
  return new Promise(async (resolve, reject) => {
    let results;
    try {
      results = await setOptionsAndReturnOpenGraphResults(options);
    } catch (exception) {
      const returnError = {
        success: false,
        requestUrl: options.url,
        error: exception.message,
        errorDetails: exception,
      };
      if (hasCallback) return callback(true, returnError);
      const returnValues = {
        error: true,
        result: returnError,
      };
      return reject(returnValues);
    }
    if (hasCallback) return callback(false, results.ogObject, results.response);
    const returnValues = {
      error: false,
      result: results.ogObject,
      response: results.response,
    };
    return resolve(returnValues);
  });
};

module.exports = (options, callback) => exports.run(options, callback);
<|endoftext|>