<commit_before>angular.module('reCAPTCHA', []).provider('reCAPTCHA', function() {
    var _publicKey = null,
        _options = {},
        self = this;

    this.setPublicKey = function(publicKey) {
        _publicKey = publicKey;
    };

    this.setOptions = function(options) {
        _options = options;
    };

    this._createScript = function($document, callback) {
        var scriptTag = $document.createElement('script');
        scriptTag.type = 'text/javascript';
        scriptTag.async = true;
        scriptTag.src = '//www.google.com/recaptcha/api/js/recaptcha_ajax.js';
        scriptTag.onreadystatechange = function() {
            if (this.readyState == 'complete') {
                callback();
            }
        };
        scriptTag.onload = callback;
        var s = $document.getElementsByTagName('body')[0];
        s.appendChild(scriptTag);
    };

    this.$get = ['$q', '$rootScope', '$window', '$document', function($q, $rootScope, $window, $document) {
        var deferred = $q.defer();

        if (!$window.Recaptcha) {
            self._createScript($document[0], deferred.resolve);
        } else {
            deferred.resolve();
        }

        return {
            create: function(element, callback) {
                if (!_publicKey) {
                    throw new Error('Please provide your PublicKey via setPublicKey');
                }
                _options.callback = callback;

                deferred.promise.then(function() {
                    $window.Recaptcha.create(
                        _publicKey,
                        element,
                        _options
                    );
                });
            },
            response: function() {
                return $window.Recaptcha.get_response();
            },
            challenge: function() {
                return $window.Recaptcha.get_challenge();
            },
            reload: function() {
                return $window.Recaptcha.reload();
            },
            destroy: function() {
                $window.Recaptcha.destroy();
            }
        };
    }];

}).directive('reCaptcha', ['reCAPTCHA', '$compile', function(reCAPTCHA, $compile) {
    return {
        restrict: 'A',
        require: 'ngModel',
        scope: {
            ngModel: '='
        },
        link: function(scope, element, attrs, controller) {
            var name = attrs.name || 'reCaptcha';
            scope.clear = function() {
                scope.ngModel = {
                    response: '',
                    challenge: false
                };
            };

            // Create reCAPTCHA
            reCAPTCHA.create(element[0], function() {

                // Reset on Start
                scope.clear();

                // watch if challenge changes
                scope.$watch(function() {
                    return reCAPTCHA.challenge();
                }, function (newValue) {
                    scope.ngModel.challenge = newValue;
                });

                // Attach model and click handler
                $compile(angular.element(document.querySelector('input#recaptcha_response_field')).attr('ng-required', 'true'))(scope);
                $compile(angular.element(document.querySelector('input#recaptcha_response_field')).attr('ng-model', 'ngModel.response'))(scope);
                $compile(angular.element(document.querySelector('a#recaptcha_reload_btn')).attr('ng-click', 'clear()'))(scope);

            });

            // Destroy Element
            scope.$on('$destroy', reCAPTCHA.destroy);
        }
    };
}]);
<commit_msg>Check if .Recaptcha exists before destroying it<commit_after>angular.module('reCAPTCHA', []).provider('reCAPTCHA', function() {
    var _publicKey = null,
        _options = {},
        self = this;

    this.setPublicKey = function(publicKey) {
        _publicKey = publicKey;
    };

    this.setOptions = function(options) {
        _options = options;
    };

    this._createScript = function($document, callback) {
        var scriptTag = $document.createElement('script');
        scriptTag.type = 'text/javascript';
        scriptTag.async = true;
        scriptTag.src = '//www.google.com/recaptcha/api/js/recaptcha_ajax.js';
        scriptTag.onreadystatechange = function() {
            if (this.readyState == 'complete') {
                callback();
            }
        };
        scriptTag.onload = callback;
        var s = $document.getElementsByTagName('body')[0];
        s.appendChild(scriptTag);
    };

    this.$get = ['$q', '$rootScope', '$window', '$document', function($q, $rootScope, $window, $document) {
        var deferred = $q.defer();

        if (!$window.Recaptcha) {
            self._createScript($document[0], deferred.resolve);
        } else {
            deferred.resolve();
        }

        return {
            create: function(element, callback) {
                if (!_publicKey) {
                    throw new Error('Please provide your PublicKey via setPublicKey');
                }
                _options.callback = callback;

                deferred.promise.then(function() {
                    $window.Recaptcha.create(
                        _publicKey,
                        element,
                        _options
                    );
                });
            },
            response: function() {
                return $window.Recaptcha.get_response();
            },
            challenge: function() {
                return $window.Recaptcha.get_challenge();
            },
            reload: function() {
                return $window.Recaptcha.reload();
            },
            destroy: function() {
                if ($window.Recaptcha) {
                    $window.Recaptcha.destroy();
                }
            }
        };
    }];

}).directive('reCaptcha', ['reCAPTCHA', '$compile', function(reCAPTCHA, $compile) {
    return {
        restrict: 'A',
        require: 'ngModel',
        scope: {
            ngModel: '='
        },
        link: function(scope, element, attrs, controller) {
            var name = attrs.name || 'reCaptcha';
            scope.clear = function() {
                scope.ngModel = {
                    response: '',
                    challenge: false
                };
            };

            // Create reCAPTCHA
            reCAPTCHA.create(element[0], function() {

                // Reset on Start
                scope.clear();

                // watch if challenge changes
                scope.$watch(function() {
                    return reCAPTCHA.challenge();
                }, function (newValue) {
                    scope.ngModel.challenge = newValue;
                });

                // Attach model and click handler
                $compile(angular.element(document.querySelector('input#recaptcha_response_field')).attr('ng-required', 'true'))(scope);
                $compile(angular.element(document.querySelector('input#recaptcha_response_field')).attr('ng-model', 'ngModel.response'))(scope);
                $compile(angular.element(document.querySelector('a#recaptcha_reload_btn')).attr('ng-click', 'clear()'))(scope);

            });

            // Destroy Element
            scope.$on('$destroy', reCAPTCHA.destroy);
        }
    };
}]);
<|endoftext|>


<commit_before>
function removeChatWindow() {

	$('table:eq(3) td:eq(2) center:eq(0) *:lt(2)').remove();
	$('table:eq(3) td:eq(2) br').remove();
}

var  jumpLastUnreadedMessage = {

	init : function() {
		$('.b-h-o-head:eq(2)').next().find('a').each(function() {

			// If theres a new message
			if($(this).find('small').length > 0) {

				// Get the new messages count
				var newMsg = parseInt($(this).find('small').html().match(/\d+/g));

				// Get last msn's page number
				var page = Math.ceil( newMsg / 80 );

				// Rewrite the url
				$(this).attr('href', $(this).attr('href') + '&order=reverse&index='+page+'&newmsg='+newMsg+'');
			}
		});
	},

	jump : function() {

		// Get new messages counter
		var newMsg = document.location.href.split('&newmsg=')[1];

		// Return if there isnot comment counter set
		if(typeof newMsg == "undefined" || newMsg == '') {
			return false;
		}

		// Get the last msg
		var lastMsg = newMsg % 80;

		// Target comment ele
		var target = $('#ujhszjott').next().children('center:eq('+(lastMsg-1)+')');

		// Insert the horizontal rule
		$('
').insertAfter(target);

		// Target offsetTop
		var targetOffset = $(target).offset().top;

		// Scroll to target element
		$('body').animate({ scrollTop : targetOffset}, 500);

		// Watch offsetTop while the content loads completly
		var interval = setInterval(function(){
			// Target offsetTop
			var targetOffset = $(target).offset().top;

			// Scroll to target element
			$('body').animate({ scrollTop : targetOffset}, 200);
		}, 200, target);

		// Clear interval when the page loads
		$(window).load(function() {
     		clearInterval(interval);
		});
	}

};

function filterOutReadedFaves() {

	var counter = 0;

	$($('.b-h-o-head:eq(2)').next().find('div a').get().reverse()).each(function() {

		// Skip topics that have unreaded messages
		if( $(this).find('small').length > 0) {
			counter++;
			return true;
		}

		if( $(this).parent().is('div.std0') ) {

			if(counter == 0) {
				$(this).parent().addClass('ext_hidden_fave');
				return true;
			} else {
				counter = 0;
				return true;
			}

		}

		// Otherwise, add hidden class
		$(this).parent().addClass('ext_hidden_fave');
	});

	// Set the "show" button
	$('.b-h-o-head:eq(2)').append('

');
	$('#ext_show_filtered_faves').append('');

	// Apply some styles
	$('#ext_show_filtered_faves').attr('class', 'show');
	$('#ext_show_filtered_faves_arrow').attr('class', 'show');

	// Set event handling
	$('#ext_show_filtered_faves').toggle(
		function() {
			$(this).attr('class', 'hide');
			$('#ext_show_filtered_faves_arrow').attr('class', 'hide');
			$('.ext_hidden_fave').show();
		},
		function() {
			$(this).attr('class', 'show');
			$('#ext_show_filtered_faves_arrow').attr('class', 'show');
			$('.ext_hidden_fave').hide();
		}
	);
}

function shortCommentMarker() {

	$('.b-h-o-head:eq(2)').next().find('div a').each(function() {

		if( $(this).find('small').length > 0) {

			// Received new messages counter
			var newMsg = parseInt( $(this).find('small').html().match(/\d+/g) );

			// Remove the old marker text
			$(this).find('br').remove();
			$(this).find('font').remove();

			// Add the new marker after the topic title
			$(this).html( $(this).html() + ' '+newMsg+'');
		}

	});
}

function blockMessages() {

	var deletelist = dataStore['block_list'].split(',');

	$(".topichead").each( function() {

		var username = ($(this).find("table tr:eq(0) td:eq(0) a img").length == 1) ? $(this).find("table tr:eq(0) td:eq(0) a img").attr("alt") : $(this).find("table tr:eq(0) td:eq(0) a")[0].innerHTML;
			username = username.replace(/ - VIP/, "");

		for(var i = 0; i < deletelist.length; i++) {
			if(username.toLowerCase() == deletelist[i].toLowerCase()) {
				$(this).parent().remove();
			}
		}
	});
}

function getBlockedUserNameFromLink(data) {

	var userName = '';
	var tmpUrl = data['linkUrl'].replace('http://www.sg.hu/', '');

	$('.topichead a[href='+tmpUrl+']').each(function() {

		// Fetch username
		userName = $(this).html();

		// Remove the comment
		$(this).closest('center').animate({ height : 0, opacity : 0 }, 500, function() {
			$(this).remove();
		})
	});

	if(userName != '') { port.postMessage({ type : "setBlockedUser", data : userName }); }
}

function getBlockedUserNameFromImage(data) {

	var userName = '';
	var tmpUrl = data['srcUrl'].replace('http://www.sg.hu', '');

	$('.topichead img[src='+tmpUrl+']').each(function() {

		// Fetch the username
		userName = ($(this).attr('title').replace(' - VIP', ''));

		// Remove the comment
		$(this).closest('center').animate({ height : 0, opacity : 0 }, 500, function() {
			$(this).remove();
		})
	});

	if(userName != '') { port.postMessage({ type : "setBlockedUser", data : userName }); }
}

$(document).ready(function() {

	// FORUM.PHP
	if(window.location.href.match('forum.php')) {

		// Remove chat window
		if(dataStore['chat_hide'] == 'true') {
			removeChatWindow();
		}

		// Jump the last unreaded message
		if(dataStore['jump_unreaded_messages'] == 'true') {
			jumpLastUnreadedMessage.init();
		}

		// Faves: show only with unreaded messages
		if(dataStore['fav_show_only_unreaded'] == 'true') {
			filterOutReadedFaves();
		}

		// Faves: short comment marker
		if(dataStore['short_comment_marker'] == 'true') {
			shortCommentMarker();
		}
	}

	// LISTAZAS.PHP
	else if(window.location.href.match('listazas.php3')) {

		// Jump the last unreaded message
		if(dataStore['jump_unreaded_messages']) {
			jumpLastUnreadedMessage.jump();
		}

		// Block users/messages
		if(dataStore['block_list'] != '') {
			blockMessages();
		}
	}
});

var dataStore;
var port = chrome.extension.connect();

port.postMessage({ type : "getStorageData" });

port.onMessage.addListener(function(response) {

	if(response.type == 'setStorageData') {
		dataStore = response.data;

	} else if(response.type == 'getBlockedUserNameFromLink') {
		getBlockedUserNameFromLink(response.data);

	} else if(response.type == 'getBlockedUserNameFromImage') {
		getBlockedUserNameFromImage(response.data);
	}
});<commit_msg>bugfix: blocklist empty list error messages<commit_after>
function removeChatWindow() {

	$('table:eq(3) td:eq(2) center:eq(0) *:lt(2)').remove();
	$('table:eq(3) td:eq(2) br').remove();
}

var  jumpLastUnreadedMessage = {

	init : function() {
		$('.b-h-o-head:eq(2)').next().find('a').each(function() {

			// If theres a new message
			if($(this).find('small').length > 0) {

				// Get the new messages count
				var newMsg = parseInt($(this).find('small').html().match(/\d+/g));

				// Get last msn's page number
				var page = Math.ceil( newMsg / 80 );

				// Rewrite the url
				$(this).attr('href', $(this).attr('href') + '&order=reverse&index='+page+'&newmsg='+newMsg+'');
			}
		});
	},

	jump : function() {

		// Get new messages counter
		var newMsg = document.location.href.split('&newmsg=')[1];

		// Return if there isnot comment counter set
		if(typeof newMsg == "undefined" || newMsg == '') {
			return false;
		}

		// Get the last msg
		var lastMsg = newMsg % 80;

		// Target comment ele
		var target = $('#ujhszjott').next().children('center:eq('+(lastMsg-1)+')');

		// Insert the horizontal rule
		$('
').insertAfter(target);

		// Target offsetTop
		var targetOffset = $(target).offset().top;

		// Scroll to target element
		$('body').animate({ scrollTop : targetOffset}, 500);

		// Watch offsetTop while the content loads completly
		var interval = setInterval(function(){
			// Target offsetTop
			var targetOffset = $(target).offset().top;

			// Scroll to target element
			$('body').animate({ scrollTop : targetOffset}, 200);
		}, 200, target);

		// Clear interval when the page loads
		$(window).load(function() {
     		clearInterval(interval);
		});
	}

};

function filterOutReadedFaves() {

	var counter = 0;

	$($('.b-h-o-head:eq(2)').next().find('div a').get().reverse()).each(function() {

		// Skip topics that have unreaded messages
		if( $(this).find('small').length > 0) {
			counter++;
			return true;
		}

		if( $(this).parent().is('div.std0') ) {

			if(counter == 0) {
				$(this).parent().addClass('ext_hidden_fave');
				return true;
			} else {
				counter = 0;
				return true;
			}

		}

		// Otherwise, add hidden class
		$(this).parent().addClass('ext_hidden_fave');
	});

	// Set the "show" button
	$('.b-h-o-head:eq(2)').append('

');
	$('#ext_show_filtered_faves').append('');

	// Apply some styles
	$('#ext_show_filtered_faves').attr('class', 'show');
	$('#ext_show_filtered_faves_arrow').attr('class', 'show');

	// Set event handling
	$('#ext_show_filtered_faves').toggle(
		function() {
			$(this).attr('class', 'hide');
			$('#ext_show_filtered_faves_arrow').attr('class', 'hide');
			$('.ext_hidden_fave').show();
		},
		function() {
			$(this).attr('class', 'show');
			$('#ext_show_filtered_faves_arrow').attr('class', 'show');
			$('.ext_hidden_fave').hide();
		}
	);
}

function shortCommentMarker() {

	$('.b-h-o-head:eq(2)').next().find('div a').each(function() {

		if( $(this).find('small').length > 0) {

			// Received new messages counter
			var newMsg = parseInt( $(this).find('small').html().match(/\d+/g) );

			// Remove the old marker text
			$(this).find('br').remove();
			$(this).find('font').remove();

			// Add the new marker after the topic title
			$(this).html( $(this).html() + ' '+newMsg+'');
		}

	});
}

function blockMessages() {

	// Return false if theres no blocklist entry
	if(typeof dataStore['block_list'] == "undefined" || dataStore['block_list'] == '') {
		return false;
	}

	var deletelist = dataStore['block_list'].split(',');

	$(".topichead").each( function() {

		var username = ($(this).find("table tr:eq(0) td:eq(0) a img").length == 1) ? $(this).find("table tr:eq(0) td:eq(0) a img").attr("alt") : $(this).find("table tr:eq(0) td:eq(0) a")[0].innerHTML;
			username = username.replace(/ - VIP/, "");

		for(var i = 0; i < deletelist.length; i++) {
			if(username.toLowerCase() == deletelist[i].toLowerCase()) {
				$(this).parent().remove();
			}
		}
	});
}

function getBlockedUserNameFromLink(data) {

	var userName = '';
	var tmpUrl = data['linkUrl'].replace('http://www.sg.hu/', '');

	$('.topichead a[href='+tmpUrl+']').each(function() {

		// Fetch username
		userName = $(this).html();

		// Remove the comment
		$(this).closest('center').animate({ height : 0, opacity : 0 }, 500, function() {
			$(this).remove();
		})
	});

	if(userName != '') { port.postMessage({ type : "setBlockedUser", data : userName }); }
}

function getBlockedUserNameFromImage(data) {

	var userName = '';
	var tmpUrl = data['srcUrl'].replace('http://www.sg.hu', '');

	$('.topichead img[src='+tmpUrl+']').each(function() {

		// Fetch the username
		userName = ($(this).attr('title').replace(' - VIP', ''));

		// Remove the comment
		$(this).closest('center').animate({ height : 0, opacity : 0 }, 500, function() {
			$(this).remove();
		})
	});

	if(userName != '') { port.postMessage({ type : "setBlockedUser", data : userName }); }
}

$(document).ready(function() {

	// FORUM.PHP
	if(window.location.href.match('forum.php')) {

		// Remove chat window
		if(dataStore['chat_hide'] == 'true') {
			removeChatWindow();
		}

		// Jump the last unreaded message
		if(dataStore['jump_unreaded_messages'] == 'true') {
			jumpLastUnreadedMessage.init();
		}

		// Faves: show only with unreaded messages
		if(dataStore['fav_show_only_unreaded'] == 'true') {
			filterOutReadedFaves();
		}

		// Faves: short comment marker
		if(dataStore['short_comment_marker'] == 'true') {
			shortCommentMarker();
		}
	}

	// LISTAZAS.PHP
	else if(window.location.href.match('listazas.php3')) {

		// Jump the last unreaded message
		if(dataStore['jump_unreaded_messages']) {
			jumpLastUnreadedMessage.jump();
		}

		// Block users/messages
		if(dataStore['block_list'] != '') {
			blockMessages();
		}
	}
});

var dataStore;
var port = chrome.extension.connect();

port.postMessage({ type : "getStorageData" });

port.onMessage.addListener(function(response) {

	if(response.type == 'setStorageData') {
		dataStore = response.data;

	} else if(response.type == 'getBlockedUserNameFromLink') {
		getBlockedUserNameFromLink(response.data);

	} else if(response.type == 'getBlockedUserNameFromImage') {
		getBlockedUserNameFromImage(response.data);
	}
});<|endoftext|>


<commit_before>import { addCallback, runCallbacks, runCallbacksAsync } from 'meteor/vulcan:core';
import { Posts } from 'meteor/example-forum';
import { updateScore } from 'meteor/vulcan:voting';
import Users from 'meteor/vulcan:users';

import { convertFromRaw } from 'draft-js';
import { draftToHTML } from '../../editor/utils.js';
import htmlToText from 'html-to-text';

function PostsEditRunPostUndraftedSyncCallbacks (modifier, post) {
  if (modifier.$set && modifier.$set.draft === false && post.draft) {
    modifier = runCallbacks("posts.undraft.sync", modifier, post);
  }
  return modifier;
}
addCallback("posts.edit.sync", PostsEditRunPostUndraftedSyncCallbacks);

function PostsEditRunPostUndraftedAsyncCallbacks (newPost, oldPost) {
  if (!newPost.draft && oldPost.draft) {
    console.log("Running undraft async callback");
    runCallbacksAsync("posts.undraft.async", newPost, oldPost)
  }
  return newPost
}

addCallback("posts.edit.async", PostsEditRunPostUndraftedAsyncCallbacks);

/**
 * @summary set postedAt when a post is moved out of drafts
 */
function PostsSetPostedAt (modifier, post) {
  modifier.$set.postedAt = new Date();
  if (modifier.$unset) {
    delete modifier.$unset.postedAt;
  }
  return modifier;
}
addCallback("posts.undraft.sync", PostsSetPostedAt);

/**
 * @summary update frontpagePostCount when post is moved into frontpage
 */
function postsEditIncreaseFrontpagePostCount (post, oldPost) {
  if (post.frontpage && !oldPost.frontpage) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: 1}})
  }
}
addCallback("posts.edit.async", postsEditIncreaseFrontpagePostCount);

/**
 * @summary update frontpagePostCount when post is moved into frontpage
 */
function postsNewIncreaseFrontpageCount (post) {
  if (post.frontpage) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: 1}})
  }
}
addCallback("posts.new.async", postsNewIncreaseFrontpageCount);

/**
 * @summary update frontpagePostCount when post is moved into frontpage
 */
function postsRemoveDecreaseFrontpageCount (post) {
  if (post.frontpage) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: -1}})
  }
}
addCallback("posts.remove.async", postsRemoveDecreaseFrontpageCount);

/**
 * @summary update frontpagePostCount when post is moved out of frontpage
 */
function postsEditDecreaseFrontpagePostCount (post, oldPost) {
  if (!post.frontpage && oldPost.frontpage) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: -1}})
  }
}
addCallback("posts.edit.async", postsEditDecreaseFrontpagePostCount);

function PostsNewHTMLSerializeCallback (post) {
  if (post.content) {
    const contentState = convertFromRaw(post.content);
    const html = draftToHTML(contentState);
    post.htmlBody = html;
    post.body = contentState.getPlainText();
    post.excerpt = post.body.slice(0,140);
    console.log("Comments New HTML serialization", post.excerpt)
  } else if (post.htmlBody) {
    post.body = htmlToText.fromString(post.htmlBody);
    post.excerpt = post.body.slice(0,140);
  }
  return post
}

addCallback("posts.new.sync", PostsNewHTMLSerializeCallback);

function PostsEditHTMLSerializeCallback (modifier, post) {
  if (modifier.$set && modifier.$set.content) {
    const contentState = convertFromRaw(modifier.$set.content);
    console.log("Comment Edit callback: ", modifier.$set.content);
    modifier.$set.htmlBody = draftToHTML(contentState);
    modifier.$set.body = contentState.getPlainText();
    modifier.$set.excerpt = modifier.$set.body.slice(0,140);
    console.log("Comments Edit HTML serialization", modifier.$set.htmlBody, modifier.$set.body)
  } else if (modifier.$set && modifier.$set.htmlBody) {
    modifier.$set.body = htmlToText.fromString(modifier.$set.htmlBody);
    modifier.$set.exerpt = modifier.$set.body.slice(0,140);
  }
  return modifier
}

addCallback("posts.edit.sync", PostsEditHTMLSerializeCallback);
<commit_msg>Fix excerpt generation bug on post edit with HTML<commit_after>import { addCallback, runCallbacks, runCallbacksAsync } from 'meteor/vulcan:core';
import { Posts } from 'meteor/example-forum';
import { updateScore } from 'meteor/vulcan:voting';
import Users from 'meteor/vulcan:users';

import { convertFromRaw } from 'draft-js';
import { draftToHTML } from '../../editor/utils.js';
import htmlToText from 'html-to-text';

function PostsEditRunPostUndraftedSyncCallbacks (modifier, post) {
  if (modifier.$set && modifier.$set.draft === false && post.draft) {
    modifier = runCallbacks("posts.undraft.sync", modifier, post);
  }
  return modifier;
}
addCallback("posts.edit.sync", PostsEditRunPostUndraftedSyncCallbacks);

function PostsEditRunPostUndraftedAsyncCallbacks (newPost, oldPost) {
  if (!newPost.draft && oldPost.draft) {
    console.log("Running undraft async callback");
    runCallbacksAsync("posts.undraft.async", newPost, oldPost)
  }
  return newPost
}

addCallback("posts.edit.async", PostsEditRunPostUndraftedAsyncCallbacks);

/**
 * @summary set postedAt when a post is moved out of drafts
 */
function PostsSetPostedAt (modifier, post) {
  modifier.$set.postedAt = new Date();
  if (modifier.$unset) {
    delete modifier.$unset.postedAt;
  }
  return modifier;
}
addCallback("posts.undraft.sync", PostsSetPostedAt);

/**
 * @summary update frontpagePostCount when post is moved into frontpage
 */
function postsEditIncreaseFrontpagePostCount (post, oldPost) {
  if (post.frontpage && !oldPost.frontpage) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: 1}})
  }
}
addCallback("posts.edit.async", postsEditIncreaseFrontpagePostCount);

/**
 * @summary update frontpagePostCount when post is moved into frontpage
 */
function postsNewIncreaseFrontpageCount (post) {
  if (post.frontpage) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: 1}})
  }
}
addCallback("posts.new.async", postsNewIncreaseFrontpageCount);

/**
 * @summary update frontpagePostCount when post is moved into frontpage
 */
function postsRemoveDecreaseFrontpageCount (post) {
  if (post.frontpage) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: -1}})
  }
}
addCallback("posts.remove.async", postsRemoveDecreaseFrontpageCount);

/**
 * @summary update frontpagePostCount when post is moved out of frontpage
 */
function postsEditDecreaseFrontpagePostCount (post, oldPost) {
  if (!post.frontpage && oldPost.frontpage) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: -1}})
  }
}
addCallback("posts.edit.async", postsEditDecreaseFrontpagePostCount);

function PostsNewHTMLSerializeCallback (post) {
  if (post.content) {
    const contentState = convertFromRaw(post.content);
    const html = draftToHTML(contentState);
    post.htmlBody = html;
    post.body = contentState.getPlainText();
    post.excerpt = post.body.slice(0,140);
    console.log("Comments New HTML serialization", post.excerpt)
  } else if (post.htmlBody) {
    post.body = htmlToText.fromString(post.htmlBody);
    post.excerpt = post.body.slice(0,140);
  }
  return post
}

addCallback("posts.new.sync", PostsNewHTMLSerializeCallback);

function PostsEditHTMLSerializeCallback (modifier, post) {
  if (modifier.$set && modifier.$set.content) {
    const contentState = convertFromRaw(modifier.$set.content);
    console.log("Comment Edit callback: ", modifier.$set.content);
    modifier.$set.htmlBody = draftToHTML(contentState);
    modifier.$set.body = contentState.getPlainText();
    modifier.$set.excerpt = modifier.$set.body.slice(0,140);
    console.log("Comments Edit HTML serialization", modifier.$set.htmlBody, modifier.$set.body)
  } else if (modifier.$set && modifier.$set.htmlBody) {
    modifier.$set.body = htmlToText.fromString(modifier.$set.htmlBody);
    modifier.$set.excerpt = modifier.$set.body.slice(0,140);
  }
  return modifier
}

addCallback("posts.edit.sync", PostsEditHTMLSerializeCallback);
<|endoftext|>


<commit_before>(function(root) {
  'use strict';

  function RGBScale(colors, positions, domain) {
    var scale = (new Scale())
      .colors(colors)
      .positions(positions)
      .domain(domain);

    var getColor = function(value) {
      return scale.getColor(value);
    };

    getColor._scale = scale;

    return getColor;
  }

  /**
   * Scale
   */
  function Scale() {
    if (!(this instanceof Scale)) {
      return new Scale();
    }

    this._colors = [];
    this._positions = [];
    this._domain = [0,1];
    this._min = 0;
    this._max = 1;
    this._numClasses = 0;
    this._colorCache = {};
  }

  Scale.prototype.colors = function(colors) {
    if (Array.isArray(colors)) {
      this._resetCache();
      this._colors = colors.slice(0);

      for (var i = 0; i < this._colors.length; i++) {
        if (this._colors[i].length === 3) {
          this._colors[i][3] = 1;
        }
      }
    }

    return this;
  };

  Scale.prototype.positions = function(positions) {
    if (Array.isArray(positions)) {
      this._positions = positions.slice(0);
    } else {
      positions = [];

      var colorsLength = this._colors.length;
      var c = 0;
      var w = 0;
      var ref = colorsLength - 1;

      for (; 0 <= ref ? w <= ref : w >= ref; c = (0 <= ref ? ++w : --w)) {
        this._positions.push(c / (colorsLength - 1));
      }
    }

    return this;
  };

  Scale.prototype.domain = function(domain) {
    if (Array.isArray(domain)) {
      this._domain = domain.slice(0);
      this._min = domain[0];
      this._max = domain[domain.length - 1];

      this._resetCache();
    }

    if (this._domain.length === 2) {
      this._numClasses = 0;
    } else {
      this._numClasses = domain.length - 1;
    }

    return this;
  };

  Scale.prototype._resetCache = function() {
    this._colorCache = {};
  };

  Scale.prototype._getClass = function(value) {
    if (this._domain.length) {
      var n = this._domain.length;
      var i = 0;

      while (i < n && value >= this._domain[i]) {
        i++;
      }

      return i - 1;
    }

    return 0;
  };

  Scale.prototype.getColor = function(value) {
    var t;
    var color;

    if (typeof value !== 'number') {
      value = 0;
    }

    if (this._domain.length > 2) {
      var c = this._getClass(value);
      t = c / (this._numClasses - 1);
    } else {
      t = (this.min !== this._max ? (value - this._min) / (this._max - this._min) : 0);
      t = Math.min(1, Math.max(0, t));
    }

    var k = Math.floor(t * 10000);

    if (this._colorCache[k]) {
      color = this._colorCache[k];
    } else {
      var i = 0;
      var o = 0;
      var ref = this._positions.length - 1;

      for (; 0 <= ref ? o <= ref : o >= ref; i = (0 <= ref ? ++o : --o)) {
        var p = this._positions[i];

        if ((t <= p) || (t >= p && i === this._positions.length - 1)) {
          color = this._colors[i];
          break;
        }

        if (t > p && t < this._positions[i + 1]) {
          t = (t - p) / (this._positions[i + 1] - p);
          color = Scale._interpolateRGB(this._colors[i], this._colors[i + 1], t);
          break;
        }
      }

      if (color) {
        this._colorCache[k] = color;
      } else {
        color = [0,0,0,1];
      }
    }

    return color;
  };

  /**
   * clipRGB
   * @desc clamp colors to 0 or to 255 if out of bounds
   * @param {number[]} rgb - rgb colors in array
   */
  Scale._clipRGB = function(rgb) {
    if (!(Array.isArray(rgb) && rgb.length >= 3)) {
      rgb = [0,0,0,1];
    }

    for (var i = 0; i < rgb.length; i++) {
      if (i < 3) {
        if (rgb[i] < 0) {
          rgb[i] = 0;
        } else if (rgb[i] > 255) {
          rgb[i] = 255;
        }
      } else if (i === 3) {
        if (rgb[i] < 0) {
          rgb[i] = 0;
        } else if (rgb[i] > 1) {
          rgb[i] = 1;
        }
      }
    }

    return rgb;
  };

  /**
   * clipT
   * @desc Clips target value to stay within constraints 0-1
   * @param {number} target - target value
   * @return clipped value
   */
  Scale._clipT = function(t) {
    if (typeof t !== 'number') {
      t = 0;
    }

    if (t > 1) {
      t = 1;
    } else if (t < 0) {
      t = 0;
    }

    return t;
  };

  /**
   * interpolateRGB
   * @desc Interpolate RGB colors.
   * @param {number[]} rgb1 - rgb array
   * @param {number[]} rgb2 - rgb array
   * @param {number} target - target value between units 0 - 1
   * @return interpolated rgb color array
   */
  Scale._interpolateRGB =  function(rgb1, rgb2, t) {
    rgb1 = Scale._clipRGB(rgb1);
    rgb2 = Scale._clipRGB(rgb2);
    t = Scale._clipT(t);

    var r_1 = rgb1[0];
    var g_1 = rgb1[1];
    var b_1 = rgb1[2];
    var a_1 = rgb1[3];

    var r_2 = rgb2[0];
    var g_2 = rgb2[1];
    var b_2 = rgb2[2];
    var a_2 = rgb2[3];

    var r_3 = r_1 + t * (r_2 - r_1);
    var g_3 = g_1 + t * (g_2 - g_1);
    var b_3 = b_1 + t * (b_2 - b_1);
    var a_3 = a_1 + t * (a_2 - a_1);

    var result = [r_3, g_3, b_3, a_3];
    return result;
  };

  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = RGBScale;
    }
    exports.RGBScale = RGBScale;
  } else if (typeof define === 'function' && define.amd) {
    define([], function() {
      return RGBScale;
    });
  } else {
    root.RGBScale = RGBScale;
  }

})(this);
<commit_msg>jsdoc<commit_after>(function(root) {
  'use strict';

  /**
   * RGBScale
   * @desc RGB color scale.
   * @param {number[]} colors - rgb colors in array
   * @param {number[]} positions - color position offsets
   * @param {number[]} domain - scale domain
   * @return {function} scale function
   */
  function RGBScale(colors, positions, domain) {
    if (!(this instanceof RGBScale)) {
      return new RGBScale(colors, positions, domain);
    }

    this._colors = [];
    this._positions = [];
    this._domain = [0,1];
    this._min = 0;
    this._max = 1;
    this._numClasses = 0;
    this._colorCache = {};

    this.colors(colors);
    this.positions(positions);
    this.domain(domain);

    return function(value) {
      return this._getColor(value);
    }.bind(this);
  }

  /**
   * colors
   * @desc Set color values
   * @param {number[]} colors - RGB color values
   */
  RGBScale.prototype.colors = function(colors) {
    if (Array.isArray(colors)) {
      this._resetCache();
      this._colors = colors.slice(0);

      for (var i = 0; i < this._colors.length; i++) {
        if (this._colors[i].length === 3) {
          this._colors[i][3] = 1;
        }
      }
    }

    return this;
  };

  /**
   * positions
   * @desc Set position values
   * @param {number[]} positions - position values
   */
  RGBScale.prototype.positions = function(positions) {
    if (Array.isArray(positions)) {
      this._positions = positions.slice(0);
    } else {
      positions = [];

      var colorsLength = this._colors.length;
      var c = 0;
      var w = 0;
      var ref = colorsLength - 1;

      for (; 0 <= ref ? w <= ref : w >= ref; c = (0 <= ref ? ++w : --w)) {
        this._positions.push(c / (colorsLength - 1));
      }
    }

    return this;
  };

  /**
   * domain
   * @desc Set domain and min/max values
   * @param {number[]} domain - domain values
   */
  RGBScale.prototype.domain = function(domain) {
    if (Array.isArray(domain)) {
      this._domain = domain.slice(0);
      this._min = domain[0];
      this._max = domain[domain.length - 1];

      this._resetCache();
    }

    if (this._domain.length === 2) {
      this._numClasses = 0;
    } else {
      this._numClasses = domain.length - 1;
    }

    return this;
  };

  /**
   * resetCache
   * @desc Reset color cache
   */
  RGBScale.prototype._resetCache = function() {
    this._colorCache = {};
  };

  /**
   * getClass
   * @desc Return class for domain value
   * @param {number} value - domain value
   * @return {number} class value
   */
  RGBScale.prototype._getClass = function(value) {
    if (this._domain.length) {
      var n = this._domain.length;
      var i = 0;

      while (i < n && value >= this._domain[i]) {
        i++;
      }

      return i - 1;
    }

    return 0;
  };

  /**
   * getColor
   * @desc Return color for domain value
   * @param {number} value - domain value
   * @return {number[]} RGB color
   */
  RGBScale.prototype._getColor = function(value) {
    var t;
    var color;

    if (typeof value !== 'number') {
      value = 0;
    }

    if (this._domain.length > 2) {
      var c = this._getClass(value);
      t = c / (this._numClasses - 1);
    } else {
      t = (this.min !== this._max ? (value - this._min) / (this._max - this._min) : 0);
      t = Math.min(1, Math.max(0, t));
    }

    var k = Math.floor(t * 10000);

    if (this._colorCache[k]) {
      color = this._colorCache[k];
    } else {
      var i = 0;
      var o = 0;
      var ref = this._positions.length - 1;

      for (; 0 <= ref ? o <= ref : o >= ref; i = (0 <= ref ? ++o : --o)) {
        var p = this._positions[i];

        if ((t <= p) || (t >= p && i === this._positions.length - 1)) {
          color = this._colors[i];
          break;
        }

        if (t > p && t < this._positions[i + 1]) {
          t = (t - p) / (this._positions[i + 1] - p);
          color = RGBScale._interpolateRGB(this._colors[i], this._colors[i + 1], t);
          break;
        }
      }

      if (color) {
        this._colorCache[k] = color;
      } else {
        color = [0,0,0,1];
      }
    }

    return color;
  };

  /**
   * clipRGB
   * @desc clamp colors to 0 or to 255 if out of bounds
   * @param {number[]} rgb - rgb colors in array
   */
  RGBScale._clipRGB = function(rgb) {
    if (!(Array.isArray(rgb) && rgb.length >= 3)) {
      rgb = [0,0,0,1];
    }

    for (var i = 0; i < rgb.length; i++) {
      if (i < 3) {
        if (rgb[i] < 0) {
          rgb[i] = 0;
        } else if (rgb[i] > 255) {
          rgb[i] = 255;
        }
      } else if (i === 3) {
        if (rgb[i] < 0) {
          rgb[i] = 0;
        } else if (rgb[i] > 1) {
          rgb[i] = 1;
        }
      }
    }

    return rgb;
  };

  /**
   * clipT
   * @desc Clips target value to stay within constraints 0-1
   * @param {number} target - target value
   * @return clipped value
   */
  RGBScale._clipT = function(t) {
    if (typeof t !== 'number') {
      t = 0;
    }

    if (t > 1) {
      t = 1;
    } else if (t < 0) {
      t = 0;
    }

    return t;
  };

  /**
   * interpolateRGB
   * @desc Interpolate RGB colors.
   * @param {number[]} rgb1 - rgb array
   * @param {number[]} rgb2 - rgb array
   * @param {number} target - target value between units 0 - 1
   * @return interpolated rgb color array
   */
  RGBScale._interpolateRGB =  function(rgb1, rgb2, t) {
    rgb1 = RGBScale._clipRGB(rgb1);
    rgb2 = RGBScale._clipRGB(rgb2);
    t = RGBScale._clipT(t);

    var r_1 = rgb1[0];
    var g_1 = rgb1[1];
    var b_1 = rgb1[2];
    var a_1 = rgb1[3];

    var r_2 = rgb2[0];
    var g_2 = rgb2[1];
    var b_2 = rgb2[2];
    var a_2 = rgb2[3];

    var r_3 = r_1 + t * (r_2 - r_1);
    var g_3 = g_1 + t * (g_2 - g_1);
    var b_3 = b_1 + t * (b_2 - b_1);
    var a_3 = a_1 + t * (a_2 - a_1);

    var result = [r_3, g_3, b_3, a_3];
    return result;
  };

  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = RGBScale;
    }
    exports.RGBScale = RGBScale;
  } else if (typeof define === 'function' && define.amd) {
    define([], function() {
      return RGBScale;
    });
  } else {
    root.RGBScale = RGBScale;
  }
})(this);
<|endoftext|>


<commit_before>'use strict'

const AuthError = require('./AuthError')

// Remove the trailing slash so we can always safely append /xyz.
function stripSlash (url) {
  return url.replace(/\/$/, '')
}

module.exports = class RequestClient {
  static VERSION = require('../package.json').version

  constructor (uppy, opts) {
    this.uppy = uppy
    this.opts = opts
    this.onReceiveResponse = this.onReceiveResponse.bind(this)
    this.allowedHeaders = ['accept', 'content-type']
    this.preflightDone = false
  }

  get hostname () {
    const { companion } = this.uppy.getState()
    const host = this.opts.companionUrl
    return stripSlash(companion && companion[host] ? companion[host] : host)
  }

  get defaultHeaders () {
    return {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'Uppy-Versions': '@uppy/companion-client=1.0.3'
    }
  }

  headers () {
    return Promise.resolve(
      Object.assign({}, this.defaultHeaders, this.opts.serverHeaders || {})
    )
  }

  _getPostResponseFunc (skip) {
    return (response) => {
      if (!skip) {
        return this.onReceiveResponse(response)
      }

      return response
    }
  }

  onReceiveResponse (response) {
    const state = this.uppy.getState()
    const companion = state.companion || {}
    const host = this.opts.companionUrl
    const headers = response.headers
    // Store the self-identified domain name for the Companion instance we just hit.
    if (headers.has('i-am') && headers.get('i-am') !== companion[host]) {
      this.uppy.setState({
        companion: Object.assign({}, companion, {
          [host]: headers.get('i-am')
        })
      })
    }
    return response
  }

  _getUrl (url) {
    if (/^(https?:|)\/\//.test(url)) {
      return url
    }
    return `${this.hostname}/${url}`
  }

  _json (res) {
    if (res.status === 401) {
      throw new AuthError()
    }

    if (res.status < 200 || res.status > 300) {
      throw new Error(`Failed request to ${res.url}. ${res.statusText}`)
    }
    return res.json()
  }

  preflight (path) {
    return new Promise((resolve, reject) => {
      if (this.preflightDone) {
        return resolve(this.allowedHeaders.slice())
      }

      fetch(this._getUrl(path), {
        method: 'OPTIONS'
      })
        .then((response) => {
          if (response.headers.has('access-control-allow-headers')) {
            this.allowedHeaders = response.headers.get('access-control-allow-headers')
              .split(',').map((headerName) => headerName.trim().toLowerCase())
          }
          this.preflightDone = true
          resolve(this.allowedHeaders.slice())
        })
        .catch(reject)
    })
  }

  preflightAndHeaders (path) {
    return new Promise((resolve, reject) => {
      this.preflight(path).then((allowedHeaders) => {
        this.headers().then((headers) => {
          // filter to keep only allowed Headers
          Object.keys(headers).forEach((header) => {
            if (allowedHeaders.indexOf(header.toLowerCase()) === -1) {
              this.uppy.log(`[CompanionClient] excluding unallowed header ${header}`)
              delete headers[header]
            }
          })

          resolve(headers)
        })
      }).catch(reject)
    })
  }

  get (path, skipPostResponse) {
    return new Promise((resolve, reject) => {
      this.preflightAndHeaders(path).then((headers) => {
        fetch(this._getUrl(path), {
          method: 'get',
          headers: headers,
          credentials: 'same-origin'
        })
          .then(this._getPostResponseFunc(skipPostResponse))
          .then((res) => this._json(res).then(resolve))
          .catch((err) => {
            err = err.isAuthError ? err : new Error(`Could not get ${this._getUrl(path)}. ${err}`)
            reject(err)
          })
      })
    })
  }

  post (path, data, skipPostResponse) {
    return new Promise((resolve, reject) => {
      this.preflightAndHeaders(path).then((headers) => {
        fetch(this._getUrl(path), {
          method: 'post',
          headers: headers,
          credentials: 'same-origin',
          body: JSON.stringify(data)
        })
          .then(this._getPostResponseFunc(skipPostResponse))
          .then((res) => this._json(res).then(resolve))
          .catch((err) => {
            err = err.isAuthError ? err : new Error(`Could not post ${this._getUrl(path)}. ${err}`)
            reject(err)
          })
      })
    })
  }

  delete (path, data, skipPostResponse) {
    return new Promise((resolve, reject) => {
      this.preflightAndHeaders(path).then((headers) => {
        fetch(`${this.hostname}/${path}`, {
          method: 'delete',
          headers: headers,
          credentials: 'same-origin',
          body: data ? JSON.stringify(data) : null
        })
          .then(this._getPostResponseFunc(skipPostResponse))
          .then((res) => this._json(res).then(resolve))
          .catch((err) => {
            err = err.isAuthError ? err : new Error(`Could not delete ${this._getUrl(path)}. ${err}`)
            reject(err)
          })
      })
    })
  }
}
<commit_msg>companion-client: flatten promise usage<commit_after>'use strict'

const AuthError = require('./AuthError')

// Remove the trailing slash so we can always safely append /xyz.
function stripSlash (url) {
  return url.replace(/\/$/, '')
}

module.exports = class RequestClient {
  static VERSION = require('../package.json').version

  constructor (uppy, opts) {
    this.uppy = uppy
    this.opts = opts
    this.onReceiveResponse = this.onReceiveResponse.bind(this)
    this.allowedHeaders = ['accept', 'content-type']
    this.preflightDone = false
  }

  get hostname () {
    const { companion } = this.uppy.getState()
    const host = this.opts.companionUrl
    return stripSlash(companion && companion[host] ? companion[host] : host)
  }

  get defaultHeaders () {
    return {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'Uppy-Versions': '@uppy/companion-client=1.0.3'
    }
  }

  headers () {
    return Promise.resolve(
      Object.assign({}, this.defaultHeaders, this.opts.serverHeaders || {})
    )
  }

  _getPostResponseFunc (skip) {
    return (response) => {
      if (!skip) {
        return this.onReceiveResponse(response)
      }

      return response
    }
  }

  onReceiveResponse (response) {
    const state = this.uppy.getState()
    const companion = state.companion || {}
    const host = this.opts.companionUrl
    const headers = response.headers
    // Store the self-identified domain name for the Companion instance we just hit.
    if (headers.has('i-am') && headers.get('i-am') !== companion[host]) {
      this.uppy.setState({
        companion: Object.assign({}, companion, {
          [host]: headers.get('i-am')
        })
      })
    }
    return response
  }

  _getUrl (url) {
    if (/^(https?:|)\/\//.test(url)) {
      return url
    }
    return `${this.hostname}/${url}`
  }

  _json (res) {
    if (res.status === 401) {
      throw new AuthError()
    }

    if (res.status < 200 || res.status > 300) {
      throw new Error(`Failed request to ${res.url}. ${res.statusText}`)
    }
    return res.json()
  }

  preflight (path) {
    return new Promise((resolve, reject) => {
      if (this.preflightDone) {
        return resolve(this.allowedHeaders.slice())
      }

      fetch(this._getUrl(path), {
        method: 'OPTIONS'
      })
        .then((response) => {
          if (response.headers.has('access-control-allow-headers')) {
            this.allowedHeaders = response.headers.get('access-control-allow-headers')
              .split(',').map((headerName) => headerName.trim().toLowerCase())
          }
          this.preflightDone = true
          resolve(this.allowedHeaders.slice())
        })
        .catch(reject)
    })
  }

  preflightAndHeaders (path) {
    return Promise.all([this.preflight(path), this.headers()])
      .then(([allowedHeaders, headers]) => {
        // filter to keep only allowed Headers
        Object.keys(headers).forEach((header) => {
          if (allowedHeaders.indexOf(header.toLowerCase()) === -1) {
            this.uppy.log(`[CompanionClient] excluding unallowed header ${header}`)
            delete headers[header]
          }
        })

        return headers
      })
  }

  get (path, skipPostResponse) {
    return new Promise((resolve, reject) => {
      this.preflightAndHeaders(path).then((headers) => {
        fetch(this._getUrl(path), {
          method: 'get',
          headers: headers,
          credentials: 'same-origin'
        })
          .then(this._getPostResponseFunc(skipPostResponse))
          .then((res) => this._json(res).then(resolve))
          .catch((err) => {
            err = err.isAuthError ? err : new Error(`Could not get ${this._getUrl(path)}. ${err}`)
            reject(err)
          })
      }).catch(reject)
    })
  }

  post (path, data, skipPostResponse) {
    return new Promise((resolve, reject) => {
      this.preflightAndHeaders(path).then((headers) => {
        fetch(this._getUrl(path), {
          method: 'post',
          headers: headers,
          credentials: 'same-origin',
          body: JSON.stringify(data)
        })
          .then(this._getPostResponseFunc(skipPostResponse))
          .then((res) => this._json(res).then(resolve))
          .catch((err) => {
            err = err.isAuthError ? err : new Error(`Could not post ${this._getUrl(path)}. ${err}`)
            reject(err)
          })
      }).catch(reject)
    })
  }

  delete (path, data, skipPostResponse) {
    return new Promise((resolve, reject) => {
      this.preflightAndHeaders(path).then((headers) => {
        fetch(`${this.hostname}/${path}`, {
          method: 'delete',
          headers: headers,
          credentials: 'same-origin',
          body: data ? JSON.stringify(data) : null
        })
          .then(this._getPostResponseFunc(skipPostResponse))
          .then((res) => this._json(res).then(resolve))
          .catch((err) => {
            err = err.isAuthError ? err : new Error(`Could not delete ${this._getUrl(path)}. ${err}`)
            reject(err)
          })
      }).catch(reject)
    })
  }
}
<|endoftext|>


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

// Ballista polyfill.
// Author: Matt Giuca <mgiuca@chromium.org>
//
// Implements a (partial) Ballista API for demonstration purposes. Note: This
// API, by design, does things that aren't possible in a polyfill, so this will
// be broken without specific browser hacks. All work-in-progress; don't get too
// excited about it.
"use strict";

(function() {

// Portions are copied (and heavily modified) from tha navigator-connect
// polyfill by mkruisselbrink and reillyeon:
// https://github.com/mkruisselbrink/navigator-connect

// The URL of the proxy app, which provides the handler chooser and routes
// actions through to the chosen handler.
// TODO(mgiuca): Move this to a fixed public location.
var kProxyUrl = 'http://localhost:8080/choose';

var kProxyUrlSuffix = '?actions-handler-proxy';

// DOM element for the <div> containing the chooser UI (hosted by the proxy
// site) in an iframe. Only non-null while choosing is taking place.
var chooserDiv = null;

// The handler's service worker provides a special proxy page that will be
// embedded in an iframe in the requester (by connectToHandler).
//
// This page proxies messages between the requester foreground page and the
// handler service worker.
function onFetch(event) {
  var targetUrl = event.request.url;
  // Detect whether kProxyUrlSuffix appears in the query string.
  var proxySuffixIndex = targetUrl.indexOf(kProxyUrlSuffix);
  if (proxySuffixIndex === -1 ||
      (proxySuffixIndex + kProxyUrlSuffix.length != targetUrl.length &&
       targetUrl[proxySuffixIndex + kProxyUrlSuffix.length] != '&')) {
    // Not a navigator-connect attempt
    return;
  }
  // In the real world this should not reply to all fetches.
  event.respondWith(fetch('polyfill/proxy-iframe.html'));
  event.stopImmediatePropagation();
}

function onMessage(event) {
  if (typeof event != 'object')
    return;

  var data = event.data;

  if (data === undefined)
    return;

  if (data.type == 'connect') {
    data.port.postMessage({connected: true});
    data.port.onmessage = e => {
      onMessageReceived(e.data, data.port);
    }
    event.stopImmediatePropagation();
    return;
  } else if (data.type == 'sendPortToProxy') {
    sendPortToProxy(data.port);
  } else if (data.type == 'closeChooser') {
    closeChooser();
  }
}

var findLastTopLevelClient = undefined;

// Listen for 'fetch' and 'message', if in a service worker.
if (self.WorkerNavigator !== undefined) {
  self.addEventListener('fetch', onFetch);
  self.addEventListener('message', onMessage);

  // Finds the most recently opened top-level client belonging to this service
  // worker. Returns a promise. Rejects the promise if none found.
  findLastTopLevelClient = () => {
    return clients.matchAll().then(allClients => {
      for (var i = allClients.length - 1; i >= 0; i--) {
        if (allClients[i].frameType == 'top-level') {
          return allClients[i];
        }
      }
      throw new Error('No available clients; please open a tab.');
    });
  }
} else {
  // Receive messages from the service worker.
  navigator.serviceWorker.addEventListener('message', onMessage);
}

// Creates an iframe at |url|, and shows it in a popup window. Returns a promise
// that resolves once the iframe has loaded, with the iframe DOM element.
function createIframePopup(url) {
  chooserDiv = document.createElement('div');
  chooserDiv.style.zIndex = 100;
  chooserDiv.style.position = 'absolute';
  chooserDiv.style.left = '200px';
  chooserDiv.style.top = '200px';
  chooserDiv.style.width = '640px';
  chooserDiv.style.height = '480px';

  var iframe = document.createElement('iframe');
  return new Promise((resolve, reject) => {
    iframe.onload = e => resolve(iframe);

    iframe.setAttribute('src', url);
    iframe.style.width = '100%';
    iframe.style.height = '100%';

    chooserDiv.appendChild(iframe);
    document.body.appendChild(chooserDiv);
  });
}

// Establishes a connection with the polyfill handler (by embedding a page from
// the handler's domain in an iframe), and posts a MessagePort object to it.
// Asynchronous; no return value.
function sendPortToProxy(port) {
  if (self.document === undefined) {
    // We are in a service worker. No way to create an iframe here (needed to
    // establish a connection to handler service worker), so connect to a random
    // foreground page and establish a connection there (it does not need to be
    // the same foreground page that initiated the action, as long as it is
    // running this polyfill code). If there is no open requester foreground
    // page, this will fail. This *should not* happen in the real API, but there
    // is no way around this in the polyfill.
    findLastTopLevelClient().then(client => {
      client.postMessage({type: 'sendPortToProxy', port: port}, [port]);
    });
    return;
  }

  createIframePopup(kProxyUrl).then(iframe => {
    iframe.contentWindow.postMessage({port: port}, '*', [port]);
  });
}

// Destroy the chooser div.
function closeChooser() {
  if (self.document === undefined) {
    // We are in a service worker. Tell the foreground page to destroy the
    // chooser.
    findLastTopLevelClient().then(client => {
      client.postMessage({type: 'closeChooser'});
    });
    return;
  }

  document.body.removeChild(chooserDiv);
}

// Prompts the user for a handler app and establish a connection with the chosen
// handler. Returns (in a promise) a MessagePort on succesful connection to the
// handler.
function connectToHandler() {
  return new Promise(function(resolve, reject) {
    var channel = new MessageChannel();
    channel.port1.onmessage = e => {
      closeChooser();

      if (e.data.connected) {
        channel.port1.onmessage = null;
        resolve(channel.port1);
      } else if (e.data.connected == false) {
        // Fail silently. (This happens if the user cancels the dialog.)
      } else {
        reject(new Error("Received unexpected response from handler."));
      }
    };
    sendPortToProxy(channel.port2);
  });
};

// Polyfill Cache.addAll.
// Not necessary in Chrome 45 with --enable-experimental-web-platform-features.
if (Cache.prototype.addAll === undefined) {
  Cache.prototype.addAll = function(urls) {
    return Promise.all(urls.map(url => this.add(url)));
  }
}

// TODO(mgiuca): Rewrite prototype-based "classes" using actual ES6 classes.
// This is blocked on Firefox implementing the "super" keyword.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1066239

// A base class that implements the EventTarget interface.
function CustomEventTarget() {
  // Map from event type to list of listeners.
  this._listeners = {};
}

CustomEventTarget.prototype.addEventListener = function(type, listener,
                                                        useCapture) {
  var listeners = this._listeners;
  var listeners_of_type = listeners[type];
  if (listeners_of_type === undefined) {
    listeners[type] = listeners_of_type = [];
  }

  for (var i = 0; i < listeners_of_type.length; i++) {
    if (listeners_of_type[i] === listener) return;
  }

  listeners_of_type.push(listener);
};

CustomEventTarget.prototype.removeEventListener = function(type, listener,
                                                           useCapture) {
  var listeners = this._listeners;
  var listeners_of_type = listeners[type];
  if (listeners_of_type === undefined) {
    listeners[type] = listeners_of_type = [];
  }

  for (var i = 0; i < listeners_of_type.length; i++) {
    if (listeners_of_type[i] === listener) {
      listeners_of_type.splice(i, 1);

      if (listeners_of_type.length == 0) delete listeners[type];

      return;
    }
  }
};

CustomEventTarget.prototype.dispatchEvent = function(evt) {
  var listeners = this._listeners;
  var listeners_of_type = listeners[evt.type];
  if (listeners_of_type === undefined) {
    listeners[evt.type] = listeners_of_type = [];
  }

  for (var i = 0; i < listeners_of_type.length; i++) {
    var listener = listeners_of_type[i];
    if (listener.handleEvent !== undefined) {
      listener.handleEvent(evt);
    } else {
      listener.call(evt.target, evt);
    }
  }
};

// An Action is an object representing a web action in flight.
// Each Action has an integer |id|, which is unique among all actions from the
// requester that created it (different requesters can have actions of the same
// ID). This id is exposed to the client.
function Action(id) {
  this.id = id;
}

// A map from action IDs to MessagePort objects. Handler only.
var actionMap = new Map;

// The next action ID to use.
var nextActionId = 0;

var newHandleEvent = null;
var newUpdateEvent = null;

// Polyfill navigator.actions.
if (navigator.actions === undefined) {
  var NavigatorActions = function() {
    CustomEventTarget.call(this);
  };
  NavigatorActions.prototype = Object.create(CustomEventTarget.prototype);
  var actions = new NavigatorActions();
  navigator.actions = actions;

  var event_or_extendable_event = Event;

  // HandleEvent is only available when the global scope is a
  // ServiceWorkerGlobalScope.
  if (self.ExtendableEvent !== undefined) {
    newHandleEvent = function(id, options, data) {
      var event = new ExtendableEvent('handle');
      event.id = id;
      event.options = options;
      event.data = data;
      event.reject = err => {
        // TODO(mgiuca): Cause the promise to be rejected.
        throw err;
      }
      return event;
    }

    event_or_extendable_event = ExtendableEvent;
  }

  // UpdateEvent is an ExtendableEvent when the global scope is a
  // ServiceWorkerGlobalScope; otherwise it is just an Event.
  newUpdateEvent = function(id, data, done) {
    var event = new event_or_extendable_event('update');
    event.id = id;
    event.data = data;
    event.done = done;
    return event;
  }

  // Performs an action with a given |options| and |data|. |options| is either
  // a verb (string) or a dictionary of various fields used to identify which
  // handlers can be used. |data| is an arbitrary object to be passed to the
  // handler.
  //
  // Returns a Promise<Action> with an action object allowing further
  // interaction with the handler. Fails with AbortError if a connection could
  // not be made.
  actions.performAction = function(options, data) {
    return connectToHandler().then(port => {
      var id = nextActionId++;
      if (typeof options == 'string')
        options = {verb: options};
      var action = new Action(id);

      // Send the options and data payload to the handler.
      var message = {type: 'action', options: options, data: data, id: id};
      port.postMessage(message);
      port.onmessage = m => onMessageReceived(m.data, null);

      return action;
    });
  };

  // Sends an updated version of the data payload associated with an action back
  // to the requester. This may be called multiple times per action, but should
  // send a complete copy of the data on each call (this is not a stream
  // protocol). |id| is the id of the action.
  actions.update = function(id, data, done) {
    if (!actionMap.has(id))
      throw new Error('No such action id: ' + id);

    var port = actionMap.get(id);
    var message = {type: 'update', data: data, id: id, done: done == true};
    port.postMessage(message);
  };
}

// Called when a message is received (on both the handler and requester).
// |port| is a MessagePort on the handler; null on the requester.
function onMessageReceived(data, port) {
  if (data.type == 'action') {
    if (newHandleEvent === null) {
      throw new Error(
          'navigator.actions requests must go to a service worker.');
    }

    actionMap.set(data.id, port);

    // Forward the event as a 'handle' event to the global object.
    var handleEvent = newHandleEvent(data.id, data.options, data.data);
    actions.dispatchEvent(handleEvent);
  } else if (data.type == 'update') {
    // Forward the event as an 'update' event to navigator.actions.
    var updateEvent = newUpdateEvent(data.id, data.data, data.done);
    actions.dispatchEvent(updateEvent);
  } else {
    console.log('Received unknown message:', data);
  }
}

// Registers a handler with the proxy server.
function registerHandler(name, url, verbs) {
  console.log('registerHandler:', name, url, verbs);
}

// Parses the webapp manifest, and if there are any action handlers, prompts the
// user to register with the proxy server.
function performHandlerRegistration() {
  var links = document.querySelectorAll('link[rel=manifest]');
  for (var i = 0; i < links.length; i++) {
    var manifestUrl = links[i].href;
    fetch(manifestUrl).then(response => {
      if (!response.ok)
        return;

      response.json().then(json => {
        if (json.actions == undefined || json.actions.length == 0)
          return;

        // We have a manifest with action handlers. Attempt to register this
        // handler with the proxy server.
        var name = json.name || document.title;
        var url = json.scope || document.location.href;
        var verbs = [];
        for (var verb in json.actions)
          verbs.push(verb);
        registerHandler(name, url, verbs);
      });
    });
  }
}

addEventListener('load', performHandlerRegistration, false);

})();
<commit_msg>polyfill: Show the registration UI when a manifest is discovered with action handlers.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Ballista polyfill.
// Author: Matt Giuca <mgiuca@chromium.org>
//
// Implements a (partial) Ballista API for demonstration purposes. Note: This
// API, by design, does things that aren't possible in a polyfill, so this will
// be broken without specific browser hacks. All work-in-progress; don't get too
// excited about it.
"use strict";

(function() {

// Portions are copied (and heavily modified) from tha navigator-connect
// polyfill by mkruisselbrink and reillyeon:
// https://github.com/mkruisselbrink/navigator-connect

// The URL of the proxy app, which provides the handler chooser and routes
// actions through to the chosen handler.
// TODO(mgiuca): Move this to a fixed public location.
var kChooseUrl = 'http://localhost:8080/choose';

// The URL of the proxy app, which provides registration UI for handlers.
var kRegisterUrl = 'http://localhost:8080/register';

var kProxyUrlSuffix = '?actions-handler-proxy';

// DOM element for the <div> containing the chooser UI (hosted by the proxy
// site) in an iframe. Only non-null while choosing is taking place.
var chooserDiv = null;

// The handler's service worker provides a special proxy page that will be
// embedded in an iframe in the requester (by connectToHandler).
//
// This page proxies messages between the requester foreground page and the
// handler service worker.
function onFetch(event) {
  var targetUrl = event.request.url;
  // Detect whether kProxyUrlSuffix appears in the query string.
  var proxySuffixIndex = targetUrl.indexOf(kProxyUrlSuffix);
  if (proxySuffixIndex === -1 ||
      (proxySuffixIndex + kProxyUrlSuffix.length != targetUrl.length &&
       targetUrl[proxySuffixIndex + kProxyUrlSuffix.length] != '&')) {
    // Not a navigator-connect attempt
    return;
  }
  // In the real world this should not reply to all fetches.
  event.respondWith(fetch('polyfill/proxy-iframe.html'));
  event.stopImmediatePropagation();
}

function onMessage(event) {
  if (typeof event != 'object')
    return;

  var data = event.data;

  if (data === undefined)
    return;

  if (data.type == 'connect') {
    data.port.postMessage({connected: true});
    data.port.onmessage = e => {
      onMessageReceived(e.data, data.port);
    }
    event.stopImmediatePropagation();
    return;
  } else if (data.type == 'sendPortToProxy') {
    sendPortToProxy(data.port);
  } else if (data.type == 'closeChooser') {
    closeChooser();
  }
}

var findLastTopLevelClient = undefined;

// Listen for 'fetch' and 'message', if in a service worker.
if (self.WorkerNavigator !== undefined) {
  self.addEventListener('fetch', onFetch);
  self.addEventListener('message', onMessage);

  // Finds the most recently opened top-level client belonging to this service
  // worker. Returns a promise. Rejects the promise if none found.
  findLastTopLevelClient = () => {
    return clients.matchAll().then(allClients => {
      for (var i = allClients.length - 1; i >= 0; i--) {
        if (allClients[i].frameType == 'top-level') {
          return allClients[i];
        }
      }
      throw new Error('No available clients; please open a tab.');
    });
  }
} else {
  // Receive messages from the service worker.
  navigator.serviceWorker.addEventListener('message', onMessage);
}

// Creates an iframe at |url|, and shows it in a popup window. Returns a promise
// that resolves once the iframe has loaded, with the iframe DOM element.
function createIframePopup(url) {
  chooserDiv = document.createElement('div');
  chooserDiv.style.zIndex = 100;
  chooserDiv.style.position = 'absolute';
  chooserDiv.style.left = '200px';
  chooserDiv.style.top = '200px';
  chooserDiv.style.width = '640px';
  chooserDiv.style.height = '480px';

  var iframe = document.createElement('iframe');
  return new Promise((resolve, reject) => {
    iframe.onload = e => resolve(iframe);

    iframe.setAttribute('src', url);
    iframe.style.width = '100%';
    iframe.style.height = '100%';

    chooserDiv.appendChild(iframe);
    document.body.appendChild(chooserDiv);
  });
}

// Establishes a connection with the polyfill handler (by embedding a page from
// the handler's domain in an iframe), and posts a MessagePort object to it.
// Asynchronous; no return value.
function sendPortToProxy(port) {
  if (self.document === undefined) {
    // We are in a service worker. No way to create an iframe here (needed to
    // establish a connection to handler service worker), so connect to a random
    // foreground page and establish a connection there (it does not need to be
    // the same foreground page that initiated the action, as long as it is
    // running this polyfill code). If there is no open requester foreground
    // page, this will fail. This *should not* happen in the real API, but there
    // is no way around this in the polyfill.
    findLastTopLevelClient().then(client => {
      client.postMessage({type: 'sendPortToProxy', port: port}, [port]);
    });
    return;
  }

  createIframePopup(kChooseUrl).then(iframe => {
    iframe.contentWindow.postMessage({port: port}, '*', [port]);
  });
}

// Destroy the chooser div.
function closeChooser() {
  if (self.document === undefined) {
    // We are in a service worker. Tell the foreground page to destroy the
    // chooser.
    findLastTopLevelClient().then(client => {
      client.postMessage({type: 'closeChooser'});
    });
    return;
  }

  document.body.removeChild(chooserDiv);
}

// Prompts the user for a handler app and establish a connection with the chosen
// handler. Returns (in a promise) a MessagePort on succesful connection to the
// handler.
function connectToHandler() {
  return new Promise(function(resolve, reject) {
    var channel = new MessageChannel();
    channel.port1.onmessage = e => {
      closeChooser();

      if (e.data.connected) {
        channel.port1.onmessage = null;
        resolve(channel.port1);
      } else if (e.data.connected == false) {
        // Fail silently. (This happens if the user cancels the dialog.)
      } else {
        reject(new Error("Received unexpected response from handler."));
      }
    };
    sendPortToProxy(channel.port2);
  });
};

// Polyfill Cache.addAll.
// Not necessary in Chrome 45 with --enable-experimental-web-platform-features.
if (Cache.prototype.addAll === undefined) {
  Cache.prototype.addAll = function(urls) {
    return Promise.all(urls.map(url => this.add(url)));
  }
}

// TODO(mgiuca): Rewrite prototype-based "classes" using actual ES6 classes.
// This is blocked on Firefox implementing the "super" keyword.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1066239

// A base class that implements the EventTarget interface.
function CustomEventTarget() {
  // Map from event type to list of listeners.
  this._listeners = {};
}

CustomEventTarget.prototype.addEventListener = function(type, listener,
                                                        useCapture) {
  var listeners = this._listeners;
  var listeners_of_type = listeners[type];
  if (listeners_of_type === undefined) {
    listeners[type] = listeners_of_type = [];
  }

  for (var i = 0; i < listeners_of_type.length; i++) {
    if (listeners_of_type[i] === listener) return;
  }

  listeners_of_type.push(listener);
};

CustomEventTarget.prototype.removeEventListener = function(type, listener,
                                                           useCapture) {
  var listeners = this._listeners;
  var listeners_of_type = listeners[type];
  if (listeners_of_type === undefined) {
    listeners[type] = listeners_of_type = [];
  }

  for (var i = 0; i < listeners_of_type.length; i++) {
    if (listeners_of_type[i] === listener) {
      listeners_of_type.splice(i, 1);

      if (listeners_of_type.length == 0) delete listeners[type];

      return;
    }
  }
};

CustomEventTarget.prototype.dispatchEvent = function(evt) {
  var listeners = this._listeners;
  var listeners_of_type = listeners[evt.type];
  if (listeners_of_type === undefined) {
    listeners[evt.type] = listeners_of_type = [];
  }

  for (var i = 0; i < listeners_of_type.length; i++) {
    var listener = listeners_of_type[i];
    if (listener.handleEvent !== undefined) {
      listener.handleEvent(evt);
    } else {
      listener.call(evt.target, evt);
    }
  }
};

// An Action is an object representing a web action in flight.
// Each Action has an integer |id|, which is unique among all actions from the
// requester that created it (different requesters can have actions of the same
// ID). This id is exposed to the client.
function Action(id) {
  this.id = id;
}

// A map from action IDs to MessagePort objects. Handler only.
var actionMap = new Map;

// The next action ID to use.
var nextActionId = 0;

var newHandleEvent = null;
var newUpdateEvent = null;

// Polyfill navigator.actions.
if (navigator.actions === undefined) {
  var NavigatorActions = function() {
    CustomEventTarget.call(this);
  };
  NavigatorActions.prototype = Object.create(CustomEventTarget.prototype);
  var actions = new NavigatorActions();
  navigator.actions = actions;

  var event_or_extendable_event = Event;

  // HandleEvent is only available when the global scope is a
  // ServiceWorkerGlobalScope.
  if (self.ExtendableEvent !== undefined) {
    newHandleEvent = function(id, options, data) {
      var event = new ExtendableEvent('handle');
      event.id = id;
      event.options = options;
      event.data = data;
      event.reject = err => {
        // TODO(mgiuca): Cause the promise to be rejected.
        throw err;
      }
      return event;
    }

    event_or_extendable_event = ExtendableEvent;
  }

  // UpdateEvent is an ExtendableEvent when the global scope is a
  // ServiceWorkerGlobalScope; otherwise it is just an Event.
  newUpdateEvent = function(id, data, done) {
    var event = new event_or_extendable_event('update');
    event.id = id;
    event.data = data;
    event.done = done;
    return event;
  }

  // Performs an action with a given |options| and |data|. |options| is either
  // a verb (string) or a dictionary of various fields used to identify which
  // handlers can be used. |data| is an arbitrary object to be passed to the
  // handler.
  //
  // Returns a Promise<Action> with an action object allowing further
  // interaction with the handler. Fails with AbortError if a connection could
  // not be made.
  actions.performAction = function(options, data) {
    return connectToHandler().then(port => {
      var id = nextActionId++;
      if (typeof options == 'string')
        options = {verb: options};
      var action = new Action(id);

      // Send the options and data payload to the handler.
      var message = {type: 'action', options: options, data: data, id: id};
      port.postMessage(message);
      port.onmessage = m => onMessageReceived(m.data, null);

      return action;
    });
  };

  // Sends an updated version of the data payload associated with an action back
  // to the requester. This may be called multiple times per action, but should
  // send a complete copy of the data on each call (this is not a stream
  // protocol). |id| is the id of the action.
  actions.update = function(id, data, done) {
    if (!actionMap.has(id))
      throw new Error('No such action id: ' + id);

    var port = actionMap.get(id);
    var message = {type: 'update', data: data, id: id, done: done == true};
    port.postMessage(message);
  };
}

// Called when a message is received (on both the handler and requester).
// |port| is a MessagePort on the handler; null on the requester.
function onMessageReceived(data, port) {
  if (data.type == 'action') {
    if (newHandleEvent === null) {
      throw new Error(
          'navigator.actions requests must go to a service worker.');
    }

    actionMap.set(data.id, port);

    // Forward the event as a 'handle' event to the global object.
    var handleEvent = newHandleEvent(data.id, data.options, data.data);
    actions.dispatchEvent(handleEvent);
  } else if (data.type == 'update') {
    // Forward the event as an 'update' event to navigator.actions.
    var updateEvent = newUpdateEvent(data.id, data.data, data.done);
    actions.dispatchEvent(updateEvent);
  } else {
    console.log('Received unknown message:', data);
  }
}

// Registers a handler with the proxy server.
function registerHandler(handler) {
  // This channel will be used to get back a "close" signal from the proxy.
  var channel = new MessageChannel();
  channel.port1.onmessage = e => {
    if (e.data.close) {
      closeChooser();
    } else {
      reject(new Error("Received unexpected response from proxy."));
    }
  };

  // Show the registration UI.
  createIframePopup(kRegisterUrl)
      .then(iframe => {
        iframe.contentWindow.postMessage(
            {handler: handler, port: channel.port2}, '*', [channel.port2]);
      });
}

// Parses the webapp manifest, and if there are any action handlers, prompts the
// user to register with the proxy server.
function performHandlerRegistration() {
  var links = document.querySelectorAll('link[rel=manifest]');
  for (var i = 0; i < links.length; i++) {
    var manifestUrl = links[i].href;
    fetch(manifestUrl).then(response => {
      if (!response.ok)
        return;

      response.json().then(json => {
        if (json.actions == undefined || json.actions.length == 0)
          return;

        // We have a manifest with action handlers. Attempt to register this
        // handler with the proxy server.
        var name = json.name || document.title;
        var url = json.scope || document.location.href;
        var verbs = [];
        for (var verb in json.actions)
          verbs.push(verb);
        registerHandler({name: name, url: url, verbs: verbs});
      });
    });
  }
}

addEventListener('load', performHandlerRegistration, false);

})();
<|endoftext|>


<commit_before>goog.provide('ol.layer.Vector');

goog.require('goog.object');
goog.require('ol.layer.Layer');
goog.require('ol.style.Style');

/**
 * @enum {string}
 */
ol.layer.VectorProperty = {
  RENDER_ORDER: 'renderOrder'
};

/**
 * @classdesc
 * Vector data that is rendered client-side.
 * Note that any property set in the options is set as a {@link ol.Object}
 * property on the layer object; for example, setting `title: 'My Title'` in the
 * options means that `title` is observable, and has get/set accessors.
 *
 * @constructor
 * @extends {ol.layer.Layer}
 * @fires ol.render.Event
 * @param {olx.layer.VectorOptions=} opt_options Options.
 * @api stable
 */
ol.layer.Vector = function(opt_options) {

  var options = goog.isDef(opt_options) ?
      opt_options : /** @type {olx.layer.VectorOptions} */ ({});

  var baseOptions = goog.object.clone(options);

  delete baseOptions.style;
  delete baseOptions.renderBuffer;
  delete baseOptions.updateWhileAnimating;
  delete baseOptions.updateWhileInteracting;
  goog.base(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));

  /**
   * @type {number}
   * @private
   */
  this.renderBuffer_ = goog.isDef(options.renderBuffer) ?
      options.renderBuffer : 100;

  /**
   * User provided style.
   * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
   * @private
   */
  this.style_ = null;

  /**
   * Style function for use within the library.
   * @type {ol.style.StyleFunction|undefined}
   * @private
   */
  this.styleFunction_ = undefined;

  this.setStyle(options.style);

  /**
   * @type {boolean}
   * @private
   */
  this.updateWhileAnimating_ = goog.isDef(options.updateWhileAnimating) ?
      options.updateWhileAnimating : false;

  /**
   * @type {boolean}
   * @private
   */
  this.updateWhileInteracting_ = goog.isDef(options.updateWhileInteracting) ?
      options.updateWhileInteracting : false;

};
goog.inherits(ol.layer.Vector, ol.layer.Layer);

/**
 * @return {number|undefined} Render buffer.
 */
ol.layer.Vector.prototype.getRenderBuffer = function() {
  return this.renderBuffer_;
};

/**
 * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render
 *     order.
 */
ol.layer.Vector.prototype.getRenderOrder = function() {
  return /** @type {function(ol.Feature, ol.Feature):number|null|undefined} */ (
      this.get(ol.layer.VectorProperty.RENDER_ORDER));
};

/**
 * Return the associated {@link ol.source.Vector vectorsource} of the layer.
 * @function
 * @return {ol.source.Vector} Source.
 * @api stable
 */
ol.layer.Vector.prototype.getSource;

/**
 * Get the style for features.  This returns whatever was passed to the `style`
 * option at construction or to the `setStyle` method.
 * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
 *     Layer style.
 * @api stable
 */
ol.layer.Vector.prototype.getStyle = function() {
  return this.style_;
};

/**
 * Get the style function.
 * @return {ol.style.StyleFunction|undefined} Layer style function.
 * @api stable
 */
ol.layer.Vector.prototype.getStyleFunction = function() {
  return this.styleFunction_;
};

/**
 * @return {boolean} Whether the rendered layer should be updated while
 *     animating.
 */
ol.layer.Vector.prototype.getUpdateWhileAnimating = function() {
  return this.updateWhileAnimating_;
};

/**
 * @return {boolean} Whether the rendered layer should be updated while
 *     interacting.
 */
ol.layer.Vector.prototype.getUpdateWhileInteracting = function() {
  return this.updateWhileInteracting_;
};

/**
 * @param {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder
 *     Render order.
 */
ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) {
  this.set(ol.layer.VectorProperty.RENDER_ORDER, renderOrder);
};

/**
 * Set the style for features.  This can be a single style object, an array
 * of styles, or a function that takes a feature and resolution and returns
 * an array of styles. If it is `undefined` the default style is used. If
 * it is `null` the layer has no style (a `null` style), so only features
 * that have their own styles will be rendered in the layer. See
 * {@link ol.style} for information on the default style.
 * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction|undefined}
 *     style Layer style.
 * @api stable
 */
ol.layer.Vector.prototype.setStyle = function(style) {
  this.style_ = goog.isDef(style) ? style : ol.style.defaultStyleFunction;
  this.styleFunction_ = goog.isNull(style) ?
      undefined : ol.style.createStyleFunction(this.style_);
  this.changed();
};
<commit_msg>Add assertion for renderOrder<commit_after>goog.provide('ol.layer.Vector');

goog.require('goog.asserts');
goog.require('goog.object');
goog.require('ol.layer.Layer');
goog.require('ol.style.Style');

/**
 * @enum {string}
 */
ol.layer.VectorProperty = {
  RENDER_ORDER: 'renderOrder'
};

/**
 * @classdesc
 * Vector data that is rendered client-side.
 * Note that any property set in the options is set as a {@link ol.Object}
 * property on the layer object; for example, setting `title: 'My Title'` in the
 * options means that `title` is observable, and has get/set accessors.
 *
 * @constructor
 * @extends {ol.layer.Layer}
 * @fires ol.render.Event
 * @param {olx.layer.VectorOptions=} opt_options Options.
 * @api stable
 */
ol.layer.Vector = function(opt_options) {

  var options = goog.isDef(opt_options) ?
      opt_options : /** @type {olx.layer.VectorOptions} */ ({});

  goog.asserts.assert(
      !goog.isDef(options.renderOrder) || goog.isNull(options.renderOrder) ||
      goog.isFunction(options.renderOrder),
      'renderOrder must be a comparator function');

  var baseOptions = goog.object.clone(options);

  delete baseOptions.style;
  delete baseOptions.renderBuffer;
  delete baseOptions.updateWhileAnimating;
  delete baseOptions.updateWhileInteracting;
  goog.base(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));

  /**
   * @type {number}
   * @private
   */
  this.renderBuffer_ = goog.isDef(options.renderBuffer) ?
      options.renderBuffer : 100;

  /**
   * User provided style.
   * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
   * @private
   */
  this.style_ = null;

  /**
   * Style function for use within the library.
   * @type {ol.style.StyleFunction|undefined}
   * @private
   */
  this.styleFunction_ = undefined;

  this.setStyle(options.style);

  /**
   * @type {boolean}
   * @private
   */
  this.updateWhileAnimating_ = goog.isDef(options.updateWhileAnimating) ?
      options.updateWhileAnimating : false;

  /**
   * @type {boolean}
   * @private
   */
  this.updateWhileInteracting_ = goog.isDef(options.updateWhileInteracting) ?
      options.updateWhileInteracting : false;

};
goog.inherits(ol.layer.Vector, ol.layer.Layer);

/**
 * @return {number|undefined} Render buffer.
 */
ol.layer.Vector.prototype.getRenderBuffer = function() {
  return this.renderBuffer_;
};

/**
 * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render
 *     order.
 */
ol.layer.Vector.prototype.getRenderOrder = function() {
  return /** @type {function(ol.Feature, ol.Feature):number|null|undefined} */ (
      this.get(ol.layer.VectorProperty.RENDER_ORDER));
};

/**
 * Return the associated {@link ol.source.Vector vectorsource} of the layer.
 * @function
 * @return {ol.source.Vector} Source.
 * @api stable
 */
ol.layer.Vector.prototype.getSource;

/**
 * Get the style for features.  This returns whatever was passed to the `style`
 * option at construction or to the `setStyle` method.
 * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction}
 *     Layer style.
 * @api stable
 */
ol.layer.Vector.prototype.getStyle = function() {
  return this.style_;
};

/**
 * Get the style function.
 * @return {ol.style.StyleFunction|undefined} Layer style function.
 * @api stable
 */
ol.layer.Vector.prototype.getStyleFunction = function() {
  return this.styleFunction_;
};

/**
 * @return {boolean} Whether the rendered layer should be updated while
 *     animating.
 */
ol.layer.Vector.prototype.getUpdateWhileAnimating = function() {
  return this.updateWhileAnimating_;
};

/**
 * @return {boolean} Whether the rendered layer should be updated while
 *     interacting.
 */
ol.layer.Vector.prototype.getUpdateWhileInteracting = function() {
  return this.updateWhileInteracting_;
};

/**
 * @param {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder
 *     Render order.
 */
ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) {
  goog.asserts.assert(
      !goog.isDef(renderOrder) || goog.isNull(renderOrder) ||
      goog.isFunction(renderOrder),
      'renderOrder must be a comparator function');
  this.set(ol.layer.VectorProperty.RENDER_ORDER, renderOrder);
};

/**
 * Set the style for features.  This can be a single style object, an array
 * of styles, or a function that takes a feature and resolution and returns
 * an array of styles. If it is `undefined` the default style is used. If
 * it is `null` the layer has no style (a `null` style), so only features
 * that have their own styles will be rendered in the layer. See
 * {@link ol.style} for information on the default style.
 * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction|undefined}
 *     style Layer style.
 * @api stable
 */
ol.layer.Vector.prototype.setStyle = function(style) {
  this.style_ = goog.isDef(style) ? style : ol.style.defaultStyleFunction;
  this.styleFunction_ = goog.isNull(style) ?
      undefined : ol.style.createStyleFunction(this.style_);
  this.changed();
};
<|endoftext|>