<commit_before>var templates = [
    "root/externallib/text!root/plugins/assign/view.html",
    "root/externallib/text!root/plugins/assign/submissions.html"
];

define(templates, function (assignTpl, submissionsTpl) {
    var plugin = {
        settings: {
            name: "assign",
            type: "mod",
            component: "mod_assign",
            lang: {
                component: "core"
            }
        },

        storage: {
            "assign_file": {type: "model"},
            "assign_files": {type: "collection", model: "assign_file"}
        },

        routes: [
            ["assign/view/:courseId/:cmid", "view_assign", "viewAssign"]
        ],

        sectionsCache: [],

         /**
         * Determines is the plugin is visible.
         * It may check Moodle remote site version, device OS, device type, etc...
         * This function is called when a alink to a plugin functinality is going to be rendered.
         *
         * @return {bool} True if the plugin is visible for the site and device
         */
        isPluginVisible: function() {
            // Check core services.
            var visible =   MM.util.wsAvailable('mod_assign_get_assignments') &&
                            MM.util.wsAvailable('mod_assign_get_submissions');

            return visible;
        },

        render: function(courseId, sectionId, section, module) {
            var data = {
                "courseId": courseId,
                "sectionId": sectionId,
                "section": section,
                "module": module
            };
            // Store the section name.
            MM.plugins.assign.sectionsCache[module.contentid] = MM.util.formatText(section.name);

            return MM.tpl.render(MM.plugins.assign.templates.view.html, data);
        },

        perPage: 20,

        submissionsCache: null,

        /**
         * Display a assign
         * @param  {Number} cmid The course module number id
         *
         */
        viewAssign: function(courseId, cmid) {
            // Loading ....
            $("#info-" + cmid, "#panel-right").attr("src", "img/loadingblack.gif");

            // First, load the complete information of assigns in this course.
            var params = {
                "courseids[0]": courseId
            };

            MM.moodleWSCall("mod_assign_get_assignments",
                params,
                function(result) {
                    var course = result.courses.shift();
                    var currentAssign;
                    _.each(course.assignments, function(assign) {
                        if (assign.cmid == cmid) {
                            currentAssign = assign;
                        }
                    });
                    if (currentAssign) {
                        MM.plugins.assign._showSubmissions(currentAssign);
                    }
                },
                null,
                function (error) {
                    $("#info-" + cmid, "#panel-right").attr("src", "img/info.png");
                    MM.popErrorMessage(error);
                }
            );
        },

        /**
         * Display submissions of a assign
         * @param  {Object} assign assign object
         *
         */
        _showSubmissions: function(assign) {

            var params = {
                "assignmentids[0]": assign.id
            };

            MM.moodleWSCall("mod_assign_get_submissions",
                params,
                // Success callback.
                function(result) {
                    // Stops loading...
                    $("#info-" + assign.cmid, "#panel-right").attr("src", "img/info.png");
                    var siteId = MM.config.current_site.id;

                    var sectionName = "";
                    if (MM.plugins.assign.sectionsCache[assign.cmid]) {
                        sectionName = MM.plugins.assign.sectionsCache[assign.cmid];
                    }

                    var pageTitle = '
\

\
                                \

\

\

' + MM.util.formatText(assign.name) + '
\

\

';

                    var data = {
                        "assign": assign,
                        "sectionName": sectionName,
                        "activityLink": MM.config.current_site.siteurl + '/mod/assign/view.php?id=' + assign.cmid,
                        "submissions": [],
                        "users": {}
                    };

                    // Check if we can view submissions, with enought permissions.
                    if (result.warnings.length > 0 && result.warnings[0].warningcode == 1) {
                        data.canviewsubmissions = false;
                    } else {
                        data.canviewsubmissions = true;
                        data.submissions = result.assignments[0].submissions;
                    }

                    // Handle attachments.
                    for (var el in assign.introattachments) {
                        var attachment = assign.introattachments[el];

                        assign.introattachments[el].id = assign.id + "-intro-" + el;

                        var uniqueId = MM.config.current_site.id + "-" + hex_md5(attachment.fileurl);
                        var path = MM.db.get("assign_files", uniqueId);
                        if (path) {
                            assign.introattachments[el].localpath = path.get("localpath");
                        }

                        var extension = MM.util.getFileExtension(attachment.filename);
                        if (typeof(MM.plugins.contents.templates.mimetypes[extension]) != "undefined") {
                            assign.introattachments[el].icon = MM.plugins.contents.templates.mimetypes[extension]["icon"] + "-64.png";
                        }
                    }

                    // Render the page if the user is likely an student.
                    if (! data.canviewsubmissions) {
                        MM.plugins.assign._renderSubmissionsPage(data, pageTitle);
                    } else {
                        // In this case, we would need additional information (like pre-fetching the course participants).
                        MM.plugins.participants._loadParticipants(assign.course, 0, 0,
                            function(users) {

                                // Recover the users who has made submissions, we need to retrieve the full information later.
                                var userIds = [];
                                data.submissions.forEach(function(sub) {
                                    userIds.push(sub.userid);
                                });

                                // Save the users in the users table. We are going to need the user names.
                                var newUser;
                                users.forEach(function(user) {
                                    newUser = {
                                        'id': MM.config.current_site.id + '-' + user.id,
                                        'userid': user.id,
                                        'fullname': user.fullname,
                                        'profileimageurl': user.profileimageurl
                                    };
                                    MM.db.insert('users', newUser);
                                    if (userIds.indexOf(user.id) > -1) {
                                        data.users[user.id] = newUser;
                                    }
                                });
                                // Render the submissions page.
                                MM.plugins.assign._renderSubmissionsPage(data, pageTitle);
                            },
                            function(m) {
                                $("#info-" + assign.cmid, "#panel-right").attr("src", "img/info.png");
                                MM.popErrorMessage(error);
                            }
                        );
                    }
                },
                null,
                function (error) {
                    $("#info-" + assign.cmid, "#panel-right").attr("src", "img/info.png");
                    MM.popErrorMessage(error);
                }
            );
        },

        _renderSubmissionsPage: function(data, pageTitle) {

            MM.plugins.assign.submissionsCache = data.submissions;

            var html = MM.tpl.render(MM.plugins.assign.templates.submissions.html, data);
            MM.panels.show("right", html, {title: pageTitle});

            // Handle intro files downloads.
            $(".assign-download", "#panel-right").on(MM.clickType, function(e) {
                e.preventDefault();
                e.stopPropagation();

                var url = $(this).data("downloadurl");
                var filename = $(this).data("filename");
                var attachmentId = $(this).data("attachmentid");

                MM.plugins.assign._downloadFile(url, filename, attachmentId);
            });
        },

        _downloadFile: function(url, filename, attachmentId) {
            // Add the token.
            var downloadURL = MM.fixPluginfile(url);
            var siteId = MM.config.current_site.id;
            var downCssId = $("#downimg-" + attachmentId);
            var linkCssId = $("#attachment-" + attachmentId);

            filename = MM.fs.normalizeFileName(filename);

            var directory = siteId + "/assign-files/" + attachmentId;
            var filePath = directory + "/" + filename;

            MM.fs.init(function() {
                if (MM.deviceConnected()) {
                    MM.log("Starting download of Moodle file: " + downloadURL);
                    // All the functions are asynchronous, like createDir.
                    MM.fs.createDir(directory, function() {
                        MM.log("Downloading Moodle file to " + filePath + " from URL: " + downloadURL);

                        $(downCssId).attr("src", "img/loadingblack.gif");
                        MM.moodleDownloadFile(downloadURL, filePath,
                            function(fullpath) {
                                MM.log("Download of content finished " + fullpath + " URL: " + downloadURL);

                                var uniqueId = siteId + "-" + hex_md5(url);
                                var file = {
                                    id: uniqueId,
                                    url: url,
                                    site: siteId,
                                    localpath: fullpath
                                };
                                MM.db.insert("assign_files", file);

                                $(downCssId).remove();
                                $(linkCssId).attr("href", fullpath);
                                $(linkCssId).attr("rel", "external");
                                // Remove class and events.
                                $(linkCssId).removeClass("assign-download");
                                $(linkCssId).off(MM.clickType);

                                // Android, open in new browser
                                MM.handleFiles(linkCssId);
                                MM._openFile(fullpath);

                            },
                            function(fullpath) {
                                $(downCssId).remove();
                                MM.log("Error downloading " + fullpath + " URL: " + downloadURL);
                            }
                        );
                    });
                } else {
                    MM.popErrorMessage(MM.lang.s("errornoconnectednocache"));
                }
            });
        },

        templates: {
            "view": {
                html: assignTpl
            },
            "submissions": {
                html: submissionsTpl
            }
        }

    };

    MM.registerPlugin(plugin);

});<commit_msg>MOBILE-924 assign: Helper methods<commit_after>var templates = [
    "root/externallib/text!root/plugins/assign/view.html",
    "root/externallib/text!root/plugins/assign/submissions.html"
];

define(templates, function (assignTpl, submissionsTpl) {
    var plugin = {
        settings: {
            name: "assign",
            type: "mod",
            component: "mod_assign",
            lang: {
                component: "core"
            }
        },

        storage: {
            "assign_file": {type: "model"},
            "assign_files": {type: "collection", model: "assign_file"}
        },

        routes: [
            ["assign/view/:courseId/:cmid", "view_assign", "viewAssign"]
        ],

        sectionsCache: [],

         /**
         * Determines is the plugin is visible.
         * It may check Moodle remote site version, device OS, device type, etc...
         * This function is called when a alink to a plugin functinality is going to be rendered.
         *
         * @return {bool} True if the plugin is visible for the site and device
         */
        isPluginVisible: function() {
            // Check core services.
            var visible =   MM.util.wsAvailable('mod_assign_get_assignments') &&
                            MM.util.wsAvailable('mod_assign_get_submissions');

            return visible;
        },

        render: function(courseId, sectionId, section, module) {
            var data = {
                "courseId": courseId,
                "sectionId": sectionId,
                "section": section,
                "module": module
            };
            // Store the section name.
            MM.plugins.assign.sectionsCache[module.contentid] = MM.util.formatText(section.name);

            return MM.tpl.render(MM.plugins.assign.templates.view.html, data);
        },

        perPage: 20,

        submissionsCache: null,

        /**
         * Display a assign
         * @param  {Number} cmid The course module number id
         *
         */
        viewAssign: function(courseId, cmid) {
            // Loading ....
            $("#info-" + cmid, "#panel-right").attr("src", "img/loadingblack.gif");

            // First, load the complete information of assigns in this course.
            var params = {
                "courseids[0]": courseId
            };

            MM.moodleWSCall("mod_assign_get_assignments",
                params,
                function(result) {
                    var course = result.courses.shift();
                    var currentAssign;
                    _.each(course.assignments, function(assign) {
                        if (assign.cmid == cmid) {
                            currentAssign = assign;
                        }
                    });
                    if (currentAssign) {
                        MM.plugins.assign._showSubmissions(currentAssign);
                    }
                },
                null,
                function (error) {
                    $("#info-" + cmid, "#panel-right").attr("src", "img/info.png");
                    MM.popErrorMessage(error);
                }
            );
        },

        /**
         * Display submissions of a assign
         * @param  {Object} assign assign object
         *
         */
        _showSubmissions: function(assign) {

            var params = {
                "assignmentids[0]": assign.id
            };

            MM.moodleWSCall("mod_assign_get_submissions",
                params,
                // Success callback.
                function(result) {
                    // Stops loading...
                    $("#info-" + assign.cmid, "#panel-right").attr("src", "img/info.png");
                    var siteId = MM.config.current_site.id;

                    var sectionName = "";
                    if (MM.plugins.assign.sectionsCache[assign.cmid]) {
                        sectionName = MM.plugins.assign.sectionsCache[assign.cmid];
                    }

                    var pageTitle = '
\

\
                                \

\

\

' + MM.util.formatText(assign.name) + '
\

\

';

                    var data = {
                        "assign": assign,
                        "sectionName": sectionName,
                        "activityLink": MM.config.current_site.siteurl + '/mod/assign/view.php?id=' + assign.cmid,
                        "submissions": [],
                        "users": {}
                    };

                    // Check if we can view submissions, with enought permissions.
                    if (result.warnings.length > 0 && result.warnings[0].warningcode == 1) {
                        data.canviewsubmissions = false;
                    } else {
                        data.canviewsubmissions = true;
                        data.submissions = result.assignments[0].submissions;
                    }

                    // Handle attachments.
                    for (var el in assign.introattachments) {
                        var attachment = assign.introattachments[el];

                        assign.introattachments[el].id = assign.id + "-intro-" + el;

                        var uniqueId = MM.config.current_site.id + "-" + hex_md5(attachment.fileurl);
                        var path = MM.db.get("assign_files", uniqueId);
                        if (path) {
                            assign.introattachments[el].localpath = path.get("localpath");
                        }

                        var extension = MM.util.getFileExtension(attachment.filename);
                        if (typeof(MM.plugins.contents.templates.mimetypes[extension]) != "undefined") {
                            assign.introattachments[el].icon = MM.plugins.contents.templates.mimetypes[extension]["icon"] + "-64.png";
                        }
                    }

                    // Render the page if the user is likely an student.
                    if (! data.canviewsubmissions) {
                        MM.plugins.assign._renderSubmissionsPage(data, pageTitle);
                    } else {
                        // In this case, we would need additional information (like pre-fetching the course participants).
                        MM.plugins.participants._loadParticipants(assign.course, 0, 0,
                            function(users) {

                                // Recover the users who has made submissions, we need to retrieve the full information later.
                                var userIds = [];
                                data.submissions.forEach(function(sub) {
                                    userIds.push(sub.userid);
                                });

                                // Save the users in the users table. We are going to need the user names.
                                var newUser;
                                users.forEach(function(user) {
                                    newUser = {
                                        'id': MM.config.current_site.id + '-' + user.id,
                                        'userid': user.id,
                                        'fullname': user.fullname,
                                        'profileimageurl': user.profileimageurl
                                    };
                                    MM.db.insert('users', newUser);
                                    if (userIds.indexOf(user.id) > -1) {
                                        data.users[user.id] = newUser;
                                    }
                                });
                                // Render the submissions page.
                                MM.plugins.assign._renderSubmissionsPage(data, pageTitle);
                            },
                            function(m) {
                                $("#info-" + assign.cmid, "#panel-right").attr("src", "img/info.png");
                                MM.popErrorMessage(error);
                            }
                        );
                    }
                },
                null,
                function (error) {
                    $("#info-" + assign.cmid, "#panel-right").attr("src", "img/info.png");
                    MM.popErrorMessage(error);
                }
            );
        },

        _renderSubmissionsPage: function(data, pageTitle) {

            MM.plugins.assign.submissionsCache = data.submissions;

            var html = MM.tpl.render(MM.plugins.assign.templates.submissions.html, data);
            MM.panels.show("right", html, {title: pageTitle});

            // Handle intro files downloads.
            $(".assign-download", "#panel-right").on(MM.clickType, function(e) {
                e.preventDefault();
                e.stopPropagation();

                var url = $(this).data("downloadurl");
                var filename = $(this).data("filename");
                var attachmentId = $(this).data("attachmentid");

                MM.plugins.assign._downloadFile(url, filename, attachmentId);
            });
        },

        _getSubmissionText: function(submission) {
            var text = '';
            if (submission.plugins) {
                submission.plugins.forEach(function(plugin) {
                    if (plugin.type == 'onlinetext') {
                        return plugin.editorfields.text;
                    }
                });
            }
            return text;
        },

        _getSubmissionFiles: function(submission) {
            var files = [];
            if (submission.plugins) {
                submission.plugins.forEach(function(plugin) {
                    if (plugin.type == 'file') {
                        return plugin.fileareas.files;
                    }
                });
            }
            return files;
        },

        _downloadFile: function(url, filename, attachmentId) {
            // Add the token.
            var downloadURL = MM.fixPluginfile(url);
            var siteId = MM.config.current_site.id;
            var downCssId = $("#downimg-" + attachmentId);
            var linkCssId = $("#attachment-" + attachmentId);

            filename = MM.fs.normalizeFileName(filename);

            var directory = siteId + "/assign-files/" + attachmentId;
            var filePath = directory + "/" + filename;

            MM.fs.init(function() {
                if (MM.deviceConnected()) {
                    MM.log("Starting download of Moodle file: " + downloadURL);
                    // All the functions are asynchronous, like createDir.
                    MM.fs.createDir(directory, function() {
                        MM.log("Downloading Moodle file to " + filePath + " from URL: " + downloadURL);

                        $(downCssId).attr("src", "img/loadingblack.gif");
                        MM.moodleDownloadFile(downloadURL, filePath,
                            function(fullpath) {
                                MM.log("Download of content finished " + fullpath + " URL: " + downloadURL);

                                var uniqueId = siteId + "-" + hex_md5(url);
                                var file = {
                                    id: uniqueId,
                                    url: url,
                                    site: siteId,
                                    localpath: fullpath
                                };
                                MM.db.insert("assign_files", file);

                                $(downCssId).remove();
                                $(linkCssId).attr("href", fullpath);
                                $(linkCssId).attr("rel", "external");
                                // Remove class and events.
                                $(linkCssId).removeClass("assign-download");
                                $(linkCssId).off(MM.clickType);

                                // Android, open in new browser
                                MM.handleFiles(linkCssId);
                                MM._openFile(fullpath);

                            },
                            function(fullpath) {
                                $(downCssId).remove();
                                MM.log("Error downloading " + fullpath + " URL: " + downloadURL);
                            }
                        );
                    });
                } else {
                    MM.popErrorMessage(MM.lang.s("errornoconnectednocache"));
                }
            });
        },

        templates: {
            "view": {
                html: assignTpl
            },
            "submissions": {
                html: submissionsTpl
            }
        }

    };

    MM.registerPlugin(plugin);

});<|endoftext|>


<commit_before>/*
 * prototype.js
 * Enhances various prototype objects
 */

(function(){

var regex = {
	all_whitespace: /\s+/g,
	css_declaration: /\s*([\w-]+)\s*:\s*([^;]*);/g,
	whitespace: /\s+/
};

function log(msg) {
	console.log("prototype.js: "+msg);
}

function warn(msg) {
	console.warn("prototype.js: "+msg);
}

function error(msg) {
	console.error("prototype.js: "+msg);
}

function getCssPropertyName(prop) {
	return prop.replace(/[A-Z]/g, function(letter) { return "-"+letter.toLowerCase() });
}

function getVisibleProperty(el, prop) {
	var value
	if ( el.getCssStyle("display") === "none" ) {
		var display = el.style.display;
		var visibility = el.style.visibility;
		el.setStyle("visibility", "hidden");
		el.setStyle("display", "block");
		value = typeof(prop) === "string" ? el[prop] : prop();
		el.setStyle("display", display);
		el.setStyle("visibility", visibility);
	} else {
		value = typeof(prop) === "string" ? el[prop] : prop();
	}
	return value;
}

function enhancePrototype(classNames, methods) {
	if ( typeof(classNames) === "string" ) classNames = [classNames]
	for ( var i = 0, l = classNames.length; i < l; i++ ) {
		var className = classNames[i]

		var key,
			object = window[className],
			prototype = object && object.prototype || object;

		if ( prototype && typeof(prototype) === "object" ) {
			for ( key in methods ) {
				if ( methods.hasOwnProperty(key) ) {
					if ( prototype[key] !== undefined ) {
						warn(className+".prototype already has method "+key);
					} else {
						log("Adding method "+key+" to "+className+".prototype");
						prototype[key] = methods[key];
					}
				}
			}
		} else {
			throw new Error(className+" has no prototype and is not an object")
		}
	}
}

// -- Document ---------------------------------------------------------------------------------------------------------

enhancePrototype("Document", {
	getLeft: function(rel) {
		return 0 - ((rel && rel.getLeft()) || 0);
	},
	getTop: function(rel) {
		return 0 - ((rel && rel.getTop()) || 0);
	}
})

// -- Document & HTMLElement -------------------------------------------------------------------------------------------

enhancePrototype(["Document", "HTMLElement"], {
	getElementByClassName: function(classNames) {
		var el = this.getElementsByClassName(classNames)
		if ( el && el.length > 0 ) return el[0]
		return null
	},
	getElementByTagName: function(tagName) {
		var el = this.getElementsByTagName(tagName)
		if ( el && el.length > 0 ) return el[0]
		return null
	},
})

// -- HTMLCollection ---------------------------------------------------------------------------------------------------

enhancePrototype("HTMLCollection", {
	forEach: Array.prototype.forEach
});

// -- HTMLElement ------------------------------------------------------------------------------------------------------

enhancePrototype("HTMLElement", {
	addClass: function(value) {
		if ( !this.className || this.className.trim().length === 0 ) {
			this.className = value;
		} else {
			var class_list = value.split(regex.whitespace),
				class_string = ' ' + (this.className || '') + ' ',
				new_class, i, l;
			for ( i = 0, l = class_list.length; i < l; i++ ) {
				new_class = class_list[i];
				if ( class_string.indexOf(' ' + new_class + ' ') === -1 ) {
					class_string += new_class + ' ';
				}
			}
			this.className = class_string.replace(regex.all_whitespace, ' ').trim();
		}
	},
	getBottom: function(rel) {
		return this.getTop(rel) + getVisibleProperty(this, "offsetHeight");
	},
	getBoundaries: function(rel) {
		var top = this.getTop(rel),
			left = this.getLeft(rel)
		return {
			top: top,
			left: left,
			right: left + getVisibleProperty(this, "offsetWidth"),
			bottom: top + getVisibleProperty(this, "offsetHeight")
		}
	},
	getCssStyle: function(prop, format) {
		var css = window.getComputedStyle(this)
		if ( !prop ) {
			var stylesheet = {},
				cssText = css.cssText
			while ( (result = regex.css_declaration.matches(cssText, [1,2])) != null ) {
				stylesheet[result[0].trim()] = result[1].trim()
			}
			return stylesheet
		}

		prop = getCssPropertyName(prop)

		if ( format ) {
			var val = css.getPropertyCSSValue(prop)
			var key = "get"+format+"Value"
			if ( val[key] ) {
				try {
					return val[key]()
				} catch(e) {
					error("Unable to call "+key+" on css property value for "+prop)
				}
			} else {
				error("Invalid css format for property "+prop+": "+format)
			}
		}

		return css.getPropertyValue(prop)
	},
	getHeight: function(where) {
		var extra = 0;
		if ( where === 'outer' ) {
			extra += parseInt(this.getCssStyle("margin-top")) || 0;
			extra += parseInt(this.getCssStyle("margin-bottom")) || 0;
		} else {
			if ( where === 'inner' || where === 'content' ) {
				extra -= parseInt(this.getCssStyle("border-top-width")) || 0;
				extra -= parseInt(this.getCssStyle("border-bottom-width")) || 0;
			}
			if ( where === 'content' ) {
				extra -= parseInt(this.getCssStyle("padding-top")) || 0;
				extra -= parseInt(this.getCssStyle("padding-bottom")) || 0;
			}
		}
		return getVisibleProperty(this, "offsetHeight") + extra;
	},
	getLeft: function(rel) {
		var parent = this.getParent();
		return getVisibleProperty(this, "offsetLeft")
			+ ((parent && parent.getLeft()) || 0)
			- ((rel && rel.getLeft()) || 0);
	},
	getNextElement: function() {
		var child = this.firstElementChild
		if ( child ) return child
		return this.getNextNonChildElement()
	},
	getNextNonChildElement: function() {
		var sibling = this.nextElementSibling
		if ( sibling ) return sibling

		var parent = this.parentNode
		if ( !parent || parent.nodeType !== parent.ELEMENT_NODE ) return null

		return parent.getNextNonChildElement()
	},
	getParent: function() {
		return getVisibleProperty(this, "offsetParent");
	},
	getRight: function(rel) {
		return this.getLeft(rel) + getVisibleProperty(this, "offsetWidth");
	},
	getSize: function(where) {
		return { width: this.getWidth(where), height: this.getHeight(where) };
	},
	getTop: function(rel) {
		var parent = this.getParent();
		return getVisibleProperty(this, "offsetTop")
			+ ((parent && parent.getTop()) || 0)
			- ((rel && rel.getTop()) || 0);
	},
	getWidth: function(where) {
		var extra = 0;
		if ( where === 'outer' ) {
			extra += parseInt(this.getCssStyle("margin-left")) || 0;
			extra += parseInt(this.getCssStyle("margin-right")) || 0;
		} else {
			if ( where === 'inner' || where === 'content' ) {
				extra -= parseInt(this.getCssStyle("border-left-width")) || 0;
				extra -= parseInt(this.getCssStyle("border-right-width")) || 0;
			}
			if ( where === 'content' ) {
				extra -= parseInt(this.getCssStyle("padding-left")) || 0;
				extra -= parseInt(this.getCssStyle("padding-right")) || 0;
			}
		}
		return getVisibleProperty(this, "offsetWidth") + extra;
	},
	hasClass: function(value) {
		if ( typeof(value) !== "string" || value.trim().length == 0 ) return
		if ( !this.className || this.className.trim().length === 0 ) {
			return false
		}
		var values = this.className.split(regex.whitespace)
		var classes = this.className.split(regex.whitespace)
		for ( var i = 0, l = classes.length; i < l; i++ ) {
			for ( var x = values.length - 1; x >= 0; x-- ) {
				if ( classes[i] == values[x] ) {
					values.splice(x, 1)
				}
			}
		}

		return values.length === 0
	},
	isMarkupLoaded: function() {
		// An element is considered loaded when:
		return (
			// - the document is fully loaded
			document.readyState !== "loading"
			// - it's not part of the document structure (ie. created via document.createElement)
			|| (this.nodeType === this.ELEMENT_NODE && this.parentNode === null)
			// - it has a non child element following it in the document structure
			|| this.getNextNonChildElement()
		) && true || false
	},
	removeClass: function(value) {
		if ( !this.className || this.className.trim().length == 0 ) {
			return
		}

		var classes_to_remove = {}
		var value_list = value.split(regex.whitespace)
		for ( var i = 0, l = value_list.length; i < l; i++ ) {
			classes_to_remove[value_list[i]] = true
		}

		var class_list = this.className.split(regex.whitespace)
		for ( var i = class_list.length-1; i >= 0; i-- ) {
			if ( class_list[i] in classes_to_remove ) {
				class_list.splice(i, 1)
			}
		}

		this.className = class_list.join(" ")
	},
	setAttributes: function(value) {
		for ( var key in value ) {
			this.setAttribute(key, value[key])
		}
	},
	setStyle: function() {
		if ( arguments.length >= 2 ) {
			var val = arguments[1]
			if ( typeof(val) === "number" ) val = val+"px"
			this.style[arguments[0]] = val
		} else if ( arguments.length === 1 ) {
			var value = arguments[0]
			if ( typeof(value) === "string" ) {
				this.setAttribute("style", value)
			} else {
				for ( var key in value ) {
					var val = value[key]
					if ( typeof(val) === "number" ) val = val+"px"
					this.style[key] = val
				}
			}
		}
	}
})

// -- Node -------------------------------------------------------------------------------------------------------------

enhancePrototype("Node", {
	appendTo: function(parent) {
		if ( !parent ) return
		parent.appendChild(this)
	},
	insertAbove: function(ref) {
		if ( !ref || !ref.parentNode ) return
		ref.parentNode.insertBefore(this, ref)
	},
	insertBelow: function(ref) {
		if ( !ref || !ref.parentNode ) return
		var nextSibling = ref.nextSibling
		if ( nextSibling ) {
			ref.parentNode.insertBefore(this, nextSibling)
		} else {
			ref.parentNode.appendChild(this)
		}
	},
	prependTo: function(parent) {
		if ( !parent ) return
		var child = parent.firstChild
		if ( child ) {
			parent.insertBefore(this, child)
		} else {
			parent.appendChild(this)
		}
	}
})

// -- NodeList ---------------------------------------------------------------------------------------------------------

enhancePrototype("NodeList", {
	forEach: Array.prototype.forEach
})

// -- RegExp -----------------------------------------------------------------------------------------------------------

enhancePrototype("RegExp", {
	match: function(string, index) {
		var matches = this.exec(string)
		if ( matches && matches.length >= index ) {
			return matches[index]
		}
		return null
	},
	matches: function(string, indexes) {
		if ( !indexes || indexes.length === 0 ) return null
		var matches = this.exec(string)
		if ( matches && matches.length > 0 ) {
			var results = []
			for ( var i = 0, l = indexes.length; i < l; i++ ) {
				var num = indexes[i]
				results.push(matches[num] || null)
			}
			return results
		}
		return null
	}
})

// -- String -----------------------------------------------------------------------------------------------------------

enhancePrototype("String", {
	format: function(args) {
		// If an array or table is passed in, use it; otherwise, turn the arguments list into an array
		args = typeof(args) === 'object' ? args : Array.prototype.slice.call(arguments)
		// Replace each instance of {x}, where x is an array index or table key, with the value from args.
		return this.replace(/{([^}]+)}/g, function(match, key) {
			return key in args ? args[key] : "{"+key+"}"
		})
	}
})

})()
<commit_msg>Added reverseForEach prototype method<commit_after>/*
 * prototype.js
 * Enhances various prototype objects
 */

(function(){

var regex = {
	all_whitespace: /\s+/g,
	css_declaration: /\s*([\w-]+)\s*:\s*([^;]*);/g,
	whitespace: /\s+/
};

function log(msg) {
	console.log("prototype.js: "+msg);
}

function warn(msg) {
	console.warn("prototype.js: "+msg);
}

function error(msg) {
	console.error("prototype.js: "+msg);
}

function getCssPropertyName(prop) {
	return prop.replace(/[A-Z]/g, function(letter) { return "-"+letter.toLowerCase() });
}

function getVisibleProperty(el, prop) {
	var value
	if ( el.getCssStyle("display") === "none" ) {
		var display = el.style.display;
		var visibility = el.style.visibility;
		el.setStyle("visibility", "hidden");
		el.setStyle("display", "block");
		value = typeof(prop) === "string" ? el[prop] : prop();
		el.setStyle("display", display);
		el.setStyle("visibility", visibility);
	} else {
		value = typeof(prop) === "string" ? el[prop] : prop();
	}
	return value;
}

function enhancePrototype(classNames, methods) {
	if ( typeof(classNames) === "string" ) classNames = [classNames]
	for ( var i = 0, l = classNames.length; i < l; i++ ) {
		var className = classNames[i]

		var key,
			object = window[className],
			prototype = object && object.prototype || object;

		if ( prototype && typeof(prototype) === "object" ) {
			for ( key in methods ) {
				if ( methods.hasOwnProperty(key) ) {
					if ( prototype[key] !== undefined ) {
						warn(className+".prototype already has method "+key);
					} else {
						log("Adding method "+key+" to "+className+".prototype");
						prototype[key] = methods[key];
					}
				}
			}
		} else {
			throw new Error(className+" has no prototype and is not an object")
		}
	}
}

// -- Array ------------------------------------------------------------------------------------------------------------

enhancePrototype("Array", {
	reverseForEach: function(func) {
		for ( var i = this.length - 1; i >= 0; i-- ) {
			func.call(arguments[1], this[i], i, this);
		}
	}
});

// -- Document ---------------------------------------------------------------------------------------------------------

enhancePrototype("Document", {
	getLeft: function(rel) {
		return 0 - ((rel && rel.getLeft()) || 0);
	},
	getTop: function(rel) {
		return 0 - ((rel && rel.getTop()) || 0);
	}
})

// -- Document & HTMLElement -------------------------------------------------------------------------------------------

enhancePrototype(["Document", "HTMLElement"], {
	getElementByClassName: function(classNames) {
		var el = this.getElementsByClassName(classNames)
		if ( el && el.length > 0 ) return el[0]
		return null
	},
	getElementByTagName: function(tagName) {
		var el = this.getElementsByTagName(tagName)
		if ( el && el.length > 0 ) return el[0]
		return null
	},
})

// -- HTMLCollection ---------------------------------------------------------------------------------------------------

enhancePrototype("HTMLCollection", {
	forEach: Array.prototype.forEach,
	reverseForEach: Array.prototype.reverseForEach
});

// -- HTMLElement ------------------------------------------------------------------------------------------------------

enhancePrototype("HTMLElement", {
	addClass: function(value) {
		if ( !this.className || this.className.trim().length === 0 ) {
			this.className = value;
		} else {
			var class_list = value.split(regex.whitespace),
				class_string = ' ' + (this.className || '') + ' ',
				new_class, i, l;
			for ( i = 0, l = class_list.length; i < l; i++ ) {
				new_class = class_list[i];
				if ( class_string.indexOf(' ' + new_class + ' ') === -1 ) {
					class_string += new_class + ' ';
				}
			}
			this.className = class_string.replace(regex.all_whitespace, ' ').trim();
		}
	},
	getBottom: function(rel) {
		return this.getTop(rel) + getVisibleProperty(this, "offsetHeight");
	},
	getBoundaries: function(rel) {
		var top = this.getTop(rel),
			left = this.getLeft(rel)
		return {
			top: top,
			left: left,
			right: left + getVisibleProperty(this, "offsetWidth"),
			bottom: top + getVisibleProperty(this, "offsetHeight")
		}
	},
	getCssStyle: function(prop, format) {
		var css = window.getComputedStyle(this)
		if ( !prop ) {
			var stylesheet = {},
				cssText = css.cssText
			while ( (result = regex.css_declaration.matches(cssText, [1,2])) != null ) {
				stylesheet[result[0].trim()] = result[1].trim()
			}
			return stylesheet
		}

		prop = getCssPropertyName(prop)

		if ( format ) {
			var val = css.getPropertyCSSValue(prop)
			var key = "get"+format+"Value"
			if ( val[key] ) {
				try {
					return val[key]()
				} catch(e) {
					error("Unable to call "+key+" on css property value for "+prop)
				}
			} else {
				error("Invalid css format for property "+prop+": "+format)
			}
		}

		return css.getPropertyValue(prop)
	},
	getHeight: function(where) {
		var extra = 0;
		if ( where === 'outer' ) {
			extra += parseInt(this.getCssStyle("margin-top")) || 0;
			extra += parseInt(this.getCssStyle("margin-bottom")) || 0;
		} else {
			if ( where === 'inner' || where === 'content' ) {
				extra -= parseInt(this.getCssStyle("border-top-width")) || 0;
				extra -= parseInt(this.getCssStyle("border-bottom-width")) || 0;
			}
			if ( where === 'content' ) {
				extra -= parseInt(this.getCssStyle("padding-top")) || 0;
				extra -= parseInt(this.getCssStyle("padding-bottom")) || 0;
			}
		}
		return getVisibleProperty(this, "offsetHeight") + extra;
	},
	getLeft: function(rel) {
		var parent = this.getParent();
		return getVisibleProperty(this, "offsetLeft")
			+ ((parent && parent.getLeft()) || 0)
			- ((rel && rel.getLeft()) || 0);
	},
	getNextElement: function() {
		var child = this.firstElementChild
		if ( child ) return child
		return this.getNextNonChildElement()
	},
	getNextNonChildElement: function() {
		var sibling = this.nextElementSibling
		if ( sibling ) return sibling

		var parent = this.parentNode
		if ( !parent || parent.nodeType !== parent.ELEMENT_NODE ) return null

		return parent.getNextNonChildElement()
	},
	getParent: function() {
		return getVisibleProperty(this, "offsetParent");
	},
	getRight: function(rel) {
		return this.getLeft(rel) + getVisibleProperty(this, "offsetWidth");
	},
	getSize: function(where) {
		return { width: this.getWidth(where), height: this.getHeight(where) };
	},
	getTop: function(rel) {
		var parent = this.getParent();
		return getVisibleProperty(this, "offsetTop")
			+ ((parent && parent.getTop()) || 0)
			- ((rel && rel.getTop()) || 0);
	},
	getWidth: function(where) {
		var extra = 0;
		if ( where === 'outer' ) {
			extra += parseInt(this.getCssStyle("margin-left")) || 0;
			extra += parseInt(this.getCssStyle("margin-right")) || 0;
		} else {
			if ( where === 'inner' || where === 'content' ) {
				extra -= parseInt(this.getCssStyle("border-left-width")) || 0;
				extra -= parseInt(this.getCssStyle("border-right-width")) || 0;
			}
			if ( where === 'content' ) {
				extra -= parseInt(this.getCssStyle("padding-left")) || 0;
				extra -= parseInt(this.getCssStyle("padding-right")) || 0;
			}
		}
		return getVisibleProperty(this, "offsetWidth") + extra;
	},
	hasClass: function(value) {
		if ( typeof(value) !== "string" || value.trim().length == 0 ) return
		if ( !this.className || this.className.trim().length === 0 ) {
			return false
		}
		var values = this.className.split(regex.whitespace)
		var classes = this.className.split(regex.whitespace)
		for ( var i = 0, l = classes.length; i < l; i++ ) {
			for ( var x = values.length - 1; x >= 0; x-- ) {
				if ( classes[i] == values[x] ) {
					values.splice(x, 1)
				}
			}
		}

		return values.length === 0
	},
	isMarkupLoaded: function() {
		// An element is considered loaded when:
		return (
			// - the document is fully loaded
			document.readyState !== "loading"
			// - it's not part of the document structure (ie. created via document.createElement)
			|| (this.nodeType === this.ELEMENT_NODE && this.parentNode === null)
			// - it has a non child element following it in the document structure
			|| this.getNextNonChildElement()
		) && true || false
	},
	removeClass: function(value) {
		if ( !this.className || this.className.trim().length == 0 ) {
			return
		}

		var classes_to_remove = {}
		var value_list = value.split(regex.whitespace)
		for ( var i = 0, l = value_list.length; i < l; i++ ) {
			classes_to_remove[value_list[i]] = true
		}

		var class_list = this.className.split(regex.whitespace)
		for ( var i = class_list.length-1; i >= 0; i-- ) {
			if ( class_list[i] in classes_to_remove ) {
				class_list.splice(i, 1)
			}
		}

		this.className = class_list.join(" ")
	},
	setAttributes: function(value) {
		for ( var key in value ) {
			this.setAttribute(key, value[key])
		}
	},
	setStyle: function() {
		if ( arguments.length >= 2 ) {
			var val = arguments[1]
			if ( typeof(val) === "number" ) val = val+"px"
			this.style[arguments[0]] = val
		} else if ( arguments.length === 1 ) {
			var value = arguments[0]
			if ( typeof(value) === "string" ) {
				this.setAttribute("style", value)
			} else {
				for ( var key in value ) {
					var val = value[key]
					if ( typeof(val) === "number" ) val = val+"px"
					this.style[key] = val
				}
			}
		}
	}
})

// -- Node -------------------------------------------------------------------------------------------------------------

enhancePrototype("Node", {
	appendTo: function(parent) {
		if ( !parent ) return
		parent.appendChild(this)
	},
	insertAbove: function(ref) {
		if ( !ref || !ref.parentNode ) return
		ref.parentNode.insertBefore(this, ref)
	},
	insertBelow: function(ref) {
		if ( !ref || !ref.parentNode ) return
		var nextSibling = ref.nextSibling
		if ( nextSibling ) {
			ref.parentNode.insertBefore(this, nextSibling)
		} else {
			ref.parentNode.appendChild(this)
		}
	},
	prependTo: function(parent) {
		if ( !parent ) return
		var child = parent.firstChild
		if ( child ) {
			parent.insertBefore(this, child)
		} else {
			parent.appendChild(this)
		}
	}
})

// -- NodeList ---------------------------------------------------------------------------------------------------------

enhancePrototype("NodeList", {
	forEach: Array.prototype.forEach,
	reverseForEach: Array.prototype.reverseForEach
})

// -- RegExp -----------------------------------------------------------------------------------------------------------

enhancePrototype("RegExp", {
	match: function(string, index) {
		var matches = this.exec(string)
		if ( matches && matches.length >= index ) {
			return matches[index]
		}
		return null
	},
	matches: function(string, indexes) {
		if ( !indexes || indexes.length === 0 ) return null
		var matches = this.exec(string)
		if ( matches && matches.length > 0 ) {
			var results = []
			for ( var i = 0, l = indexes.length; i < l; i++ ) {
				var num = indexes[i]
				results.push(matches[num] || null)
			}
			return results
		}
		return null
	}
})

// -- String -----------------------------------------------------------------------------------------------------------

enhancePrototype("String", {
	format: function(args) {
		// If an array or table is passed in, use it; otherwise, turn the arguments list into an array
		args = typeof(args) === 'object' ? args : Array.prototype.slice.call(arguments)
		// Replace each instance of {x}, where x is an array index or table key, with the value from args.
		return this.replace(/{([^}]+)}/g, function(match, key) {
			return key in args ? args[key] : "{"+key+"}"
		})
	}
})

})()
<|endoftext|>


<commit_before>/*
    Framework for building object relational database apps
    Copyright (C) 2021  John Rogelstad

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
/*jslint browser*/
/*global f*/
/**
    @module Core
*/

function form(data, feather) {
    let model;
    let props;

    function properties() {
        if (!props) {
            let keys;
            let formFeather = model.data.feather();
            let result = [];

            if (!formFeather) {
                return result;
            }
            formFeather = f.catalog().getFeather(formFeather);
            keys = Object.keys(formFeather.properties || []).sort();
            keys.unshift("");
            props = keys.map(function (key) {
                return {
                    value: key,
                    label: key
                };
            });
        }
        return props;
    }

    function handleProperties() {
        props = undefined;
    }

    feather = feather || f.catalog().getFeather("Form");
    model = f.createModel(data, feather);

    /**
        Feathers datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.feathers
        @for Models.Form
        @type Property
    */
    model.addCalculated({
        name: "feathers",
        type: "array",
        function: f.feathers
    });

    /**
        Modules datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.modules
        @for Models.Form
        @type Property
    */
    model.addCalculated({
        name: "modules",
        type: "array",
        function: f.catalog().store().data().modules
    });

    /**
        Feather properties datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.properties
        @for Models.Form
        @type Property
    */
    model.addCalculated({
        name: "properties",
        type: "array",
        function: properties
    });

    model.onChanged("feather", handleProperties);
    model.onLoad(handleProperties);
    model.onValidate(function () {
        if (model.data.isDefault() && !model.data.isActive()) {
            throw "Default form must be active";
        }
    });

    return model;
}

f.catalog().registerModel("Form", form);

function formAttr(data, feather) {
    let model;

    function handleProp(name, validator) {
        let attr = model.data.attr;
        let formFeather = model.parent().data.feather();
        let prop = model.data[name];
        let fprop;
        let readOnly;

        if (!formFeather || !attr()) {
            prop.isReadOnly(true);
            return;
        }

        formFeather = f.catalog().getFeather(formFeather);
        if (formFeather) {
            fprop = formFeather.properties[attr()];

            readOnly = validator(fprop);
            prop.isReadOnly(readOnly);
        }

        return readOnly;
    }

    function handleColumns() {
        let columns = model.data.columns();

        function validator(fprop) {
            return Boolean(
                !fprop || typeof fprop.type !== "object" ||
                !fprop.type.parentOf
            );
        }

        if (handleProp("columns", validator)) {
            columns.canAdd(false);
        } else {
            columns.canAdd(true);
        }
    }

    function handleDataList() {
        function validator(fprop) {
            return Boolean(
                !fprop || typeof fprop.type === "object" ||
                fprop.type === "boolean"
            );
        }

        handleProp("dataList", validator);
    }

    function handleDisableCurrency() {
        function validator(fprop) {
            return Boolean(
                !fprop || fprop.type !== "object" ||
                fprop.format !== "money"
            );
        }

        handleProp("disableCurrency", validator);
    }

    function handleRelationWidget() {
        function validator(fprop) {
            return Boolean(
                !fprop || typeof fprop.type !== "object" ||
                fprop.type.parentOf
            );
        }

        handleProp("relationWidget", validator);
    }

    function properties() {
        let keys;
        let formFeather = model.parent().data.feather();
        let result = [];

        if (!formFeather) {
            return result;
        }
        formFeather = f.catalog().getFeather(formFeather);
        keys = Object.keys(formFeather.properties || []).sort();
        result = keys.map(function (key) {
            return {
                value: key,
                label: key
            };
        });
        result.unshift({
            value: "",
            label: ""
        });

        return result;
    }

    feather = feather || f.catalog().getFeather("FormAttr");
    model = f.createModel(data, feather);

    /**
        Feather properties datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.properties
        @for Models.FormAttr
        @type Property
    */
    model.addCalculated({
        name: "properties",
        type: "array",
        function: properties
    });

    model.onChanged("attr", handleColumns);
    model.onChanged("attr", handleDataList);
    model.onChanged("attr", handleDisableCurrency);
    model.onChanged("attr", handleRelationWidget);
    model.onLoad(handleColumns);
    model.onLoad(handleDataList);
    model.onLoad(handleDisableCurrency);
    model.onLoad(handleRelationWidget);

    model.data.columns().canAdd(false);

    model.onValidate(function () {
        let found = model.parent().data.properties().find(
            (p) => model.data.attr() === p.value
        );

        if (!found) {
            throw (
                "Attribute '" + model.data.attr() + "' not in feather '" +
                model.parent().data.feather() + "'"
            );
        }
    });

    return model;
}

f.catalog().registerModel("FormAttr", formAttr);

function formAttrColumn(data, feather) {
    let model;
    let stateClean;

    function getChildFeather() {
        let formFeather = model.parent().parent().data.feather();
        let parentAttr = model.parent().data.attr();
        let childFeather;

        if (!formFeather || !parentAttr) {
            return;
        }

        formFeather = f.catalog().getFeather(formFeather);
        childFeather = f.catalog().getFeather(
            formFeather.properties[parentAttr].type.relation
        );

        return childFeather;
    }

    function resolveProperties(feather, properties, ary, prefix) {
        prefix = prefix || "";
        let result = ary || [];

        properties.forEach(function (key) {
            let rfeather;
            let prop = feather.properties[key];
            let isObject = typeof prop.type === "object";
            let path = prefix + key;

            if (isObject && prop.type.properties) {
                rfeather = f.catalog().getFeather(prop.type.relation);
                resolveProperties(
                    rfeather,
                    prop.type.properties,
                    result,
                    path + "."
                );
            }

            if (
                isObject && (
                    prop.type.childOf ||
                    prop.type.parentOf ||
                    prop.type.isChild
                )
            ) {
                return;
            }

            result.push(path);
        });

        return result;
    }

    function handleProp(name, validator) {
        let attr = model.data.attr;
        let prop = model.data[name];
        let childFeather = getChildFeather();
        let fprop;
        let readOnly;

        if (!attr() || !childFeather) {
            prop.isReadOnly(true);
            return;
        }

        fprop = childFeather.properties[attr()];

        readOnly = validator(fprop);
        prop.isReadOnly(readOnly);
    }

    function handleDataList() {
        function validator(fprop) {
            return Boolean(
                !fprop || typeof fprop.type === "object" ||
                fprop.type === "boolean"
            );
        }

        handleProp("dataList", validator);
    }

    function handleShowCurrency() {
        function validator(fprop) {
            return Boolean(
                !fprop || fprop.type !== "object" ||
                fprop.format !== "money"
            );
        }

        handleProp("showCurrency", validator);
    }

    function properties() {
        let keys;
        let childFeather = getChildFeather();
        let result = [];

        if (!childFeather) {
            return result;
        }

        keys = Object.keys(childFeather.properties || []).sort();
        keys = resolveProperties(childFeather, keys).sort();
        return keys.map(function (key) {
            return {
                value: key,
                label: key
            };
        });
    }

    feather = feather || f.catalog().getFeather("FormAttrColumn");
    model = f.createModel(data, feather);

    /**
        Feather properties datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.properties
        @for Models.FormAttrColumn
        @type Property
    */
    model.addCalculated({
        name: "properties",
        type: "array",
        function: properties
    });

    model.onChanged("attr", handleDataList);
    model.onChanged("attr", handleShowCurrency);
    stateClean = model.state().resolve("/Ready/Fetched/Clean");
    stateClean.enter(handleDataList);
    stateClean.enter(handleShowCurrency);

    model.onValidate(function () {
        let childFeather;

        if (!model.data.properties().some(
            (p) => model.data.attr() === p.value
        )) {
            childFeather = getChildFeather();
            if (childFeather) {
                throw (
                    "Attribute '" + model.data.attr() + "' not in feather '" +
                    getChildFeather().name + "'"
                );
            } else {
                throw "Feather must be selected to set attributes";
            }
        }
    });

    return model;
}

f.catalog().registerModel("FormAttrColumn", formAttrColumn);
<commit_msg>Update form.js<commit_after>/*
    Framework for building object relational database apps
    Copyright (C) 2021  John Rogelstad

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
/*jslint browser*/
/*global f*/
/**
    @module Core
*/

function form(data, feather) {
    let model;
    let props;

    function properties() {
        if (!props) {
            let keys;
            let formFeather = model.data.feather();
            let result = [];

            if (!formFeather) {
                return result;
            }
            formFeather = f.catalog().getFeather(formFeather);
            keys = Object.keys(formFeather.properties || []).sort();
            keys.unshift("");
            props = keys.map(function (key) {
                return {
                    value: key,
                    label: key
                };
            });
        }
        return props;
    }

    function handleProperties() {
        props = undefined;
    }

    feather = feather || f.catalog().getFeather("Form");
    model = f.createModel(data, feather);

    /**
        Feathers datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.feathers
        @for Models.Form
        @type Property
    */
    model.addCalculated({
        name: "feathers",
        type: "array",
        function: f.feathers
    });

    /**
        Modules datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.modules
        @for Models.Form
        @type Property
    */
    model.addCalculated({
        name: "modules",
        type: "array",
        function: f.catalog().store().data().modules
    });

    /**
        Feather properties datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.properties
        @for Models.Form
        @type Property
    */
    model.addCalculated({
        name: "properties",
        type: "array",
        function: properties
    });

    model.onChanged("feather", handleProperties);
    model.onLoad(handleProperties);
    model.onValidate(function () {
        if (model.data.isDefault() && !model.data.isActive()) {
            throw "Default form must be active";
        }
    });

    return model;
}

f.catalog().registerModel("Form", form);

function formAttr(data, feather) {
    let model;

    function handleProp(name, validator) {
        let attr = model.data.attr;
        let formFeather = model.parent().data.feather();
        let prop = model.data[name];
        let fprop;
        let readOnly;

        if (!formFeather || !attr()) {
            prop.isReadOnly(true);
            return;
        }

        formFeather = f.catalog().getFeather(formFeather);
        if (formFeather) {
            fprop = formFeather.properties[attr()];

            readOnly = validator(fprop);
            prop.isReadOnly(readOnly);
        }

        return readOnly;
    }

    function handleColumns() {
        let columns = model.data.columns();

        function validator(fprop) {
            return Boolean(
                !fprop || typeof fprop.type !== "object" ||
                !fprop.type.parentOf
            );
        }

        if (handleProp("columns", validator)) {
            columns.canAdd(false);
        } else {
            columns.canAdd(true);
        }
    }

    function handleDataList() {
        function validator(fprop) {
            return Boolean(
                !fprop || typeof fprop.type === "object" ||
                fprop.type === "boolean"
            );
        }

        handleProp("dataList", validator);
    }

    function handleDisableCurrency() {
        function validator(fprop) {
            return Boolean(
                !fprop || fprop.type !== "object" ||
                fprop.format !== "money"
            );
        }

        handleProp("disableCurrency", validator);
    }

    function handleRelationWidget() {
        function validator(fprop) {
            return Boolean(
                !fprop || typeof fprop.type !== "object" ||
                fprop.type.parentOf
            );
        }

        handleProp("relationWidget", validator);
    }

    function properties() {
        let keys;
        let formFeather = model.parent().data.feather();
        let result = [];

        if (!formFeather) {
            return result;
        }
        formFeather = f.catalog().getFeather(formFeather);
        keys = Object.keys(formFeather.properties || []).sort();
        result = keys.map(function (key) {
            return {
                value: key,
                label: key
            };
        });
        result.unshift({
            value: "",
            label: ""
        });

        return result;
    }

    feather = feather || f.catalog().getFeather("FormAttr");
    model = f.createModel(data, feather);

    /**
        Feather properties datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.properties
        @for Models.FormAttr
        @type Property
    */
    model.addCalculated({
        name: "properties",
        type: "array",
        function: properties
    });

    model.onChanged("attr", handleColumns);
    model.onChanged("attr", handleDataList);
    model.onChanged("attr", handleDisableCurrency);
    model.onChanged("attr", handleRelationWidget);
    model.onLoad(handleColumns);
    model.onLoad(handleDataList);
    model.onLoad(handleDisableCurrency);
    model.onLoad(handleRelationWidget);

    model.data.columns().canAdd(false);

    model.onValidate(function () {
        let found = model.parent().data.properties().find(
            (p) => model.data.attr() === p.value
        );

        if (!found) {
            throw (
                "Attribute '" + model.data.attr() + "' not in feather '" +
                model.parent().data.feather() + "'"
            );
        }
    });

    return model;
}

f.catalog().registerModel("FormAttr", formAttr);

function formAttrColumn(data, feather) {
    let model;
    let stateClean;

    function getChildFeather() {
        let formFeather = model.parent().parent().data.feather();
        let parentAttr = model.parent().data.attr();
        let childFeather;

        if (!formFeather || !parentAttr) {
            return;
        }

        formFeather = f.catalog().getFeather(formFeather);
        if (formFeather &&
            formFeather.properties[parentAttr]
        ) {
            childFeather = f.catalog().getFeather(
                formFeather.properties[parentAttr].type.relation
            );
        }

        return childFeather;
    }

    function resolveProperties(feather, properties, ary, prefix) {
        prefix = prefix || "";
        let result = ary || [];

        properties.forEach(function (key) {
            let rfeather;
            let prop = feather.properties[key];
            let isObject = typeof prop.type === "object";
            let path = prefix + key;

            if (isObject && prop.type.properties) {
                rfeather = f.catalog().getFeather(prop.type.relation);
                resolveProperties(
                    rfeather,
                    prop.type.properties,
                    result,
                    path + "."
                );
            }

            if (
                isObject && (
                    prop.type.childOf ||
                    prop.type.parentOf ||
                    prop.type.isChild
                )
            ) {
                return;
            }

            result.push(path);
        });

        return result;
    }

    function handleProp(name, validator) {
        let attr = model.data.attr;
        let prop = model.data[name];
        let childFeather = getChildFeather();
        let fprop;
        let readOnly;

        if (!attr() || !childFeather) {
            prop.isReadOnly(true);
            return;
        }

        fprop = childFeather.properties[attr()];

        readOnly = validator(fprop);
        prop.isReadOnly(readOnly);
    }

    function handleDataList() {
        function validator(fprop) {
            return Boolean(
                !fprop || typeof fprop.type === "object" ||
                fprop.type === "boolean"
            );
        }

        handleProp("dataList", validator);
    }

    function handleShowCurrency() {
        function validator(fprop) {
            return Boolean(
                !fprop || fprop.type !== "object" ||
                fprop.format !== "money"
            );
        }

        handleProp("showCurrency", validator);
    }

    function properties() {
        let keys;
        let childFeather = getChildFeather();
        let result = [];

        if (!childFeather) {
            return result;
        }

        keys = Object.keys(childFeather.properties || []).sort();
        keys = resolveProperties(childFeather, keys).sort();
        return keys.map(function (key) {
            return {
                value: key,
                label: key
            };
        });
    }

    feather = feather || f.catalog().getFeather("FormAttrColumn");
    model = f.createModel(data, feather);

    /**
        Feather properties datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.properties
        @for Models.FormAttrColumn
        @type Property
    */
    model.addCalculated({
        name: "properties",
        type: "array",
        function: properties
    });

    model.onChanged("attr", handleDataList);
    model.onChanged("attr", handleShowCurrency);
    stateClean = model.state().resolve("/Ready/Fetched/Clean");
    stateClean.enter(handleDataList);
    stateClean.enter(handleShowCurrency);

    model.onValidate(function () {
        let childFeather;

        if (!model.data.properties().some(
            (p) => model.data.attr() === p.value
        )) {
            childFeather = getChildFeather();
            if (childFeather) {
                throw (
                    "Attribute '" + model.data.attr() + "' not in feather '" +
                    getChildFeather().name + "'"
                );
            } else {
                throw "Feather must be selected to set attributes";
            }
        }
    });

    return model;
}

f.catalog().registerModel("FormAttrColumn", formAttrColumn);
<|endoftext|>


<commit_before>var pinterestrc = (pinterestrc) ? pinterestrc : {};

pinterestrc.MenuController = (function() {
  /**
   * Keeps track of displayed context menu items and their event handlers so
   * that they can be cleaned up when the context menu gets hidden.
   */
  let activeMenuHandlers = [];

  /**
   * Show and create handler for a pinning option represented by aMenuItem
   */
  let addMenuItem = function addMenuItem(aMenuItem, aDict) {
    let targetSrc = aDict.media;
    // TODO: Investigate whether we can get something for non-http
    if (!/^https?:\/\//.test(targetSrc)) {
      return false;
    }

    aMenuItem.hidden = false;
    let menuItemListener = function() {
      pinterestrc.PinterestContext.pinTarget(aDict);
    };
    aMenuItem.addEventListener("command", menuItemListener);

    activeMenuHandlers.push({
      item : aMenuItem,
      listener : menuItemListener
    });
    aMenuItem = null;
    return true;
  };

  /**
   * Hides pinning options by default and clean up listeners when popup closes
   */
  let unloadMenuItems = function unloadMenuItems() {
    for (let i = 0, len = activeMenuHandlers.length; i < len; i++) {
      let menuHandler = activeMenuHandlers[i];
      menuHandler.item.hidden = true;
      menuHandler.item.removeEventListener("command", menuHandler.listener);
    }
    activeMenuHandlers = [];
  };

  return {
    addMenuItem : addMenuItem,
    unloadMenuItems : unloadMenuItems
  };
})();

pinterestrc.PinterestContext = {
  /**
   * Handles the pinning action, fetching parameters from target information
   */
  pinTarget : function pinTarget(aDict) {
    pinterestrc.ThumbnailRewrite.rewriteParams(aDict);

    let createURI = makeURI("http://pinterest.com/pin/create/bookmarklet/")
      .QueryInterface(Ci.nsIURL);

    let pinParams = {};

    pinParams.media = encodeURIComponent(aDict.media);

    if (aDict.url !== undefined && aDict.url) {
      pinParams.url = encodeURIComponent(aDict.url);
    } else {
      pinParams.url = encodeURIComponent(window.content.location.href);
    }

    // Set the alt test of the pin.
    if (aDict.alt !== undefined && aDict.alt) {
      pinParams.alt = encodeURIComponent(aDict.alt);
    }

    // Not sure what Pinterest uses the title for, but let's give it to them
    let pageTitle = window.content.document.title;
    if (pageTitle) {
      pinParams.title = encodeURIComponent(pageTitle);
    }

    pinParams.is_video = (aDict.is_video !== undefined && aDict.is_video);

    // Convert params to query string and append it to create uri
    let paramList = [];
    for (let key in pinParams) {
      paramList.push(key + "=" + pinParams[key]);
    }
    let queryString = "?" + paramList.join("&");
    createURI.query = queryString;

    this.displayDialog(createURI.resolve(""));
  },

  /**
   * Displays our custom-built 'add a pin' dialog
   */
  displayDialog : function displayDialog(createPath) {
    let doc = window.content.document;

    // Inject necessary CSS to show dialog. If the user has already pinned one
    // image from the page we can leave the injected CSS lying around since it
    // is unreferenced. If the user ends up pinning another image from the same
    // page, we then don't have to reload the CSS.
    let dialogStyle = doc.getElementById("pinterest-context-dialog-style");
    if (dialogStyle == null) {
      dialogStyle = doc.createElement("link");
      dialogStyle.setAttribute("rel", "stylesheet");
      dialogStyle.setAttribute("href", "resource://pinterest-context/dialog.css");
      doc.head.appendChild(dialogStyle);
    }

    // Create translucent overlay over page
    let dialogBackdrop = doc.createElement("div");
    dialogBackdrop.className = "pinterest-context-backdrop";
    doc.body.appendChild(dialogBackdrop);

    // Create dialog container
    let dialogBox = doc.createElement("div");
    dialogBox.className = "pinterest-context-dialog";

    // Handle closing the dialog
    function escapeHandler(aEvent) {
      if (aEvent.keyCode == 27) { // escape key-code
        closeDialog();
      }
    };
    doc.addEventListener("keypress", escapeHandler);

    function closeDialog() {
      doc.body.removeChild(dialogBackdrop);
      doc.body.removeChild(dialogBox);
      doc.removeEventListener("keypress", escapeHandler);
      // We don't need to remove listeners attached to our newly created DOM
      // elements since we're destroying the elements.
    };

    // Clicking outside the dialog closes the dialog
    dialogBackdrop.addEventListener("click", closeDialog);

    // Create header {
    let dialogHeader = doc.createElement("div");
    dialogHeader.className = "pinterest-context-dialog-header";

      // Header text {
      let dialogHeaderText = doc.createElement("h2");
      // TODO: Localization Evil!
      dialogHeaderText.textContent = "Add a Pin";
      dialogHeaderText.className = "pinterest-context-dialog-header-text";
      //}

      // Close button {
      let dialogClose = doc.createElement("a");
      dialogClose.className = "pinterest-context-dialog-close";
      // CSS relies on having a nested span, see dialog.css
      dialogClose.appendChild(doc.createElement("span"));
      dialogClose.addEventListener("click", closeDialog);
      //}

    dialogHeader.appendChild(dialogHeaderText);
    dialogHeader.appendChild(dialogClose);
    //}

    // Use iframe to reach Pinterest
    let dialogFrame = doc.createElement("iframe");
    dialogFrame.setAttribute("class", "pinterest-context-dialog-frame");
    dialogFrame.setAttribute("src", createPath);

    dialogBox.appendChild(dialogHeader);
    dialogBox.appendChild(dialogFrame);
    doc.body.appendChild(dialogBox);
  },

  /**
   * Returns the lowest (DOM-wise) background image for the given target
   *
   * @returns a URL string or null
   */
  findBackgroundImage : function findBackgroundImage(aTarget) {
    // Bubble up DOM tree looking for a background image
    while (aTarget) {
      if (aTarget.nodeType == Node.ELEMENT_NODE) {
        // Check to find each successive ancestor for a background image
        let bgImageURL = aTarget.ownerDocument.defaultView
          .getComputedStyle(aTarget, "").getPropertyCSSValue("background-image");

        // Computed background may yield any number of URIs. If we've found 1,
        // then we are successful. If we find multiple, we are unable to decide
        // and we'll give up. If we find none, keep looking.
        let numBgFound = 0;
        if (bgImageURL instanceof CSSPrimitiveValue &&
            bgImageURL.primitiveType == CSSPrimitiveValue.CSS_URI) {
          numBgFound = 1;
        } else if (bgImageURL instanceof CSSValueList) {
          numBgFound = bgImageURL.length;
          if (bgImageURL.length == 1) {
            bgImageURL = bgImageURL[0];
            if (bgImageURL.primitiveType != CSSPrimitiveValue.CSS_URI) {
              numBgFound = 0;
            }
          }
        }

        if (numBgFound > 1) {
          return null;
        } else if (numBgFound == 1) {
          bgImageURL = bgImageURL.getStringValue();
          return bgImageURL;
        }
      }

      aTarget = aTarget.parentNode;
    }

    // Nothing found
    return null;
  }
};

/**
 * Initializes required event handlers
 */
window.addEventListener("load", function() {
  /**
   * Configures the context menu to show pinning options when it makes sense
   */
  function onPopupShowing(aEvent) {
    let target = document.popupNode;
    let currentLocation = window.content.location;
    pinterestrc.SiteServicesController.handleLocation(currentLocation, target);
  }

  // Avoid circular-reference created by closure
  (function() {
    let menu = document.getElementById("contentAreaContextMenu");
    menu.addEventListener("popupshowing", onPopupShowing, false);
    menu.addEventListener(
      "popuphiding", pinterestrc.MenuController.unloadMenuItems, false);
  })();
}, false);
<commit_msg>Add UI Cleanup TODO<commit_after>var pinterestrc = (pinterestrc) ? pinterestrc : {};

pinterestrc.MenuController = (function() {
  /**
   * Keeps track of displayed context menu items and their event handlers so
   * that they can be cleaned up when the context menu gets hidden.
   */
  let activeMenuHandlers = [];

  /**
   * Show and create handler for a pinning option represented by aMenuItem
   */
  let addMenuItem = function addMenuItem(aMenuItem, aDict) {
    let targetSrc = aDict.media;
    // TODO: Investigate whether we can get something for non-http
    if (!/^https?:\/\//.test(targetSrc)) {
      return false;
    }

    aMenuItem.hidden = false;
    let menuItemListener = function() {
      pinterestrc.PinterestContext.pinTarget(aDict);
    };
    aMenuItem.addEventListener("command", menuItemListener);

    activeMenuHandlers.push({
      item : aMenuItem,
      listener : menuItemListener
    });
    aMenuItem = null;
    return true;
  };

  /**
   * Hides pinning options by default and clean up listeners when popup closes
   */
  let unloadMenuItems = function unloadMenuItems() {
    for (let i = 0, len = activeMenuHandlers.length; i < len; i++) {
      let menuHandler = activeMenuHandlers[i];
      menuHandler.item.hidden = true;
      menuHandler.item.removeEventListener("command", menuHandler.listener);
    }
    activeMenuHandlers = [];
  };

  return {
    addMenuItem : addMenuItem,
    unloadMenuItems : unloadMenuItems
  };
})();

pinterestrc.PinterestContext = {
  /**
   * Handles the pinning action, fetching parameters from target information
   */
  pinTarget : function pinTarget(aDict) {
    pinterestrc.ThumbnailRewrite.rewriteParams(aDict);

    let createURI = makeURI("http://pinterest.com/pin/create/bookmarklet/")
      .QueryInterface(Ci.nsIURL);

    let pinParams = {};

    pinParams.media = encodeURIComponent(aDict.media);

    if (aDict.url !== undefined && aDict.url) {
      pinParams.url = encodeURIComponent(aDict.url);
    } else {
      pinParams.url = encodeURIComponent(window.content.location.href);
    }

    // Set the alt test of the pin.
    if (aDict.alt !== undefined && aDict.alt) {
      pinParams.alt = encodeURIComponent(aDict.alt);
    }

    // Not sure what Pinterest uses the title for, but let's give it to them
    let pageTitle = window.content.document.title;
    if (pageTitle) {
      pinParams.title = encodeURIComponent(pageTitle);
    }

    pinParams.is_video = (aDict.is_video !== undefined && aDict.is_video);

    // Convert params to query string and append it to create uri
    let paramList = [];
    for (let key in pinParams) {
      paramList.push(key + "=" + pinParams[key]);
    }
    let queryString = "?" + paramList.join("&");
    createURI.query = queryString;

    this.displayDialog(createURI.resolve(""));
  },

  /**
   * Displays our custom-built 'add a pin' dialog
   *
   * TODO: This is gross unmaintainable Java-esque UI code. Although slower, we
   * should load the DOM structure stuff from an HTML fragment.
   */
  displayDialog : function displayDialog(createPath) {
    let doc = window.content.document;

    // Inject necessary CSS to show dialog. If the user has already pinned one
    // image from the page we can leave the injected CSS lying around since it
    // is unreferenced. If the user ends up pinning another image from the same
    // page, we then don't have to reload the CSS.
    let dialogStyle = doc.getElementById("pinterest-context-dialog-style");
    if (dialogStyle == null) {
      dialogStyle = doc.createElement("link");
      dialogStyle.setAttribute("rel", "stylesheet");
      dialogStyle.setAttribute("href", "resource://pinterest-context/dialog.css");
      doc.head.appendChild(dialogStyle);
    }

    // Create translucent overlay over page
    let dialogBackdrop = doc.createElement("div");
    dialogBackdrop.className = "pinterest-context-backdrop";
    doc.body.appendChild(dialogBackdrop);

    // Create dialog container
    let dialogBox = doc.createElement("div");
    dialogBox.className = "pinterest-context-dialog";

    // Handle closing the dialog
    function escapeHandler(aEvent) {
      if (aEvent.keyCode == 27) { // escape key-code
        closeDialog();
      }
    };
    doc.addEventListener("keypress", escapeHandler);

    function closeDialog() {
      doc.body.removeChild(dialogBackdrop);
      doc.body.removeChild(dialogBox);
      doc.removeEventListener("keypress", escapeHandler);
      // We don't need to remove listeners attached to our newly created DOM
      // elements since we're destroying the elements.
    };

    // Clicking outside the dialog closes the dialog
    dialogBackdrop.addEventListener("click", closeDialog);

    // Create header {
    let dialogHeader = doc.createElement("div");
    dialogHeader.className = "pinterest-context-dialog-header";

      // Header text {
      let dialogHeaderText = doc.createElement("h2");
      // TODO: Localization Evil!
      dialogHeaderText.textContent = "Add a Pin";
      dialogHeaderText.className = "pinterest-context-dialog-header-text";
      //}

      // Close button {
      let dialogClose = doc.createElement("a");
      dialogClose.className = "pinterest-context-dialog-close";
      // CSS relies on having a nested span, see dialog.css
      dialogClose.appendChild(doc.createElement("span"));
      dialogClose.addEventListener("click", closeDialog);
      //}

    dialogHeader.appendChild(dialogHeaderText);
    dialogHeader.appendChild(dialogClose);
    //}

    // Use iframe to reach Pinterest
    let dialogFrame = doc.createElement("iframe");
    dialogFrame.setAttribute("class", "pinterest-context-dialog-frame");
    dialogFrame.setAttribute("src", createPath);

    dialogBox.appendChild(dialogHeader);
    dialogBox.appendChild(dialogFrame);
    doc.body.appendChild(dialogBox);
  },

  /**
   * Returns the lowest (DOM-wise) background image for the given target
   *
   * @returns a URL string or null
   */
  findBackgroundImage : function findBackgroundImage(aTarget) {
    // Bubble up DOM tree looking for a background image
    while (aTarget) {
      if (aTarget.nodeType == Node.ELEMENT_NODE) {
        // Check to find each successive ancestor for a background image
        let bgImageURL = aTarget.ownerDocument.defaultView
          .getComputedStyle(aTarget, "").getPropertyCSSValue("background-image");

        // Computed background may yield any number of URIs. If we've found 1,
        // then we are successful. If we find multiple, we are unable to decide
        // and we'll give up. If we find none, keep looking.
        let numBgFound = 0;
        if (bgImageURL instanceof CSSPrimitiveValue &&
            bgImageURL.primitiveType == CSSPrimitiveValue.CSS_URI) {
          numBgFound = 1;
        } else if (bgImageURL instanceof CSSValueList) {
          numBgFound = bgImageURL.length;
          if (bgImageURL.length == 1) {
            bgImageURL = bgImageURL[0];
            if (bgImageURL.primitiveType != CSSPrimitiveValue.CSS_URI) {
              numBgFound = 0;
            }
          }
        }

        if (numBgFound > 1) {
          return null;
        } else if (numBgFound == 1) {
          bgImageURL = bgImageURL.getStringValue();
          return bgImageURL;
        }
      }

      aTarget = aTarget.parentNode;
    }

    // Nothing found
    return null;
  }
};

/**
 * Initializes required event handlers
 */
window.addEventListener("load", function() {
  /**
   * Configures the context menu to show pinning options when it makes sense
   */
  function onPopupShowing(aEvent) {
    let target = document.popupNode;
    let currentLocation = window.content.location;
    pinterestrc.SiteServicesController.handleLocation(currentLocation, target);
  }

  // Avoid circular-reference created by closure
  (function() {
    let menu = document.getElementById("contentAreaContextMenu");
    menu.addEventListener("popupshowing", onPopupShowing, false);
    menu.addEventListener(
      "popuphiding", pinterestrc.MenuController.unloadMenuItems, false);
  })();
}, false);
<|endoftext|>


<commit_before>
import "regenerator/runtime"
import { take, put, cps } from "redux-saga/effects"
import { addSaga } from "../utilities"

addSaga(function* share(getStore) {

  while (true) {
    const { payload } = yield take("SHARE.SHARE")

    let { share } = getStore()
    let msg = {}

    for (let key in share.content) {
      if (share.content[key] != null ) {
        msg[key] = share.content[key]
      }

    }

    if (
      typeof window != "undefined" &&
      window != null &&
      window.socialmessage &&
      Object.keys(msg).length
    ) {

      if (msg.image && msg.image[0] === "/") {
        msg.image = "http:" + msg.image
      }

      if (msg.url) {
        msg.text = `${msg.text}\n${msg.url}`
      }

      window.socialmessage.send(msg)
    }

    yield put({ type: "SHARE.SHARE" })

  }

})
<commit_msg>no need to append url to sharing message text because plugin does this<commit_after>
import "regenerator/runtime"
import { take, put, cps } from "redux-saga/effects"
import { addSaga } from "../utilities"

addSaga(function* share(getStore) {

  while (true) {
    const { payload } = yield take("SHARE.SHARE")

    let { share } = getStore()
    let msg = {}

    for (let key in share.content) {
      if (share.content[key] != null ) {
        msg[key] = share.content[key]
      }

    }

    if (
      typeof window != "undefined" &&
      window != null &&
      window.socialmessage &&
      Object.keys(msg).length
    ) {

      if (msg.image && msg.image[0] === "/") {
        msg.image = "http:" + msg.image
      }

      window.socialmessage.send(msg)
    }

    yield put({ type: "SHARE.SHARE" })

  }

})
<|endoftext|>


<commit_before>export default function(state = null, action) {
	return state;
}<commit_msg>Add request payload data to state<commit_after>import { FETCH_WEATHER } from "../actions/index";

export default function(state = [], action) {
	switch (action.type) {
		case FETCH_WEATHER:
			// return state.concat([ action.payload.data ]);
			return [ action.payload.data, ...state ]; // insert array at the top [ city, city, city, ...]
	}
	return state;
}<|endoftext|>


<commit_before>(function() {

  var Entity = function() {
    var x;
    var y;

    return {
      init: function(x0, y0) {
        setCoor(x, y);
      },

      setCoor: function(x0, y0) {
        x = x0;
        y = y0;
      },

      getCoor: function() {
        return {
          x: x,
          y: y
        };
      },

      move: function(dx, dy) {
        x += dx;
        y += dy;
      }
    };
  };

  var KillableEntity = function() {
    var hitPoints;

    var KillableEntity = Object.create(Entity());

    KillableEntity.init = function(x0, y0, hp0) {
      this.setCoor(x0, y0);
      this.setHealth(hp0);
    };

    KillableEntity.setHealth = function(hp) {
      hitPoints = hp;
    };

    KillableEntity.getHealth = function() {
      return hitPoints;
    };

    return KillableEntity;
  };

  var Monster = function() {
    var attack;
    var defense;

    var Monster = Object.create(KillableEntity());

    Monster.init = function(x0, y0, hp0, a0, d0) {
      this.setCoor(x0, y0);
      this.setHealth(hp0);
      this.setAttack(a0);
      this.setDefense(d0);
    };

    Monster.setAttack = function(a0) {
      attack = a0;
    };

    Monster.getAttack = function() {
      return attack;
    };

    Monster.setDefense = function(d0) {
      defense = d0;
    };

    Monster.getDefense = function() {
      return defense;
    };

    Monster.takeDamage = function(damage) {
      var health = this.getHealth();
      damage = Math.max(0, damage - this.getDefense());
      this.setHealth(health - damage);
    };

    return Monster;
  };

  var milo = Object.create(Monster());

  milo.init(2, 5, 30, 4, 6);

  console.log(milo.getCoor());
  console.log(milo.getHealth());
  console.log(milo.getDefense());
  milo.takeDamage(10);
  console.log(milo.getHealth());

})();
<commit_msg>Standardize object 'class' syntax<commit_after>(function() {

  var Entity = function() {
    var x;
    var y;

    var Entity = {};

    Entity.init = function(x0, y0) {
      setCoor(x, y);
    };

    Entity.setCoor = function(x0, y0) {
      x = x0;
      y = y0;
    };

    Entity.getCoor = function() {
      return {
        x: x,
        y: y
      };
    };

    Entity.move = function(dx, dy) {
      x += dx;
      y += dy;
    };

    return Entity;
  };

  var KillableEntity = function() {
    var hitPoints;

    var KillableEntity = Object.create(Entity());

    KillableEntity.init = function(x0, y0, hp0) {
      this.setCoor(x0, y0);
      this.setHealth(hp0);
    };

    KillableEntity.setHealth = function(hp) {
      hitPoints = hp;
    };

    KillableEntity.getHealth = function() {
      return hitPoints;
    };

    return KillableEntity;
  };

  var Monster = function() {
    var attack;
    var defense;

    var Monster = Object.create(KillableEntity());

    Monster.init = function(x0, y0, hp0, a0, d0) {
      this.setCoor(x0, y0);
      this.setHealth(hp0);
      this.setAttack(a0);
      this.setDefense(d0);
    };

    Monster.setAttack = function(a0) {
      attack = a0;
    };

    Monster.getAttack = function() {
      return attack;
    };

    Monster.setDefense = function(d0) {
      defense = d0;
    };

    Monster.getDefense = function() {
      return defense;
    };

    Monster.takeDamage = function(damage) {
      var health = this.getHealth();
      damage = Math.max(0, damage - this.getDefense());
      this.setHealth(health - damage);
    };

    return Monster;
  };

  var milo = Object.create(Monster());

  milo.init(2, 5, 30, 4, 6);

  console.log(milo.getCoor());
  console.log(milo.getHealth());
  console.log(milo.getDefense());
  milo.takeDamage(10);
  console.log(milo.getHealth());
  console.log(milo);

})();
<|endoftext|>


<commit_before>(function(){

    var app = angular.module('routineApp', [
        'ui.router',
        'ngResource',
        'ngMessages',
        'angular-jwt',
        'routineApp.homeControllers',
        'routineApp.routineControllers',
        'routineApp.routineServices',
        'routineApp.userControllers',
        'routineApp.userServices',
        'routineApp.directives',
        'routineApp.filters'
    ])

    .config(function($interpolateProvider, $httpProvider, $resourceProvider, $stateProvider, $locationProvider, $urlRouterProvider, jwtInterceptorProvider){

        jwtInterceptorProvider.authPrefix = '';

        jwtInterceptorProvider.tokenGetter = function(jwtHelper, UserService) {

            var token = localStorage.getItem('token');

            if (token && !jwtHelper.isTokenExpired(token)) {

                var tokenJson = (angular.toJson({'token': token}));

                UserService.refreshToken(tokenJson)
                .success(function(response){
                    localStorage.setItem('token', response['token']);
                })
                .error(function(){

                });
            }

            return "JWT " + localStorage.getItem('token');
        }

        $httpProvider.interceptors.push('jwtInterceptor');

        // Routing
        $locationProvider.html5Mode({
          enabled: true,
          requireBase: false
        });

        $stateProvider
            .state('home', {
                url: '/home',
                templateUrl: '/partials/home/home.html',
                controller: 'HomeController as home'
            })
            .state('routines', {
                url: '/',
                templateUrl: '/partials/routines/routines.html',
                controller: 'RoutineListController as list'
            })
            .state('routine_create_routine', {
                url: '/create-routine',
                templateUrl: '/partials/routines/routine_create_routine.html',
                controller: 'RoutineCreateRoutineController as create'
            })
            .state('routine_create_exercises', {
                url: '/create-exercises',
                templateUrl: '/partials/routines/routine_create_exercises.html',
                controller: 'RoutineCreateExercisesController as create'
            })
            .state('routine_edit', {
                url: '/edit/:id',
                templateUrl: '/partials/routines/routine_edit.html',
                controller: 'RoutineEditController as edit'
            })
            .state('routine_use', {
                url: '/use/:id',
                templateUrl: '/partials/routines/routine_use.html',
                controller: 'RoutineUseController as use'
            })
            .state('user_authenticate', {
                url: '/login',
                templateUrl: '/partials/users/user_authenticate.html',
                controller: 'UserLoginController as login'
            })
            .state('user_create', {
                url: '/signup',
                templateUrl: '/partials/users/user_create.html',
                controller: 'UserCreateController as create'
            });

        $urlRouterProvider.otherwise('/');

    })

    .run(function($rootScope, $location, UserService, $state) {

        $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState) {

            if (!UserService.isLoggedIn() && toState.name!="user_authenticate" && toState.name!="user_create" ) {

                $rootScope.returnTo = new Object();

                if (toState.url.indexOf(":") != -1) {
                    $rootScope.returnTo.State = toState.url.split(":")[0];
                } else {
                    $rootScope.returnTo.State = toState.url;
                }
                $rootScope.returnTo.StateParams = toParams.Id;

                $state.go('user_authenticate');
                event.preventDefault();
            }

        });
    });

})();

<commit_msg>Maintainance - Updated whitelist for jwt<commit_after>(function(){

    var app = angular.module('routineApp', [
        'ui.router',
        'ngResource',
        'ngMessages',
        'angular-jwt',
        'routineApp.homeControllers',
        'routineApp.routineControllers',
        'routineApp.routineServices',
        'routineApp.userControllers',
        'routineApp.userServices',
        'routineApp.directives',
        'routineApp.filters'
    ])

    .config(function($interpolateProvider, $httpProvider, $resourceProvider, $stateProvider, $locationProvider,
                     $urlRouterProvider, jwtInterceptorProvider, jwtOptionsProvider) {

        jwtOptionsProvider.config({
            whiteListedDomains: ['http://thunderapi.kayra.co.uk/', 'localhost']
        });

        jwtInterceptorProvider.authPrefix = '';

        jwtInterceptorProvider.tokenGetter = function(jwtHelper, UserService) {

            var token = localStorage.getItem('token');

            if (token && !jwtHelper.isTokenExpired(token)) {

                var tokenJson = (angular.toJson({'token': token}));

                UserService.refreshToken(tokenJson)
                .success(function(response){
                    localStorage.setItem('token', response['token']);
                })
                .error(function(){

                });
            }

            return "JWT " + localStorage.getItem('token');
        }

        $httpProvider.interceptors.push('jwtInterceptor');

        // Routing
        $locationProvider.html5Mode({
          enabled: true,
          requireBase: false
        });

        $stateProvider
            .state('home', {
                url: '/home',
                templateUrl: '/partials/home/home.html',
                controller: 'HomeController as home'
            })
            .state('routines', {
                url: '/',
                templateUrl: '/partials/routines/routines.html',
                controller: 'RoutineListController as list'
            })
            .state('routine_create_routine', {
                url: '/create-routine',
                templateUrl: '/partials/routines/routine_create_routine.html',
                controller: 'RoutineCreateRoutineController as create'
            })
            .state('routine_create_exercises', {
                url: '/create-exercises',
                templateUrl: '/partials/routines/routine_create_exercises.html',
                controller: 'RoutineCreateExercisesController as create'
            })
            .state('routine_edit', {
                url: '/edit/:id',
                templateUrl: '/partials/routines/routine_edit.html',
                controller: 'RoutineEditController as edit'
            })
            .state('routine_use', {
                url: '/use/:id',
                templateUrl: '/partials/routines/routine_use.html',
                controller: 'RoutineUseController as use'
            })
            .state('user_authenticate', {
                url: '/login',
                templateUrl: '/partials/users/user_authenticate.html',
                controller: 'UserLoginController as login'
            })
            .state('user_create', {
                url: '/signup',
                templateUrl: '/partials/users/user_create.html',
                controller: 'UserCreateController as create'
            });

        $urlRouterProvider.otherwise('/');

    })

    .run(function($rootScope, $location, UserService, $state) {

        $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState) {

            if (!UserService.isLoggedIn() && toState.name!="user_authenticate" && toState.name!="user_create" ) {

                $rootScope.returnTo = new Object();

                if (toState.url.indexOf(":") != -1) {
                    $rootScope.returnTo.State = toState.url.split(":")[0];
                } else {
                    $rootScope.returnTo.State = toState.url;
                }
                $rootScope.returnTo.StateParams = toParams.Id;

                $state.go('user_authenticate');
                event.preventDefault();
            }

        });
    });

})();

<|endoftext|>