<commit_before>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you 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.
 */

/*
 * Dependencies
 *
 * /dev/lib/jquery/plugins/jqmodal.sakai-edited.js
 */

require(['jquery', 'sakai/sakai.api.core'], function($, sakai) {

    /**
     * @name sakai_global.savecontent
     *
     * @class savecontent
     *
     * @description
     * Savecontent dialog box
     *
     * @version 0.0.1
     * @param {String} tuid Unique id of the widget
     * @param {Boolean} showSettings Show the settings of the widget or not
     */
    sakai_global.savecontent = function(tuid, showSettings) {

        var $rootel = $('#' + tuid);
        var $savecontent_widget = $('#savecontent_widget', $rootel);
        var $savecontent_container = $('#savecontent_container', $rootel);
        var $savecontent_template = $('#savecontent_template', $rootel);
        var $savecontent_close = $('.savecontent_close', $rootel);
        var $savecontent_save = $('#savecontent_save', $rootel);
        var newlyShared = {};
        var allNewlyShared = [];
        var contentObj = {};
        var clickedEl = null;

        $savecontent_widget.jqm({
            modal: false,
            overlay: 0,
            zIndex: 1000,
            toTop: true
        });

        sakai_global.savecontent.getNewContent = function(library) {
            if (!library) {
                return allNewlyShared;
            } else if (newlyShared[library]) {
                return newlyShared[library];
            } else {
                return [];
            }
        };

        var deleteContent = function(e, paths) {
            if (paths && paths.length) {
                $.each(paths, function(i, path) {
                    $.each(allNewlyShared, function(j, newlyShared) {
                        if (newlyShared && newlyShared._path === path) {
                            allNewlyShared.splice(j,1);
                        }
                    });
                    $.each(newlyShared, function(lib, items) {
                        $.each(items, function(k, item) {
                            if (item && item._path === path) {
                                items.splice(k,1);
                            }
                        });
                    });
                });
            }
        };

        $(document).on('done.deletecontent.sakai', deleteContent);

        /**
         * toggleSavecontent
         * Displays the widget
         */
        var toggleSavecontent = function() {

            $savecontent_save.removeAttr('disabled');

            var savecontentTop = clickedEl.offset().top + clickedEl.height() - 3;
            var savecontentLeft = clickedEl.offset().left + clickedEl.width() / 2 - 122;

            $savecontent_widget.css({
                top: savecontentTop,
                left: savecontentLeft
            });

            sakai.api.Util.getTemplates(function(success, templates) {
                if (success) {
                    var json = {
                        'files': contentObj.data,
                        'context': contentObj.context,
                        'libraryHasIt': contentObj.libraryHasIt,
                        'groups': contentObj.memberOfGroups,
                        'sakai': sakai,
                        'templates': templates
                    };
                    $savecontent_container.html(sakai.api.Util.TemplateRenderer('#savecontent_template', json));
                    enableDisableAddButton();
                    $savecontent_widget.jqmShow();
                } else {
                    debug.error('Could not get the group templates');
                }
            });
        };

        var getFileIDs = function() {
            var tempArr = [];
            $.each(contentObj.data, function(i, content) {
                tempArr.push(content.body['_path']);
            });
            return tempArr;
        };

        /**
         * Checks if the content is a part of my library
         */
        var selectedAlreadyMyLibraryMember = function() {
            contentObj.libraryHasIt = true;
            $.each(contentObj.data, function(i, content) {
                if (!sakai.api.Content.isContentInLibrary(content.body, sakai.data.me.user.userid) && !sakai.api.Content.Collections.isCollectionInMyLibrary(content.body)) {
                    contentObj.libraryHasIt = false;
                }
                content.body.canShare = sakai.api.Content.canCurrentUserShareContent(content.body);
            });
            toggleSavecontent();
        };

        /**
         * Check if collections are in the content data set, and fetch their members
         */
        var checkCollectionMembers = function(callback) {
            var checkCollections = [];
            $.each(contentObj.data, function(i, selectedContent) {
                var contentItem = selectedContent.body;
                if (!(sakai_global.content_profile && sakai_global.content_profile.content_data &&
                    sakai_global.content_profile.content_data.content_path === '/p/' + contentItem._path) &&
                    sakai.api.Content.Collections.isCollection(contentItem)) {
                    var collectionId = sakai.api.Content.Collections.getCollectionGroupId(contentItem);
                    if (checkCollections.indexOf(collectionId) < 0) {
                        checkCollections.push(sakai.api.Content.Collections.getCollectionGroupId(contentItem));
                    }
                }
            });
            if (checkCollections.length > 0) {
                sakai.api.Groups.getMembers(checkCollections, function(success, data) {
                    $.each(contentObj.data, function(i, contentItem) {
                        if (sakai.api.Content.Collections.isCollection(contentItem.body)) {
                            var collectionId = sakai.api.Content.Collections.getCollectionGroupId(contentItem.body);
                            if (data[collectionId]) {
                                contentItem.body.members = {
                                    'managers': data[collectionId].managers.results,
                                    'viewers': data[collectionId].members.results
                                };
                            }
                        }
                    });
                    callback();
                }, false, true);
            } else {
                callback();
            }
        };

        /**
         * Determines if the selected content items are a part of any groups
         */
        var selectAlreadyInGroup = function() {
            checkCollectionMembers(function() {
                $.each(contentObj.memberOfGroups.entry, function(j, memberOfGroup) {
                    memberOfGroup.alreadyHasIt = true;
                    $.each(contentObj.data, function(i, selectedContent) {
                        var contentItem = selectedContent.body;
                        if (sakai_global.content_profile && sakai_global.content_profile.content_data &&
                            sakai_global.content_profile.content_data.content_path === '/p/' + contentItem._path) {
                            contentItem = sakai_global.content_profile.content_data;
                        }
                        var isContentInGroup = sakai.api.Content.isContentInLibrary(contentItem, memberOfGroup['sakai:group-id']);
                        if (!isContentInGroup) {
                            memberOfGroup.alreadyHasIt = false;
                        }
                    });
                });
                selectedAlreadyMyLibraryMember();
            });
        };

        /**
         * hideSavecontent
         * Hides the widget
         */
        var hideSavecontent = function() {
            $(window).trigger('hiding.savecontent.sakai');
            $savecontent_widget.jqmHide();
        };

        /**
         * saveContent
         * Saves the content to the selected group
         * @param {String} id     ID of the group we want to add as a viewer
         */
        var saveContent = function(id) {
            if ($('#savecontent_select option:selected', $rootel).data('redirect') !== true) {
                $savecontent_save.attr('disabled', 'disabled');
                $.each(contentObj.data, function(i, content) {
                    if (sakai.api.Content.Collections.isCollection(content.body)) {
                        sakai.api.Content.Collections.shareCollection(content.body['_path'], id, false, function() {
                            finishSaveContent(content.body['_path'], id);
                        });
                    } else {
                        sakai.api.Content.addToLibrary(content.body['_path'], id, false, finishSaveContent);
                    }
                });
                $(document).trigger('done.newaddcontent.sakai');
                var notificationBody = false;
                var notificationTitle = false;
                if (sakai.api.Content.Collections.isCollection(id)) {
                    notificationBody = decodeURIComponent($('#savecontent_collection_add_library_body').html());
                    notificationBody = notificationBody.replace('${collectionid}', sakai.api.Security.safeOutput(sakai.api.Content.Collections.getCollectionPoolId(id)));
                    notificationBody = notificationBody.replace('${collectiontitle}', sakai.api.Security.safeOutput($('#savecontent_select option:selected', $rootel).text()));
                    notificationTitle = $('#savecontent_collection_add_library_title').html();
                } else if (id === sakai.data.me.user.userid) {
                    notificationBody = decodeURIComponent($('#savecontent_my_add_library_body').html());
                    notificationTitle = $('#savecontent_group_add_library_title').html();
                } else {
                    notificationBody = decodeURIComponent($('#savecontent_group_add_library_body').html());
                    notificationBody = notificationBody.replace('${groupid}', sakai.api.Security.safeOutput(id));
                    notificationBody = notificationBody.replace('${grouplibrary}', sakai.api.Security.safeOutput($('#savecontent_select option:selected', $rootel).text()));
                    notificationTitle = $('#savecontent_group_add_library_title').html();
                }
                sakai.api.Util.notification.show(notificationTitle, notificationBody);
                hideSavecontent();
            } else {
                document.location = '/create#l=' + $('#savecontent_select', $rootel).val() + '&contentToAdd=' + getFileIDs().toString();
            }
        };

        var finishSaveContent = function(contentId, entityId) {
            // cache the content locally
            if (sakai_global.content_profile) {
                sakai_global.content_profile.content_data.members.viewers.push({
                    'userid': entityId
                });
            }
            $(window).trigger('sakai.entity.updateOwnCounts', {contentId:contentId,entityID:entityId});
        };

        enableDisableAddButton = function() {
            var dropdownSelection = $('#savecontent_select option:selected', $rootel);
            if (dropdownSelection.attr('disabled') || !dropdownSelection.val()) {
                $savecontent_save.attr('disabled', 'disabled');
            } else {
                $savecontent_save.removeAttr('disabled');
            }
        };

        // bind savecontent cancel
        $savecontent_close.on('click', function() {
            hideSavecontent();
        });

        // bind savecontent save button
        $savecontent_save.on('click', function() {
            var dropdownSelection = $('#savecontent_select option:selected', $rootel);
            if (dropdownSelection.val() === 'new_collection') {
                var contentToAdd = [];
                $.each(contentObj.data, function(index, item) {
                    contentToAdd.push(item.body);
                });
                hideSavecontent();
                $(document).trigger('create.collections.sakai', [contentToAdd]);
            } else if (!dropdownSelection.is(':disabled') && dropdownSelection.val()) {
                saveContent(dropdownSelection.val());
            }
        });

        $('#savecontent_select', $rootel).on('change', function() {
            enableDisableAddButton();
        });

        sakai.api.Util.hideOnClickOut('.savecontent_dialog', '.savecontent_trigger', hideSavecontent);

        $(document).on('click', '.savecontent_trigger', function(el) {
            clickedEl = $(this);
            idArr = clickedEl.attr('data-entityid');
            if (idArr.length > 1 && !$.isArray(idArr)) {
                idArr = idArr.split(',');
            }

            contentObj.memberOfGroups = $.extend(true, {}, sakai.api.Groups.getMemberships(sakai.data.me.groups, true));
            contentObj.context = $(el.currentTarget).attr('data-entitycontext') || false;

            var batchRequests = [];
            $.each(idArr, function(i, id) {
                batchRequests.push({
                    'url': '/p/' + id + '.2.json',
                    'method': 'GET'
                });
            });
            sakai.api.Server.batch(batchRequests, function(success, data) {
                if (success) {
                    $.each(data.results, function(i, content) {
                        data.results[i].body = $.parseJSON(data.results[i].body);
                    });
                    contentObj.data = data.results;
                    selectAlreadyInGroup();
                }
            });
        });
    };

    sakai.api.Widgets.widgetLoader.informOnLoad('savecontent');
});
<commit_msg>SAKIII-6186 - IE8: Search results: Clicking the plus icon on content in your library does show that you've 'got it'<commit_after>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you 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.
 */

/*
 * Dependencies
 *
 * /dev/lib/jquery/plugins/jqmodal.sakai-edited.js
 */

require(['jquery', 'sakai/sakai.api.core'], function($, sakai) {

    /**
     * @name sakai_global.savecontent
     *
     * @class savecontent
     *
     * @description
     * Savecontent dialog box
     *
     * @version 0.0.1
     * @param {String} tuid Unique id of the widget
     * @param {Boolean} showSettings Show the settings of the widget or not
     */
    sakai_global.savecontent = function(tuid, showSettings) {

        var $rootel = $('#' + tuid);
        var $savecontent_widget = $('#savecontent_widget', $rootel);
        var $savecontent_container = $('#savecontent_container', $rootel);
        var $savecontent_template = $('#savecontent_template', $rootel);
        var $savecontent_close = $('.savecontent_close', $rootel);
        var $savecontent_save = $('#savecontent_save', $rootel);
        var newlyShared = {};
        var allNewlyShared = [];
        var contentObj = {};
        var clickedEl = null;

        $savecontent_widget.jqm({
            modal: false,
            overlay: 0,
            zIndex: 1000,
            toTop: true
        });

        sakai_global.savecontent.getNewContent = function(library) {
            if (!library) {
                return allNewlyShared;
            } else if (newlyShared[library]) {
                return newlyShared[library];
            } else {
                return [];
            }
        };

        var deleteContent = function(e, paths) {
            if (paths && paths.length) {
                $.each(paths, function(i, path) {
                    $.each(allNewlyShared, function(j, newlyShared) {
                        if (newlyShared && newlyShared._path === path) {
                            allNewlyShared.splice(j,1);
                        }
                    });
                    $.each(newlyShared, function(lib, items) {
                        $.each(items, function(k, item) {
                            if (item && item._path === path) {
                                items.splice(k,1);
                            }
                        });
                    });
                });
            }
        };

        $(document).on('done.deletecontent.sakai', deleteContent);

        /**
         * toggleSavecontent
         * Displays the widget
         */
        var toggleSavecontent = function() {

            $savecontent_save.removeAttr('disabled');

            var savecontentTop = clickedEl.offset().top + clickedEl.height() - 3;
            var savecontentLeft = clickedEl.offset().left + clickedEl.width() / 2 - 122;

            $savecontent_widget.css({
                top: savecontentTop,
                left: savecontentLeft
            });

            sakai.api.Util.getTemplates(function(success, templates) {
                if (success) {
                    var json = {
                        'files': contentObj.data,
                        'context': contentObj.context,
                        'libraryHasIt': contentObj.libraryHasIt,
                        'groups': contentObj.memberOfGroups,
                        'sakai': sakai,
                        'templates': templates
                    };
                    $savecontent_container.html(sakai.api.Util.TemplateRenderer('#savecontent_template', json));
                    enableDisableAddButton();
                    $savecontent_widget.jqmShow();
                } else {
                    debug.error('Could not get the group templates');
                }
            });
        };

        var getFileIDs = function() {
            var tempArr = [];
            $.each(contentObj.data, function(i, content) {
                tempArr.push(content.body['_path']);
            });
            return tempArr;
        };

        /**
         * Checks if the content is a part of my library
         */
        var selectedAlreadyMyLibraryMember = function() {
            contentObj.libraryHasIt = true;
            $.each(contentObj.data, function(i, content) {
                if (!sakai.api.Content.isContentInLibrary(content.body, sakai.data.me.user.userid) && !sakai.api.Content.Collections.isCollectionInMyLibrary(content.body)) {
                    contentObj.libraryHasIt = false;
                }
                content.body.canShare = sakai.api.Content.canCurrentUserShareContent(content.body);
            });
            toggleSavecontent();
        };

        /**
         * Check if collections are in the content data set, and fetch their members
         */
        var checkCollectionMembers = function(callback) {
            var checkCollections = [];
            $.each(contentObj.data, function(i, selectedContent) {
                var contentItem = selectedContent.body;
                if (!(sakai_global.content_profile && sakai_global.content_profile.content_data &&
                    sakai_global.content_profile.content_data.content_path === '/p/' + contentItem._path) &&
                    sakai.api.Content.Collections.isCollection(contentItem)) {
                    var collectionId = sakai.api.Content.Collections.getCollectionGroupId(contentItem);
                    if (checkCollections.indexOf(collectionId) < 0) {
                        checkCollections.push(sakai.api.Content.Collections.getCollectionGroupId(contentItem));
                    }
                }
            });
            if (checkCollections.length > 0) {
                sakai.api.Groups.getMembers(checkCollections, function(success, data) {
                    $.each(contentObj.data, function(i, contentItem) {
                        if (sakai.api.Content.Collections.isCollection(contentItem.body)) {
                            var collectionId = sakai.api.Content.Collections.getCollectionGroupId(contentItem.body);
                            if (data[collectionId]) {
                                contentItem.body.members = {
                                    'managers': data[collectionId].managers.results,
                                    'viewers': data[collectionId].members.results
                                };
                            }
                        }
                    });
                    callback();
                }, false, true);
            } else {
                callback();
            }
        };

        /**
         * Determines if the selected content items are a part of any groups
         */
        var selectAlreadyInGroup = function() {
            checkCollectionMembers(function() {
                $.each(contentObj.memberOfGroups.entry, function(j, memberOfGroup) {
                    memberOfGroup.alreadyHasIt = true;
                    $.each(contentObj.data, function(i, selectedContent) {
                        var contentItem = selectedContent.body;
                        if (sakai_global.content_profile && sakai_global.content_profile.content_data &&
                            sakai_global.content_profile.content_data.content_path === '/p/' + contentItem._path) {
                            contentItem = sakai_global.content_profile.content_data;
                        }
                        var isContentInGroup = sakai.api.Content.isContentInLibrary(contentItem, memberOfGroup['sakai:group-id']);
                        if (!isContentInGroup) {
                            memberOfGroup.alreadyHasIt = false;
                        }
                    });
                });
                selectedAlreadyMyLibraryMember();
            });
        };

        /**
         * hideSavecontent
         * Hides the widget
         */
        var hideSavecontent = function() {
            $(window).trigger('hiding.savecontent.sakai');
            $savecontent_widget.jqmHide();
        };

        /**
         * saveContent
         * Saves the content to the selected group
         * @param {String} id     ID of the group we want to add as a viewer
         */
        var saveContent = function(id) {
            if ($('#savecontent_select option:selected', $rootel).data('redirect') !== true) {
                $savecontent_save.attr('disabled', 'disabled');
                $.each(contentObj.data, function(i, content) {
                    if (sakai.api.Content.Collections.isCollection(content.body)) {
                        sakai.api.Content.Collections.shareCollection(content.body['_path'], id, false, function() {
                            finishSaveContent(content.body['_path'], id);
                        });
                    } else {
                        sakai.api.Content.addToLibrary(content.body['_path'], id, false, finishSaveContent);
                    }
                });
                $(document).trigger('done.newaddcontent.sakai');
                var notificationBody = false;
                var notificationTitle = false;
                if (sakai.api.Content.Collections.isCollection(id)) {
                    notificationBody = decodeURIComponent($('#savecontent_collection_add_library_body').html());
                    notificationBody = notificationBody.replace('${collectionid}', sakai.api.Security.safeOutput(sakai.api.Content.Collections.getCollectionPoolId(id)));
                    notificationBody = notificationBody.replace('${collectiontitle}', sakai.api.Security.safeOutput($('#savecontent_select option:selected', $rootel).text()));
                    notificationTitle = $('#savecontent_collection_add_library_title').html();
                } else if (id === sakai.data.me.user.userid) {
                    notificationBody = decodeURIComponent($('#savecontent_my_add_library_body').html());
                    notificationTitle = $('#savecontent_group_add_library_title').html();
                } else {
                    notificationBody = decodeURIComponent($('#savecontent_group_add_library_body').html());
                    notificationBody = notificationBody.replace('${groupid}', sakai.api.Security.safeOutput(id));
                    notificationBody = notificationBody.replace('${grouplibrary}', sakai.api.Security.safeOutput($('#savecontent_select option:selected', $rootel).text()));
                    notificationTitle = $('#savecontent_group_add_library_title').html();
                }
                sakai.api.Util.notification.show(notificationTitle, notificationBody);
                hideSavecontent();
            } else {
                document.location = '/create#l=' + $('#savecontent_select', $rootel).val() + '&contentToAdd=' + getFileIDs().toString();
            }
        };

        var finishSaveContent = function(contentId, entityId) {
            // cache the content locally
            if (sakai_global.content_profile) {
                sakai_global.content_profile.content_data.members.viewers.push({
                    'userid': entityId
                });
            }
            $(window).trigger('sakai.entity.updateOwnCounts', {contentId:contentId,entityID:entityId});
        };

        enableDisableAddButton = function() {
            var dropdownSelection = $('#savecontent_select option:selected', $rootel);
            if (dropdownSelection.attr('disabled') || !dropdownSelection.val()) {
                $savecontent_save.attr('disabled', 'disabled');
            } else {
                $savecontent_save.removeAttr('disabled');
            }
        };

        // bind savecontent cancel
        $savecontent_close.on('click', function() {
            hideSavecontent();
        });

        // bind savecontent save button
        $savecontent_save.on('click', function() {
            var dropdownSelection = $('#savecontent_select option:selected', $rootel);
            if (dropdownSelection.val() === 'new_collection') {
                var contentToAdd = [];
                $.each(contentObj.data, function(index, item) {
                    contentToAdd.push(item.body);
                });
                hideSavecontent();
                $(document).trigger('create.collections.sakai', [contentToAdd]);
            } else if (!dropdownSelection.is(':disabled') && dropdownSelection.val()) {
                saveContent(dropdownSelection.val());
            }
        });

        $('#savecontent_select', $rootel).on('change', function() {
            enableDisableAddButton();
        });

        sakai.api.Util.hideOnClickOut('.savecontent_dialog', '.savecontent_trigger', hideSavecontent);

        $(document).on('click', '.savecontent_trigger', function(el) {
            clickedEl = $(this);
            idArr = clickedEl.attr('data-entityid');
            if (idArr.length > 1 && !$.isArray(idArr)) {
                idArr = idArr.split(',');
            }

            contentObj.memberOfGroups = $.extend(true, {}, sakai.api.Groups.getMemberships(sakai.data.me.groups, true));
            contentObj.context = $(el.currentTarget).attr('data-entitycontext') || false;

            var batchRequests = [];
            $.each(idArr, function(i, id) {
                batchRequests.push({
                    'url': '/p/' + id + '.2.json',
                    'method': 'GET',
                    'cache': false
                });
            });
            sakai.api.Server.batch(batchRequests, function(success, data) {
                if (success) {
                    $.each(data.results, function(i, content) {
                        data.results[i].body = $.parseJSON(data.results[i].body);
                    });
                    contentObj.data = data.results;
                    selectAlreadyInGroup();
                }
            });
        });
    };

    sakai.api.Widgets.widgetLoader.informOnLoad('savecontent');
});
<|endoftext|>


<commit_before>"use strict";
var async = require('async');
var fixedURIencode = require('./encoder');

class Remover{
	constructor(){

	}

	remove(type, text, cb){
		cb(new Error("Undefined method called!"));
	}
}
//attachment:주소 형식의 이미지 제거
class AttachmentRemover extends Remover{
	constructor(){
		super();
		this.regex = /^attachment[ ]*:[ ]*(.*\..*)$/mg;
	}

	remove(type, text, cb){
		switch(type){
			case 'replace':
				text = text.replace(this.regex, (match, p1) => {
					return '[이미지](https://attach.namu.wiki/' + fixedURIencode(p1) + ')';
				});
				break;

			case 'tag':
				text = text.replace(this.regex, '$1');
				break;

			case 'whole':
				text = text.replace(this.regex, '');
				break;
		}

		cb(text);
	}
}

class BraceRemover extends Remover{
	constructor(tag){
		super();
		this.tag = tag;
	}

	remove(type, text, cb){
		switch(type){
			case 'tag':
				text = text.replace(new RegExp("{{{" + this.tag, 'g'), '').replace(/}}}/g, '');
				break;

			case 'whole':
				text = text.replace(new RegExp("{{{" + this.tag + "[^]*?}}}", 'g'), '');
				break;
		}

		cb(text);
	}
}

class FootnoteRemover extends Remover{
	constructor(){
		super();
		this.regex = /\[\*[^ ]*[ ]*(.+?)[^\]][\]]{1}(?!\]+)/g;
	}

	remove(type, text, cb){
		switch(type){
			case 'tag':
				text = text.replace(this.regex, '$1');
				break;

			case 'whole':
				text = text.replace(this.regex, '');
				break;
		}

		cb(text);
	}
}

class HyperlinkRemover extends Remover{
	constructor(){
		super();
		this.regex = /\[\[([^\[\]\|]*)(?:\|([^\[\]]*))?\]\]/g;
	}

	remove(param, text, cb){
		var split = param.split("|");
		var type = split[0];
		var paragraph = split[1];

		if(paragraph === 'noparagraph') text = text.replace(/\[\[#s-\d(\.\d)*(?:\|.*)?\]\]/g, '');

		if(p1.startsWith(':파일:')) p1 = p1.replace(':파일:', 'https://namu.wiki/file/');
		switch(type){
			case 'whole':
				cb(text.replace(/\[\[[^\[\]]*\]\]/g, ''));
				return;

			case 'replace':
				text = text.replace(this.regex, function(match, p1, p2){
					if(!p2){
						return p1;
					}

					if(!/^(http|https):\/\/[^]+/.match(text)){
						return p2 + "(" + p1 + ")";
					}

					return '[' + p2 + ']' + '(' + fixedURIencode(p1) + ')';
				});

			case 'former':
				text = text.replace(this.regex, function(match, p1){
					return p1;
				});
				break;

			case 'latter':
				text = text.replace(this.regex, function(match, p1, p2){
					if(!p2){
						return p1;
					}
					return p2;
				});
				break;

			case 'tag':
				text = text.replace(/\[\[/g, '').replace(/\]\]/g, '');
				break;
		}

		cb(text);
	}
}

//링크만 있는 이미지 제거
class ImageRemover extends Remover{
	constructor(){
		super();
		this.regex = /((https|http)?:\/\/[^ ]+\.(jpg|jpeg|png|gif))(?:\?([^ ]+))?/ig;
	}

	remove(type, text, cb){
		 switch(type){
			 case 'whole': text = text.replace(this.regex, ''); break;
			 case 'replace': text = text.replace(this.regex, (match, p1) => {
				 return '[이미지](' + fixedURIencode(p1) + ')';
			 }); break;
		 }

		 cb(text);
	}
}

class IncludeRemover extends Remover{
	constructor(){
		super();
	}

	remove(type, text, cb){
		if(type === "whole"){
			text = text.replace(/include\[\(.*\)\]/ig, '').replace(/include\(.*\)/ig, '');
		}

		cb(text);
	}
}

class LineBreakRemover extends Remover{
	constructor(){
		super();
	}

	remove(type, text, cb){
		switch(type){
			case "whole": text = text.replace(/\[br\]/g, ''); break;
			case "replace": text = text.replace(/\[br\]/g, '\n'); break;
		}

		cb(text);
	}
}

class MultipleDefinitionRemover extends Remover{
	constructor(){
		super();
		this.removers = arguments;
	}

	remove(type, text, cb){
		async.each(this.removers, function(v, callback){
			v.remove(type, text, function(res){
				text = res;
				callback();
			});
		}, function(){
			cb(text)
		});
	}
}

//[[파일:이미지]] 형식의 이미지 제거
class NamuImageRemover extends Remover{
	constructor(){
		super();
		this.regex = /\[\[파일:([^\[\]]*?)\]\]/g;
	}

	remove(type, text, cb, query){
		switch(type){
			case 'whole':
				text = text.replace(this.regex, '');
				break;

			case 'replace':
				text = text.replace(this.regex, (match, p1) => {
					return '[이미지](https://namu.wiki/file/' + fixedURIencode(p1) + ')';
				});
				break;
		}

		cb(text);
	}
}

class QuoteRemover extends Remover{
	constructor(){
		super();
	}

	remove(type, text, cb){
		switch(type){
			case 'replace':
				text = text.replace(/^>(.*)$/gm, '` $1`');
				break;

			case 'tag':
				text = text.replace(/^>/gm, '');
				break;

			case 'whole':
				text = text.replace(/^>.*$/gm, '');
				break;
		}

		cb(text);
	}
}

class SimpleTagRemover extends Remover{
	constructor(tag, polyfill){
		super();
		this.tag = tag;
		this.polyfill = polyfill;
	}

	remove(type, text, cb){
		switch(type){
			case 'replace':
				text = text.replace(new RegExp("(" + this.tag + ")", 'ig'), this.polyfill);
				break;

			case 'tag':
				text = text.replace(new RegExp(this.tag, 'g'), '');
				break;

			case 'whole':
				text = text.replace(new RegExp(this.tag + ".*?" + this.tag, 'g'), '');
				break;
		}

		cb(text);
	}
}

class TableRemover extends Remover{
	constructor(){
		super();
	}

	remove(type, text, cb){
		switch(type){
			case 'tag':
				text = text
					.replace(/<#[a-fA-F0-9]{3,6}?>/g, '') //<#000000> 꼴 제거
					.replace(/\|\|/g, '') // || 제거
					.replace(/<\|[0-9]+>/g, '') //<|숫자> 꼴 제거
					.replace(/<-[0-9]+>/g, '') //<-숫자> 꼴 제거
					.replace(/<table.*?=.*?>/g, '') //<table???=???> 꼴 제거
					.replace(/<v\|[0-9]+>/g, '') //<v|숫자>
					.replace(/<\)>/g, '') //<)> 제거
					.replace(/<\(>/g, '') //<(> 제거
					.replace(/<:>/g, '') //<:> 제거
					.replace(/<(bgcolor|rowbgcolor|width|height)=.*?>/g, '') //<???=???> 꼴 제거
					.replace(/\|.*?\|/g, ''); //캡션
		}

		cb(text);
	}
}

module.exports = {
	bold: new SimpleTagRemover("'''", "**"),
	italic: new SimpleTagRemover("''", "*"),
	underline: new SimpleTagRemover("__"),
	striken: new MultipleDefinitionRemover(new SimpleTagRemover("--", "~~"), new SimpleTagRemover("~~", "~~")),

	superscript: new SimpleTagRemover("\\^\\^"),
	subscript: new SimpleTagRemover(",,"),

	html: new BraceRemover("#!html"),
	size: new BraceRemover("\\+[0-5]"),
	color: new BraceRemover("#[a-zA-Z0-9]+"),
	nomarkup: new BraceRemover(""),

	attachment: new AttachmentRemover(),
	image: new ImageRemover(),
	namuimage: new NamuImageRemover(),

	hyperlink: new HyperlinkRemover(),

	quote: new QuoteRemover(),
	line: new SimpleTagRemover("\\[br\\]", "\n"),
	table: new TableRemover(),
	include: new IncludeRemover(),
	footnote: new FootnoteRemover()
};
<commit_msg>Fixed p1 is not defined<commit_after>"use strict";
var async = require('async');
var fixedURIencode = require('./encoder');

class Remover{
	constructor(){

	}

	remove(type, text, cb){
		cb(new Error("Undefined method called!"));
	}
}
//attachment:주소 형식의 이미지 제거
class AttachmentRemover extends Remover{
	constructor(){
		super();
		this.regex = /^attachment[ ]*:[ ]*(.*\..*)$/mg;
	}

	remove(type, text, cb){
		switch(type){
			case 'replace':
				text = text.replace(this.regex, (match, p1) => {
					return '[이미지](https://attach.namu.wiki/' + fixedURIencode(p1) + ')';
				});
				break;

			case 'tag':
				text = text.replace(this.regex, '$1');
				break;

			case 'whole':
				text = text.replace(this.regex, '');
				break;
		}

		cb(text);
	}
}

class BraceRemover extends Remover{
	constructor(tag){
		super();
		this.tag = tag;
	}

	remove(type, text, cb){
		switch(type){
			case 'tag':
				text = text.replace(new RegExp("{{{" + this.tag, 'g'), '').replace(/}}}/g, '');
				break;

			case 'whole':
				text = text.replace(new RegExp("{{{" + this.tag + "[^]*?}}}", 'g'), '');
				break;
		}

		cb(text);
	}
}

class FootnoteRemover extends Remover{
	constructor(){
		super();
		this.regex = /\[\*[^ ]*[ ]*(.+?)[^\]][\]]{1}(?!\]+)/g;
	}

	remove(type, text, cb){
		switch(type){
			case 'tag':
				text = text.replace(this.regex, '$1');
				break;

			case 'whole':
				text = text.replace(this.regex, '');
				break;
		}

		cb(text);
	}
}

class HyperlinkRemover extends Remover{
	constructor(){
		super();
		this.regex = /\[\[([^\[\]\|]*)(?:\|([^\[\]]*))?\]\]/g;
	}

	remove(param, text, cb){
		var split = param.split("|");
		var type = split[0];
		var paragraph = split[1];

		if(paragraph === 'noparagraph') text = text.replace(/\[\[#s-\d(\.\d)*(?:\|.*)?\]\]/g, '');

		switch(type){
			case 'whole':
				cb(text.replace(/\[\[[^\[\]]*\]\]/g, ''));
				return;

			case 'replace':
				text = text.replace(this.regex, function(match, p1, p2){
					if(p1.startsWith(':파일:')) p1 = p1.replace(':파일:', 'https://namu.wiki/file/');

					if(!p2){
						return p1;
					}

					if(!/^(http|https):\/\/[^]+/.match(text)){
						return p2 + "(" + p1 + ")";
					}

					return '[' + p2 + ']' + '(' + fixedURIencode(p1) + ')';
				});

			case 'former':
				text = text.replace(this.regex, function(match, p1){
					if(p1.startsWith(':파일:')) p1 = p1.replace(':파일:', 'https://namu.wiki/file/');
					return p1;
				});
				break;

			case 'latter':
				text = text.replace(this.regex, function(match, p1, p2){
					if(!p2){
						if(p1.startsWith(':파일:')) p1 = p1.replace(':파일:', 'https://namu.wiki/file/');
						return p1;
					}
					return p2;
				});
				break;

			case 'tag':
				text = text.replace(/\[\[/g, '').replace(/\]\]/g, '');
				break;
		}

		cb(text);
	}
}

//링크만 있는 이미지 제거
class ImageRemover extends Remover{
	constructor(){
		super();
		this.regex = /((https|http)?:\/\/[^ ]+\.(jpg|jpeg|png|gif))(?:\?([^ ]+))?/ig;
	}

	remove(type, text, cb){
		 switch(type){
			 case 'whole': text = text.replace(this.regex, ''); break;
			 case 'replace': text = text.replace(this.regex, (match, p1) => {
				 return '[이미지](' + fixedURIencode(p1) + ')';
			 }); break;
		 }

		 cb(text);
	}
}

class IncludeRemover extends Remover{
	constructor(){
		super();
	}

	remove(type, text, cb){
		if(type === "whole"){
			text = text.replace(/include\[\(.*\)\]/ig, '').replace(/include\(.*\)/ig, '');
		}

		cb(text);
	}
}

class LineBreakRemover extends Remover{
	constructor(){
		super();
	}

	remove(type, text, cb){
		switch(type){
			case "whole": text = text.replace(/\[br\]/g, ''); break;
			case "replace": text = text.replace(/\[br\]/g, '\n'); break;
		}

		cb(text);
	}
}

class MultipleDefinitionRemover extends Remover{
	constructor(){
		super();
		this.removers = arguments;
	}

	remove(type, text, cb){
		async.each(this.removers, function(v, callback){
			v.remove(type, text, function(res){
				text = res;
				callback();
			});
		}, function(){
			cb(text)
		});
	}
}

//[[파일:이미지]] 형식의 이미지 제거
class NamuImageRemover extends Remover{
	constructor(){
		super();
		this.regex = /\[\[파일:([^\[\]]*?)\]\]/g;
	}

	remove(type, text, cb, query){
		switch(type){
			case 'whole':
				text = text.replace(this.regex, '');
				break;

			case 'replace':
				text = text.replace(this.regex, (match, p1) => {
					return '[이미지](https://namu.wiki/file/' + fixedURIencode(p1) + ')';
				});
				break;
		}

		cb(text);
	}
}

class QuoteRemover extends Remover{
	constructor(){
		super();
	}

	remove(type, text, cb){
		switch(type){
			case 'replace':
				text = text.replace(/^>(.*)$/gm, '` $1`');
				break;

			case 'tag':
				text = text.replace(/^>/gm, '');
				break;

			case 'whole':
				text = text.replace(/^>.*$/gm, '');
				break;
		}

		cb(text);
	}
}

class SimpleTagRemover extends Remover{
	constructor(tag, polyfill){
		super();
		this.tag = tag;
		this.polyfill = polyfill;
	}

	remove(type, text, cb){
		switch(type){
			case 'replace':
				text = text.replace(new RegExp("(" + this.tag + ")", 'ig'), this.polyfill);
				break;

			case 'tag':
				text = text.replace(new RegExp(this.tag, 'g'), '');
				break;

			case 'whole':
				text = text.replace(new RegExp(this.tag + ".*?" + this.tag, 'g'), '');
				break;
		}

		cb(text);
	}
}

class TableRemover extends Remover{
	constructor(){
		super();
	}

	remove(type, text, cb){
		switch(type){
			case 'tag':
				text = text
					.replace(/<#[a-fA-F0-9]{3,6}?>/g, '') //<#000000> 꼴 제거
					.replace(/\|\|/g, '') // || 제거
					.replace(/<\|[0-9]+>/g, '') //<|숫자> 꼴 제거
					.replace(/<-[0-9]+>/g, '') //<-숫자> 꼴 제거
					.replace(/<table.*?=.*?>/g, '') //<table???=???> 꼴 제거
					.replace(/<v\|[0-9]+>/g, '') //<v|숫자>
					.replace(/<\)>/g, '') //<)> 제거
					.replace(/<\(>/g, '') //<(> 제거
					.replace(/<:>/g, '') //<:> 제거
					.replace(/<(bgcolor|rowbgcolor|width|height)=.*?>/g, '') //<???=???> 꼴 제거
					.replace(/\|.*?\|/g, ''); //캡션
		}

		cb(text);
	}
}

module.exports = {
	bold: new SimpleTagRemover("'''", "**"),
	italic: new SimpleTagRemover("''", "*"),
	underline: new SimpleTagRemover("__"),
	striken: new MultipleDefinitionRemover(new SimpleTagRemover("--", "~~"), new SimpleTagRemover("~~", "~~")),

	superscript: new SimpleTagRemover("\\^\\^"),
	subscript: new SimpleTagRemover(",,"),

	html: new BraceRemover("#!html"),
	size: new BraceRemover("\\+[0-5]"),
	color: new BraceRemover("#[a-zA-Z0-9]+"),
	nomarkup: new BraceRemover(""),

	attachment: new AttachmentRemover(),
	image: new ImageRemover(),
	namuimage: new NamuImageRemover(),

	hyperlink: new HyperlinkRemover(),

	quote: new QuoteRemover(),
	line: new SimpleTagRemover("\\[br\\]", "\n"),
	table: new TableRemover(),
	include: new IncludeRemover(),
	footnote: new FootnoteRemover()
};
<|endoftext|>


<commit_before>import {Pos, Node, inline, slice} from "../model"

import {defineStep, TransformResult, Step, Transform} from "./transform"
import {PosMap, MovedRange, ReplacedRange} from "./map"
import {copyTo, replaceHasEffect} from "./tree"

function samePathDepth(a, b) {
  for (let i = 0;; i++)
    if (i == a.path.length || i == b.path.length || a.path[i] != b.path[i])
      return i
}

function sizeBefore(node, at) {
  if (node.type.block) {
    let size = 0
    for (let i = 0; i < at; i++) size += node.content[i].size
    return size
  } else {
    return at
  }
}

export function replace(doc, from, to, root, repl) {
  let origParent = doc.path(root)
  if (repl.nodes.length && repl.nodes[0].type.type != origParent.type.contains)
    return null

  let copy = copyTo(doc, root)
  let parent = copy.path(root)
  parent.content.length = 0
  let depth = root.length

  let fromEnd = depth == from.depth
  let start = fromEnd ? from.offset : from.path[depth]
  parent.pushNodes(origParent.slice(0, start))
  if (!fromEnd) {
    parent.push(slice.before(origParent.content[start], from, depth + 1))
    ++start
  } else {
    start = parent.content.length
  }
  parent.pushNodes(repl.nodes)
  let end
  if (depth == to.depth) {
    end = to.offset
  } else {
    let n = to.path[depth]
    parent.push(slice.after(origParent.content[n], to, depth + 1))
    end = n + 1
  }
  parent.pushNodes(origParent.slice(end))

  var moved = []

  let rightEdge = start + repl.nodes.length, startLen = parent.content.length
  if (repl.nodes.length)
    mendLeft(parent, start, depth, repl.openLeft)
  mendRight(parent, rightEdge + (parent.content.length - startLen), root,
            repl.nodes.length ? repl.openRight : from.depth - depth)

  function mendLeft(node, at, depth, open) {
    if (node.type.block)
      return inline.stitchTextNodes(node, at)

    if (open == 0 || depth == from.depth) return

    let before = node.content[at - 1], after = node.content[at]
    if (before.sameMarkup(after)) {
      let oldSize = before.content.length
      before.pushFrom(after)
      node.content.splice(at, 1)
      mendLeft(before, oldSize, depth + 1, open - 1)
    }
  }

  function addMoved(start, size, dest) {
    if (start.cmp(dest))
      moved.push(new MovedRange(start, size, dest))
  }

  function mendRight(node, at, path, open) {
    let toEnd = path.length == to.depth
    let after = node.content[at], before

    let sBefore = toEnd ? sizeBefore(node, at) : at + 1
    let movedStart = toEnd ? to : to.shorten(path.length, 1)
    let movedSize = node.maxOffset - sBefore

    if (!toEnd && open > 0 && (before = node.content[at - 1]).sameMarkup(after)) {
      after.content = before.content.concat(after.content)
      node.content.splice(at - 1, 1)
      addMoved(movedStart, movedSize, new Pos(path, sBefore - 1))
      mendRight(after, before.content.length, path.concat(at - 1), open - 1)
    } else {
      if (node.type.block) inline.stitchTextNodes(node, at)
      addMoved(movedStart, movedSize, new Pos(path, sBefore))
      if (!toEnd) mendRight(after, 0, path.concat(at), 0)
    }
  }

  return {doc: copy, moved}
}

const nullRepl = {nodes: [], openLeft: 0, openRight: 0}

defineStep("replace", {
  apply(doc, step) {
    let root = step.pos.path
    if (step.from.depth < root.length || step.to.depth < root.length)
      return null
    for (let i = 0; i < root.length; i++)
      if (step.from.path[i] != root[i] || step.to.path[i] != root[i]) return null

    let result = replace(doc, step.from, step.to, root, step.param || nullRepl)
    if (!result) return null
    let {doc: out, moved} = result
    let end = moved.length ? moved[moved.length - 1].dest : step.to
    let replaced = new ReplacedRange(step.from, step.to, step.from, end, step.pos, step.pos)
    return new TransformResult(out, new PosMap(moved, [replaced]))
  },
  invert(step, oldDoc, map) {
    let depth = step.pos.depth
    let between = slice.between(oldDoc, step.from, step.to, false)
    for (let i = 0; i < depth; i++) between = between.content[0]
    return new Step("replace", step.from, map.map(step.to).pos, step.from.shorten(depth), {
      nodes: between.content,
      openLeft: step.from.depth - depth,
      openRight: step.to.depth - depth
    })
  }
})

function buildInserted(nodesLeft, source, start, end) {
  let sliced = slice.between(source, start, end, false)
  let nodesRight = []
  for (let node = sliced, i = 0; i <= start.path.length; i++, node = node.content[0])
    nodesRight.push(node)
  let same = samePathDepth(start, end)
  let searchLeft = nodesLeft.length - 1, searchRight = nodesRight.length - 1
  let result = null

  let inner = nodesRight[searchRight]
  if (inner.type.block && inner.size && nodesLeft[searchLeft].type.block) {
    result = nodesLeft[searchLeft--].copy(inner.content)
    nodesRight[--searchRight].content.shift()
  }

  for (;;) {
    let node = nodesRight[searchRight], type = node.type, matched = null
    let outside = searchRight <= same
    for (let i = searchLeft; i >= 0; i--) {
      let left = nodesLeft[i]
      if (outside ? left.type.contains == type.contains : left.type == type) {
        matched = i
        break
      }
    }
    if (matched != null) {
      if (!result) {
        result = nodesLeft[matched].copy(node.content)
        searchLeft = matched - 1
      } else {
        while (searchLeft >= matched)
          result = nodesLeft[searchLeft--].copy([result])
        result.pushFrom(node)
      }
    }
    if (matched != null || node.content.length == 0) {
      if (outside) break
      if (searchRight) nodesRight[searchRight - 1].content.shift()
    }
    searchRight--
  }

  let repl = {nodes: result ? result.content : [],
              openLeft: start.depth - searchRight,
              openRight: end.depth - searchRight}
  return {repl, depth: searchLeft + 1}
}

function moveText(tr, doc, before, after) {
  let root = samePathDepth(before, after)
  let cutAt = after.shorten(null, 1)
  while (cutAt.path.length > root && doc.path(cutAt.path).content.length == 1)
    cutAt = cutAt.shorten(null, 1)
  tr.split(cutAt, cutAt.path.length - root)
  let start = after, end = new Pos(start.path, doc.path(start.path).maxOffset)
  let parent = doc.path(start.path.slice(0, root))
  let wanted = parent.pathNodes(before.path.slice(root))
  let existing = parent.pathNodes(start.path.slice(root))
  while (wanted.length && existing.length && wanted[0].sameMarkup(existing[0])) {
    wanted.shift()
    existing.shift()
  }
  if (existing.length || wanted.length)
    tr.step("ancestor", start, end, null, {
      depth: existing.length,
      wrappers: wanted.map(n => n.copy())
    })
  for (let i = root; i < before.path.length; i++)
    tr.join(before.shorten(i, 1))
}

Transform.prototype.delete = function(from, to) {
  return this.replace(from, to)
}

Transform.prototype.replace = function(from, to, source, start, end) {
  let repl, depth, doc = this.doc, maxDepth = samePathDepth(from, to)
  if (source) {
    ;({repl, depth} = buildInserted(doc.pathNodes(from.path), source, start, end))
    while (depth > maxDepth) {
      repl = {nodes: [doc.path(from.path.slice(0, depth)).copy(repl.nodes)],
              openLeft: repl.openLeft + 1, openRight: repl.openRight + 1}
      depth--
    }
  } else {
    repl = nullRepl
    depth = maxDepth
  }
  let root = from.shorten(depth), docAfter = doc, after = to
  if (repl.nodes.length || replaceHasEffect(doc, from, to)) {
    let result = this.step("replace", from, to, root, repl)
    docAfter = result.doc
    after = result.map.map(to).pos
  }

  // If no text nodes before or after end of replacement, don't glue text
  if (!doc.path(to.path).type.block) return this
  if (!(repl.nodes.length ? source.path(end.path).type.block : doc.path(from.path).type.block)) return this

  let nodesAfter = doc.path(root.path).pathNodes(to.path.slice(depth)).slice(1)
  let nodesBefore
  if (repl.nodes.length) {
    let inserted = repl.nodes
    nodesBefore = []
    for (let i = 0; i < repl.openRight; i++) {
      let last = inserted[inserted.length - 1]
      nodesBefore.push(last)
      inserted = last.content
    }
  } else {
    nodesBefore = doc.path(root.path).pathNodes(from.path.slice(depth)).slice(1)
  }
  if (nodesAfter.length != nodesBefore.length ||
      !nodesAfter.every((n, i) => n.sameMarkup(nodesBefore[i]))) {
    let before = Pos.before(docAfter, after.shorten(null, 0))
    moveText(this, docAfter, before, after)
  }
  return this
}

Transform.prototype.insert = function(pos, nodes, end = pos) {
  if (!Array.isArray(nodes)) nodes = [nodes]
  this.step("replace", pos, end, pos.shorten(samePathDepth(pos, end), 1),
            {nodes: nodes, openLeft: 0, openRight: 0})
  return this
}

Transform.prototype.insertInline = function(pos, nodes, end = pos) {
  if (!Array.isArray(nodes)) nodes = [nodes]
  let styles = inline.inlineStylesAt(this.doc, pos)
  nodes = nodes.map(n => new Node.Inline(n.type, n.attrs, styles, n.text))
  return this.insert(pos, nodes, end)
}

Transform.prototype.insertText = function(pos, text, end = pos) {
  return this.insertInline(pos, Node.text(text), end)
}
<commit_msg>Remove end parameter to Transform.insert*<commit_after>import {Pos, Node, inline, slice} from "../model"

import {defineStep, TransformResult, Step, Transform} from "./transform"
import {PosMap, MovedRange, ReplacedRange} from "./map"
import {copyTo, replaceHasEffect} from "./tree"

function samePathDepth(a, b) {
  for (let i = 0;; i++)
    if (i == a.path.length || i == b.path.length || a.path[i] != b.path[i])
      return i
}

function sizeBefore(node, at) {
  if (node.type.block) {
    let size = 0
    for (let i = 0; i < at; i++) size += node.content[i].size
    return size
  } else {
    return at
  }
}

export function replace(doc, from, to, root, repl) {
  let origParent = doc.path(root)
  if (repl.nodes.length && repl.nodes[0].type.type != origParent.type.contains)
    return null

  let copy = copyTo(doc, root)
  let parent = copy.path(root)
  parent.content.length = 0
  let depth = root.length

  let fromEnd = depth == from.depth
  let start = fromEnd ? from.offset : from.path[depth]
  parent.pushNodes(origParent.slice(0, start))
  if (!fromEnd) {
    parent.push(slice.before(origParent.content[start], from, depth + 1))
    ++start
  } else {
    start = parent.content.length
  }
  parent.pushNodes(repl.nodes)
  let end
  if (depth == to.depth) {
    end = to.offset
  } else {
    let n = to.path[depth]
    parent.push(slice.after(origParent.content[n], to, depth + 1))
    end = n + 1
  }
  parent.pushNodes(origParent.slice(end))

  var moved = []

  let rightEdge = start + repl.nodes.length, startLen = parent.content.length
  if (repl.nodes.length)
    mendLeft(parent, start, depth, repl.openLeft)
  mendRight(parent, rightEdge + (parent.content.length - startLen), root,
            repl.nodes.length ? repl.openRight : from.depth - depth)

  function mendLeft(node, at, depth, open) {
    if (node.type.block)
      return inline.stitchTextNodes(node, at)

    if (open == 0 || depth == from.depth) return

    let before = node.content[at - 1], after = node.content[at]
    if (before.sameMarkup(after)) {
      let oldSize = before.content.length
      before.pushFrom(after)
      node.content.splice(at, 1)
      mendLeft(before, oldSize, depth + 1, open - 1)
    }
  }

  function addMoved(start, size, dest) {
    if (start.cmp(dest))
      moved.push(new MovedRange(start, size, dest))
  }

  function mendRight(node, at, path, open) {
    let toEnd = path.length == to.depth
    let after = node.content[at], before

    let sBefore = toEnd ? sizeBefore(node, at) : at + 1
    let movedStart = toEnd ? to : to.shorten(path.length, 1)
    let movedSize = node.maxOffset - sBefore

    if (!toEnd && open > 0 && (before = node.content[at - 1]).sameMarkup(after)) {
      after.content = before.content.concat(after.content)
      node.content.splice(at - 1, 1)
      addMoved(movedStart, movedSize, new Pos(path, sBefore - 1))
      mendRight(after, before.content.length, path.concat(at - 1), open - 1)
    } else {
      if (node.type.block) inline.stitchTextNodes(node, at)
      addMoved(movedStart, movedSize, new Pos(path, sBefore))
      if (!toEnd) mendRight(after, 0, path.concat(at), 0)
    }
  }

  return {doc: copy, moved}
}

const nullRepl = {nodes: [], openLeft: 0, openRight: 0}

defineStep("replace", {
  apply(doc, step) {
    let root = step.pos.path
    if (step.from.depth < root.length || step.to.depth < root.length)
      return null
    for (let i = 0; i < root.length; i++)
      if (step.from.path[i] != root[i] || step.to.path[i] != root[i]) return null

    let result = replace(doc, step.from, step.to, root, step.param || nullRepl)
    if (!result) return null
    let {doc: out, moved} = result
    let end = moved.length ? moved[moved.length - 1].dest : step.to
    let replaced = new ReplacedRange(step.from, step.to, step.from, end, step.pos, step.pos)
    return new TransformResult(out, new PosMap(moved, [replaced]))
  },
  invert(step, oldDoc, map) {
    let depth = step.pos.depth
    let between = slice.between(oldDoc, step.from, step.to, false)
    for (let i = 0; i < depth; i++) between = between.content[0]
    return new Step("replace", step.from, map.map(step.to).pos, step.from.shorten(depth), {
      nodes: between.content,
      openLeft: step.from.depth - depth,
      openRight: step.to.depth - depth
    })
  }
})

function buildInserted(nodesLeft, source, start, end) {
  let sliced = slice.between(source, start, end, false)
  let nodesRight = []
  for (let node = sliced, i = 0; i <= start.path.length; i++, node = node.content[0])
    nodesRight.push(node)
  let same = samePathDepth(start, end)
  let searchLeft = nodesLeft.length - 1, searchRight = nodesRight.length - 1
  let result = null

  let inner = nodesRight[searchRight]
  if (inner.type.block && inner.size && nodesLeft[searchLeft].type.block) {
    result = nodesLeft[searchLeft--].copy(inner.content)
    nodesRight[--searchRight].content.shift()
  }

  for (;;) {
    let node = nodesRight[searchRight], type = node.type, matched = null
    let outside = searchRight <= same
    for (let i = searchLeft; i >= 0; i--) {
      let left = nodesLeft[i]
      if (outside ? left.type.contains == type.contains : left.type == type) {
        matched = i
        break
      }
    }
    if (matched != null) {
      if (!result) {
        result = nodesLeft[matched].copy(node.content)
        searchLeft = matched - 1
      } else {
        while (searchLeft >= matched)
          result = nodesLeft[searchLeft--].copy([result])
        result.pushFrom(node)
      }
    }
    if (matched != null || node.content.length == 0) {
      if (outside) break
      if (searchRight) nodesRight[searchRight - 1].content.shift()
    }
    searchRight--
  }

  let repl = {nodes: result ? result.content : [],
              openLeft: start.depth - searchRight,
              openRight: end.depth - searchRight}
  return {repl, depth: searchLeft + 1}
}

function moveText(tr, doc, before, after) {
  let root = samePathDepth(before, after)
  let cutAt = after.shorten(null, 1)
  while (cutAt.path.length > root && doc.path(cutAt.path).content.length == 1)
    cutAt = cutAt.shorten(null, 1)
  tr.split(cutAt, cutAt.path.length - root)
  let start = after, end = new Pos(start.path, doc.path(start.path).maxOffset)
  let parent = doc.path(start.path.slice(0, root))
  let wanted = parent.pathNodes(before.path.slice(root))
  let existing = parent.pathNodes(start.path.slice(root))
  while (wanted.length && existing.length && wanted[0].sameMarkup(existing[0])) {
    wanted.shift()
    existing.shift()
  }
  if (existing.length || wanted.length)
    tr.step("ancestor", start, end, null, {
      depth: existing.length,
      wrappers: wanted.map(n => n.copy())
    })
  for (let i = root; i < before.path.length; i++)
    tr.join(before.shorten(i, 1))
}

Transform.prototype.delete = function(from, to) {
  return this.replace(from, to)
}

Transform.prototype.replace = function(from, to, source, start, end) {
  let repl, depth, doc = this.doc, maxDepth = samePathDepth(from, to)
  if (source) {
    ;({repl, depth} = buildInserted(doc.pathNodes(from.path), source, start, end))
    while (depth > maxDepth) {
      repl = {nodes: [doc.path(from.path.slice(0, depth)).copy(repl.nodes)],
              openLeft: repl.openLeft + 1, openRight: repl.openRight + 1}
      depth--
    }
  } else {
    repl = nullRepl
    depth = maxDepth
  }
  let root = from.shorten(depth), docAfter = doc, after = to
  if (repl.nodes.length || replaceHasEffect(doc, from, to)) {
    let result = this.step("replace", from, to, root, repl)
    docAfter = result.doc
    after = result.map.map(to).pos
  }

  // If no text nodes before or after end of replacement, don't glue text
  if (!doc.path(to.path).type.block) return this
  if (!(repl.nodes.length ? source.path(end.path).type.block : doc.path(from.path).type.block)) return this

  let nodesAfter = doc.path(root.path).pathNodes(to.path.slice(depth)).slice(1)
  let nodesBefore
  if (repl.nodes.length) {
    let inserted = repl.nodes
    nodesBefore = []
    for (let i = 0; i < repl.openRight; i++) {
      let last = inserted[inserted.length - 1]
      nodesBefore.push(last)
      inserted = last.content
    }
  } else {
    nodesBefore = doc.path(root.path).pathNodes(from.path.slice(depth)).slice(1)
  }
  if (nodesAfter.length != nodesBefore.length ||
      !nodesAfter.every((n, i) => n.sameMarkup(nodesBefore[i]))) {
    let before = Pos.before(docAfter, after.shorten(null, 0))
    moveText(this, docAfter, before, after)
  }
  return this
}

Transform.prototype.insert = function(pos, nodes) {
  if (!Array.isArray(nodes)) nodes = [nodes]
  this.step("replace", pos, pos, pos,
            {nodes: nodes, openLeft: 0, openRight: 0})
  return this
}

Transform.prototype.insertInline = function(pos, nodes) {
  if (!Array.isArray(nodes)) nodes = [nodes]
  let styles = inline.inlineStylesAt(this.doc, pos)
  nodes = nodes.map(n => new Node.Inline(n.type, n.attrs, styles, n.text))
  return this.insert(pos, nodes)
}

Transform.prototype.insertText = function(pos, text) {
  return this.insertInline(pos, Node.text(text))
}
<|endoftext|>


<commit_before>'use babel';

export default {
  ecmaVersion: {
    doc: 'The ECMAScript version to parse. Should be either 5, 6 or 7. Default is 6.'
  },
  libs: {
    browser: {
      doc: 'JavaScript'
    },
    jquery: {
      doc: 'JQuery'
    }
  },
  loadEagerly: {
    doc: 'loadEagerly allows you to force some files to always be loaded, it may be an array of filenames or glob patterns (i.e. foo/bar/*.js).'
  },
  dontLoad: {
    doc: 'The dontLoad option can be used to prevent Tern from loading certain files. It also takes an array of file names or glob patterns.'
  },
  plugins: {
    doc: 'Plugins used by this project. Currenty you can only activate the plugin from this view without setting up the options for it. After saving the config, plugins with default options are added to the .tern-project file. Unchecking the plugin will result in removing the plugin property entirely from the .tern-project file. Please refer to this page for detailed information for the build in server plugins.'
  }
};
<commit_msg>update tern-config-docs<commit_after>'use babel';

export default {
  ecmaVersion: {
    doc: 'The ECMAScript version to parse. Should be either 5, 6 or 7. Default is 6.'
  },
  libs: {
    browser: {
      doc: 'JavaScript'
    },
    jquery: {
      doc: 'JQuery'
    },
    underscore: {
      doc: 'underscore'
    },
    chai: {
      doc: 'chai'
    }
  },
  loadEagerly: {
    doc: 'loadEagerly allows you to force some files to always be loaded, it may be an array of filenames or glob patterns (i.e. foo/bar/*.js).'
  },
  dontLoad: {
    doc: 'The dontLoad option can be used to prevent Tern from loading certain files. It also takes an array of file names or glob patterns.'
  },
  plugins: {
    doc: 'Plugins used by this project. Currenty you can only activate the plugin from this view without setting up the options for it. After saving the config, plugins with default options are added to the .tern-project file. Unchecking the plugin will result in removing the plugin property entirely from the .tern-project file. Please refer to this page for detailed information for the build in server plugins.'
  }
};
<|endoftext|>


<commit_before>// Subscribe to Provider data
Meteor.subscribe('providers');
Meteor.subscribe('counties');

// Map handler
let map = {};

// Close Map Search
Thriver.providers.closeMapSearch = () => {
  const search = document.querySelector('.providers .providerSearch');
  if (search instanceof Element && search.classList.contains('active')) {
    search.classList.remove('active');
  }
};

// Close Map Search
Thriver.providers.openDetails = () =>
  document.getElementById('service-providers').classList.remove('full-view');

// Setting the map view to full
const fullMap = (condition) => {
  if (condition === true) {
    document.getElementById('service-providers').classList.add('full-view');
    if (map.getZoom() > 0) map.setZoom(7);
    map.setCenter(new google.maps.LatLng(44.863579, -89.574563));
  } else {
    document.getElementById('service-providers').classList.remove('full-view');
  }
};

// Hide info label on mouseout
const hideLabel = () => {
  if (map.infowindow) map.infowindow.close();
};

// Initialize Google Maps API
const initialize = () => {
  // Create SVG Marker Pin
  const createPin = (fillColor, strokeColor) => ({
    // SVG Path
    path: 'M 24,4.1C21.2,1.4,17.9,0,14,0c-3.9,0-7.2,1.4-9.9,4.1C1.4,6.9,0,10.2,0,14c0,2,0.3,3.6,0.9,4.9l10,21.2c0.3,0.6,0.7,1.1,1.3,1.4c0.6,0.3,1.2,0.5,1.9,0.5c0.7,0,1.3-0.2,1.9-0.5c0.6-0.3,1-0.8,1.3-1.4l10-21.2c0.6-1.3,0.9-2.9,0.9-4.9C28.1,10.2,26.7,6.9,24,4.1L24,4.1z M19,19c-1.4,1.4-3,2.1-5,2.1c-1.9,0-3.6-0.7-5-2.1C7.7,17.6,7,16,7,14c0-1.9,0.7-3.6,2.1-5c1.4-1.4,3-2.1,5-2.1c1.9,0,3.6,0.7,5,2.1c1.4,1.4,2.1,3,2.1,5C21.1,16,20.4,17.6,19,19L19,19z M19,19',

    // Attributes
    fillColor,
    fillOpacity: 1,
    scale: 0.9,
    strokeWeight: 2,
    strokeColor,

    // Pin Position/Offset Controls
    size: new google.maps.Size(29, 43),
    origin: new google.maps.Point(0, 0),
    anchor: new google.maps.Point(14, 43),
  });

  // Hover effects
  // NOTE: Can't use lambda expression because of `this` context
  const mouseover = function () {
    this.setIcon(createPin('#00cad9', '#004146'), this);
  };
  const mouseout = function () {
    this.setIcon(createPin('#00b7c5', '#004146'), this);
  };

  // Display info label on hover
  // NOTE: Can't use lambda expression because of `this` context
  const displayLabel = function () {
    // Close any existing infowindow
    if (map.infowindow) map.infowindow.close();

    // Create new infowindow
    map.infowindow = new google.maps.InfoWindow({
      content: `<p class="providerTitle">${this.title}</p>`,
    });

    // Open new infowindow
    map.infowindow.open(this.get('map'), this);
  };

  // Map Initialization function
  const init = () => {
    const mapElement = document.querySelector('#mapCanvas');

    // Reset map
    const handleLocationError = () => {
      document.getElementById('service-providers').classList.add('full-view');
      if (map.getZoom() > 0) map.setZoom(7);
      map.setCenter(new google.maps.LatLng(44.863579, -89.574563));
    };

    // Map options
    const options = {
      scrollwheel: false,
      zoom: 7,
      center: new google.maps.LatLng(43.1, -89.4),
      zoomControl: true,
      minZoom: 7,
      maxZoom: 16,
      streetViewControl: false,
      zoomControlOptions: {
        style: 3,
      },
    };

    // Map instance
    map = new google.maps.Map(mapElement, options);

    // County Layer
    const countyLayer = new geoXML3.parser({
      map,
      suppressInfoWindows: true,
    });
    countyLayer.parse('/packages/thriver_maps/lib/client/data/wisconsin_counties.kml');

    // Map Options
    map.set('styles', [{
      featureType: 'poi',
      elementType: 'labels',
      stylers: [{ visibility: 'off' }],
    }, {
      stylers: [
        { gamma: 0.78 },
        { lightness: 5 },
        { saturation: 22 },
      ],
    }, {
      featureType: 'water',
      stylers: [
        { hue: '#00ddff' },
        { color: '#04648d' },
        { saturation: -23 },
        { lightness: 17 },
        { gamma: 1.94 },
      ],
    }]);

    // Wait for collection to become available before acting on it
    Deps.autorun((c) => {
      // Get all providers' IDs, names, and coordinates
      const providers = Thriver.providers.collection.find({}, { name: 1, coordinates: 1 });

      // If collection not ready, try again
      if (!providers.count()) return;

      // Create map markers for each provider
      providers.forEach((provider) => {
        const marker = new google.maps.Marker({
          position: new google.maps.LatLng(provider.coordinates[0],
            provider.coordinates[1]),
          icon: createPin('#00b7c5', '#004146'),
          animation: google.maps.Animation.DROP,
          title: provider.name,
          id: provider._id,
        });

        // Add hover effect
        google.maps.event.addListener(marker, 'mouseover', mouseover);
        google.maps.event.addListener(marker, 'mouseout', mouseout);

        // Display Label
        google.maps.event.addListener(marker, 'mouseover', displayLabel);

        // Add to map
        marker.setMap(map);

        // Click Marker
        // NOTE: Can't use lambda expression because of `this` context
        marker.addListener('click', function () {
          fullMap(false);
          google.maps.event.trigger(map, 'resize');
          map.setZoom(16);
          map.panTo(marker.getPosition());

          // Show results if the result has an ID
          if (this.id) {
            Session.set('currentProvider',
              Thriver.providers.collection.findOne({ _id: this.id }));
          }
        });
      });

      // Stop
      c.stop();
      const infoWindow = new google.maps.InfoWindow({ map });

      // Geolocation
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition((position) => {
          // Get the distance from current location for each provider
          const distanceProviders = Thriver.providers.collection.find({},
            { coordinates: 1 }).map((provider) => {
              // Calculate distance
              const distance = google.maps.geometry.spherical.computeDistanceBetween(
                new google.maps.LatLng(position.coords.latitude,
                  position.coords.longitude),
                new google.maps.LatLng(provider.coordinates[0],
                  provider.coordinates[1])
              );

              return { id: provider._id, distance };
            });

          // Sort array
          distanceProviders.sort((a, b) => a.distance - b.distance);

          // Get closest provider
          const closest = Thriver.providers.collection.findOne({ _id: distanceProviders[0].id });

          // Center on it
          map.panTo(new google.maps.LatLng(
              closest.coordinates[0],
              closest.coordinates[1]
          ));

          map.setZoom(11);

          // Show results
          Session.set('currentProvider', closest);
        }, () => {
          handleLocationError(true, infoWindow, map.getCenter());
        });
      } else {
        handleLocationError(false, infoWindow, map.getCenter());
      }
    });

    // Create a WCASA map marker
    (function () {
      const marker = new google.maps.Marker({
        position: new google.maps.LatLng(43.0346679, -89.4252416),
        icon: createPin('#062131', '##062131'),
        animation: google.maps.Animation.DROP,
        title: 'WCASA',
      });

      // Display Label
      google.maps.event.addListener(marker, 'mouseover', displayLabel);
      google.maps.event.addListener(marker, 'mouseout', hideLabel);

      // Add to map
      marker.setMap(map);
    }());
  };

  // Create maps API script
  const script = document.createElement('script');
  script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp' +
    '&libraries=geometry&callback=initializeMap';
  document.body.appendChild(script);

  // Maps API will look for initialize script in global scope
  window.initializeMap = init;
};

// Helper for moving map based on provider locations
const moveMap = (county) => {
  check(county, String);

  let providers = [];
  let x = [];
  let y = [];

  // Get coordinates for all providers for that county
  providers = Thriver.providers.collection.find({ counties: { $elemMatch: { $in: [county] } } },
    { coordinates: 1, name: 1 }).map(provider =>
      // We only care about the coordinates
      ({
        coordinates: provider.coordinates,
        id: provider._id,
        name: provider.name,
      }));

  // Calculate bounding box
  // Determine lowest and highest Lat values
  providers.sort((a, b) =>
    b.coordinates[0] - a.coordinates[0]);

  x = [providers[0].coordinates[0],
    providers[providers.length - 1].coordinates[0]];

  // Determine lowest and highest Lon values
  providers.sort((a, b) =>
    b.coordinates[1] - a.coordinates[1]);

  y = [providers[0].coordinates[1],
    providers[providers.length - 1].coordinates[1]];

  // Bounds
  map.fitBounds(new google.maps.LatLngBounds(
      new google.maps.LatLng(x[1], y[1]), // Southwest
      new google.maps.LatLng(x[0], y[0])  // to Northeast
  ));

  // If there was only one result, click on it for the user
  if (providers.length === 1) {
    Session.set('currentProvider',
      Thriver.providers.collection.findOne({ _id: providers[0].id }));
  } else {
    document.getElementById('service-providers').classList.add('full-view');
    google.maps.event.trigger(map, 'resize');
  }
};

// Get county from ZIP code number
const getCounty = (zip) => {
  check(zip, String);

  let county = '';

  // Get county
  county = Thriver.providers.counties.findOne({ zips:
    // Minimongo doesn't support $eq for some reason
    { $elemMatch: { $in: [zip] } } });

  // Now get providers that support that county
  moveMap(county.name);
};

const outlineCounty = () =>
  // County Data
  new google.maps.KmlLayer({
    url: '/packages/$USER_$PACKAGENAME/lib/client/data/wisconsin_counties.kml',
    map,
  });

Template.providers.onRendered(initialize);

Template.providers.events({
  // County drop-down list
  'change #county': (event) => {
    Thriver.providers.openDetails();
    const name = event.target.value;

    check(name, String);

    moveMap(name);
    outlineCounty();

    // Close search field
    Thriver.providers.closeMapSearch();
    document.body.classList.remove('providersListOpen');
  },

  'click .mapView': (event) => {
    event.stopPropagation();
    event.preventDefault();

    document.body.classList.remove('providersListOpen');
    document.getElementById('service-providers').classList.add('full-view');

    google.maps.event.trigger(map, 'resize');

    hideLabel();
    fullMap(true);
  },

  'click .fullMap': (event) => {
    event.stopPropagation();
    event.preventDefault();

    document.body.classList.remove('providersListOpen');
    document.getElementById('service-providers').classList.add('full-view');

    google.maps.event.trigger(map, 'resize');

    hideLabel();
    fullMap(true);
  },

  // Clicking ZIP Code GO button
  'click #zip + .submit': (event) => {
    // Stop form submission
    event.preventDefault();

    getCounty(event.currentTarget.parentElement.querySelector('#zip').value);

    // Close search field
    Thriver.providers.closeMapSearch();
    Thriver.providers.openDetails();
  },

  // Reaching 5 digits
  'keyup #zip': (event) => {
    if (event.currentTarget.value.length === 5) {
      Thriver.providers.openDetails();
      getCounty(event.currentTarget.value);
      document.body.classList.remove('providersListOpen');

      // Close search field
      Thriver.providers.closeMapSearch();
      google.maps.event.trigger(map, 'resize');
    }
  },
});

/**
 * Follow Provider link to pin on map
 * @method
 *   @param {$.Event} event - jQuery event passed to handler
 */
Template.providerListViewItem.events({
  'click div.pad': (event) => {
    check(event, $.Event);

    event.stopPropagation();
    event.preventDefault();

    const data = Template.instance().data;

    // Update info section
    Session.set('currentProvider',
      Thriver.providers.collection.findOne({ _id: data._id }));

    // Update map
    map.panTo(new google.maps.LatLng(
        data.coordinates[0],
        data.coordinates[1]
    ));
    map.setZoom(11);

    // Close providers section
    document.body.classList.remove('providersListOpen');
  },
});
<commit_msg>Closes #143. Map Pin size reduced.<commit_after>// Subscribe to Provider data
Meteor.subscribe('providers');
Meteor.subscribe('counties');

// Map handler
let map = {};

// Close Map Search
Thriver.providers.closeMapSearch = () => {
  const search = document.querySelector('.providers .providerSearch');
  if (search instanceof Element && search.classList.contains('active')) {
    search.classList.remove('active');
  }
};

// Close Map Search
Thriver.providers.openDetails = () =>
  document.getElementById('service-providers').classList.remove('full-view');

// Setting the map view to full
const fullMap = (condition) => {
  if (condition === true) {
    document.getElementById('service-providers').classList.add('full-view');
    if (map.getZoom() > 0) map.setZoom(7);
    map.setCenter(new google.maps.LatLng(44.863579, -89.574563));
  } else {
    document.getElementById('service-providers').classList.remove('full-view');
  }
};

// Hide info label on mouseout
const hideLabel = () => {
  if (map.infowindow) map.infowindow.close();
};

// Initialize Google Maps API
const initialize = () => {
  // Create SVG Marker Pin
  const createPin = (fillColor, strokeColor) => ({
    // SVG Path
    path: 'M 24,4.1C21.2,1.4,17.9,0,14,0c-3.9,0-7.2,1.4-9.9,4.1C1.4,6.9,0,10.2,0,14c0,2,0.3,3.6,0.9,4.9l10,21.2c0.3,0.6,0.7,1.1,1.3,1.4c0.6,0.3,1.2,0.5,1.9,0.5c0.7,0,1.3-0.2,1.9-0.5c0.6-0.3,1-0.8,1.3-1.4l10-21.2c0.6-1.3,0.9-2.9,0.9-4.9C28.1,10.2,26.7,6.9,24,4.1L24,4.1z M19,19c-1.4,1.4-3,2.1-5,2.1c-1.9,0-3.6-0.7-5-2.1C7.7,17.6,7,16,7,14c0-1.9,0.7-3.6,2.1-5c1.4-1.4,3-2.1,5-2.1c1.9,0,3.6,0.7,5,2.1c1.4,1.4,2.1,3,2.1,5C21.1,16,20.4,17.6,19,19L19,19z M19,19',

    // Attributes
    fillColor,
    fillOpacity: 1,
    scale: 0.6,
    strokeWeight: 2,
    strokeColor,

    // Pin Position/Offset Controls
    size: new google.maps.Size(29, 43),
    origin: new google.maps.Point(0, 0),
    anchor: new google.maps.Point(14, 43),
  });

  // Hover effects
  // NOTE: Can't use lambda expression because of `this` context
  const mouseover = function () {
    this.setIcon(createPin('#00cad9', '#004146'), this);
  };
  const mouseout = function () {
    this.setIcon(createPin('#00b7c5', '#004146'), this);
  };

  // Display info label on hover
  // NOTE: Can't use lambda expression because of `this` context
  const displayLabel = function () {
    // Close any existing infowindow
    if (map.infowindow) map.infowindow.close();

    // Create new infowindow
    map.infowindow = new google.maps.InfoWindow({
      content: `<p class="providerTitle">${this.title}</p>`,
    });

    // Open new infowindow
    map.infowindow.open(this.get('map'), this);
  };

  // Map Initialization function
  const init = () => {
    const mapElement = document.querySelector('#mapCanvas');

    // Reset map
    const handleLocationError = () => {
      document.getElementById('service-providers').classList.add('full-view');
      if (map.getZoom() > 0) map.setZoom(7);
      map.setCenter(new google.maps.LatLng(44.863579, -89.574563));
    };

    // Map options
    const options = {
      scrollwheel: false,
      zoom: 7,
      center: new google.maps.LatLng(43.1, -89.4),
      zoomControl: true,
      minZoom: 7,
      maxZoom: 16,
      streetViewControl: false,
      zoomControlOptions: {
        style: 3,
      },
    };

    // Map instance
    map = new google.maps.Map(mapElement, options);

    // County Layer
    const countyLayer = new geoXML3.parser({
      map,
      suppressInfoWindows: true,
    });
    countyLayer.parse('/packages/thriver_maps/lib/client/data/wisconsin_counties.kml');

    // Map Options
    map.set('styles', [{
      featureType: 'poi',
      elementType: 'labels',
      stylers: [{ visibility: 'off' }],
    }, {
      stylers: [
        { gamma: 0.78 },
        { lightness: 5 },
        { saturation: 22 },
      ],
    }, {
      featureType: 'water',
      stylers: [
        { hue: '#00ddff' },
        { color: '#04648d' },
        { saturation: -23 },
        { lightness: 17 },
        { gamma: 1.94 },
      ],
    }]);

    // Wait for collection to become available before acting on it
    Deps.autorun((c) => {
      // Get all providers' IDs, names, and coordinates
      const providers = Thriver.providers.collection.find({}, { name: 1, coordinates: 1 });

      // If collection not ready, try again
      if (!providers.count()) return;

      // Create map markers for each provider
      providers.forEach((provider) => {
        const marker = new google.maps.Marker({
          position: new google.maps.LatLng(provider.coordinates[0],
            provider.coordinates[1]),
          icon: createPin('#00b7c5', '#004146'),
          animation: google.maps.Animation.DROP,
          title: provider.name,
          id: provider._id,
        });

        // Add hover effect
        google.maps.event.addListener(marker, 'mouseover', mouseover);
        google.maps.event.addListener(marker, 'mouseout', mouseout);

        // Display Label
        google.maps.event.addListener(marker, 'mouseover', displayLabel);

        // Add to map
        marker.setMap(map);

        // Click Marker
        // NOTE: Can't use lambda expression because of `this` context
        marker.addListener('click', function () {
          fullMap(false);
          google.maps.event.trigger(map, 'resize');
          map.setZoom(16);
          map.panTo(marker.getPosition());

          // Show results if the result has an ID
          if (this.id) {
            Session.set('currentProvider',
              Thriver.providers.collection.findOne({ _id: this.id }));
          }
        });
      });

      // Stop
      c.stop();
      const infoWindow = new google.maps.InfoWindow({ map });

      // Geolocation
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition((position) => {
          // Get the distance from current location for each provider
          const distanceProviders = Thriver.providers.collection.find({},
            { coordinates: 1 }).map((provider) => {
              // Calculate distance
              const distance = google.maps.geometry.spherical.computeDistanceBetween(
                new google.maps.LatLng(position.coords.latitude,
                  position.coords.longitude),
                new google.maps.LatLng(provider.coordinates[0],
                  provider.coordinates[1])
              );

              return { id: provider._id, distance };
            });

          // Sort array
          distanceProviders.sort((a, b) => a.distance - b.distance);

          // Get closest provider
          const closest = Thriver.providers.collection.findOne({ _id: distanceProviders[0].id });

          // Center on it
          map.panTo(new google.maps.LatLng(
              closest.coordinates[0],
              closest.coordinates[1]
          ));

          map.setZoom(11);

          // Show results
          Session.set('currentProvider', closest);
        }, () => {
          handleLocationError(true, infoWindow, map.getCenter());
        });
      } else {
        handleLocationError(false, infoWindow, map.getCenter());
      }
    });

    // Create a WCASA map marker
    (function () {
      const marker = new google.maps.Marker({
        position: new google.maps.LatLng(43.0346679, -89.4252416),
        icon: createPin('#062131', '##062131'),
        animation: google.maps.Animation.DROP,
        title: 'WCASA',
      });

      // Display Label
      google.maps.event.addListener(marker, 'mouseover', displayLabel);
      google.maps.event.addListener(marker, 'mouseout', hideLabel);

      // Add to map
      marker.setMap(map);
    }());
  };

  // Create maps API script
  const script = document.createElement('script');
  script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp' +
    '&libraries=geometry&callback=initializeMap';
  document.body.appendChild(script);

  // Maps API will look for initialize script in global scope
  window.initializeMap = init;
};

// Helper for moving map based on provider locations
const moveMap = (county) => {
  check(county, String);

  let providers = [];
  let x = [];
  let y = [];

  // Get coordinates for all providers for that county
  providers = Thriver.providers.collection.find({ counties: { $elemMatch: { $in: [county] } } },
    { coordinates: 1, name: 1 }).map(provider =>
      // We only care about the coordinates
      ({
        coordinates: provider.coordinates,
        id: provider._id,
        name: provider.name,
      }));

  // Calculate bounding box
  // Determine lowest and highest Lat values
  providers.sort((a, b) =>
    b.coordinates[0] - a.coordinates[0]);

  x = [providers[0].coordinates[0],
    providers[providers.length - 1].coordinates[0]];

  // Determine lowest and highest Lon values
  providers.sort((a, b) =>
    b.coordinates[1] - a.coordinates[1]);

  y = [providers[0].coordinates[1],
    providers[providers.length - 1].coordinates[1]];

  // Bounds
  map.fitBounds(new google.maps.LatLngBounds(
      new google.maps.LatLng(x[1], y[1]), // Southwest
      new google.maps.LatLng(x[0], y[0])  // to Northeast
  ));

  // If there was only one result, click on it for the user
  if (providers.length === 1) {
    Session.set('currentProvider',
      Thriver.providers.collection.findOne({ _id: providers[0].id }));
  } else {
    document.getElementById('service-providers').classList.add('full-view');
    google.maps.event.trigger(map, 'resize');
  }
};

// Get county from ZIP code number
const getCounty = (zip) => {
  check(zip, String);

  let county = '';

  // Get county
  county = Thriver.providers.counties.findOne({ zips:
    // Minimongo doesn't support $eq for some reason
    { $elemMatch: { $in: [zip] } } });

  // Now get providers that support that county
  moveMap(county.name);
};

const outlineCounty = () =>
  // County Data
  new google.maps.KmlLayer({
    url: '/packages/$USER_$PACKAGENAME/lib/client/data/wisconsin_counties.kml',
    map,
  });

Template.providers.onRendered(initialize);

Template.providers.events({
  // County drop-down list
  'change #county': (event) => {
    Thriver.providers.openDetails();
    const name = event.target.value;

    check(name, String);

    moveMap(name);
    outlineCounty();

    // Close search field
    Thriver.providers.closeMapSearch();
    document.body.classList.remove('providersListOpen');
  },

  'click .mapView': (event) => {
    event.stopPropagation();
    event.preventDefault();

    document.body.classList.remove('providersListOpen');
    document.getElementById('service-providers').classList.add('full-view');

    google.maps.event.trigger(map, 'resize');

    hideLabel();
    fullMap(true);
  },

  'click .fullMap': (event) => {
    event.stopPropagation();
    event.preventDefault();

    document.body.classList.remove('providersListOpen');
    document.getElementById('service-providers').classList.add('full-view');

    google.maps.event.trigger(map, 'resize');

    hideLabel();
    fullMap(true);
  },

  // Clicking ZIP Code GO button
  'click #zip + .submit': (event) => {
    // Stop form submission
    event.preventDefault();

    getCounty(event.currentTarget.parentElement.querySelector('#zip').value);

    // Close search field
    Thriver.providers.closeMapSearch();
    Thriver.providers.openDetails();
  },

  // Reaching 5 digits
  'keyup #zip': (event) => {
    if (event.currentTarget.value.length === 5) {
      Thriver.providers.openDetails();
      getCounty(event.currentTarget.value);
      document.body.classList.remove('providersListOpen');

      // Close search field
      Thriver.providers.closeMapSearch();
      google.maps.event.trigger(map, 'resize');
    }
  },
});

/**
 * Follow Provider link to pin on map
 * @method
 *   @param {$.Event} event - jQuery event passed to handler
 */
Template.providerListViewItem.events({
  'click div.pad': (event) => {
    check(event, $.Event);

    event.stopPropagation();
    event.preventDefault();

    const data = Template.instance().data;

    // Update info section
    Session.set('currentProvider',
      Thriver.providers.collection.findOne({ _id: data._id }));

    // Update map
    map.panTo(new google.maps.LatLng(
        data.coordinates[0],
        data.coordinates[1]
    ));
    map.setZoom(11);

    // Close providers section
    document.body.classList.remove('providersListOpen');
  },
});
<|endoftext|>